764 lines
28 KiB
C++
Raw Normal View History

2026-06-26 17:55:15 +08:00
#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>
2026-06-26 17:55:15 +08:00
#include <QDir>
#include <QTimer>
#include <QBrush>
#include <QFrame>
#include <QListView>
#include <QColor>
#include <QPalette>
#include <QProgressBar>
#include <QPushButton>
2026-06-26 17:55:15 +08:00
#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); }"));
}
}
}
2026-06-26 17:55:15 +08:00
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; }");
2026-06-26 17:55:15 +08:00
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")));
2026-06-26 17:55:15 +08:00
// 主画面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);
}
2026-06-26 17:55:15 +08:00
}
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);
}
}
2026-07-02 10:57:32 +08:00
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<int>(i + 1))
.arg(static_cast<int>(result.boxes.size()));
upsertResultItem(row++, head, static_cast<int>(i), ResultItem::DisplayMode::Precision);
}
2026-07-02 10:57:32 +08:00
for (size_t i = 0; i < result.distances.size(); ++i)
{
const QString head = QStringLiteral("帧#%1 杆间距 %2/%3")
.arg(result.frameId)
.arg(static_cast<int>(i + 1))
.arg(static_cast<int>(result.distances.size()));
upsertResultItem(row++, head, static_cast<int>(i), ResultItem::DisplayMode::PrecisionDistance);
}
}
2026-06-26 17:55:15 +08:00
trimResultItems(row);
ui->detect_result_list->scrollToBottom();
2026-06-26 17:55:15 +08:00
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));
2026-06-26 17:55:15 +08:00
}
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);
2026-06-26 17:55:15 +08:00
}
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();
}
2026-06-26 17:55:15 +08:00
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"));
2026-06-26 17:55:15 +08:00
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)
2026-06-26 17:55:15 +08:00
{
appendLog(QStringLiteral("已请求 Server 开始实时推流"));
applyUiState(UiState::Live);
}
else
{
appendLog(QStringLiteral("启动距离检测/拉流失败"));
applyUiState(UiState::Idle);
}
ui->btn_start->setEnabled(true);
ui->btn_detect_start->setEnabled(true);
2026-06-26 17:55:15 +08:00
}
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);
2026-06-26 17:55:15 +08:00
}
}
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)
2026-06-26 17:55:15 +08:00
{
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);
2026-06-26 17:55:15 +08:00
}
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);
}
2026-07-01 18:16:18 +08:00
dlg.SetDisplayOptions(cfg.display);
2026-06-26 17:55:15 +08:00
if (dlg.exec() == QDialog::Accepted)
{
cfg.algo = dlg.GetParams();
2026-07-01 18:16:18 +08:00
cfg.display = dlg.GetDisplayOptions();
2026-06-26 17:55:15 +08:00
ConfigManager::Instance().SetConfig(cfg);
ConfigManager::Instance().Save();
2026-07-01 18:16:18 +08:00
m_maxResultItems = cfg.display.maxResultListItems;
if (m_pPresenter) m_pPresenter->UpdateDisplayOptions(cfg.display);
2026-06-26 17:55:15 +08:00
if (m_pPresenter) m_pPresenter->PushAlgoParams(cfg.algo);
appendLog(QStringLiteral("已应用算法参数"));
}
}
void MainWindow::on_btn_test_clicked()
{
m_resultDisplayMode = ResultDisplayMode::Precision;
clearResultList();
2026-06-26 17:55:15 +08:00
// 「测试」按钮 → 单次检测请求
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);
}