2026-07-01 18:16:18 +08:00

756 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <QDateTime>
#include <QMessageBox>
#include <QResizeEvent>
#include <QPixmap>
#include <QListWidgetItem>
#include <QGraphicsView>
#include <QScreen>
#include <QApplication>
#include <QMenu>
#include <QAction>
#include <QFileDialog>
#include <QStandardPaths>
#include <QThread>
#include <QProgressDialog>
#include <QDir>
#include <QTimer>
#include <QBrush>
#include <QFrame>
#include <QListView>
#include <QColor>
#include <QPalette>
#include <QProgressBar>
#include <QPushButton>
#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<QLabel*>())
{
label->setPalette(pal);
label->setStyleSheet(QStringLiteral("color: rgb(221, 225, 233); background-color: transparent; font-size: 18px;"));
}
for (QPushButton* button : dialog.findChildren<QPushButton*>())
{
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<QProgressBar*>())
{
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<ResultItem*>(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<int>(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<int>(i), ResultItem::DisplayMode::Distance);
}
}
else if (result.boxes.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<int>(i + 1))
.arg(static_cast<int>(result.boxes.size()));
upsertResultItem(row++, head, static_cast<int>(i), ResultItem::DisplayMode::Precision);
}
}
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<double>(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);
}