# 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)可按本节排序逐项推进。