1123 lines
38 KiB
C++
Raw Normal View History

2026-08-04 14:23:07 +08:00
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "resultitem.h"
#include <QGraphicsScene>
#include <QStandardItemModel>
#include <QDebug>
#include <QPainter>
#include <QBrush>
#include <QMessageBox>
#include <QFileDialog>
#include <QListWidgetItem>
#include <QtMath>
#include <QThread>
#include <QDateTime>
#include <QTextCursor>
#include <QStringListModel>
#include <QScreen>
#include <QApplication>
#include "VrLog.h"
#include "VrDateUtils.h"
#include <QIcon>
#include <QMetaType>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMenu>
#include <QAction>
#include <QPushButton>
#include <QAbstractButton>
2026-08-04 14:23:07 +08:00
#include <QStandardPaths>
#include <QDir>
#include <QGraphicsView>
#include "StatorWorkpiecePositionPresenter.h"
#include <QProcess>
#include <QSettings>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QEvent>
#include "AboutDialog.h"
#include "StyledMessageBox.h"
2026-08-04 14:23:07 +08:00
#include "Version.h"
#include "IVrUtils.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_selectedButton(nullptr)
, m_contextMenu(nullptr)
, m_saveDataAction(nullptr)
{
ui->setupUi(this);
// 设置窗口图标Windows使用.ico格式
#ifdef _WIN32
this->setWindowIcon(QIcon(":/common/resource/logo.ico"));
#else
this->setWindowIcon(QIcon(":/common/resource/logo.png"));
#endif
// 设置状态栏字体
QFont statusFont = statusBar()->font();
statusFont.setPointSize(12);
statusBar()->setFont(statusFont);
// 设置状态栏颜色和padding
statusBar()->setStyleSheet("QStatusBar { color: rgb(239, 241, 245); padding: 20px; }");
// 设置CPU序列号显示左下角
setupCPUSerialDisplay();
// 在状态栏右侧添加版本信息(包含编译时间)
// 点击版本号时通过 AboutDialog 显示软件、算法和机器信息。
m_softwareVersionText = QString("%1_%2%3%4%5%6%7")
.arg(GetStatorWorkpiecePositionFullVersion())
.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(m_softwareVersionText);
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);
// 启动后自动最大化显示
this->showMaximized();
// 初始化时隐藏label_work
ui->label_work->setVisible(false);
ui->detect_image_2->setVisible(false);
// 初始化GraphicsScene
QGraphicsScene* scene = new QGraphicsScene(this);
ui->detect_image->setScene(scene);
// 为第二个图像视图初始化GraphicsScene
QGraphicsScene* scene2 = new QGraphicsScene(this);
ui->detect_image_2->setScene(scene2);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
// 注册自定义类型,使其能够在信号槽中跨线程传递
qRegisterMetaType<StatorWorkpiecePositionDetectionResult>("StatorWorkpiecePositionDetectionResult");
qRegisterMetaType<WorkStatus>("WorkStatus");
qRegisterMetaType<WorkpiecePoint3D>("WorkpiecePoint3D");
qRegisterMetaType<WorkpiecePosition>("WorkpiecePosition");
// 连接工作状态更新信号槽
connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel);
// 连接检测结果更新信号槽
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
// 初始化右键菜单
setupContextMenu();
updateStatusLog(tr("设备开始初始化..."));
// 初始化模块
Init();
}
MainWindow::~MainWindow()
{
if (m_debugDetectionThread.joinable()) {
m_debugDetectionThread.join();
}
// Dialogs retain Presenter/device pointers. Destroy them before Presenter.
delete ui_dialogCameraLevel;
ui_dialogCameraLevel = nullptr;
delete ui_dialogCamera;
ui_dialogCamera = nullptr;
delete ui_dialogConfig;
ui_dialogConfig = nullptr;
// 释放业务逻辑处理类
if (m_presenter) {
StatorWorkpiecePositionPresenter* presenter = m_presenter;
m_presenter = nullptr;
delete presenter;
}
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(STATORWORKPIECEPOSITION_APP_NAME,
m_softwareVersionText,
algoVersion,
this);
dlg.exec();
return true;
}
return QMainWindow::eventFilter(obj, event);
}
void MainWindow::updateStatusLog(const QString& message)
{
// 使用日志辅助类更新日志
if (m_logHelper) {
m_logHelper->appendLog(message);
}
}
void MainWindow::clearDetectionLog()
{
// 使用日志辅助类清空日志
if (m_logHelper) {
m_logHelper->clearLog();
}
}
void MainWindow::Init()
{
// 创建业务逻辑处理类
m_presenter = new StatorWorkpiecePositionPresenter();
// 设置状态回调接口使用BasePresenter的模板方法
m_presenter->SetStatusCallback<IYStatorWorkpiecePositionStatus>(this);
m_deviceStatusWidget = new DeviceStatusWidget(); //因为初始化回调的数据要存储所以要在init前创建好
// 连接DeviceStatusWidget的相机点击信号到MainWindow的槽函数
connect(m_deviceStatusWidget, SIGNAL(cameraClicked(int)), this, SLOT(onCameraClicked(int)));
// 将设备状态widget添加到frame_dev中
QVBoxLayout* frameDevLayout = new QVBoxLayout(ui->frame_dev);
frameDevLayout->setContentsMargins(0, 0, 0, 0);
frameDevLayout->addWidget(m_deviceStatusWidget);
// 设置列表视图模式
ui->detect_result_list->setViewMode(QListView::IconMode);
ui->detect_result_list->setResizeMode(QListView::Adjust);
ui->detect_result_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
ui->detect_result_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 初始化期间禁用所有功能按钮
setButtonsEnabled(false);
// BasePresenter::Init() already owns and joins its initialization thread.
// Calling it directly avoids a detached wrapper accessing a deleted window.
updateStatusLog(tr("正在初始化系统..."));
const int result = m_presenter->Init();
if (result != 0) {
updateStatusLog(tr("初始化失败,错误码:%1").arg(result));
}
}
void MainWindow::displayImage(const QImage& image)
{
if (image.isNull()) {
updateStatusLog(tr("图片无效"));
return;
}
QGraphicsScene* scene = ui->detect_image->scene();
scene->clear();
QPixmap pixmap = QPixmap::fromImage(image);
scene->addPixmap(pixmap);
ui->detect_image->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
}
// 在第二个图像视图中显示图像
void MainWindow::displayImageInSecondView(const QImage& image)
{
if (image.isNull()) {
updateStatusLog(tr("第二相机图片无效"));
return;
}
QGraphicsScene* scene2 = ui->detect_image_2->scene();
scene2->clear();
QPixmap pixmap = QPixmap::fromImage(image);
scene2->addPixmap(pixmap);
ui->detect_image_2->fitInView(scene2->sceneRect(), Qt::KeepAspectRatio);
}
// 添加扩展版本的检测结果函数
void MainWindow::addDetectionResult(const StatorWorkpiecePositionDetectionResult& result)
{
// 清空之前的所有检测结果数据
ui->detect_result_list->clear();
// 设置item间距和流向gridSize现在在布局调整函数中动态设置
int itemSpacing = 0;
ui->detect_result_list->setSpacing(itemSpacing); // 设置item间距为0px
// 获取当前的gridSize由布局调整函数设置
QSize currentGridSize = ui->detect_result_list->gridSize();
int gridWidth = currentGridSize.width();
int gridHeight = currentGridSize.height();
// 显示每个工件的中心点坐标
for (size_t i = 0; i < result.positions.size(); i++) {
ResultItem* resultWidget = new ResultItem();
resultWidget->setAutoFillBackground(true);
// 设置中心点数据
resultWidget->setResultData(i + 1, result.positions[i]);
// 创建QListWidgetItem
QListWidgetItem* item = new QListWidgetItem();
item->setBackground(QBrush(Qt::transparent));
item->setSizeHint(QSize(gridWidth, gridHeight));
item->setFlags(item->flags() & ~Qt::ItemIsDragEnabled & ~Qt::ItemIsDropEnabled);
ui->detect_result_list->addItem(item);
ui->detect_result_list->setItemWidget(item, resultWidget);
}
}
// 状态更新槽函数
void MainWindow::OnStatusUpdate(const std::string& statusMessage)
{
LOG_DEBUG("[UI Display] Status update: %s\n", statusMessage.c_str());
const QString message = QString::fromStdString(statusMessage);
QMetaObject::invokeMethod(this, [this, message]() {
updateStatusLog(message);
}, Qt::QueuedConnection);
}
void MainWindow::OnDetectionResult(const StatorWorkpiecePositionDetectionResult& result)
{
// 通过信号槽机制更新UI确保在主线程中执行
emit detectionResultUpdateRequested(result);
}
void MainWindow::OnCamera1StatusChanged(bool isConnected)
{
QMetaObject::invokeMethod(this, [this, isConnected]() {
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateCamera1Status(isConnected);
}
}, Qt::QueuedConnection);
}
// 相机2状态更新槽函数
void MainWindow::OnCamera2StatusChanged(bool isConnected)
{
QMetaObject::invokeMethod(this, [this, isConnected]() {
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateCamera2Status(isConnected);
}
}, Qt::QueuedConnection);
}
// 相机个数更新槽函数
void MainWindow::OnCameraCountChanged(int cameraCount)
{
QMetaObject::invokeMethod(this, [this, cameraCount]() {
// 设置设备状态widget中的相机数量
if (m_deviceStatusWidget) {
m_deviceStatusWidget->setCameraCount(cameraCount);
// 设置相机名称(从配置文件中读取)
if (m_presenter) {
const auto& cameraList = m_presenter->GetCameraList();
if (cameraList.size() >= 1) {
QString camera1Name = QString::fromStdString(cameraList[0].first);
m_deviceStatusWidget->setCamera1Name(camera1Name);
}
if (cameraList.size() >= 2) {
QString camera2Name = QString::fromStdString(cameraList[1].first);
m_deviceStatusWidget->setCamera2Name(camera2Name);
}
}
}
// 根据相机个数调整页面布局
adjustLayoutForCameraCount(cameraCount);
updateStatusLog(cameraCount < 2
? tr("系统使用单相机模式")
: tr("系统使用双相机模式"));
}, Qt::QueuedConnection);
}
// 机械臂状态更新槽函数
void MainWindow::OnRobotConnectionChanged(bool isConnected)
{
QMetaObject::invokeMethod(this, [this, isConnected]() {
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateRobotStatus(isConnected);
}
}, Qt::QueuedConnection);
}
// 串口连接状态更新槽函数
void MainWindow::OnSerialConnectionChanged(bool isConnected)
{
QMetaObject::invokeMethod(this, [this, isConnected]() {
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateSerialStatus(isConnected);
}
}, Qt::QueuedConnection);
}
// 工作状态更新槽函数
void MainWindow::OnWorkStatusChanged(WorkStatus status)
{
// 通过信号槽机制更新UI确保在主线程中执行
emit workStatusUpdateRequested(status);
}
void MainWindow::updateWorkStatusLabel(WorkStatus status)
{
// 如果状态变为Working清空检测日志表示开始新的检测
if (status == WorkStatus::Working) {
clearDetectionLog();
}
// 获取状态对应的显示文本
QString statusText = QString::fromStdString(WorkStatusToString(status));
// 在label_work中显示状态
if (!ui->label_work) return;
ui->label_work->setText(statusText);
statusText = "【工作状态】" + statusText;
updateStatusLog(statusText);
// 根据不同状态设置不同的样式和按钮启用状态
switch (status) {
case WorkStatus::Ready:
ui->label_work->setStyleSheet("color: green;");
setButtonsEnabled(true); // 就绪状态下启用所有按钮
break;
case WorkStatus::InitIng:
ui->label_work->setStyleSheet("color: blue;");
setButtonsEnabled(false); // 初始化期间禁用按钮
break;
case WorkStatus::Working:
ui->label_work->setStyleSheet("color: blue;");
setButtonsEnabled(false); // 工作期间禁用按钮
break;
case WorkStatus::Completed:
ui->label_work->setStyleSheet("color: green; font-weight: bold;");
setButtonsEnabled(true); // 完成后启用按钮
break;
case WorkStatus::Error:
ui->label_work->setStyleSheet("color: red; font-weight: bold;");
setButtonsEnabled(true); // 错误状态下仍可操作
break;
default:
ui->label_work->setStyleSheet("");
setButtonsEnabled(false); // 未知状态禁用按钮
break;
}
}
void MainWindow::updateDetectionResultDisplay(const StatorWorkpiecePositionDetectionResult& result)
{
// 根据相机索引决定显示在哪个图像视图上
if (!result.image.isNull()) {
if (result.cameraIndex == 2) {
// 第二个相机的结果显示在detect_image_2上
updateStatusLog(tr("相机2检测图像更新"));
displayImageInSecondView(result.image);
} else {
// 第一个相机或默认显示在detect_image上
updateStatusLog(tr("相机1检测图像更新"));
displayImage(result.image);
}
}
// 2D模型检测完成后会先推送图像结果此时没有3D位姿不刷新结果列表。
if (!result.positions.empty()) {
addDetectionResult(result);
}
}
void MainWindow::on_btn_start_clicked()
{
// 检查Presenter是否已初始化
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,请等待初始化完成"));
return;
}
QMessageBox taskBox(this);
taskBox.setWindowTitle(tr("选择检测类型"));
taskBox.setText(tr("请选择本次检测类型"));
taskBox.setStyleSheet(
"QMessageBox { background-color: #25262A; color: rgb(221, 225, 233); font-size: 16pt; }"
"QMessageBox QLabel { color: rgb(221, 225, 233); background-color: transparent; font-size: 16pt; }"
"QMessageBox QPushButton { background-color: #404040; color: rgb(221, 225, 233); border: 1px solid #666666; padding: 8px 14px; font-size: 14pt; min-width: 120px; }"
"QMessageBox QPushButton:hover { background-color: #505050; }");
QPushButton* workpieceButton = taskBox.addButton(tr("工件检测"), QMessageBox::AcceptRole);
QPushButton* binButton = taskBox.addButton(tr("料框检测"), QMessageBox::ActionRole);
taskBox.addButton(tr("取消"), QMessageBox::RejectRole);
for (QAbstractButton* button : taskBox.buttons()) {
button->setStyleSheet(
"QPushButton { background-color: #404040; color: rgb(221, 225, 233); border: 1px solid #666666; padding: 8px 14px; font-size: 14pt; }"
"QPushButton:hover { background-color: #505050; }");
}
taskBox.setDefaultButton(workpieceButton);
taskBox.exec();
QAbstractButton* clickedButton = taskBox.clickedButton();
if (clickedButton != workpieceButton && clickedButton != binButton) {
return;
}
const bool detectBin = (clickedButton == binButton);
2026-08-04 14:23:07 +08:00
// 清空检测日志,开始新的检测
clearDetectionLog();
updateStatusLog(detectBin ? tr("开始料框检测") : tr("开始工件检测"));
2026-08-04 14:23:07 +08:00
// 使用Presenter启动检测
const int result = detectBin
? m_presenter->StartBinDetection(-1, false)
: m_presenter->StartWorkpieceDetection(-1, false);
2026-08-04 14:23:07 +08:00
if (result != 0) {
updateStatusLog(tr("启动检测失败,错误码:%1").arg(result));
}
}
void MainWindow::on_btn_stop_clicked()
{
// 检查Presenter是否已初始化
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,请等待初始化完成"));
return;
}
m_presenter->StopDetection();
}
void MainWindow::on_btn_camera_clicked()
{
// 检查是否有其他按钮已被选中
if (m_selectedButton != nullptr && m_selectedButton != ui->btn_camera) {
updateStatusLog(tr("请先关闭当前打开的配置窗口"));
return;
}
// 检查Presenter是否已初始化
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,请等待初始化完成"));
return;
}
// 设置当前按钮为选中状态
setButtonSelectedState(ui->btn_camera, true);
// 使用新的相机列表获取功能
// 如果对话框不存在创建新的CommonDialogCamera
if (nullptr == ui_dialogCamera) {
ui_dialogCamera = new CommonDialogCamera(m_presenter->GetCameraList(), this);
// 连接对话框关闭信号
connect(ui_dialogCamera, &QDialog::finished, this, [this]() {
setButtonSelectedState(ui->btn_camera, false);
});
}
ui_dialogCamera->show();
}
void MainWindow::on_btn_algo_config_clicked()
{
// 检查是否有其他按钮已被选中
if (m_selectedButton != nullptr && m_selectedButton != ui->btn_algo_config) {
updateStatusLog(tr("请先关闭当前打开的配置窗口"));
return;
}
// 检查Presenter是否已初始化
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,请等待初始化完成"));
return;
}
// 设置当前按钮为选中状态
setButtonSelectedState(ui->btn_algo_config, true);
// 获取配置对象
ConfigManager* config = m_presenter->GetConfigManager();
if (!config) {
// 恢复按钮状态
setButtonSelectedState(ui->btn_algo_config, false);
StyledMessageBox::warning(this, tr("错误"), tr("配置模块未正确初始化!"));
2026-08-04 14:23:07 +08:00
return;
}
if(nullptr == ui_dialogConfig){
try {
ui_dialogConfig = new DialogAlgoarg(config, this);
// 连接对话框关闭信号
connect(ui_dialogConfig, &QDialog::finished, this, [this]() {
setButtonSelectedState(ui->btn_algo_config, false);
});
} catch (const std::exception& e) {
// 恢复按钮状态
setButtonSelectedState(ui->btn_algo_config, false);
// 删除可能已创建的对话框
if (ui_dialogConfig) {
delete ui_dialogConfig;
ui_dialogConfig = nullptr;
}
StyledMessageBox::critical(this, tr("错误"),
QString("创建配置对话框失败: %1").arg(e.what()));
2026-08-04 14:23:07 +08:00
return;
} catch (...) {
// 恢复按钮状态
setButtonSelectedState(ui->btn_algo_config, false);
// 删除可能已创建的对话框
if (ui_dialogConfig) {
delete ui_dialogConfig;
ui_dialogConfig = nullptr;
}
StyledMessageBox::critical(this, tr("错误"), tr("创建配置对话框失败: 未知错误"));
2026-08-04 14:23:07 +08:00
return;
}
}
ui_dialogConfig->show();
}
void MainWindow::on_btn_camera_levelling_clicked()
{
// 检查是否有其他按钮已被选中
if (m_selectedButton != nullptr && m_selectedButton != ui->btn_camera_levelling) {
updateStatusLog(tr("请先关闭当前打开的配置窗口"));
return;
}
// 检查Presenter是否已初始化
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,请等待初始化完成"));
return;
}
// 设置当前按钮为选中状态
setButtonSelectedState(ui->btn_camera_levelling, true);
if(nullptr == ui_dialogCameraLevel){
ui_dialogCameraLevel = new CommonDialogCameraLevel(this);
// 连接对话框关闭信号
connect(ui_dialogCameraLevel, &QDialog::finished, this, [this]() {
setButtonSelectedState(ui->btn_camera_levelling, false);
});
}
// 设置相机列表、presenter、计算器和结果保存器到对话框
// m_presenter 同时实现了 ICameraLevelCalculator 和 ICameraLevelResultSaver 接口
ui_dialogCameraLevel->SetCameraList(m_presenter->GetCameraList(), m_presenter, m_presenter, m_presenter);
ui_dialogCameraLevel->show();
}
void MainWindow::on_btn_hide_clicked()
{
// 最小化窗口
this->showMinimized();
}
void MainWindow::on_btn_close_clicked()
{
// 关闭应用程序
this->close();
}
void MainWindow::on_btn_test_clicked()
{
// 检查是否有其他按钮已被选中
if (m_selectedButton != nullptr && m_selectedButton != ui->btn_test) {
updateStatusLog(tr("请先关闭当前打开的配置窗口"));
return;
}
// 设置当前按钮为选中状态
setButtonSelectedState(ui->btn_test, true);
clearDetectionLog();
// 复检必须先固定二维算法使用的左目图像,再选择与其对应的 3D 数据。
const QString imageFileName = QFileDialog::getOpenFileName(
this,
tr("复检步骤 1/2选择左目图像"),
QString(),
tr("图像文件 (*.png *.jpg *.jpeg *.bmp);;所有文件 (*.*)"));
if (imageFileName.isEmpty()) {
// 用户取消了文件选择,恢复按钮状态
setButtonSelectedState(ui->btn_test, false);
updateStatusLog(tr("已取消复检图像选择"));
return;
}
updateStatusLog(tr("复检步骤 1/2已选择左目图像 %1")
.arg(QFileInfo(imageFileName).fileName()));
const QString dataFileName = QFileDialog::getOpenFileName(
this,
tr("复检步骤 2/2选择对应的 3D 数据"),
QFileInfo(imageFileName).absolutePath(),
tr("激光数据文件 (*.txt *.dat);;所有文件 (*.*)"));
if (dataFileName.isEmpty()) {
setButtonSelectedState(ui->btn_test, false);
updateStatusLog(tr("已取消复检 3D 数据选择"));
return;
}
updateStatusLog(tr("复检步骤 2/2已选择 3D 数据 %1")
.arg(QFileInfo(dataFileName).fileName()));
// 检查Presenter是否已初始化
if (!m_presenter) {
// 恢复按钮状态
setButtonSelectedState(ui->btn_test, false);
StyledMessageBox::warning(this, tr("错误"), tr("系统未正确初始化!"));
2026-08-04 14:23:07 +08:00
return;
}
if (m_debugDetectionThread.joinable()) {
m_debugDetectionThread.join();
}
m_debugDetectionThread = std::thread([this, imageFileName, dataFileName]() {
const int result = m_presenter->LoadDebugImageAndDataAndDetect(
imageFileName.toStdString(), dataFileName.toStdString());
const QString message = result == 0
? tr("图像和 3D 数据完整复检成功")
: tr("图像和 3D 数据复检失败: %1").arg(result);
QMetaObject::invokeMethod(this, [this, message]() {
updateStatusLog(message);
}, Qt::QueuedConnection);
// 测试完成后恢复按钮状态
QMetaObject::invokeMethod(this, [this]() {
setButtonSelectedState(ui->btn_test, false);
}, 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);
// 窗口控制按钮(最小化和关闭)始终可用
// if (ui->btn_hide) ui->btn_hide->setEnabled(true);
// if (ui->btn_close) ui->btn_close->setEnabled(true);
}
// 设置按钮图像
void MainWindow::setButtonImage(QPushButton* button, const QString& imagePath)
{
if (button) {
QString styleSheet = QString("image: url(%1);background-color: rgb(38, 40, 47);border: none;").arg(imagePath);
button->setStyleSheet(styleSheet);
}
}
// 设置按钮选中状态
void MainWindow::setButtonSelectedState(QPushButton* button, bool selected)
{
if (!button) return;
QString normalImage, 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)
{
QList<QPushButton*> 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::adjustLayoutForCameraCount(int cameraCount)
{
if (cameraCount <= 1) {
// 单相机模式布局
setupSingleCameraLayout();
} else {
// 多相机模式布局
setupMultiCameraLayout();
}
}
// 获取可用高度(屏幕高度 - 任务栏 - 边距)
int MainWindow::getAvailableHeight()
{
// 获取主屏幕的可用几何区域(自动排除任务栏)
QScreen* screen = QApplication::primaryScreen();
if (screen) {
QRect availableGeometry = screen->availableGeometry();
// 额外预留20px底部边距
return availableGeometry.height() - 30;
}
// 如果无法获取屏幕信息,返回默认值
return 1050; // 默认值1080 - 任务栏约30px - 20px边距
}
// 设置单相机模式布局
void MainWindow::setupSingleCameraLayout()
{
// 获取可用高度已排除任务栏和20px边距
int availableHeight = getAvailableHeight();
// 单相机模式下的原始布局
// detect_image: 占据左侧大部分空间,动态计算高度
int detectImageTop = 140;
int detectImageHeight = availableHeight - detectImageTop;
ui->detect_image->setGeometry(20, detectImageTop, 1311, detectImageHeight);
// detect_image_2: 隐藏
ui->detect_image_2->setVisible(false);
// 设备状态框架: 位于右上方,恢复原始尺寸
ui->frame_dev->setGeometry(1344, 140, 556, 64);
// 检测结果列表: 位于右侧中间
ui->detect_result_list->setGeometry(1344, 216, 556, 622);
// 单相机模式下恢复原有的垂直滚动设置
ui->detect_result_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
ui->detect_result_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->detect_result_list->setFlow(QListView::TopToBottom); // 从上到下排列
ui->detect_result_list->setWrapping(false); // 不换行,每行一个
// 单相机模式设置gridSize每行显示1个目标
int gridWidth = 550;
int gridHeight = 65;
ui->detect_result_list->setGridSize(QSize(gridWidth, gridHeight));
// 恢复设备状态widget的原始布局
m_deviceStatusWidget->setCameraCount(1);
// 日志: 位于右下方,动态计算高度
int detectLogTop = 850;
int detectLogHeight = availableHeight - detectLogTop;
ui->detect_log->setGeometry(1344, detectLogTop, 556, detectLogHeight);
updateStatusLog(tr("页面布局已调整为单相机模式"));
}
// 设置多相机模式布局
void MainWindow::setupMultiCameraLayout()
{
// 获取可用高度已排除任务栏和20px边距
int availableHeight = getAvailableHeight();
// 多相机模式下的布局调整
// detect_image: 缩小到左侧一半空间,动态计算高度
int detectImageTop = 140;
int detectImageHeight = availableHeight - detectImageTop;
ui->detect_image->setGeometry(10, detectImageTop, 940, detectImageHeight);
// detect_image_2: 显示在右侧一半空间,动态计算高度
ui->detect_image_2->setVisible(true);
ui->detect_image_2->setGeometry(970, detectImageTop, 940, detectImageHeight);
// 设备状态框架: 移动到下方左侧,调整为更高的尺寸以适应纵向排布
ui->frame_dev->setGeometry(10, 806, 180, 200);
// 检测结果列表: 移动到下方中间
ui->detect_result_list->setGeometry(196, 806, 1380, 200);
// 多相机模式设置gridSize每行显示1个目标水平排列
ui->detect_result_list->setFlow(QListView::LeftToRight); // 水平排列
ui->detect_result_list->setWrapping(false); // 不换行
ui->detect_result_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->detect_result_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
int gridWidth = 540;
int gridHeight = 65;
ui->detect_result_list->setGridSize(QSize(gridWidth, gridHeight));
m_deviceStatusWidget->setCameraCount(2);
// 日志: 移动到下方右侧,动态计算高度
int detectLogTop = 806;
int detectLogHeight = availableHeight - detectLogTop;
ui->detect_log->setGeometry(1582, detectLogTop, 328, detectLogHeight);
updateStatusLog(tr("页面布局已调整为多相机模式"));
}
// 处理相机点击事件
void MainWindow::onCameraClicked(int cameraIndex)
{
if (m_presenter) {
if (!m_presenter->SelectCamera(cameraIndex)) {
updateStatusLog(tr("检测进行中,暂不能切换相机"));
}
}
}
// 设置右键菜单
void MainWindow::setupContextMenu()
{
// 创建右键菜单
m_contextMenu = new QMenu(this);
// 创建保存数据动作
m_saveDataAction = new QAction(tr("保存检测数据"), this);
m_saveDataAction->setIcon(QIcon(":/common/resource/save.png")); // 如果有保存图标的话
// 设置右键菜单的样式表,使菜单项默认显示为灰色
m_contextMenu->setStyleSheet(
"QMenu::item { color: gray; }"
"QMenu::item:enabled { color: gray; }"
"QMenu::item:disabled { color: gray; }"
);
// 添加动作到菜单
m_contextMenu->addAction(m_saveDataAction);
// 连接信号槽
connect(m_saveDataAction, &QAction::triggered, this, &MainWindow::onSaveDetectionData);
// 为两个图像视图设置右键菜单事件
ui->detect_image->setContextMenuPolicy(Qt::CustomContextMenu);
ui->detect_image_2->setContextMenuPolicy(Qt::CustomContextMenu);
// 连接右键菜单信号
connect(ui->detect_image, &QGraphicsView::customContextMenuRequested,
this, [this](const QPoint& pos) { showContextMenu(pos, ui->detect_image); });
connect(ui->detect_image_2, &QGraphicsView::customContextMenuRequested,
this, [this](const QPoint& pos) { showContextMenu(pos, ui->detect_image_2); });
}
// 显示右键菜单
void MainWindow::showContextMenu(const QPoint& pos, QGraphicsView* view)
{
// 检查是否有检测数据可以保存
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,无法保存数据"));
return;
}
// 根据视图确定相机索引
int cameraIndex = (view == ui->detect_image_2) ? 2 : 1;
// 检查是否是上次检测的相机
if (cameraIndex != m_presenter->GetDetectIndex()) {
updateStatusLog(tr("当前视图不是上次检测的相机,无法保存数据"));
return;
}
if (m_presenter->GetDetectionDataCacheSize() == 0) {
updateStatusLog(tr("没有检测数据可以保存"));
return;
}
// 显示右键菜单
m_contextMenu->exec(view->mapToGlobal(pos));
}
// 保存检测数据槽函数
void MainWindow::onSaveDetectionData()
{
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,无法保存数据"));
return;
}
// 让用户选择保存目录
QString dirPath = QFileDialog::getExistingDirectory(this,
tr("选择保存目录"),
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (dirPath.isEmpty()) {
updateStatusLog(tr("用户取消了保存操作"));
return;
}
// 生成文件名(包含时间戳和相机信息)
std::string timeStamp = CVrDateUtils::GetNowTime();
std::string fileName = "Laserline_" + std::to_string(m_presenter->GetDetectIndex()) + "_" + timeStamp + ".txt";
QString fullPath = QDir(dirPath).filePath(QString::fromStdString(fileName));
// 保存数据
if (saveDetectionDataToFile(fullPath, m_presenter->GetDetectIndex())) {
updateStatusLog(tr("检测数据和2D图像已保存基础文件%1").arg(fileName.c_str()));
} else {
updateStatusLog(tr("保存检测数据失败"));
}
}
// 保存检测数据到文件
bool MainWindow::saveDetectionDataToFile(const QString& filePath, int cameraIndex)
{
try {
// 直接调用Presenter的保存方法
int result = m_presenter->SaveDetectionDataToFile(filePath.toStdString());
if (result == 0) {
updateStatusLog(tr("检测数据保存成功"));
return true;
} else {
updateStatusLog(tr("保存数据失败,错误码:%1").arg(result));
return false;
}
} catch (const std::exception& e) {
updateStatusLog(tr("保存数据时发生异常:%1").arg(e.what()));
return false;
}
}
// 获取CPU序列号跨平台实现
QString MainWindow::getCPUSerialNumber()
{
QString serialNumber = "UNKNOWN";
#ifdef _WIN32
// Windows平台使用WMI获取CPU序列号
QProcess process;
process.start("wmic", QStringList() << "cpu" << "get" << "ProcessorId");
process.waitForFinished();
QString output = QString::fromLocal8Bit(process.readAllStandardOutput());
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
// 第一行是标题"ProcessorId",第二行是实际的序列号
if (lines.size() >= 2) {
serialNumber = lines[1].trimmed();
}
// 如果WMIC失败尝试读取注册表备用方案
if (serialNumber.isEmpty() || serialNumber == "UNKNOWN") {
QSettings settings("HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", QSettings::NativeFormat);
serialNumber = settings.value("ProcessorNameString", "UNKNOWN").toString();
// 从处理器名称中提取唯一标识
if (serialNumber != "UNKNOWN") {
// 使用处理器名称的哈希作为替代
serialNumber = QString("WIN_%1").arg(QString::number(qHash(serialNumber), 16).toUpper());
}
}
#else
// Linux/ARM Ubuntu平台从/proc/cpuinfo读取
QFile file("/proc/cpuinfo");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
QString line;
// 首先尝试查找Serial字段
while (!in.atEnd()) {
line = in.readLine();
if (line.contains("Serial", Qt::CaseInsensitive)) {
QStringList parts = line.split(':');
if (parts.size() >= 2) {
serialNumber = parts[1].trimmed();
break;
}
}
}
file.close();
}
#endif
// 转换为大写
return serialNumber.toUpper();
}
// 设置CPU序列号显示
void MainWindow::setupCPUSerialDisplay()
{
return;
QString cpuSerial = getCPUSerialNumber();
// 创建CPU序列号标签
m_cpuSerialLabel = new QLabel(QString("编号: %1").arg(cpuSerial));
m_cpuSerialLabel->setStyleSheet("color: rgb(239, 241, 245); font-size: 16px; margin-left: 16px;");
// 添加到状态栏左侧
statusBar()->addWidget(m_cpuSerialLabel);
LOG_INFO("CPU Serial Number: %s\n", cpuSerial.toStdString().c_str());
}