1339 lines
41 KiB
C++
1339 lines
41 KiB
C++
#include "mainwindow.h"
|
||
#include "ui_mainwindow.h"
|
||
|
||
#include <QAbstractItemView>
|
||
#include <QApplication>
|
||
#include <QBrush>
|
||
#include <QCloseEvent>
|
||
#include <QComboBox>
|
||
#include <QDateTime>
|
||
#include <QDir>
|
||
#include <QFileDialog>
|
||
#include <QHBoxLayout>
|
||
#include <QIcon>
|
||
#include <QLabel>
|
||
#include <QLineEdit>
|
||
#include <QListWidgetItem>
|
||
#include <QMetaObject>
|
||
#include <QPushButton>
|
||
#include <QResizeEvent>
|
||
#include <QScroller>
|
||
#include <QScrollerProperties>
|
||
#include <QSignalBlocker>
|
||
#include <QStandardPaths>
|
||
#include <QThread>
|
||
#include <QTimer>
|
||
#include <QVariant>
|
||
#include <QVBoxLayout>
|
||
|
||
#include <algorithm>
|
||
#include <thread>
|
||
|
||
#include "IVrUtils.h"
|
||
#include "IVrConfig.h"
|
||
#include "PathManager.h"
|
||
#include "dialogcontrolpassword.h"
|
||
#include "SingleTargetResultWidget.h"
|
||
#include "StyledMessageBox.h"
|
||
#include "Version.h"
|
||
#include "VrDateUtils.h"
|
||
#include "VrLog.h"
|
||
|
||
namespace {
|
||
|
||
void EnableComboBoxTouchScroll(QComboBox* combo)
|
||
{
|
||
if (!combo) {
|
||
return;
|
||
}
|
||
|
||
// 触摸屏下拉一次显示有限项,其余靠滑动和滚动条选择。
|
||
combo->setMaxVisibleItems(8);
|
||
QAbstractItemView* view = combo->view();
|
||
if (!view) {
|
||
return;
|
||
}
|
||
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||
view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||
view->setTextElideMode(Qt::ElideNone);
|
||
QWidget* viewport = view->viewport();
|
||
viewport->setAttribute(Qt::WA_AcceptTouchEvents, true);
|
||
QScroller::grabGesture(viewport, QScroller::LeftMouseButtonGesture);
|
||
QScroller* scroller = QScroller::scroller(viewport);
|
||
if (scroller) {
|
||
QScrollerProperties properties = scroller->scrollerProperties();
|
||
properties.setScrollMetric(
|
||
QScrollerProperties::VerticalOvershootPolicy,
|
||
QVariant(static_cast<int>(QScrollerProperties::OvershootAlwaysOff)));
|
||
scroller->setScrollerProperties(properties);
|
||
}
|
||
}
|
||
|
||
QString GuideStateName(ParkingGuideState state)
|
||
{
|
||
switch (state) {
|
||
case ParkingGuideState::DockingStarted:
|
||
return QStringLiteral("泊位引导启动");
|
||
case ParkingGuideState::Capturing:
|
||
return QStringLiteral("捕获");
|
||
case ParkingGuideState::Tracking:
|
||
return QStringLiteral("跟踪");
|
||
case ParkingGuideState::ApproachRate:
|
||
return QStringLiteral("接近率");
|
||
case ParkingGuideState::CenterLineAligned:
|
||
return QStringLiteral("对准中心线");
|
||
case ParkingGuideState::Slow:
|
||
return QStringLiteral("慢速");
|
||
case ParkingGuideState::AzimuthGuidance:
|
||
return QStringLiteral("方位引导");
|
||
case ParkingGuideState::StopPositionReached:
|
||
return QStringLiteral("到达停止位置");
|
||
case ParkingGuideState::DockingCompleted:
|
||
return QStringLiteral("泊位引导完成");
|
||
case ParkingGuideState::Overshot:
|
||
return QStringLiteral("越位");
|
||
case ParkingGuideState::StoppedShort:
|
||
return QStringLiteral("提前停止");
|
||
case ParkingGuideState::Waiting:
|
||
return QStringLiteral("等待");
|
||
case ParkingGuideState::SlowBadWeather:
|
||
return QStringLiteral("异常慢速-恶劣天气");
|
||
case ParkingGuideState::SlowAircraftLost:
|
||
return QStringLiteral("异常慢速-飞机丢失");
|
||
case ParkingGuideState::AircraftVerificationFailed:
|
||
return QStringLiteral("飞机验证失败");
|
||
case ParkingGuideState::GateBlocked:
|
||
return QStringLiteral("机位受阻");
|
||
case ParkingGuideState::ViewBlocked:
|
||
return QStringLiteral("视野受阻");
|
||
case ParkingGuideState::SbuStop:
|
||
return QStringLiteral("SBU停止");
|
||
case ParkingGuideState::TooFast:
|
||
return QStringLiteral("速度过快");
|
||
case ParkingGuideState::EmergencyStop:
|
||
return QStringLiteral("紧急停止");
|
||
case ParkingGuideState::ChocksOn:
|
||
return QStringLiteral("挡轮挡");
|
||
case ParkingGuideState::SystemError:
|
||
return QStringLiteral("系统错误");
|
||
case ParkingGuideState::SystemFailure:
|
||
return QStringLiteral("系统故障");
|
||
case ParkingGuideState::PowerFailure:
|
||
return QStringLiteral("电源故障");
|
||
case ParkingGuideState::Unknown:
|
||
default:
|
||
return QStringLiteral("未知");
|
||
}
|
||
}
|
||
|
||
QString FormatGuideState(const ParkingSpaceGuideInfo& info)
|
||
{
|
||
const ParkingGuideState state = ParkingGuideStateFromCode(info.guideStateCode);
|
||
const int displayCode = state == ParkingGuideState::Unknown
|
||
? info.guideStateCode
|
||
: static_cast<int>(state);
|
||
QString text = QStringLiteral("%1 %2").arg(displayCode).arg(GuideStateName(state));
|
||
|
||
const QString guideText = info.guideText.trimmed();
|
||
if (!guideText.isEmpty()) {
|
||
text += QStringLiteral(" / %1").arg(guideText);
|
||
}
|
||
return text;
|
||
}
|
||
|
||
QString FormatNumber(double value, const QString& unit, int precision = 2)
|
||
{
|
||
return QStringLiteral("%1%2").arg(value, 0, 'f', precision).arg(unit);
|
||
}
|
||
|
||
bool IsKnownAircraftModel(const QString& modelType)
|
||
{
|
||
const QString normalized = modelType.trimmed().toUpper();
|
||
return !normalized.isEmpty() &&
|
||
normalized != QStringLiteral("未知") &&
|
||
normalized != QStringLiteral("请选择机型");
|
||
}
|
||
|
||
QString FormatModelVerification(const ParkingSpaceGuideInfo& info)
|
||
{
|
||
const QString expected = info.expectedModelType.trimmed().toUpper();
|
||
const QString recognized = info.modelType.trimmed().toUpper();
|
||
if (!IsKnownAircraftModel(recognized)) {
|
||
return QStringLiteral("未知");
|
||
}
|
||
if (!IsKnownAircraftModel(expected)) {
|
||
return QStringLiteral("实测(%1)").arg(recognized);
|
||
}
|
||
if (info.modelMatched) {
|
||
return QStringLiteral("匹配(%1)").arg(recognized);
|
||
}
|
||
if (recognized.compare(expected, Qt::CaseInsensitive) != 0) {
|
||
return QStringLiteral("不匹配(%1)").arg(recognized);
|
||
}
|
||
return QStringLiteral("未知");
|
||
}
|
||
|
||
void ConfigureResultWidget(SingleTargetResultWidget* resultWidget)
|
||
{
|
||
if (!resultWidget) {
|
||
return;
|
||
}
|
||
|
||
// Keep this presentation local to ParkingSpaceGuide; UICommon defaults are shared by other apps.
|
||
auto* mainLayout = qobject_cast<QVBoxLayout*>(resultWidget->layout());
|
||
if (mainLayout) {
|
||
mainLayout->setContentsMargins(96, 12, 96, 12);
|
||
}
|
||
if (mainLayout && mainLayout->count() > 0) {
|
||
auto* headerLayout = qobject_cast<QHBoxLayout*>(mainLayout->itemAt(0)->layout());
|
||
if (headerLayout && headerLayout->count() == 2) {
|
||
headerLayout->setSpacing(12);
|
||
headerLayout->setStretch(0, 0);
|
||
headerLayout->setStretch(1, 0);
|
||
headerLayout->addStretch(1);
|
||
}
|
||
}
|
||
|
||
resultWidget->setStyleSheet(
|
||
resultWidget->styleSheet()
|
||
+ QStringLiteral(
|
||
"#singleTargetTitle { font-size: 42px; }"
|
||
"#singleTargetStatus { font-size: 48px; font-weight: 700; padding: 0; }"
|
||
"#singleTargetEmpty { font-size: 42px; }"
|
||
"#singleTargetFieldName { font-size: 38px; }"
|
||
"#singleTargetFieldValue { font-size: 40px; }"));
|
||
|
||
auto* statusLabel = resultWidget->findChild<QLabel*>(QStringLiteral("singleTargetStatus"));
|
||
if (statusLabel) {
|
||
statusLabel->setStyleSheet(QStringLiteral("background-color: transparent;"));
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
|
||
MainWindow::MainWindow(QWidget* parent)
|
||
: MainWindow(ParkingSpaceGuideSimulationOptions(), parent)
|
||
{
|
||
}
|
||
|
||
MainWindow::MainWindow(const ParkingSpaceGuideSimulationOptions& simulationOptions,
|
||
QWidget* parent)
|
||
: QMainWindow(parent)
|
||
, ui(new Ui::MainWindow)
|
||
, m_simulationOptions(simulationOptions)
|
||
{
|
||
ui->setupUi(this);
|
||
EnableComboBoxTouchScroll(ui->combo_model);
|
||
setWindowTitle(m_simulationOptions.enabled
|
||
? QStringLiteral("停机引导(仿真)")
|
||
: QStringLiteral("停机引导"));
|
||
if (ui->label) {
|
||
ui->label->setText(QStringLiteral("停机引导"));
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
setWindowIcon(QIcon(":/common/resource/logo.ico"));
|
||
#else
|
||
setWindowIcon(QIcon(":/common/resource/logo.png"));
|
||
#endif
|
||
|
||
QFont statusFont = statusBar()->font();
|
||
statusFont.setPointSize(12);
|
||
statusBar()->setFont(statusFont);
|
||
statusBar()->setStyleSheet("QStatusBar { color: rgb(239, 241, 245); padding: 20px; }");
|
||
|
||
const QString versionWithBuildTime = QString("%1_%2%3%4%5%6%7")
|
||
.arg(GetParkingSpaceGuideFullVersion())
|
||
.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'));
|
||
|
||
m_versionLabel = new QLabel(versionWithBuildTime);
|
||
m_versionLabel->setStyleSheet("color: rgb(239, 241, 245); font-size: 20px; margin-right: 16px;");
|
||
m_versionLabel->setCursor(Qt::PointingHandCursor);
|
||
m_versionLabel->installEventFilter(this);
|
||
statusBar()->addPermanentWidget(m_versionLabel);
|
||
|
||
setWindowFlags(Qt::FramelessWindowHint);
|
||
showMaximized();
|
||
|
||
if (ui->label_work) {
|
||
ui->label_work->setCursor(Qt::PointingHandCursor);
|
||
ui->label_work->installEventFilter(this);
|
||
updateWorkModeLabel();
|
||
}
|
||
|
||
m_logHelper = new DetectLogHelper(ui->detect_log, this);
|
||
|
||
qRegisterMetaType<DetectionResult>("DetectionResult");
|
||
qRegisterMetaType<WorkStatus>("WorkStatus");
|
||
qRegisterMetaType<ParkingSpaceGuidePosition>("ParkingSpaceGuidePosition");
|
||
|
||
connect(this, &MainWindow::workStatusUpdateRequested,
|
||
this, &MainWindow::updateWorkStatusLabel,
|
||
Qt::QueuedConnection);
|
||
setupContextMenu();
|
||
updateStatusLog(tr("设备初始化开始"));
|
||
Init();
|
||
}
|
||
|
||
MainWindow::~MainWindow()
|
||
{
|
||
if (m_presenter) {
|
||
m_presenter->RequestShutdown();
|
||
}
|
||
if (m_initThread.joinable()) {
|
||
m_initThread.join();
|
||
}
|
||
if (m_testThread.joinable()) {
|
||
m_testThread.join();
|
||
}
|
||
|
||
if (m_presenter) {
|
||
m_presenter->DeinitApp();
|
||
m_presenter->SetStatusCallback(static_cast<IYParkingSpaceGuideStatus*>(nullptr));
|
||
delete m_presenter;
|
||
m_presenter = nullptr;
|
||
}
|
||
|
||
delete ui;
|
||
}
|
||
|
||
void MainWindow::closeEvent(QCloseEvent* event)
|
||
{
|
||
if (m_shutdownComplete) {
|
||
event->accept();
|
||
return;
|
||
}
|
||
|
||
event->ignore();
|
||
if (m_closing.exchange(true)) {
|
||
return;
|
||
}
|
||
|
||
QApplication::setQuitOnLastWindowClosed(false);
|
||
setEnabled(false);
|
||
hide();
|
||
if (m_presenter) {
|
||
LOG_INFO("[ParkingSpaceGuide][Shutdown] MainWindow close requested\n");
|
||
m_presenter->RequestShutdown();
|
||
}
|
||
QTimer::singleShot(0, this, &MainWindow::continueShutdown);
|
||
}
|
||
|
||
void MainWindow::continueShutdown()
|
||
{
|
||
if (m_initRunning.load() || m_testRunning.load()) {
|
||
QTimer::singleShot(20, this, &MainWindow::continueShutdown);
|
||
return;
|
||
}
|
||
|
||
LOG_INFO("[ParkingSpaceGuide][Shutdown] MainWindow worker threads complete\n");
|
||
if (m_initThread.joinable()) {
|
||
m_initThread.join();
|
||
}
|
||
if (m_testThread.joinable()) {
|
||
m_testThread.join();
|
||
}
|
||
if (m_presenter) {
|
||
LOG_INFO("[ParkingSpaceGuide][Shutdown] MainWindow DeinitApp begin\n");
|
||
m_presenter->DeinitApp();
|
||
m_presenter->SetStatusCallback(static_cast<IYParkingSpaceGuideStatus*>(nullptr));
|
||
LOG_INFO("[ParkingSpaceGuide][Shutdown] MainWindow DeinitApp complete\n");
|
||
}
|
||
|
||
m_shutdownComplete = true;
|
||
QApplication::quit();
|
||
}
|
||
|
||
bool MainWindow::eventFilter(QObject* obj, QEvent* event)
|
||
{
|
||
if (obj == m_versionLabel && event->type() == QEvent::MouseButtonPress) {
|
||
const QString algoVersion = m_presenter ? m_presenter->GetAlgoVersion() : QString();
|
||
AboutDialog dlg(PARKINGSPACEGUIDE_APP_NAME, m_versionLabel->text(), algoVersion, this);
|
||
dlg.exec();
|
||
return true;
|
||
}
|
||
if (obj == ui->label_work && event->type() == QEvent::MouseButtonPress) {
|
||
if (!m_presenter || safetyControlsLocked() || m_initRunning.load()) {
|
||
return true;
|
||
}
|
||
// 开始引导后禁止切换正常/维护模式。
|
||
if (m_presenter->IsDetectionRunning()) {
|
||
updateStatusLog(tr("停机引导运行中,禁止切换工作模式"));
|
||
return true;
|
||
}
|
||
const bool nextMaintenance = !m_presenter->IsMaintenanceMode();
|
||
if (!promptControlPassword(nextMaintenance
|
||
? tr("进入维护模式")
|
||
: tr("退出维护模式"))) {
|
||
return true;
|
||
}
|
||
if (m_presenter->SetMaintenanceMode(nextMaintenance)) {
|
||
updateWorkModeLabel();
|
||
updateStatusLog(nextMaintenance
|
||
? tr("已进入维护模式")
|
||
: tr("已退出维护模式"));
|
||
}
|
||
return true;
|
||
}
|
||
|
||
return QMainWindow::eventFilter(obj, event);
|
||
}
|
||
|
||
void MainWindow::updateStatusLog(const QString& message)
|
||
{
|
||
if (QThread::currentThread() != thread()) {
|
||
QMetaObject::invokeMethod(this, [this, message]() {
|
||
updateStatusLog(message);
|
||
}, Qt::QueuedConnection);
|
||
return;
|
||
}
|
||
|
||
if (m_logHelper) {
|
||
m_logHelper->appendLog(message);
|
||
}
|
||
}
|
||
|
||
void MainWindow::clearDetectionLog()
|
||
{
|
||
if (m_logHelper) {
|
||
m_logHelper->clearLog();
|
||
}
|
||
}
|
||
|
||
void MainWindow::Init()
|
||
{
|
||
m_presenter = new ParkingSpaceGuidePresenter(m_simulationOptions);
|
||
m_presenter->SetStatusCallback<IYParkingSpaceGuideStatus>(this);
|
||
connect(m_presenter,
|
||
&ParkingSpaceGuidePresenter::SelectedModelChanged,
|
||
this,
|
||
[this](const QString& modelType) {
|
||
setAircraftModelSelection(modelType);
|
||
},
|
||
Qt::QueuedConnection);
|
||
|
||
m_deviceStatusWidget = new DeviceStatusWidget(ui->frame_dev);
|
||
m_deviceStatusWidget->setCameraCount(2, true);
|
||
m_deviceStatusWidget->setCamera1Name(tr("相机"));
|
||
m_deviceStatusWidget->setCamera2Name(tr("雷达"));
|
||
m_deviceStatusWidget->setRobotVisible(false);
|
||
connect(m_deviceStatusWidget, SIGNAL(cameraClicked(int)), this, SLOT(onCameraClicked(int)));
|
||
|
||
auto* frameDevLayout = new QVBoxLayout(ui->frame_dev);
|
||
frameDevLayout->setContentsMargins(0, 0, 0, 0);
|
||
frameDevLayout->addWidget(m_deviceStatusWidget);
|
||
|
||
SingleTargetResultWidget::setupListWidget(ui->detect_result_list, QSize(520, 420));
|
||
ensureResultWidget();
|
||
updateSystemStatusLabel();
|
||
|
||
setButtonsEnabled(false);
|
||
|
||
m_initRunning.store(true);
|
||
m_initThread = std::thread([this]() {
|
||
updateStatusLog(tr("系统初始化中..."));
|
||
|
||
const int result = m_presenter->Init();
|
||
if (m_closing.load()) {
|
||
m_initRunning.store(false);
|
||
return;
|
||
}
|
||
if (result != 0) {
|
||
updateStatusLog(tr("初始化失败,错误码:%1").arg(result));
|
||
} else {
|
||
updateStatusLog(tr("初始化完成"));
|
||
QMetaObject::invokeMethod(this, [this]() {
|
||
populateAircraftModels();
|
||
addDetectionResult(DetectionResult());
|
||
updateWorkModeLabel();
|
||
}, Qt::QueuedConnection);
|
||
if (m_simulationOptions.enabled) {
|
||
updateStatusLog(tr("仿真数据已全部加载到内存,请选择机型并点击开始进行回放"));
|
||
} else {
|
||
updateStatusLog(tr("请选择机型并点击开始"));
|
||
}
|
||
}
|
||
m_initRunning.store(false);
|
||
// 初始化线程结束后再按实际引导状态刷新按钮,避免停止键一直被关掉
|
||
if (!m_closing.load()) {
|
||
QMetaObject::invokeMethod(this, [this]() {
|
||
if (m_closing.load()) {
|
||
return;
|
||
}
|
||
setButtonsEnabled(true);
|
||
}, Qt::QueuedConnection);
|
||
}
|
||
});
|
||
}
|
||
|
||
void MainWindow::ensureResultWidget()
|
||
{
|
||
if (m_resultWidget && m_resultItem) {
|
||
return;
|
||
}
|
||
|
||
m_resultWidget = new SingleTargetResultWidget();
|
||
m_resultWidget->setAutoFillBackground(true);
|
||
m_resultItem = new QListWidgetItem();
|
||
m_resultItem->setBackground(QBrush(Qt::transparent));
|
||
m_resultItem->setFlags(m_resultItem->flags() &
|
||
~Qt::ItemIsDragEnabled &
|
||
~Qt::ItemIsDropEnabled);
|
||
ui->detect_result_list->addItem(m_resultItem);
|
||
ui->detect_result_list->setItemWidget(m_resultItem, m_resultWidget);
|
||
ConfigureResultWidget(m_resultWidget);
|
||
updateResultItemSize();
|
||
addDetectionResult(DetectionResult());
|
||
}
|
||
|
||
void MainWindow::updateResultItemSize()
|
||
{
|
||
if (!m_resultItem || !ui || !ui->detect_result_list) {
|
||
return;
|
||
}
|
||
|
||
const int itemWidth = (std::max)(
|
||
360,
|
||
ui->detect_result_list->viewport()->width() - 8);
|
||
const int itemHeight = (std::max)(
|
||
360,
|
||
ui->detect_result_list->viewport()->height());
|
||
const QSize itemSize(itemWidth, itemHeight);
|
||
if (m_resultItem->sizeHint() != itemSize) {
|
||
m_resultItem->setSizeHint(itemSize);
|
||
SingleTargetResultWidget::setupListWidget(
|
||
ui->detect_result_list,
|
||
itemSize);
|
||
}
|
||
}
|
||
|
||
void MainWindow::addDetectionResult(const DetectionResult& result)
|
||
{
|
||
ensureResultWidget();
|
||
|
||
ParkingSpaceGuideInfo displayInfo;
|
||
if (result.parkingSpaceInfoList.empty()) {
|
||
displayInfo.targetId = currentTargetId();
|
||
displayInfo.parkId = currentParkId();
|
||
displayInfo.expectedModelType.clear();
|
||
displayInfo.modelType = QStringLiteral("未知");
|
||
displayInfo.guideStateCode = static_cast<int>(ParkingGuideState::Waiting);
|
||
displayInfo.guideText = QStringLiteral("WAIT");
|
||
} else {
|
||
displayInfo = result.parkingSpaceInfoList.front();
|
||
}
|
||
|
||
const ParkingSpaceGuideInfo& info = displayInfo;
|
||
const ParkingGuideState guideState = ParkingGuideStateFromCode(info.guideStateCode);
|
||
m_resultWidget->setTitle(tr("当前状态"));
|
||
m_resultWidget->setStatus(
|
||
QStringLiteral("%1(%2)")
|
||
.arg(GuideStateName(guideState))
|
||
.arg(info.guideStateCode),
|
||
info.hasException || guideState == ParkingGuideState::EmergencyStop);
|
||
QLabel* statusLabel = m_resultWidget->findChild<QLabel*>(
|
||
QStringLiteral("singleTargetStatus"));
|
||
if (statusLabel) {
|
||
statusLabel->setStyleSheet(
|
||
QStringLiteral("background-color: transparent;"));
|
||
}
|
||
m_resultWidget->setFields({
|
||
{tr("目标编号"), info.targetId},
|
||
{tr("停机位"), info.parkId},
|
||
{tr("机型校验"), FormatModelVerification(info)},
|
||
{tr("轮挡区域人员"), info.personInChockRegion
|
||
? tr("已检测") : tr("未检测")},
|
||
{tr("区域人数"), QString::number(info.personCount)},
|
||
{tr("引导状态"), FormatGuideState(info)},
|
||
{tr("到停止线"), FormatNumber(info.distance, tr("毫米"), 0)},
|
||
{tr("横向偏移"), FormatNumber(info.lateralOffset, tr("毫米"), 0)},
|
||
{tr("偏转角"), FormatNumber(info.angle, tr("度"))},
|
||
{tr("速度"), FormatNumber(info.aircraftSpeed, tr("米/秒"), 2)}
|
||
});
|
||
}
|
||
|
||
void MainWindow::OnStatusUpdate(const std::string& statusMessage)
|
||
{
|
||
updateStatusLog(QString::fromStdString(statusMessage));
|
||
}
|
||
|
||
void MainWindow::OnDetectionResult(const DetectionResult& result)
|
||
{
|
||
const ParkingGuideState guideState =
|
||
result.parkingSpaceInfoList.empty()
|
||
? ParkingGuideState::Unknown
|
||
: ParkingGuideStateFromCode(
|
||
result.parkingSpaceInfoList.front().guideStateCode);
|
||
const bool preserveStateTransition =
|
||
result.imageIsRoi ||
|
||
guideState == ParkingGuideState::StopPositionReached ||
|
||
guideState == ParkingGuideState::DockingCompleted ||
|
||
guideState == ParkingGuideState::ChocksOn;
|
||
if (preserveStateTransition) {
|
||
if (QThread::currentThread() == thread()) {
|
||
updateDetectionResultDisplay(result);
|
||
} else {
|
||
QMetaObject::invokeMethod(this, [this, result]() {
|
||
updateDetectionResultDisplay(result);
|
||
}, Qt::QueuedConnection);
|
||
}
|
||
return;
|
||
}
|
||
|
||
bool queueUpdate = false;
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_pendingResultMutex);
|
||
m_pendingDetectionResult = result;
|
||
if (!m_detectionResultUpdateQueued) {
|
||
m_detectionResultUpdateQueued = true;
|
||
queueUpdate = true;
|
||
}
|
||
}
|
||
if (!queueUpdate) {
|
||
return;
|
||
}
|
||
|
||
QMetaObject::invokeMethod(this, [this]() {
|
||
DetectionResult latestResult;
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_pendingResultMutex);
|
||
latestResult = m_pendingDetectionResult;
|
||
m_detectionResultUpdateQueued = false;
|
||
}
|
||
updateDetectionResultDisplay(latestResult);
|
||
}, Qt::QueuedConnection);
|
||
}
|
||
|
||
void MainWindow::OnPreviewFrame(const QImage& image)
|
||
{
|
||
if (image.isNull() || !ui_dialogAlgoArg) {
|
||
return;
|
||
}
|
||
if (QThread::currentThread() == thread()) {
|
||
ui_dialogAlgoArg->OnPreviewFrame(image);
|
||
} else {
|
||
QMetaObject::invokeMethod(this, [this, image]() {
|
||
if (ui_dialogAlgoArg) {
|
||
ui_dialogAlgoArg->OnPreviewFrame(image);
|
||
}
|
||
}, Qt::QueuedConnection);
|
||
}
|
||
}
|
||
|
||
void MainWindow::resizeEvent(QResizeEvent* event)
|
||
{
|
||
QMainWindow::resizeEvent(event);
|
||
updateResultItemSize();
|
||
}
|
||
|
||
void MainWindow::OnCamera1StatusChanged(bool isConnected)
|
||
{
|
||
if (QThread::currentThread() != thread()) {
|
||
QMetaObject::invokeMethod(this, [this, isConnected]() {
|
||
OnCamera1StatusChanged(isConnected);
|
||
}, Qt::QueuedConnection);
|
||
return;
|
||
}
|
||
|
||
if (m_deviceStatusWidget) {
|
||
m_deviceStatusWidget->updateCamera1Status(isConnected);
|
||
}
|
||
}
|
||
|
||
void MainWindow::OnCamera2StatusChanged(bool isConnected)
|
||
{
|
||
if (QThread::currentThread() != thread()) {
|
||
QMetaObject::invokeMethod(this, [this, isConnected]() {
|
||
OnCamera2StatusChanged(isConnected);
|
||
}, Qt::QueuedConnection);
|
||
return;
|
||
}
|
||
|
||
if (m_deviceStatusWidget) {
|
||
m_deviceStatusWidget->updateCamera2Status(isConnected);
|
||
}
|
||
}
|
||
|
||
void MainWindow::OnRobotConnectionChanged(bool isConnected)
|
||
{
|
||
Q_UNUSED(isConnected);
|
||
}
|
||
|
||
void MainWindow::OnSerialConnectionChanged(bool isConnected)
|
||
{
|
||
if (QThread::currentThread() != thread()) {
|
||
QMetaObject::invokeMethod(this, [this, isConnected]() {
|
||
OnSerialConnectionChanged(isConnected);
|
||
}, Qt::QueuedConnection);
|
||
return;
|
||
}
|
||
|
||
if (m_deviceStatusWidget) {
|
||
m_deviceStatusWidget->updateSerialStatus(isConnected);
|
||
}
|
||
}
|
||
|
||
void MainWindow::OnCameraCountChanged(int cameraCount)
|
||
{
|
||
Q_UNUSED(cameraCount);
|
||
if (QThread::currentThread() != thread()) {
|
||
QMetaObject::invokeMethod(this, [this]() {
|
||
OnCameraCountChanged(2);
|
||
}, Qt::QueuedConnection);
|
||
return;
|
||
}
|
||
|
||
if (m_deviceStatusWidget) {
|
||
m_deviceStatusWidget->setCameraCount(2, true);
|
||
m_deviceStatusWidget->setCamera1Name(tr("相机"));
|
||
m_deviceStatusWidget->setCamera2Name(tr("雷达"));
|
||
}
|
||
}
|
||
|
||
void MainWindow::OnWorkStatusChanged(WorkStatus status)
|
||
{
|
||
emit workStatusUpdateRequested(status);
|
||
}
|
||
|
||
void MainWindow::updateWorkStatusLabel(WorkStatus status)
|
||
{
|
||
m_emergencyStopControlsLocked =
|
||
status == WorkStatus::EmergencyStop;
|
||
if (ui_dialogAlgoArg) {
|
||
const bool detectionRunning =
|
||
m_presenter && m_presenter->IsDetectionRunning();
|
||
ui_dialogAlgoArg->setEnabled(
|
||
!safetyControlsLocked() && !detectionRunning);
|
||
}
|
||
|
||
if (status == WorkStatus::Working || status == WorkStatus::Detecting) {
|
||
clearDetectionLog();
|
||
}
|
||
|
||
const QString statusText = QString::fromStdString(WorkStatusToString(status));
|
||
updateStatusLog(tr("工作状态:%1").arg(statusText));
|
||
updateWorkModeLabel();
|
||
updateSystemStatusLabel();
|
||
|
||
switch (status) {
|
||
case WorkStatus::Ready:
|
||
resetAircraftModelSelection();
|
||
setButtonsEnabled(true);
|
||
break;
|
||
case WorkStatus::InitIng:
|
||
setButtonsEnabled(false);
|
||
break;
|
||
case WorkStatus::Working:
|
||
case WorkStatus::Detecting:
|
||
setButtonsEnabled(true);
|
||
break;
|
||
case WorkStatus::Completed:
|
||
resetAircraftModelSelection();
|
||
setButtonsEnabled(true);
|
||
break;
|
||
case WorkStatus::Error:
|
||
resetAircraftModelSelection();
|
||
setButtonsEnabled(true);
|
||
break;
|
||
case WorkStatus::EmergencyStop:
|
||
setButtonsEnabled(false);
|
||
break;
|
||
default:
|
||
setButtonsEnabled(false);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void MainWindow::updateDetectionResultDisplay(const DetectionResult& result)
|
||
{
|
||
if (!result.parkingSpaceInfoList.empty()) {
|
||
const ParkingGuideState state = ParkingGuideStateFromCode(
|
||
result.parkingSpaceInfoList.front().guideStateCode);
|
||
if (state == ParkingGuideState::EmergencyStop ||
|
||
state == ParkingGuideState::SystemFailure) {
|
||
m_safetyResultControlsLocked = true;
|
||
} else if (state == ParkingGuideState::Waiting) {
|
||
m_safetyResultControlsLocked = false;
|
||
}
|
||
|
||
if (ui_dialogAlgoArg) {
|
||
const bool detectionRunning =
|
||
m_presenter && m_presenter->IsDetectionRunning();
|
||
ui_dialogAlgoArg->setEnabled(
|
||
!safetyControlsLocked() && !detectionRunning);
|
||
}
|
||
// 急停/故障锁死全部按键;WAIT 解除后按引导是否在跑恢复停止键
|
||
setButtonsEnabled(!safetyControlsLocked());
|
||
}
|
||
|
||
addDetectionResult(result);
|
||
updateWorkModeLabel();
|
||
}
|
||
|
||
void MainWindow::on_btn_start_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
updateStatusLog(tr("系统未初始化"));
|
||
return;
|
||
}
|
||
if (!promptControlPassword(tr("开始引导"))) {
|
||
return;
|
||
}
|
||
|
||
const QString selectedModel = selectedAircraftModel();
|
||
setButtonsEnabled(false);
|
||
if (ui_dialogAlgoArg) {
|
||
ui_dialogAlgoArg->setEnabled(false);
|
||
}
|
||
clearDetectionLog();
|
||
updateStatusLog(selectedModel.isEmpty()
|
||
? tr("开始停机引导检测,机型以实际检测结果为准")
|
||
: tr("开始%1停机引导检测").arg(selectedModel));
|
||
if (!m_presenter->StartDetection(selectedModel, -1)) {
|
||
updateStatusLog(tr("启动检测失败"));
|
||
setButtonsEnabled(true);
|
||
if (ui_dialogAlgoArg) {
|
||
ui_dialogAlgoArg->setEnabled(!safetyControlsLocked());
|
||
}
|
||
}
|
||
}
|
||
|
||
void MainWindow::on_btn_stop_clicked()
|
||
{
|
||
if (!promptControlPassword(tr("停止引导"))) {
|
||
return;
|
||
}
|
||
if (m_presenter) {
|
||
m_presenter->StopDetection();
|
||
}
|
||
}
|
||
|
||
void MainWindow::on_btn_chock_on_clicked()
|
||
{
|
||
recordManualChock(true);
|
||
}
|
||
|
||
void MainWindow::on_btn_chock_off_clicked()
|
||
{
|
||
recordManualChock(false);
|
||
}
|
||
|
||
void MainWindow::showParameterDialog(DialogAlgoArg::ConfigPage page, QPushButton* button)
|
||
{
|
||
if (m_selectedButton && m_selectedButton != button) {
|
||
updateStatusLog(tr("请先关闭当前参数窗口"));
|
||
return;
|
||
}
|
||
|
||
if (!m_presenter) {
|
||
updateStatusLog(tr("系统未初始化"));
|
||
return;
|
||
}
|
||
if (m_presenter->IsDetectionRunning()) {
|
||
updateStatusLog(tr("停机引导进行中,暂不能修改参数"));
|
||
return;
|
||
}
|
||
if (!promptControlPassword(tr("参数设置"))) {
|
||
return;
|
||
}
|
||
|
||
setButtonSelectedState(button, true);
|
||
|
||
if (!ui_dialogAlgoArg) {
|
||
ui_dialogAlgoArg = new DialogAlgoArg(this);
|
||
connect(ui_dialogAlgoArg, &QDialog::finished, this, [this]() {
|
||
if (m_presenter) {
|
||
m_presenter->SetLivePreviewEnabled(false);
|
||
}
|
||
if (m_selectedButton) {
|
||
setButtonSelectedState(m_selectedButton, false);
|
||
}
|
||
});
|
||
}
|
||
|
||
ui_dialogAlgoArg->SetPresenter(m_presenter);
|
||
ui_dialogAlgoArg->SetCurrentPage(page);
|
||
m_presenter->SetLivePreviewEnabled(true);
|
||
ui_dialogAlgoArg->show();
|
||
ui_dialogAlgoArg->raise();
|
||
ui_dialogAlgoArg->activateWindow();
|
||
}
|
||
|
||
void MainWindow::on_btn_camera_clicked()
|
||
{
|
||
updateStatusLog(tr("打开相机参数设置"));
|
||
showParameterDialog(DialogAlgoArg::ConfigPage::Camera, ui->btn_camera);
|
||
}
|
||
|
||
void MainWindow::on_btn_algo_config_clicked()
|
||
{
|
||
updateStatusLog(tr("打开检测参数设置"));
|
||
showParameterDialog(DialogAlgoArg::ConfigPage::Detection, ui->btn_algo_config);
|
||
}
|
||
|
||
void MainWindow::on_btn_camera_levelling_clicked()
|
||
{
|
||
updateStatusLog(tr("打开雷达地面调平参数"));
|
||
showParameterDialog(DialogAlgoArg::ConfigPage::Calibration, ui->btn_camera_levelling);
|
||
}
|
||
|
||
void MainWindow::on_btn_hide_clicked()
|
||
{
|
||
showMinimized();
|
||
}
|
||
|
||
void MainWindow::on_btn_close_clicked()
|
||
{
|
||
close();
|
||
}
|
||
|
||
void MainWindow::on_btn_test_clicked()
|
||
{
|
||
if (!m_presenter) {
|
||
StyledMessageBox::warning(this, tr("错误"), tr("系统未初始化"));
|
||
return;
|
||
}
|
||
if (!promptControlPassword(tr("测试检测"))) {
|
||
return;
|
||
}
|
||
|
||
if (m_selectedButton && m_selectedButton != ui->btn_test) {
|
||
updateStatusLog(tr("请先关闭当前参数窗口"));
|
||
return;
|
||
}
|
||
|
||
setButtonSelectedState(ui->btn_test, true);
|
||
setButtonsEnabled(false);
|
||
clearDetectionLog();
|
||
updateStatusLog(tr("运行预留算法接口测试"));
|
||
|
||
if (m_testThread.joinable()) {
|
||
m_testThread.join();
|
||
}
|
||
m_testRunning.store(true);
|
||
m_testThread = std::thread([this]() {
|
||
const bool ok = m_presenter->TriggerDetection(-1);
|
||
if (!m_closing.load()) {
|
||
updateStatusLog(ok ? tr("测试检测完成") : tr("测试检测失败"));
|
||
}
|
||
QMetaObject::invokeMethod(this, [this]() {
|
||
if (m_closing.load()) {
|
||
return;
|
||
}
|
||
setButtonSelectedState(ui->btn_test, false);
|
||
setButtonsEnabled(true);
|
||
}, Qt::QueuedConnection);
|
||
m_testRunning.store(false);
|
||
});
|
||
}
|
||
|
||
void MainWindow::setButtonsEnabled(bool enabled)
|
||
{
|
||
const bool detectionRunning =
|
||
m_presenter && m_presenter->IsDetectionRunning();
|
||
const bool safetyLocked = safetyControlsLocked();
|
||
const bool initDone = !m_initRunning.load();
|
||
const bool canStart =
|
||
enabled &&
|
||
initDone &&
|
||
!safetyLocked &&
|
||
!detectionRunning;
|
||
// 自动进入引导后必须能点停止;急停锁定时除外
|
||
const bool canStop =
|
||
!safetyLocked &&
|
||
detectionRunning;
|
||
const bool canChock =
|
||
initDone &&
|
||
!safetyLocked &&
|
||
m_presenter;
|
||
|
||
if (ui->combo_model) {
|
||
ui->combo_model->setEnabled(canStart);
|
||
}
|
||
if (ui->btn_start) {
|
||
ui->btn_start->setEnabled(canStart);
|
||
}
|
||
if (ui->btn_stop) {
|
||
ui->btn_stop->setEnabled(canStop);
|
||
}
|
||
if (ui->btn_camera) {
|
||
ui->btn_camera->setEnabled(canStart);
|
||
}
|
||
if (ui->btn_algo_config) {
|
||
ui->btn_algo_config->setEnabled(canStart);
|
||
}
|
||
if (ui->btn_camera_levelling) {
|
||
ui->btn_camera_levelling->setEnabled(canStart);
|
||
}
|
||
if (ui->btn_test) {
|
||
ui->btn_test->setEnabled(canStart);
|
||
}
|
||
if (ui->btn_chock_on) {
|
||
ui->btn_chock_on->setEnabled(canChock);
|
||
}
|
||
if (ui->btn_chock_off) {
|
||
ui->btn_chock_off->setEnabled(canChock);
|
||
}
|
||
updateWorkModeLabel();
|
||
updateSystemStatusLabel();
|
||
}
|
||
|
||
void MainWindow::resetAircraftModelSelection()
|
||
{
|
||
setAircraftModelSelection(QString());
|
||
}
|
||
|
||
QString MainWindow::selectedAircraftModel() const
|
||
{
|
||
if (!ui->combo_model || ui->combo_model->currentIndex() <= 0) {
|
||
return QString();
|
||
}
|
||
return ui->combo_model->currentText().trimmed();
|
||
}
|
||
|
||
void MainWindow::setAircraftModelSelection(const QString& modelType)
|
||
{
|
||
if (!ui->combo_model) {
|
||
return;
|
||
}
|
||
const QString normalized = modelType.trimmed();
|
||
const QSignalBlocker blocker(ui->combo_model);
|
||
if (normalized.isEmpty()) {
|
||
ui->combo_model->setCurrentIndex(0);
|
||
return;
|
||
}
|
||
int index = ui->combo_model->findText(normalized, Qt::MatchFixedString);
|
||
if (index < 0) {
|
||
ui->combo_model->addItem(normalized);
|
||
index = ui->combo_model->findText(normalized, Qt::MatchFixedString);
|
||
}
|
||
ui->combo_model->setCurrentIndex(index > 0 ? index : 0);
|
||
}
|
||
|
||
void MainWindow::populateAircraftModels()
|
||
{
|
||
if (!ui->combo_model) {
|
||
return;
|
||
}
|
||
const QString current = selectedAircraftModel();
|
||
const QSignalBlocker blocker(ui->combo_model);
|
||
ui->combo_model->clear();
|
||
ui->combo_model->addItem(QStringLiteral("请选择机型"));
|
||
AircraftModelsConfig models = AircraftModelsConfig::DefaultModels();
|
||
if (m_presenter && m_presenter->GetConfigManager()) {
|
||
models = m_presenter->GetConfigManager()->GetConfigResult().aircraftModels;
|
||
if (models.models.empty()) {
|
||
models = AircraftModelsConfig::DefaultModels();
|
||
}
|
||
}
|
||
for (const AircraftModelItem& item : models.models) {
|
||
if (!item.name.trimmed().isEmpty()) {
|
||
ui->combo_model->addItem(item.name.trimmed());
|
||
}
|
||
}
|
||
if (!current.isEmpty()) {
|
||
const int index = ui->combo_model->findText(current, Qt::MatchFixedString);
|
||
ui->combo_model->setCurrentIndex(index > 0 ? index : 0);
|
||
}
|
||
}
|
||
|
||
QString MainWindow::currentTargetId() const
|
||
{
|
||
if (m_presenter && m_presenter->GetConfigManager()) {
|
||
const std::string targetId =
|
||
m_presenter->GetConfigManager()->GetConfigResult()
|
||
.udpBroadcastConfig.targetId;
|
||
if (!targetId.empty()) {
|
||
return QString::fromStdString(targetId);
|
||
}
|
||
}
|
||
return QStringLiteral("T001");
|
||
}
|
||
|
||
QString MainWindow::currentParkId() const
|
||
{
|
||
if (m_presenter && m_presenter->GetConfigManager()) {
|
||
const std::string parkId =
|
||
m_presenter->GetConfigManager()->GetConfigResult()
|
||
.udpBroadcastConfig.parkId;
|
||
if (!parkId.empty()) {
|
||
return QString::fromStdString(parkId);
|
||
}
|
||
}
|
||
return QStringLiteral("P01");
|
||
}
|
||
|
||
void MainWindow::updateSystemStatusLabel()
|
||
{
|
||
if (!ui || !ui->label_system_status) {
|
||
return;
|
||
}
|
||
// 开始引导后为系统已激活,停止引导后为未激活
|
||
const bool activated =
|
||
m_presenter && m_presenter->IsDetectionRunning();
|
||
ui->label_system_status->setText(activated
|
||
? tr("系统已激活")
|
||
: tr("未激活"));
|
||
ui->label_system_status->setStyleSheet(activated
|
||
? QStringLiteral(
|
||
"color: rgb(0, 255, 0);"
|
||
"background-color: transparent;"
|
||
"border: none;"
|
||
"font-weight: 700;")
|
||
: QStringLiteral(
|
||
"color: rgb(172, 181, 194);"
|
||
"background-color: transparent;"
|
||
"border: none;"
|
||
"font-weight: 700;"));
|
||
}
|
||
|
||
void MainWindow::updateWorkModeLabel()
|
||
{
|
||
if (!ui->label_work) {
|
||
return;
|
||
}
|
||
const bool maintenance = m_presenter && m_presenter->IsMaintenanceMode();
|
||
const bool guidanceRunning =
|
||
m_presenter && m_presenter->IsDetectionRunning();
|
||
ui->label_work->setText(maintenance
|
||
? tr("维护模式")
|
||
: tr("正常模式"));
|
||
ui->label_work->setCursor(guidanceRunning
|
||
? Qt::ForbiddenCursor
|
||
: Qt::PointingHandCursor);
|
||
if (guidanceRunning) {
|
||
ui->label_work->setStyleSheet(
|
||
QStringLiteral("color: rgb(130, 132, 138); font-weight: 700;"));
|
||
} else {
|
||
ui->label_work->setStyleSheet(maintenance
|
||
? QStringLiteral("color: rgb(255, 160, 40); font-weight: 700;")
|
||
: QStringLiteral("color: rgb(0, 255, 0); font-weight: 700;"));
|
||
}
|
||
}
|
||
|
||
bool MainWindow::promptControlPassword(const QString& title)
|
||
{
|
||
if (!m_presenter) {
|
||
StyledMessageBox::warning(this, tr("错误"), tr("系统未初始化"));
|
||
return false;
|
||
}
|
||
if (m_presenter->ValidateControlPassword(QString())) {
|
||
return true;
|
||
}
|
||
// 有效期内不再弹窗,超时后重新输入。
|
||
if (m_passwordValidUntilMs > QDateTime::currentMSecsSinceEpoch()) {
|
||
return true;
|
||
}
|
||
|
||
DialogControlPassword dialog(this);
|
||
dialog.SetTitle(title);
|
||
dialog.SetPasswordChecker([this](const QString& password) {
|
||
return m_presenter->ValidateControlPassword(password);
|
||
});
|
||
if (dialog.exec() != QDialog::Accepted) {
|
||
return false;
|
||
}
|
||
RememberControlPassword();
|
||
return true;
|
||
}
|
||
|
||
int MainWindow::ControlPasswordValidSeconds() const
|
||
{
|
||
if (!m_presenter || !m_presenter->GetConfigManager()) {
|
||
return 120;
|
||
}
|
||
return m_presenter->GetConfigManager()->GetConfigResult()
|
||
.controlPanelConfig.validSeconds;
|
||
}
|
||
|
||
void MainWindow::RememberControlPassword()
|
||
{
|
||
const int validSeconds = ControlPasswordValidSeconds();
|
||
m_passwordValidUntilMs = QDateTime::currentMSecsSinceEpoch() +
|
||
static_cast<qint64>(validSeconds) * 1000;
|
||
}
|
||
|
||
void MainWindow::recordManualChock(bool chockOn)
|
||
{
|
||
if (!m_presenter) {
|
||
updateStatusLog(tr("系统未初始化"));
|
||
return;
|
||
}
|
||
if (!promptControlPassword(chockOn ? tr("上轮挡") : tr("下轮挡"))) {
|
||
return;
|
||
}
|
||
if (!m_presenter->RecordManualChock(chockOn)) {
|
||
updateStatusLog(chockOn ? tr("记录上轮挡时间失败") : tr("记录下轮挡时间失败"));
|
||
return;
|
||
}
|
||
updateStatusLog(chockOn ? tr("已记录上轮挡时间") : tr("已记录下轮挡时间"));
|
||
}
|
||
|
||
bool MainWindow::safetyControlsLocked() const
|
||
{
|
||
return m_emergencyStopControlsLocked ||
|
||
m_safetyResultControlsLocked;
|
||
}
|
||
|
||
void MainWindow::setButtonImage(QPushButton* button, const QString& imagePath)
|
||
{
|
||
if (button) {
|
||
button->setStyleSheet(QString("image: url(%1);background-color: rgb(38, 40, 47);border: none;")
|
||
.arg(imagePath));
|
||
}
|
||
}
|
||
|
||
void MainWindow::setButtonSelectedState(QPushButton* button, bool selected)
|
||
{
|
||
if (!button) {
|
||
return;
|
||
}
|
||
|
||
QString normalImage;
|
||
QString selectedImage;
|
||
if (button == ui->btn_camera) {
|
||
normalImage = ":/common/resource/config_camera.png";
|
||
selectedImage = ":/common/resource/config_camera_s.png";
|
||
} else if (button == ui->btn_algo_config) {
|
||
normalImage = ":/common/resource/config_algo.png";
|
||
selectedImage = ":/common/resource/config_algo_s.png";
|
||
} else if (button == ui->btn_camera_levelling) {
|
||
normalImage = ":/common/resource/config_camera_level.png";
|
||
selectedImage = ":/common/resource/config_camera_level_s.png";
|
||
} else if (button == ui->btn_test) {
|
||
normalImage = ":/common/resource/config_data_test.png";
|
||
selectedImage = ":/common/resource/config_data_test_s.png";
|
||
}
|
||
|
||
if (selected) {
|
||
setButtonImage(button, selectedImage);
|
||
m_selectedButton = button;
|
||
setOtherButtonsEnabled(button, false);
|
||
} else {
|
||
setButtonImage(button, normalImage);
|
||
if (m_selectedButton == button) {
|
||
m_selectedButton = nullptr;
|
||
setOtherButtonsEnabled(nullptr, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
void MainWindow::restoreAllButtonStates()
|
||
{
|
||
setButtonSelectedState(ui->btn_camera, false);
|
||
setButtonSelectedState(ui->btn_algo_config, false);
|
||
setButtonSelectedState(ui->btn_camera_levelling, false);
|
||
setButtonSelectedState(ui->btn_test, false);
|
||
}
|
||
|
||
void MainWindow::setOtherButtonsEnabled(QPushButton* exceptButton, bool enabled)
|
||
{
|
||
const QList<QPushButton*> configButtons = {
|
||
ui->btn_camera,
|
||
ui->btn_algo_config,
|
||
ui->btn_camera_levelling,
|
||
ui->btn_test
|
||
};
|
||
|
||
const bool controlsEnabled =
|
||
enabled &&
|
||
!safetyControlsLocked() &&
|
||
!(m_presenter && m_presenter->IsDetectionRunning());
|
||
for (QPushButton* button : configButtons) {
|
||
if (button && button != exceptButton) {
|
||
button->setEnabled(controlsEnabled);
|
||
}
|
||
}
|
||
}
|
||
|
||
void MainWindow::onCameraClicked(int cameraIndex)
|
||
{
|
||
if (m_presenter) {
|
||
m_presenter->SetDefaultCameraIndex(cameraIndex);
|
||
}
|
||
}
|
||
|
||
void MainWindow::setupContextMenu()
|
||
{
|
||
m_contextMenu = new QMenu(this);
|
||
m_saveDataAction = new QAction(tr("保存检测数据"), this);
|
||
m_contextMenu->addAction(m_saveDataAction);
|
||
|
||
connect(m_saveDataAction, &QAction::triggered, this, &MainWindow::onSaveDetectionData);
|
||
|
||
ui->detect_result_list->setContextMenuPolicy(Qt::CustomContextMenu);
|
||
connect(ui->detect_result_list, &QListWidget::customContextMenuRequested,
|
||
this, &MainWindow::showContextMenu);
|
||
}
|
||
|
||
void MainWindow::showContextMenu(const QPoint& pos)
|
||
{
|
||
if (!m_presenter) {
|
||
updateStatusLog(tr("系统未初始化,无法保存数据"));
|
||
return;
|
||
}
|
||
|
||
if (m_presenter->GetDetectionDataCacheSize() == 0) {
|
||
updateStatusLog(tr("没有可保存的检测数据"));
|
||
return;
|
||
}
|
||
|
||
m_contextMenu->exec(ui->detect_result_list->mapToGlobal(pos));
|
||
}
|
||
|
||
void MainWindow::onSaveDetectionData()
|
||
{
|
||
if (!m_presenter) {
|
||
updateStatusLog(tr("系统未初始化,无法保存数据"));
|
||
return;
|
||
}
|
||
|
||
const QString dirPath = QFileDialog::getExistingDirectory(
|
||
this,
|
||
tr("选择保存目录"),
|
||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
|
||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||
|
||
if (dirPath.isEmpty()) {
|
||
updateStatusLog(tr("保存已取消"));
|
||
return;
|
||
}
|
||
|
||
const std::string timeStamp = CVrDateUtils::GetNowTime();
|
||
const QString fileName = QString("停机引导_%1_%2.txt")
|
||
.arg(m_presenter->GetDetectIndex())
|
||
.arg(QString::fromStdString(timeStamp));
|
||
const QString fullPath = QDir(dirPath).filePath(fileName);
|
||
|
||
if (saveDetectionDataToFile(fullPath)) {
|
||
updateStatusLog(tr("检测数据已保存:%1").arg(fileName));
|
||
} else {
|
||
updateStatusLog(tr("保存检测数据失败"));
|
||
}
|
||
}
|
||
|
||
bool MainWindow::saveDetectionDataToFile(const QString& filePath)
|
||
{
|
||
if (!m_presenter) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
const int result = m_presenter->SaveDetectionDataToFile(filePath.toStdString());
|
||
if (result != 0) {
|
||
updateStatusLog(tr("保存数据失败,错误码:%1").arg(result));
|
||
return false;
|
||
}
|
||
return true;
|
||
} catch (const std::exception&) {
|
||
updateStatusLog(tr("保存数据异常"));
|
||
return false;
|
||
}
|
||
}
|