杰仔 ad5a7f40b5 feat: 优化双相机布局和调试数据存储
fix(螺杆定位): 修改TCP协议只输出一个目标点
2026-04-25 19:01:54 +08:00

912 lines
29 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 "XyzRpyItem.h"
#include <QGraphicsScene>
#include <QStandardItemModel>
#include <QDebug>
#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 <QMenu>
#include <QAction>
#include <QStandardPaths>
#include <QDir>
#include <QGraphicsView>
#include <QPushButton>
#include "ScrewPositionPresenter.h"
#include "ResultListLayoutHelper.h"
#include "Version.h"
#include "IVrUtils.h"
namespace {
PositionData<double> ReorderDisplayPose(const PositionData<double>& position, int poseOutputOrder)
{
PositionData<double> reordered = position;
switch (poseOutputOrder) {
case 1: // RX-RZ-RY
reordered.roll = position.roll;
reordered.pitch = position.yaw;
reordered.yaw = position.pitch;
break;
case 2: // RY-RX-RZ
reordered.roll = position.pitch;
reordered.pitch = position.roll;
reordered.yaw = position.yaw;
break;
case 3: // RY-RZ-RX
reordered.roll = position.pitch;
reordered.pitch = position.yaw;
reordered.yaw = position.roll;
break;
case 4: // RZ-RX-RY
reordered.roll = position.yaw;
reordered.pitch = position.roll;
reordered.yaw = position.pitch;
break;
case 5: // RZ-RY-RX
reordered.roll = position.yaw;
reordered.pitch = position.pitch;
reordered.yaw = position.roll;
break;
case 0: // RX-RY-RZ
default:
break;
}
return reordered;
}
} // namespace
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; }");
// 在状态栏右侧添加版本信息(包含编译时间)
QString versionWithBuildTime = QString("%1_%2%3%4%5%6%7")
.arg(GetScrewPositionFullVersion())
.arg(YEAR)
.arg(MONTH, 2, 10, QChar('0'))
.arg(DAY, 2, 10, QChar('0'))
.arg(HOUR, 2, 10, QChar('0'))
.arg(MINUTE, 2, 10, QChar('0'))
.arg(SECOND, 2, 10, QChar('0'));
QLabel* buildLabel = new QLabel(versionWithBuildTime);
buildLabel->setStyleSheet("color: rgb(239, 241, 245); font-size: 20px; margin-right: 16px;");
buildLabel->setCursor(Qt::PointingHandCursor);
buildLabel->installEventFilter(this);
statusBar()->addPermanentWidget(buildLabel);
m_versionLabel = buildLabel;
// 隐藏标题栏
setWindowFlags(Qt::FramelessWindowHint);
// 启动后自动最大化显示
this->showMaximized();
// 初始化时隐藏label_work
ui->label_work->setVisible(false);
// 初始化GraphicsScene
QGraphicsScene* scene = new QGraphicsScene(this);
ui->detect_image->setScene(scene);
// 初始化日志辅助类
m_logHelper = new DetectLogHelper(ui->detect_log, this);
// 注册自定义类型,使其能够在信号槽中跨线程传递
qRegisterMetaType<DetectionResult>("DetectionResult");
qRegisterMetaType<WorkStatus>("WorkStatus");
qRegisterMetaType<ScrewPosition>("ScrewPosition");
// 连接工作状态更新信号槽
connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel);
// 连接检测结果更新信号槽
connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay);
// 初始化右键菜单
setupContextMenu();
updateStatusLog(tr("设备开始初始化..."));
// 初始化模块
Init();
}
bool MainWindow::eventFilter(QObject* obj, QEvent* event)
{
if (obj == m_versionLabel && event->type() == QEvent::MouseButtonPress) {
const QString versionStr = m_versionLabel->text();
const QString algoVersion = m_presenter ? m_presenter->GetAlgoVersion() : QString();
AboutDialog dlg(SCREWPOSITION_APP_NAME, versionStr, algoVersion, this);
dlg.exec();
return true;
}
return QMainWindow::eventFilter(obj, event);
}
bool MainWindow::promptDetectionType(DetectionType& detectionType)
{
QMessageBox msgBox(this);
msgBox.setWindowTitle(QStringLiteral("选择检测类型"));
msgBox.setText(QStringLiteral("请选择本次检测任务"));
msgBox.setIcon(QMessageBox::Question);
msgBox.setStyleSheet(
"QMessageBox { background-color: rgb(37, 38, 42); }"
"QMessageBox QLabel { color: rgb(221, 225, 233); background-color: transparent; font-size: 18px; }"
"QMessageBox QPushButton {"
"min-width: 120px;"
"padding: 8px 16px;"
"border: 1px solid rgb(102, 102, 102);"
"border-radius: 4px;"
"background-color: rgb(64, 64, 64);"
"color: rgb(255, 255, 255);"
"font-size: 16px;"
"}"
"QMessageBox QPushButton:hover { background-color: rgb(80, 80, 80); font-size: 16px; }"
"QMessageBox QPushButton:pressed { background-color: rgb(54, 54, 54); font-size: 16px; }");
QPushButton* screwButton = msgBox.addButton(QStringLiteral("检测螺杆"), QMessageBox::AcceptRole);
QPushButton* toolDiskButton = msgBox.addButton(QStringLiteral("检测工具盘"), QMessageBox::ActionRole);
QPushButton* cancelButton = msgBox.addButton(QMessageBox::Cancel);
if (cancelButton) {
cancelButton->setText(QStringLiteral("取消"));
}
msgBox.exec();
if (msgBox.clickedButton() == screwButton) {
detectionType = DETECTION_TYPE_SCREW;
return true;
}
if (msgBox.clickedButton() == toolDiskButton) {
detectionType = DETECTION_TYPE_TOOL_DISK;
return true;
}
return false;
}
MainWindow::~MainWindow()
{
// 释放业务逻辑处理类
if (m_presenter) {
// 先清除回调,防止悬空指针
m_presenter->SetStatusCallback(static_cast<IYScrewPositionStatus*>(nullptr));
m_presenter->DeinitApp();
delete m_presenter;
m_presenter = nullptr;
}
delete ui;
}
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 ScrewPositionPresenter();
// 设置状态回调接口使用BasePresenter的模板方法
m_presenter->SetStatusCallback<IYScrewPositionStatus>(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);
// 设置列表视图模式(与 ResultListLayoutHelper 一致)
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);
ui->detect_result_list->setFlow(QListView::LeftToRight);
ui->detect_result_list->setWrapping(true);
ui->detect_result_list->setGridSize(QSize(178, 182));
// 初始化期间禁用所有功能按钮
setButtonsEnabled(false);
// 在线程中执行初始化业务逻辑
std::thread initThread([this]() {
updateStatusLog(tr("正在初始化系统..."));
int result = m_presenter->Init();
if (result != 0) {
updateStatusLog(tr("初始化失败,错误码:%1").arg(result));
} else {
updateStatusLog(tr("系统初始化完成"));
}
});
// 分离线程,让其在后台运行
initThread.detach();
}
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::addDetectionResult(const DetectionResult& result)
{
// 清空之前的所有检测结果数据
ui->detect_result_list->clear();
// 使用与 ResultListLayoutHelper 一致的 gridSize
const int gridWidth = 178;
const int gridHeight = 182;
// 根据检测类型选择标题前缀
const QString titlePrefix = (result.positions.size() > 0 && result.screwInfoList.empty())
? QStringLiteral("\u5de5\u5177\u76d8")
: QStringLiteral("\u87ba\u6746");
int poseOutputOrder = 0;
if (m_presenter && m_presenter->GetConfigManager()) {
poseOutputOrder = m_presenter->GetConfigManager()->GetConfigResult().poseOutputOrder;
}
// 收集待显示条目:先放所有目标点,再放所有接近点,保证两列并排
struct DisplayEntry {
int index;
QString prefix;
PositionData<double> position;
};
std::vector<DisplayEntry> targetEntries;
std::vector<DisplayEntry> approachEntries;
targetEntries.reserve(result.positions.size());
approachEntries.reserve(result.positions.size());
for (size_t i = 0; i < result.positions.size(); i++) {
const auto& pos = result.positions[i];
const PositionData<double> displayTarget = ReorderDisplayPose(pos, poseOutputOrder);
targetEntries.push_back({static_cast<int>(i) + 1, titlePrefix, displayTarget});
// 接近点XYZ 替换为 approachX/Y/Z姿态与目标点相同
PositionData<double> approachPos = pos;
approachPos.x = pos.approachX;
approachPos.y = pos.approachY;
approachPos.z = pos.approachZ;
const PositionData<double> displayApproach = ReorderDisplayPose(approachPos, poseOutputOrder);
approachEntries.push_back({static_cast<int>(i) + 1, QStringLiteral("接近点"), displayApproach});
}
auto addCardFn = [&](const DisplayEntry& entry) {
XyzRpyItem* resultWidget = new XyzRpyItem();
resultWidget->setAutoFillBackground(true);
resultWidget->setResultData(entry.index, entry.prefix, entry.position);
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);
};
for (const auto& e : targetEntries) {
addCardFn(e);
}
for (const auto& e : approachEntries) {
addCardFn(e);
}
}
// 状态更新槽函数
void MainWindow::OnStatusUpdate(const std::string& statusMessage)
{
LOG_DEBUG("[UI Display] Status update: %s\n", statusMessage.c_str());
updateStatusLog(QString::fromStdString(statusMessage));
}
void MainWindow::OnDetectionResult(const DetectionResult& result)
{
// 通过信号槽机制更新UI确保在主线程中执行
emit detectionResultUpdateRequested(result);
}
void MainWindow::OnCamera1StatusChanged(bool isConnected)
{
// 直接更新设备状态widget
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateCamera1Status(isConnected);
}
}
// 相机2状态更新槽函数
void MainWindow::OnCamera2StatusChanged(bool isConnected)
{
// 直接更新设备状态widget
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateCamera2Status(isConnected);
}
}
// 相机个数更新槽函数
void MainWindow::OnCameraCountChanged(int cameraCount)
{
// 设置设备状态widget中的相机数量
if (m_deviceStatusWidget) {
m_deviceStatusWidget->setCameraCount(cameraCount, true);
// 设置相机名称(从配置文件中读取)
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);
}
}
}
// 如果只有一个相机,更新状态消息
if (cameraCount < 2) {
updateStatusLog(tr("系统使用单相机模式"));
} else {
updateStatusLog(tr("系统使用双相机模式"));
}
}
// 机械臂状态更新槽函数
void MainWindow::OnRobotConnectionChanged(bool isConnected)
{
// 直接更新设备状态widget
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateRobotStatus(isConnected);
}
}
// 串口连接状态更新槽函数
void MainWindow::OnSerialConnectionChanged(bool isConnected)
{
// 直接更新设备状态widget
if (m_deviceStatusWidget) {
m_deviceStatusWidget->updateSerialStatus(isConnected);
}
}
// 工作状态更新槽函数
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 DetectionResult& result)
{
// 显示检测结果图像
updateStatusLog(tr("检测结果更新"));
displayImage(result.image);
// 更新检测结果到列表
addDetectionResult(result);
}
void MainWindow::on_btn_start_clicked()
{
// 检查Presenter是否已初始化
if (!m_presenter) {
updateStatusLog(tr("系统未初始化,请等待初始化完成"));
return;
}
// 清空检测日志,开始新的检测
DetectionType detectionType = DETECTION_TYPE_SCREW;
if (!promptDetectionType(detectionType)) {
updateStatusLog(tr("已取消开始检测"));
return;
}
clearDetectionLog();
updateStatusLog(detectionType == DETECTION_TYPE_SCREW
? tr("已选择螺杆检测")
: tr("已选择工具盘检测"));
// 使用Presenter启动检测
if (!m_presenter->TriggerDetection(-1, detectionType)) {
updateStatusLog(tr("开始检测失败"));
}
}
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);
if(nullptr == ui_dialogConfig){
ui_dialogConfig = new DialogAlgoArg(this);
// 连接对话框关闭信号
connect(ui_dialogConfig, &QDialog::finished, this, [this]() {
setButtonSelectedState(ui->btn_algo_config, false);
});
}
ui_dialogConfig->SetPresenter(m_presenter);
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);
// 打开文件选择对话框
QString fileName = QFileDialog::getOpenFileName(
this,
tr("选择调试数据文件"),
QString(),
tr("激光数据文件 (*.txt);;所有文件 (*.*)")
);
if (fileName.isEmpty()) {
// 用户取消了文件选择,恢复按钮状态
setButtonSelectedState(ui->btn_test, false);
return;
}
// 检查Presenter是否已初始化
if (!m_presenter) {
// 恢复按钮状态
setButtonSelectedState(ui->btn_test, false);
QMessageBox::warning(this, tr("错误"), tr("系统未正确初始化!"));
return;
}
// 选择检测类型
DetectionType detectionType = DETECTION_TYPE_SCREW;
if (!promptDetectionType(detectionType)) {
setButtonSelectedState(ui->btn_test, false);
updateStatusLog(tr("已取消数据复检"));
return;
}
// 清空检测日志,开始新的检测
clearDetectionLog();
updateStatusLog(detectionType == DETECTION_TYPE_SCREW
? tr("数据复检:已选择螺杆检测")
: tr("数据复检:已选择工具盘检测"));
std::thread t([this, fileName, detectionType]() {
int result = m_presenter->LoadAndDetect(fileName, detectionType);
if (result == 0) {
updateStatusLog(tr("调试数据加载和检测成功"));
} else {
QString errorMsg = tr("调试数据复检失败: %1").arg(result);
updateStatusLog(errorMsg);
}
// 测试完成后恢复按钮状态
QMetaObject::invokeMethod(this, [this]() {
setButtonSelectedState(ui->btn_test, false);
}, Qt::QueuedConnection);
});
t.detach();
}
// 设置按钮启用/禁用状态
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);
}
// 设置按钮图像
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::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->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);
// 连接右键菜单信号
connect(ui->detect_image, &QGraphicsView::customContextMenuRequested,
this, [this](const QPoint& pos) { showContextMenu(pos, ui->detect_image); });
}
// 显示右键菜单
void MainWindow::showContextMenu(const QPoint& pos, QGraphicsView* view)
{
// 检查是否有检测数据可以保存
if (!m_presenter) {
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("检测数据已保存到:%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;
}
}