From ba8847d7b4e02cf26d9f0c94e4b7d429983a47ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B5=E6=9C=B1=E9=93=AD?= Date: Sun, 20 Sep 2026 09:34:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20'WeighUI'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 邵朱铭 --- WeighUI/main.cpp | 43 ++++ WeighUI/main.qml | 51 ++++ WeighUI/weighui.cpp | 611 ++++++++++++++++++++++++++++++++++++++++++++ WeighUI/weighui.h | 79 ++++++ WeighUI/weighui.ui | 31 +++ 5 files changed, 815 insertions(+) create mode 100644 WeighUI/main.cpp create mode 100644 WeighUI/main.qml create mode 100644 WeighUI/weighui.cpp create mode 100644 WeighUI/weighui.h create mode 100644 WeighUI/weighui.ui diff --git a/WeighUI/main.cpp b/WeighUI/main.cpp new file mode 100644 index 0000000..9d71309 --- /dev/null +++ b/WeighUI/main.cpp @@ -0,0 +1,43 @@ +#include "weighui.h" + +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + // 使用 linuxfb 平台 + qputenv("QT_QPA_PLATFORM", "linuxfb"); + qputenv("QT_PLUGIN_PATH", "/userdata/lib/qt/plugins"); + + QApplication a(argc, argv); + + // ── 解析命令行参数 ── + QCommandLineParser parser; + parser.setApplicationDescription("WeighUI - 称重系统 UI"); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption domainOpt(QStringList() << "d" << "domain", + "DDS domain id (default 0).", + "id", "0"); + parser.addOption(domainOpt); + + parser.process(a); + + bool ok = false; + int domainId = parser.value(domainOpt).toInt(&ok); + if (!ok || domainId < 0) { + qWarning() << "Invalid --domain value:" << parser.value(domainOpt) + << ", fallback to 0"; + domainId = 0; + } + + qDebug() << "WeighUI 启动, DDS domain =" << domainId; + + WeighUI w(domainId); + w.show(); + return a.exec(); +} \ No newline at end of file diff --git a/WeighUI/main.qml b/WeighUI/main.qml new file mode 100644 index 0000000..9fafbdc --- /dev/null +++ b/WeighUI/main.qml @@ -0,0 +1,51 @@ +import QtQuick 2.15 + +Rectangle { + id: root + width: 800 + height: 1280 + color: "white" + + Image { + id: backgroundImage + anchors.fill: parent + source: "qrc:/123.png" + fillMode: Image.PreserveAspectFit + smooth: true + + Component.onCompleted: { + console.log("QML: 图片加载中...") + } + + onStatusChanged: { + if (status === Image.Error) { + console.log("QML: 图片加载失败") + placeholderText.visible = true + } else if (status === Image.Ready) { + console.log("QML: 图片加载成功") + } + } + } + + Text { + id: placeholderText + anchors.centerIn: parent + text: "背景图片加载失败" + color: "red" + font.pixelSize: 24 + visible: false + } + + Text { + anchors.centerIn: parent + text: "QML 界面已显示" + font.pixelSize: 40 + color: "blue" + visible: false + } + + Component.onCompleted: { + console.log("QML: 界面初始化完成") + placeholderText.visible = true + } +} diff --git a/WeighUI/weighui.cpp b/WeighUI/weighui.cpp new file mode 100644 index 0000000..9c4b9ea --- /dev/null +++ b/WeighUI/weighui.cpp @@ -0,0 +1,611 @@ +#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, 不打日志, 避免闪烁 +} \ No newline at end of file diff --git a/WeighUI/weighui.h b/WeighUI/weighui.h new file mode 100644 index 0000000..992d178 --- /dev/null +++ b/WeighUI/weighui.h @@ -0,0 +1,79 @@ +#ifndef WEIGHUI_H +#define WEIGHUI_H + +#include +#include +#include +#include +#include +#include +#include + +class DdsSubscriberApp; + +QT_BEGIN_NAMESPACE +namespace Ui { +class WeighUI; +} +QT_END_NAMESPACE + +class WeighUI : public QMainWindow +{ + Q_OBJECT + +public: + explicit WeighUI(int domainId = 0, QWidget *parent = nullptr); + ~WeighUI(); + +private slots: + void onCheckWeightInfoOk(); // 定时检查队列,有 WeightInfoOk 数据就弹窗+播放 + void onCheckWeightInfoError(); // 定时检查队列,有 WeightInfoError 数据就弹窗(样式与 WeightInfoOk 完全一致) + void onShowPopup(const QString& warningText); // 显示弹窗 + void onPopupClosed(); // 弹窗关闭后处理 + void onCheckSummaryUpdate(); // 定时检查 SummaryUpdate 队列,播放 readcardstart 提示音 + void onCheckRealTimeWeight(); // 实时重量轮询: 根据重量显示/隐藏实时弹窗 + void onEnableRealTimePopup(); // 启动5秒后启用实时弹窗 + +private: + // 实时重量显示: 与 onShowPopup 中 Warning 弹窗使用完全相同的手法 + // 离屏 800x500 横屏 -> 离屏渲染 -> 旋转 90° -> 嵌入主窗口的 500x800 容器内显示 + void startDdsSubscriber(); // 启动 DDS 订阅线程 + void stopDdsSubscriber(); // 停止 DDS 订阅线程 + void buildRealTimeWeightDisplay(); // 构造实时重量显示控件 + void destroyRealTimeWeightDisplay(); // 销毁实时重量显示控件 + QPixmap renderRealTimePopupContent(const QString& weightText); // 渲染并旋转一次实时弹窗内容 + + Ui::WeighUI *ui; + QTimer *m_closeTimer; // 关闭弹窗定时器 + QTimer *m_pollTimer; // 轮询 WeightInfoOk 队列的定时器 + QTimer *m_errorPollTimer; // 轮询 WeightInfoError 队列的定时器 + QTimer *m_summaryPollTimer; // 轮询 SummaryUpdate 队列的定时器 + QTimer *m_realTimePollTimer; // 轮询 ScaleInfo 队列的定时器(实时重量) + QTimer *m_enablePopupTimer; // 启动5秒后启用实时弹窗的定时器 + QLabel *m_imageLabel; // 显示图片的Label + QWidget *m_popupWidget; // 弹窗Widget (Warning 弹窗, 单独顶层窗口, 5秒后自动关闭) + QSoundEffect *m_soundEffect; // 称重完成提示音 (sound19.wav) + QSoundEffect *m_soundReadCardStart; // 读卡成功提示音 (readcardstart.wav) + QSoundEffect *m_soundError; // 称重异常提示音 (sound51.wav) + + // 实时重量显示控件(嵌入主窗口内的子控件, 不是顶层窗口, 不受 X11/Wayland 窗口管理器干预) + // 视觉风格与 Warning 弹窗保持一致: 离屏渲染 -> rotate(90) -> 嵌入容器显示 + QWidget *m_realTimeWeightContainer; // 500x800 显示容器(嵌入到 m_imageLabel 之上) + QLabel *m_realTimeWeightImage; // 容器内的 QLabel, 显示旋转后的 pixmap + QWidget *m_realTimePopupSource; // 离屏渲染源(800x500 横屏, WA_DontShowOnScreen) + QLabel *m_realTimeSourceWeight; // 离屏源内的重量 Label + QString m_realTimeLastWeightText; // 上次渲染的文本, 文本未变时不重新渲染 + QPixmap m_realTimeLastPixmap; // 缓存的上次渲染结果(旋转后的), 文本未变时直接复用 + bool m_realTimePopupEnabled; // 是否已过5秒、可以显示实时弹窗 + bool m_realTimePopupVisible; // 实时重量显示控件当前是否可见 + + std::shared_ptr m_ddsSubscriber; + std::thread *m_ddsThread; // DDS 订阅线程 + + int m_domainId = 0; // DDS domain id (可由 --domain 覆盖) + +protected: + void closeEvent(QCloseEvent *event) override; // 关闭事件 +}; + +#endif // WEIGHUI_H \ No newline at end of file diff --git a/WeighUI/weighui.ui b/WeighUI/weighui.ui new file mode 100644 index 0000000..5d76e44 --- /dev/null +++ b/WeighUI/weighui.ui @@ -0,0 +1,31 @@ + + + WeighUI + + + + 0 + 0 + 800 + 600 + + + + WeighUI + + + + + + 0 + 0 + 800 + 27 + + + + + + + +