BayTemplate/CLAUDE.md

1092 lines
52 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
BayTemplate is a **Bay Template Designer of Grid Framework DesignTime** tool - a Qt-based application for designing electrical power grid structures. It features a dockable UI with a graphics canvas for placing and manipulating grid elements.
## Build Commands
### Build the Project
使用 CMake presets 进行配置和构建:
```bash
# Configure using preset
cmake --preset default
# Build with Ninja
ninja -C build/Debug
```
### Output Location
- Executable: `build/Debug/BayTemplate.app` (macOS)
## Architecture
### Core Components
#### 1. Docking System (Qt Advanced Docking System 4.3.1)
- **CDockManager** manages dockable panels
- **Left Dock**: 图元面板 (GraphicElementsPanel) - library of draggable grid elements
- **Center**: DrawingPanel - main canvas area with DesignerScene
- **Right Dock**: 属性编辑器 (PropertyEditor) - QDetailsView for editing selected item properties
#### 2. Graphics View Framework
- **DesignerView** (`QGraphicsView` subclass): Custom view with zoom (0.02-50x), pan (middle-button drag), checkerboard background
- **DesignerScene** (`QGraphicsScene` subclass): Custom scene with grid overlay (20px spacing), routes mouse events through SelectorManager
#### 3. Selector System
Selector hierarchy in `include/util/`:
- **BaseSelector**: Abstract base for all selectors
- **CreatingSelector**: Mode for creating new items from template
- **MovingSelector**: Mode for moving items
- **RotationSelector**: Mode for rotating items around origin
- **ScalingSelector**: Mode for scaling items
- **EditingSelector**: Mode for editing polygon vertex positions
- **SelectorManager**: Singleton that manages the active working selector
Mouse events flow: `DesignerScene``SelectorManager::getWorkingSelector()` → specific selector implementation
#### 4. Graphics Items (`include/graphicsItem/`)
- **GraphicsBaseItem**: Abstract base class for all grid elements
- **GraphicsRectItem**: Rectangle element
- **GraphicsPolygonItem**: Polygon element (editable vertices)
- **GraphicsItemGroup**: Container for grouped items (supports flattening to prevent nesting)
- **GraphicsBusSectionItem**: Bus section element
- **ItemControlHandle**: Visual handles for manipulation (rotation, scaling, editing)
#### 5. Property Editor (Qt PropertyEditor)
- **QDetailsView**: Qt Quick-based property editor in right dock
- **QCustomType**: Custom property type for graphics item properties
- **PropertyTypeCustomization_CustomType**: Custom property editor integration
- Property editor observes `QGraphicsScene` and displays properties of selected items
#### 6. Command Pattern (Undo/Redo)
- **QUndoStack** (`m_pUndoStack` in CMainWindow) manages undo/redo
- **OperationCommand**: Base command class
- **AddItemCommand**: Add item to scene
- **DeleteItemCommand**: Remove item from scene
- **CreateItemGoupCommand**: Group selected items (with flattening)
- **DestroyItemGoupCommand**: Ungroup items
### Signal Flow
1. **User drags from 图元面板**`GraphicElementsPanel` emits signal → `DesignerScene::signalAddItem()` → creates item with `AddItemCommand`
2. **User clicks on canvas**`DesignerView``DesignerScene``SelectorManager::getWorkingSelector()` → selector processes event
3. **User selects items**`DesignerScene::selectionChanged()``CMainWindow::onSignal_selectionChanged()` → updates property editor
4. **User modifies property in 属性编辑器**`QDetailsView` updates item property → scene updates
### Key Files and Relationships
```
source/main.cpp # Entry point - creates CMainWindow
└── source/mainwindow.h/.cpp # Main window with CDockManager, initializes all components
├── initializeDockUi() # Sets up docks (left, center, right)
├── initializeAction() # Sets up QUndoStack and menu actions
├── initializeDocument() # Creates Document, associates with DesignerScene
├── Event handlers # onSignal_addItem, onSignal_selectionChanged, etc.
└── File operations # onAction_new(), onAction_open(), onAction_save()
source/document.h/.cpp # Document class - serialization and state management
├── serialize()/deserialize() # Core JSON serialization
├── saveToFile()/loadFromFile() # File I/O operations
└── State management # filename, modified, timestamps, meta data
source/drawingPanel.h/.cpp # Central dock widget containing DesignerView
└── DesignerView + DesignerScene
source/designerView.h/.cpp # QGraphicsView subclass - zoom, pan, middle-button navigation
source/designerScene.h/.cpp # QGraphicsScene subclass - grid background, group operations
└── Delegates to SelectorManager for mouse events
source/util/selectorManager.h/.cpp # Singleton managing active selector
└── Selector::mousePressEvent/ReleaseEvent/MoveEvent()
source/graphicsItem/ # Graphics item implementations
├── GraphicsBaseItem # Base class with common functionality
├── GraphicsRectItem # Rectangle
├── GraphicsPolygonItem # Editable polygon
├── GraphicsItemGroup # Item grouping
└── GraphicsBusSectionItem
source/propertyType/ # Custom property editor integration
├── CustomType.h
├── CustomGadget.h
└── PropertyTypeCustomization_CustomType.cpp
```
### Third-Party Dependencies
1. **QtADS** (`QtADS/` subdirectory, v4.3.1): Advanced docking system - must be present as subdirectory
2. **PropertyEditor** (`PropertyEditor/` subdirectory): Qt Quick-based property editing system with QDetailsView
Both are added via `add_subdirectory()` in CMakeLists.txt and must exist in the repository root.
### Platform Support
- **Qt Version**: Qt5 or Qt6 (auto-detected, `find_package(QT NAMES Qt6 Qt5 ...)`)
- **Architectures**: x86, x64, arm64, aarch64 (auto-detected in CMakeLists.txt)
- **Platforms**: Windows, macOS, Linux, Android (Android uses shared library)
### Important Implementation Details
1. **CMAKE_AUTOUIC_SEARCH_PATHS**: Set to `"ui"` in CMakeLists.txt because .ui files are in separate directory from header files
2. **Group Flattening**: When creating a group that contains existing groups, the scene flattens nested groups first to prevent group nesting (see `DesignerScene::createGroup()`)
3. **Zoom Implementation**: Uses `QGraphicsView::zoom()` with smooth factor `qPow(1.0015, angleDelta.y())` per wheel event
4. **Wchar String Conversion**: Uses `QString::fromWCharArray(L"中文")` for Chinese UI strings
5. **Resource File**: `resource/BayTemplate.qrc` contains checkerboard background and icons, referenced in .ui files and CMakeLists.txt
---
## Graphics View Architecture Deep Dive
### MVC/MVVM Architecture Mapping
BayTemplate implements a classic **Model-View-Controller** pattern using Qt's Graphics View Framework:
| Layer | Qt Graphics View | BayTemplate Implementation | Responsibility |
|-------|------------------|---------------------------|----------------|
| **Model** | QGraphicsItem | GraphicsBaseItem, GraphicsRectItem, GraphicPolygonItem, GraphicsItemGroup | 数据模型存储图元几何数据、样式属性pen/brush、变换信息位置/旋转/缩放)|
| **Model Manager** | QGraphicsScene | DesignerScene | 模型管理:管理 item 生命周期、选择状态、碰撞检测、背景绘制(网格)|
| **View** | QGraphicsView | DesignerView | 视图呈现:坐标系变换、滚动条控制、鼠标事件捕获、缩放/平移 |
| **Controller** | (Qt 无内置) | SelectorManager + BaseSelector 体系 | 控制器:解释用户输入,转换为模型操作(创建/移动/旋转/缩放/编辑)|
**关键点:**
- Qt 的 Graphics View 框架提供了 MVC 中的 Model 和 View 层
- Controller 层需要开发者自行实现BayTemplate 使用 SelectorManager 作为中枢
- DesignerScene 介于 Model Manager 和 Controller 之间,负责事件分发
### View-Scene-Item 三层架构详解
```
┌─────────────────────────────────────────────────────────────┐
│ DesignerView │
│ (Viewport / Presentation) │
├─────────────────────────────────────────────────────────────┤
│ • 坐标变换Viewport Coordinates ↔ Scene Coordinates │
│ • 缩放控制0.02x - 50x (mouse wheel with smooth zoom) │
│ • 平移控制Middle-button drag (translate transform) │
│ • 事件捕获mousePressEvent/moveEvent/releaseEvent │
│ • 无滚动条setHorizontal/VerticalScrollBarPolicy(Off) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ DesignerScene │
│ (Scene Graph Manager) │
├─────────────────────────────────────────────────────────────┤
│ • Item 管理addItem(), removeItem(), selectedItems() │
│ • 背景绘制drawBackground() 绘制 20px 间距的藏青色虚线网格 │
│ • 事件分发:将鼠标事件转发给 SelectorManager │
│ • 组操作createGroup() 实现扁平化打组 │
│ • 信号发射signalAddItem(), selectionChanged() │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ GraphicsItem │
│ (Data Model) │
├─────────────────────────────────────────────────────────────┤
│ GraphicsBaseItem (AbstractShapeType<QGraphicsItem>) │
│ ├── GraphicsRectItem (rectangle, 圆角矩形) │
│ ├── GraphicPolygonItem (多点绘制,顶点编辑) │
│ ├── GraphicsBusSectionItem (总线段) │
│ └── GraphicsItemGroup (AbstractShapeType<QGroup>) │
└─────────────────────────────────────────────────────────────┘
```
### 坐标系与变换系统
BayTemplate 使用多层坐标系,理解它们对添加 Document 类至关重要:
```
1. Viewport Coordinates (像素坐标)
└── DesignerView 的 viewport 矩形,(0,0) 在左上角
└── 受窗口大小、缩放影响
2. Scene Coordinates (场景坐标)
└── 全局坐标系,(0,0) 为场景原点
└── 通过 mapToScene()/mapFromScene() 与 Viewport 互转
└── 图元的位置 pos() 使用场景坐标
3. Item Coordinates (图元局部坐标)
└── 每个 item 的局部坐标系,原点在 (0,0)
└── BayTemplate 中,图元中心点通常与 (0,0) 重合
└── boundingRect() 返回局部坐标中的边界
4. Parent Coordinates (父项坐标)
└── 当 item 加入 group 后,使用 group 的局部坐标
└── mapToParent()/mapFromParent() 用于转换
```
**变换链Transform Chain**
```
Item Coordinates → Item Transform → Parent Coordinates → ... → Scene Coordinates → View Transform → Viewport Coordinates
```
- **Item Transform**: 由 `setPos()`, `setRotation()`, `setScale()` 控制的 3×3 变换矩阵
- **Transform Origin**: `setTransformOriginPoint()` 设置变换中心BayTemplate 中通常为中心点)
- **View Transform**: DesignerView 的 zoom/pan 产生的变换,影响所有 item 的显示
**关键函数:**
- `item->mapToScene(point)`: Item 局部坐标 → 场景坐标
- `item->mapToView(point)`: Item 局部坐标 → Viewport 坐标
- `view->mapToScene(point)`: Viewport 坐标 → 场景坐标
- `view->mapSceneToViewport(rect)`: 场景矩形 → Viewport 矩形
### 操作模式与状态机Selector 系统)
SelectorManager 实现了一个**状态机**,不同的 Selector 代表不同的操作模式:
```
┌─────────────────┐
│ ST_base │
│ (空闲状态) │
└────────┬────────┘
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ST_creating │ │ ST_moving │ │ ST_editing │
│ (创建模式) │ │ (移动模式) │ │ (顶点编辑) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ ST_scaling │ │ ST_rotation │
│ (缩放模式) │ │ (旋转模式) │
└──────────────┘ └──────────────┘
│ │
└────────┬─────────┘
┌────────┐
│ ST_base│ (操作完成后返回)
└────────┘
```
**状态转换规则:**
| 触发条件 | 从状态 | 到状态 | 说明 |
|---------|--------|--------|------|
| 从图元面板拖拽 | ST_base | ST_creating | 设置 CreatingSelector 的 m_pCreatingItem |
| 点击选中 item左键 | ST_base | ST_moving/ST_scaling/ST_rotation | 根据是否点击在 handle 上判断 |
| 点击空白处 | 任何 | ST_base | 取消选择,禁用 RubberBandDrag |
| mouseReleaseEvent | ST_moving/scaling/rotation | ST_base | 操作完成,返回空闲状态 |
**BaseSelector 静态成员(所有 Selector 共享):**
```cpp
static QPointF ms_ptMouseDown; // 鼠标按下位置(场景坐标)
static QPointF ms_ptMouseLast; // 鼠标当前位置(场景坐标)
static double ms_dAngleMouseDownToItem; // 按下时鼠标与 item 中心的夹角(弧度→角度)
static int ms_nDragHandle; // 当前拖拽的 handle 类型
```
### Handle 控制系统
ItemControlHandle 是围绕在选中 item 周围的视觉控件,用于精细操作:
```
H_leftTop ──── H_top ──── H_rightTop
│ │ │
│ │ │
┌────┴────────────┴────────────┴────┐
│ │
│ Item Bounding │
│ Rect │
│ │
└────┬────────────┬────────────┬────┘
│ │ │
H_left ────── H_bottom ──── H_rightBottom
```
**Handle 类型枚举:**
```cpp
enum HandleTag {
H_none = 0, // 无 handle
H_leftTop, // 左上角(缩放)
H_top, // 上边中点(缩放)
H_rightTop, // 右上角(缩放)
H_right, // 右边中点(缩放)
H_rightBottom, // 右下角(缩放)
H_bottom, // 下边中点(缩放)
H_leftBottom, // 左下角(缩放)
H_left, // 左边中点(缩放)
H_rotate_leftTop, // 左上角外侧(旋转)
H_rotate_rightTop, // 右上角外侧(旋转)
H_rotate_rightBottom, // 右下角外侧(旋转)
H_rotate_leftBottom, // 左下角外侧(旋转)
H_edit, // 自定义编辑点(多边形顶点、圆角控制点)
// ... 更多编辑点
};
```
**Handle 工作机制:**
1. `AbstractShapeType::updateHandles()` 根据 boundingRect 计算 handle 位置
2. Handle 位置在 boundingRect 外扩 5px (nMargin = 5)
3. `collidesWithHandle()` 检测鼠标点击在哪个 handle 上
4. 不同操作模式对 handle 的响应不同
### 操作副本Operation Copy模式
移动和旋转操作使用"预览副本"技术,提供视觉反馈:
```cpp
// 1. mousePressEvent: 创建副本
void createOperationCopy() {
m_pOperationCopy = new QGraphicsPathItem(this->shape());
m_pOperationCopy->setPen(Qt::DashLine); // 虚线表示预览
m_pOperationCopy->setPos(this->pos());
scene->addItem(m_pOperationCopy);
m_movingIniPos = this->pos();
}
// 2. mouseMoveEvent: 移动副本(本体不动)
void moveOperationCopy(const QPointF& delta) {
m_pOperationCopy->setPos(m_movingIniPos + delta);
}
// 3. mouseReleaseEvent: 副本位置应用到本体
void removeOperationCopy() {
this->setPos(m_pOperationCopy->pos()); // 本体移动到副本位置
scene->removeItem(m_pOperationCopy);
delete m_pOperationCopy;
}
```
**优势:**
- 移动过程中保持原来的 selection state
- 可以拖出 boundingRect_selected 范围(否则会被误认为取消选择)
- 旋转/缩放同理
### 图元基类架构AbstractShapeType 模板)
```cpp
// 使用模板继承,允许继承 QGraphicsItem 或 QGraphicsItemGroup
template <typename BaseType = QGraphicsItem>
class AbstractShapeType : public BaseType {
protected:
ShapeType m_type; // T_undefined/T_item/T_group
QPen m_pen; // 画笔(边框)
QBrush m_brush; // 画刷(填充)
double m_dWidth, m_dHeight; // 宽高
QRectF m_boundingRect; // 局部坐标中的边界矩形
QRectF m_boundingRect_selected; // 选中框(外扩 5px
double m_dSyncRotationByParent; // 父项旋转同步数据
QVector<ItemControlHandle*> m_vecHanle;
QGraphicsPathItem* m_pOperationCopy;
QPointF m_movingIniPos;
};
// 具体类型定义
typedef AbstractShapeType<QGraphicsItem> AbstractShape; // 单图元基类
// GraphicsBaseItem : QObject, AbstractShape
// GraphicsItemGroup : QObject, AbstractShapeType<QGraphicsItemGroup> // 组图元基类
```
**关键点:**
- `m_boundingRect` 的中心点与 item 的 (0,0) 原点重合,简化变换计算
- `updateCoordinate()` 在 resize/editShape 后重新校准原点
- `m_dSyncRotationByParent` 用于组内 item 跟踪父组的旋转(因为 item 的 rotation() 不会自动更新)
### 命令模式Command Pattern
所有可撤销操作都通过 QUndoCommand 实现:
```cpp
class AddItemCommand : public QUndoCommand {
void undo() override { scene->removeItem(m_pItem); }
void redo() override {
if(!m_pItem->scene()) scene->addItem(m_pItem);
}
};
class DeleteItemCommand : public QUndoCommand {
void undo() override {
foreach(item, m_listItem) scene->addItem(item);
}
void redo() override {
foreach(item, m_listItem) scene->removeItem(item);
}
};
```
**关键点:**
- `QUndoStack::push(command)` 会自动调用 `command->redo()`
- `DeleteItemCommand` 使用 `removeItem()` 而非 `delete`,确保 undo 能恢复
- 命令对象持有 item 的引用(非所有权)
### 组Group扁平化策略
`DesignerScene::createGroup()` 实现组扁平化:
```cpp
// 如果选中的 item 中包含已有的 group先解散该 group将子项加入新组
foreach (item, selectedItems) {
if (item->getType() == T_group) {
GraphicsItemGroup* group = qgraphicsitem_cast<GraphicsItemGroup*>(item);
foreach (child, group->childItems()) {
listItem.push_back(child); // 将子项加入新列表
}
removeItem(group);
delete group; // 删除旧组
}
}
// 创建新组,避免嵌套
```
**目的:**
- 简化场景图结构(无嵌套的树)
- 简化坐标计算和碰撞检测
- 统一 undo/redo 逻辑
---
## Document 类架构(持久化层)
### Document 类在 MVC 中的位置
Document 类作为**持久化层**插入现有架构,位于 Model 层之下:
```
┌─────────────────────────────────────────┐
│ Controller │
│ (SelectorManager + 各种 Selector) │
└──────────────┬──────────────────────────┘
┌──────────────▼──────────────────────────┐
│ View │
│ (DesignerView + UI) │
└──────────────┬──────────────────────────┘
┌──────────────▼──────────────────────────┐
│ Model Manager │
│ (DesignerScene) │
└──────────────┬──────────────────────────┘
┌──────────────▼──────────────────────────┐
│ Model │
│ (GraphicsItem 层次结构) │
└──────────────┬──────────────────────────┘
┌──────────────▼──────────────────────────┐
│ Document │
│ (序列化/状态管理) │
└──────────────────────────────────────────┘
```
**设计原则:**
- **组合优于继承**Document 持有 `DesignerScene*` 弱引用,不继承 Scene
- **单向依赖**Document 只依赖 DesignerScene 的接口,不依赖 CMainWindow
- **信号槽通信**:通过信号与外部通信修改状态,避免紧耦合
### Document 类职责
1. **序列化/反序列化**JSON 格式)
- `serialize()` / `deserialize()`: 核心序列化为 QByteArray
- `saveToFile()` / `loadFromFile()`: 文件 I/O 封装
- 递归序列化/反序列化图元层次结构(支持组图元嵌套)
2. **状态管理**
- `m_filename`: 文档文件名(空表示未保存的新文档)
- `m_modified`: 是否被修改(通过 `setModified()``modifiedChanged` 信号通知)
- `m_created` / `m_modifiedTime` / `m_lastSavedTime`: 时间戳
- `m_metaData`: 可扩展的自定义元数据 (`QMap<QString, QVariant>`)
3. **与 CMainWindow 的集成**
- `initializeDocument()`: 创建 Document 并关联 DesignerScene
- 连接 `modifiedChanged` 信号来更新窗口标题(添加 * 标记)
- `closeEvent()` 中检查 `isModified()` 提示用户保存
- 文件操作New/Open/Save 通过 Document 完成
### 序列化格式v1.0
```json
{
"version": "1.0",
"created": "2024-01-01T12:00:00",
"modified": "2024-01-01T14:30:00",
"filename": "/path/to/file.bay",
"metaData": {
"customKey": "customValue"
},
"items": [
{
"type": "GraphicsRectItem",
"id": "140737488320",
"pos": {"x": 100.0, "y": 200.0},
"rotation": 45.0,
"scale": {"x": 1.0, "y": 1.0},
"pen": {"color": "#000000", "width": 1.0, "style": 0},
"brush": {"color": "#FF0000"},
"width": 100.0,
"height": 50.0,
"properties": {},
"children": []
},
{
"type": "GraphicsItemGroup",
"pos": {"x": 300.0, "y": 150.0},
"rotation": 0.0,
"scale": {"x": 1.0, "y": 1.0},
"width": 200.0,
"height": 100.0,
"children": [
{ "type": "GraphicsRectItem", ... },
{ "type": "GraphicsBusSectionItem", ... }
]
}
]
}
```
### 实现细节
**类型识别:** 使用 `qobject_cast<T*>(item)` 配合 `getTypeString()` 函数识别图元类型(要求类型有 `Q_OBJECT` 宏)
**QVariant ↔ QJsonValue 转换:** 提供 `toJsonValue()` / `fromJsonValue()` 辅助函数处理元数据序列化
**坐标系统:** 图元使用中心原点,`pos()` 存储场景坐标,`boundingRect()` 使用局部坐标
**文件扩展名:** `.bay` 作为 BayTemplate 专用格式
### 关键文件
- `include/document.h`: Document 类声明
- `source/document.cpp`: 序列化/反序列化实现
- `source/mainwindow.cpp`: `initializeDocument()`, `onAction_new()`, `onAction_open()`, `onAction_save()`
---
## Selector 系统设计模式总结
### 状态模式State Pattern
SelectorManager 实现状态模式,不同的 Selector 子类代表不同的状态:
```cpp
// Context
class SelectorManager {
BaseSelector* getWorkingSelector(); // 返回当前状态的 Selector
void setWorkingSelector(SelectorType);
};
// State 接口
class BaseSelector {
virtual void mousePressEvent(...) = 0;
virtual void mouseMoveEvent(...) = 0;
virtual void mouseReleaseEvent(...) = 0;
};
// Concrete State
class MovingSelector : public BaseSelector { ... };
class ScalingSelector : public BaseSelector { ... };
// ...
```
**静态成员共享的原因:**
BaseSelector 的静态成员ms_ptMouseDown 等)在所有 Selector 实例间共享,使得状态切换时无需传递上下文数据。
### 操作副本模式Operation Preview Pattern
移动、旋转、缩放都使用"操作副本"作为视觉预览:
1. mousePressEvent: 创建虚线副本
2. mouseMoveEvent: 移动副本(本体不动)
3. mouseReleaseEvent: 将副本的变换应用到本体,删除副本
### Handle 系统Manipulator Pattern
ItemControlHandle 实现操纵器模式,将抽象的变换操作(缩放、旋转)映射到可视化的交互控件。
---
## 坐标系转换速查表
| 转换 | 函数 | 说明 |
|------|------|------|
| Item → Scene | `item->mapToScene(point)` | 局部坐标转场景坐标 |
| Scene → Item | `item->mapFromScene(point)` | 场景坐标转局部坐标 |
| Item → Viewport | `item->mapToView(point)` | 局部坐标转屏幕像素 |
| Item → Parent | `item->mapToParent(point)` | 局部坐标转父项坐标 |
| Viewport → Scene | `view->mapToScene(point)` | 屏幕像素转场景坐标 |
| Scene Rect → Viewport Rect | `view->mapSceneToViewport(rect)` | 场景矩形转屏幕矩形 |
## Handle 类型速查表
| Handle 类型 | 位置 | 功能 |
|------------|------|------|
| H_leftTop ~ H_left | boundingRect 八方向 | 缩放 |
| H_rotate_leftTop ~ H_rotate_leftBottom | boundingRect 外扩 | 旋转 |
| H_edit + N | 自定义位置 | 形状编辑(多边形顶点、圆角控制点)|
## 操作模式速查表
| SelectorType | 触发方式 | 用途 |
|-------------|---------|------|
| ST_base | 默认 | 空闲状态,允许橡胶框选择 |
| ST_creating | 从图元面板拖拽 | 创建新图元 |
| ST_moving | 点击 item 主体 | 移动选中项 |
| ST_scaling | 点击缩放 handle | 缩放选中项 |
| ST_rotation | 点击旋转 handle | 旋转选中项 |
| ST_editing | 点击编辑 handle | 编辑多边形顶点等 |
---
## PropertyEditor 系统架构深度剖析
### 概述
PropertyEditor 是一个第三方属性编辑器库(位于 `PropertyEditor/` 子目录),受虚幻引擎属性面板启发,借助 Qt 反射系统自动构建 QObject 的属性编辑 UI。核心亮点
- **基于 QML GPU 渲染**:使用 `QQuickWidget` 嵌套 QML TreeView + Delegate 实现
- **自动反射**:通过 `Q_PROPERTY` 宏声明的属性自动生成编辑器行
- **类型驱动编辑器选择**:根据 `QMetaType` 自动匹配对应的 ValueEditor QML 组件
- **可扩展定制**:通过 `IPropertyTypeCustomization` 接口为特定类型自定义整行布局和子项结构
### 完整数据流
```
CMainWindow::onSignal_selectionChanged()
QDetailsView::setObject(QObject*) ← 设置被检查的 QObject
QQuickDetailsView::setObject(QObject*) ← QML 端的入口
QQuickDetailsViewModel::setObject(QObject*) ← QAbstractItemModel
│ └─ 创建根 QPropertyHandle (FindOrCreate)
│ └─ 创建根 QDetailsViewRow_Property(mRoot)
│ └─ mRoot->invalidateChildren()
│ │
│ ▼
│ QDetailsViewRow_Property::attachChildren()
│ │
│ ├── 有 IPropertyTypeCustomization
│ │ YES → customizeChildren(handle, layoutBuilder)
│ │ NO → PropertyTypeCustomization_ObjectDefault::customizeChildren()
│ │ ├─ 检查 objectHandle 是否实现 IPropertyGroupProvider
│ │ │ YES → 按 group 名称收集属性 → addGroup() + 子 LayoutBuilder
│ │ │ NO → 遍历 metaObject->propertyCount() 平铺属性
│ │ └─ layoutBuilder->addProperty(childHandle)
│ │
│ ▼
│ QQuickTreeViewEx (QML TableView) 为每行创建 Delegate
│ │
│ ▼
│ QQuickDetailsViewPrivate::initItemCallback()
│ │
│ ▼
│ QDetailsViewRow_Property::setupItem(QQuickItem* inParent)
│ │
│ ├── 有 IPropertyTypeCustomization
│ │ YES → customizeHeaderRow(handle, rowBuilder)
│ │ NO → rowBuilder->makePropertyRow(handle)
│ │ │
│ │ ├── makeNameValueSlot()
│ │ │ └─ 创建 QML Row: [展开箭头] [名称区] [分隔条] [值编辑区]
│ │ │
│ │ ├── handle->setupNameEditor(nameSlot)
│ │ │ └─ IPropertyHandleImpl::createNameEditor()
│ │ │ └─ 创建 Text { text: model.name } QML 组件
│ │ │
│ │ └── handle->steupValueEditor(valueSlot)
│ │ └─ IPropertyHandleImpl::createValueEditor()
│ │ └─ 根据类型查找注册的编辑器 QML 组件
│ │
│ ▼
│ [最终渲染为带颜色的行,左侧名称 + 右侧编辑器控件]
```
### 核心类详解
#### 1. QDetailsViewC++ 侧的 QWidget 包装器)
```cpp
// source/PropertyEditor/source/src/QDetailsView.cpp
class QDetailsView : public QWidget {
QQuickWidget* mQuickWidget; // 嵌入 QML 引擎
QQuickDetailsView* mQuickDetailsView; // QML 端的 DetailsView 对象
void setObject(QObject* inObject); // 关键入口:设置被检查的对象
};
```
#### 2. QQuickDetailsViewModelQAbstractItemModel 的树形实现)
```cpp
// source/PropertyEditor/source/src/QQuickDetailsViewModel.cpp
class QQuickDetailsViewModel : public QAbstractItemModel {
QSharedPointer<QDetailsViewRow_Property> mRoot; // 根行(不可见)
QObject* mObject; // 被检查的对象
void setObject(QObject* inObject) {
mObject = inObject;
mRoot->setHandle(QPropertyHandle::FindOrCreate(mObject)); // 创建根 Handle
mRoot->invalidateChildren(); // 触发递归构建属性树
}
};
```
**模型的树结构**(带分组示例):
```
mRoot (QDetailsViewRow_Property, 不可见)
├── QDetailsViewRow_Group("数值") ← 分组标题(可折叠)
│ ├── QDetailsViewRow_Property("Int") ← QMetaProperty
│ ├── QDetailsViewRow_Property("Float")
│ └── QDetailsViewRow_Property("LimitedDouble")
├── QDetailsViewRow_Group("文本") ← 分组标题(可折叠)
│ ├── QDetailsViewRow_Property("String")
│ └── QDetailsViewRow_Property("Directory")
├── QDetailsViewRow_Group("自定义类型") ← 分组标题(可折叠)
│ ├── QDetailsViewRow_Property("CustomEnum")
│ ├── QDetailsViewRow_Property("CustomType") ← IPropertyTypeCustomization
│ │ ├── QDetailsViewRow_Property("ArraySize")
│ │ └── QDetailsViewRow_Property("Array")
│ │ ├── QDetailsViewRow_Property("[0]")
│ │ └── QDetailsViewRow_Property("[1]")
│ └── QDetailsViewRow_Property("CustomGadget")
│ ├── QDetailsViewRow_Property("LimitedDouble")
│ └── QDetailsViewRow_Property("Desc")
└── QDetailsViewRow_Property("m_lastBoudingRectF") ← 未分组属性(直接挂根)
```
#### 3. QPropertyHandle属性操作的统一入口
```cpp
// source/PropertyEditor/source/include/QPropertyHandle.h
// source/PropertyEditor/source/src/QPropertyHandle.cpp
class QPropertyHandle : public QObject {
Q_OBJECT
Q_PROPERTY(QVariant Var READ getVar WRITE setVar NOTIFY asVarChanged)
using Getter = std::function<QVariant()>;
using Setter = std::function<void(QVariant)>;
QMetaType mType; // 属性的 QMetaType
PropertyType mPropertyType; // RawType | Enum | Sequential | Associative | Object
QString mPropertyPath; // 属性路径(如 "SubObject.LimitedDouble"
Getter mGetter; // 获取属性值的 lambda
Setter mSetter; // 设置属性值的 lambda
QVariantHash mMetaData; // 从 Q_CLASSINFO 解析的元数据Min, Max, Step, Precision
QSharedPointer<IPropertyHandleImpl> mImpl; // 根据 PropertyType 选择的实现策略
};
```
**关键接口**
- `static FindOrCreate(parent, type, path, getter, setter)` — 在父对象中查找已有 Handle找不到则创建新的。Handle 作为 `parent` 的子 QObject 存储,生命周期跟随父对象
- `getVar()` / `setVar()` — 通过 Getter/Setter lambda 读写实际属性值
- `setVar()` 的 rollback 机制:设置后重新读取,如果值不匹配则发射 `asRequestRollback` 信号通知编辑器回滚显示值
- `parserType(QMetaType)` — 根据 QMetaType 自动分类:
- 可转为 QVariantList 且不可转为 QString → Sequential列表
- 可转为 QVariantMap → Associative映射
- IsEnumeration flag → Enum
- 有 metaObjectQObject 或 QSharedPointer<QObject>)→ Object
- 否则 → RawType基本类型
- `findOrCreateChild()` — 为 Object 类型的属性创建子 Handle自动连接到父 Handle 的 `asVarChanged` 信号以触发子项刷新
#### 4. IPropertyHandleImpl编辑器创建策略
```
IPropertyHandleImpl
├── QPropertyHandleImpl_RawType → 调用 QQuickDetailsViewManager::createValueEditor()
│ 查找已注册的 TypeEditor
├── QPropertyHandleImpl_Enum → 创建 ComboBox 选择器
├── QPropertyHandleImpl_Object → createValueEditor() 返回 nullptrObject 类型本身不显示值编辑器)
│ ├── getObject() / getGadget() — 区分 QObject* 和 Q_GADGET 值类型
│ ├── refreshObjectPtr() — 从 Handle 重新读取对象指针
│ └── 核心作用:作为属性树的内部节点,展开显示子属性
├── QPropertyHandleImpl_Sequential → 展开为 index-based 子行([0], [1], ...
└── QPropertyHandleImpl_Associative → 展开为 key-value 子行
```
#### 5. IDetailsViewRow模型树节点
```
IDetailsViewRow (抽象基类)
├── QDetailsViewRow_Property ← 包装一个 QPropertyHandle
│ setupItem(): 如果有 IPropertyTypeCustomization → customizeHeaderRow()
│ 否则 → makePropertyRow(handle)(默认的名称+值编辑器布局)
│ attachChildren(): 如果有 IPropertyTypeCustomization → customizeChildren()
│ 否则 → PropertyTypeCustomization_ObjectDefault遍历 metaObject->propertyCount()
├── QDetailsViewRow_Custom ← 支持通过 lambda 创建自定义行
│ setupItem(): 调用 mRowCreator(&builder) 直接构造 QML
└── QDetailsViewRow_Group ← 可折叠的属性分组标题行
isGroup() 返回 truesetupItem() 渲染粗体分组标题 + 折叠时显示子项计数 "(N)"
```
**QDetailsViewRow_Group**:
- `isGroup()` 返回 `true`,通过 model 的 `isGroup` role 暴露给 QML delegate
- `setupItem()` 使用 `makeNameValueSlot()` 构建行布局:
- **名称区**:粗体 (`font.bold: true`) 文本显示分组名(如"数值"、"颜色"
- **值区**:折叠时显示 `"(N)"` 表示子属性数量(通过 `groupChildCount` 属性绑定)
- 高度固定 25px颜色使用 `ColorPalette.theme.labelPrimary`
- 展开/折叠由 delegate 模板中的 `TapHandler` 触发 `detailsView.toggleExpanded(row)`
- `setObject()` 时 C++ 侧自动展开所有 group 行(一次性,避免 delegate 重建时的重复展开循环)
#### 6. IPropertyTypeCustomization类型自定义扩展接口
```cpp
class IPropertyTypeCustomization {
virtual void customizeHeaderRow(QPropertyHandle*, QQuickDetailsViewRowBuilder*);
virtual void customizeChildren(QPropertyHandle*, QQuickDetailsViewLayoutBuilder*);
};
```
- `customizeHeaderRow`: 自定义该属性行本身的 UI替换默认的 name + value editor
- `customizeChildren`: 自定义该属性展开后的子行结构
- 注册方式:`QQuickDetailsViewManager::Get()->registerPropertyTypeCustomization<QCustomType, MyCustomization>()`
**内置 Customization**
- `PropertyTypeCustomization_ObjectDefault`:默认的 QObject 处理,遍历 `metaObject->propertyCount()`,为每个 QMetaProperty 创建子 Handle使用 `prop.read()`/`prop.write()`。Gadget 类型从索引 0 开始QObject 类型从索引 1 开始(跳过 `objectName`
- `PropertyTypeCustomization_Sequential`:展开 QList/QVector 等顺序容器
- `PropertyTypeCustomization_Associative`:展开 QMap 等关联容器
- `PropertyTypeCustomization_Matrix4x4`4×4 矩阵编辑器
#### 7. QQuickDetailsViewManager全局注册中心Singleton
```cpp
QHash<const QMetaObject*, PropertyTypeCustomizationCreator> mClassCustomizationMap; // QObject 类型 → 自定义
QHash<QMetaType, PropertyTypeCustomizationCreator> mMetaTypeCustomizationMap; // 值类型 → 自定义
QHash<QMetaType, TypeEditorCreator> mTypeEditorCreatorMap; // 基本类型 → 编辑器 QML 组件
```
- 内置注册的基本类型编辑器:`int/unsigned int/size_t`, `float`, `double`, `QString`, `QVector2D/3D/4D`, `QColor`, `QDir`
- 内置注册的类型自定义:`QMatrix4x4` → `PropertyTypeCustomization_Matrix4x4`
- QML 模块注册:`DetailsView` (1.0), `ColorPalette` (单例)
- `getCustomPropertyType()` 查找逻辑:
1. Sequential → PropertyTypeCustomization_Sequential
2. Associative → PropertyTypeCustomization_Associative
3. Object → 查找 mClassCustomizationMap精确匹配 + 继承匹配fallback 到 ObjectDefault
4. RawType → 查找 mMetaTypeCustomizationMap精确匹配 + 继承匹配)
#### 8. QQuickDetailsViewRowBuilder / QQuickDetailsViewLayoutBuilderQML 布局构建器)
- **RowBuilder**:构建单行 UI
- `makeNameValueSlot()` — 创建标准的 [名称区域 | 分隔条 | 值区域] QML 布局
- `setupItem(parent, qmlCode)` — 在父 QML Item 中创建子 QML 组件
- `setupLabel(parent, text)` — 创建文本标签
- `makePropertyRow(handle)` — 默认属性行name slot + value slot
- **LayoutBuilder**:管理子行结构
- `addProperty(handle, overrideName)` — 添加属性子行
- `addCustomRow(lambda, overrideName)` — 添加自定义行
- `addGroup(groupName)` — 创建 `QDetailsViewRow_Group` 分组标题行,返回其指针供后续向组内添加子行
- `addObject(QObject*)` — 添加对象作为属性行
### Q_CLASSINFO 元数据系统
通过 `Q_CLASSINFO` 宏向属性编辑器传递校验约束,格式为逗号分隔的 `key=value` 对:
```cpp
Q_CLASSINFO("LimitedDouble", "Min=0,Max=10,Step=0.5,Precision=2")
```
`QPropertyHandle::resloveMetaData()` 解析 `metaObject->classInfo()` 中与属性名匹配的项,将键值对存入 `mMetaData`。ValueEditor 从 `mMetaData` 中读取约束(`Min`, `Max`, `Step`, `Precision`)来配置输入控件。
### Q_PROPERTY_VAR 宏BayTemplate 项目封装)
```cpp
// include/CommonInclude.h
#define Q_PROPERTY_VAR(Type, Name)\
Q_PROPERTY(Type Name READ get##Name WRITE set##Name)\
Type get##Name() { return Name; }\
void set##Name(Type var) {\
Name = var;\
qDebug() << "Set" << #Name << ": " << var;\
}\
Type Name
```
这个宏将属性声明、getter/setter 实现、成员变量声明合并为一行,大大简化 Q_PROPERTY 的使用。
### 当前 BayTemplate 集成状态
**初始化流程**`source/mainwindow.cpp:118-128`
```cpp
// 1. 注册自定义类型编辑器
QQuickDetailsViewManager::Get()->registerPropertyTypeCustomization<QCustomType, PropertyTypeCustomization_CustomType>();
// 2. 创建 QDetailsView 并挂载到右侧 Dock
m_pPropertiesEditorView = new QDetailsView();
m_pPropertiesEditorView->setObject(m_pDrawingPanel->getQGraphicsScene());
```
**选择变化处理**`source/mainwindow.cpp:226-235`
```cpp
void CMainWindow::onSignal_selectionChanged() {
QList<QGraphicsItem*> selectedItems = scene->selectedItems();
if (selectedItems.count() != 1) {
m_pPropertiesEditorView->setObject(scene); // 多选/无选 → 显示场景属性
return;
}
m_pPropertiesEditorView->setObject(static_cast<QObject*>(selectedItems.first()));
}
```
**当前图元的 Q_PROPERTY 状态**
- `GraphicsBusSectionItem`:唯一完整声明了 Q_PROPERTY 的图元Int, Float, LimitedDouble, String, Directory, Vec2-4, Mat4, Color, ColorList, ColorMap, CustomEnum, CustomType, CustomGadget, CustomGadgetPtr, CustomGadgetSharedPtr 等) — 同时重写 `getPropertyGroup()` 将属性分入 5 个可折叠分组(数值、文本、向量/矩阵、颜色、自定义类型)
- `GraphicsBaseItem` 实现了 `IPropertyGroupProvider` 接口,默认返回空字符串(无分组),子类可重写
- `GraphicsRectItem`、`GraphicsPolygonItem`、`GraphicsItemGroup`**无 Q_PROPERTY 声明** — 因此属性编辑器不会显示任何属性行
**BayTemplate 自定义类型**
- `QCustomType` (结构体)`{ unsigned int ArraySize; QVector<int> Array; }` — 通过 `PropertyTypeCustomization_CustomType` 定制header 显示 Sort 按钮children 展开为 ArraySize + Array
- `QCustomGadget` (Q_GADGET)`{ double LimitedDouble; QString Desc; }` — 会被属性编辑器递归展开子属性
### 属性编辑器的值读写循环
```
用户修改 QML 编辑器中的值
QML 编辑器 emit valueChanged(QVariant)
│ (通过 connect 连接)
QPropertyHandle::setVar(var)
├── mSetter(var) ← 写入实际对象属性
├── emit asVarChanged(var) ← 通知其他监听者
├── QVariant currVar = mGetter() ← 重新读取验证
└── if (currVar != var) emit asRequestRollback(currVar) ← 值被拦截时通知编辑器回滚
```
### 属性分组系统 (Property Grouping)
属性分组允许 QObject 子类将 Q_PROPERTY 属性归类到可折叠的分组标题下,使属性编辑器在有大量属性时更易浏览。
#### IPropertyGroupProvider 接口
```cpp
// PropertyEditor/source/include/IPropertyGroupProvider.h
class IPropertyGroupProvider {
public:
virtual ~IPropertyGroupProvider() = default;
virtual QString getPropertyGroup(const QString& propertyName) const = 0;
};
```
- 返回空字符串表示属性不分组(默认行为)
- 返回非空字符串表示属性属于该名称的分组
- 同名分组的属性会被收集到同一个 group 下,按分组调用顺序排列
- **不属于 PropertyEditor 库本身**,而是由使用方(如 BayTemplate 的 GraphicsBaseItem实现
#### 分组构建流程
```
PropertyTypeCustomization_ObjectDefault::customizeChildren(handle, builder)
├── 检查 objectHandle->getObject() 是否实现 IPropertyGroupProvider
│ NO → 原有逻辑:遍历 metaObject->propertyCount() 平铺所有属性
│ YES → 新分组逻辑:
│ 1. 遍历所有 QMetaProperty
│ 2. 调用 groupProvider->getPropertyGroup(prop.name()) 获取分组名
│ 3. 空字符串 → 加入 ungroupedProps 列表
│ 4. 非空 → 加入对应 groupedProps[groupName] 列表
│ 5. 遍历 groupedProps
│ ├── builder->addGroup(groupName) → 创建 QDetailsViewRow_Group
│ └── 在 group 内用子 LayoutBuilder 添加该组的属性
│ 6. 遍历 ungroupedProps
│ └── builder->addProperty() 直接添加到根(不分组)
```
#### QDetailsViewRow_Group 渲染
在 QML delegate 中通过 `isGroup` role 区分 group 行和普通属性行:
- **已展开**:显示粗体分组标题(如"颜色"),右侧无内容
- **已折叠**:显示粗体分组标题 + 右侧 `"(N)"` 显示子属性数量
- 展开/折叠箭头由 `makeNameValueSlot()` 中的图标控制(根据 `detailsDelegate.expanded` 选择 expand.png / unexpand.png
- 点击行触发 `TapHandler { onTapped: detailsView.toggleExpanded(row) }`
**折叠计数实现**`QQuickDetailsViewRow.cpp`
```qml
// 在 value slot 区域创建 countLabel
Item {
property int groupChildCount: 0
visible: !detailsDelegate.expanded // 仅折叠时可见
Text {
anchors.right: parent.right
text: "(" + groupChildCount + ")"
color: ColorPalette.theme.textPrimary
}
}
```
`groupChildCount` 在 C++ 侧通过 `countLabel->setProperty("groupChildCount", childCount)` 设置。
#### Model 角色扩展
ViewModel 新增 `isGroup` role
```cpp
// QQuickDetailsViewModel
enum Roles { name = 0, isGroup };
// roleNames() 返回 { { Roles::isGroup, "isGroup" } }
// data() 分发
case Roles::isGroup: return node->isGroup();
```
`QQuickDetailsViewPrivate::updateRequiredProperties()` 在每个 delegate 初始化时通过 `setRequiredProperty("isGroup", ...)` 将值推送到 QML delegate。
#### setObject() 自动展开
`QQuickDetailsView::setObject()` 在切换检查对象后,遍历顶层 model 行,对所有 `isGroup()` 为 true 的行调用 `expand(row)`。这是一次性的 C++ 侧操作,在 delegate 创建之前完成,避免 delegate 重建时的重复展开循环。
#### BayTemplate 集成
**GraphicsBaseItem** 实现 `IPropertyGroupProvider`,默认返回空字符串(无分组):
```cpp
class GraphicsBaseItem : public QObject, public AbstractShape, public IPropertyGroupProvider {
QString getPropertyGroup(const QString&) const override { return {}; }
};
```
**GraphicsBusSectionItem** 重写 `getPropertyGroup()` 作为使用示例(`source/graphicsItem/graphicsBusSectionItem.cpp`
| 分组名 | 包含属性 |
|--------|---------|
| 数值 | Int, Float, LimitedDouble |
| 文本 | String, Directory |
| 向量/矩阵 | Vec2, Vec3, Vec4, Mat4 |
| 颜色 | Color, ColorList, ColorMap |
| 自定义类型 | CustomEnum, CustomType, CustomGadget, CustomGadgetPtr, CustomGadgetSharedPtr |
不分组(返回空)的属性:`m_lastBoudingRectF`, `m_lastBoudingPointF`, `m_dRatioX`, `m_dRatioY`(内部状态属性)。
**扩展方式**:任意 QObject 子类只需实现 `IPropertyGroupProvider` 接口并重写 `getPropertyGroup()`PropertyEditor 的 `PropertyTypeCustomization_ObjectDefault` 会自动检测并使用分组布局。
### 关键文件索引
| 文件 | 角色 |
|------|------|
| `PropertyEditor/source/include/QDetailsView.h` | C++ QWidget 包装器 |
| `PropertyEditor/source/src/QDetailsView.cpp` | 创建 QQuickWidget加载 QML DetailsView |
| `PropertyEditor/source/include/QQuickDetailsView.h` | QML 类型定义Q_PROPERTY Object, SpliterPencent|
| `PropertyEditor/source/src/QQuickDetailsView.cpp` | 设置 Delegate 模板 |
| `PropertyEditor/source/include/QQuickDetailsViewModel.h` | QAbstractItemModel 实现 |
| `PropertyEditor/source/src/QQuickDetailsViewModel.cpp` | setObject() 入口,构建根 Handle |
| `PropertyEditor/source/include/QPropertyHandle.h` | 属性操作统一入口(核心抽象)|
| `PropertyEditor/source/src/QPropertyHandle.cpp` | getter/setter 绑定,类型解析,子 Handle 创建 |
| `PropertyEditor/source/include/PropertyHandleImpl/IPropertyHandleImpl.h` | Impl 策略基类 |
| `PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_Object.cpp` | QObject/QGadget 的属性展开逻辑 |
| `PropertyEditor/source/src/PropertyHandleImpl/QPropertyHandleImpl_RawType.cpp` | 基本类型的值编辑器委托 |
| `PropertyEditor/source/src/QQuickDetailsViewRow.cpp` | 模型行节点setupItem + attachChildren|
| `PropertyEditor/source/src/QQuickDetailsViewLayoutBuilder.cpp` | QML 布局构建器 |
| `PropertyEditor/source/include/IPropertyTypeCustomization.h` | 类型定制接口 |
| `PropertyEditor/source/include/IPropertyGroupProvider.h` | 属性分组接口QObject 声明属性所属分组)|
| `PropertyEditor/source/src/Customization/PropertyTypeCustomization_ObjectDefault.cpp` | 默认 QObject 属性展开(含分组检测逻辑)|
| `PropertyEditor/source/include/QQuickDetailsViewMananger.h` | 全局注册中心单例 |
| `PropertyEditor/source/src/QQuickDetailsViewMananger.cpp` | 类型编辑器注册、自定义类型查找 |
| `PropertyEditor/source/src/QQuickDetailsViewBasicTypeEditor.cpp` | 基本类型编辑器注册int, float, QString, QColor 等)|
| `PropertyEditor/source/src/QQuickDetailsViewPrivate.h` | 内部的 Model + Delegate 回调 |
| `PropertyEditor/source/src/QQuickFunctionLibrary.cpp` | QML 辅助函数库 |
| `include/propertyType/CustomType.h` | BayTemplate 自定义测试类型 |
| `include/propertyType/CustomGadget.h` | BayTemplate 自定义测试 Gadget |
| `include/CommonInclude.h` | Q_PROPERTY_VAR 宏 |
| `source/propertyType/PropertyTypeCustomization_CustomType.cpp` | BayTemplate 对 QCustomType 的编辑器定制 |
| `include/graphicsItem/graphicsBusSectionItem.h` | 唯一声明了 Q_PROPERTY 的图元(测试用途)|