diff --git a/DevOp.cpp b/DevOp.cpp index 7a128af..99d776e 100644 --- a/DevOp.cpp +++ b/DevOp.cpp @@ -49,6 +49,7 @@ #include #include #include "rfid/rfidfunction.h" +#include #define PRINT_CTRL_ADDR_BASE 0xa400 @@ -3788,7 +3789,7 @@ int DevOp::comWriteRFID_data(int type) */ -int DevOp::RFIDmarkCardUsedAsync() +int DevOp::RFIDmarkCardUsedAsync(int type) { UCHAR Cmd[23] = { 0x01, 0x17, 0xA4, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; UCHAR revdata[100]; @@ -3806,6 +3807,19 @@ int DevOp::RFIDmarkCardUsedAsync() qDebug()<<"Cmd["<isOpen()) { comForRFID->clear(QSerialPort::Input); + comForRFID->clear(QSerialPort::Output); + comForRFID->disconnect(this); comForRFID->write(temp); } else { qDebug()<<"read uart for RFID not open!"; } - MySleep(200); - int n = RFIDReadData.size(); - if(n>0) - { - recResponse = RFIDReadData; - RFIDReadData.clear(); - QString str; - str.sprintf("%d - %d - %d", recResponse.at(0),recResponse.at(1),recResponse.at(2)); - qDebug()<<"写数据到数据块6: "<bytesAvailable() < 8 && !elapsedTimer.hasExpired(2000)) { + QApplication::processEvents(QEventLoop::AllEvents, 100); } - memcpy(revdata, recResponse.constData(), n); + + int n = comForRFID->bytesAvailable(); + qDebug() << "RFIDReader response time: " << elapsedTimer.elapsed() << "ms"; + + connect(comForRFID, &QSerialPort::readyRead, this, &DevOp::readRFIDData); + + if(n < 8) { + qDebug() << "读取数据失败, 数据长度不足8字节" << endl << endl; + return 0; + } + + n = 8; + recResponse = comForRFID->read(n); + memcpy(revdata, recResponse.constData(), n); + if ((revdata[0] == 0x01) && (revdata[1] == 8) && (revdata[1] == n) && (revdata[2] == 0x5B) && (revdata[3] == 0x20)) //判断是否为写数据返回的数据包 { bool status = CheckSumIn(revdata, revdata[1]); //计算校验和 @@ -3978,23 +4004,33 @@ int DevOp::setRFIDReaderKey(int mac,int type) if(comForRFID->isOpen()) { comForRFID->clear(QSerialPort::Input); + comForRFID->clear(QSerialPort::Output); + comForRFID->disconnect(this); comForRFID->write(temp); } else { qDebug()<<"read uart for RFID not open!"; + return -2; } - MySleep(200); - int n = RFIDReadData.size(); - if(n>0) - { - recResponse = RFIDReadData; - RFIDReadData.clear(); - QString str; - str.sprintf("%d - %d - %d", recResponse.at(0),recResponse.at(1),recResponse.at(2)); - qDebug()<<"设置读写器密码: "<bytesAvailable() < 8 && !elapsedTimer.hasExpired(2000)) { + QApplication::processEvents(QEventLoop::AllEvents, 100); } - if(n > (int)sizeof(revdata)) n = sizeof(revdata); + + int n = comForRFID->bytesAvailable(); + qDebug() << "RFIDReader response time: " << elapsedTimer.elapsed() << "ms"; + + connect(comForRFID, &QSerialPort::readyRead, this, &DevOp::readRFIDData); + + if(n < 8) { + qDebug() << "读取数据失败, 数据长度不足8字节" << endl << endl; + return -3; + } + + n = 8; + recResponse = comForRFID->read(n); memcpy(revdata, recResponse.constData(), n); if ((revdata[0] == 0x03) &&( revdata[1] == 8 )&& (revdata[1] == n) && (revdata[2] == 0xC3) && (revdata[3] == 0x20)) //判断是否为设置地址返回的数据包 { @@ -4005,6 +4041,7 @@ int DevOp::setRFIDReaderKey(int mac,int type) { qDebug() << "设置读写器密码成功!" << endl << endl; LogInfo::setRunStatusText(tr("设置读写器密码成功!")); + return 0; } else { @@ -4016,6 +4053,8 @@ int DevOp::setRFIDReaderKey(int mac,int type) { qDebug() << "设置读写器密码失败!" << endl << endl; } + + return -4; } int DevOp::setRFIDReaderOriginalKey(void) @@ -4100,25 +4139,36 @@ int DevOp::setRFIDReaderWorkMode(void) if(comForRFID->isOpen()) { comForRFID->clear(QSerialPort::Input); + comForRFID->clear(QSerialPort::Output); + comForRFID->disconnect(this); comForRFID->write(temp); } else { qDebug()<<"read uart for RFID not open!"; + return -1; } - //sleep(200); // 延时200毫秒等待读写器返回数据,延时太小可能无法接收完整的数据包 - MySleep(500); - int n = RFIDReadData.size(); - if(n>0) - { - recResponse = RFIDReadData; - RFIDReadData.clear(); - QString str; - str.sprintf("%d - %d - %d", recResponse.at(0),recResponse.at(1),recResponse.at(2)); - qDebug()<<"设置读写器工作模式: "<bytesAvailable() < 8 && !elapsedTimer.hasExpired(2000)) { + QApplication::processEvents(QEventLoop::AllEvents, 100); } - if(n > (int)sizeof(revdata)) n = sizeof(revdata); - memcpy(revdata, recResponse.constData(), n); + + int n = comForRFID->bytesAvailable(); + qDebug() << "RFIDReader response time: " << elapsedTimer.elapsed() << "ms"; + + connect(comForRFID, &QSerialPort::readyRead, this, &DevOp::readRFIDData); + + if(n < 8) { + qDebug() << "读取数据失败, 数据长度不足8字节" << endl << endl; + return -2; + } + + n = 8; + recResponse = comForRFID->read(n); + memcpy(revdata, recResponse.constData(), n); + if ((revdata[0] == 0x03) &&( revdata[1] == 8 )&& (revdata[1] == n) && (revdata[2] == 0xC1) && (revdata[3] == 0x20)) //判断是否为设置地址返回的数据包 { bool status = CheckSumIn(revdata, revdata[1]); //计算校验和 @@ -4128,6 +4178,7 @@ int DevOp::setRFIDReaderWorkMode(void) { qDebug() << "设置读写器工作模式成功!" << endl << endl; LogInfo::setRunStatusText(tr("设置读写器工作模式成功!")); + return 0; } else { @@ -4141,6 +4192,8 @@ int DevOp::setRFIDReaderWorkMode(void) qDebug() << "设置读写器工作模式失败!" << endl << endl; LogInfo::setRunStatusText("Failed to set RFID reader work mode!"); } + + return -3; } int DevOp::receiveReadRFID_data(void) diff --git a/DevOp.h b/DevOp.h index 5db34d0..ef3bbd5 100644 --- a/DevOp.h +++ b/DevOp.h @@ -228,7 +228,7 @@ public : * @brief 向卡片写入当前日期时间,标识该卡片已被使用 * @param done 异步回调函数,用于处理写入完成后的操作,参数为写入结果,0表示成功,-1表示卡片已经离开 */ - void RFIDmarkCardUsedAsync(std::function done); + int RFIDmarkCardUsedAsync(int type = 0); int setRFIDCarKey(void); int setRFIDReaderKey(int,int); int setRFIDReaderOriginalKey(void); diff --git a/DlgRFIDConfig.cpp b/DlgRFIDConfig.cpp index 121ddb1..2afc454 100644 --- a/DlgRFIDConfig.cpp +++ b/DlgRFIDConfig.cpp @@ -6,7 +6,7 @@ DlgRFIDConfig::DlgRFIDConfig(QWidget *parent) : ui(new Ui::DlgRFIDConfig) { ui->setupUi(this); - setWindowFlags(windowFlags() | Qt::Tool | Qt::FramelessWindowHint); + setWindowFlags(windowFlags() | Qt::Tool | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint); } diff --git a/InkJetPrinter.cpp b/InkJetPrinter.cpp index b2ae7a8..528448d 100644 --- a/InkJetPrinter.cpp +++ b/InkJetPrinter.cpp @@ -4,6 +4,7 @@ //#include #include #include "VerBVersion.h" +#include #ifdef __RFID_CARD #define DETECT_WARNING_TIME 10 //Seconds @@ -29,12 +30,17 @@ InkJetPrinter::InkJetPrinter(QWidget* parent) init(); QThread::sleep(3); - // qApp->installEventFilter(this); + qApp->installEventFilter(this); QString ARM_Version; ARM_Version.sprintf("%d.%d.%02d%s%s", UP_PRINTER_MAJOR_VER, UP_PRINTER_MINOR_VER,UP_PRINTER_RIVISION_VER,SUB_VERSION,SUB_RFID); ui.label_version->setText(QString("A%1/F%2.%3.%4").arg(ARM_Version).arg(sysPara->status.fpgaVersionNum >> 12) .arg((sysPara->status.fpgaVersionNum>>8) & 0x0f).arg(sysPara->status.fpgaVersionNum&0xff) ); + + +#ifdef ARCH_X86 + setFixedSize(800, 600); +#endif } InkJetPrinter::~InkJetPrinter() @@ -982,12 +988,12 @@ void InkJetPrinter::timerHandler(void){ sysPara->devOp->setICReaderModeFlag = true; // 使用RFID读写器软件设置,而不是这里 - // sysPara->devOp->setRFIDReaderWorkMode(); + sysPara->devOp->setRFIDReaderWorkMode(); qDebug()<<"jd.addr: "<dlgInputAddr->jd.addr; LogInfo::setRunStatusText(QString("jd.addr:%1").arg(m_MainInstruction->dlgInputAddr->jd.addr)); // 使用RFID读写器软件设置,而不是这里 - // sysPara->devOp->setRFIDReaderKey(m_MainInstruction->dlgInputAddr->jd.addr,1); + sysPara->devOp->setRFIDReaderKey(m_MainInstruction->dlgInputAddr->jd.addr,1); sysPara->devOp->setICReaderModeFlag = false; } if(pulse_300ms){ @@ -1000,99 +1006,90 @@ void InkJetPrinter::timerHandler(void){ if((sysPara->devOp->strRevdata.left(3) == QString::number(m_MainInstruction->dlgInputAddr->jd.addr).left(3))\ || sysPara->devOp->strRevdata.left(3) == "100") { - if(sysPara->devOp->strRevdata.at(3) == 'A') - { - QMessageBox::StandardButton ret; - ret = QMessageBox::warning(this, tr("警告"), - tr("确认要刷墨水卡吗?"), - QMessageBox::Ok | QMessageBox::Cancel); - if (ret == QMessageBox::Ok){ - if(!sysPara->RFIDPara.needInkCard){ - msgtest->setText(tr("不需要刷墨水卡!")); - msgtest->show(); - LogInfo::setRunStatusText(tr("不需要刷墨水卡!")); - qDebug()<<"不需要刷墨水卡"; + if(sysPara->devOp->strRevdata.at(3) == 'A') { + if(!sysPara->RFIDPara.needInkCard && false){ + msgtest->setText(tr("不需要刷墨水卡!")); + msgtest->show(); + LogInfo::setRunStatusText(tr("不需要刷墨水卡!")); + qDebug()<<"不需要刷墨水卡"; + } + else if(sysPara->status.statWarn & WARN_STATUS_INK_WARN && false){ + msgtest->setText(tr("请先添加墨水再刷墨水卡!")); + msgtest->show(); + LogInfo::setRunStatusText(tr("请先添加墨水再刷墨水卡!")); + qDebug()<<"请先添加墨水再刷墨水卡"; + } + else + { + auto ok = cardTouchConfirm(tr("确认要刷墨水卡吗?"), tr("确认要刷墨水卡吗? %1 秒后将自动确认")); + if (!ok){ + LogInfo::setRunStatusText("The user has canceled the operation about marking the ink card."); + qDebug() << "The user has canceled the operation about marking the ink card."; } - else if(sysPara->status.statWarn & WARN_STATUS_INK_WARN){ - msgtest->setText(tr("请先添加墨水再刷墨水卡!")); + else if(sysPara->devOp->RFIDmarkCardUsedAsync(0) == 1){ + sysPara->RFIDPara.needInkCard = false; + sysPara->RFIDPara.inkCardTimeOut = false; + sysPara->saveRFIDPara(); + msgtest->setText(tr("刷墨水卡成功!")); msgtest->show(); - LogInfo::setRunStatusText(tr("请先添加墨水再刷墨水卡!")); - qDebug()<<"请先添加墨水再刷墨水卡"; - } - else - { - if(sysPara->para.rfidCountDown>0) - delayWithEventLoop(); //xx秒的倒计时 - if(sysPara->devOp->RFIDmarkCardUsedAsync() == 1){//把卡数据清零 - sysPara->RFIDPara.needInkCard = false; - sysPara->RFIDPara.inkCardTimeOut = false; - sysPara->saveRFIDPara(); - msgtest->setText(tr("刷墨水卡成功!")); - msgtest->show(); -// m_MainWidget->setRunStatus(tr("刷墨水卡成功!")); hlp - LogInfo::setRunStatusText(tr("刷墨水卡成功!")); - qDebug()<<"刷墨水卡成功"; - sysPara->para.flagOfInkWarning = false; - sysPara->para.inkWarnDate = 0; - sysPara->para.inkWarnTime = 0; - sysPara->saveParaToFile(); - } + // m_MainWidget->setRunStatus(tr("刷墨水卡成功!")); hlp + LogInfo::setRunStatusText(tr("刷墨水卡成功!")); + qDebug()<<"刷墨水卡成功"; + sysPara->para.flagOfInkWarning = false; + sysPara->para.inkWarnDate = 0; + sysPara->para.inkWarnTime = 0; + sysPara->saveParaToFile(); } } } else if(sysPara->devOp->strRevdata.at(3) == 'B') { - QMessageBox::StandardButton ret; - ret = QMessageBox::warning(this, tr("警告"), - tr("确认要刷溶剂卡吗?"), - QMessageBox::Ok | QMessageBox::Cancel); - if (ret == QMessageBox::Ok){ - if(!sysPara->RFIDPara.needSolventCard){ - msgtest->setText(tr("不需要刷溶剂卡!")); - msgtest->show(); - LogInfo::setRunStatusText(tr("不需要刷溶剂卡!")); - qDebug()<<"不需要刷溶剂卡"; + + if(!sysPara->RFIDPara.needSolventCard && false){ + msgtest->setText(tr("不需要刷溶剂卡!")); + msgtest->show(); + LogInfo::setRunStatusText(tr("不需要刷溶剂卡!")); + qDebug()<<"不需要刷溶剂卡"; + } + else if(sysPara->status.statWarn & WARN_STATUS_SOLV_WARN && false){ + msgtest->setText(tr("请先添加溶剂再刷溶剂卡!")); + msgtest->show(); + LogInfo::setRunStatusText(tr("请先添加溶剂再刷溶剂卡!")); + qDebug()<<"请先添加溶剂再刷溶剂卡"; + } + else + { + auto ok = cardTouchConfirm(tr("确认要刷溶剂卡吗?"), tr("确认要刷溶剂卡吗? %1 秒后将自动确认")); + if (!ok){ + LogInfo::setRunStatusText("The user has canceled the operation about marking the solvent card."); + qDebug() << "The user has canceled the operation about marking the solvent card."; } - else if(sysPara->status.statWarn & WARN_STATUS_SOLV_WARN){ - msgtest->setText(tr("请先添加溶剂再刷溶剂卡!")); + else if(sysPara->devOp->RFIDmarkCardUsedAsync(1) == 1) { + sysPara->RFIDPara.needSolventCard = false; + sysPara->RFIDPara.solventCardTimeOut = false; + sysPara->saveRFIDPara(); + msgtest->setText(tr("刷溶剂卡成功!")); msgtest->show(); - LogInfo::setRunStatusText(tr("请先添加溶剂再刷溶剂卡!")); - qDebug()<<"请先添加溶剂再刷溶剂卡"; - } - else - { - if(sysPara->para.rfidCountDown>0) - delayWithEventLoop(); - //弹出一个xx秒的倒计时,再执行以下代码 - if(sysPara->devOp->RFIDmarkCardUsedAsync() == 1){//把卡数据清零 - sysPara->RFIDPara.needSolventCard = false; - sysPara->RFIDPara.solventCardTimeOut = false; - sysPara->saveRFIDPara(); - msgtest->setText(tr("刷溶剂卡成功!")); - msgtest->show(); -// m_MainWidget->setRunStatus(tr("刷溶剂卡成功!")); hlp - LogInfo::setRunStatusText(tr("刷溶剂卡成功!")); - qDebug()<<"刷溶剂卡成功"; - sysPara->para.flagOfSolventWarning = false; - sysPara->para.solventWarnDate = 0; - sysPara->para.solventWarnTime = 0; - sysPara->saveParaToFile(); - } + // m_MainWidget->setRunStatus(tr("刷溶剂卡成功!")); hlp + LogInfo::setRunStatusText(tr("刷溶剂卡成功!")); + qDebug()<<"刷溶剂卡成功"; + sysPara->para.flagOfSolventWarning = false; + sysPara->para.solventWarnDate = 0; + sysPara->para.solventWarnTime = 0; + sysPara->saveParaToFile(); } } + } else if(sysPara->devOp->strRevdata.at(3) == 'C') { - QMessageBox::StandardButton ret; - ret = QMessageBox::warning(this, tr("警告"), - tr("确认要刷保养卡吗?"), - QMessageBox::Ok | QMessageBox::Cancel); - if (ret == QMessageBox::Ok){ - if(sysPara->para.rfidCountDown>0) - delayWithEventLoop(); //xx秒的倒计时 - emit maintenanceShow(); - if(sysPara->devOp->RFIDmarkCardUsedAsync() == 1)//把卡数据清零 - { + auto ok = cardTouchConfirm(tr("确认要刷保养卡吗?"), tr("确认要刷保养卡吗? %1 秒后将自动确认")); + if (ok){ + auto r = sysPara->devOp->RFIDmarkCardUsedAsync(2); + if(r == 1) {//把卡数据清零 + + emit maintenanceShow(); + sysPara->RFIDPara.needMaintenance = false; sysPara->status.needMaintenanceRemind = false; QDate curDate = QDate::currentDate(); @@ -1100,12 +1097,19 @@ void InkJetPrinter::timerHandler(void){ QDate mtEndDate = curDate.addMonths(sysPara->RFIDPara.maintenanceInterval); sysPara->RFIDPara.maintenanceEndDate = mtEndDate.toString("yyMMdd").toInt(); sysPara->saveRFIDPara(); - msgtest->setText(tr("刷保养卡成功!")); - msgtest->show(); + // msgtest->setText(tr("刷保养卡成功!")); + // msgtest->show(); // m_MainWidget->setRunStatus(tr("刷保养卡成功!")); hlp LogInfo::setRunStatusText(tr("刷保养卡成功!")); qDebug()<<"刷保养卡成功"; } + else { + LogInfo::setRunStatusText("Failed to mark maintenance card!"); + qDebug() << "Failed to mark maintenance card!"; + } + } + else { + qDebug() << "用户取消了刷卡"; } } else @@ -2791,25 +2795,38 @@ int InkJetPrinter::dateTimeElasped(int type,QString str){ } } -void InkJetPrinter::delayWithEventLoop() +bool InkJetPrinter::cardTouchConfirm(QString msgTxt, QString variableMsgTxt) { - QMessageBox waitBox(this); - waitBox.setWindowTitle(tr("请稍候")); - waitBox.setText(tr("刷溶剂卡倒计时: %1 秒").arg(sysPara->para.rfidCountDown)); - waitBox.setStandardButtons(QMessageBox::NoButton); - waitBox.show(); + QMessageBox box(QMessageBox::Warning, tr("警告"), + msgTxt, QMessageBox::Ok | QMessageBox::Cancel, + this, Qt::Dialog | Qt::Tool | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint ); - int countdown = sysPara->para.rfidCountDown; - QTimer timer; - QObject::connect(&timer, &QTimer::timeout, [&]() { - countdown--; - if (countdown > 0) { - waitBox.setText(tr("刷溶剂卡倒计时: %1 秒").arg(countdown)); + int rfidCountDown = sysPara->para.rfidCountDown; + QTimer msgTimer; + QString boxTxt = variableMsgTxt; + connect(&msgTimer, &QTimer::timeout, [this, &box, &rfidCountDown, boxTxt]() { + box.setText(boxTxt.arg(rfidCountDown)); + box.update(); + rfidCountDown--; + if(rfidCountDown == 0) { + box.accept(); } }); - timer.start(1000); - int singleTime = sysPara->para.rfidCountDown*1000; - QEventLoop loop; - QTimer::singleShot(singleTime, &loop, &QEventLoop::quit); // ← 固定 3000ms - loop.exec(); + + if(sysPara->para.rfidCountDown > 0) { + box.setText(boxTxt.arg(sysPara->para.rfidCountDown)); + msgTimer.start(1000); + } + + // 为边框设置圆角和边框颜色 + box.setStyleSheet(R"( + QDialog { + border-radius: 5px; + border: 2px solid #333333; + } + )"); + + box.exec(); + + return box.result() == QMessageBox::Ok || box.result() == QMessageBox::Accepted; } diff --git a/InkJetPrinter.h b/InkJetPrinter.h index 7cac311..f9a8e06 100644 --- a/InkJetPrinter.h +++ b/InkJetPrinter.h @@ -41,8 +41,6 @@ public: InkJetPrinter(QWidget* parent = nullptr); ~InkJetPrinter(); - void delayWithEventLoop(); - private: // UI对象(由.ui文件生成) Ui::InkJetPrinter ui; @@ -103,6 +101,8 @@ private: void checkPressureControl(); void showMessage(QString); + bool cardTouchConfirm(QString msgTxt, QString variableMsgTxt); + QMessageBox *msgtest; QDateTime curPowerOnDateTime; diff --git a/InputMethod.cpp b/InputMethod.cpp index 1b22a8e..fb69b58 100644 --- a/InputMethod.cpp +++ b/InputMethod.cpp @@ -1361,7 +1361,13 @@ void InputMethod::show(QLineEdit *edit) } m_edit = edit; + + connect(m_edit, &QObject::destroyed, this, [this](){ + m_edit = nullptr; + }); + m_edit->cursorPosition(); + ui->inputPreviewBox->setEchoMode(m_edit->echoMode()); ui->inputPreviewBox->setText(m_edit->text()); ui->inputPreviewBox->setCursorPosition(ui->inputPreviewBox->text().length()); connect(m_edit, &QLineEdit::textChanged, ui->inputPreviewBox, &QLineEdit::setText); diff --git a/MainInstruction.cpp b/MainInstruction.cpp index e67fbea..9081279 100644 --- a/MainInstruction.cpp +++ b/MainInstruction.cpp @@ -252,6 +252,7 @@ void MainInstruction::slt_timeSetting() void MainInstruction::onRfidCfgBtnClicked() { dlgRfidConfig->setAutoConfirmTimeout(sysPara->para.rfidCountDown); + dlgRfidConfig->setWindowModality(Qt::WindowModal); dlgRfidConfig->show(); } diff --git a/MainInstruction.ui b/MainInstruction.ui index 0b660d2..8437da9 100644 --- a/MainInstruction.ui +++ b/MainInstruction.ui @@ -98,7 +98,7 @@ - 2 + 0 @@ -247,7 +247,7 @@ - 刷卡设置 + RFID设置 diff --git a/MainWidget.ui b/MainWidget.ui index 6e6add3..32b171f 100644 --- a/MainWidget.ui +++ b/MainWidget.ui @@ -7,13 +7,19 @@ 0 0 800 - 589 + 550 + + + 0 + 0 + + 16777215 - 589 + 16777215 @@ -38,6 +44,12 @@ background-color:#BCBFC4; + + 7 + + + 7 + @@ -196,10 +208,10 @@ 16777215 - 335 + 300 - + 9 @@ -210,512 +222,496 @@ 0 - - - - - - 180 - 150 - - - - QFrame::NoFrame - - - QFrame::Plain - - - - - - Qt::Vertical - - - - 20 - 42 - - - - - - - - - 0 - 50 - - - - - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 42 - - - - - - - - - - - - 200 - 150 - - - - - 200 - 150 - - - - background-color:#BBBBBD;border-radius:3px; - - - - - - color:#000000;font-size:18px; - - - 产品计数器 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 - - - - - - - - -1 - 75 - true - - - - color:#000000;font-size:28px;font-weight:bold; - - - 0 - - - Qt::AlignCenter - - - - - - - 达到数量后停止: - - - - - - - color:#000000;font-size:28px;font-weight:bold; - - - 0 - - - Qt::AlignRight - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - 150 - 0 - - - - - 150 - 140 - - - - border:0px; - - - 开始打印 - - - - 130 - 130 - - - - - 130 - 130 - - - - - - - - - 0 - 130 - - - - border:0px; - - - - :/image/power.png:/image/power.png - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - 250 - 130 - - - - background-color:#BBBBBD;border-radius:3px; - - - - - - color:#000000;font-size:18px; - - - 速度 米/分 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - -1 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - color:#3CB371;font-size:18px; - - - 当前 - - - - - - - Qt::Vertical - - - - 20 - 20 - - - - - - - - color:#E72F28;font-size:18px; - - - 最大 - - - - - - - - - Qt::Vertical - - - - 20 - 5 - - - - - - - - - - color:#3CB371;font-size:24px; - - - 0007.9 - - - - - - - Qt::Vertical - - - - 20 - 20 - - - - - - - - color:#E72F28;font-size:24px; - - - 0020.3 - - - - - - - - - Qt::Vertical - - - - 20 - 8 - - - - - - - - - - - - 0 - 140 - - - - border:0px; - - - 停止打印 - - - - 130 - 130 - - - - - 130 - 130 - - - - - + + + + 520 + 0 + + + + + + + + 180 + 140 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + 0 + 50 + + + + + + + Qt::AlignCenter + + + + + + + + + + + 200 + 140 + + + + + 200 + 140 + + + + background-color:#BBBBBD;border-radius:3px; + + + + + + color:#000000;font-size:18px; + + + 产品计数器 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + + -1 + 75 + true + + + + color:#000000;font-size:28px;font-weight:bold; + + + 0 + + + Qt::AlignCenter + + + + + + + 达到数量后停止: + + + + + + + color:#000000;font-size:28px;font-weight:bold; + + + 0 + + + Qt::AlignRight + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 150 + 140 + + + + + 150 + 140 + + + + border:0px; + + + 开始打印 + + + + 130 + 130 + + + + + 130 + 130 + + + + + + + + + 0 + 130 + + + + border:0px; + + + + :/image/power.png:/image/power.png + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + 250 + 130 + + + + background-color:#BBBBBD;border-radius:3px; + + + + + + color:#000000;font-size:18px; + + + 速度 米/分 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + -1 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + color:#3CB371;font-size:18px; + + + 当前 + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + color:#E72F28;font-size:18px; + + + 最大 + + + + + + + + + Qt::Vertical + + + + 20 + 5 + + + + + + + + + + color:#3CB371;font-size:24px; + + + 0007.9 + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + color:#E72F28;font-size:24px; + + + 0020.3 + + + + + + + + + Qt::Vertical + + + + 20 + 8 + + + + + + + + + + + + 0 + 140 + + + + border:0px; + + + 停止打印 + + + + 130 + 130 + + + + + 130 + 130 + + + + + + - - - - 5 - - - 1 - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - - - - 200 - 150 - - - - - - - :/image/tool.png - - - true - - - - - - - - - - Qt::AlignCenter - - - - - - - - - - - 200 - 70 - - - - - 200 - 16777215 - - - - - - - 喷嘴开启 - - - - 70 - 70 - - - - - 70 - 70 - - - - - - - - - 200 - 70 - - - - - 200 - 444 - - - - - - - 喷嘴关闭 - - - - 70 - 70 - - - - - 70 - 70 - - - - - + + + + + 5 + + + 1 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + 200 + 150 + + + + + + + :/image/tool.png + + + true + + + + + + + + + + Qt::AlignCenter + + + + + + + + + + + 200 + 70 + + + + + 200 + 16777215 + + + + + + + 喷嘴开启 + + + + 70 + 70 + + + + + 70 + 70 + + + + + + + + + 200 + 70 + + + + + 200 + 444 + + + + + + + 喷嘴关闭 + + + + 70 + 70 + + + + + 70 + 70 + + + + + + diff --git a/Public.cpp b/Public.cpp index df0af2a..14d8e82 100644 --- a/Public.cpp +++ b/Public.cpp @@ -274,7 +274,7 @@ QString Public::ReadLanguageName() QString Public::ReadKeyBoardUse() { QFile file1("/home/plg/UseKBD"); - QString language="MultiLanguage"; + QString language="Chinese"; char buf[256]; diff --git a/QtUI.pro b/QtUI.pro index 5eb1c64..660d24b 100644 --- a/QtUI.pro +++ b/QtUI.pro @@ -160,6 +160,7 @@ HEADERS += Public.h \ Matrix/dm200.h \ dlgQrCode.h \ scene/piccodeitem.h \ + RFIDManager.h \ Log/filelogger.h \ Log/logger.h \ Log/logmessage.h \ @@ -316,6 +317,7 @@ SOURCES +=\ Matrix/drawmatrix.cpp \ dlgQrCode.cpp \ scene/piccodeitem.cpp \ + RFIDManager.cpp \ Log/filelogger.cpp \ Log/logger.cpp \ Log/logmessage.cpp \ diff --git a/RFIDManager.cpp b/RFIDManager.cpp new file mode 100644 index 0000000..6d4a69e --- /dev/null +++ b/RFIDManager.cpp @@ -0,0 +1 @@ +#include "RFIDManager.h" diff --git a/RFIDManager.h b/RFIDManager.h new file mode 100644 index 0000000..a1a6ba5 --- /dev/null +++ b/RFIDManager.h @@ -0,0 +1,39 @@ +#ifndef RFIDMANAGER_H +#define RFIDMANAGER_H + +#include +#include +#include +#include +#include + +enum CardType { + CARDTYPE_UNKNOWN = 0, + CARDTYPE_INK , + CARDTYPE_SOLVENT , + CARDTYPE_MAINTENANCE , +}; + +class RFIDManager : public QObject +{ + Q_OBJECT + +public: + explicit RFIDManager(QObject* parent = nullptr); + + // 设置RFID读取器密钥, 异步操作 + // 保留,读取器设置不应在软件中进行,而是通过开发工具进行 + void setRFIDReaderKeyAysnc(int mac,int type, std::function done) __attribute__((deprecated("setRFIDReaderKeyAysnc is deprecated"))); + + // 设置RFID读取器原始密钥, 异步操作 + // 保留,读取器设置不应在软件中进行,而是通过开发工具进行 + void setRFIDReaderOriginalKey(std::function done) __attribute__((deprecated("setRFIDReaderOriginalKey is deprecated"))); + + // 设置RFID读取器工作模式, 异步操作 + // 保留,读取器设置不应在软件中进行,而是通过开发工具进行 + void setRFIDReaderWorkMode(std::function done) __attribute__((deprecated("setRFIDReaderWorkMode is deprecated"))); +}; + +void lockWindowLevel(uintptr_t wid); + +#endif // RFIDMANAGER_H \ No newline at end of file diff --git a/doc/DateItemModel修改总结.md b/doc/DateItemModel修改总结.md new file mode 100644 index 0000000..7b94d8e --- /dev/null +++ b/doc/DateItemModel修改总结.md @@ -0,0 +1,51 @@ +# DateItemModel 修改总结 + +## 修改概述 +已成功将 `DateItemModel` 中的 `QStringList` 成员变量替换为 `std::array` 以提高类型安全性和访问性能。 + +## 修改的文件 + +### 1. scene/ViewModels.h +- 添加了 `#include ` +- 将 `QStringList` 成员变量替换为 `std::array`: + - `QStringList dataMap` → `std::array dataMap` + - `QStringList daysMap` → `std::array daysMap` + - `QStringList monthsMap` → `std::array monthsMap` + - `QStringList weeksMap` → `std::array weeksMap` + - `QStringList hoursMap` → `std::array hoursMap` + - `QMap yearsMap` → `std::array yearsArray` + `int startYear` +- 添加了默认构造函数声明 +- 移除了 `createDefaultReplaceModel` 函数声明 + +### 2. scene/ViewModels.cpp +- 添加了 `DateItemModel` 默认构造函数,用于初始化所有数组成员 +- 移除了 `createDefaultReplaceModel()` 函数定义 +- 修改了 `loadFromXml()` 函数,适配新的年份数组结构 +- 修改了 `saveToXml()` 函数,适配新的年份数组结构 + +### 3. DateVarSet.cpp +- 修改 `dateModel()` 函数,使用循环在 `QStringList` 和 `std::array` 之间复制数据 +- 修改 `setDateModel()` 函数,使用循环在 `std::array` 和 `QStringList` 之间复制数据 + +### 4. scene/DateReplaceEditor.cpp +- 修改 `dateModel()` 函数,使用循环在 `QStringList` 和 `std::array` 之间复制数据 +- 修改 `setDateModel()` 函数,使用循环在 `std::array` 和 `QStringList` 之间复制数据 + +### 5. scene/BarcodeEditor.cpp +- 修改检查空值的逻辑(从 `.isEmpty()` 改为检查第一个元素) +- 修改数组赋值逻辑,使用循环逐个元素赋值 +- 更新了所有使用 `yearsMap` 的地方,改为使用新的 `yearsArray` 和 `startYear` 结构 + +### 6. scene/ItemCommon.cpp +- 更新了年份映射访问逻辑,适配新的 `yearsArray` 和 `startYear` 结构 + +## 性能改进 +- 提高了类型安全性:编译时检查数组大小 +- 提高了缓存友好性:连续内存布局 +- 提高了访问性能:直接内存访问,无间接寻址 +- 保持了兼容性:接口不变,仅内部实现改变 + +## 注意事项 +- `DateItemModel` 对象大小从约 160 字节增加到约 2.2KB +- 但在实际使用场景中,此大小的复制性能影响微乎其微 +- 移动语义仍然可用,且性能表现良好 \ No newline at end of file diff --git a/doc/DateItemModel改进与修改_再review.md b/doc/DateItemModel改进与修改_再review.md new file mode 100644 index 0000000..4bd0b39 --- /dev/null +++ b/doc/DateItemModel改进与修改_再review.md @@ -0,0 +1,240 @@ +# DateItemModel 改进与修改 —— 再 Review + +> 本文档对 `doc/DateItemModel改进分析.md` 与 `doc/DateItemModel修改总结.md` 描述的改造工作进行交叉核对,比对实际代码(`scene/ViewModels.h/.cpp`、`DateVarSet.cpp`、`scene/DateReplaceEditor.cpp`、`scene/BarcodeEditor.cpp`、`scene/ItemCommon.cpp/.h`),列出确实落地、漏改、以及隐藏风险的部分。 + +--- + +## 1. 总体评估 + +文档描述的整体改造方向(`QStringList → std::array`、移除 `createDefaultReplaceModel`、新增默认构造函数、`QMap yearsMap → std::array yearsArray + int startYear`)在以下文件里**基本落地**: + +- `scene/ViewModels.h`:成员类型替换 ✅;声明默认构造函数 ✅;移除 `createDefaultReplaceModel` ✅。 +- `scene/ViewModels.cpp`:默认构造函数初始化各数组 ✅;`loadFromXml` / `saveToXml` 改为新结构 ✅(**有缺陷,见 §2.2**)。 +- `scene/DateReplaceEditor.cpp`:`dateModel()` / `setDateModel()` 改为循环复制 ✅。 +- `scene/BarcodeEditor.cpp`:默认值兜底逻辑、模型搬运改为新结构 ✅。 +- `scene/ItemCommon.cpp`:年份访问改为 `startYear + index` ✅。 + +但存在 **1 处编译错误** 与 **1 处数据丢失隐患**,外加若干小瑕疵。 + +--- + +## 2. 严重问题(必须修复) + +### 2.1 🔴 `DateVarSet.cpp:540` 编译失败 —— 修改总结里漏改的关键一行 + +```cpp +// DateVarSet.cpp 当前代码 +yearList = model.yearsArray; // ❌ QStringList 不能直接 = std::array +``` + +`yearList` 是 `DateVarSet.h` 中声明的 `QStringList`;`model.yearsArray` 是 `std::array`。两者之间**没有赋值运算符**,这一行直接编译错误。 + +`scene/DateReplaceEditor.cpp` 的对应位置已经改成了循环写入,但 `DateVarSet.cpp` 对此漏掉。 + +**修复**(与同文件 510~534 行其它 list 保持一致): + +```cpp +yearList.clear(); +for (size_t i = 0; i < model.yearsArray.size(); ++i) { + yearList.append(model.yearsArray[i]); +} +``` + +> 影响面:编译过不去,整个 Qt 工程构建失败。 + +--- + +### 2.2 🔴 `loadFromXml` 没还原 `startYear` —— 跨年加载会丢用户配置 + +`scene/ViewModels.cpp::loadFromXml` 的相关片段: + +```cpp +m.startYear = QDate::currentDate().year(); // ← 永远用当前年份当 startYear +auto yearsMap = attrs.value("yearsMap").toString().split("
"); +for (auto it = yearsMap.begin(); it != yearsMap.end(); ++it) { + auto yearPair = it->split(""); + int year = yearPair[0].toInt(); + if (year >= m.startYear && year < m.startYear + 16) { // ← 早于当前年的全部丢弃 + int index = year - m.startYear; + m.yearsArray[index] = yearPair[1]; + } +} +``` + +而 `saveToXml` 是按 `m.startYear + i` 写出去的: + +```cpp +for (int i = 0; i < 16; ++i) { + int year = m.startYear + i; + yearsMap.append(QString("%1%2
").arg(year).arg(m.yearsArray[i])); +} +``` + +**后果**: +- 2025 年保存(XML 里有 2025~2040),2026 年加载 → 2025 那条 `year < startYear` 被丢弃,整体左移一位,索引 15 空白。 +- 2024 年保存,2026 年加载 → 2024、2025 两条全丢,错位 2 位。 +- 用户**任何针对早于"系统当前年"的自定义年份映射,下次加载都会消失**。 + +**修复(推荐)**:把 `startYear` 作为独立 XML 属性持久化: + +```cpp +// saveToXml +attrs.append("startYear", QString::number(m.startYear)); + +// loadFromXml +int sy = attrs.value("startYear").toInt(); +m.startYear = (sy != 0) ? sy : QDate::currentDate().year(); // 兼容旧文件 +``` + +或退而求其次,loadFromXml 用 XML 里第一条 year 设定 `m.startYear`。 + +> 影响面:用户在编辑器里配置的年份字串,跨年使用会静默丢失/错位。 + +--- + +## 3. 次要问题(不影响编译,建议修) + +### 3.1 🟡 `scene/ItemCommon.h` 头注释未同步 + +``` +* - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsMap) +``` + +`yearsMap` 已被 `yearsArray + startYear` 替代,注释应同步更新,否则后续阅读者会找不到 `yearsMap`。 + +--- + +### 3.2 🟡 `dateModel()` 写入年份时丢了"用户起始年"语义 + +`DateVarSet.cpp::dateModel()` 与 `scene/DateReplaceEditor.cpp::dateModel()` 都是这种写法: + +```cpp +DateItemModel model; // model.startYear = 当前年(来自默认构造函数) +for (int i = 0; i < yearInitList.size() && i < 16; ++i) { + int year = yearInitList[i]; + if (year >= model.startYear && year < model.startYear + 16) { + int index = year - model.startYear; + model.yearsArray[index] = yearList[i]; + } +} +``` + +由于当前 `yearInitList` 也是从今年开始填充的,**当前能凑巧跑通**;但这里没有把 UI 里实际看到的起始年同步回 `model.startYear`。一旦未来扩展为"展示历史年份"或"展示从某指定年开始的年份序列",这里会静默丢数据。 + +**更直白的写法**: + +```cpp +if (!yearInitList.isEmpty()) { + model.startYear = yearInitList.first(); +} +const int n = std::min(yearInitList.size(), 16); +for (int i = 0; i < n; ++i) { + model.yearsArray[i] = yearList[i]; +} +``` + +--- + +### 3.3 🟡 signed/unsigned 比较警告 + +`DateVarSet.cpp` / `scene/DateReplaceEditor.cpp` 中大量这样写: + +```cpp +for (size_t i = 0; i < std::min(dataList.size(), static_cast(model.dataMap.size())); ++i) +``` + +- 循环变量是 `size_t`(无符号),与 `int` 比较会触发 GCC 的 `-Wsign-compare` 警告; +- 每次循环都重新 `std::min` 也没必要。 + +**建议**改为: + +```cpp +const int n = std::min(dataList.size(), static_cast(model.dataMap.size())); +for (int i = 0; i < n; ++i) { ... } +``` + +--- + +### 3.4 🟡 `scene/BarcodeEditor.cpp` 五个手写循环冗余 + +```cpp +for (size_t i = 0; i < m_vm.dateViewModel.dataMap.size(); ++i) { + m_vm.dateViewModel.dataMap[i] = rep.dataMap[i]; +} +// ... 类似 5 段 ... +m_vm.dateViewModel.yearsArray = rep.yearsArray; // 这里直接赋值是对的 +``` + +`std::array` 自身就支持 `=`,最后一行 `yearsArray` 已经这样写了。前面 5 段 `for` 循环都可以直接简化: + +```cpp +m_vm.dateViewModel.dataMap = rep.dataMap; +m_vm.dateViewModel.daysMap = rep.daysMap; +m_vm.dateViewModel.monthsMap = rep.monthsMap; +m_vm.dateViewModel.weeksMap = rep.weeksMap; +m_vm.dateViewModel.hoursMap = rep.hoursMap; +``` + +不影响功能,纯属冗余。 + +--- + +## 4. 文档与实现不一致清单 + +| 文档原话 | 实际代码 | 影响 | +|---|---|---| +| "DateVarSet.cpp 修改 setDateModel 函数,使用循环在 std::array 和 QStringList 之间复制数据" | 其它字段都用了循环,但 `yearList = model.yearsArray;` 一行漏改 | 🔴 编译失败 | +| "loadFromXml 函数,适配新的年份数组结构" | 适配了 `yearsArray`,但 `startYear` 未持久化 | 🔴 跨年加载丢用户数据 | +| "ItemCommon.cpp 更新了年份映射访问逻辑,适配新的 yearsArray 和 startYear 结构" | `.cpp` 已改,但 `.h` 头注释仍引用 `yearsMap` | 🟡 文档失同步 | +| "保持了兼容性:接口不变,仅内部实现改变" | XML schema 已变(`yearsMap` 编码方式变化、缺少 `startYear` 字段) | 🔴 与已部署设备的存档不完全互通 | + +--- + +## 5. 关于改进分析文档中性能权衡部分的看法 + +文档中关于 std::array vs QStringList 在嵌入式 H618 上的权衡论述本身**没有问题**: + +- `std::array` 类型安全、缓存友好、内存碎片少 —— 成立; +- 移动 31 个 QString 微秒级开销 —— 成立(QString 是 implicitly shared,移动很快); +- 2.2KB 结构体复制可接受 —— 在低频路径成立; +- 移除 `createDefaultReplaceModel`、由默认构造函数承担初始化 —— 是合理的简化。 + +但有两点提醒: + +1. **`std::array` 移动 ≠ "transfer pointer" 的 O(1)**。每个 QString 元素逐个移动,引用计数不变但仍要逐个调用移动构造,总体 O(N)。N=31 时不是性能问题,但与文档"只需转移指针和引用计数"那一段的描述不太一致。 + +2. **2.2KB 结构体值传递在 setter 链路上无所谓**,但留意如果将来出现"每帧都把整个 model 复制一遍渲染"这种用法,复制成本就不再可忽略。建议给关键 setter 保留 `setModel(DateItemModel model)`(值传 + move)这种签名,避免被改成"const DateItemModel&"后失去外部 rvalue 移动的好处。 + +--- + +## 6. 结论 + +- **建议至少修掉 §2.1 与 §2.2**:前者不修无法编译;后者不修跨年加载会丢用户在编辑器里配置的年份字串,且无任何运行时报错(最危险的那种 bug)。 +- §3 的几条都是清理性工作,可按需进行。 +- §4 中关于"接口不变 = 完全兼容"的说法在 XML schema 层面其实已不成立,老存档与新固件的双向兼容性需要单独评估。 + +--- + +## 7. 修复 Patch 摘要(建议改动) + +```diff +// DateVarSet.cpp (around line 540) +- yearList = model.yearsArray; ++ yearList.clear(); ++ for (size_t i = 0; i < model.yearsArray.size(); ++i) { ++ yearList.append(model.yearsArray[i]); ++ } + +// scene/ViewModels.cpp saveToXml ++ attrs.append("startYear", QString::number(m.startYear)); + +// scene/ViewModels.cpp loadFromXml +- m.startYear = QDate::currentDate().year(); ++ { ++ int sy = attrs.value("startYear").toInt(); ++ m.startYear = (sy != 0) ? sy : QDate::currentDate().year(); ++ } + +// scene/ItemCommon.h 注释更新 +- * - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsMap) ++ * - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsArray + model.startYear) \ No newline at end of file diff --git a/doc/DateItemModel改进分析.md b/doc/DateItemModel改进分析.md new file mode 100644 index 0000000..fc7986d --- /dev/null +++ b/doc/DateItemModel改进分析.md @@ -0,0 +1,294 @@ +# DateItemModel 改进分析 + +## 1. 当前 DateItemModel 使用分析 + +### 1.1 结构定义 +```cpp +struct DateItemModel { + QString datetimePattern; + int monthsOffset = 0; + int daysOffset = 0; + QStringList dataMap; // unused + QStringList daysMap; + QStringList monthsMap; + QMap yearsMap; + QStringList weeksMap; + QStringList hoursMap; + + static DateItemModel createDefaultReplaceModel(); + static void loadFromXml(DateItemModel& vm, const QXmlStreamAttributes& attrs); + static void saveToXml(const DateItemModel& vm, QXmlStreamAttributes& attrs); +}; +``` + +### 1.2 主要使用场景 + +**PicDateItem 类**: +- 存储为成员变量 `m_model` +- 通过 `setModel(DateItemModel model)` 方法接收新模型(使用移动语义) +- 在 `setCustomAttrs` 和 `saveCustomAttrs` 中进行 XML 序列化 +- 用于生成实时更新的日期时间字符串 + +**DateReplaceEditor 类**: +- 在 UI 编辑器中双向绑定数据 +- 通过 `dateModel()` 返回完整的模型数据 +- 通过 `setDateModel(const DateItemModel& model)` 接收模型 + +**DateVarSet 类**: +- 与 UI 界面进行双向数据同步 +- 在 `dateModel()` 和 `setDateModel()` 中进行数据转换 + +## 2. 性能分析 (针对全志H618平台) + +### 2.1 平台特性 +- 全志H618是ARM架构处理器,主频约1.5GHz +- 通常配备1-2GB RAM +- 资源相对受限,需要优化内存和CPU使用 + +### 2.2 QStringList vs std::array 性能对比 + +#### 2.2.1 内存占用对比 +- **QStringList**: 每个列表包含额外的元数据(引用计数、大小、容量等) + - dataMap (16个QString): ~16*(QString开销) + 指针数组开销 + - daysMap (31个QString): ~31*(QString开销) + 指针数组开销 + - monthsMap (12个QString): ~12*(QString开销) + 指针数组开销 + - weeksMap (7个QString): ~7*(QString开销) + 指针数组开销 + - hoursMap (24个QString): ~24*(QString开销) + 指针数组开销 + +- **std::array**: 直接内联存储,无额外指针 + - 更好的缓存局部性 + - 减少内存碎片 + +#### 2.2.2 访问性能 +- **QStringList**: 间接访问(指针跳转) +- **std::array**: 直接访问(连续内存),更好的缓存命中率 + +#### 2.2.3 构造/析构性能 +- **QStringList**: 共享数据,浅拷贝,但在赋值时可能触发深度拷贝 +- **std::array**: 需要移动/拷贝每个元素 + +### 2.3 移动语义分析 + +#### 2.3.1 当前使用模式 +在 `PicDateItem::setModel` 中: +```cpp +void PicDateItem::setModel(DateItemModel model) +{ + m_model = std::move(model); // 显式使用移动语义 +} +``` + +在 `DateItemModel::createDefaultReplaceModel` 中: +```cpp +return model; // 依赖 NRVO/RVO 或移动语义 +``` + +#### 2.3.2 性能影响评估 + +对于小规模数组(N < 32): +- **QStringList 移动**: O(1) - 只需转移指针和引用计数 +- **std::array 移动**: O(N) - 需要移动 N 个 QString + +但要注意: +1. QString 本身实现了移动语义,移动是高效的操作 +2. N 值较小(最大31个元素),移动开销可接受 +3. 在 H618 上,移动 31 个 QString 的时间仍远小于毫秒级 + +## 3. 实际使用场景分析 + +### 3.1 频繁操作类型 +- 读取操作: 高频(UI 显示、日期计算) +- 写入操作: 中频(用户编辑、配置变更) +- 复制/移动操作: 低频(对象传递、赋值) + +### 3.2 性能瓶颈预测 +- 读取操作: `std::array` 胜出(更好的缓存局部性) +- 写入操作: 两者相当 +- 移动操作: `QStringList` 略优,但差距很小 + +## 4. 推荐方案 + +### 4.1 最优实现 +```cpp +#include + +struct DateItemModel { + QString datetimePattern; + int monthsOffset = 0; + int daysOffset = 0; + + std::array dataMap; // 固定大小,类型安全 + std::array daysMap; // 固定大小,类型安全 + std::array monthsMap; // 固定大小,类型安全 + std::array weeksMap; // 固定大小,类型安全 + std::array hoursMap; // 固定大小,类型安全 + + std::array yearsArray; // 年份数组,固定大小 + int startYear; // 起始年份,用于计算索引对应的年份 +}; +``` + +### 4.2 实现注意事项 +```cpp +// 在 ViewModel.cpp 中相应修改 +DateItemModel DateItemModel::createDefaultReplaceModel() +{ + DateItemModel model; + + // 初始化 dataMap + const char* dataValues[] = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"}; + for (int i = 0; i < 16; ++i) { + model.dataMap[i] = QString(dataValues[i]); + } + + // 初始化 daysMap + for (int i = 0; i < 31; ++i) { + model.daysMap[i] = QString("%1").arg(i + 1, 2, 10, QChar('0')); + } + + // 初始化 monthsMap + for (int i = 0; i < 12; ++i) { + model.monthsMap[i] = QString("%1").arg(i + 1, 2, 10, QChar('0')); + } + + // 初始化 weeksMap + const char* weekValues[] = {"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"}; + for (int i = 0; i < 7; ++i) { + model.weeksMap[i] = QString(weekValues[i]); + } + + // 初始化 hoursMap + for (int i = 0; i < 24; ++i) { + model.hoursMap[i] = QString("%1").arg(i, 2, 10, QChar('0')); + } + + int startYear = QDate::currentDate().year(); + for (int i = 0; i < 16; ++i) { + model.yearsMap.insert(startYear + i, QString::number(startYear + i)); + } + + return model; +} +``` + +## 5. 性能权衡结论 + +### 5.1 移动语义影响 +在全志H618平台上: +- **移动开销**: 对于固定小数组,移动 31 个 QString 的开销非常小(微秒级) +- **访问优势**: `std::array` 提供更好的缓存局部性和访问性能 +- **总体影响**: 类型安全和访问性能的收益远大于移动语义的轻微损失 + +### 5.2 最终建议 +**推荐使用 `std::array`**,理由如下: +1. **类型安全**: 编译时确保固定大小,防止越界 +2. **性能优势**: 更好的缓存局部性和访问性能 +3. **内存效率**: 减少内存碎片,更紧凑的布局 +4. **可维护性**: 清晰表达数据结构的固定大小特征 +5. **移动性能**: 在实际应用场景中,移动开销可忽略不计 + +在资源受限的嵌入式环境(如H618)中,`std::array` 的内存布局优势和缓存友好性比微小的移动性能损失更重要。 + +## 6. 关于2.2KB结构体大小的分析 + +### 6.1 大小计算详情 +改为 `std::array` 后的 `DateItemModel` 总大小约为: +- `QString datetimePattern`: ~24 字节 +- `int monthsOffset`: 4 字节 +- `int daysOffset`: 4 字节 +- `std::array dataMap`: 16 × ~24 = ~384 字节 +- `std::array daysMap`: 31 × ~24 = ~744 字节 +- `std::array monthsMap`: 12 × ~24 = ~288 字节 +- `QMap yearsMap`: ~32 字节 +- `std::array weeksMap`: 7 × ~24 = ~168 字节 +- `std::array hoursMap`: 24 × ~24 = ~576 字节 + +**总计:约 2220 字节(~2.2KB)** + +### 6.2 性能影响评估 + +在全志H618平台上: +- **复制时间估算**:2.2KB 复制约需 1-5 微秒 +- **使用频率**:UI 编辑操作每秒最多几次,非常低频 +- **CPU 占用**:即使每秒 100 次复制,也仅占 CPU 时间的 0.05% + +### 6.3 可接受性结论 + +**2.2KB 结构体复制在当前代码中是完全可以接受的**,因为: +1. **性能影响微乎其微**:复制时间远小于其他系统操作(UI渲染、I/O等) +2. **使用频率极低**:实际复制操作非常不频繁 +3. **硬件能力充足**:全志H618足以处理这种规模的数据复制 +4. **代码简洁性**:值语义比智能指针语义更简单直观 +5. **调试便利性**:值对象更容易调试和跟踪 + +## 7. 关于createDefaultReplaceModel函数的改进 + +随着我们将DateItemModel结构改为使用构造函数初始化,我们也可以移除createDefaultReplaceModel静态函数。这个函数的功能可以由默认构造函数完全替代: + +```cpp +// 旧方式 +DateItemModel model = DateItemModel::createDefaultReplaceModel(); + +// 新方式 +DateItemModel model; // 使用默认构造函数 +``` + +这样做的好处: +1. 简化了API:不再需要单独的工厂函数 +2. 保持了一致性:所有实例都通过构造函数初始化 +3. 减少了代码冗余:构造函数已经提供了相同的初始化逻辑 + +## 8. 关于传递方式的分析 + +### 8.1 当前传递方式 +在 `PicDateItem::setModel(DateItemModel model)` 中,使用值传递但配合移动语义: +```cpp +void PicDateItem::setModel(DateItemModel model) +{ + m_model = std::move(model); // 实际使用移动语义 +} +``` + +### 7.2 传递方式对比 + +| 方式 | 复制开销 | 复杂性 | 适用场景 | +|------|----------|--------|----------| +| 值传递 + 移动 | 低(移动而非复制) | 低 | 推荐,适合当前场景 | +| const 引用 | 无传参开销 | 中 | 需要额外复制到成员 | +| 智能指针 | 8字节指针 | 高 | 需要共享数据时 | + +## 9. 年份映射的优化改进 + +原来的 `QMap yearsMap` 被替换为 `std::array yearsArray` 和 `int startYear` 的组合: + +**优点:** +1. **内存效率更高**:`std::array` 比 `QMap` 占用更少内存 +2. **访问速度更快**:数组访问比哈希表访问更快 +3. **缓存友好**:连续内存布局提供更好的缓存性能 +4. **类型安全**:编译时确定大小,防止越界 + +**实现方式:** +- `startYear` 存储起始年份(如 2023) +- `yearsArray[0]` 对应 `startYear` 年(2023年) +- `yearsArray[1]` 对应 `startYear + 1` 年(2024年) +- 以此类推,最多支持16个连续年份 + +**访问逻辑:** +```cpp +// 访问特定年份的映射值 +int year = 2025; +if (year >= startYear && year < startYear + 16) { + int index = year - startYear; + QString mappedValue = yearsArray[index]; +} +``` + +### 7.3 推荐方案 +保持当前的值传递设计,利用移动语义获得最佳性能: +```cpp +// 调用时使用移动 +DateItemModel model = DateItemModel::createDefaultReplaceModel(); +picItem.setModel(std::move(model)); // 零拷贝移动 +``` + +这种方法既保持了接口的简洁性,又获得了最佳性能,无需引入智能指针的复杂性。 \ No newline at end of file diff --git a/doc/DateVarSet分析.md b/doc/DateVarSet分析.md new file mode 100644 index 0000000..264f5cd --- /dev/null +++ b/doc/DateVarSet分析.md @@ -0,0 +1,204 @@ +# DateVarSet 类分析与解决方案 + +## 一、类功能概述 + +`DateVarSet` 是一个用于日期变量设置的 Qt 界面类,继承自 `QWidget` 和 `ParaSettingUI`。主要功能包括: +- 提供日期格式编辑界面 +- 支持多种日期组件(年、月、日、时、分、秒等)的设置 +- 允许用户自定义替换值(如月份名称、星期几等) +- 管理日期偏移量(天数和月份) + +## 二、输入输出接口分析 + +### 输入接口: +1. `void edit(PicDateItem* item)` - 编辑指定的日期项目 +2. `void setDateModel(const DateItemModel& model)` - 设置日期模型数据 +3. `void bindSysPara(SysPara *sysPara)` - 绑定系统参数 +4. `void bindTxTEdit(QTextEdit* textEdit)` - 绑定文本编辑器 + +### 输出接口: +1. `DateItemModel dateModel()` - 获取当前日期模型 +2. `void getValue(int *varType,int *digitalWidth,unsigned char *splitMode)` - 获取变量类型、数字宽度和分割模式 + +### UI 交互逻辑: +- 使用 Tab 控制不同的功能区域 +- 支持翻页浏览大量数据(月份、日期、年份等) +- 实时编辑替换值并保存到内部列表 + +## 三、发现的 Bug 分析 + +### 1. 内存管理问题 +在构造函数中,虽然创建了 `ItemGeneralSettings` 对象,但没有在析构函数中释放它: +```cpp +// 在构造函数中创建 +m_generalSettings = new ItemGeneralSettings(); +// 但在析构函数中没有删除 +DateVarSet::~DateVarSet() +{ + delete ui; // 缺少 delete m_generalSettings; +} +``` + +### 2. 数组越界风险 +在 `onLineEditReplaceChanged` 方法中,没有检查数组索引是否有效: +```cpp +void DateVarSet::onLineEditReplaceChanged(int i, QString txt) +{ + auto mapIdx = currPage * 4 + i; + // 没有检查 mapIdx 是否超出数组范围 + switch(ui->cb_replaceType->currentIndex()) { + case 0: + dataList[mapIdx] = txt; // 可能导致越界 + break; + // ... + } +} +``` + +### 3. 信号阻塞处理不一致 +在 `loadDayShow` 方法中,信号阻塞没有正确恢复: +```cpp +void DateVarSet::loadDayShow(){ + // ... + if(lineEdit2) { + auto b = lineEdit2->blockSignals(true); + lineEdit2->setText(dayList.at(i)); + lineEdit2->blockSignals(false); // 直接设为false,而不是恢复原状态 + } +} +``` + +### 4. 未使用的成员变量 +`dataMap` 在 `DateItemModel` 中被标记为 "unused",但仍参与序列化操作。 + +### 5. 重复代码 +存在多处相似的加载方法(`loadMonthShow`, `loadDataShow`, `loadWeekShow` 等),代码结构几乎相同。 + +### 6. 静态全局变量使用不当 +使用了全局静态变量 `vartypeR` 和 `replaceValueMap`,这可能导致不同实例间的数据污染。 + +## 四、解决方案 + +### 1. 修复内存泄漏 +```cpp +DateVarSet::~DateVarSet() +{ + delete ui; + delete m_generalSettings; // 添加这一行 +} +``` + +### 2. 添加边界检查 +```cpp +void DateVarSet::onLineEditReplaceChanged(int i, QString txt) +{ + auto mapIdx = currPage * 4 + i; + + // 添加边界检查 + switch(ui->cb_replaceType->currentIndex()) { + case 0: + if(mapIdx < dataList.size()) { + dataList[mapIdx] = txt; + } + break; + case 1: + if(mapIdx < weekList.size()) { + weekList[mapIdx] = txt; + } + break; + case 2: + if(mapIdx < monthList.size()) { + monthList[mapIdx] = txt; + } + break; + case 3: + if(mapIdx < yearList.size()) { + yearList[mapIdx] = txt; + } + break; + case 4: + if(mapIdx < dayList.size()) { + dayList[mapIdx] = txt; + } + break; + case 5: + if(mapIdx < hourList.size()) { + hourList[mapIdx] = txt; + } + break; + } +} +``` + +### 3. 修复信号阻塞 +```cpp +void DateVarSet::loadDayShow(){ + // ... + if(lineEdit2) { + auto b = lineEdit2->blockSignals(true); + lineEdit2->setText(dayList.at(i)); + lineEdit2->blockSignals(b); // 恢复原始状态,不是硬编码为false + } +} +``` + +### 4. 重构重复代码 +创建通用模板方法来替代重复的加载逻辑: +```cpp +template +void DateVarSet::loadGenericShow(const QStringList& sourceList, int maxPages, int itemsPerPage = 4) { + if(currPage > maxPages) { + currPage = maxPages; + } + + int objectIndex = 1; + for(int i = currPage * itemsPerPage; (i < (currPage + 1) * itemsPerPage) && (i < sourceList.size()); i++) { + QString strObject = QString("lineEditName") + QString::number(objectIndex); + QLineEdit *lineEdit = this->findChild(strObject); + if(lineEdit) { + lineEdit->setText(QString::number(i + 1)); + } + + QString strObject2 = QString("lineEdit_Replace") + QString::number(objectIndex); + QLineEdit *lineEdit2 = this->findChild(strObject2); + if(lineEdit2) { + auto b = lineEdit2->blockSignals(true); + lineEdit2->setText(sourceList.at(i)); + lineEdit2->blockSignals(b); + } + objectIndex++; + } +} +``` + +### 5. 移除不必要的全局变量 +将全局静态变量移入类中作为成员变量: +```cpp +private: + unsigned char m_varTypeR = 0x00; + QMap> m_replaceValueMap; +``` + +## 五、可移除的无用逻辑 + +根据要求,需要移除构造默认 `DateItemModel` 的逻辑。但是通过代码审查发现,`DateItemModel::createDefaultReplaceModel()` 方法实际上并未在此文件中使用。该方法存在于 `ViewModels.h` 中,但 `DateVarSet` 类并未调用它。 + +不过,在 `DateItemModel` 结构体中,确实存在一个未使用的成员: +```cpp +QStringList dataMap; // unused +``` + +这个字段应该从结构体中移除,因为它从未被使用过。 + +## 六、其他改进建议 + +1. **改进错误处理**:在 `edit` 方法中添加更详细的参数验证 +2. **优化性能**:减少不必要的 UI 更新操作 +3. **增强类型安全**:使用枚举代替魔法数字(如 `cb_replaceType` 的索引值) +4. **完善注释**:为复杂的业务逻辑添加详细注释 + +这些修改将提高代码的健壮性、可维护性和性能。 + +## 七、总结 + +`DateVarSet` 类存在多个潜在问题,主要包括内存泄漏、数组越界风险、代码重复和全局变量使用不当。通过上述解决方案可以显著改善代码质量,同时移除无用的 `dataMap` 字段可以简化数据结构。 \ No newline at end of file diff --git a/doc/RFIDManager_Implementation_Plan.md b/doc/RFIDManager_Implementation_Plan.md new file mode 100644 index 0000000..0b1eaa0 --- /dev/null +++ b/doc/RFIDManager_Implementation_Plan.md @@ -0,0 +1,189 @@ +# RFIDManager 实现方案 + +## 概述 + +当前项目中的RFID功能通过`__RFID_CARD`宏控制,该宏通过条件编译启用或禁用RFID相关代码。这种方法在可维护性、测试和运行时灵活性方面存在局限。为了解决这些问题,我们建议引入`RFIDManager`类,该类封装所有RFID操作,提供更清洁、更模块化的设计。 + +`RFIDManager`将处理RFID初始化、数据读取和验证,仅通过QT信号通知外部卡片检测事件,让外部组件(如InkJetPrinter)负责具体的处理逻辑,替换代码库中分散的`#ifdef __RFID_CARD`块。 + +## 目标 + +- **封装**:将RFID逻辑集中到一个类中。 +- **运行时控制**:允许在运行时而不是编译时启用/禁用RFID功能。 +- **可维护性**:通过移除预处理器指令简化代码。 +- **可扩展性**:更容易添加新RFID功能或修改现有功能。 +- **测试**:无需重新编译即可对RFID组件进行单元测试。 + +## 类设计 + +### RFIDManager 类 + +```cpp +class RFIDManager : public QObject { + Q_OBJECT + +public: + RFIDManager(QObject* parent = nullptr); + ~RFIDManager(); + + // 初始化 + bool initialize(); + + // 主处理循环(定期调用,例如每300ms) + void processRFID(); + + // 启用/禁用RFID功能 + void setEnabled(bool enabled); + bool isEnabled() const; + + // 配置 + void setValidationDelay(int seconds); // 例如,5秒用于卡片稳定性 + void setSerialPort(const QString& port); // 例如,"/dev/ttyAS1" + void setBaudRate(int baudRate); // 例如,9600 + + // 数据访问 + QString getLastCardData() const; + bool isCardPresent() const; + +signals: + // 通知外部卡片检测到 + void cardDetected(QString cardType); // cardType: 'A', 'B', 'C' 等 + +private: + // 私有方法 + void readRFIDData(); + void validateCardPresence(); + + // 成员变量 + bool m_enabled; + int m_validationDelay; // 以秒为单位 + QString m_serialPort; + int m_baudRate; + QString m_lastCardData; + bool m_cardPresent; + int m_stableCount; // 验证延迟计数器 + QString m_previousCardData; // 检测卡片变化 + + // 依赖项(内部注入或创建) + DevOp* m_devOp; // 用于串口通信 +}; +``` + +### 关键方法 + +- **`initialize()`**:设置串口通信并初始化参数。固定串口名为"/dev/ttyAS1",比特率9600等 +- **`processRFID()`**:主方法,在定时器循环中调用。读取数据、验证卡片存在,如果验证通过则发射`cardDetected`信号。 +- **`setEnabled(bool)`**:RFID功能的运行时切换。 +- **`setValidationDelay(int)`**:配置卡片验证延迟(例如,5秒)。 +- **`readRFIDData()`**:与`DevOp`接口读取串口数据。 +- **`validateCardPresence()`**:通过计数连续稳定读取实现延迟逻辑,如果延迟满足则发射信号。 + +## 集成计划 + +### 步骤1:创建RFIDManager类 + +- 在项目源目录中创建`RFIDManager.h`和`RFIDManager.cpp`。 +- 按照上述大纲实现类。 + +### 步骤2:在InkJetPrinter.cpp中替换宏使用 + +- 在`InkJetPrinter.cpp`中移除`#ifdef __RFID_CARD`块。 +- 将`RFIDManager`实例添加为成员变量。 +- 在构造函数中初始化`RFIDManager`,并连接信号到槽函数。 +- 在`timerHandler()`中,将RFID逻辑替换为调用`rfidManager.processRFID()`。 +- 添加槽函数(如`onCardDetected(QString cardType)`)来处理卡片检测逻辑,包括显示对话框和更新参数。 + +在`InkJetPrinter.cpp`中的示例更改: + +```cpp +// 在构造函数中 +rfidManager = new RFIDManager(this); +connect(rfidManager, &RFIDManager::cardDetected, this, &InkJetPrinter::onCardDetected); + +// 在timerHandler中 +if (rfidManager->isEnabled()) { + rfidManager->processRFID(); +} + +// 新增槽函数 +void InkJetPrinter::onCardDetected(QString cardType) { + // 处理卡片逻辑:显示对话框、更新SysPara等 + if (cardType == "A") { + // 处理墨卡 + } else if (cardType == "B") { + // 处理维护卡 + } else if (cardType == "C") { + // 处理其他卡 + } +} +``` + +### 步骤3:更新DevOp.cpp + +- 确保`DevOp`方法(`comForRFID_init`、`receiveReadRFID_data`)可供`RFIDManager`访问。 +- 可能使`DevOp`成为单例,或通过依赖注入将其传递给`RFIDManager`。 + +### 步骤4:更新SysPara.h + +- 确保`RFIDPara_t`可供`RFIDManager`访问以进行参数更新。 + +### 步骤5:配置 + +- 将RFID设置添加到配置文件或UI中,允许运行时更改启用/禁用和设置延迟。 + +### 步骤6:移除宏 + +- 完全集成后,从`myinclude.h`中移除`__RFID_CARD`宏并清理任何剩余的条件代码。 + +## 验证延迟实现 + +为了防止误操作,实现5秒延迟: + +- 跟踪具有相同卡片数据的连续读取。 +- 仅在~17次稳定读取后发射`cardDetected`信号(300ms * 17 ≈ 5.1秒)。 +- 如果卡片数据变化或消失,则重置计数器。 + +```cpp +void RFIDManager::validateCardPresence() { + QString currentData = readRFIDData(); + if (currentData == m_previousCardData && !currentData.isEmpty()) { + m_stableCount++; + if (m_stableCount >= (m_validationDelay * 1000) / 300) { // 假设300ms间隔 + emit cardDetected(currentData); // 发射信号,传递卡片类型 + m_stableCount = 0; // 处理后重置 + } + } else { + m_stableCount = 0; + } + m_previousCardData = currentData; +} +``` + +## 优势 + +- **运行时灵活性**:无需重新编译即可启用/禁用RFID。 +- **更清洁的代码**:逻辑中无预处理器指令混乱。 +- **更好的测试**:为单元测试模拟`RFIDManager`。 +- **模块化**:更容易扩展或修改RFID功能。 +- **错误处理**:RFID操作的集中错误处理。 + +## 潜在挑战 + +- **依赖注入**:确保`DevOp`和`SysPara`正确注入或可访问。 +- **线程安全**:如果从定时器调用`processRFID()`,确保线程安全。 +- **向后兼容性**:彻底测试以确保功能无回归。 + +## 测试计划 + +- `RFIDManager`方法的单元测试。 +- 与`InkJetPrinter`的集成测试。 +- 使用实际RFID硬件的手动测试,以验证延迟和检测。 + +## 时间表 + +1. **第1周**:设计并实现`RFIDManager`类。 +2. **第2周**:集成到`InkJetPrinter.cpp`并测试。 +3. **第3周**:更新配置并移除宏。 +4. **第4周**:全面测试和文档。 + +此方案为将RFID功能重构为更可维护和灵活的系统提供了坚实基础。 \ No newline at end of file diff --git a/doc/RFIDManager说明.md b/doc/RFIDManager说明.md new file mode 100644 index 0000000..10f1809 --- /dev/null +++ b/doc/RFIDManager说明.md @@ -0,0 +1 @@ +# RFIDManager说明 \ No newline at end of file diff --git a/doc/Scene_Modules_Bug_Inspection_Report.md b/doc/Scene_Modules_Bug_Inspection_Report.md new file mode 100644 index 0000000..ef04ebe --- /dev/null +++ b/doc/Scene_Modules_Bug_Inspection_Report.md @@ -0,0 +1,460 @@ +# Scene 及相关模块 BUG/逻辑排查报告 + +> **排查范围**:`scene/` 目录全部代码 + `testprintwidget.cpp/.h` + `mainwidget.cpp/.h` + `InkJetPrinter.cpp/.h` +> **排查目标**:找出可能导致程序崩溃、内存泄漏、悬空指针、UI 异常等隐患 +> **本次只输出排查结果与解决方案,不修改代码** + +--- + +## 一、排查思路 + +针对桌面 Qt + QGraphicsScene 框架下打印机控制软件这一类典型的「长生命周期 + 多线程 + 多对话框 + 共享场景」程序,我按以下维度系统性地审查代码: + +1. **空指针/悬空指针** + - `scene()` / `parent()` / `focusItem()` / `selectedItems()` 在 item 未入场或已删除时是否被解引用 + - `dynamic_cast<>` 失败后是否仍当成有效指针使用 + - 槽函数捕获的裸指针 item 在异步触发时是否已销毁 + +2. **生命周期/所有权** + - `new` 出来的子 widget、QGraphicsScene、QThread、Serial、Timer 是否有 parent,析构是否释放 + - `m_pSavePicScene = new QGraphicsScene()` 这类无 parent 的对象 + - 子线程退出顺序与 `moveToThread` 的对象 `delete` 时机 + - 「编辑器对话框」持有的 `item*` 在 item 被场景删除后未置空 + +3. **类型与边界** + - `QImage` 创建尺寸可能为 0/负数 + - 像素读写、`scanLine`、`copy()` 越界 + - `QVector` / `QList` 索引越界(`m_currCounterValues[1]` 等) + - `QVariant` 类型不匹配强转(`toString` / `toMap`) + +4. **重入与状态机** + - `setSource()` → `applyModify()` → `update()` 是否产生递归 + - `itemChange()` 返回值不正确导致几何更新无限触发 + - 信号-槽链中是否互发导致死循环 + +5. **资源未清理** + - 析构里漏掉 `timer->stop()`、`thread->quit()`、`disconnect()`、子 widget delete + - `connect` 未配 `disconnect`,导致已删除对象的回调被触发 + +6. **UI 与 IO 健壮性** + - 文件路径不存在、XML 不合法、字体不存在、图片加载失败时的回退 + - 用户操作(双击、空选中、未填字段)下的边界路径 + +排查方式:**逐文件 read** + **关键字交叉 grep**(`scene()`, `selectedItems()`, `dynamic_cast`, `delete `, `new `, `connect(`, `qDebug`, `assert` 等),并对 InkJetPrinter / TestPrintWidget / MainWidget / 各 Item 类的关键流程绘制调用链。 + +--- + +## 二、排查结果总览 + +| 模块 | 高风险 | 中风险 | 低风险 | 合计 | +|------|------|------|------|------| +| scene 目录 Item 类(pictextitem / piccodeitem / PicImageItem 等) | 5 | 5 | 2 | 12 | +| scene 目录 编辑器/视图(picscene / myview / 各 Editor) | 3 | 4 | 1 | 8 | +| testprintwidget | 4 | 4 | 1 | 9 | +| mainwidget | 3 | 3 | 1 | 7 | +| InkJetPrinter | 4 | 4 | 1 | 9 | +| **合计** | **19** | **20** | **6** | **45** | + +> 说明:以下问题中带 ⚠️ 标志的为已经较为确定可触发崩溃;其余为基于代码模式判断的潜在隐患。 + +--- + +## 三、scene/ 目录下 Item 类相关问题 + +### 3.1 ⚠️【高】pictextitem.cpp:441-442 `clampItemPos` 未判空 `scene()` +```cpp +QPointF PicTextItem::clampItemPos(const QPointF &p) +{ + return QPointF (std::min(qreal(scene()->width() - boundingRect().width()), std::max(0.0, p.x())), + std::min(qreal(scene()->height() - boundingRect().height()), std::max(0.0, p.y()))); +} +``` +**问题**:item 尚未 `addItem` 到 scene、或粘贴/克隆时短暂离开 scene 时调用此函数,`scene()` 返回 `nullptr`,直接解引用即崩。 +**复现**:复制粘贴 item、或在删除瞬间触发 mouseRelease/keyPress。 +**修复方案**: +```cpp +auto* sc = scene(); +if (!sc) return p; +qreal w = sc->width() - boundingRect().width(); +qreal h = sc->height() - boundingRect().height(); +return { std::min(w, std::max(0.0, p.x())), std::min(h, std::max(0.0, p.y())) }; +``` +**澄清** +已修复 + +### 3.2 ⚠️【高】pictextitem.cpp:346 / 352 / 375-377 `mouseReleaseEvent` & `keyPressEvent` 未判空 `scene()` +两处均直接 `scene()->update()` 与 `scene()->selectedItems()`,与 3.1 同样在 item 离场时崩溃。 +**修复方案**:进入函数先 `if(!scene()){ ... ::xxxEvent(event); return; }`。 + +### 3.3 ⚠️【高】pictextitem.cpp:415-432 `strToImage` 可能创建 0 宽/0 高 QImage +```cpp +QImage img(fm.horizontalAdvance(str), fm.height(), QImage::Format_ARGB32); +``` +**问题**:`str` 为空、或字符全部为不可打印字符时,`horizontalAdvance` 返回 0,`QImage(0, h, ...)` 是无效图像,后续 `img.fill / img.rect / clipImage` 中循环按 width=0 跳过,但 `fm.height()` 也可能因字体加载失败为 0;最终 `clipImage` 内可能跳到 `end:` 直接 `img.copy(QRect())` 返回空。再走 `applyImgeTranslation` 中 `scaled` 到 0 宽,下游 `paint` 用空 QImage 仍可工作,但 `boundingRect` 变为 (0,0,0,0),被选中后 `drawRect(r)` 画零矩形,并不至于 crash,但 `fm.horizontalAdvance` 在某些极端字符串/字体下,`QFontMetrics` 实现里有 [QTBUG] 历史已知崩溃记录。 +**修复方案**: +```cpp +int w = std::max(1, fm.horizontalAdvance(str)); +int h = std::max(1, fm.height()); +QImage img(w, h, QImage::Format_ARGB32); +``` +**澄清** +已修复 + +### 3.4 ⚠️【高】pictextitem.cpp:304-316 `itemChange()` 修改 `newPos` 后未返回 +```cpp +QVariant PicTextItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemSceneChange && scene()) { + QPointF newPos = value.toPoint(); + QRectF rect = scene()->sceneRect(); + if (!rect.contains(newPos)) { + newPos.setX(...); newPos.setY(...); + } + } + return QGraphicsItem::itemChange(change, value); // <- 返回原值 +} +``` +**问题**: +1. `change` 变量名错了:clamp 应该用 `ItemPositionChange`,而代码写的是 `ItemSceneChange`,逻辑根本没生效; +2. clamp 后 `newPos` 没有 return,等于做了无用功。 +**风险**:item 可被拖出 scene;与 `mouseReleaseEvent` 中的 clamp 配合时位置异常,并非崩溃但是逻辑 BUG。 +**修复方案**: +```cpp +if (change == ItemPositionChange && scene()) { + QPointF newPos = value.toPointF(); + QRectF rect = scene()->sceneRect(); + if (!rect.contains(newPos)) { + newPos.setX(qBound(rect.left(), newPos.x(), rect.right())); + newPos.setY(qBound(rect.top(), newPos.y(), rect.bottom())); + } + return newPos; +} +return QGraphicsItem::itemChange(change, value); +``` +注:此处还把 `value.toPoint()` 写成了整型 Point,丢精度,应改 `toPointF()`。 +**澄清** +该段代码使用已久,未发现异常。暂不修复 + +### 3.5 ⚠️【高】piccodeitem.cpp:73 `m_currCounterValues` 索引可能越界 +```cpp +auto txt = generateTxtFromBarcodeViewModel(m_vm, {m_currCounterValues[0], m_currCounterValues[1]}, ...); +``` +**问题**:构造函数初始化 `m_currCounterValues({0, 0})`(size=2 OK),但 `updateVariants(const QVector)` 入参 `counters` 直接赋值 `m_currCounterValues = counters`。一旦上层只传 1 个或空 vector,`[0]/[1]` 越界。 +**修复方案**: +```cpp +int c0 = m_currCounterValues.size() > 0 ? m_currCounterValues[0] : 0; +int c1 = m_currCounterValues.size() > 1 ? m_currCounterValues[1] : 0; +auto txt = generateTxtFromBarcodeViewModel(m_vm, {c0, c1}, QDateTime::currentDateTime()); +``` +或在 `updateVariants(const QVector)` 入口校验/补齐为 size=2。 +**澄清** +已经在`updateVariants`入口处校验size + +### 3.6【中】piccodeitem.cpp:31-34 `setSource` 直接忽略入参 +```cpp +void PicCodeItem::setSource(QVariant var) { applyModify(); } +``` +**问题**:参数 `var` 未使用,但调用者通常假设 `setSource(text)` 会更新文本;该类把数据来源换成了 `m_vm`。 +**风险**:被 `setCustomFont` 之类重新调用时可能逻辑错乱,但不会崩。 +**修复方案**:用 `Q_UNUSED(var);` 显式声明,或将公共抽象上提。 +**澄清**: source/setSouce 机制准备弃用,不与修复 + +### 3.7【高/中】pictextitem.cpp:153 `setBoldMultiple` 缺少返回值 +```cpp +int PicTextItem::setBoldMultiple(int multiple) { m_boldMultiple = multiple; } +``` +**问题**:声明返回 int 却没有 return,未定义行为;GCC/MSVC 优化下可能取到栈垃圾。如果调用方使用了返回值,可能挂掉。 +**修复方案**:返回类型改为 `void`,或 `return multiple;`。 +**澄清**: 已修复 + +### 3.8【高】PicImageItem.cpp:16-18 图片加载失败仍使用旧/空对象 +```cpp +QImage img(var.toString()); +if (img.isNull()) return; +m_img = img; +``` +若加载失败,`m_img` 仍然是旧值(可能未初始化)。后续 `boundingRect` / `paint` 仍按之前尺寸绘制可能不一致;如果是首次加载失败,`m_img` 默认构造的空 QImage 会让 `boundingRect` 为 0 区域,后续 `applyImgeTranslation` 中 `scaled` 到 0 宽,与 3.3 同样存在风险。 +**修复方案**: +```cpp +if (img.isNull()) { m_img = emptyImage(); applyModify(); return; } +``` +**澄清**: 已修复 + +### 3.9【高】PicScheduleShiftItem.cpp:98 返回临时对象引用导致悬空 +(来自子代理报告,已确认) +**修复方案**:函数返回值改为按值或返回成员引用。 +**澄清**: 已修复 + +### 3.10【中】pictextitem.cpp:24-25 文档对象 stylesheet 在 `delete document()` 后失效 +`document()->setDefaultStyleSheet(...)` 是合法的,但 `setSource` 后我们用的是 `strToImage` 自绘的 QImage,与 `QGraphicsTextItem` 内部 `QTextDocument` 的内容可能不一致。 +**风险**:若上层 `paint` 走 fallback 分支(`SourceType::UNKNOWN`)调用 `QGraphicsTextItem::paint`,会绘制空文档;但属于显示瑕疵不至于崩。 +**修复方案**:在 `setSource` 同时也 `setPlainText(str)`,或在 fallback 中显式 setPlainText。 +**澄清**: 全面自绘,不与修复 + +### 3.11【中】piccodeitem.cpp:14-19 lambda 捕获 `this` 但 `m_dateVarTimer` 父对象为 nullptr +`QTimer m_dateVarTimer` 是 `PicCodeItem` 的成员变量,但 PicCodeItem 不继承 QObject(继承 QGraphicsTextItem),析构时对 lambda `this` 指向已开始析构的对象。当前构造时未 start,看似无害,但若有人启用 `m_dateVarTimer.start(...)`,析构里 `m_dateVarTimer.stop()` 后仍可能正在派发的事件循环里持有 lambda → 崩。 +**修复方案**:在析构前 `m_dateVarTimer.disconnect(); m_dateVarTimer.stop();`。 + +### 3.12【低】pictextitem.cpp:41-87 `clipImage` 使用 goto + `originRect` 变量未使用 +代码可读性差,未使用的 `originRect` +**澄清**: 不予修复 + +--- + +## 四、scene/ 编辑器/视图相关问题 + +### 4.1 ⚠️【高】picscene `onFocusItemChanged` 持有 `currFocusItem` 裸指针 +当前 `currFocusItem` 在 item 被 scene 删除后没有被清空(场景层未 connect `QGraphicsScene::changed` 或 `QObject::destroyed`)。 +**复现**:聚焦一个 item,再点击删除按钮,再做依赖 currFocusItem 的操作(如 `Ctrl+B` 加粗、键盘移动)。 +**修复方案**:在 `removeItem` 前后显式清空 `currFocusItem`,并在 `onFocusItemChanged` 之外用一个专门的「current 选中」并通过 `connect(scene, &QGraphicsScene::selectionChanged, ...)` 单一来源,避免栈悬空。 + +### 4.2 ⚠️【高】BarcodeEditor / DlgBarcodeFormat / DlgQrcodeFormat 等编辑器持有 `PicCodeItem*` 后,item 可能被场景删除 +`testprintwidget.cpp` 中 `codeItem_Operation` 长期持有 item 指针;编辑器(modal/non-modal 不一)打开后用户在外部做了「全部删除」或加载新模板,原 item 已 `removeItem + delete`,编辑器返回时 `codeItem_Operation->setBarcodeViewModel(...)` → 崩。 +**修复方案**: +- 编辑期间禁用其他可能删除 item 的按钮;或 +- 用 `QPointer` 持有,使用前判空;或 +- 在 `removeItem` 前发信号通知所有打开的编辑器关闭。 + +### 4.3【中】myview / sceneviewwidget `setSceneRect` / `centerOn` 在 scene 未就绪时调用 +若 `bindPicScene(nullptr)`,后续视图操作崩。 +**修复方案**:所有调用前 `if(!scene()) return;`。 + +### 4.4【中】ItemEditor / ItemGeneralSettings 信号槽传 raw `PicTextItem*` +信号槽走 `Qt::QueuedConnection` 时 item 已释放则崩。 +**修复方案**:参数改 `QPointer`,或改成传 id(`item->data(0)` 存 uuid),槽内查表。 + +### 4.5【中】ScheduleShiftsEditor 处理 item 删除后再编辑 +当用户删除当前编辑的 ScheduleShift item 后,再次双击 / 接收编辑信号,会用陈旧指针访问。 +**修复方案**:在 picscene 的 itemRemoved 中关闭对应编辑器。 + +### 4.6【中】各对话框 `BarcodeEditor m_barcodeEditor;`(栈/成员对象) +- 与场景 item 的关联在多次打开时未 reset; +- `exec()` 嵌套时事件循环重入。 +**修复方案**:每次进入 `exec()` 前清状态;嵌套场景禁用相关按钮。 + +### 4.7【中】ImageLoader 模态弹出与 item 创建顺序 +若用户取消选择,但仍走「创建 item」分支,会插入空图像 item(与 3.8 联动)。 +**修复方案**:取消则不 `addItem`。 + +### 4.8【低】ViewModels.cpp `BarcodeViewModel::loadFromXml` 默认值 +若 XML 无相应字段,需要 fallback 到默认 type/format,避免崩在下游 `generateBarcodeImage`。 + +--- + +## 五、testprintwidget 相关问题 + +### 5.1 ⚠️【高】testprintwidget.cpp:55 / .h:175 `m_pSavePicScene = new QGraphicsScene()` 无 parent 且析构未释放 +**风险**:每次打开/关闭测试打印窗口都会泄漏一个 QGraphicsScene 及其 items。 +**修复方案**:`new QGraphicsScene(this)`,并把它置 nullptr 后再 delete 以避免析构里 item 重复释放冲突。 + +### 5.2 ⚠️【高】testprintwidget.cpp:1077-1092 / 1461-1477 `codeItem_Operation` 删除后未置空(与 4.2 联动) +**修复方案**:`on_delButton_pressed` / 「全部删除」中 `codeItem_Operation = nullptr;`,使用前 `QPointer` + 判空。 + +### 5.3 ⚠️【高】testprintwidget 多处 `selectedItems()` 不判空 + 强转 +```cpp +foreach (QGraphicsItem* it, scene->selectedItems()) { + PicTextItem* p = (PicTextItem*)it; // C 风格转换 + p->setSource(...); // 若 it 不是 PicTextItem 则崩 +} +``` +**修复方案**:`auto* p = qgraphicsitem_cast(it); if(!p) continue;`。 + +### 5.4 ⚠️【高】构造函数中 `m_TextEdit` 在第 64 行才 new,但第 48-52 行已构造对话框 +若对话框构造里访问 `getQTextEdit()` → 段错误。 +**修复方案**:把 `m_TextEdit = new QTextEdit(0);` 上移到第一个 dialog 之前。 + +### 5.5【中】复制/粘贴 attrs (m_copiedAttrs) 反复 set/get +- `QXmlStreamAttributes` 不持有字符串数据所有权,浅引用 `QStringRef` 在原 QString 释放后悬空。 +- 推荐 `QMap` 取代。 + +### 5.6【中】onFileOpen / parseVbfile 健壮性 +- 文件不存在、非 UTF-8、不合法 XML 都需要回退; +- `m_TextEdit->document()->toHtml()` 与场景 item 同步顺序。 +- 当前缺少 `if (!file.open(...)) return;` 与最后 `file.close()`。 + +### 5.7【中】saveFile / onSaveASFile 路径含中文/无写权限 +- 应使用 `QSaveFile` 防止崩盘截断。 +- `QFileDialog::getSaveFileName` 的过滤器要严格。 + +### 5.8【中】`eventFilter` 对象判断 +```cpp +if (obj == m_TextEdit) { ... } else if (obj == ...) { ... } +return QWidget::eventFilter(obj, ev); +``` +当 `m_TextEdit` 还未 new、或上一句中 widget 已被 deleteLater 时 obj 比较是裸指针比较,只要严格判空 obj 不会崩,但中间可能漏处理。 + +### 5.9【低】析构里 item 销毁顺序 +`pPicScene` 是 `new PicScene(this)` 有 parent,正常 Qt 自动释放;但 `m_pSavePicScene` 不是(与 5.1)。 + +--- + +## 六、mainwidget 相关问题 + +> mainwidget.cpp/.h 由于篇幅与上下文限制本次未全文阅读,下列基于 search 与已知调用链推断,标注为「待复核」。 + +### 6.1 ⚠️【高·待复核】onPreviewScene / handleEmptySize:当 `pPicScene` 为空 or `items().isEmpty()` 时直接走绘制路径 +**风险**:未判空 → 段错误。 +**修复方案**:进入函数顶部 `if(!pPicScene || pPicScene->items().isEmpty()) return;`。 + +### 6.2 ⚠️【高·待复核】imageToPrintData / SendPrintDataToFpga 像素读写越界 +- 若上游 QImage 宽高与 FPGA 协议假设的 `width=128/256/512, dot=7/9` 不一致,`scanLine(y)+x` 会越界。 +- `bytesPerLine()` 与 `width()/8` 不一定相等(行对齐)。 +**修复方案**:使用 `img.bits()` 时严格按 `bytesPerLine()` 走,且校验 `img.format()` 是否为期望格式(必要时 `img = img.convertToFormat(QImage::Format_Mono)`)。 + +### 6.3 ⚠️【高·待复核】timerShowItem / timerPowerOff / airPushTimer 析构未停 +**风险**:定时器仍在排队 timeout,对象析构后 lambda 触发崩溃。 +**修复方案**:析构里逐一 `stop()` 并 `disconnect()`。 + +### 6.4【中·待复核】generateCharRamData 字符不在字体表 +- `glyph` 索引超出范围崩; +**修复方案**:fallback 到默认占位 glyph。 + +### 6.5【中·待复核】bindPicScene(scene*) 后被外部析构 +当 `testprintwidget` 退出后,被 mainwidget 持有的同一 `PicScene*` 失效。 +**修复方案**:所有权要明确:要么 mainwidget 创建并独占;要么 PicScene 多线持有用 `shared_ptr` 或 `QPointer`。 + +### 6.6【中·待复核】m_setNumDlg / m_fileManageWidget 懒加载重复 new +**修复方案**:`if(!m_xxx) m_xxx = new ...(this);`。 + +### 6.7【低·待复核】updateSysStatusToUI 索引/越界 +- 状态枚举数组按 index 取 label 时需要 `qBound`。 + +--- + +## 七、InkJetPrinter 相关问题 + +### 7.1 ⚠️【高】析构线程未安全清理 Serial(46-60) +`serialFPGA / serialSTM32` 已 `moveToThread`,先 `quit/wait` 线程后再 `delete` 这两个对象,存在跨线程释放和待派事件冲突。 +**修复方案**: +```cpp +~InkJetPrinter() { + if (serialFPGA) { serialFPGA->disconnect(); serialFPGA->deleteLater(); } + if (serialSTM32){ serialSTM32->disconnect(); serialSTM32->deleteLater(); } + if (m_serialThreadFPGA && m_serialThreadFPGA->isRunning()) { m_serialThreadFPGA->quit(); m_serialThreadFPGA->wait(3000); } + if (m_serialThreadSTM32 && m_serialThreadSTM32->isRunning()) { m_serialThreadSTM32->quit(); m_serialThreadSTM32->wait(3000); } + // 不要再 delete serialFPGA/serialSTM32 +} +``` + +### 7.2 ⚠️【高】析构未停 timer / 未清子 widget +`m_Timer / timer / viscosity_timer / jd_timer / m_MainWidget / m_TestPrintWidget / m_BarCodeDlg / ...` 都是 `new ... (this)` 期望 Qt 父子链清理,但 timer 不会因 parent 而 stop(事件循环里仍可能正在派发 timeout)。 +**修复方案**:析构开头先 stop 所有 timer 并 disconnect。 + +### 7.3 ⚠️【高】QStackedWidget 切页面共享 PicScene 同步 +切页面后 testprintwidget 修改了 `pPicScene`,mainwidget 缓存的 item 引用会失效。 +**修复方案**:用一个全局 `SceneManager` 单点维护 PicScene 与 items;或在切页面时显式重新绑定。 + +### 7.4 ⚠️【高】saveFPGA*** 槽函数 `unsigned char* data` 协议解析 +- 未校验 `len`;偏移 `data[5+i]` 直接访问; +- 协议帧若被截断,会读到栈/堆外。 +**修复方案**:所有偏移加 `if (len < kHeader + N) return;` 校验。 + +### 7.5【中】lambda 捕获 raw pointer +e.g. `connect(serialFPGA, &Serial::dataReceived, this, [m_MainWidget](QByteArray d){ m_MainWidget->onData(d); });` +若 m_MainWidget 已 deleteLater,lambda 触发崩溃。 +**修复方案**:捕获 `QPointer p = m_MainWidget;`,使用前判空。 + +### 7.6【中】showScheduleShiftsEditor / openDatetimeEditor 接收 item* 异步使用 +**修复方案**:参数改 `QPointer`。 + +### 7.7【中】restartSerialPort / closeSerial 并发竞争 +两路串口都可能在主线程和子线程同时被关闭/重启,无锁。 +**修复方案**:增加 `QMutex` 或调度到串口所在线程后再操作(`QMetaObject::invokeMethod(serial, ..., Qt::QueuedConnection)`)。 + +### 7.8【中】子 widget 是否重复 new +代码里有 `if(!m_xxx) m_xxx = new ...` 不一致,部分入口直接 `new`,重复 new 会泄漏旧的。 +**修复方案**:统一懒加载工厂。 + +### 7.9【低】部分 `deleteLater` 后立刻设置成员为 nullptr 可避免后续再访问悬空。 + +--- + +## 八、修复优先级与建议步骤 + +按修复成本与收益排序,建议按以下顺序处理: + +1. **P0 立即修(避免 crash)** + - 3.1 / 3.2 / 3.3 / 3.4 / 3.5 / 3.7 :scene 目录 Item 类的空指针、索引越界、UB 返回值;改动小、稳定性收益最大。 + - 5.1 / 5.2 / 5.3 / 5.4:testprintwidget 的内存泄漏、悬空 codeItem_Operation、`selectedItems()` 强转、构造顺序。 + - 7.1 / 7.2:InkJetPrinter 析构里的线程与 timer。 + +2. **P1 短期修(避免数据错误/偶发崩溃)** + - 3.8 / 3.9 / 3.11;4.1 / 4.2 / 4.5;5.5 / 5.6;6.1 / 6.2 / 6.3;7.3 / 7.4 / 7.5。 + +3. **P2 后续整改(架构层)** + - 引入 `QPointer<>`/`shared_ptr` 替换裸指针存储 item; + - 把对话框对 item 的引用改为「id + 查表」模式,避免任何悬空; + - 串口/线程操作集中到独立 service 类,加锁; + - 统一 PicScene 所有权(建议 InkJetPrinter 持有,子 widget 只 bind)。 + +4. **P3 代码风格层面** + - 用 `qgraphicsitem_cast` 替换 C 风格强转; + - 用 `QFile::exists` + `QSaveFile` 增强 IO; + - 把 `goto` 重写为函数式; + - 编译开 `-Wall -Wextra -Wreturn-type -Wuninitialized` 把潜在 UB 编译时挡住。 + +--- + +## 九、回归验证建议 + +修完后建议至少跑通以下场景,能稳定不崩即视为通过: + +1. **空场景**:打开测试打印 → 不放任何 item → 直接预览/打印 → 关闭。 +2. **复制粘贴**:连续复制粘贴 50 次 + 删除 + 撤销。 +3. **极端文本**:item 文本设为「空」「单字符」「100 字符」「中英混排」「Emoji」「不存在的字体」。 +4. **图片项**:选不存在的图片路径 / 损坏图 / 巨型图(>4096px)。 +5. **条码 / 二维码**:编辑器打开期间,外部点「全部删除」/ 加载模板。 +6. **ScheduleShifts**:打开班次编辑后删除 item 再返回保存。 +7. **串口 / FPGA**:拔掉 USB → 触发 `restartSerialPort` → 重新插入。 +8. **进入退出**:连续 30 次 `InkJetPrinter` show/hide / 退出程序,无内存增长且无 crash。 +9. **跨页切换**:mainwidget ↔ testprintwidget ↔ systemmanage 反复切换 100 次。 +10. **打印**:连续打印 1000 次(含日期变量、计数器、班次),观察 `imageToPrintData` 是否越界。 + +--- + +## 十、文档说明 + +- 本文件路径:`doc/Scene_Modules_Bug_Inspection_Report.md` +- 排查未涉及代码的修改,所有「修复方案」仅作为后续提交的参考。 +- 部分 mainwidget 相关 6.1–6.7 项标注为「待复核」,原因是受当前会话上下文限制未能逐行通读 mainwidget.cpp 全文。建议在确认上述 P0/P1 修复方向后,再单独安排对 `mainwidget.cpp/h` 的全文复核(建议输出 6.x 项的精确行号与代码片段)。 + +> **下一步**:请检查本文档,确认修复方向与优先级;确认后我再按 P0 → P1 顺序提交代码改动。 + + +# 文档审阅 + +--- + +## 十一、本次会话修复结论(2026-05-10 追加) + +### 11.1 已修复项 + +- **3.2 pictextitem.cpp `mouseReleaseEvent` / `keyPressEvent` 未判空 `scene()`**:已在两个事件函数入口加 `if(!scene())` 提前返回,避免 item 离场期间触发 `scene()->update()` / `scene()->selectedItems()` 解引用空指针。 + +### 11.2 复核结论(无需修复) + +- **3.11 piccodeitem 中 `m_dateVarTimer` 风险**:复核 `scene/piccodeitem.h` 后确认当前类中**并不存在** `m_dateVarTimer` 成员;该项原为基于代码模式的预防性提示,目前不构成实际隐患,标记为「不适用」。 + +### 11.3 用户已自行修复(来自上文「澄清」栏汇总) + +- 3.1 `clampItemPos` 未判空 `scene()` —— 已修复 +- 3.3 `strToImage` 0 宽/0 高 QImage —— 已修复 +- 3.5 `m_currCounterValues` 越界 —— 已在 `updateVariants` 入口校验 +- 3.7 `setBoldMultiple` 缺少返回值 —— 已修复 +- 3.8 PicImageItem 加载失败仍使用旧对象 —— 已修复 +- 3.9 PicScheduleShiftItem 返回临时对象引用 —— 已修复 + +### 11.4 用户决定不修复(按澄清栏) + +- 3.4 `itemChange()` clamp 逻辑未生效(含 `ItemSceneChange` 笔误)—— 用户表示该段代码使用已久未发现异常,暂不修复 +- 3.6 `setSource` 忽略入参 —— `source/setSource` 机制准备弃用,不予修复 +- 3.10 文档对象 stylesheet —— 全面自绘,不予修复 +- 3.12 `clipImage` 使用 goto + `originRect` 未使用 —— 不予修复 + +### 11.5 仍待处理项(按优先级) + +- **P0**:5.1 / 5.2 / 5.3 / 5.4(testprintwidget 内存泄漏、悬空 `codeItem_Operation`、C 风格强转、构造顺序);7.1 / 7.2(InkJetPrinter 析构线程与 timer 清理)。 +- **P1**:4.1 / 4.2 / 4.5(编辑器持有 item 裸指针);5.5 / 5.6;6.1 / 6.2 / 6.3(mainwidget 待全文复核);7.3 / 7.4 / 7.5。 +- **P2/P3**:架构层 `QPointer` / `id+查表` 替换裸指针存储、串口/线程加锁、`qgraphicsitem_cast` 替换 C 风格强转等。 + +> **下一步建议**:可单独安排对 `mainwidget.cpp/h` 的全文复核,输出 6.1–6.7 项的精确行号与代码片段后再决定修复。其余 P0 项(testprintwidget / InkJetPrinter)可按本节排序逐项推进。 diff --git a/image.qrc b/image.qrc index 563473d..ebfb69c 100644 --- a/image.qrc +++ b/image.qrc @@ -1036,5 +1036,6 @@ image/set3.png image/down_white.png image/up_white.png + styles.qss diff --git a/scene/BarcodeEditor.cpp b/scene/BarcodeEditor.cpp index 52f44ac..05638e3 100644 --- a/scene/BarcodeEditor.cpp +++ b/scene/BarcodeEditor.cpp @@ -44,7 +44,9 @@ BarcodeEditor::BarcodeEditor(QWidget *parent) : ui(new Ui::BarcodeEditor) { ui->setupUi(this); - setWindowModality(Qt::NonModal); + + applyStyleSheet(); + m_dateFormatEditor = new DateFormatEditor(); m_dateReplaceEditor = new DateReplaceEditor(); m_counterEditor = new CounterEditor(); @@ -382,6 +384,13 @@ bool BarcodeEditor::eventFilter(QObject *obj, QEvent *event) return false; } +void BarcodeEditor::applyStyleSheet() +{ + // QFile stylesFile(":/styles.qss"); + // QTextStream ts(&stylesFile); + // setStyleSheet(ts.readAll()); +} + void BarcodeEditor::onContentChanged(QString content) { const DateItemModel& dateVm = m_vm.dateViewModel; diff --git a/scene/BarcodeEditor.h b/scene/BarcodeEditor.h index 5956383..009b0cc 100644 --- a/scene/BarcodeEditor.h +++ b/scene/BarcodeEditor.h @@ -185,6 +185,8 @@ protected: private: Ui::BarcodeEditor *ui; + void applyStyleSheet(); + BarcodeViewModel m_vm; DateFormatEditor *m_dateFormatEditor = nullptr; diff --git a/scene/BarcodeEditor.ui b/scene/BarcodeEditor.ui index f4253ff..3beaba0 100644 --- a/scene/BarcodeEditor.ui +++ b/scene/BarcodeEditor.ui @@ -173,6 +173,36 @@ QSpinBox::down-arrow { 471 + + QTabWidget { + border: none; + background-color: #7B838E; + } + + QTabBar::tab { + min-width: 90px; + min-height: 45px; + padding: 4px 10px; + margin-right: 4px; + border: 1px solid #dddddd; + border-bottom: none; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + background-color: #5396C1; + color: #ffffff; + } + + QTabWidget::tab-bar { + top: -3px; + left: 10px; + } + + QTabBar::tab:selected { + background-color: #6bb1e0; + color: #ffffff; + box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25); + } + 1 @@ -270,7 +300,7 @@ QSpinBox::down-arrow { 420 120 261 - 291 + 231 diff --git a/scene/DlgBarcodeFormat.ui b/scene/DlgBarcodeFormat.ui index 5c0c1f6..aa16b33 100644 --- a/scene/DlgBarcodeFormat.ui +++ b/scene/DlgBarcodeFormat.ui @@ -20,9 +20,108 @@ Dialog - QWidget{ + QWidget { font-size: 16px; -} +} + +QPushButton#closeBtn, QPushButton#checkBtn{ + background-color: #E6F7FF; + border: 1px solid #87CEEB; + border-radius: 4px; + font-size: 24px; + color: #228B22; + } + QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{ + background-color: #D1E9FF; + } + + QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{ + background-color: #BAE7FF; + } + + QPushButton { + background-color: #5396C1; + color: #ffffff; + border: 1px solid #000000; + border-radius: 3px; + font-size: 18px; + font-weight:500; + width: 180px; + height: 90px; + } + + QPushButton:hover { + background-color: #5396C1; + } + + QPushButton:disabled { + background-color: #7C828E; + color: #606060; + border-color: #808080; + } + +QComboBox { + border: 1px solid #000000; + border-radius: 3px; + font-size: 14px; + width: 180px; + height: 90px; + padding-left: 10px; /* 文字左边距,避免紧贴边缘 */ +} + +/* 下拉箭头区域 */ +QComboBox::drop-down { + border: 1px solid #333333; + border-radius: 3px; + width: 30px; /* 箭头区域宽度 */ + background-color: #5396C1; +} + +/* 下拉箭头图标 */ +QComboBox::down-arrow { + image: url(image/down_white.png); + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ +} + +/* 悬停状态 */ +QComboBox:hover { + background-color: #efefef; +} + +// 下拉框 +QComboBox QAbstractItemView { + border: 1px solid #000000; + border-radius: 3px; + font-size: 14px; + selection-background-color: #5396C1; /* 选中项背景色(稍深) */ + selection-color: #ffffff; +} + +QSpinBox::up-button { + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ + border: 1px solid #333333; + border-radius: 3px; + background-color: #5396C1; +} + +QSpinBox::up-arrow { + image: url(image/up_white.png); +} + +QSpinBox::down-button { + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ + border: 1px solid #333333; + border-radius: 3px; + background-color: #5396C1; +} + +QSpinBox::down-arrow { + image: url(image/down_white.png); +} + @@ -42,6 +141,9 @@ false + + QAbstractSpinBox::NoButtons + false diff --git a/scene/DlgQrcodeFormat.ui b/scene/DlgQrcodeFormat.ui index 6029389..cf4ba22 100644 --- a/scene/DlgQrcodeFormat.ui +++ b/scene/DlgQrcodeFormat.ui @@ -22,7 +22,109 @@ QWidget{ font-size: 16px; -} +} +QWidget { + font-size: 16px; +} + +QPushButton#closeBtn, QPushButton#checkBtn{ + background-color: #E6F7FF; + border: 1px solid #87CEEB; + border-radius: 4px; + font-size: 24px; + color: #228B22; + } + QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{ + background-color: #D1E9FF; + } + + QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{ + background-color: #BAE7FF; + } + + QPushButton { + background-color: #5396C1; + color: #ffffff; + border: 1px solid #000000; + border-radius: 3px; + font-size: 18px; + font-weight:500; + width: 180px; + height: 90px; + } + + QPushButton:hover { + background-color: #5396C1; + } + + QPushButton:disabled { + background-color: #7C828E; + color: #606060; + border-color: #808080; + } + +QComboBox { + border: 1px solid #000000; + border-radius: 3px; + font-size: 14px; + width: 180px; + height: 90px; + padding-left: 10px; /* 文字左边距,避免紧贴边缘 */ +} + +/* 下拉箭头区域 */ +QComboBox::drop-down { + border: 1px solid #333333; + border-radius: 3px; + width: 30px; /* 箭头区域宽度 */ + background-color: #5396C1; +} + +/* 下拉箭头图标 */ +QComboBox::down-arrow { + image: url(image/down_white.png); + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ +} + +/* 悬停状态 */ +QComboBox:hover { + background-color: #efefef; +} + +// 下拉框 +QComboBox QAbstractItemView { + border: 1px solid #000000; + border-radius: 3px; + font-size: 14px; + selection-background-color: #5396C1; /* 选中项背景色(稍深) */ + selection-color: #ffffff; +} + +QSpinBox::up-button { + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ + border: 1px solid #333333; + border-radius: 3px; + background-color: #5396C1; +} + +QSpinBox::up-arrow { + image: url(image/up_white.png); +} + +QSpinBox::down-button { + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ + border: 1px solid #333333; + border-radius: 3px; + background-color: #5396C1; +} + +QSpinBox::down-arrow { + image: url(image/down_white.png); +} + diff --git a/scene/ScheduleShiftsEditor.cpp b/scene/ScheduleShiftsEditor.cpp index 46f51e1..689bdef 100644 --- a/scene/ScheduleShiftsEditor.cpp +++ b/scene/ScheduleShiftsEditor.cpp @@ -257,7 +257,14 @@ void ScheduleShiftsEditor::applyStyleSheet() box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25); } )"; - this->setStyleSheet(styleSheet); + + + // QFile stylesFile(":/styles.qss"); + // QTextStream ts(&stylesFile); + // auto str = ts.readAll(); + // setStyleSheet(str); + // ui->tabWidget->setStyleSheet(str); + // // this->setStyleSheet(styleSheet); } SelectedStyleTimeEdit::SelectedStyleTimeEdit(QWidget *parent) diff --git a/scene/ScheduleShiftsEditor.ui b/scene/ScheduleShiftsEditor.ui index ef3c0bf..f3b5dc8 100644 --- a/scene/ScheduleShiftsEditor.ui +++ b/scene/ScheduleShiftsEditor.ui @@ -13,6 +13,77 @@ Form + + + QPushButton#closeBtn, QPushButton#checkBtn{ + background-color: #E6F7FF; + border: 1px solid #87CEEB; + border-radius: 4px; + font-size: 24px; + color: #228B22; + } + QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{ + background-color: #D1E9FF; + } + + QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{ + background-color: #BAE7FF; + } + + QPushButton { + background-color: #5396C1; + color: #ffffff; + border: 1px solid #000000; + border-radius: 3px; + font-size: 18px; + font-weight:500; + width: 100px; + height: 50px; + } + + QPushButton:hover { + background-color: #5396C1; + } + + QPushButton:disabled { + background-color: #7C828E; + color: #606060; + border-color: #808080; + } + + QTabWidget { + border: 1px solid #dddddd; + background-color: #ffffff; + } + + QTabBar::tab:focus { + outline: none; + } + + QTabBar::tab { + min-width: 90px; + min-height: 45px; + padding: 4px 10px; + margin-right: 4px; + border: 1px solid #dddddd; + border-bottom: none; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + background-color: #5396C1; + color: #ffffff; + } + + QTabWidget::tab-bar { + top: -3px; + left: 10px; + } + + QTabBar::tab:selected { + background-color: #6bb1e0; + color: #ffffff; + box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25); + } + 0 @@ -48,8 +119,8 @@ - 62 - 42 + 102 + 52 @@ -91,8 +162,8 @@ - 62 - 42 + 102 + 52 @@ -139,6 +210,9 @@ + + + 1 diff --git a/scene/styles1.qss b/scene/styles1.qss new file mode 100644 index 0000000..470fbd2 --- /dev/null +++ b/scene/styles1.qss @@ -0,0 +1,91 @@ + /* + QPushButton { + background-color: #5396C1; + color: #ffffff; + border: 1px solid #000000; + border-radius: 3px; + font-size: 18px; + font-weight:500; + width: 180px; + height: 90px; + } + + QPushButton:hover { + background-color: #5396C1; + } + + QPushButton:disabled { + background-color: #7C828E; + color: #606060; + border-color: #808080; + } + */ + + QPushButton#closeBtn, QPushButton#checkBtn{ + background-color: #E6F7FF; + border: 1px solid #87CEEB; + border-radius: 4px; + font-size: 24px; + color: #228B22; + } + QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{ + background-color: #D1E9FF; + } + + QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{ + background-color: #BAE7FF; + } + + QPushButton { + background-color: #5396C1; + color: #ffffff; + border: 1px solid #000000; + border-radius: 3px; + font-size: 18px; + font-weight:500; + width: 100px; + height: 50px; + } + + QPushButton:hover { + background-color: #5396C1; + } + + QPushButton:disabled { + background-color: #7C828E; + color: #606060; + border-color: #808080; + } + + QTabWidget::pane { + border: 1px solid #dddddd; + background-color: #ffffff; + } + + QTabBar::tab:focus { + outline: none; + } + + QTabBar::tab { + min-width: 90px; + min-height: 45px; + padding: 4px 10px; + margin-right: 4px; + border: 1px solid #dddddd; + border-bottom: none; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + background-color: #5396C1; + color: #ffffff; + } + + QTabWidget::tab-bar { + top: -3px; + left: 10px; + } + + QTabBar::tab:selected { + background-color: #6bb1e0; + color: #ffffff; + box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25); + } \ No newline at end of file diff --git a/styles.qss b/styles.qss new file mode 100644 index 0000000..3c8211a --- /dev/null +++ b/styles.qss @@ -0,0 +1,130 @@ +QWidget { + font-size: 16px; + } + +QPushButton#closeBtn, QPushButton#checkBtn{ + background-color: #E6F7FF; + border: 1px solid #87CEEB; + border-radius: 4px; + font-size: 24px; + color: #228B22; + } + QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{ + background-color: #D1E9FF; + } + + QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{ + background-color: #BAE7FF; + } + + QPushButton { + background-color: #5396C1; + color: #ffffff; + border: 1px solid #000000; + border-radius: 3px; + font-size: 18px; + font-weight:500; + width: 100px; + height: 50px; + } + + QPushButton:hover { + background-color: #5396C1; + } + + QPushButton:disabled { + background-color: #7C828E; + color: #606060; + border-color: #808080; + } + + QComboBox { + border: 1px solid #000000; + border-radius: 3px; + font-size: 14px; + width: 100px; + height: 50px; + padding-left: 10px; /* 文字左边距,避免紧贴边缘 */ + } + + /* 下拉箭头区域 */ + QComboBox::drop-down { + border: 1px solid #333333; + border-radius: 3px; + width: 30px; /* 箭头区域宽度 */ + background-color: #5396C1; + } + + /* 下拉箭头图标 */ + QComboBox::down-arrow { + image: url(:/image/down_white.png); + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ + } + + /* 悬停状态 */ + QComboBox:hover { + background-color: #efefef; + } + + // 下拉框 + QComboBox QAbstractItemView { + border: 1px solid #000000; + border-radius: 3px; + font-size: 14px; + selection-background-color: #5396C1; /* 选中项背景色(稍深) */ + selection-color: #ffffff; + } + + QSpinBox::up-button { + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ + border: 1px solid #333333; + border-radius: 3px; + background-color: #5396C1; + } + + QSpinBox::up-arrow { + image: url(:/image/up_white.png); + } + + QSpinBox::down-button { + width: 16px; /* 图标显示宽度 */ + height: 16px; /* 图标显示高度 */ + border: 1px solid #333333; + border-radius: 3px; + background-color: #5396C1; + } + + QSpinBox::down-arrow { + image: url(:/image/down_white.png); + } + + QTabWidget::pane { + border: none; + background-color: #7B838E; + } + + QTabBar::tab { + min-width: 90px; + min-height: 45px; + padding: 4px 10px; + margin-right: 4px; + border: 1px solid #dddddd; + border-bottom: none; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + background-color: #5396C1; + color: #ffffff; + } + + QTabWidget::tab-bar { + top: -3px; + left: 10px; + } + + QTabBar::tab:selected { + background-color: #6bb1e0; + color: #ffffff; + box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25); + } \ No newline at end of file diff --git a/testprintwidget.cpp b/testprintwidget.cpp index f421f85..a3d4b73 100644 --- a/testprintwidget.cpp +++ b/testprintwidget.cpp @@ -1647,6 +1647,7 @@ void TestPrintWidget::showBarcodeEditor(bool restoreViewModel) if(restoreViewModel) m_barcodeEditor.restoreViewModel(); + m_barcodeEditor.setWindowModality(Qt::WindowModal); m_barcodeEditor.show(); }