1339 lines
41 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 <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;
}
}