调整部分样式

This commit is contained in:
2026-05-11 09:11:30 +08:00
parent e40a72cd7c
commit c8a5a121c9
31 changed files with 2763 additions and 660 deletions
+460
View File
@@ -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<int>)` 入参 `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<int>)` 入口校验/补齐为 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<PicCodeItem>` 持有,使用前判空;或
- 在 `removeItem` 前发信号通知所有打开的编辑器关闭。
### 4.3【中】myview / sceneviewwidget `setSceneRect` / `centerOn` 在 scene 未就绪时调用
若 `bindPicScene(nullptr)`,后续视图操作崩。
**修复方案**:所有调用前 `if(!scene()) return;`。
### 4.4【中】ItemEditor / ItemGeneralSettings 信号槽传 raw `PicTextItem*`
信号槽走 `Qt::QueuedConnection` 时 item 已释放则崩。
**修复方案**:参数改 `QPointer<PicTextItem>`,或改成传 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<PicTextItem*>(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<QString,QString>` 取代。
### 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<MainWidget> p = m_MainWidget;`,使用前判空。
### 7.6【中】showScheduleShiftsEditor / openDatetimeEditor 接收 item* 异步使用
**修复方案**:参数改 `QPointer<PicTextItem>`。
### 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)可按本节排序逐项推进。