|
|
|
|
@ -0,0 +1,611 @@
|
|
|
|
|
#include "weighui.h"
|
|
|
|
|
#include "./ui_weighui.h"
|
|
|
|
|
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QPixmap>
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QCloseEvent>
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
#include <QTransform>
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QFrame>
|
|
|
|
|
|
|
|
|
|
#include <thread>
|
|
|
|
|
#include <mutex>
|
|
|
|
|
#include <queue>
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
#include "dds/Subscriber.hpp"
|
|
|
|
|
#include "dds/msg.hpp"
|
|
|
|
|
|
|
|
|
|
// DdsMsgData 静态成员定义
|
|
|
|
|
std::queue<WeighingSystem::WeightInfoOk> DdsMsgData::WeightInfoOk_queue_;
|
|
|
|
|
std::queue<WeighingSystem::WeightInfoError> DdsMsgData::WeightInfoError_queue_;
|
|
|
|
|
std::queue<WeighingSystem::SummaryUpdate> DdsMsgData::SummaryUpdate_queue_;
|
|
|
|
|
std::queue<WeighingSystem::ScaleInfo> 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<DdsSubscriberApp>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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, 不打日志, 避免闪烁
|
|
|
|
|
}
|