#include "mainwindow.h" #include "ui_mainwindow.h" #include "Presenter/Inc/DroneScrewCtrlPresenter.h" #include "Presenter/Inc/ConfigManager.h" #include "dialogalgoarg.h" #include "dialogcamerasetting.h" #include "resultitem.h" #include "Version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DeviceStatusWidget.h" #include "DetectLogHelper.h" #include "AboutDialog.h" #include "ResultListLayoutHelper.h" #include "IVrUtils.h" namespace { constexpr int kThemeWindowR = 25; constexpr int kThemeWindowG = 26; constexpr int kThemeWindowB = 28; constexpr int kThemeTextR = 221; constexpr int kThemeTextG = 225; constexpr int kThemeTextB = 233; const QString& panelStyle() { static const QString kStyle = QStringLiteral( "background-color: rgb(37, 38, 42);" "border: 1px solid rgb(51, 53, 60);" "color: rgb(239, 241, 245);"); return kStyle; } QString iconButtonStyle(const QString& imagePath) { return QStringLiteral( "QPushButton { image: url(%1); background-color: rgb(38, 40, 47); border: none; }" "QPushButton:hover { background-color: rgb(47, 49, 56); }" "QPushButton:pressed { background-color: rgb(31, 33, 38); }" "QPushButton:disabled { background-color: rgb(38, 40, 47); }") .arg(imagePath); } QString windowButtonStyle(const QString& imagePath) { return QStringLiteral( "QPushButton { image: url(%1); background-color: transparent; border: none; }" "QPushButton:hover { background-color: rgb(47, 49, 56); }" "QPushButton:pressed { background-color: rgb(31, 33, 38); }") .arg(imagePath); } void applyPullingDialogTheme(QProgressDialog& dialog) { const QColor windowColor(kThemeWindowR, kThemeWindowG, kThemeWindowB); const QColor textColor(kThemeTextR, kThemeTextG, kThemeTextB); QPalette pal = dialog.palette(); pal.setColor(QPalette::Window, windowColor); pal.setColor(QPalette::WindowText, textColor); pal.setColor(QPalette::Text, textColor); pal.setColor(QPalette::ButtonText, textColor); dialog.setPalette(pal); dialog.setAutoFillBackground(true); dialog.setStyleSheet(QStringLiteral( "QProgressDialog { background-color: rgb(25, 26, 28); color: rgb(221, 225, 233); }" "QProgressDialog * { color: rgb(221, 225, 233); }" "QProgressDialog QLabel { color: rgb(221, 225, 233); background-color: transparent; font-size: 18px; }" "QProgressDialog QPushButton { background-color: rgb(47, 48, 52); color: rgb(221, 225, 233); border: 1px solid rgb(75, 78, 86); padding: 5px 16px; }" "QProgressDialog QPushButton:hover { background-color: rgb(58, 60, 66); }" "QProgressDialog QProgressBar { background-color: rgb(47, 48, 52); border: 1px solid rgb(75, 78, 86); color: rgb(221, 225, 233); text-align: center; }" "QProgressDialog QProgressBar::chunk { background-color: rgb(72, 116, 184); }")); for (QLabel* label : dialog.findChildren()) { label->setPalette(pal); label->setStyleSheet(QStringLiteral("color: rgb(221, 225, 233); background-color: transparent; font-size: 18px;")); } for (QPushButton* button : dialog.findChildren()) { button->setPalette(pal); button->setStyleSheet(QStringLiteral( "QPushButton { background-color: rgb(47, 48, 52); color: rgb(221, 225, 233); border: 1px solid rgb(75, 78, 86); padding: 5px 16px; }" "QPushButton:hover { background-color: rgb(58, 60, 66); }")); } for (QProgressBar* bar : dialog.findChildren()) { bar->setPalette(pal); bar->setStyleSheet(QStringLiteral( "QProgressBar { background-color: rgb(47, 48, 52); border: 1px solid rgb(75, 78, 86); color: rgb(221, 225, 233); text-align: center; }" "QProgressBar::chunk { background-color: rgb(72, 116, 184); }")); } } } MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // 设置窗口图标 #ifdef _WIN32 this->setWindowIcon(QIcon(":/dronescrewctrl/resource/logo.ico")); #else this->setWindowIcon(QIcon(":/dronescrewctrl/resource/logo.png")); #endif // 状态栏:右侧显示版本信息 QFont statusFont = statusBar()->font(); statusFont.setPointSize(12); statusBar()->setFont(statusFont); statusBar()->setStyleSheet("QStatusBar { color: rgb(221, 225, 233); padding: 20px; }" "QStatusBar QLabel { color: rgb(221, 225, 233); }"); QString versionWithBuildTime = QString("%1_%2%3%4%5%6%7") .arg(GetDroneScrewCtrlFullVersion()) .arg(YEAR) .arg(MONTH, 2, 10, QChar('0')) .arg(DAY, 2, 10, QChar('0')) .arg(HOUR, 2, 10, QChar('0')) .arg(MINUTE, 2, 10, QChar('0')) .arg(SECOND, 2, 10, QChar('0')); QLabel* buildLabel = new QLabel(versionWithBuildTime); buildLabel->setStyleSheet("QLabel { color: rgb(221, 225, 233); font-size: 20px; margin-right: 16px; }"); buildLabel->setCursor(Qt::PointingHandCursor); buildLabel->installEventFilter(this); statusBar()->addPermanentWidget(buildLabel); m_versionLabel = buildLabel; initUi(); // 无边框 + 最大化 setWindowFlags(Qt::FramelessWindowHint); this->showMaximized(); // 第二相机画面初始隐藏 ui->detect_image_2->setVisible(false); // 初始化 detect_image_2 的 GraphicsScene QGraphicsScene* scene2 = new QGraphicsScene(this); ui->detect_image_2->setScene(scene2); // 设备状态组件放入 frame_dev m_deviceStatusWidget = new DeviceStatusWidget(); QVBoxLayout* frameDevLayout = new QVBoxLayout(ui->frame_dev); frameDevLayout->setContentsMargins(0, 0, 0, 0); frameDevLayout->addWidget(m_deviceStatusWidget); // 本应用只有一个设备:隐藏机械臂、单设备占满、关闭相机切换点击 m_deviceStatusWidget->setRobotVisible(false); m_deviceStatusWidget->setCameraCount(1); m_deviceStatusWidget->setCameraClickable(false); m_deviceStatusWidget->setCamera1Name(QStringLiteral("设备")); m_deviceStatusWidget->updateCamera1Status(false); // 日志辅助类 m_logHelper = new DetectLogHelper(ui->detect_log, this); auto cfg = ConfigManager::Instance().GetConfig(); m_maxResultItems = cfg.display.maxResultListItems; m_pPresenter = new DroneScrewCtrlPresenter(this); m_pPresenter->SetStatusReceiver(this); // 信号连接要在 InitApp 之前,避免漏掉启动时的首次连接/状态事件 connect(m_pPresenter, &DroneScrewCtrlPresenter::statusMessage, this, [this](const QString& s) { appendLog(s); }); connect(m_pPresenter, &DroneScrewCtrlPresenter::serverConnectionChanged, this, &MainWindow::OnServerConnectionChanged); connect(m_pPresenter, &DroneScrewCtrlPresenter::serverInfoReceived, this, &MainWindow::onServerInfoReceived); m_pPresenter->ApplyConfig(cfg); // 软件启动默认进入空闲态;不在主界面构造阶段向 Server 下发控制命令。 applyUiState(UiState::Idle); // 网络发现放到窗口完成构造后执行,避免主界面显示前被 UDP/ZMQ 超时阻塞。 QTimer::singleShot(100, this, [this]() { if (m_pPresenter) m_pPresenter->InitApp(); }); } MainWindow::~MainWindow() { if (m_pPresenter) { m_pPresenter->DeinitApp(); delete m_pPresenter; m_pPresenter = nullptr; } delete ui; } void MainWindow::initUi() { setWindowTitle(QStringLiteral("无人机螺杆控制端")); if (ui->centralwidget) ui->centralwidget->setStyleSheet(QStringLiteral("background-color: rgb(25, 26, 28);")); if (ui->groupBox) ui->groupBox->setStyleSheet(QStringLiteral("background-color: rgb(38, 40, 47); border: none;")); if (ui->label) ui->label->setStyleSheet(QStringLiteral("color: rgb(239, 241, 245); background-color: transparent;")); if (ui->detect_log) ui->detect_log->setStyleSheet(panelStyle()); if (ui->detect_image) ui->detect_image->setStyleSheet(panelStyle()); if (ui->detect_image_2) ui->detect_image_2->setStyleSheet(panelStyle()); if (ui->frame_dev) ui->frame_dev->setStyleSheet(QStringLiteral("background-color: transparent; border: none;")); if (ui->btn_camera) ui->btn_camera->setStyleSheet(iconButtonStyle(QStringLiteral(":/common/resource/config_camera.png"))); if (ui->btn_algo_config) ui->btn_algo_config->setStyleSheet(iconButtonStyle(QStringLiteral(":/common/resource/config_algo.png"))); if (ui->btn_test) ui->btn_test->setStyleSheet(iconButtonStyle(QStringLiteral(":/common/resource/config_data_test.png"))); if (ui->btn_close) ui->btn_close->setStyleSheet(windowButtonStyle(QStringLiteral(":/common/resource/close.png"))); if (ui->btn_hide) ui->btn_hide->setStyleSheet(windowButtonStyle(QStringLiteral(":/common/resource/hide.png"))); // 主画面:QGraphicsScene + PixmapItem if (ui->detect_image) { m_pScene = new QGraphicsScene(this); m_pPixmapItem = new QGraphicsPixmapItem(); m_pScene->addItem(m_pPixmapItem); ui->detect_image->setScene(m_pScene); ui->detect_image->setRenderHint(QPainter::SmoothPixmapTransform); ui->detect_image->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->detect_image->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } // 隐藏工作状态标签 if (ui->label_work) ui->label_work->hide(); if (ui->detect_result_list) { ui->detect_result_list->setStyleSheet(QStringLiteral( "QListWidget { background-color: rgb(37, 38, 42); border: 1px solid rgb(51, 53, 60); color: rgb(239, 241, 245); }" "QListWidget::item { background-color: rgb(37, 38, 42); border: none; }" "QListWidget::item:selected { background-color: rgb(37, 38, 42); }")); ui->detect_result_list->setFrameShape(QFrame::NoFrame); ui->detect_result_list->setAutoFillBackground(true); ui->detect_result_list->setSpacing(0); ui->detect_result_list->setViewMode(QListView::ListMode); ui->detect_result_list->setResizeMode(QListView::Adjust); ui->detect_result_list->setFlow(QListView::TopToBottom); ui->detect_result_list->setWrapping(false); ui->detect_result_list->setGridSize(QSize()); ui->detect_result_list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->detect_result_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } } void MainWindow::OnRtspFrame(const QImage& frame) { m_currentImage = frame; renderDisplay(); } void MainWindow::OnDetectionResult(const CtrlDetectionFrame& result) { if (!ui->detect_result_list) return; auto upsertResultItem = [this, &result](int row, const QString& head, int targetIndex, ResultItem::DisplayMode mode) { QListWidgetItem* item = nullptr; ResultItem* w = nullptr; if (row >= 0 && row < ui->detect_result_list->count()) { item = ui->detect_result_list->item(row); w = qobject_cast(ui->detect_result_list->itemWidget(item)); } if (!item) { item = new QListWidgetItem(); ui->detect_result_list->addItem(item); } item->setForeground(QBrush(Qt::white)); item->setBackground(QBrush(QColor(37, 38, 42))); item->setData(Qt::UserRole, head); if (!w) { w = new ResultItem(); ui->detect_result_list->setItemWidget(item, w); } w->setResultData(targetIndex, result, mode); QSize hint = w->sizeHint(); if (hint.height() < w->minimumHeight()) hint.setHeight(w->minimumHeight()); const int listWidth = ui->detect_result_list->viewport() ? ui->detect_result_list->viewport()->width() : ui->detect_result_list->width(); if (listWidth > 0) hint.setWidth(listWidth); w->setMinimumHeight(hint.height()); item->setSizeHint(hint); }; auto trimResultItems = [this](int keepCount) { while (ui->detect_result_list->count() > keepCount) { QListWidgetItem* item = ui->detect_result_list->takeItem(keepCount); delete item; } }; int row = 0; if (m_resultDisplayMode == ResultDisplayMode::Distance) { const QString head = QStringLiteral("帧#%1 测距 %2") .arg(result.frameId) .arg(static_cast(result.distances.size())); if (result.distances.empty()) { upsertResultItem(row++, head, -1, ResultItem::DisplayMode::Distance); } else { for (size_t i = 0; i < result.distances.size(); ++i) upsertResultItem(row++, head, static_cast(i), ResultItem::DisplayMode::Distance); } } else if (result.boxes.empty() && result.distances.empty()) { const QString head = QStringLiteral("帧#%1 精准目标 0") .arg(result.frameId); upsertResultItem(row++, head, -1, ResultItem::DisplayMode::Precision); } else { for (size_t i = 0; i < result.boxes.size(); ++i) { const QString head = QStringLiteral("帧#%1 目标 %2/%3") .arg(result.frameId) .arg(static_cast(i + 1)) .arg(static_cast(result.boxes.size())); upsertResultItem(row++, head, static_cast(i), ResultItem::DisplayMode::Precision); } for (size_t i = 0; i < result.distances.size(); ++i) { const QString head = QStringLiteral("帧#%1 杆间距 %2/%3") .arg(result.frameId) .arg(static_cast(i + 1)) .arg(static_cast(result.distances.size())); upsertResultItem(row++, head, static_cast(i), ResultItem::DisplayMode::PrecisionDistance); } } trimResultItems(row); ui->detect_result_list->scrollToBottom(); clampResultList(); } void MainWindow::OnWorkStatusChanged(CtrlWorkStatus status) { if (!ui->label_work) return; switch (status) { case CtrlWorkStatus::Idle: ui->label_work->setText(QStringLiteral("空闲")); break; case CtrlWorkStatus::Connecting: ui->label_work->setText(QStringLiteral("连接中")); break; case CtrlWorkStatus::Streaming: ui->label_work->setText(QStringLiteral("拉流中")); break; case CtrlWorkStatus::Detecting: ui->label_work->setText(QStringLiteral("检测中")); break; case CtrlWorkStatus::Error: ui->label_work->setText(QStringLiteral("错误")); break; } } void MainWindow::OnServerConnectionChanged(bool connected) { if (ui->label_work) ui->label_work->setText(connected ? QStringLiteral("已连接") : QStringLiteral("未连接")); // 单设备状态联动 if (m_deviceStatusWidget) m_deviceStatusWidget->updateCamera1Status(connected); } void MainWindow::onServerInfoReceived(const WDRemoteServerInfo& info) { // 更新相机参数到 UI(如果有相机设置对话框,这里可以刷新) // 更新算法参数到 dialogalgoarg(延迟到对话框打开时读取,或缓存到成员变量) m_cachedServerInfo = info; appendLog(QStringLiteral("已从服务器获取参数:曝光=%1, 增益=%2, score=%3") .arg(info.leftExposure, 0, 'f', 1) .arg(info.leftGain, 0, 'f', 1) .arg(static_cast(info.algoScore), 0, 'f', 2)); } void MainWindow::OnStatusMessage(const QString& msg) { appendLog(msg); } void MainWindow::appendLog(const QString& msg) { if (m_logHelper) m_logHelper->appendLog(msg); statusBar()->showMessage(msg, 5000); } void MainWindow::clampResultList() { if (!ui->detect_result_list) return; while (ui->detect_result_list->count() > m_maxResultItems) { QListWidgetItem* it = ui->detect_result_list->takeItem(0); delete it; } } void MainWindow::clearResultList() { if (ui->detect_result_list) ui->detect_result_list->clear(); } void MainWindow::resizeEvent(QResizeEvent* event) { QMainWindow::resizeEvent(event); renderDisplay(); } void MainWindow::renderDisplay() { if (!ui->detect_image || !m_pPixmapItem || !m_pScene) return; if (m_currentImage.isNull()) return; m_pPixmapItem->setPixmap(QPixmap::fromImage(m_currentImage)); const QSize imageSize = m_currentImage.size(); const QSize viewSize = ui->detect_image->viewport() ? ui->detect_image->viewport()->size() : ui->detect_image->size(); if (imageSize != m_lastRenderImageSize || viewSize != m_lastRenderViewSize) { m_pScene->setSceneRect(m_pPixmapItem->boundingRect()); ui->detect_image->fitInView(m_pPixmapItem, Qt::KeepAspectRatio); m_lastRenderImageSize = imageSize; m_lastRenderViewSize = viewSize; } } void MainWindow::applyUiState(UiState s) { m_uiState = s; // 用 image 而非 background-image:图片会缩放填满按钮内容区, // 背景/边框与顶部工具按钮(btn_camera 等)保持一致 static const QString kStartImg = iconButtonStyle(QStringLiteral(":/common/resource/start_image.png")); static const QString kStopAll = iconButtonStyle(QStringLiteral(":/common/resource/stop_all.png")); static const QString kStartDetect = iconButtonStyle(QStringLiteral(":/common/resource/start_detect.png")); switch (s) { case UiState::Idle: // 空闲:主按钮=开始实时显示;检测按钮可用(支持不传图直接检测) ui->btn_start->setStyleSheet(kStartImg); ui->btn_start->setToolTip(QStringLiteral("开始实时显示")); ui->btn_detect_start->setStyleSheet(kStartDetect); ui->btn_detect_start->setToolTip(QStringLiteral("开始检测")); ui->btn_detect_start->setVisible(true); break; case UiState::Live: // 实时显示中:主按钮=停止;检测按钮可用 ui->btn_start->setStyleSheet(kStopAll); ui->btn_start->setToolTip(QStringLiteral("停止")); ui->btn_detect_start->setStyleSheet(kStartDetect); ui->btn_detect_start->setToolTip(QStringLiteral("开始检测")); ui->btn_detect_start->setVisible(true); break; case UiState::Detect: // 检测中:主按钮=停止;检测按钮隐藏(避免重复触发) ui->btn_start->setStyleSheet(kStopAll); ui->btn_start->setToolTip(QStringLiteral("停止")); ui->btn_detect_start->setVisible(false); break; } } void MainWindow::on_btn_start_clicked() { // 主开关:空闲 → 开始实时推流并拉流显示;运行中(实时/检测) → 全部停止 if (m_uiState == UiState::Idle) { m_resultDisplayMode = ResultDisplayMode::Distance; clearResultList(); appendLog(QStringLiteral("正在启动距离检测并打开拉流...")); ui->btn_start->setEnabled(false); ui->btn_detect_start->setEnabled(false); QProgressDialog pullingDialog(QStringLiteral("正在拉流,请稍候..."), QString(), 0, 0, this); pullingDialog.setWindowTitle(QStringLiteral("提示")); pullingDialog.setCancelButton(nullptr); pullingDialog.setWindowModality(Qt::ApplicationModal); pullingDialog.setMinimumDuration(0); pullingDialog.setAutoClose(false); pullingDialog.setAutoReset(false); pullingDialog.setStyleSheet(QStringLiteral( "QProgressDialog { background-color: rgb(25, 26, 28); color: rgb(221, 225, 233); }" "QProgressDialog QLabel { color: rgb(221, 225, 233); background-color: transparent; font-size: 18px; }" "QProgressDialog QProgressBar { background-color: rgb(47, 48, 52); border: 1px solid rgb(75, 78, 86); color: rgb(221, 225, 233); }")); applyPullingDialogTheme(pullingDialog); pullingDialog.show(); applyPullingDialogTheme(pullingDialog); QApplication::processEvents(); const bool ok = m_pPresenter && m_pPresenter->StartLiveStream(); pullingDialog.close(); QApplication::processEvents(); if (ok) { appendLog(QStringLiteral("已请求 Server 开始实时推流")); applyUiState(UiState::Live); } else { appendLog(QStringLiteral("启动距离检测/拉流失败")); applyUiState(UiState::Idle); } ui->btn_start->setEnabled(true); ui->btn_detect_start->setEnabled(true); } else { appendLog(QStringLiteral("正在请求 Server 停止...")); ui->btn_start->setEnabled(false); ui->btn_detect_start->setEnabled(false); QApplication::processEvents(); const bool ok = m_pPresenter && m_pPresenter->StopAll(); if (ok) { appendLog(QStringLiteral("已请求 Server 停止")); applyUiState(UiState::Idle); } else { appendLog(QStringLiteral("Server 停止失败,状态未切换")); } ui->btn_start->setEnabled(true); ui->btn_detect_start->setEnabled(true); } } void MainWindow::on_btn_detect_start_clicked() { // 空闲或实时显示态均可开始检测(可不传图直接检测);检测中则忽略。 // StartDetection 内部会先停实时推流,从空闲进入也安全。 if (m_uiState == UiState::Detect) return; m_resultDisplayMode = ResultDisplayMode::Precision; clearResultList(); appendLog(QStringLiteral("正在启动精准检测...")); ui->btn_start->setEnabled(false); ui->btn_detect_start->setEnabled(false); QApplication::processEvents(); const UiState previousState = m_uiState; const bool ok = m_pPresenter && m_pPresenter->StartDetection(); if (ok) { appendLog(QStringLiteral("已请求 Server 开始检测")); applyUiState(UiState::Detect); } else { appendLog(QStringLiteral("启动精准检测失败")); applyUiState(previousState); } ui->btn_start->setEnabled(true); ui->btn_detect_start->setEnabled(m_uiState != UiState::Detect); } void MainWindow::on_btn_close_clicked() { close(); } void MainWindow::on_btn_hide_clicked() { showMinimized(); } void MainWindow::on_btn_camera_clicked() { // 「相机」按钮 → 相机设置:Server IP + 曝光时间(μs) + 增益 auto cfg0 = ConfigManager::Instance().GetConfig(); DialogCameraSetting dlg(this); // 优先使用从 Server 读取的参数 DroneScrewCameraParams camParams = cfg0.camera; if (m_cachedServerInfo.ok) { camParams.exposure = m_cachedServerInfo.leftExposure; camParams.gain = m_cachedServerInfo.leftGain; } dlg.SetParams(QString::fromStdString(cfg0.server.serverIp), camParams); // 存盘 + 下发:IP 变化才重连,曝光/增益始终实时下发 auto applyFn = [this](const QString& ip, const DroneScrewCameraParams& cam) { auto cfg = ConfigManager::Instance().GetConfig(); const bool ipChanged = (ip.toStdString() != cfg.server.serverIp); cfg.server.serverIp = ip.toStdString(); cfg.camera = cam; ConfigManager::Instance().SetConfig(cfg); ConfigManager::Instance().Save(); if (m_pPresenter) { if (ipChanged) m_pPresenter->ApplyConfig(cfg); // IP 改变才重建连接 m_pPresenter->SetServerExposure(cam.exposure); m_pPresenter->SetServerGain(cam.gain); } appendLog(QStringLiteral("已应用相机设置: IP=%1 曝光=%2μs 增益=%3") .arg(ip).arg(cam.exposure).arg(cam.gain)); }; // 「应用」:实时下发但不关闭对话框,便于一边看实时图一边调曝光 connect(&dlg, &DialogCameraSetting::applyRequested, this, applyFn); if (dlg.exec() == QDialog::Accepted) applyFn(dlg.GetServerIp(), dlg.GetCameraParams()); } void MainWindow::on_btn_algo_config_clicked() { auto cfg = ConfigManager::Instance().GetConfig(); DialogAlgoArg dlg(this); // 优先使用从 Server 读取的参数,没有则用本地配置 if (m_cachedServerInfo.ok) { DroneScrewAlgoUiParams serverParams; serverParams.scoreThreshold = m_cachedServerInfo.algoScore; serverParams.nmsThreshold = m_cachedServerInfo.algoNms; serverParams.inputWidth = m_cachedServerInfo.algoWidth; serverParams.inputHeight = m_cachedServerInfo.algoHeight; serverParams.modelPath = m_cachedServerInfo.algoModel; serverParams.modelType = m_cachedServerInfo.algoModelType; dlg.SetParams(serverParams); } else { dlg.SetParams(cfg.algo); } dlg.SetDisplayOptions(cfg.display); if (dlg.exec() == QDialog::Accepted) { cfg.algo = dlg.GetParams(); cfg.display = dlg.GetDisplayOptions(); ConfigManager::Instance().SetConfig(cfg); ConfigManager::Instance().Save(); m_maxResultItems = cfg.display.maxResultListItems; if (m_pPresenter) m_pPresenter->UpdateDisplayOptions(cfg.display); if (m_pPresenter) m_pPresenter->PushAlgoParams(cfg.algo); appendLog(QStringLiteral("已应用算法参数")); } } void MainWindow::on_btn_test_clicked() { m_resultDisplayMode = ResultDisplayMode::Precision; clearResultList(); // 「测试」按钮 → 单次检测请求 if (m_pPresenter && m_pPresenter->TriggerSingleDetection()) appendLog(QStringLiteral("已发送单次检测请求")); } // ======================== 页面布局 ======================== int MainWindow::getAvailableHeight() { QScreen* screen = QApplication::primaryScreen(); if (screen) { QRect availableGeometry = screen->availableGeometry(); return availableGeometry.height() - 30; } return 1050; } void MainWindow::adjustLayoutForCameraCount(int cameraCount) { if (cameraCount <= 1) setupSingleCameraLayout(); else setupMultiCameraLayout(); } void MainWindow::setupSingleCameraLayout() { int availableHeight = getAvailableHeight(); ResultListLayoutHelper::setupSingleCameraMode( ui->detect_result_list, ui->detect_image, ui->detect_image_2, ui->frame_dev, ui->detect_log, availableHeight); m_deviceStatusWidget->setCameraCount(1); appendLog(QStringLiteral("页面布局已调整为单相机模式")); } void MainWindow::setupMultiCameraLayout() { int availableHeight = getAvailableHeight(); ResultListLayoutHelper::setupMultiCameraMode( ui->detect_result_list, ui->detect_image, ui->detect_image_2, ui->frame_dev, ui->detect_log, availableHeight); m_deviceStatusWidget->setCameraCount(2); appendLog(QStringLiteral("页面布局已调整为双相机模式")); } // ======================== 事件过滤器 ======================== bool MainWindow::eventFilter(QObject* obj, QEvent* event) { if (obj == m_versionLabel && event->type() == QEvent::MouseButtonPress) { QString versionStr = m_versionLabel->text(); AboutDialog dlg(DRONESCREWCTRL_APP_NAME, versionStr, QString(), this); dlg.exec(); return true; } return QMainWindow::eventFilter(obj, event); }