# QLineEdit 自绘小眼睛 / echo mode 切换（qt_lineedit_eye_toggle）

Day4 上午教学示例（OUTLINE_4DAY.md Day4 上午「消息机制和事件 + 绘图事件」），**同时覆盖两个主题**：

- **组件绘制**——覆写 `paintEvent`，用 QPainter 在 QLineEdit 右侧自绘眼睛图标；
- **事件捕捉**——覆写 `mousePressEvent` / `mouseMoveEvent`，几何 hit-test 命中眼睛时切换
  `echoMode`，且不阻断基类的光标定位 / 拖选。

载体是 QLineEdit 密码框：右侧一个自绘小眼睛，点击在 `Password` ↔ `Normal` 间切换，并显式
处理与 QLineEdit 内置 clear button 的位置冲突。

## 三区对照（先简后深）

| 区 | 内容 | 教学作用 |
|---|---|---|
| ① echo mode 四态 | Normal / NoEcho / Password / PasswordEchoOnEdit 静态对照 | 先给 echo mode 全貌，眼睛只是切换载体 |
| ② addAction 一行版 | `addAction(QIcon, TrailingPosition)` 加眼睛，点击切换 | Qt 内置、1 行实现——「为什么还要自绘」的引子 |
| ③ 自绘版主秀 | `PasswordLineEdit` 自绘眼睛 + 「启用 clear button」复选框 | 主题落地 + clear button 避让 |

## 关键教学点

### 1. 组件绘制
- `paintEvent` **先调基类**画原生内容（文本 / 边框 / clear button），再用 QPainter 叠加眼睛。
  顺序不能反——基类画完后 clear button 几何才最终确定，眼睛避让才准。
- 眼睛用 QPainter 纯代码绘制（眼眶椭圆 + 瞳孔 / 斜杠两态），无外部资源。绘制逻辑抽成自由函数
  `paintEye()`，③区自绘与②区 `QIcon` pixmap 共用（DRY，且对照更直观）。

### 2. 事件捕捉
- `mousePressEvent`：命中眼睛矩形 → 切 `echoMode` + `e->accept()` + `return`（**不**调基类，
  否则 QLineEdit 把这次点击当光标定位）；非命中 → 调基类（光标定位 / 双击选词 / 右键菜单照常）。
  这是「自绘交互区与原生编辑行为共存」的核心。
- `mouseMoveEvent` + `setMouseTracking(true)`：实现 hover 高亮。**坑卡**：`mouseTracking` 默认关，
  不按鼠标键就收不到 `mouseMoveEvent`，hover 高亮出不来。

### 3. clear button 避让（读 clearButton 子 widget 几何）
QLineEdit 内置 clear button 的宽度随主题 / DPI / 字号变化，**不能硬编码偏移**（硬编码是反面教材，
换主题即错位）。正确做法：clear button 是 QLineEdit 的一个 `objectName=="clearButton"` 直接子 widget
（Qt5 私有 `QLineEditIconButton`），用 `findChild` 读它的真实几何：

```cpp
if (QWidget* cb = findChild<QWidget*>(QStringLiteral("clearButton"),
                                      Qt::FindDirectChildrenOnly)) {
    rightEdge = cb->x() - 4;   // 眼睛贴 clear button 左侧，留 4px 缝
}
```

眼睛紧贴其左侧；未启用 clear button 或无文本（clear button 不占位）时贴右边。勾选③区复选框即可实时
观察眼睛从「贴右」移到「贴 clear button 左侧」、两者不重叠。

> 为什么不用 `QStyle::subControlRect`？QLineEdit 不经 QStyle 的 complex-control 绘制，
> `QStyle` 没有 `CC_LineEdit` / `SC_LineEditClearButton`，无对应 subcontrol 可查——读子 widget 是唯一可靠途径。

## 目录结构
```
lineedit_eye_toggle/
├── qt_lineedit_eye_toggle.cpp   # 唯一源文件（paintEye + PasswordLineEdit + ShowcaseWidget + main）
├── CMakeLists.txt               # 独立顶层工程（可单独 cmake -S . -B build，不走仓库统一构建）
└── README.md                    # 本文档
```

## 构建与运行（Windows + MinGW 7.3.0 + Qt 5.14.2）

> 必须用与 Qt 5.14.2 ABI 匹配的 mingw730_64，不可用 PATH 中更高版本的 g++。

### A. 集成构建（仓库统一构建树）
```bash
# 配置（在仓库根）
cmake -S . -B build_mw -G "MinGW Makefiles" \
  -DCMAKE_PREFIX_PATH=E:/Qt/Qt5.14.2/5.14.2/mingw73_64 \
  -DCMAKE_CXX_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/g++.exe \
  -DCMAKE_C_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/gcc.exe

cmake --build build_mw --target qt_lineedit_eye_toggle

# 运行（GUI，点 ②③ 的眼睛、勾选 clear button 体验）
PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/bin:$PATH \
QT_QPA_PLATFORM_PLUGIN_PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/plugins/platforms \
  ./build_mw/docs/teaching/examples/qt_lineedit_eye_toggle.exe

# 离屏自动退出验证（不交互，退出码应为 0）
QT_QPA_PLATFORM=offscreen \
QT_QPA_PLATFORM_PLUGIN_PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/plugins/platforms \
PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/bin:$PATH \
  ./build_mw/docs/teaching/examples/qt_lineedit_eye_toggle.exe; echo "exit=$?"
```

### B. 独立构建（单独 cmake 工程）
```bash
cd docs/teaching/examples/lineedit_eye_toggle
cmake -S . -B build -G "MinGW Makefiles" \
  -DCMAKE_PREFIX_PATH=E:/Qt/Qt5.14.2/5.14.2/mingw73_64 \
  -DCMAKE_CXX_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/g++.exe \
  -DCMAKE_C_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/gcc.exe
cmake --build build
PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/bin:$PATH \
QT_QPA_PLATFORM_PLUGIN_PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/plugins/platforms \
  ./build/qt_lineedit_eye_toggle.exe
```

> 两条路径编译同一份 `qt_lineedit_eye_toggle.cpp`，行为一致。集成路径进入仓库统一构建；
> 独立路径可把这个目录单独拷走即用（仅需本目录三文件 + 一个 Qt 5.14.x）。
