本示例演示如何使用 Graphics View 框架创建流程图。
可以添加流程图形状和文本,并通过箭头连接形状,如上图所示。形状、箭头和文本可以被赋予不同的颜色,并且可以更改文本的字体、样式和下划线。
Qt Graphics View 框架旨在管理和显示自定义的2D图形项。框架的主要类是 QGraphicsItem, QGraphicsScene 和 QGraphicsView。QGraphicsScene 管理 QGraphicsItem 并为它们提供一个平面。QGraphicsView是一个widget,用于在屏幕上渲染场景。
该框架包括一个事件传播体系结构,允许对场景中的项目进行精确的双精度交互。项目可以处理关键事件、鼠标按下、移动、释放和双击事件,还可以跟踪鼠标移动。
Graphics View使用BSP(Binary Space Partitioning:二叉空间分割)树,它可以实时可视化大型场景,即使有数百万个项。
在本例中,我们将演示如何通过实现继承QGraphicScene和QGraphicsItem的类来创建此类自定义图形场景和项。
#include "mainwindow.h"
#include
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(diagramscene);
QApplication a(argc, argv);
MainWindow w;
w.setGeometry(100, 100, 800, 500);
w.show();
return a.exec();
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
class DiagramScene;
QT_BEGIN_NAMESPACE
class QAction;
class QToolBox;
class QSpinBox;
class QComboBox;
class QFontComboBox;
class QButtonGroup;
class QLineEdit;
class QGraphicsTextItem;
class QFont;
class QToolButton;
class QAbstractButton;
class QGraphicsView;
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void backgroundButtonGroupClicked(QAbstractButton *button);
void buttonGroupClicked(int id);
private:
void createToolBox();
void createActions();
void createMenus();
void createToolbars();
QMenu *createColorMenu(const char *slot, QColor defaultColor);
QIcon createColorToolButtonIcon(const QString &image, QColor color);
QIcon createColorIcon(QColor color);
void about();
QWidget *createBackgroundCellWidget(const QString &text,
const QString &image);
DiagramScene *scene;
QGraphicsView *view;
QAction *exitAction;
QAction *addAction;
QAction *deleteAction;
QAction *toFrontAction;
QAction *sendBackAction;
QAction *aboutAction;
QMenu *fileMenu;
QMenu *itemMenu;
QMenu *aboutMenu;
QToolBar *textToolBar;
QToolBar *editToolBar;
QToolBar *colorToolBar;
QToolBar *pointerToolbar;
QComboBox *sceneScaleCombo;
QComboBox *itemColorCombo;
QComboBox *textColorCombo;
QComboBox *fontSizeCombo;
QFontComboBox *fontCombo;
QToolBox *toolBox;
QButtonGroup *buttonGroup;
QButtonGroup *pointerTypeGroup;
QButtonGroup *backgroundButtonGroup;
QToolButton *fontColorToolButton;
QToolButton *fillColorToolButton;
QToolButton *lineColorToolButton;
QAction *boldAction;
QAction *underlineAction;
QAction *italicAction;
QAction *textAction;
QAction *fillAction;
QAction *lineAction;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include
const int InsertTextButton = 10;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
createActions();
createToolBox();
createMenus();
// scene = new DiagramScene(itemMenu, this);
// scene->setSceneRect(QRectF(0, 0, 5000, 5000));
// connect(scene, &DiagramScene::itemInserted,
// this, &MainWindow::itemInserted);
// connect(scene, &DiagramScene::textInserted,
// this, &MainWindow::textInserted);
// connect(scene, &DiagramScene::itemSelected,
// this, &MainWindow::itemSelected);
createToolbars();
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(toolBox);
// view = new QGraphicsView(scene);
//layout->addWidget(view);
QWidget *widget = new QWidget;
widget->setLayout(layout);
setCentralWidget(widget);
setWindowTitle(tr("Diagramscene"));
setUnifiedTitleAndToolBarOnMac(true);
}
MainWindow::~MainWindow()
{
}
void MainWindow::createToolBox()
{
buttonGroup = new QButtonGroup(this);
buttonGroup->setExclusive(false);
connect(buttonGroup, QOverload<int>::of(&QButtonGroup::buttonClicked),
this, &MainWindow::buttonGroupClicked);
QGridLayout *layout = new QGridLayout;
// layout->addWidget(createCellWidget(tr("Conditional"), DiagramItem::Conditional), 0, 0);
// layout->addWidget(createCellWidget(tr("Process"), DiagramItem::Step),0, 1);
// layout->addWidget(createCellWidget(tr("Input/Output"), DiagramItem::Io), 1, 0);
QToolButton *textButton = new QToolButton;
textButton->setCheckable(true);
//InsertTextButton是一个ID,我们前面定义为10
buttonGroup->addButton(textButton, InsertTextButton);
textButton->setIcon(QIcon(QPixmap(":/images/textpointer.png")));
textButton->setIconSize(QSize(50, 50));
QGridLayout *textLayout = new QGridLayout;
textLayout->addWidget(textButton, 0, 0, Qt::AlignHCenter);
// textButton->setText("Text");
textLayout->addWidget(new QLabel(tr("Text")), 1, 0, Qt::AlignCenter);
QWidget *textWidget = new QWidget;
textWidget->setLayout(textLayout);
layout->addWidget(textWidget, 1, 1);
//使得第三行占10个位置,使得图像:A和文字:text能挨在一起
layout->setRowStretch(3, 10);
layout->setColumnStretch(2, 10);
QWidget *itemWidget = new QWidget;
itemWidget->setLayout(layout);
backgroundButtonGroup = new QButtonGroup(this);
connect(backgroundButtonGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked),
this, &MainWindow::backgroundButtonGroupClicked);
QGridLayout *backgroundLayout = new QGridLayout;
backgroundLayout->addWidget(createBackgroundCellWidget(tr("Blue Grid"),
":/images/background1.png"), 0, 0);
backgroundLayout->addWidget(createBackgroundCellWidget(tr("White Grid"),
":/images/background2.png"), 0, 1);
backgroundLayout->addWidget(createBackgroundCellWidget(tr("Gray Grid"),
":/images/background3.png"), 1, 0);
backgroundLayout->addWidget(createBackgroundCellWidget(tr("No Grid"),
":/images/background4.png"), 1, 1);
backgroundLayout->setRowStretch(2, 10);
backgroundLayout->setColumnStretch(2, 10);
QWidget *backgroundWidget = new QWidget;
backgroundWidget->setLayout(backgroundLayout);
toolBox = new QToolBox;
toolBox->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored));
toolBox->setMinimumWidth(itemWidget->sizeHint().width());
toolBox->addItem(itemWidget, tr("Basic Flowchart Shapes"));
toolBox->addItem(backgroundWidget, tr("Backgrounds"));
}
void MainWindow::createActions()
{
toFrontAction = new QAction(QIcon(":/images/bringtofront.png"),
tr("Bring to &Front"), this);
toFrontAction->setShortcut(tr("Ctrl+F"));
toFrontAction->setStatusTip(tr("Bring item to front"));
// connect(toFrontAction, &QAction::triggered, this, &MainWindow::bringToFront);
sendBackAction = new QAction(QIcon(":/images/sendtoback.png"), tr("Send to &Back"), this);
sendBackAction->setShortcut(tr("Ctrl+T"));
sendBackAction->setStatusTip(tr("Send item to back"));
// connect(sendBackAction, &QAction::triggered, this, &MainWindow::sendToBack);
deleteAction = new QAction(QIcon(":/images/delete.png"), tr("&Delete"), this);
deleteAction->setShortcut(tr("Delete"));
deleteAction->setStatusTip(tr("Delete item from diagram"));
// connect(deleteAction, &QAction::triggered, this, &MainWindow::deleteItem);
exitAction = new QAction(tr("E&xit"), this);
exitAction->setShortcuts(QKeySequence::Quit);
exitAction->setStatusTip(tr("Quit Scenediagram example"));
connect(exitAction, &QAction::triggered, this, &QWidget::close);
boldAction = new QAction(tr("Bold"), this);
boldAction->setCheckable(true);
QPixmap pixmap(":/images/bold.png");
boldAction->setIcon(QIcon(pixmap));
boldAction->setShortcut(tr("Ctrl+B"));
// connect(boldAction, &QAction::triggered, this, &MainWindow::handleFontChange);
italicAction = new QAction(QIcon(":/images/italic.png"), tr("Italic"), this);
italicAction->setCheckable(true);
italicAction->setShortcut(tr("Ctrl+I"));
// connect(italicAction, &QAction::triggered, this, &MainWindow::handleFontChange);
underlineAction = new QAction(QIcon(":/images/underline.png"), tr("Underline"), this);
underlineAction->setCheckable(true);
underlineAction->setShortcut(tr("Ctrl+U"));
// connect(underlineAction, &QAction::triggered, this, &MainWindow::handleFontChange);
aboutAction = new QAction(tr("A&bout"), this);
aboutAction->setShortcut(tr("F1"));
connect(aboutAction, &QAction::triggered, this, &MainWindow::about);
}
void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(exitAction);
itemMenu = menuBar()->addMenu(tr("&Item"));
itemMenu->addAction(deleteAction);
itemMenu->addSeparator();
itemMenu->addAction(toFrontAction);
itemMenu->addAction(sendBackAction);
aboutMenu = menuBar()->addMenu(tr("&Help"));
aboutMenu->addAction(aboutAction);
}
void MainWindow::createToolbars()
{
editToolBar = addToolBar(tr("Edit"));
editToolBar->addAction(deleteAction);
editToolBar->addAction(toFrontAction);
editToolBar->addAction(sendBackAction);
fontCombo = new QFontComboBox();
// connect(fontCombo, &QFontComboBox::currentFontChanged,
// this, &MainWindow::currentFontChanged);
fontSizeCombo = new QComboBox;
fontSizeCombo->setEditable(true);
for (int i = 8; i < 30; i = i + 2)
fontSizeCombo->addItem(QString().setNum(i));
QIntValidator *validator = new QIntValidator(2, 64, this);
//验证范围
fontSizeCombo->setValidator(validator);
// connect(fontSizeCombo, &QComboBox::currentTextChanged,
// this, &MainWindow::fontSizeChanged);
//QToolButton是一种特殊的按钮,它提供对特定命令或选项的快速访问。与普通的命令按钮不同,工具按钮通常不显示文本标签,而是显示图标。
fontColorToolButton = new QToolButton;
fontColorToolButton->setPopupMode(QToolButton::MenuButtonPopup);
fontColorToolButton->setMenu(createColorMenu(SLOT(textColorChanged()), Qt::black));
textAction = fontColorToolButton->menu()->defaultAction();
fontColorToolButton->setIcon(createColorToolButtonIcon(":/images/textpointer.png", Qt::black));
fontColorToolButton->setAutoFillBackground(true);
// connect(fontColorToolButton, &QAbstractButton::clicked,
// this, &MainWindow::textButtonTriggered);
fillColorToolButton = new QToolButton;
fillColorToolButton->setPopupMode(QToolButton::MenuButtonPopup);
fillColorToolButton->setMenu(createColorMenu(SLOT(itemColorChanged()), Qt::white));
fillAction = fillColorToolButton->menu()->defaultAction();
fillColorToolButton->setIcon(createColorToolButtonIcon(
":/images/floodfill.png", Qt::white));
// connect(fillColorToolButton, &QAbstractButton::clicked,
// this, &MainWindow::fillButtonTriggered);
lineColorToolButton = new QToolButton;
lineColorToolButton->setPopupMode(QToolButton::MenuButtonPopup);
lineColorToolButton->setMenu(createColorMenu(SLOT(lineColorChanged()), Qt::black));
lineAction = lineColorToolButton->menu()->defaultAction();
lineColorToolButton->setIcon(createColorToolButtonIcon(
":/images/linecolor.png", Qt::black));
// connect(lineColorToolButton, &QAbstractButton::clicked,
// this, &MainWindow::lineButtonTriggered);
textToolBar = addToolBar(tr("Font"));
textToolBar->addWidget(fontCombo);
textToolBar->addWidget(fontSizeCombo);
textToolBar->addAction(boldAction);
textToolBar->addAction(italicAction);
textToolBar->addAction(underlineAction);
colorToolBar = addToolBar(tr("Color"));
colorToolBar->addWidget(fontColorToolButton);
colorToolBar->addWidget(fillColorToolButton);
colorToolBar->addWidget(lineColorToolButton);
QToolButton *pointerButton = new QToolButton;
pointerButton->setCheckable(true);
pointerButton->setChecked(true);
pointerButton->setIcon(QIcon(":/images/pointer.png"));
QToolButton *linePointerButton = new QToolButton;
linePointerButton->setCheckable(true);
linePointerButton->setIcon(QIcon(":/images/linepointer.png"));
pointerTypeGroup = new QButtonGroup(this);
// pointerTypeGroup->addButton(pointerButton, int(DiagramScene::MoveItem));
// pointerTypeGroup->addButton(linePointerButton, int(DiagramScene::InsertLine));
// connect(pointerTypeGroup, QOverload::of(&QButtonGroup::buttonClicked),
// this, &MainWindow::pointerGroupClicked);
sceneScaleCombo = new QComboBox;
QStringList scales;
scales << tr("50%") << tr("75%") << tr("100%") << tr("125%") << tr("150%");
sceneScaleCombo->addItems(scales);
sceneScaleCombo->setCurrentIndex(2);
// connect(sceneScaleCombo, &QComboBox::currentTextChanged,
// this, &MainWindow::sceneScaleChanged);
pointerToolbar = addToolBar(tr("Pointer type"));
pointerToolbar->addWidget(pointerButton);
pointerToolbar->addWidget(linePointerButton);
pointerToolbar->addWidget(sceneScaleCombo);
}
QIcon MainWindow::createColorToolButtonIcon(const QString &imageFile, QColor color)
{
QPixmap pixmap(50, 80);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
QPixmap image(imageFile);
//!start:在按钮上方水平居中绘制图标,下端再画一个颜色框。
//按钮上的区域
QRect target(4, 0, 42, 43);
//图片的区域
QRect source(0, 0, 42, 43);
//QRect(left, top, width, height)
//这里height写的80,实际上大于等于20效果都一样
painter.fillRect(QRect(0, 60, 50, 80), color);
//将源的给定像素的矩形部分,绘制到绘制设备中的给定目标区域。
painter.drawPixmap(target, image, source);
//!start:在按钮上方水平居中绘制图标,下端再画一个颜色框。
return QIcon(pixmap);
}
QMenu *MainWindow::createColorMenu(const char *slot, QColor defaultColor)
{
QList<QColor> colors;
colors << Qt::black << Qt::white << Qt::red << Qt::blue << Qt::yellow;
QStringList names;
names << tr("black") << tr("white") << tr("red") << tr("blue")
<< tr("yellow");
QMenu *colorMenu = new QMenu(this);
for (int i = 0; i < colors.count(); ++i) {
QAction *action = new QAction(names.at(i), this);
action->setData(colors.at(i));
action->setIcon(createColorIcon(colors.at(i)));
connect(action, SIGNAL(triggered()), this, slot);
colorMenu->addAction(action);
if (colors.at(i) == defaultColor)
colorMenu->setDefaultAction(action);
}
return colorMenu;
}
QIcon MainWindow::createColorIcon(QColor color)
{
QPixmap pixmap(20, 20);
QPainter painter(&pixmap);
painter.setPen(Qt::NoPen);
painter.fillRect(QRect(0, 0, 20, 20), color);
return QIcon(pixmap);
}
void MainWindow::about()
{
QMessageBox::about(this, tr("About Diagram Scene"),
tr("The Diagram Scene example shows "
"use of the graphics framework."));
}
void MainWindow::buttonGroupClicked(int id)
{
const QList<QAbstractButton *> buttons = buttonGroup->buttons();
for (QAbstractButton *button : buttons) {
if (buttonGroup->button(id) != button)
button->setChecked(false);
}
// if (id == InsertTextButton) {
// scene->setMode(DiagramScene::InsertText);
// } else {
// scene->setItemType(DiagramItem::DiagramType(id));
// scene->setMode(DiagramScene::InsertItem);
// }
}
void MainWindow::backgroundButtonGroupClicked(QAbstractButton *button)
{
const QList<QAbstractButton *> buttons = backgroundButtonGroup->buttons();
for (QAbstractButton *myButton : buttons) {
if (myButton != button)
button->setChecked(false);
}
QString text = button->text();
// if (text == tr("Blue Grid"))
// scene->setBackgroundBrush(QPixmap(":/images/background1.png"));
// else if (text == tr("White Grid"))
// scene->setBackgroundBrush(QPixmap(":/images/background2.png"));
// else if (text == tr("Gray Grid"))
// scene->setBackgroundBrush(QPixmap(":/images/background3.png"));
// else
// scene->setBackgroundBrush(QPixmap(":/images/background4.png"));
// scene->update();
// view->update();
}
QWidget *MainWindow::createBackgroundCellWidget(const QString &text, const QString &image)
{
QToolButton *button = new QToolButton;
//下面这行可以不需要
button->setText(text);
button->setIcon(QIcon(image));
button->setIconSize(QSize(50, 50));
button->setCheckable(true);
backgroundButtonGroup->addButton(button);
QGridLayout *layout = new QGridLayout;
layout->addWidget(button, 0, 0, Qt::AlignHCenter);
layout->addWidget(new QLabel(text), 1, 0, Qt::AlignCenter);
QWidget *widget = new QWidget;
widget->setLayout(layout);
return widget;
}
通过参数指定图片和颜色,绘制出新的pixmap,当做Icon。这样每次切换颜色的时候,按钮的图标的颜色部分也会跟着变化。
ToolButton可以设置一个菜单
主体部分左边为一个ToolBox(包含两个widget,每个widget又包含一个ButtonGroup),右边为Scene。
#ifndef DIAGRAMITEM_H
#define DIAGRAMITEM_H
#include
#include
QT_BEGIN_NAMESPACE
class QPixmap;
class QGraphicsSceneContextMenuEvent;
class QMenu;
class QPolygonF;
QT_END_NAMESPACE
class Arrow;
//QGraphicsPolygonItem类提供了多边形项,可以将其添加到QGraphicScene中
class DiagramItem : public QGraphicsPolygonItem
{
public:
enum {
Type = UserType + 15 };
enum DiagramType {
Step, Conditional, StartEnd, Io };
DiagramItem(DiagramType diagramType, QMenu *contextMenu, QGraphicsItem *parent = nullptr);
void removeArrow(Arrow *arrow);
void removeArrows();
DiagramType diagramType() const {
return myDiagramType; }
QPolygonF polygon() const {
return myPolygon; }
void addArrow(Arrow *arrow);
QPixmap image() const;
int type() const override {
return Type; }
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
private:
//对于一个Item来说,我们需要知道它的形状,各个点的值,右键菜单,连接情况
//图形类型,只是我们前面自己定义的枚举量
DiagramType myDiagramType;
//点的数据集合QVector
QPolygonF myPolygon;
//右键菜单
QMenu *myContextMenu;
//Item与其他ITem的连接关系
QVector<Arrow *> arrows;
};
#endif // DIAGRAMITEM_H
从类的成员变量可以很容易的分析出来,DiagramItem就是一个基本图元,需要存储,控制Item的形状类型、点数据、右键菜单、连接箭头。
#include "diagramitem.h"
#include "arrow.h"
#include
#include
#include
#include
DiagramItem::DiagramItem(DiagramType diagramType, QMenu *contextMenu,
QGraphicsItem *parent)
: QGraphicsPolygonItem(parent), myDiagramType(diagramType)
, myContextMenu(contextMenu)
{
QPainterPath path;
//根据形状类型,赋予点数据
switch (myDiagramType) {
case StartEnd:
//moveTo(第一个点位置)
//arcTo(矩形原点x,矩形原点y,长,宽,start angle,sweep)
path.moveTo(90, 0);
path.arcTo(40, -60, 50, 50, 0, 90);//从0度,扫过90度(0-90°)
path.arcTo(-80, -60, 50, 50, 90, 90);//从90度,扫过90度(90-180°)
path.arcTo(-80, 0, 50, 50, 180, 90);//从180度,扫过90度(180-270°)
path.arcTo(40, 0, 50, 50, 270, 90);//从270度,扫过90度(270-360°)
// path.lineTo(90, 0);
myPolygon = path.toFillPolygon();
break;
case Conditional:
myPolygon << QPointF(-100, 0) << QPointF(0, 100)
<< QPointF(100, 0) << QPointF(0, -100)
<< QPointF(-100, 0);
break;
case Step:
myPolygon << QPointF(-100, -100) << QPointF(100, -100)
<< QPointF(100, 100) << QPointF(-100, 100)
<< QPointF(-100, -100);
break;
default:
myPolygon << QPointF(-120, -80) << QPointF(-70, 80)
<< QPointF(120, 80) << QPointF(70, -80)
<< QPointF(-120, -80);
break;
}
setPolygon(myPolygon);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
//发生位移,缩放,选择变化
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
}
void DiagramItem::removeArrow(Arrow *arrow)
{
arrows.removeAll(arrow);
}
void DiagramItem::removeArrows()
{
//arrows会在for循环内变化,所以需要拷贝出来使用
//加上const是一个好的习惯
const auto arrowsCopy = arrows;
for (Arrow *arrow : arrowsCopy) {
//arrow包括两个Item,startItem和endItem,分别对应箭头的两端
arrow->startItem()->removeArrow(arrow);
arrow->endItem()->removeArrow(arrow);
//返回当前Item所在的scene
scene()->removeItem(arrow);
delete arrow;
}
}
void DiagramItem::addArrow(Arrow *arrow)
{
arrows.append(arrow);
}
QPixmap DiagramItem::image() const
{
QPixmap pixmap(250, 250);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setPen(QPen(Qt::black, 8));
painter.translate(125, 125);
painter.drawPolyline(myPolygon);
return pixmap;
}
void DiagramItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
scene()->clearSelection();
setSelected(true);
myContextMenu->exec(event->screenPos());
}
QVariant DiagramItem::itemChange(GraphicsItemChange change,
const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange) {
for (Arrow *arrow : qAsConst(arrows))
arrow->updatePosition();
}
return value;
}
#ifndef ARROW_H
#define ARROW_H
#include
class DiagramItem;
//QGraphicsLineItem为基类
class Arrow : public QGraphicsLineItem
{
public:
enum {
Type = UserType + 4 };
Arrow(DiagramItem *startItem, DiagramItem *endItem,
QGraphicsItem *parent = nullptr);
int type() const override {
return Type; }
QRectF boundingRect() const override;
QPainterPath shape() const override;
void setColor(const QColor &color) {
myColor = color; }
DiagramItem *startItem() const {
return myStartItem; }
DiagramItem *endItem() const {
return myEndItem; }
void updatePosition();
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget = nullptr) override;
private:
DiagramItem *myStartItem;
DiagramItem *myEndItem;
QPolygonF arrowHead;
QColor myColor = Qt::black;
};
#endif // ARROW_H
#include "arrow.h"
#include "diagramitem.h"
#include
#include
#include
Arrow::Arrow(DiagramItem *startItem, DiagramItem *endItem, QGraphicsItem *parent)
: QGraphicsLineItem(parent), myStartItem(startItem), myEndItem(endItem)
{
setFlag(QGraphicsItem::ItemIsSelectable, true);
setPen(QPen(myColor, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
}
QRectF Arrow::boundingRect() const
{
qreal extra = (pen().width() + 20) / 2.0;
return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(),
line().p2().y() - line().p1().y()))
.normalized()//解决小于0的情况
.adjusted(-extra, -extra, extra, extra);
}
QPainterPath Arrow::shape() const
{
QPainterPath path = QGraphicsLineItem::shape();
path.addPolygon(arrowHead);
return path;
}
void Arrow::updatePosition()
{
QLineF line(mapFromItem(myStartItem, 50, 50), mapFromItem(myEndItem, 0, 0));
setLine(line);
}
void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *)
{
//连接的两个item是否碰在一起
if (myStartItem->collidesWithItem(myEndItem))
return;
QPen myPen = pen();
myPen.setColor(myColor);
qreal arrowSize = 20;
painter->setPen(myPen);
painter->setBrush(myColor);
QLineF centerLine(myStartItem->pos(), myEndItem->pos());
QPolygonF endPolygon = myEndItem->polygon();
//第一个点再scene中的坐标
QPointF p1 = endPolygon.first() + myEndItem->pos();
QPointF intersectPoint;
for (int i = 1; i < endPolygon.count(); ++i) {
//第二个点再scene中的坐标
QPointF p2 = endPolygon.at(i) + myEndItem->pos();
QLineF polyLine = QLineF(p1, p2);
QLineF::IntersectionType intersectionType =
polyLine.intersects(centerLine, &intersectPoint);
if (intersectionType == QLineF::BoundedIntersection)
break;//找到了交叉的边,退出。intersectPoint保存交叉点坐标
//检查下一条边
p1 = p2;
}
//从endItem的交叉点划线,往startItem的中心划线。
//startItem中心到与startItem交叉点之间部分会被startItem挡住。
//setLine与line配合使用
setLine(QLineF(intersectPoint, myStartItem->pos()));
double angle = std::atan2(-line().dy(), line().dx());
//箭头的两条边的顶点
//line().p1()等于intersectPoint
QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize,
cos(angle + M_PI / 3) * arrowSize);
QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,
cos(angle + M_PI - M_PI / 3) * arrowSize);
arrowHead.clear();
arrowHead << line().p1() << arrowP1 << arrowP2;
painter->drawLine(line());
painter->drawPolygon(arrowHead);
//选中状态,在线的两边画虚线
if (isSelected()) {
painter->setPen(QPen(myColor, 1, Qt::DashLine));
QLineF myLine = line();
myLine.translate(0, 4.0);
painter->drawLine(myLine);
myLine.translate(0,-8.0);
painter->drawLine(myLine);
}
}
#ifndef DIAGRAMTEXTITEM_H
#define DIAGRAMTEXTITEM_H
#include
QT_BEGIN_NAMESPACE
class QGraphicsSceneMouseEvent;
QT_END_NAMESPACE
class DiagramTextItem : public QGraphicsTextItem
{
Q_OBJECT
public:
//DiagramTextItem::Type是所有DiagramTextItem类型的公共Type
enum {
Type = UserType + 3 };
DiagramTextItem(QGraphicsItem *parent = nullptr);
int type() const override {
return Type; }
signals:
void lostFocus(DiagramTextItem *item);
void selectedChange(QGraphicsItem *item);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
void focusOutEvent(QFocusEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif // DIAGRAMTEXTITEM_H
#include "diagramtextitem.h"
#include "diagramscene.h"
DiagramTextItem::DiagramTextItem(QGraphicsItem *parent)
: QGraphicsTextItem(parent)
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
}
QVariant DiagramTextItem::itemChange(GraphicsItemChange change,
const QVariant &value)
{
if (change == QGraphicsItem::ItemSelectedHasChanged)
emit selectedChange(this);
return value;
}
void DiagramTextItem::focusOutEvent(QFocusEvent *event)
{
setTextInteractionFlags(Qt::NoTextInteraction);
emit lostFocus(this);
QGraphicsTextItem::focusOutEvent(event);
}
void DiagramTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if (textInteractionFlags() == Qt::NoTextInteraction)
setTextInteractionFlags(Qt::TextEditorInteraction);
QGraphicsTextItem::mouseDoubleClickEvent(event);
}
#ifndef DIAGRAMSCENE_H
#define DIAGRAMSCENE_H
#include "diagramitem.h"
#include "diagramtextitem.h"
#include
QT_BEGIN_NAMESPACE
class QGraphicsSceneMouseEvent;
class QMenu;
class QPointF;
class QGraphicsLineItem;
class QFont;
class QGraphicsTextItem;
class QColor;
QT_END_NAMESPACE
class DiagramScene : public QGraphicsScene
{
Q_OBJECT
public:
enum Mode {
InsertItem, InsertLine, InsertText, MoveItem };
explicit DiagramScene(QMenu *itemMenu, QObject *parent = nullptr);
QFont font() const {
return myFont; }
QColor textColor() const {
return myTextColor; }
QColor itemColor() const {
return myItemColor; }
QColor lineColor() const {
return myLineColor; }
void setLineColor(const QColor &color);
void setTextColor(const QColor &color);
void setItemColor(const QColor &color);
void setFont(const QFont &font);
public slots:
void setMode(Mode mode);
void setItemType(DiagramItem::DiagramType type);
void editorLostFocus(DiagramTextItem *item);
signals:
void itemInserted(DiagramItem *item);
void textInserted(QGraphicsTextItem *item);
void itemSelected(QGraphicsItem *item);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override;
private:
bool isItemChange(int type) const;
DiagramItem::DiagramType myItemType;
QMenu *myItemMenu;
Mode myMode;
bool leftButtonDown;
QPointF startPoint;
QGraphicsLineItem *line;
QFont myFont;
DiagramTextItem *textItem;
QColor myTextColor;
QColor myItemColor;
QColor myLineColor;
};
#endif // DIAGRAMSCENE_H
#include "diagramscene.h"
#include "arrow.h"
#include
#include
DiagramScene::DiagramScene(QMenu *itemMenu, QObject *parent)
: QGraphicsScene(parent)
{
myItemMenu = itemMenu;
myMode = MoveItem;
myItemType = DiagramItem::Step;
line = nullptr;
textItem = nullptr;
myItemColor = Qt::white;
myTextColor = Qt::black;
myLineColor = Qt::black;
}
void DiagramScene::setLineColor(const QColor &color)
{
myLineColor = color;
//isItemChange是我们自定义的函数,判断当前选择的items中,有没有指定的type类型的item
if (isItemChange(Arrow::Type)) {
Arrow *item = qgraphicsitem_cast<Arrow *>(selectedItems().first());
item->setColor(myLineColor);
update();
}
}
void DiagramScene::setTextColor(const QColor &color)
{
myTextColor = color;
if (isItemChange(DiagramTextItem::Type)) {
DiagramTextItem *item = qgraphicsitem_cast<DiagramTextItem *>(selectedItems().first());
item->setDefaultTextColor(myTextColor);
}
}
void DiagramScene::setItemColor(const QColor &color)
{
myItemColor = color;
if (isItemChange(DiagramItem::Type)) {
DiagramItem *item = qgraphicsitem_cast<DiagramItem *>(selectedItems().first());
item->setBrush(myItemColor);
}
}
void DiagramScene::setFont(const QFont &font)
{
myFont = font;
if (isItemChange(DiagramTextItem::Type)) {
QGraphicsTextItem *item = qgraphicsitem_cast<DiagramTextItem *>(selectedItems().first());
//此时,选择可以更改,因此第一个选择的项可能不是DiagramTextItem
if (item)
item->setFont(myFont);
}
}
void DiagramScene::setMode(Mode mode)
{
myMode = mode;
}
void DiagramScene::setItemType(DiagramItem::DiagramType type)
{
myItemType = type;
}
void DiagramScene::editorLostFocus(DiagramTextItem *item)
{
QTextCursor cursor = item->textCursor();
cursor.clearSelection();
item->setTextCursor(cursor);
if (item->toPlainText().isEmpty()) {
removeItem(item);
item->deleteLater();
}
}
//在scene中点击鼠标,通过模式选择需要创建的图元
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (mouseEvent->button() != Qt::LeftButton)
return;
DiagramItem *item;
switch (myMode) {
case InsertItem:
item = new DiagramItem(myItemType, myItemMenu);
item->setBrush(myItemColor);
addItem(item);
item->setPos(mouseEvent->scenePos());
emit itemInserted(item);
break;
case InsertLine:
line = new QGraphicsLineItem(QLineF(mouseEvent->scenePos(),
mouseEvent->scenePos()));
line->setPen(QPen(myLineColor, 2));
addItem(line);
break;
case InsertText:
textItem = new DiagramTextItem();
textItem->setFont(myFont);
textItem->setTextInteractionFlags(Qt::TextEditorInteraction);
textItem->setZValue(1000.0);
connect(textItem, &DiagramTextItem::lostFocus,
this, &DiagramScene::editorLostFocus);
connect(textItem, &DiagramTextItem::selectedChange,
this, &DiagramScene::itemSelected);
addItem(textItem);
textItem->setDefaultTextColor(myTextColor);
textItem->setPos(mouseEvent->scenePos());
emit textInserted(textItem);
default:
;
}
QGraphicsScene::mousePressEvent(mouseEvent);
}
void DiagramScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (myMode == InsertLine && line != nullptr) {
QLineF newLine(line->line().p1(), mouseEvent->scenePos());
line->setLine(newLine);
} else if (myMode == MoveItem) {
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
}
//只对箭头起作用
void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (line != nullptr && myMode == InsertLine) {
QList<QGraphicsItem *> startItems = items(line->line().p1());
//startItems里包含line本身,需要去掉。
//这里if判断看着有点多余,但在极端情况下有用。比如划线时间特别短,只画了一个点就release。
if (startItems.count() && startItems.first() == line)
startItems.removeFirst();
QList<QGraphicsItem *> endItems = items(line->line().p2());
if (endItems.count() && endItems.first() == line)
endItems.removeFirst();
removeItem(line);
delete line;
//如果startItem和endItem都存在,切都是图形。则可以开始画箭头线了。
if (startItems.count() > 0 && endItems.count() > 0 &&
startItems.first()->type() == DiagramItem::Type &&
endItems.first()->type() == DiagramItem::Type &&
startItems.first() != endItems.first()) {
DiagramItem *startItem = qgraphicsitem_cast<DiagramItem *>(startItems.first());
DiagramItem *endItem = qgraphicsitem_cast<DiagramItem *>(endItems.first());
Arrow *arrow = new Arrow(startItem, endItem);
arrow->setColor(myLineColor);
startItem->addArrow(arrow);
endItem->addArrow(arrow);
arrow->setZValue(-1000.0);
addItem(arrow);
//下面这条语句好像有点多余
arrow->updatePosition();
}
}
line = nullptr;
QGraphicsScene::mouseReleaseEvent(mouseEvent);
}
bool DiagramScene::isItemChange(int type) const
{
const QList<QGraphicsItem *> items = selectedItems();
//cb是一个lambda函数
const auto cb = [type](const QGraphicsItem *item) {
return item->type() == type; };
//吧items中的item,一个个放到cb函数中进行对比
return std::find_if(items.begin(), items.end(), cb) != items.end();
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "diagramitem.h"
#include
class DiagramScene;
QT_BEGIN_NAMESPACE
class QAction;
class QToolBox;
class QSpinBox;
class QComboBox;
class QFontComboBox;
class QButtonGroup;
class QLineEdit;
class QGraphicsTextItem;
class QFont;
class QToolButton;
class QAbstractButton;
class QGraphicsView;
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
private slots:
void backgroundButtonGroupClicked(QAbstractButton *button);
void buttonGroupClicked(int id);
void deleteItem();
void pointerGroupClicked(int id);
void bringToFront();
void sendToBack();
void itemInserted(DiagramItem *item);
void textInserted(QGraphicsTextItem *item);
void currentFontChanged(const QFont &font);
void fontSizeChanged(const QString &size);
void sceneScaleChanged(const QString &scale);
void textColorChanged();
void itemColorChanged();
void lineColorChanged();
void textButtonTriggered();
void fillButtonTriggered();
void lineButtonTriggered();
void handleFontChange();
void itemSelected(QGraphicsItem *item);
void about();
private:
void createToolBox();
void createActions();
void createMenus();
void createToolbars();
QMenu *createColorMenu(const char *slot, QColor defaultColor);
QIcon createColorToolButtonIcon(const QString &image, QColor color);
QIcon createColorIcon(QColor color);
QWidget *createCellWidget(const QString &text,
DiagramItem::DiagramType type);
QWidget *createBackgroundCellWidget(const QString &text,
const QString &image);
DiagramScene *scene;
QGraphicsView *view;
QAction *exitAction;
QAction *addAction;
QAction *deleteAction;
QAction *toFrontAction;
QAction *sendBackAction;
QAction *aboutAction;
QMenu *fileMenu;
QMenu *itemMenu;
QMenu *aboutMenu;
QToolBar *textToolBar;
QToolBar *editToolBar;
QToolBar *colorToolBar;
QToolBar *pointerToolbar;
QComboBox *sceneScaleCombo;
QComboBox *itemColorCombo;
QComboBox *textColorCombo;
QComboBox *fontSizeCombo;
QFontComboBox *fontCombo;
QToolBox *toolBox;
QButtonGroup *buttonGroup;
QButtonGroup *pointerTypeGroup;
QButtonGroup *backgroundButtonGroup;
QToolButton *fontColorToolButton;
QToolButton *fillColorToolButton;
QToolButton *lineColorToolButton;
QAction *boldAction;
QAction *underlineAction;
QAction *italicAction;
QAction *textAction;
QAction *fillAction;
QAction *lineAction;
};
#endif // MAINWINDOW_H
#include "arrow.h"
#include "diagramitem.h"
#include "diagramscene.h"
#include "diagramtextitem.h"
#include "mainwindow.h"
#include
const int InsertTextButton = 10;
MainWindow::MainWindow()
{
createActions();
createToolBox();
createMenus();
scene = new DiagramScene(itemMenu, this);
scene->setSceneRect(QRectF(0, 0, 5000, 5000));
connect(scene, &DiagramScene::itemInserted,
this, &MainWindow::itemInserted);
connect(scene, &DiagramScene::textInserted,
this, &MainWindow::textInserted);
connect(scene, &DiagramScene::itemSelected,
this, &MainWindow::itemSelected);
createToolbars();
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(toolBox);
view = new QGraphicsView(scene);
layout->addWidget(view);
QWidget *widget = new QWidget;
widget->setLayout(layout);
setCentralWidget(widget);
setWindowTitle(tr("Diagramscene"));
setUnifiedTitleAndToolBarOnMac(true);
}
void MainWindow::createToolBox()
{
buttonGroup = new QButtonGroup(this);
buttonGroup->setExclusive(false);
connect(buttonGroup, QOverload<int>::of(&QButtonGroup::buttonClicked),
this, &MainWindow::buttonGroupClicked);
QGridLayout *layout = new QGridLayout;
layout->addWidget(createCellWidget(tr("Conditional"), DiagramItem::Conditional), 0, 0);
layout->addWidget(createCellWidget(tr("Process"), DiagramItem::Step),0, 1);
layout->addWidget(createCellWidget(tr("Input/Output"), DiagramItem::Io), 1, 0);
QToolButton *textButton = new QToolButton;
textButton->setCheckable(true);
//InsertTextButton是一个ID,我们前面定义为10
buttonGroup->addButton(textButton, InsertTextButton);
textButton->setIcon(QIcon(QPixmap(":/images/textpointer.png")));
textButton->setIconSize(QSize(50, 50));
QGridLayout *textLayout = new QGridLayout;
textLayout->addWidget(textButton, 0, 0, Qt::AlignHCenter);
textButton->setText("Text");
textLayout->addWidget(new QLabel(tr("Text")), 1, 0, Qt::AlignCenter);
QWidget *textWidget = new QWidget;
textWidget->setLayout(textLayout);
layout->addWidget(textWidget, 1, 1);
//使得第三行占10个位置,使得图像:A和文字:text能挨在一起
layout->setRowStretch(3, 10);
layout->setColumnStretch(2, 10);
QWidget *itemWidget = new QWidget;
itemWidget->setLayout(layout);
backgroundButtonGroup = new QButtonGroup(this);
connect(backgroundButtonGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked),
this, &MainWindow::backgroundButtonGroupClicked);
QGridLayout *backgroundLayout = new QGridLayout;
backgroundLayout->addWidget(createBackgroundCellWidget(tr("Blue Grid"),
":/images/background1.png"), 0, 0);
backgroundLayout->addWidget(createBackgroundCellWidget(tr("White Grid"),
":/images/background2.png"), 0, 1);
backgroundLayout->addWidget(createBackgroundCellWidget(tr("Gray Grid"),
":/images/background3.png"), 1, 0);
backgroundLayout->addWidget(createBackgroundCellWidget(tr("No Grid"),
":/images/background4.png"), 1, 1);
backgroundLayout->setRowStretch(2, 10);
backgroundLayout->setColumnStretch(2, 10);
QWidget *backgroundWidget = new QWidget;
backgroundWidget->setLayout(backgroundLayout);
toolBox = new QToolBox;
toolBox->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored));
toolBox->setMinimumWidth(itemWidget->sizeHint().width());
toolBox->addItem(itemWidget, tr("Basic Flowchart Shapes"));
toolBox->addItem(backgroundWidget, tr("Backgrounds"));
}
void MainWindow::createActions()
{
toFrontAction = new QAction(QIcon(":/images/bringtofront.png"),
tr("Bring to &Front"), this);
toFrontAction->setShortcut(tr("Ctrl+F"));
toFrontAction->setStatusTip(tr("Bring item to front"));
connect(toFrontAction, &QAction::triggered, this, &MainWindow::bringToFront);
sendBackAction = new QAction(QIcon(":/images/sendtoback.png"), tr("Send to &Back"), this);
sendBackAction->setShortcut(tr("Ctrl+T"));
sendBackAction->setStatusTip(tr("Send item to back"));
connect(sendBackAction, &QAction::triggered, this, &MainWindow::sendToBack);
deleteAction = new QAction(QIcon(":/images/delete.png"), tr("&Delete"), this);
deleteAction->setShortcut(tr("Delete"));
deleteAction->setStatusTip(tr("Delete item from diagram"));
connect(deleteAction, &QAction::triggered, this, &MainWindow::deleteItem);
exitAction = new QAction(tr("E&xit"), this);
exitAction->setShortcuts(QKeySequence::Quit);
exitAction->setStatusTip(tr("Quit Scenediagram example"));
connect(exitAction, &QAction::triggered, this, &QWidget::close);
boldAction = new QAction(tr("Bold"), this);
boldAction->setCheckable(true);
QPixmap pixmap(":/images/bold.png");
boldAction->setIcon(QIcon(pixmap));
boldAction->setShortcut(tr("Ctrl+B"));
connect(boldAction, &QAction::triggered, this, &MainWindow::handleFontChange);
italicAction = new QAction(QIcon(":/images/italic.png"), tr("Italic"), this);
italicAction->setCheckable(true);
italicAction->setShortcut(tr("Ctrl+I"));
connect(italicAction, &QAction::triggered, this, &MainWindow::handleFontChange);
underlineAction = new QAction(QIcon(":/images/underline.png"), tr("Underline"), this);
underlineAction->setCheckable(true);
underlineAction->setShortcut(tr("Ctrl+U"));
connect(underlineAction, &QAction::triggered, this, &MainWindow::handleFontChange);
aboutAction = new QAction(tr("A&bout"), this);
aboutAction->setShortcut(tr("F1"));
connect(aboutAction, &QAction::triggered, this, &MainWindow::about);
}
void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(exitAction);
itemMenu = menuBar()->addMenu(tr("&Item"));
itemMenu->addAction(deleteAction);
itemMenu->addSeparator();
itemMenu->addAction(toFrontAction);
itemMenu->addAction(sendBackAction);
aboutMenu = menuBar()->addMenu(tr("&Help"));
aboutMenu->addAction(aboutAction);
}
void MainWindow::createToolbars()
{
editToolBar = addToolBar(tr("Edit"));
editToolBar->addAction(deleteAction);
editToolBar->addAction(toFrontAction);
editToolBar->addAction(sendBackAction);
fontCombo = new QFontComboBox();
connect(fontCombo, &QFontComboBox::currentFontChanged,
this, &MainWindow::currentFontChanged);
fontSizeCombo = new QComboBox;
fontSizeCombo->setEditable(true);
for (int i = 8; i < 30; i = i + 2)
fontSizeCombo->addItem(QString().setNum(i));
QIntValidator *validator = new QIntValidator(2, 64, this);
//验证范围
fontSizeCombo->setValidator(validator);
connect(fontSizeCombo, &QComboBox::currentTextChanged,
this, &MainWindow::fontSizeChanged);
//QToolButton是一种特殊的按钮,它提供对特定命令或选项的快速访问。
//与普通的命令按钮不同,工具按钮通常不显示文本标签,而是显示图标。
fontColorToolButton = new QToolButton;
fontColorToolButton->setPopupMode(QToolButton::MenuButtonPopup);
fontColorToolButton->setMenu(createColorMenu(SLOT(textColorChanged()), Qt::black));
textAction = fontColorToolButton->menu()->defaultAction();
fontColorToolButton->setIcon(createColorToolButtonIcon(":/images/textpointer.png", Qt::black));
fontColorToolButton->setAutoFillBackground(true);
connect(fontColorToolButton, &QAbstractButton::clicked,
this, &MainWindow::textButtonTriggered);
fillColorToolButton = new QToolButton;
fillColorToolButton->setPopupMode(QToolButton::MenuButtonPopup);
fillColorToolButton->setMenu(createColorMenu(SLOT(itemColorChanged()), Qt::white));
fillAction = fillColorToolButton->menu()->defaultAction();
fillColorToolButton->setIcon(createColorToolButtonIcon(
":/images/floodfill.png", Qt::white));
connect(fillColorToolButton, &QAbstractButton::clicked,
this, &MainWindow::fillButtonTriggered);
lineColorToolButton = new QToolButton;
lineColorToolButton->setPopupMode(QToolButton::MenuButtonPopup);
lineColorToolButton->setMenu(createColorMenu(SLOT(lineColorChanged()), Qt::black));
lineAction = lineColorToolButton->menu()->defaultAction();
lineColorToolButton->setIcon(createColorToolButtonIcon(
":/images/linecolor.png", Qt::black));
connect(lineColorToolButton, &QAbstractButton::clicked,
this, &MainWindow::lineButtonTriggered);
textToolBar = addToolBar(tr("Font"));
textToolBar->addWidget(fontCombo);
textToolBar->addWidget(fontSizeCombo);
textToolBar->addAction(boldAction);
textToolBar->addAction(italicAction);
textToolBar->addAction(underlineAction);
colorToolBar = addToolBar(tr("Color"));
colorToolBar->addWidget(fontColorToolButton);
colorToolBar->addWidget(fillColorToolButton);
colorToolBar->addWidget(lineColorToolButton);
QToolButton *pointerButton = new QToolButton;
pointerButton->setCheckable(true);
pointerButton->setChecked(true);
pointerButton->setIcon(QIcon(":/images/pointer.png"));
QToolButton *linePointerButton = new QToolButton;
linePointerButton->setCheckable(true);
linePointerButton->setIcon(QIcon(":/images/linepointer.png"));
pointerTypeGroup = new QButtonGroup(this);
pointerTypeGroup->addButton(pointerButton, int(DiagramScene::MoveItem));
pointerTypeGroup->addButton(linePointerButton, int(DiagramScene::InsertLine));
connect(pointerTypeGroup, QOverload<int>::of(&QButtonGroup::buttonClicked),
this, &MainWindow::pointerGroupClicked);
sceneScaleCombo = new QComboBox;
QStringList scales;
scales << tr("50%") << tr("75%") << tr("100%") << tr("125%") << tr("150%");
sceneScaleCombo->addItems(scales);
sceneScaleCombo->setCurrentIndex(2);
connect(sceneScaleCombo, &QComboBox::currentTextChanged,
this, &MainWindow::sceneScaleChanged);
pointerToolbar = addToolBar(tr("Pointer type"));
pointerToolbar->addWidget(pointerButton);
pointerToolbar->addWidget(linePointerButton);
pointerToolbar->addWidget(sceneScaleCombo);
}
QIcon MainWindow::createColorToolButtonIcon(const QString &imageFile, QColor color)
{
QPixmap pixmap(50, 80);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
QPixmap image(imageFile);
//!start:在按钮上方水平居中绘制图标,下端再画一个颜色框。
//按钮上的区域
QRect target(4, 0, 42, 43);
//图片的区域
QRect source(0, 0, 42, 43);
//QRect(left, top, width, height)
//这里height写的80,实际上大于等于20效果都一样
painter.fillRect(QRect(0, 60, 50, 80), color);
//将源的给定像素的矩形部分,绘制到绘制设备中的给定目标区域。
painter.drawPixmap(target, image, source);
//!start:在按钮上方水平居中绘制图标,下端再画一个颜色框。
return QIcon(pixmap);
}
QMenu *MainWindow::createColorMenu(const char *slot, QColor defaultColor)
{
QList<QColor> colors;
colors << Qt::black << Qt::white << Qt::red << Qt::blue << Qt::yellow;
QStringList names;
names << tr("black") << tr("white") << tr("red") << tr("blue")
<< tr("yellow");
QMenu *colorMenu = new QMenu(this);
for (int i = 0; i < colors.count(); ++i) {
QAction *action = new QAction(names.at(i), this);
action->setData(colors.at(i));
action->setIcon(createColorIcon(colors.at(i)));
connect(action, SIGNAL(triggered()), this, slot);
colorMenu->addAction(action);
if (colors.at(i) == defaultColor)
colorMenu->setDefaultAction(action);
}
return colorMenu;
}
QIcon MainWindow::createColorIcon(QColor color)
{
QPixmap pixmap(20, 20);
QPainter painter(&pixmap);
painter.setPen(Qt::NoPen);
painter.fillRect(QRect(0, 0, 20, 20), color);
return QIcon(pixmap);
}
void MainWindow::about()
{
QMessageBox::about(this, tr("About Diagram Scene"),
tr("The Diagram Scene example shows "
"use of the graphics framework."));
}
void MainWindow::buttonGroupClicked(int id)
{
const QList<QAbstractButton *> buttons = buttonGroup->buttons();
for (QAbstractButton *button : buttons) {
if (buttonGroup->button(id) != button)
button->setChecked(false);
}
if (id == InsertTextButton) {
scene->setMode(DiagramScene::InsertText);
} else {
scene->setItemType(DiagramItem::DiagramType(id));
scene->setMode(DiagramScene::InsertItem);
}
}
void MainWindow::backgroundButtonGroupClicked(QAbstractButton *button)
{
const QList<QAbstractButton *> buttons = backgroundButtonGroup->buttons();
for (QAbstractButton *myButton : buttons) {
if (myButton != button)
button->setChecked(false);
}
QString text = button->text();
if (text == tr("Blue Grid"))
scene->setBackgroundBrush(QPixmap(":/images/background1.png"));
else if (text == tr("White Grid"))
scene->setBackgroundBrush(QPixmap(":/images/background2.png"));
else if (text == tr("Gray Grid"))
scene->setBackgroundBrush(QPixmap(":/images/background3.png"));
else
scene->setBackgroundBrush(QPixmap(":/images/background4.png"));
scene->update();
view->update();
}
QWidget *MainWindow::createBackgroundCellWidget(const QString &text, const QString &image)
{
QToolButton *button = new QToolButton;
//下面这行可以不需要
button->setText(text);
button->setIcon(QIcon(image));
button->setIconSize(QSize(50, 50));
button->setCheckable(true);
backgroundButtonGroup->addButton(button);
QGridLayout *layout = new QGridLayout;
layout->addWidget(button, 0, 0, Qt::AlignHCenter);
layout->addWidget(new QLabel(text), 1, 0, Qt::AlignCenter);
QWidget *widget = new QWidget;
widget->setLayout(layout);
return widget;
}
void MainWindow::itemInserted(DiagramItem *item)
{
pointerTypeGroup->button(int(DiagramScene::MoveItem))->setChecked(true);
scene->setMode(DiagramScene::Mode(pointerTypeGroup->checkedId()));
buttonGroup->button(int(item->diagramType()))->setChecked(false);
}
void MainWindow::textInserted(QGraphicsTextItem *)
{
buttonGroup->button(InsertTextButton)->setChecked(false);
scene->setMode(DiagramScene::Mode(pointerTypeGroup->checkedId()));
}
QWidget *MainWindow::createCellWidget(const QString &text, DiagramItem::DiagramType type)
{
DiagramItem item(type, itemMenu);
QIcon icon(item.image());
QToolButton *button = new QToolButton;
button->setIcon(icon);
button->setIconSize(QSize(50, 50));
button->setCheckable(true);
buttonGroup->addButton(button, int(type));
QGridLayout *layout = new QGridLayout;
layout->addWidget(button, 0, 0, Qt::AlignHCenter);
layout->addWidget(new QLabel(text), 1, 0, Qt::AlignCenter);
QWidget *widget = new QWidget;
widget->setLayout(layout);
return widget;
}
void MainWindow::itemSelected(QGraphicsItem *item)
{
DiagramTextItem *textItem =
qgraphicsitem_cast<DiagramTextItem *>(item);
QFont font = textItem->font();
fontCombo->setCurrentFont(font);
fontSizeCombo->setEditText(QString().setNum(font.pointSize()));
boldAction->setChecked(font.weight() == QFont::Bold);
italicAction->setChecked(font.italic());
underlineAction->setChecked(font.underline());
}
void MainWindow::bringToFront()
{
if (scene->selectedItems().isEmpty())
return;
QGraphicsItem *selectedItem = scene->selectedItems().first();
const QList<QGraphicsItem *> overlapItems = selectedItem->collidingItems();
//将选中的itemz值设为0,其他的重叠item都+0.1,确保最小
qreal zValue = 0;
for (const QGraphicsItem *item : overlapItems) {
if (item->zValue() >= zValue && item->type() == DiagramItem::Type)
zValue = item->zValue() + 0.1;
}
selectedItem->setZValue(zValue);
}
void MainWindow::sendToBack()
{
if (scene->selectedItems().isEmpty())
return;
QGraphicsItem *selectedItem = scene->selectedItems().first();
const QList<QGraphicsItem *> overlapItems = selectedItem->collidingItems();
qreal zValue = 0;
for (const QGraphicsItem *item : overlapItems) {
if (item->zValue() <= zValue && item->type() == DiagramItem::Type)
zValue = item->zValue() - 0.1;
}
selectedItem->setZValue(zValue);
}
void MainWindow::deleteItem()
{
QList<QGraphicsItem *> selectedItems = scene->selectedItems();
for (QGraphicsItem *item : qAsConst(selectedItems)) {
if (item->type() == Arrow::Type) {
scene->removeItem(item);
Arrow *arrow = qgraphicsitem_cast<Arrow *>(item);
arrow->startItem()->removeArrow(arrow);
arrow->endItem()->removeArrow(arrow);
delete item;
}
}
selectedItems = scene->selectedItems();
for (QGraphicsItem *item : qAsConst(selectedItems)) {
if (item->type() == DiagramItem::Type)
qgraphicsitem_cast<DiagramItem *>(item)->removeArrows();
scene->removeItem(item);
delete item;
}
}
void MainWindow::handleFontChange()
{
QFont font = fontCombo->currentFont();
font.setPointSize(fontSizeCombo->currentText().toInt());
font.setWeight(boldAction->isChecked() ? QFont::Bold : QFont::Normal);
font.setItalic(italicAction->isChecked());
font.setUnderline(underlineAction->isChecked());
scene->setFont(font);
}
void MainWindow::currentFontChanged(const QFont &)
{
handleFontChange();
}
void MainWindow::fontSizeChanged(const QString &)
{
handleFontChange();
}
void MainWindow::textButtonTriggered()
{
scene->setTextColor(qvariant_cast<QColor>(textAction->data()));
}
void MainWindow::fillButtonTriggered()
{
scene->setItemColor(qvariant_cast<QColor>(fillAction->data()));
}
void MainWindow::textColorChanged()
{
textAction = qobject_cast<QAction *>(sender());
fontColorToolButton->setIcon(createColorToolButtonIcon(
":/images/textpointer.png",
qvariant_cast<QColor>(textAction->data())));
textButtonTriggered();
}
void MainWindow::itemColorChanged()
{
fillAction = qobject_cast<QAction *>(sender());
fillColorToolButton->setIcon(createColorToolButtonIcon(
":/images/floodfill.png",
qvariant_cast<QColor>(fillAction->data())));
fillButtonTriggered();
}
void MainWindow::lineButtonTriggered()
{
scene->setLineColor(qvariant_cast<QColor>(lineAction->data()));
}
void MainWindow::lineColorChanged()
{
lineAction = qobject_cast<QAction *>(sender());
lineColorToolButton->setIcon(createColorToolButtonIcon(
":/images/linecolor.png",
qvariant_cast<QColor>(lineAction->data())));
lineButtonTriggered();
}
void MainWindow::sceneScaleChanged(const QString &scale)
{
double newScale = scale.left(scale.indexOf(tr("%"))).toDouble() / 100.0;
QMatrix oldMatrix = view->matrix();
view->resetMatrix();
//view的中心不变
view->translate(oldMatrix.dx(), oldMatrix.dy());
view->scale(newScale, newScale);
}
//移动还是连线
void MainWindow::pointerGroupClicked(int)
{
scene->setMode(DiagramScene::Mode(pointerTypeGroup->checkedId()));
}