29 KiB
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 进行配置和构建:
# 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 (
QGraphicsViewsubclass): Custom view with zoom (0.02-50x), pan (middle-button drag), checkerboard background - DesignerScene (
QGraphicsScenesubclass): 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
QGraphicsSceneand displays properties of selected items
6. Command Pattern (Undo/Redo)
- QUndoStack (
m_pUndoStackin 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
-
User drags from 图元面板 →
GraphicElementsPanelemits signal →DesignerScene::signalAddItem()→ creates item withAddItemCommand -
User clicks on canvas →
DesignerView→DesignerScene→SelectorManager::getWorkingSelector()→ selector processes event -
User selects items →
DesignerScene::selectionChanged()→CMainWindow::onSignal_selectionChanged()→ updates property editor -
User modifies property in 属性编辑器 →
QDetailsViewupdates 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
- QtADS (
QtADS/subdirectory, v4.3.1): Advanced docking system - must be present as subdirectory - 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
-
CMAKE_AUTOUIC_SEARCH_PATHS: Set to
"ui"in CMakeLists.txt because .ui files are in separate directory from header files -
Group Flattening: When creating a group that contains existing groups, the scene flattens nested groups first to prevent group nesting (see
DesignerScene::createGroup()) -
Zoom Implementation: Uses
QGraphicsView::zoom()with smooth factorqPow(1.0015, angleDelta.y())per wheel event -
Wchar String Conversion: Uses
QString::fromWCharArray(L"中文")for Chinese UI strings -
Resource File:
resource/BayTemplate.qrccontains 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 共享):
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 类型枚举:
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 工作机制:
AbstractShapeType::updateHandles()根据 boundingRect 计算 handle 位置- Handle 位置在 boundingRect 外扩 5px (nMargin = 5)
collidesWithHandle()检测鼠标点击在哪个 handle 上- 不同操作模式对 handle 的响应不同
操作副本(Operation Copy)模式
移动和旋转操作使用"预览副本"技术,提供视觉反馈:
// 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 模板)
// 使用模板继承,允许继承 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 实现:
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() 实现组扁平化:
// 如果选中的 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 类职责
-
序列化/反序列化(JSON 格式)
serialize()/deserialize(): 核心序列化为 QByteArraysaveToFile()/loadFromFile(): 文件 I/O 封装- 递归序列化/反序列化图元层次结构(支持组图元嵌套)
-
状态管理
m_filename: 文档文件名(空表示未保存的新文档)m_modified: 是否被修改(通过setModified()和modifiedChanged信号通知)m_created/m_modifiedTime/m_lastSavedTime: 时间戳m_metaData: 可扩展的自定义元数据 (QMap<QString, QVariant>)
-
与 CMainWindow 的集成
initializeDocument(): 创建 Document 并关联 DesignerScene- 连接
modifiedChanged信号来更新窗口标题(添加 * 标记) closeEvent()中检查isModified()提示用户保存- 文件操作:New/Open/Save 通过 Document 完成
序列化格式(v1.0)
{
"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 子类代表不同的状态:
// 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)
移动、旋转、缩放都使用"操作副本"作为视觉预览:
- mousePressEvent: 创建虚线副本
- mouseMoveEvent: 移动副本(本体不动)
- 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 | 编辑多边形顶点等 |