# 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) │ │ ├── GraphicsRectItem (rectangle, 圆角矩形) │ │ ├── GraphicPolygonItem (多点绘制,顶点编辑) │ │ ├── GraphicsBusSectionItem (总线段) │ │ └── GraphicsItemGroup (AbstractShapeType) │ └─────────────────────────────────────────────────────────────┘ ``` ### 坐标系与变换系统 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 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 m_vecHanle; QGraphicsPathItem* m_pOperationCopy; QPointF m_movingIniPos; }; // 具体类型定义 typedef AbstractShapeType AbstractShape; // 单图元基类 // GraphicsBaseItem : QObject, AbstractShape // GraphicsItemGroup : QObject, AbstractShapeType // 组图元基类 ``` **关键点:** - `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(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`) 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(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 | 编辑多边形顶点等 |