#include "weighui.h" #include "./ui_weighui.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dds/Subscriber.hpp" #include "dds/msg.hpp" // DdsMsgData 静态成员定义 std::queue DdsMsgData::WeightInfoOk_queue_; std::queue DdsMsgData::WeightInfoError_queue_; std::queue DdsMsgData::SummaryUpdate_queue_; std::queue DdsMsgData::ScaleInfo_queue_; std::mutex DdsMsgData::queue_cv_mtx_; // 实时弹窗重量阈值: weigh 程序中 ScaleInfo.Value 单位为吨, 10kg = 0.01 吨 static const float kWeightThresholdTon = 0.01f; // 10 kg WeighUI::WeighUI(int domainId, QWidget *parent) : QMainWindow(parent) , ui(new Ui::WeighUI) , m_imageLabel(nullptr) , m_popupWidget(nullptr) , m_ddsSubscriber(nullptr) , m_ddsThread(nullptr) , m_soundEffect(nullptr) , m_soundReadCardStart(nullptr) , m_soundError(nullptr) // 实时重量显示(嵌入主窗口内的子控件, 不进窗口管理器; 视觉与 Warning 弹窗一致: 离屏渲染 -> rotate(90)) , m_realTimeWeightContainer(nullptr) , m_realTimeWeightImage(nullptr) , m_realTimePopupSource(nullptr) , m_realTimeSourceWeight(nullptr) , m_realTimeLastWeightText() , m_realTimePopupEnabled(false) , m_realTimePopupVisible(false) , m_domainId(domainId) { qDebug() << "=== WeighUI 构造函数开始 ==="; ui->setupUi(this); qDebug() << "setupUi 完成, 窗口大小:" << width() << "x" << height(); this->setAttribute(Qt::WA_DeleteOnClose); // 设置窗口为全屏无边框(适应 800x1280 竖屏) this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint); this->resize(800, 1280); this->showFullScreen(); this->raise(); this->activateWindow(); qDebug() << "窗口大小已调整为:" << width() << "x" << height(); // 隐藏菜单栏和状态栏 ui->menubar->hide(); ui->statusbar->hide(); // 设置 centralwidget 透明并隐藏 ui->centralwidget->setStyleSheet("background-color: transparent;"); ui->centralwidget->hide(); // 创建图片显示Label m_imageLabel = new QLabel(this); m_imageLabel->setAlignment(Qt::AlignCenter); m_imageLabel->setScaledContents(true); m_imageLabel->setGeometry(0, 0, width(), height()); m_imageLabel->show(); // 加载图片并旋转90度 QString imagePath = QCoreApplication::applicationDirPath() + "/123.png"; qDebug() << "尝试加载图片:" << imagePath; QPixmap pixmap(imagePath); if (!pixmap.isNull()) { QTransform transform; transform.rotate(90); QPixmap rotatedPixmap = pixmap.transformed(transform); m_imageLabel->setPixmap(rotatedPixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); qDebug() << "图片加载并旋转成功!"; } else { qDebug() << "图片加载失败!"; } // 创建定时器 m_closeTimer = new QTimer(this); m_pollTimer = new QTimer(this); m_errorPollTimer = new QTimer(this); m_summaryPollTimer = new QTimer(this); m_realTimePollTimer = new QTimer(this); m_enablePopupTimer = new QTimer(this); connect(m_closeTimer, &QTimer::timeout, this, &WeighUI::onPopupClosed); connect(m_pollTimer, &QTimer::timeout, this, &WeighUI::onCheckWeightInfoOk); connect(m_errorPollTimer, &QTimer::timeout, this, &WeighUI::onCheckWeightInfoError); connect(m_summaryPollTimer, &QTimer::timeout, this, &WeighUI::onCheckSummaryUpdate); connect(m_realTimePollTimer, &QTimer::timeout, this, &WeighUI::onCheckRealTimeWeight); connect(m_enablePopupTimer, &QTimer::timeout, this, &WeighUI::onEnableRealTimePopup); // 加载称重完成提示音 QString soundPath = QCoreApplication::applicationDirPath() + "/voice2.0/sound19.wav"; qDebug() << "加载提示音:" << soundPath; m_soundEffect = new QSoundEffect(this); m_soundEffect->setSource(QUrl::fromLocalFile(soundPath)); m_soundEffect->setVolume(1.0f); // 加载读卡成功提示音 QString readCardSoundPath = QCoreApplication::applicationDirPath() + "/voice2.0/readcardstart.wav"; qDebug() << "加载读卡提示音:" << readCardSoundPath; m_soundReadCardStart = new QSoundEffect(this); m_soundReadCardStart->setSource(QUrl::fromLocalFile(readCardSoundPath)); m_soundReadCardStart->setVolume(1.0f); // 加载称重异常提示音 QString errorSoundPath = QCoreApplication::applicationDirPath() + "/voice2.0/sound51.wav"; qDebug() << "加载称重异常提示音:" << errorSoundPath; m_soundError = new QSoundEffect(this); m_soundError->setSource(QUrl::fromLocalFile(errorSoundPath)); m_soundError->setVolume(1.0f); // 构造实时重量显示控件(嵌入主窗口内的子控件, 默认隐藏) buildRealTimeWeightDisplay(); // 启动 DDS 订阅 WeightInfoOk startDdsSubscriber(); // 启动轮询定时器(每 200ms 检查队列) m_pollTimer->start(200); m_errorPollTimer->start(200); m_summaryPollTimer->start(200); m_realTimePollTimer->start(200); // 启动5秒后启用实时弹窗的定时器 m_enablePopupTimer->setSingleShot(true); m_enablePopupTimer->start(5000); qDebug() << "=== WeighUI 构造函数结束(等待 WeightInfoOk 触发弹窗)==="; } WeighUI::~WeighUI() { stopDdsSubscriber(); destroyRealTimeWeightDisplay(); delete ui; } void WeighUI::startDdsSubscriber() { try { qDebug() << "DDS Subscriber 启动, domainId=" << m_domainId; m_ddsSubscriber = std::make_shared(m_domainId); m_ddsThread = new std::thread([this]() { m_ddsSubscriber->run(); }); qDebug() << "DDS Subscriber 启动成功"; } catch (const std::exception& e) { qDebug() << "DDS Subscriber 启动失败:" << e.what(); m_ddsSubscriber = nullptr; m_ddsThread = nullptr; } } void WeighUI::stopDdsSubscriber() { if (m_ddsSubscriber) { m_ddsSubscriber->stop(); } if (m_ddsThread) { if (m_ddsThread->joinable()) { m_ddsThread->join(); } delete m_ddsThread; m_ddsThread = nullptr; } m_ddsSubscriber.reset(); } void WeighUI::closeEvent(QCloseEvent *event) { stopDdsSubscriber(); if (m_pollTimer) m_pollTimer->stop(); if (m_errorPollTimer) m_errorPollTimer->stop(); if (m_summaryPollTimer) m_summaryPollTimer->stop(); if (m_realTimePollTimer) m_realTimePollTimer->stop(); if (m_enablePopupTimer) m_enablePopupTimer->stop(); if (m_closeTimer) m_closeTimer->stop(); destroyRealTimeWeightDisplay(); hide(); QTimer::singleShot(100, [this]() { deleteLater(); }); event->accept(); } void WeighUI::onCheckSummaryUpdate() { bool hasData = false; { std::unique_lock lock(DdsMsgData::queue_cv_mtx_); while (!DdsMsgData::SummaryUpdate_queue_.empty()) { // 全部清空,只关心 "有一次 SummaryUpdate" 即可触发声音(避免重复堆积) DdsMsgData::SummaryUpdate_queue_.pop(); hasData = true; } } if (hasData) { if (m_soundReadCardStart && m_soundReadCardStart->isLoaded()) { m_soundReadCardStart->stop(); m_soundReadCardStart->play(); } } } void WeighUI::onCheckWeightInfoOk() { QString warningText; bool hasData = false; { std::unique_lock lock(DdsMsgData::queue_cv_mtx_); if (!DdsMsgData::WeightInfoOk_queue_.empty()) { WeighingSystem::WeightInfoOk info = std::move(DdsMsgData::WeightInfoOk_queue_.front()); DdsMsgData::WeightInfoOk_queue_.pop(); lock.unlock(); warningText = QString::fromStdString(info.Warning()); if (warningText.isEmpty()) { warningText = "称重完成"; } hasData = true; qDebug() << "[WeighUI] 收到 WeightInfoOk, Warning =" << warningText; } } if (hasData) { // 播放提示音 if (m_soundEffect && m_soundEffect->isLoaded()) { m_soundEffect->stop(); m_soundEffect->play(); } onShowPopup(warningText); } } void WeighUI::onCheckWeightInfoError() { QString warningText; bool hasData = false; { std::unique_lock lock(DdsMsgData::queue_cv_mtx_); if (!DdsMsgData::WeightInfoError_queue_.empty()) { WeighingSystem::WeightInfoError info = std::move(DdsMsgData::WeightInfoError_queue_.front()); DdsMsgData::WeightInfoError_queue_.pop(); lock.unlock(); warningText = QString::fromStdString(info.Warning()); if (warningText.isEmpty()) { warningText = "称重异常"; } hasData = true; qDebug() << "[WeighUI] 收到 WeightInfoError, Warning =" << warningText; } } if (hasData) { // 弹窗样式与 WeightInfoOk 完全一致: 复用 onShowPopup if (m_soundError && m_soundError->isLoaded()) { m_soundError->stop(); m_soundError->play(); } onShowPopup(warningText); } } void WeighUI::onShowPopup(const QString& warningText) { if (m_popupWidget) { m_popupWidget->close(); m_popupWidget->deleteLater(); m_popupWidget = nullptr; } // 创建弹窗容器(横屏尺寸:宽800,高500) QWidget *popupSource = new QWidget(this); popupSource->setWindowFlags(Qt::FramelessWindowHint | Qt::Window); popupSource->resize(800, 500); // 内部布局:上方标题 + 下方信息(Warning 文本) QVBoxLayout *mainLayout = new QVBoxLayout(popupSource); mainLayout->setContentsMargins(20, 20, 20, 20); mainLayout->setSpacing(20); mainLayout->setAlignment(Qt::AlignCenter); // 上方:标题 QLabel *titleLabel = new QLabel("称重提示", popupSource); titleLabel->setAlignment(Qt::AlignCenter); titleLabel->setStyleSheet("font-size: 48px; font-weight: bold; color: #4169E1; background: transparent;"); QFrame *line = new QFrame(popupSource); line->setFrameShape(QFrame::HLine); line->setStyleSheet("background-color: #4169E1;"); // 下方:信息(Warning 文本) QLabel *warningLabel = new QLabel(warningText, popupSource); warningLabel->setAlignment(Qt::AlignCenter); warningLabel->setWordWrap(true); warningLabel->setStyleSheet("font-size: 42px; font-weight: bold; color: #FF4500; background: transparent;"); mainLayout->addWidget(titleLabel); mainLayout->addWidget(line); mainLayout->addWidget(warningLabel); // 把弹窗渲染成图片 popupSource->show(); popupSource->repaint(); QPixmap popupPixmap = popupSource->grab(); // 关闭原弹窗 popupSource->close(); popupSource->deleteLater(); // 旋转90度,跟图片一致 QTransform transform; transform.rotate(90); QPixmap rotatedPixmap = popupPixmap.transformed(transform, Qt::SmoothTransformation); // 创建新弹窗显示旋转后的图片 m_popupWidget = new QWidget(this); m_popupWidget->setWindowFlags(Qt::FramelessWindowHint | Qt::Window); m_popupWidget->setStyleSheet( "background-color: rgba(240, 248, 255, 240);" "border: 3px solid #4169E1;" "border-radius: 15px;" ); m_popupWidget->resize(rotatedPixmap.width(), rotatedPixmap.height()); QLabel *imageLabel = new QLabel(m_popupWidget); imageLabel->setPixmap(rotatedPixmap); imageLabel->setScaledContents(true); imageLabel->setGeometry(0, 0, rotatedPixmap.width(), rotatedPixmap.height()); m_popupWidget->move((width() - rotatedPixmap.width()) / 2, (height() - rotatedPixmap.height()) / 2); m_popupWidget->show(); // 5秒后关闭弹窗 m_closeTimer->setSingleShot(true); m_closeTimer->start(5000); } void WeighUI::onPopupClosed() { if (m_popupWidget) { m_popupWidget->close(); m_popupWidget->deleteLater(); m_popupWidget = nullptr; } } // ---------------------------------------------------------------------------- // 实时重量显示: 在主窗口内嵌入一个 500x800 的子控件, // 不再是独立的顶层窗口, 完全不经过 X11/Wayland 窗口管理器, // 避免 show/hide 与 Warning 弹窗抢焦点、避免被窗口管理器反复隐藏. // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- // 实时重量显示: 完全复用 Warning 弹窗的视觉风格 // 离屏源 800x500 横屏 -> 离屏渲染 -> 旋转 90° -> 嵌入主窗口的 500x800 容器内显示 // 容器直接挂在 m_imageLabel 下, 不是顶层窗口, 不会被 X11/Wayland 窗口管理器反复隐藏 // ---------------------------------------------------------------------------- void WeighUI::buildRealTimeWeightDisplay() { if (m_realTimeWeightContainer) { return; } // ---------- 1. 离屏渲染源 (800x500 横屏, 持久, 不 show) ---------- // 与 Warning 弹窗的 popupSource 结构完全一致: 标题 + 分隔线 + 文本 m_realTimePopupSource = new QWidget(); // 不挂任何 parent, 避免被 WeighUI 路由 m_realTimePopupSource->setAttribute(Qt::WA_DontShowOnScreen, true); // 关键: 用 setFixedSize 锁定 800x500, 不要让 layout/adjustSize 把它缩成 251x211 m_realTimePopupSource->setFixedSize(800, 500); QVBoxLayout *srcLayout = new QVBoxLayout(m_realTimePopupSource); srcLayout->setContentsMargins(20, 20, 20, 20); srcLayout->setSpacing(20); srcLayout->setAlignment(Qt::AlignCenter); // 标题 (与 Warning 弹窗标题样式一致) QLabel *srcTitle = new QLabel(QStringLiteral("实时称重"), m_realTimePopupSource); srcTitle->setAlignment(Qt::AlignCenter); srcTitle->setStyleSheet( "font-size: 48px;" "font-weight: bold;" "color: #4169E1;" "background: transparent;" ); srcLayout->addWidget(srcTitle); // 分隔线 (与 Warning 弹窗一致) QFrame *srcLine = new QFrame(m_realTimePopupSource); srcLine->setFrameShape(QFrame::HLine); srcLine->setStyleSheet("background-color: #4169E1;"); srcLayout->addWidget(srcLine); // 重量 Label (与 Warning 弹窗中 warning 文本一致) m_realTimeSourceWeight = new QLabel(QStringLiteral("重量: -- kg"), m_realTimePopupSource); m_realTimeSourceWeight->setAlignment(Qt::AlignCenter); m_realTimeSourceWeight->setWordWrap(true); m_realTimeSourceWeight->setStyleSheet( "font-size: 42px;" "font-weight: bold;" "color: #FF4500;" "background: transparent;" ); srcLayout->addWidget(m_realTimeSourceWeight); // 强制布局完成, 这样 render() 能拿到正确尺寸 m_realTimePopupSource->adjustSize(); // ---------- 2. 显示容器 (500x800, 嵌入 m_imageLabel) ---------- if (m_imageLabel) { m_realTimeWeightContainer = new QWidget(m_imageLabel); } else { m_realTimeWeightContainer = new QWidget(this); } m_realTimeWeightContainer->setFixedSize(500, 800); m_realTimeWeightContainer->setStyleSheet( "QWidget {" " background-color: rgb(240, 248, 255);" " border: 3px solid #4169E1;" " border-radius: 15px;" "}" ); m_realTimeWeightContainer->setAutoFillBackground(true); // 容器内只有一个 QLabel, 显示旋转后的 pixmap m_realTimeWeightImage = new QLabel(m_realTimeWeightContainer); m_realTimeWeightImage->setScaledContents(true); m_realTimeWeightImage->setAlignment(Qt::AlignCenter); m_realTimeWeightImage->setGeometry(0, 0, 500, 800); // 把容器放到 m_imageLabel 中央 int cx = 0, cy = 0; if (m_imageLabel) { cx = (m_imageLabel->width() - 500) / 2; cy = (m_imageLabel->height() - 800) / 2; } else { cx = (width() - 500) / 2; cy = (height() - 800) / 2; } m_realTimeWeightContainer->setGeometry(cx, cy, 500, 800); // 初始渲染一次 (空文本), 让首帧就有图 { QPixmap pixmap = renderRealTimePopupContent(QStringLiteral("-- kg")); m_realTimeWeightImage->setPixmap(pixmap); } // 初始隐藏, 等 onCheckRealTimeWeight 触发显示 m_realTimeWeightContainer->hide(); m_realTimePopupVisible = false; qDebug() << "[WeighUI] buildRealTimeWeightDisplay: container" << m_realTimeWeightContainer << "parent=" << m_realTimeWeightContainer->parent() << "size=" << m_realTimeWeightContainer->size() << "source=" << m_realTimePopupSource << "sourceSize=" << m_realTimePopupSource->size(); } void WeighUI::destroyRealTimeWeightDisplay() { if (m_realTimeWeightContainer) { m_realTimeWeightContainer->deleteLater(); m_realTimeWeightContainer = nullptr; } m_realTimeWeightImage = nullptr; if (m_realTimePopupSource) { m_realTimePopupSource->deleteLater(); m_realTimePopupSource = nullptr; } m_realTimeSourceWeight = nullptr; m_realTimeLastWeightText.clear(); m_realTimePopupVisible = false; } void WeighUI::onEnableRealTimePopup() { m_realTimePopupEnabled = true; qDebug() << "[WeighUI] 实时重量显示已启用 (启动 5 秒后)"; // 立即触发一次轮询, 根据当前最新重量决定是否显示 onCheckRealTimeWeight(); } // 渲染一次实时弹窗内容, 返回旋转 90 度后的 QPixmap (与 Warning 弹窗同款) // 注意: 使用 m_realTimePopupSource(持久离屏源)避免每帧 new/delete 控件; // 文本未变化时复用上次缓存的 pixmap, 不重复渲染. QPixmap WeighUI::renderRealTimePopupContent(const QString& weightText) { if (!m_realTimePopupSource || !m_realTimeSourceWeight) { return QPixmap(); } QString fullText = QStringLiteral("重量: ") + weightText; if (fullText == m_realTimeLastWeightText && !m_realTimeLastWeightText.isEmpty()) { // 文本未变: 直接复用上次的 pixmap, 不重新 render/rotate // (避免每帧无意义的渲染开销, 也避免 setPixmap 重复触发 paintEvent 造成闪烁) return m_realTimeLastPixmap; } // 1) 更新离屏源上的文本 m_realTimeSourceWeight->setText(fullText); // 2) 离屏源渲染到 pixmap QPixmap sourcePixmap(m_realTimePopupSource->size()); sourcePixmap.fill(QColor(240, 248, 255)); // 与 Warning 弹窗背景一致 m_realTimePopupSource->render(&sourcePixmap); // 3) 旋转 90 度 (与 Warning 弹窗一致) QTransform transform; transform.rotate(90); QPixmap rotatedPixmap = sourcePixmap.transformed(transform, Qt::SmoothTransformation); m_realTimeLastWeightText = fullText; m_realTimeLastPixmap = rotatedPixmap; return rotatedPixmap; } void WeighUI::onCheckRealTimeWeight() { // 消费队列中所有 ScaleInfo, 只保留最新一条 float latestWeightTon = 0.0f; bool hasWeight = false; { std::unique_lock lock(DdsMsgData::queue_cv_mtx_); while (!DdsMsgData::ScaleInfo_queue_.empty()) { WeighingSystem::ScaleInfo info = std::move(DdsMsgData::ScaleInfo_queue_.front()); DdsMsgData::ScaleInfo_queue_.pop(); latestWeightTon = info.Value(); hasWeight = true; } } // 启动未满5秒: 直接返回, 显示控件保持隐藏 if (!m_realTimePopupEnabled) { return; } // 控件未构造: 防御性返回 if (!m_realTimeWeightContainer || !m_realTimeWeightImage) { return; } // 还没有收到重量数据: 保持当前状态(不要 hide, 这是上一次闪烁的根因) // DDS ScaleInfo 的发布频率和我们的轮询频率不一定同步, // 如果每 200ms 轮询时 DDS 还没发, 队列就会是空的, 之前的代码会 hide + 重置 flag, // 下一次 DDS 推到数据就又 show 一次, 形成 show/hide/show/hide 的闪烁. // 正确做法: 没有新数据时维持上一次状态不变, 直到明确收到 "<10kg" 才 hide. if (!hasWeight) { return; } // DDS ScaleInfo.Value 的单位本身就是"吨" (见 kWeightThresholdTon 的注释) // 显示也用吨, 保留 2 位小数: 1.42 吨 // - 不要 ×1000, 否则会把"1.42 吨"显示成"1420 kg" QString weightText = QString::number(latestWeightTon, 'f', 2) + QStringLiteral(" 吨"); // 重量 < 10 kg (即 < 0.01 吨) -> 隐藏控件 (这是唯一会真正 hide 的路径) if (latestWeightTon < kWeightThresholdTon) { if (m_realTimeWeightContainer->isVisible()) { m_realTimeWeightContainer->hide(); m_realTimePopupVisible = false; qDebug() << "[WeighUI] 实时重量 < 10kg, 界面隐藏, weight=" << latestWeightTon << "吨"; } return; } // 重量 >= 0.01 吨 (10 kg) -> 在主窗口内显示实时重量控件 // 关键: 离屏渲染 + 旋转 90° 与 Warning 弹窗完全一致, 视觉风格统一; // 容器嵌入 m_imageLabel, 不是顶层窗口, 不进 X11/Wayland 窗口管理器; // show() 只在"从无到有"时调一次, 后续只 setPixmap 不打日志, 避免闪烁. QPixmap pixmap = renderRealTimePopupContent(weightText); if (!pixmap.isNull() && m_realTimeWeightImage) { m_realTimeWeightImage->setPixmap(pixmap); } if (!m_realTimeWeightContainer->isVisible()) { m_realTimeWeightContainer->show(); m_realTimeWeightContainer->raise(); // 提到 m_imageLabel 内最前 m_realTimeWeightContainer->update(); // 强制重绘, 防止某些嵌入式合成器延迟 m_realTimePopupVisible = true; qDebug() << "[WeighUI] 实时重量 >= 10kg, 界面显示, weight=" << latestWeightTon << "吨" << " container.isVisible=" << m_realTimeWeightContainer->isVisible() << " pos=" << m_realTimeWeightContainer->pos() << " size=" << m_realTimeWeightContainer->size() << " pixmap.size=" << pixmap.size(); } // 已经在显示: 仅 setPixmap 更新, 不调 show, 不打日志, 避免闪烁 }