Qt - 图形视图(Graphics View)

我们首先来介绍Qt的图形视图框架(Graphics View Framework),然后通过一个完整的示例来展示如何使用它。

  1. 概述
    Qt的图形视图框架提供了一个基于图形项的模型视图编程方法。它主要由三个部分组成:场景(QGraphicsScene)、视图(QGraphicsView)和图形项(QGraphicsItem)。

  • QGraphicsScene:管理所有的图形项,并负责将事件传递给图形项,以及提供场景的视图功能。

  • QGraphicsView:用于显示场景的内容,可以多个视图同时显示同一个场景,并且支持缩放和旋转。

  • QGraphicsItem:场景中的图形项,可以是自定义的图形、文本、甚至控件。

  1. 基本使用步骤
    a. 创建场景(QGraphicsScene)
    b. 创建图形项(例如:QGraphicsRectItem,QGraphicsEllipseItem,或者自定义的QGraphicsItem)
    c. 将图形项添加到场景中
    d. 创建视图(QGraphicsView)并设置场景

  2. 示例:创建一个简单的图形视图应用,包括矩形、椭圆和文本,并且支持鼠标事件。

  3. 进阶:自定义图形项,实现更复杂的图形和交互。

 

一、图形视图框架概述

1.1 什么是图形视图框架

Qt图形视图框架是一个用于管理和交互大量2D图形对象的强大系统。它提供了渲染、缩放、旋转和编辑自定义图形项的能力,非常适合开发CAD软件、流程图工具、游戏等需要复杂图形交互的应用。

核心组件:

  • 场景(Scene):所有图形项的容器,管理坐标系和项管理

  • 视图(View):可视化场景的窗口部件,支持缩放、旋转、拖放

  • 图形项(Item):场景中的可视对象,可以响应事件

1.2 图形视图与传统绘图对比

 
 
特性 QPainter绘图 图形视图框架
项数量 数十到数百 数千到数百万
交互性 有限 每个项可独立交互
坐标系统 单一 场景、项、视图三级坐标
内存管理 手动 自动
渲染性能 中等 高度优化

二、基础组件使用

2.1 基本场景、视图和项

#include "widget.h"

#include <QApplication>

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsTextItem>
#include <QPen>
#include <QBrush>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // 1. 创建场景 (坐标系)
    QGraphicsScene scene;
    scene.setSceneRect(-200, -200, 400, 400);  // 设置场景范围
    scene.setBackgroundBrush(Qt::lightGray);   // 设置背景

    // 2. 创建各种图形项并添加到场景
    // 矩形项
    QGraphicsRectItem *rectItem = new QGraphicsRectItem(-50, -50, 100, 100);
    rectItem->setBrush(QBrush(Qt::blue));
    rectItem->setPen(QPen(Qt::black, 2));
    rectItem->setFlag(QGraphicsItem::ItemIsMovable);  // 可移动
    rectItem->setFlag(QGraphicsItem::ItemIsSelectable); // 可选择
    scene.addItem(rectItem);

    // 椭圆项
    QGraphicsEllipseItem *ellipseItem = new QGraphicsEllipseItem(-40, -30, 80, 60);
    ellipseItem->setBrush(QBrush(Qt::red));
    ellipseItem->setPen(QPen(Qt::black, 2));
    ellipseItem->setFlag(QGraphicsItem::ItemIsMovable);
    ellipseItem->setPos(100, 0);  // 设置位置
    scene.addItem(ellipseItem);

    // 文本项
    QGraphicsTextItem *textItem = new QGraphicsTextItem("Hello Qt Graphics");
    textItem->setDefaultTextColor(Qt::white);
    textItem->setFont(QFont("Arial", 14));
    textItem->setFlag(QGraphicsItem::ItemIsMovable);
    textItem->setPos(0, 100);
    scene.addItem(textItem);

    // 3. 创建视图来显示场景
    QGraphicsView view(&scene);
    view.setRenderHint(QPainter::Antialiasing);  // 抗锯齿
    view.setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    view.setDragMode(QGraphicsView::RubberBandDrag);  // 框选模式
    view.resize(600, 600);
    view.setWindowTitle("Basic Graphics View Demo");
    view.show();

    return a.exec();
}

运行效果:

image

 

2.2 坐标系系统

void coordinateSystemDemo() {
    QGraphicsScene scene(0, 0, 800, 600);
    
    // 添加坐标轴
    scene.addLine(0, 0, 500, 0, QPen(Qt::red, 2));   // X轴
    scene.addLine(0, 0, 0, 500, QPen(Qt::green, 2)); // Y轴
    
    // 场景坐标 (全局坐标)
    QGraphicsRectItem *sceneRect = new QGraphicsRectItem(100, 100, 50, 50);
    sceneRect->setBrush(Qt::blue);
    scene.addItem(sceneRect);
    
    // 项的局部坐标
    QGraphicsRectItem *parentRect = new QGraphicsRectItem(0, 0, 100, 100);
    parentRect->setBrush(Qt::yellow);
    parentRect->setPos(200, 200);  // 在场景中的位置
    scene.addItem(parentRect);
    
    // 子项 (相对于父项的坐标)
    QGraphicsEllipseItem *childEllipse = new QGraphicsEllipseItem(-20, -20, 40, 40);
    childEllipse->setBrush(Qt::red);
    childEllipse->setParentItem(parentRect);  // 设置为子项
    childEllipse->setPos(50, 50);  // 相对于父项的位置
    
    // 坐标转换演示
    QPointF scenePoint(250, 250);  // 场景坐标
    QPointF itemPoint = childEllipse->mapFromScene(scenePoint);  // 转换为项坐标
    QPointF scenePoint2 = childEllipse->mapToScene(itemPoint);   // 转回场景坐标
    
    qDebug() << "Scene Point:" << scenePoint;
    qDebug() << "Item Local Point:" << itemPoint;
    qDebug() << "Back to Scene:" << scenePoint2;
    
    QGraphicsView view(&scene);
    view.resize(800, 600);
    view.show();
}

三、自定义图形项

3.1 基础自定义项

#include <QGraphicsItem>
#include <QPainter>
#include <QStyleOptionGraphicsItem>

class CustomGraphicsItem : public QGraphicsItem {
public:
    explicit CustomGraphicsItem(QGraphicsItem *parent = nullptr)
        : QGraphicsItem(parent) {
        // 启用标志
        setFlag(ItemIsMovable);
        setFlag(ItemIsSelectable);
        setFlag(ItemSendsGeometryChanges);
        
        // 设置光标
        setCursor(Qt::OpenHandCursor);
    }
    
    // 必须实现的纯虚函数:边界矩形
    QRectF boundingRect() const override {
        // 返回项的边界矩形(用于碰撞检测和重绘区域)
        return QRectF(-50, -50, 100, 100).adjusted(-5, -5, 5, 5);
    }
    
    // 必须实现的纯虚函数:绘制项
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
               QWidget *widget = nullptr) override {
        Q_UNUSED(widget);
        
        // 根据选中状态设置画笔和画刷
        QPen pen(isSelected() ? Qt::red : Qt::black, 2);
        pen.setCosmetic(true);  // 保持线宽不随缩放改变
        painter->setPen(pen);
        
        QBrush brush(m_color);
        painter->setBrush(brush);
        
        // 绘制主体
        painter->drawRoundedRect(-50, -50, 100, 100, 20, 20);
        
        // 绘制装饰
        painter->setPen(QPen(Qt::white, 1));
        painter->drawEllipse(-20, -20, 40, 40);
        
        // 绘制文本
        painter->setPen(QPen(Qt::white, 1));
        painter->drawText(QRectF(-50, -50, 100, 100), 
                         Qt::AlignCenter, "Custom Item");
        
        // 绘制细节(如选中时的边框)
        if (option->state & QStyle::State_Selected) {
            painter->setPen(QPen(Qt::red, 1, Qt::DashLine));
            painter->setBrush(Qt::NoBrush);
            painter->drawRect(boundingRect());
        }
    }
    
    // 处理鼠标事件
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
        if (event->button() == Qt::LeftButton) {
            setCursor(Qt::ClosedHandCursor);
            m_dragStartPos = pos();
        }
        QGraphicsItem::mousePressEvent(event);
    }
    
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
        setCursor(Qt::OpenHandCursor);
        QGraphicsItem::mouseReleaseEvent(event);
    }
    
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override {
        // 双击改变颜色
        m_color = QColor::fromHsl(rand() % 360, 255, 200);
        update();  // 触发重绘
        QGraphicsItem::mouseDoubleClickEvent(event);
    }
    
    // 项属性变化时调用
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override {
        if (change == ItemPositionHasChanged) {
            // 位置改变时发出信号(如果需要)
            emit positionChanged(pos());
        }
        return QGraphicsItem::itemChange(change, value);
    }
    
    // 设置颜色
    void setColor(const QColor &color) {
        m_color = color;
        update();
    }
    
    QColor color() const { return m_color; }
    
signals:
    void positionChanged(const QPointF &newPos);

private:
    QColor m_color = QColor(100, 150, 200);
    QPointF m_dragStartPos;
};

 

3.2 高级自定义项:可连接节点

#include <QVector2D>
#include <QGraphicsPathItem>

class ConnectionPoint {
public:
    enum Type { Input, Output };
    
    ConnectionPoint(QGraphicsItem *parent, Type type, const QPointF &pos)
        : m_parent(parent), m_type(type), m_localPos(pos) {}
    
    QPointF scenePos() const {
        return m_parent->mapToScene(m_localPos);
    }
    
    Type type() const { return m_type; }
    QGraphicsItem *parentItem() const { return m_parent; }
    
private:
    QGraphicsItem *m_parent;
    Type m_type;
    QPointF m_localPos;
};

class NodeItem : public QGraphicsItem {
public:
    explicit NodeItem(const QString &title, QGraphicsItem *parent = nullptr)
        : QGraphicsItem(parent), m_title(title) {
        setFlag(ItemIsMovable);
        setFlag(ItemIsSelectable);
        setFlag(ItemSendsGeometryChanges);
        
        // 创建连接点
        m_inputPoints.append(ConnectionPoint(this, ConnectionPoint::Input, 
                                            QPointF(-60, -30)));
        m_inputPoints.append(ConnectionPoint(this, ConnectionPoint::Input, 
                                            QPointF(-60, 30)));
        
        m_outputPoints.append(ConnectionPoint(this, ConnectionPoint::Output, 
                                             QPointF(60, 0)));
    }
    
    QRectF boundingRect() const override {
        return QRectF(-80, -50, 160, 100).adjusted(-5, -5, 5, 5);
    }
    
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
               QWidget *widget = nullptr) override {
        Q_UNUSED(option);
        Q_UNUSED(widget);
        
        // 绘制节点主体
        QLinearGradient gradient(-80, -50, -80, 50);
        gradient.setColorAt(0, QColor(200, 220, 240));
        gradient.setColorAt(1, QColor(150, 180, 220));
        
        painter->setBrush(gradient);
        painter->setPen(QPen(Qt::black, 2));
        painter->drawRoundedRect(-80, -50, 160, 100, 10, 10);
        
        // 绘制标题栏
        painter->setBrush(QColor(80, 120, 180));
        painter->drawRect(-80, -50, 160, 30);
        
        // 绘制标题
        painter->setPen(Qt::white);
        painter->setFont(QFont("Arial", 10, QFont::Bold));
        painter->drawText(QRectF(-80, -50, 160, 30), Qt::AlignCenter, m_title);
        
        // 绘制连接点
        painter->setBrush(Qt::gray);
        painter->setPen(QPen(Qt::black, 1));
        
        // 输入点
        for (const auto &point : m_inputPoints) {
            painter->drawEllipse(point.scenePos() - mapToScene(QPointF(0, 0)), 
                                5, 5);
        }
        
        // 输出点
        for (const auto &point : m_outputPoints) {
            painter->drawEllipse(point.scenePos() - mapToScene(QPointF(0, 0)), 
                                5, 5);
        }
        
        // 绘制连接点标签
        painter->setPen(Qt::black);
        painter->setFont(QFont("Arial", 8));
        painter->drawText(-75, -25, "Input 1");
        painter->drawText(-75, 35, "Input 2");
        painter->drawText(55, 5, "Output");
    }
    
    // 获取最近的连接点
    ConnectionPoint* nearestConnectionPoint(const QPointF &scenePos) {
        double minDist = std::numeric_limits<double>::max();
        ConnectionPoint *nearest = nullptr;
        
        auto checkPoints = [&](const QVector<ConnectionPoint> &points) {
            for (auto &point : points) {
                double dist = QVector2D(point.scenePos() - scenePos).length();
                if (dist < minDist && dist < 20) {  // 20像素内有效
                    minDist = dist;
                    nearest = &point;
                }
            }
        };
        
        checkPoints(m_inputPoints);
        checkPoints(m_outputPoints);
        
        return nearest;
    }
    
    QVector<ConnectionPoint> inputPoints() const { return m_inputPoints; }
    QVector<ConnectionPoint> outputPoints() const { return m_outputPoints; }
    
private:
    QString m_title;
    QVector<ConnectionPoint> m_inputPoints;
    QVector<ConnectionPoint> m_outputPoints;
};

四、连接和交互

4.1 连接线实现

class ConnectionItem : public QGraphicsPathItem {
public:
    ConnectionItem(ConnectionPoint *startPoint, ConnectionPoint *endPoint, 
                   QGraphicsItem *parent = nullptr)
        : QGraphicsPathItem(parent), m_startPoint(startPoint), m_endPoint(endPoint) {
        setPen(QPen(Qt::black, 2));
        setZValue(-1);  // 确保在节点下方
        updatePath();
    }
    
    void updatePath() {
        if (!m_startPoint || !m_endPoint) return;
        
        QPointF startPos = m_startPoint->scenePos();
        QPointF endPos = m_endPoint->scenePos();
        
        // 创建曲线路径
        QPainterPath path(startPos);
        
        // 计算控制点(贝塞尔曲线)
        qreal dx = endPos.x() - startPos.x();
        qreal dy = endPos.y() - startPos.y();
        
        QPointF ctrl1(startPos.x() + dx * 0.5, startPos.y());
        QPointF ctrl2(startPos.x() + dx * 0.5, endPos.y());
        
        path.cubicTo(ctrl1, ctrl2, endPos);
        
        setPath(path);
    }
    
    ConnectionPoint *startPoint() const { return m_startPoint; }
    ConnectionPoint *endPoint() const { return m_endPoint; }
    
private:
    ConnectionPoint *m_startPoint;
    ConnectionPoint *m_endPoint;
};

class GraphicsScene : public QGraphicsScene {
    Q_OBJECT
    
public:
    explicit GraphicsScene(QObject *parent = nullptr)
        : QGraphicsScene(parent), m_tempLine(nullptr), m_draggingLine(false) {
        // 设置场景
        setSceneRect(-1000, -1000, 2000, 2000);
        
        // 创建网格背景
        setBackgroundBrush(QBrush(QColor(240, 240, 240)));
        
        // 添加一些初始节点
        NodeItem *node1 = new NodeItem("Node 1");
        node1->setPos(-200, 0);
        addItem(node1);
        
        NodeItem *node2 = new NodeItem("Node 2");
        node2->setPos(200, 0);
        addItem(node2);
    }
    
protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
        if (event->button() == Qt::LeftButton) {
            // 查找点击的连接点
            QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
            if (NodeItem *node = dynamic_cast<NodeItem*>(item)) {
                if (ConnectionPoint *point = node->nearestConnectionPoint(event->scenePos())) {
                    if (point->type() == ConnectionPoint::Output) {
                        // 开始拖拽连接线
                        m_draggingLine = true;
                        m_startConnectionPoint = point;
                        
                        // 创建临时线
                        m_tempLine = new QGraphicsLineItem;
                        m_tempLine->setPen(QPen(Qt::black, 2, Qt::DashLine));
                        addItem(m_tempLine);
                        
                        QPointF startPos = point->scenePos();
                        m_tempLine->setLine(startPos.x(), startPos.y(),
                                           event->scenePos().x(), event->scenePos().y());
                        return;
                    }
                }
            }
        }
        
        QGraphicsScene::mousePressEvent(event);
    }
    
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
        if (m_draggingLine && m_tempLine) {
            // 更新临时线
            QPointF startPos = m_startConnectionPoint->scenePos();
            m_tempLine->setLine(startPos.x(), startPos.y(),
                               event->scenePos().x(), event->scenePos().y());
            
            // 高亮潜在的连接点
            QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
            if (NodeItem *node = dynamic_cast<NodeItem*>(item)) {
                if (ConnectionPoint *point = node->nearestConnectionPoint(event->scenePos())) {
                    if (point->type() == ConnectionPoint::Input) {
                        // 可以在此处高亮显示
                    }
                }
            }
        }
        
        QGraphicsScene::mouseMoveEvent(event);
    }
    
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
        if (m_draggingLine && m_tempLine) {
            // 查找释放点的连接点
            QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
            if (NodeItem *node = dynamic_cast<NodeItem*>(item)) {
                if (ConnectionPoint *endPoint = node->nearestConnectionPoint(event->scenePos())) {
                    if (endPoint->type() == ConnectionPoint::Input) {
                        // 创建永久连接
                        ConnectionItem *connection = new ConnectionItem(
                            m_startConnectionPoint, endPoint);
                        addItem(connection);
                        m_connections.append(connection);
                    }
                }
            }
            
            // 清理临时线
            removeItem(m_tempLine);
            delete m_tempLine;
            m_tempLine = nullptr;
            m_draggingLine = false;
        }
        
        QGraphicsScene::mouseReleaseEvent(event);
    }
    
    void drawBackground(QPainter *painter, const QRectF &rect) override {
        QGraphicsScene::drawBackground(painter, rect);
        
        // 绘制网格
        painter->setPen(QPen(QColor(200, 200, 200), 1));
        
        qreal left = int(rect.left()) - (int(rect.left()) % 50);
        qreal top = int(rect.top()) - (int(rect.top()) % 50);
        
        for (qreal x = left; x < rect.right(); x += 50) {
            painter->drawLine(x, rect.top(), x, rect.bottom());
        }
        
        for (qreal y = top; y < rect.bottom(); y += 50) {
            painter->drawLine(rect.left(), y, rect.right(), y);
        }
    }
    
private:
    QVector<ConnectionItem*> m_connections;
    ConnectionPoint *m_startConnectionPoint = nullptr;
    QGraphicsLineItem *m_tempLine = nullptr;
    bool m_draggingLine;
};

五、视图控制和交互

5.1 自定义视图控件

class CustomGraphicsView : public QGraphicsView {
    Q_OBJECT
    
public:
    explicit CustomGraphicsView(QWidget *parent = nullptr)
        : QGraphicsView(parent), m_zoomLevel(0), m_panning(false) {
        setRenderHint(QPainter::Antialiasing);
        setRenderHint(QPainter::SmoothPixmapTransform);
        setRenderHint(QPainter::TextAntialiasing);
        
        setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
        setDragMode(QGraphicsView::RubberBandDrag);
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        
        // 启用 OpenGL 加速(如果可用)
        QOpenGLWidget *glWidget = new QOpenGLWidget;
        QSurfaceFormat format;
        format.setSamples(4);  // 4倍多重采样抗锯齿
        glWidget->setFormat(format);
        setViewport(glWidget);
    }
    
protected:
    void wheelEvent(QWheelEvent *event) override {
        // 缩放控制
        if (event->modifiers() & Qt::ControlModifier) {
            qreal factor = 1.0;
            if (event->angleDelta().y() > 0) {
                factor = 1.1;  // 放大
                m_zoomLevel++;
            } else {
                factor = 0.9;  // 缩小
                m_zoomLevel--;
            }
            
            // 限制缩放范围
            if (m_zoomLevel > 10) {
                m_zoomLevel = 10;
                return;
            }
            if (m_zoomLevel < -10) {
                m_zoomLevel = -10;
                return;
            }
            
            scale(factor, factor);
            emit zoomLevelChanged(m_zoomLevel);
        } else {
            QGraphicsView::wheelEvent(event);
        }
    }
    
    void mousePressEvent(QMouseEvent *event) override {
        if (event->button() == Qt::MiddleButton) {
            // 中键拖动平移
            m_panning = true;
            m_panStartPos = event->pos();
            setCursor(Qt::ClosedHandCursor);
            return;
        }
        
        QGraphicsView::mousePressEvent(event);
    }
    
    void mouseMoveEvent(QMouseEvent *event) override {
        if (m_panning) {
            // 计算平移距离
            QPointF delta = mapToScene(event->pos()) - mapToScene(m_panStartPos);
            m_panStartPos = event->pos();
            
            // 平移视图
            centerOn(mapToScene(rect().center()) - delta);
            return;
        }
        
        QGraphicsView::mouseMoveEvent(event);
    }
    
    void mouseReleaseEvent(QMouseEvent *event) override {
        if (event->button() == Qt::MiddleButton) {
            m_panning = false;
            setCursor(Qt::ArrowCursor);
            return;
        }
        
        QGraphicsView::mouseReleaseEvent(event);
    }
    
    void contextMenuEvent(QContextMenuEvent *event) override {
        QMenu menu;
        
        QAction *zoomIn = menu.addAction("Zoom In");
        QAction *zoomOut = menu.addAction("Zoom Out");
        QAction *resetZoom = menu.addAction("Reset Zoom");
        menu.addSeparator();
        QAction *fitView = menu.addAction("Fit in View");
        QAction *showGrid = menu.addAction("Show Grid");
        showGrid->setCheckable(true);
        showGrid->setChecked(true);
        
        QAction *selected = menu.exec(event->globalPos());
        
        if (selected == zoomIn) {
            scale(1.2, 1.2);
            m_zoomLevel++;
        } else if (selected == zoomOut) {
            scale(0.8, 0.8);
            m_zoomLevel--;
        } else if (selected == resetZoom) {
            resetTransform();
            m_zoomLevel = 0;
        } else if (selected == fitView) {
            fitInView(sceneRect(), Qt::KeepAspectRatio);
        }
    }
    
    void keyPressEvent(QKeyEvent *event) override {
        switch (event->key()) {
        case Qt::Key_Plus:
        case Qt::Key_Equal:
            if (event->modifiers() & Qt::ControlModifier) {
                scale(1.2, 1.2);
                m_zoomLevel++;
            }
            break;
            
        case Qt::Key_Minus:
            if (event->modifiers() & Qt::ControlModifier) {
                scale(0.8, 0.8);
                m_zoomLevel--;
            }
            break;
            
        case Qt::Key_0:
            if (event->modifiers() & Qt::ControlModifier) {
                resetTransform();
                m_zoomLevel = 0;
            }
            break;
            
        case Qt::Key_F:
            fitInView(sceneRect(), Qt::KeepAspectRatio);
            break;
            
        case Qt::Key_Delete:
            // 删除选中项
            if (scene()) {
                QList<QGraphicsItem*> selectedItems = scene()->selectedItems();
                for (QGraphicsItem *item : selectedItems) {
                    scene()->removeItem(item);
                    delete item;
                }
            }
            break;
            
        case Qt::Key_Escape:
            if (scene()) {
                scene()->clearSelection();
            }
            break;
        }
        
        QGraphicsView::keyPressEvent(event);
    }
    
signals:
    void zoomLevelChanged(int level);
    
private:
    int m_zoomLevel;
    bool m_panning;
    QPoint m_panStartPos;
};

六、实战案例:流程图编辑器

6.1 完整流程图编辑器实现

#include <QMainWindow>
#include <QToolBar>
#include <QStatusBar>
#include <QActionGroup>
#include <QComboBox>

class FlowchartEditor : public QMainWindow {
    Q_OBJECT
    
public:
    FlowchartEditor() {
        setupUI();
        setupConnections();
        
        setWindowTitle("Flowchart Editor");
        resize(1200, 800);
    }
    
private:
    void setupUI() {
        // 创建场景和视图
        m_scene = new GraphicsScene(this);
        m_view = new CustomGraphicsView;
        m_view->setScene(m_scene);
        
        setCentralWidget(m_view);
        
        // 创建工具栏
        QToolBar *mainToolBar = addToolBar("Main");
        
        // 工具选择
        QActionGroup *toolGroup = new QActionGroup(this);
        
        QAction *selectTool = mainToolBar->addAction("Select");
        selectTool->setCheckable(true);
        selectTool->setChecked(true);
        selectTool->setIcon(QIcon::fromTheme("edit-select"));
        toolGroup->addAction(selectTool);
        
        QAction *nodeTool = mainToolBar->addAction("Add Node");
        nodeTool->setCheckable(true);
        nodeTool->setIcon(QIcon::fromTheme("insert-object"));
        toolGroup->addAction(nodeTool);
        
        QAction *connectionTool = mainToolBar->addAction("Connect");
        connectionTool->setCheckable(true);
        connectionTool->setIcon(QIcon::fromTheme("draw-connector"));
        toolGroup->addAction(connectionTool);
        
        mainToolBar->addSeparator();
        
        // 缩放控制
        QComboBox *zoomCombo = new QComboBox;
        zoomCombo->addItems({"25%", "50%", "75%", "100%", "150%", "200%", "400%"});
        zoomCombo->setCurrentIndex(3);  // 100%
        mainToolBar->addWidget(zoomCombo);
        
        mainToolBar->addSeparator();
        
        // 对齐工具
        mainToolBar->addAction("Align Left");
        mainToolBar->addAction("Align Center");
        mainToolBar->addAction("Align Right");
        mainToolBar->addAction("Distribute");
        
        // 创建菜单
        setupMenus();
        
        // 状态栏
        m_statusBar = statusBar();
        m_statusBar->showMessage("Ready");
    }
    
    void setupMenus() {
        // 文件菜单
        QMenu *fileMenu = menuBar()->addMenu("File");
        fileMenu->addAction("New", this, &FlowchartEditor::newDocument, 
                           QKeySequence::New);
        fileMenu->addAction("Open", this, &FlowchartEditor::openDocument,
                           QKeySequence::Open);
        fileMenu->addAction("Save", this, &FlowchartEditor::saveDocument,
                           QKeySequence::Save);
        fileMenu->addAction("Save As", this, &FlowchartEditor::saveAsDocument,
                           QKeySequence::SaveAs);
        fileMenu->addSeparator();
        fileMenu->addAction("Export PNG", this, &FlowchartEditor::exportPNG);
        fileMenu->addAction("Export PDF", this, &FlowchartEditor::exportPDF);
        fileMenu->addSeparator();
        fileMenu->addAction("Exit", this, &QMainWindow::close,
                           QKeySequence::Quit);
        
        // 编辑菜单
        QMenu *editMenu = menuBar()->addMenu("Edit");
        editMenu->addAction("Undo", m_scene, &QGraphicsScene::undo,
                           QKeySequence::Undo);
        editMenu->addAction("Redo", m_scene, &QGraphicsScene::redo,
                           QKeySequence::Redo);
        editMenu->addSeparator();
        editMenu->addAction("Cut", this, &FlowchartEditor::cut,
                           QKeySequence::Cut);
        editMenu->addAction("Copy", this, &FlowchartEditor::copy,
                           QKeySequence::Copy);
        editMenu->addAction("Paste", this, &FlowchartEditor::paste,
                           QKeySequence::Paste);
        editMenu->addAction("Delete", this, &FlowchartEditor::deleteSelected,
                           QKeySequence::Delete);
        editMenu->addSeparator();
        editMenu->addAction("Select All", this, &FlowchartEditor::selectAll,
                           QKeySequence::SelectAll);
        
        // 视图菜单
        QMenu *viewMenu = menuBar()->addMenu("View");
        viewMenu->addAction("Zoom In", m_view, [this]() { m_view->scale(1.2, 1.2); },
                           QKeySequence::ZoomIn);
        viewMenu->addAction("Zoom Out", m_view, [this]() { m_view->scale(0.8, 0.8); },
                           QKeySequence::ZoomOut);
        viewMenu->addAction("Reset Zoom", m_view, [this]() { m_view->resetTransform(); });
        viewMenu->addAction("Fit in View", m_view, [this]() { 
            m_view->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatio); 
        }, QKeySequence(Qt::Key_F));
        viewMenu->addSeparator();
        QAction *showGrid = viewMenu->addAction("Show Grid");
        showGrid->setCheckable(true);
        showGrid->setChecked(true);
        
        // 节点菜单
        QMenu *nodeMenu = menuBar()->addMenu("Node");
        nodeMenu->addAction("Start", this, [this]() { addNode("Start"); });
        nodeMenu->addAction("Process", this, [this]() { addNode("Process"); });
        nodeMenu->addAction("Decision", this, [this]() { addNode("Decision"); });
        nodeMenu->addAction("Input/Output", this, [this]() { addNode("I/O"); });
        nodeMenu->addAction("End", this, [this]() { addNode("End"); });
    }
    
    void setupConnections() {
        // 连接场景信号
        connect(m_scene, &QGraphicsScene::selectionChanged,
                this, &FlowchartEditor::updateSelectionStatus);
        
        connect(m_scene, &QGraphicsScene::changed,
                this, &FlowchartEditor::updateDocumentStatus);
    }
    
private slots:
    void newDocument() {
        m_scene->clear();
        m_currentFile.clear();
        setWindowTitle("Flowchart Editor - Untitled");
        m_statusBar->showMessage("New document created", 3000);
    }
    
    void openDocument() {
        // 实现文件打开逻辑
    }
    
    void saveDocument() {
        if (m_currentFile.isEmpty()) {
            saveAsDocument();
        } else {
            // 实现保存逻辑
        }
    }
    
    void saveAsDocument() {
        // 实现另存为逻辑
    }
    
    void exportPNG() {
        QString fileName = QFileDialog::getSaveFileName(this, "Export PNG",
                                                       "", "PNG Files (*.png)");
        if (!fileName.isEmpty()) {
            QRectF rect = m_scene->itemsBoundingRect();
            QImage image(rect.size().toSize(), QImage::Format_ARGB32);
            image.fill(Qt::white);
            
            QPainter painter(&image);
            m_scene->render(&painter, QRectF(), rect);
            painter.end();
            
            if (image.save(fileName, "PNG")) {
                m_statusBar->showMessage("Exported to PNG", 3000);
            }
        }
    }
    
    void exportPDF() {
        QString fileName = QFileDialog::getSaveFileName(this, "Export PDF",
                                                       "", "PDF Files (*.pdf)");
        if (!fileName.isEmpty()) {
            QPrinter printer(QPrinter::HighResolution);
            printer.setOutputFormat(QPrinter::PdfFormat);
            printer.setOutputFileName(fileName);
            
            QPainter painter;
            if (painter.begin(&printer)) {
                m_scene->render(&painter);
                painter.end();
                m_statusBar->showMessage("Exported to PDF", 3000);
            }
        }
    }
    
    void cut() {
        // 实现剪切逻辑
    }
    
    void copy() {
        // 实现复制逻辑
    }
    
    void paste() {
        // 实现粘贴逻辑
    }
    
    void deleteSelected() {
        QList<QGraphicsItem*> items = m_scene->selectedItems();
        for (QGraphicsItem *item : items) {
            m_scene->removeItem(item);
            delete item;
        }
    }
    
    void selectAll() {
        for (QGraphicsItem *item : m_scene->items()) {
            item->setSelected(true);
        }
    }
    
    void addNode(const QString &type) {
        NodeItem *node = new NodeItem(type);
        node->setPos(m_view->mapToScene(m_view->viewport()->rect().center()));
        m_scene->addItem(node);
    }
    
    void updateSelectionStatus() {
        int count = m_scene->selectedItems().size();
        if (count == 0) {
            m_statusBar->showMessage("No items selected");
        } else {
            m_statusBar->showMessage(QString("%1 item(s) selected").arg(count));
        }
    }
    
    void updateDocumentStatus() {
        // 更新文档修改状态
        if (!windowTitle().startsWith("*")) {
            setWindowTitle("*" + windowTitle());
        }
    }
    
private:
    GraphicsScene *m_scene;
    CustomGraphicsView *m_view;
    QStatusBar *m_statusBar;
    QString m_currentFile;
};

// 主函数
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    // 设置应用程序样式
    app.setStyle("Fusion");
    
    // 创建调色板
    QPalette palette;
    palette.setColor(QPalette::Window, QColor(53, 53, 53));
    palette.setColor(QPalette::WindowText, Qt::white);
    palette.setColor(QPalette::Base, QColor(25, 25, 25));
    palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
    palette.setColor(QPalette::ToolTipBase, Qt::white);
    palette.setColor(QPalette::ToolTipText, Qt::white);
    palette.setColor(QPalette::Text, Qt::white);
    palette.setColor(QPalette::Button, QColor(53, 53, 53));
    palette.setColor(QPalette::ButtonText, Qt::white);
    palette.setColor(QPalette::BrightText, Qt::red);
    palette.setColor(QPalette::Link, QColor(42, 130, 218));
    palette.setColor(QPalette::Highlight, QColor(42, 130, 218));
    palette.setColor(QPalette::HighlightedText, Qt::black);
    app.setPalette(palette);
    
    FlowchartEditor editor;
    editor.show();
    
    return app.exec();
}

 

posted @ 2022-09-19 15:29  [BORUTO]  阅读(423)  评论(0)    收藏  举报