#include "mainwindow.h" #include "ui_mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "IVrUtils.h" #include "PathManager.h" #include "SingleTargetResultWidget.h" #include "StyledMessageBox.h" #include "Version.h" #include "VrDateUtils.h" #include "VrLog.h" namespace { 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(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); } } // namespace MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); setWindowTitle(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(); ui->label_work->setVisible(false); ui->detect_image->setScene(new QGraphicsScene(this)); m_logHelper = new DetectLogHelper(ui->detect_log, this); qRegisterMetaType("DetectionResult"); qRegisterMetaType("WorkStatus"); qRegisterMetaType("ParkingSpaceGuidePosition"); connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel, Qt::QueuedConnection); connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay, Qt::QueuedConnection); setupContextMenu(); updateStatusLog(tr("设备初始化开始")); Init(); } MainWindow::~MainWindow() { 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(nullptr)); delete m_presenter; m_presenter = nullptr; } delete ui; } 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; } 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_presenter->SetStatusCallback(this); 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)); setButtonsEnabled(false); m_initThread = std::thread([this]() { updateStatusLog(tr("系统初始化中...")); const int result = m_presenter->Init(); if (result != 0) { updateStatusLog(tr("初始化失败,错误码:%1").arg(result)); } else { updateStatusLog(tr("初始化完成")); } }); } void MainWindow::displayImage(const QImage& image) { if (image.isNull()) { updateStatusLog(tr("无预览图像")); return; } QGraphicsScene* scene = ui->detect_image->scene(); scene->clear(); scene->addPixmap(QPixmap::fromImage(image)); ui->detect_image->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); } void MainWindow::addDetectionResult(const DetectionResult& result) { ui->detect_result_list->clear(); const int itemWidth = (std::max)(360, ui->detect_result_list->viewport()->width() - 8); const int itemHeight = 420; SingleTargetResultWidget::setupListWidget(ui->detect_result_list, QSize(itemWidth, itemHeight)); auto* resultWidget = new SingleTargetResultWidget(); resultWidget->setAutoFillBackground(true); auto* item = new QListWidgetItem(); item->setBackground(QBrush(Qt::transparent)); item->setSizeHint(QSize(itemWidth, itemHeight)); item->setFlags(item->flags() & ~Qt::ItemIsDragEnabled & ~Qt::ItemIsDropEnabled); ui->detect_result_list->addItem(item); ui->detect_result_list->setItemWidget(item, resultWidget); if (result.parkingSpaceInfoList.empty()) { resultWidget->setTitle(tr("当前状态")); resultWidget->setStatus(GuideStateName(ParkingGuideState::Unknown), false); resultWidget->setEmptyText(tr("暂无停机状态结果")); return; } const ParkingSpaceGuideInfo& info = result.parkingSpaceInfoList.front(); const ParkingGuideState guideState = ParkingGuideStateFromCode(info.guideStateCode); resultWidget->setTitle(tr("当前状态")); resultWidget->setStatus(GuideStateName(guideState), false); resultWidget->setFields({ {tr("目标编号"), info.targetId}, {tr("停机位"), info.parkId}, {tr("机型"), info.modelType}, {tr("引导状态"), FormatGuideState(info)}, {tr("到停止线"), FormatNumber(info.distance, tr("毫米"))}, {tr("横向偏移"), FormatNumber(info.lateralOffset, tr("毫米"))}, {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) { emit detectionResultUpdateRequested(result); } 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) { if (status == WorkStatus::Working || status == WorkStatus::Detecting) { clearDetectionLog(); } if (!ui->label_work) { return; } const QString statusText = QString::fromStdString(WorkStatusToString(status)); ui->label_work->setText(statusText); updateStatusLog(tr("工作状态:%1").arg(statusText)); switch (status) { case WorkStatus::Ready: ui->label_work->setStyleSheet("color: green;"); setButtonsEnabled(true); if (ui->btn_stop) ui->btn_stop->setEnabled(false); break; case WorkStatus::InitIng: ui->label_work->setStyleSheet("color: blue;"); setButtonsEnabled(false); break; case WorkStatus::Working: ui->label_work->setStyleSheet("color: blue;"); setButtonsEnabled(false); if (ui->btn_stop) ui->btn_stop->setEnabled(true); break; case WorkStatus::Detecting: ui->label_work->setStyleSheet("color: blue;"); setButtonsEnabled(false); break; case WorkStatus::Completed: ui->label_work->setStyleSheet("color: green; font-weight: bold;"); setButtonsEnabled(true); if (ui->btn_stop) ui->btn_stop->setEnabled(false); break; case WorkStatus::Error: ui->label_work->setStyleSheet("color: red; font-weight: bold;"); setButtonsEnabled(true); if (ui->btn_stop) ui->btn_stop->setEnabled(false); break; default: ui->label_work->setStyleSheet(""); setButtonsEnabled(false); break; } } void MainWindow::updateDetectionResultDisplay(const DetectionResult& result) { if (!result.image.isNull()) { displayImage(result.image); } addDetectionResult(result); } void MainWindow::on_btn_start_clicked() { if (!m_presenter) { updateStatusLog(tr("系统未初始化")); return; } clearDetectionLog(); updateStatusLog(tr("开始停车引导检测")); if (!m_presenter->StartDetection(-1)) { updateStatusLog(tr("启动检测失败")); } } void MainWindow::on_btn_stop_clicked() { if (m_presenter) { m_presenter->StopDetection(); } } void MainWindow::showParameterDialog(DialogAlgoArg::ConfigPage page, QPushButton* button) { if (m_selectedButton && m_selectedButton != button) { updateStatusLog(tr("请先关闭当前参数窗口")); return; } if (!m_presenter) { updateStatusLog(tr("系统未初始化")); return; } setButtonSelectedState(button, true); if (!ui_dialogAlgoArg) { ui_dialogAlgoArg = new DialogAlgoArg(this); connect(ui_dialogAlgoArg, &QDialog::finished, this, [this]() { if (m_selectedButton) { setButtonSelectedState(m_selectedButton, false); } }); } ui_dialogAlgoArg->SetPresenter(m_presenter); ui_dialogAlgoArg->SetCurrentPage(page); 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 (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_testThread = std::thread([this]() { const bool ok = m_presenter->TriggerDetection(-1); updateStatusLog(ok ? tr("测试检测完成") : tr("测试检测失败")); QMetaObject::invokeMethod(this, [this]() { setButtonSelectedState(ui->btn_test, false); const bool running = m_presenter && m_presenter->IsDetectionRunning(); setButtonsEnabled(!running); if (ui->btn_stop) ui->btn_stop->setEnabled(running); }, Qt::QueuedConnection); }); } void MainWindow::setButtonsEnabled(bool enabled) { if (ui->btn_start) ui->btn_start->setEnabled(enabled); if (ui->btn_stop) ui->btn_stop->setEnabled(enabled); if (ui->btn_camera) ui->btn_camera->setEnabled(enabled); if (ui->btn_algo_config) ui->btn_algo_config->setEnabled(enabled); if (ui->btn_camera_levelling) ui->btn_camera_levelling->setEnabled(enabled); if (ui->btn_test) ui->btn_test->setEnabled(enabled); } 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 configButtons = { ui->btn_camera, ui->btn_algo_config, ui->btn_camera_levelling, ui->btn_test }; for (QPushButton* button : configButtons) { if (button && button != exceptButton) { button->setEnabled(enabled); } } } 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_image->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->detect_image, &QGraphicsView::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_image->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; } }