BayTemplate/CLAUDE.md

29 KiB
Raw Blame History

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 (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: DesignerSceneSelectorManager::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 canvasDesignerViewDesignerSceneSelectorManager::getWorkingSelector() → selector processes event

  3. User selects itemsDesignerScene::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 共享):

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 工作机制:

  1. AbstractShapeType::updateHandles() 根据 boundingRect 计算 handle 位置
  2. Handle 位置在 boundingRect 外扩 5px (nMargin = 5)
  3. collidesWithHandle() 检测鼠标点击在哪个 handle 上
  4. 不同操作模式对 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 类职责

  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

{
    "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

移动、旋转、缩放都使用"操作副本"作为视觉预览:

  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 编辑多边形顶点等