├── .gitignore ├── DrawingTool.pro ├── LICENSE ├── README.md ├── arrow.cpp ├── arrow.h ├── diagramitem.cpp ├── diagramitem.h ├── diagramscene.cpp ├── diagramscene.h ├── diagramscene.qrc ├── diagramtextitem.cpp ├── diagramtextitem.h ├── diagramview.cpp ├── diagramview.h ├── images ├── background1.png ├── background2.png ├── background3.png ├── background4.png ├── bold.png ├── bringtofront.png ├── copy.png ├── cut.png ├── delete.png ├── floodfill.png ├── group.png ├── italic.png ├── linecolor.png ├── linepointer.png ├── paste.png ├── pointer.png ├── redo.png ├── sendtoback.png ├── textpointer.png ├── underline.png ├── undo.png └── ungroup.png ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── screenshots └── screenshot-001.png ├── undosystem.cpp └── undosystem.h /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | *.slo 3 | *.lo 4 | *.o 5 | *.a 6 | *.la 7 | *.lai 8 | *.so 9 | *.dll 10 | *.dylib 11 | 12 | # Qt-es 13 | object_script.*.Release 14 | object_script.*.Debug 15 | *_plugin_import.cpp 16 | /.qmake.cache 17 | /.qmake.stash 18 | *.pro.user 19 | *.pro.user.* 20 | *.qbs.user 21 | *.qbs.user.* 22 | *.moc 23 | moc_*.cpp 24 | moc_*.h 25 | qrc_*.cpp 26 | ui_*.h 27 | *.qmlc 28 | *.jsc 29 | Makefile* 30 | *build-* 31 | 32 | # Qt unit tests 33 | target_wrapper.* 34 | 35 | # QtCreator 36 | *.autosave 37 | 38 | # QtCreator Qml 39 | *.qmlproject.user 40 | *.qmlproject.user.* 41 | 42 | # QtCreator CMake 43 | CMakeLists.txt.user* 44 | -------------------------------------------------------------------------------- /DrawingTool.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2018-12-05T16:11:34 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = DrawingTool 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | CONFIG += c++11 26 | 27 | HEADERS = mainwindow.h \ 28 | diagramitem.h \ 29 | diagramscene.h \ 30 | arrow.h \ 31 | diagramtextitem.h \ 32 | diagramview.h \ 33 | undosystem.h 34 | SOURCES = mainwindow.cpp \ 35 | diagramitem.cpp \ 36 | main.cpp \ 37 | arrow.cpp \ 38 | diagramtextitem.cpp \ 39 | diagramscene.cpp \ 40 | diagramview.cpp \ 41 | undosystem.cpp 42 | RESOURCES = diagramscene.qrc 43 | 44 | FORMS += 45 | 46 | # Default rules for deployment. 47 | qnx: target.path = /tmp/$${TARGET}/bin 48 | else: unix:!android: target.path = /opt/$${TARGET}/bin 49 | !isEmpty(target.path): INSTALLS += target 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Tanner 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qt-DrawingTool 2 | A drawing tool based on Qt's [Diagram-Scene-Example](http://doc.qt.io/qt-5/qtwidgets-graphicsview-diagramscene-example.html). 3 | 4 | ![1](/screenshots/screenshot-001.png?raw=true) 5 | 6 | ## Added features 7 | - Zooming control with `ctrl + mouse wheel` 8 | - Dragable background 9 | - Resizable graphics item 10 | - Copy, cut and paste 11 | - Undo and redo operation 12 | - Horizontal/Vertical lines when aligning items 13 | - Sticky mode for item's position adjustment 14 | - Group/UnGroup 15 | - More shapes 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /arrow.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | 52 | #include "arrow.h" 53 | 54 | #include 55 | 56 | #include 57 | #include 58 | 59 | const qreal Pi = 3.14; 60 | 61 | //! [0] 62 | Arrow::Arrow(DiagramItem *startItem, DiagramItem *endItem, QGraphicsItem *parent) 63 | : QGraphicsLineItem(parent) 64 | { 65 | myStartItem = startItem; 66 | myEndItem = endItem; 67 | setFlag(QGraphicsItem::ItemIsSelectable, true); 68 | myColor = Qt::black; 69 | setPen(QPen(myColor, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); 70 | } 71 | //! [0] 72 | 73 | //! [1] 74 | QRectF Arrow::boundingRect() const 75 | { 76 | qreal extra = (pen().width() + 20) / 2.0; 77 | 78 | return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(), 79 | line().p2().y() - line().p1().y())) 80 | .normalized() 81 | .adjusted(-extra, -extra, extra, extra); 82 | } 83 | //! [1] 84 | 85 | //! [2] 86 | QPainterPath Arrow::shape() const 87 | { 88 | QPainterPath path = QGraphicsLineItem::shape(); 89 | path.addPolygon(arrowHead); 90 | return path; 91 | } 92 | //! [2] 93 | 94 | //! [3] 95 | void Arrow::updatePosition() 96 | { 97 | QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0)); 98 | setLine(line); 99 | } 100 | 101 | //! [3] 102 | 103 | //! [4] 104 | void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *, 105 | QWidget *) 106 | { 107 | if (myStartItem->collidesWithItem(myEndItem)) 108 | return; 109 | 110 | QPen myPen = pen(); 111 | myPen.setColor(myColor); 112 | qreal arrowSize = 20; 113 | painter->setPen(myPen); 114 | painter->setBrush(myColor); 115 | //! [4] //! [5] 116 | 117 | QLineF centerLine(myStartItem->pos(), myEndItem->pos()); 118 | QPolygonF endPolygon = myEndItem->polygon(); 119 | QPointF p1 = endPolygon.first() + myEndItem->pos(); 120 | QPointF p2; 121 | QPointF intersectPoint; 122 | QLineF polyLine; 123 | for (int i = 1; i < endPolygon.count(); ++i) { 124 | p2 = endPolygon.at(i) + myEndItem->pos(); 125 | polyLine = QLineF(p1, p2); 126 | QLineF::IntersectType intersectType = 127 | polyLine.intersect(centerLine, &intersectPoint); 128 | if (intersectType == QLineF::BoundedIntersection) 129 | break; 130 | p1 = p2; 131 | } 132 | 133 | setLine(QLineF(intersectPoint, myStartItem->pos())); 134 | //! [5] //! [6] 135 | 136 | double angle = ::acos(line().dx() / line().length()); 137 | if (line().dy() >= 0) 138 | angle = (Pi * 2) - angle; 139 | 140 | QPointF arrowP1 = line().p1() + QPointF(sin(angle + Pi / 3) * arrowSize, 141 | cos(angle + Pi / 3) * arrowSize); 142 | QPointF arrowP2 = line().p1() + QPointF(sin(angle + Pi - Pi / 3) * arrowSize, 143 | cos(angle + Pi - Pi / 3) * arrowSize); 144 | 145 | arrowHead.clear(); 146 | arrowHead << line().p1() << arrowP1 << arrowP2; 147 | //! [6] //! [7] 148 | painter->drawLine(line()); 149 | painter->drawPolygon(arrowHead); 150 | if (isSelected()) { 151 | painter->setPen(QPen(myColor, 1, Qt::DashLine)); 152 | QLineF myLine = line(); 153 | myLine.translate(0, 4.0); 154 | painter->drawLine(myLine); 155 | myLine.translate(0,-8.0); 156 | painter->drawLine(myLine); 157 | } 158 | } 159 | //! [7] 160 | -------------------------------------------------------------------------------- /arrow.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef ARROW_H 52 | #define ARROW_H 53 | 54 | #include 55 | 56 | #include "diagramitem.h" 57 | 58 | QT_BEGIN_NAMESPACE 59 | class QGraphicsPolygonItem; 60 | class QGraphicsLineItem; 61 | class QGraphicsScene; 62 | class QRectF; 63 | class QGraphicsSceneMouseEvent; 64 | class QPainterPath; 65 | QT_END_NAMESPACE 66 | 67 | //! [0] 68 | class Arrow : public QGraphicsLineItem 69 | { 70 | public: 71 | enum { Type = UserType + 4 }; 72 | 73 | Arrow(DiagramItem *startItem, DiagramItem *endItem, 74 | QGraphicsItem *parent = nullptr); 75 | 76 | int type() const override { return Type; } 77 | QRectF boundingRect() const override; 78 | QPainterPath shape() const override; 79 | void setColor(const QColor &color) { myColor = color; } 80 | const QColor getColor() { return myColor; } 81 | DiagramItem *startItem() const { return myStartItem; } 82 | DiagramItem *endItem() const { return myEndItem; } 83 | 84 | void updatePosition(); 85 | 86 | protected: 87 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 88 | QWidget *widget = nullptr) override; 89 | 90 | private: 91 | DiagramItem *myStartItem; 92 | DiagramItem *myEndItem; 93 | QColor myColor; 94 | QPolygonF arrowHead; 95 | }; 96 | //! [0] 97 | 98 | #endif // ARROW_H 99 | -------------------------------------------------------------------------------- /diagramitem.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "diagramitem.h" 52 | #include "arrow.h" 53 | 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | //! [0] 62 | DiagramItem::DiagramItem(DiagramType diagramType, QMenu *contextMenu, 63 | QGraphicsItem *parent) 64 | : QGraphicsPolygonItem(parent) 65 | { 66 | myDiagramType = diagramType; 67 | myContextMenu = contextMenu; 68 | 69 | QPainterPath path; 70 | switch (myDiagramType) { 71 | case StartEnd: 72 | path.moveTo(200, 50); 73 | path.arcTo(150, 0, 50, 50, 0, 90); 74 | path.arcTo(50, 0, 50, 50, 90, 90); 75 | path.arcTo(50, 50, 50, 50, 180, 90); 76 | path.arcTo(150, 50, 50, 50, 270, 90); 77 | path.lineTo(200, 50); 78 | myPolygon = path.toFillPolygon().translated(-125, -50); 79 | break; 80 | case Conditional: 81 | myPolygon << QPointF(-100, 0) << QPointF(0, 100) 82 | << QPointF(100, 0) << QPointF(0, -100) 83 | << QPointF(-100, 0); 84 | break; 85 | case Step: 86 | myPolygon << QPointF(-100, -100) << QPointF(100, -100) 87 | << QPointF(100, 100) << QPointF(-100, 100) 88 | << QPointF(-100, -100); 89 | break; 90 | default: 91 | myPolygon << QPointF(-120, -80) << QPointF(-70, 80) 92 | << QPointF(120, 80) << QPointF(70, -80) 93 | << QPointF(-120, -80); 94 | break; 95 | } 96 | setPolygon(myPolygon); 97 | setFlag(QGraphicsItem::ItemIsMovable, true); 98 | setFlag(QGraphicsItem::ItemIsSelectable, true); 99 | setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); 100 | setAcceptHoverEvents(true); 101 | } 102 | 103 | 104 | void DiagramItem::removeArrow(Arrow *arrow) 105 | { 106 | int index = arrows.indexOf(arrow); 107 | 108 | if (index != -1) 109 | arrows.removeAt(index); 110 | } 111 | 112 | void DiagramItem::removeArrows() 113 | { 114 | foreach (Arrow *arrow, arrows) { 115 | arrow->startItem()->removeArrow(arrow); 116 | arrow->endItem()->removeArrow(arrow); 117 | scene()->removeItem(arrow); 118 | delete arrow; 119 | } 120 | } 121 | 122 | void DiagramItem::addArrow(Arrow *arrow) 123 | { 124 | arrows.append(arrow); 125 | } 126 | 127 | QPixmap DiagramItem::image() const 128 | { 129 | QPixmap pixmap(250, 250); 130 | pixmap.fill(Qt::transparent); 131 | QPainter painter(&pixmap); 132 | painter.setPen(QPen(Qt::black, 8)); 133 | painter.translate(125, 125); 134 | painter.drawPolyline(myPolygon); 135 | 136 | return pixmap; 137 | } 138 | 139 | QList DiagramItem::resizeHandlePoints() { 140 | qreal width = resizeHandlePointWidth; 141 | QRectF rf = QRectF(boundingRect().topLeft() + QPointF(width/2, width/2), 142 | boundingRect().bottomRight() - QPointF(width/2, width/2)); 143 | qreal centerX = rf.center().x(); 144 | qreal centerY = rf.center().y(); 145 | return QList{rf.topLeft(), QPointF(centerX, rf.top()), rf.topRight(), 146 | QPointF(rf.left(), centerY), QPointF(rf.right(), centerY), 147 | rf.bottomLeft(), QPointF(centerX, rf.bottom()), rf.bottomRight()}; 148 | } 149 | 150 | bool DiagramItem::isCloseEnough(QPointF const& p1, QPointF const& p2) { 151 | qreal delta = std::abs(p1.x() - p2.x()) + std::abs(p1.y() - p2.y()); 152 | return delta < closeEnoughDistance; 153 | } 154 | 155 | DiagramItem* DiagramItem::clone() { 156 | DiagramItem* cloned = new DiagramItem(myDiagramType, myContextMenu, nullptr); 157 | cloned->myPolygon = myPolygon; 158 | cloned->setPos(scenePos()); 159 | cloned->setPolygon(myPolygon); 160 | cloned->setBrush(brush()); 161 | cloned->setZValue(zValue()); 162 | return cloned; 163 | } 164 | 165 | 166 | void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { 167 | resizeMode = false; 168 | int index = 0; 169 | foreach (QPointF const& p, resizeHandlePoints()) { 170 | if (isCloseEnough(event->pos(), p)) { 171 | resizeMode = true; 172 | break; 173 | } 174 | index++; 175 | } 176 | scaleDirection = static_cast(index); 177 | setFlag(GraphicsItemFlag::ItemIsMovable, !resizeMode); 178 | if (resizeMode) { 179 | qDebug() << "begin resizing"; 180 | previousPolygon = polygon(); 181 | event->accept(); 182 | } else { 183 | qDebug() << "item type " << this->type() << " start moving from" << scenePos(); 184 | movingStartPosition = scenePos(); 185 | QGraphicsItem::mousePressEvent(event); 186 | } 187 | } 188 | 189 | void DiagramItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { 190 | if (resizeMode) { 191 | prepareGeometryChange(); 192 | myPolygon = scaledPolygon(myPolygon, scaleDirection, event->pos()); 193 | setPolygon(myPolygon); 194 | } 195 | QGraphicsItem::mouseMoveEvent(event); 196 | } 197 | 198 | void DiagramItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { 199 | if (resizeMode) { 200 | qDebug() << "after resizing"; 201 | if (polygon() != previousPolygon) { 202 | isResized = true; 203 | } 204 | } else { 205 | qDebug() << "\tend moving in" << scenePos(); 206 | if (scenePos() != movingStartPosition) { 207 | isMoved = true; 208 | qDebug() << "-- " << scenePos() << movingStartPosition; 209 | } 210 | } 211 | resizeMode = false; 212 | QGraphicsItem::mouseReleaseEvent(event); 213 | } 214 | 215 | void DiagramItem::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { 216 | setCursor(Qt::ArrowCursor); 217 | int index = 0; 218 | foreach (QPointF const& p, resizeHandlePoints()) { 219 | if (isCloseEnough(p, event->pos())) { 220 | switch (static_cast(index)) { 221 | case TopLeft: 222 | case BottomRight: setCursor(Qt::SizeFDiagCursor); break; 223 | case Top: 224 | case Bottom: setCursor(Qt::SizeVerCursor); break; 225 | case TopRight: 226 | case BottomLeft: setCursor(Qt::SizeBDiagCursor); break; 227 | case Left: 228 | case Right: setCursor(Qt::SizeHorCursor); break; 229 | } 230 | break; 231 | } 232 | index++; 233 | } 234 | QGraphicsItem::hoverMoveEvent(event); 235 | } 236 | 237 | void DiagramItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, 238 | QWidget* widget) { 239 | // remove build-in selected state 240 | QStyleOptionGraphicsItem myOption(*option); 241 | myOption.state &= ~QStyle::State_Selected; 242 | QGraphicsPolygonItem::paint(painter, &myOption, widget); 243 | 244 | // add resize handles 245 | if (this->isSelected()) { 246 | qreal width = resizeHandlePointWidth; 247 | foreach(QPointF const& point, resizeHandlePoints()) { 248 | painter->drawEllipse(QRectF(point.x() - width/2, point.y() - width/2, width, width)); 249 | } 250 | } 251 | } 252 | 253 | void DiagramItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) 254 | { 255 | scene()->clearSelection(); 256 | setSelected(true); 257 | myContextMenu->exec(event->screenPos()); 258 | } 259 | 260 | QVariant DiagramItem::itemChange(GraphicsItemChange change, const QVariant &value) 261 | { 262 | if (change == QGraphicsItem::ItemPositionChange) { 263 | foreach (Arrow *arrow, arrows) { 264 | arrow->updatePosition(); 265 | } 266 | } 267 | 268 | return value; 269 | } 270 | 271 | QPolygonF DiagramItem::scaledPolygon(const QPolygonF& old, DiagramItem::Direction direction, 272 | const QPointF& newPos) { 273 | qreal oldWidth = old.boundingRect().width(); 274 | qreal oldHeight = old.boundingRect().height(); 275 | qreal scaleWidth, scaleHeight; 276 | switch(direction) { 277 | case TopLeft: { 278 | QPointF fixPoint = old.boundingRect().bottomRight(); 279 | scaleWidth = (fixPoint.x() - newPos.x()) / oldWidth; 280 | scaleHeight = (fixPoint.y() - newPos.y()) / oldHeight; 281 | break; 282 | } 283 | case Top: { 284 | QPointF fixPoint = old.boundingRect().bottomLeft(); 285 | scaleWidth = 1; 286 | scaleHeight = (fixPoint.y() - newPos.y()) / oldHeight; 287 | break; 288 | } 289 | case TopRight: { 290 | QPointF fixPoint = old.boundingRect().bottomLeft(); 291 | scaleWidth = (newPos.x() - fixPoint.x()) / oldWidth; 292 | scaleHeight = (fixPoint.y() - newPos.y() ) / oldHeight; 293 | break; 294 | } 295 | case Right: { 296 | QPointF fixPoint = old.boundingRect().bottomLeft(); 297 | scaleWidth = (newPos.x() - fixPoint.x()) / oldWidth; 298 | scaleHeight = 1; 299 | break; 300 | } 301 | case BottomRight: { 302 | QPointF fixPoint = old.boundingRect().topLeft(); 303 | scaleWidth = (newPos.x() - fixPoint.x()) / oldWidth; 304 | scaleHeight = (newPos.y() - fixPoint.y()) / oldHeight; 305 | break; 306 | } 307 | case Bottom: { 308 | QPointF fixPoint = old.boundingRect().topLeft(); 309 | scaleWidth = 1; 310 | scaleHeight = (newPos.y() - fixPoint.y()) / oldHeight; 311 | break; 312 | } 313 | case BottomLeft: { 314 | QPointF fixPoint = old.boundingRect().topRight(); 315 | scaleWidth = (fixPoint.x() - newPos.x()) / oldWidth; 316 | scaleHeight = (newPos.y() - fixPoint.y()) / oldHeight; 317 | break; 318 | } 319 | case Left: { 320 | QPointF fixPoint = old.boundingRect().bottomRight(); 321 | scaleWidth = (fixPoint.x() - newPos.x()) / oldWidth; 322 | scaleHeight = 1; 323 | break; 324 | } 325 | } 326 | QTransform trans; 327 | trans.scale(scaleWidth, scaleHeight); 328 | return trans.map(old); 329 | } 330 | 331 | 332 | //! [6] 333 | -------------------------------------------------------------------------------- /diagramitem.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef DIAGRAMITEM_H 52 | #define DIAGRAMITEM_H 53 | 54 | #include 55 | #include 56 | 57 | QT_BEGIN_NAMESPACE 58 | class QPixmap; 59 | class QGraphicsItem; 60 | class QGraphicsScene; 61 | class QTextEdit; 62 | class QGraphicsSceneMouseEvent; 63 | class QMenu; 64 | class QGraphicsSceneContextMenuEvent; 65 | class QPainter; 66 | class QStyleOptionGraphicsItem; 67 | class QWidget; 68 | class QPolygonF; 69 | QT_END_NAMESPACE 70 | 71 | class Arrow; 72 | 73 | //! [0] 74 | class DiagramItem : public QGraphicsPolygonItem 75 | { 76 | friend class DiagramView; 77 | public: 78 | enum { Type = UserType + 15 }; 79 | enum DiagramType { Step, Conditional, StartEnd, Io }; 80 | enum Direction {TopLeft = 0, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight }; 81 | 82 | DiagramItem(DiagramType diagramType, QMenu *contextMenu, QGraphicsItem *parent = nullptr); 83 | 84 | void removeArrow(Arrow *arrow); 85 | void removeArrows(); 86 | DiagramType diagramType() const { return myDiagramType; } 87 | QPolygonF polygon() const { return myPolygon; } 88 | void addArrow(Arrow *arrow); 89 | QPixmap image() const; 90 | int type() const override { return Type;} 91 | QList resizeHandlePoints(); 92 | bool isCloseEnough(QPointF const& p1, QPointF const& p2); 93 | DiagramItem* clone(); 94 | 95 | protected: 96 | void mousePressEvent(QGraphicsSceneMouseEvent *event) override; 97 | void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; 98 | void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; 99 | void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; 100 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 101 | QWidget *widget = nullptr) override; 102 | void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; 103 | QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; 104 | 105 | private: 106 | QPolygonF scaledPolygon(QPolygonF const& old, Direction direction, QPointF const& newPos); 107 | 108 | DiagramType myDiagramType; 109 | QPolygonF myPolygon; 110 | QMenu *myContextMenu; 111 | QList arrows; 112 | static constexpr qreal resizeHandlePointWidth = 5; 113 | static constexpr qreal closeEnoughDistance = 5; 114 | bool resizeMode = false; 115 | Direction scaleDirection; 116 | 117 | QPointF movingStartPosition; 118 | bool isMoved = false; 119 | QPolygonF previousPolygon; 120 | bool isResized = false; 121 | }; 122 | //! [0] 123 | 124 | #endif // DIAGRAMITEM_H 125 | -------------------------------------------------------------------------------- /diagramscene.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "diagramscene.h" 52 | #include "arrow.h" 53 | 54 | #include 55 | #include 56 | #include 57 | 58 | QPen const DiagramScene::penForLines = QPen(QBrush(QColor(Qt::black)), 2, Qt::PenStyle::DashLine); 59 | 60 | DiagramScene::DiagramScene(QMenu *itemMenu, QObject *parent) 61 | : QGraphicsScene(parent) 62 | { 63 | myItemMenu = itemMenu; 64 | myMode = MoveItem; 65 | myItemType = DiagramItem::Step; 66 | line = nullptr; 67 | textItem = nullptr; 68 | myItemColor = Qt::white; 69 | myTextColor = Qt::black; 70 | myLineColor = Qt::black; 71 | } 72 | 73 | void DiagramScene::setLineColor(const QColor &color) 74 | { 75 | myLineColor = color; 76 | foreach (QGraphicsItem* p, selectedItems()) { 77 | if (p->type() == Arrow::Type) { 78 | Arrow* item = qgraphicsitem_cast(p); 79 | item->setColor(myLineColor); 80 | update(); 81 | } 82 | } 83 | } 84 | 85 | void DiagramScene::setTextColor(const QColor &color) 86 | { 87 | myTextColor = color; 88 | foreach (QGraphicsItem* p, selectedItems()) { 89 | if (p->type() == DiagramTextItem::Type) { 90 | DiagramTextItem* item = qgraphicsitem_cast(p); 91 | item->setDefaultTextColor(myTextColor); 92 | } 93 | } 94 | } 95 | //! [2] 96 | 97 | //! [3] 98 | void DiagramScene::setItemColor(const QColor &color) 99 | { 100 | myItemColor = color; 101 | foreach (QGraphicsItem* p, selectedItems()) { 102 | if (p->type() == DiagramItem::Type) { 103 | DiagramItem* item = qgraphicsitem_cast(p); 104 | item->setBrush(myItemColor); 105 | } 106 | } 107 | } 108 | 109 | void DiagramScene::setFont(const QFont &font) 110 | { 111 | myFont = font; 112 | foreach (QGraphicsItem* p, selectedItems()) { 113 | if (p->type() == DiagramTextItem::Type) { 114 | DiagramTextItem* item = qgraphicsitem_cast(p); 115 | item->setFont(myFont); 116 | } 117 | } 118 | } 119 | 120 | void DiagramScene::deleteItems(QList const& items) { 121 | qDebug() << "delete items" << items; 122 | 123 | QList diagramItems; 124 | foreach (QGraphicsItem *item, items) { 125 | if (item->type() == Arrow::Type) { 126 | removeItem(item); 127 | Arrow *arrow = qgraphicsitem_cast(item); 128 | arrow->startItem()->removeArrow(arrow); 129 | arrow->endItem()->removeArrow(arrow); 130 | delete item; 131 | } else diagramItems.append(item); 132 | } 133 | 134 | foreach (QGraphicsItem *item, diagramItems) { 135 | if (item->type() == DiagramItem::Type) 136 | qgraphicsitem_cast(item)->removeArrows(); 137 | removeItem(item); 138 | delete item; 139 | } 140 | } 141 | //! [4] 142 | 143 | void DiagramScene::setMode(Mode mode) 144 | { 145 | myMode = mode; 146 | } 147 | 148 | void DiagramScene::setItemType(DiagramItem::DiagramType type) 149 | { 150 | myItemType = type; 151 | } 152 | 153 | //! [5] 154 | void DiagramScene::editorLostFocus(DiagramTextItem *item) 155 | { 156 | QTextCursor cursor = item->textCursor(); 157 | cursor.clearSelection(); 158 | item->setTextCursor(cursor); 159 | 160 | if (item->toPlainText().isEmpty()) { 161 | removeItem(item); 162 | item->deleteLater(); 163 | } else { 164 | if (item->contentIsUpdated()) { 165 | qDebug() << "content update ---"; 166 | emit textChanged(); 167 | } 168 | } 169 | 170 | } 171 | 172 | void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) 173 | { 174 | if (mouseEvent->button() != Qt::LeftButton) 175 | return; 176 | 177 | DiagramItem *item; 178 | switch (myMode) { 179 | case InsertItem: 180 | item = new DiagramItem(myItemType, myItemMenu); 181 | item->setBrush(myItemColor); 182 | addItem(item); 183 | item->setPos(mouseEvent->scenePos()); 184 | qDebug() << "insert item at: " << mouseEvent->scenePos(); 185 | qDebug() << "\ttype: " << myItemType << " color: " << myItemColor; 186 | emit itemInserted(item); 187 | hasItemSelected = itemAt(mouseEvent->scenePos(), QTransform()) != nullptr; 188 | break; 189 | 190 | case InsertLine: 191 | if (itemAt(mouseEvent->scenePos(), QTransform()) == nullptr) break; 192 | line = new QGraphicsLineItem(QLineF(mouseEvent->scenePos(), 193 | mouseEvent->scenePos())); 194 | line->setPen(QPen(myLineColor, 2)); 195 | addItem(line); 196 | break; 197 | 198 | case InsertText: 199 | textItem = new DiagramTextItem(); 200 | textItem->setFont(myFont); 201 | textItem->setTextInteractionFlags(Qt::TextEditorInteraction); 202 | textItem->setZValue(1000.0); 203 | connect(textItem, SIGNAL(lostFocus(DiagramTextItem*)), 204 | this, SLOT(editorLostFocus(DiagramTextItem*))); 205 | connect(textItem, SIGNAL(selectedChange(QGraphicsItem*)), 206 | this, SIGNAL(itemSelected(QGraphicsItem*))); 207 | addItem(textItem); 208 | textItem->setDefaultTextColor(myTextColor); 209 | textItem->setPos(mouseEvent->scenePos()); 210 | emit textInserted(textItem); 211 | qDebug() << "text inserted at" << textItem->scenePos(); 212 | break; 213 | 214 | default: 215 | hasItemSelected = itemAt(mouseEvent->scenePos(), QTransform()) != nullptr; 216 | } 217 | QGraphicsScene::mousePressEvent(mouseEvent); 218 | } 219 | 220 | void DiagramScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { 221 | if (myMode == InsertLine && line != nullptr) { 222 | QLineF newLine(line->line().p1(), mouseEvent->scenePos()); 223 | line->setLine(newLine); 224 | } else if (myMode == MoveItem) { 225 | if (hasItemSelected) 226 | mouseDraggingMoveEvent(mouseEvent); 227 | QGraphicsScene::mouseMoveEvent(mouseEvent); 228 | } 229 | } 230 | 231 | void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { 232 | hasItemSelected = false; 233 | 234 | // leave sticky mode 235 | horizontalStickyMode = false; 236 | verticalStickyMode = false; 237 | foreach(QGraphicsItem* p, selectedItems()) 238 | p->setFlag(QGraphicsItem::ItemIsMovable); 239 | 240 | clearOrthogonalLines(); 241 | if (line != nullptr && myMode == InsertLine) { 242 | QList startItems = items(line->line().p1()); 243 | if (startItems.count() && startItems.first() == line) 244 | startItems.removeFirst(); 245 | QList endItems = items(line->line().p2()); 246 | if (endItems.count() && endItems.first() == line) 247 | endItems.removeFirst(); 248 | 249 | removeItem(line); 250 | delete line; 251 | 252 | if (startItems.count() > 0 && endItems.count() > 0 && 253 | startItems.first()->type() == DiagramItem::Type && 254 | endItems.first()->type() == DiagramItem::Type && 255 | startItems.first() != endItems.first()) { 256 | DiagramItem *startItem = qgraphicsitem_cast(startItems.first()); 257 | DiagramItem *endItem = qgraphicsitem_cast(endItems.first()); 258 | Arrow *arrow = new Arrow(startItem, endItem); 259 | arrow->setColor(myLineColor); 260 | startItem->addArrow(arrow); 261 | endItem->addArrow(arrow); 262 | arrow->setZValue(-1000.0); 263 | addItem(arrow); 264 | arrow->updatePosition(); 265 | emit arrowInserted(); 266 | } 267 | } 268 | 269 | line = nullptr; 270 | QGraphicsScene::mouseReleaseEvent(mouseEvent); 271 | } 272 | 273 | void DiagramScene::wheelEvent(QGraphicsSceneWheelEvent* wheelEvent) { 274 | // ctrl key is being pressed 275 | if ((wheelEvent->modifiers() & Qt::KeyboardModifier::ControlModifier) != 0) { 276 | emit scaleChanging(wheelEvent->delta()); 277 | wheelEvent->accept(); 278 | } else { 279 | QGraphicsScene::wheelEvent(wheelEvent); 280 | } 281 | } 282 | 283 | void DiagramScene::mouseDraggingMoveEvent(QGraphicsSceneMouseEvent* event) { 284 | clearOrthogonalLines(); 285 | if ((event->buttons() & Qt::LeftButton) != 0 && selectedItems().size() == 1) { 286 | QGraphicsItem* itemUnderCursor = selectedItems().first(); 287 | QPointF curCenter = itemUnderCursor->scenePos(); 288 | QPointF const& mousePos = event->scenePos(); 289 | 290 | foreach(QGraphicsItem* p, items()) { 291 | if (p->type() != DiagramItem::Type || p == itemUnderCursor) continue; 292 | 293 | DiagramItem* item = qgraphicsitem_cast(p); 294 | QPointF const& objPoint = item->scenePos(); 295 | LineAttr lineAttr; 296 | 297 | tryEnteringStickyMode(itemUnderCursor, objPoint, mousePos); 298 | 299 | if ((lineAttr = getPointsRelationship(objPoint, curCenter)) != Other) { 300 | if ((lineAttr & Horizontal) != 0) { 301 | QGraphicsLineItem* newHLine = new QGraphicsLineItem(); 302 | newHLine->setLine(QLineF(QPointF(0, objPoint.y()), 303 | QPointF(sceneRect().width(), objPoint.y()))); 304 | newHLine->setPen(penForLines); 305 | orthogonalLines.append(newHLine); 306 | } 307 | if ((lineAttr & Vertical) != 0) { 308 | QGraphicsLineItem* newVLine = new QGraphicsLineItem(); 309 | newVLine->setLine(QLineF(QPointF(objPoint.x(), 0), 310 | QPointF(objPoint.x(), sceneRect().height()))); 311 | newVLine->setPen(penForLines); 312 | orthogonalLines.append(newVLine); 313 | } 314 | } 315 | } 316 | tryLeavingStickyMode(itemUnderCursor, mousePos); 317 | } 318 | foreach(QGraphicsLineItem* p, orthogonalLines) { 319 | addItem(p); 320 | } 321 | } 322 | 323 | void DiagramScene::clearOrthogonalLines() { 324 | foreach(QGraphicsLineItem* p, orthogonalLines) { 325 | removeItem(p); 326 | delete p; 327 | } 328 | orthogonalLines.clear(); 329 | } 330 | 331 | bool DiagramScene::closeEnough(qreal x, qreal y, qreal delta) { 332 | return std::abs(x - y) < delta; 333 | } 334 | 335 | DiagramScene::LineAttr DiagramScene::getPointsRelationship(const QPointF& p1, 336 | const QPointF& p2) { 337 | int ret = Other; 338 | ret |= closeEnough(p1.x(), p2.x(), Delta) ? Vertical : Other; 339 | ret |= closeEnough(p1.y(), p2.y(), Delta) ? Horizontal : Other; 340 | return static_cast(ret); 341 | } 342 | 343 | void DiagramScene::tryEnteringStickyMode(QGraphicsItem* item, const QPointF& target, 344 | const QPointF& mousePos) { 345 | QPointF const& itemPos = item->scenePos(); 346 | if (!verticalStickyMode) { 347 | if (closeEnough(itemPos.x(), target.x(), stickyDistance)) { // enter stickyMode 348 | verticalStickyMode = true; 349 | verticalStickPoint = mousePos; 350 | item->setFlag(QGraphicsItem::ItemIsMovable, false); 351 | item->setPos(QPointF(target.x(), itemPos.y())); 352 | } 353 | } 354 | if (!horizontalStickyMode) { 355 | if (closeEnough(itemPos.y(), target.y(), stickyDistance)) { 356 | horizontalStickyMode = true; 357 | horizontalStickPoint = mousePos; 358 | item->setFlag(QGraphicsItem::ItemIsMovable, false); 359 | item->setPos(QPointF(itemPos.x(), target.y())); 360 | } 361 | } 362 | } 363 | 364 | void DiagramScene::tryLeavingStickyMode(QGraphicsItem* item, const QPointF& mousePos) { 365 | if (verticalStickyMode) { // already in stickyMode, item should be able to move vertically 366 | item->moveBy(0, mousePos.y() - verticalStickPoint.y()); 367 | verticalStickPoint.setY(mousePos.y()); 368 | 369 | // when to exit stickyMode? 370 | if (!closeEnough(mousePos.x(), verticalStickPoint.x(), stickyDistance)) { 371 | verticalStickyMode = false; 372 | item->setFlag(QGraphicsItem::ItemIsMovable, true); 373 | } 374 | } 375 | if (horizontalStickyMode) { 376 | item->moveBy(mousePos.x() - horizontalStickPoint.x(), 0); 377 | horizontalStickPoint.setX(mousePos.x()); 378 | 379 | if (!closeEnough(mousePos.y(), horizontalStickPoint.y(), stickyDistance)) { 380 | horizontalStickyMode = false; 381 | item->setFlag(QGraphicsItem::ItemIsMovable, true); 382 | } 383 | } 384 | } 385 | 386 | -------------------------------------------------------------------------------- /diagramscene.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef DIAGRAMSCENE_H 52 | #define DIAGRAMSCENE_H 53 | 54 | #include "diagramitem.h" 55 | #include "diagramtextitem.h" 56 | 57 | #include 58 | 59 | QT_BEGIN_NAMESPACE 60 | class QGraphicsSceneMouseEvent; 61 | class QMenu; 62 | class QPointF; 63 | class QGraphicsLineItem; 64 | class QFont; 65 | class QGraphicsTextItem; 66 | class QColor; 67 | QT_END_NAMESPACE 68 | 69 | //! [0] 70 | class DiagramScene : public QGraphicsScene 71 | { 72 | Q_OBJECT 73 | 74 | public: 75 | enum Mode { InsertItem, InsertLine, InsertText, MoveItem }; 76 | 77 | explicit DiagramScene(QMenu *itemMenu, QObject *parent = nullptr); 78 | QFont font() const { return myFont; } 79 | QColor textColor() const { return myTextColor; } 80 | QColor itemColor() const { return myItemColor; } 81 | QColor lineColor() const { return myLineColor; } 82 | void setLineColor(const QColor &color); 83 | void setTextColor(const QColor &color); 84 | void setItemColor(const QColor &color); 85 | void setFont(const QFont &font); 86 | 87 | // utilities 88 | void deleteItems(QList const& items); 89 | 90 | public slots: 91 | void setMode(Mode mode); 92 | void setItemType(DiagramItem::DiagramType type); 93 | void editorLostFocus(DiagramTextItem *item); 94 | 95 | signals: 96 | void itemInserted(DiagramItem *item); 97 | void textInserted(QGraphicsTextItem *item); 98 | void textChanged(); 99 | void arrowInserted(); 100 | void itemSelected(QGraphicsItem *item); 101 | void scaleChanging(int delta); 102 | 103 | protected: 104 | void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override; 105 | void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override; 106 | void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override; 107 | void wheelEvent(QGraphicsSceneWheelEvent* wheelEvent) override; 108 | 109 | private: 110 | void mouseDraggingMoveEvent(QGraphicsSceneMouseEvent* event); 111 | void clearOrthogonalLines(); 112 | inline bool closeEnough(qreal x, qreal y, qreal delta); 113 | enum LineAttr { Other = 0, Horizontal, Vertical, Both}; 114 | 115 | LineAttr getPointsRelationship(QPointF const& p1, QPointF const& p2); 116 | void tryEnteringStickyMode(QGraphicsItem* item, QPointF const& target, QPointF const& mousePos); 117 | void tryLeavingStickyMode(QGraphicsItem* item, QPointF const& mousePos); 118 | 119 | DiagramItem::DiagramType myItemType; 120 | QMenu *myItemMenu; 121 | Mode myMode; 122 | QPointF startPoint; 123 | QGraphicsLineItem *line; 124 | QFont myFont; 125 | DiagramTextItem *textItem; 126 | QColor myTextColor; 127 | QColor myItemColor; 128 | QColor myLineColor; 129 | 130 | bool horizontalStickyMode = false; 131 | bool verticalStickyMode = false; 132 | QPointF horizontalStickPoint; 133 | QPointF verticalStickPoint; 134 | QList orthogonalLines; 135 | 136 | bool hasItemSelected = false; 137 | 138 | static const QPen penForLines; 139 | static constexpr qreal Delta = 0.1; 140 | static constexpr qreal stickyDistance = 5; 141 | }; 142 | //! [0] 143 | 144 | #endif // DIAGRAMSCENE_H 145 | -------------------------------------------------------------------------------- /diagramscene.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/pointer.png 4 | images/linepointer.png 5 | images/textpointer.png 6 | images/bold.png 7 | images/italic.png 8 | images/underline.png 9 | images/floodfill.png 10 | images/bringtofront.png 11 | images/delete.png 12 | images/sendtoback.png 13 | images/linecolor.png 14 | images/background1.png 15 | images/background2.png 16 | images/background3.png 17 | images/background4.png 18 | images/paste.png 19 | images/copy.png 20 | images/cut.png 21 | images/redo.png 22 | images/undo.png 23 | images/ungroup.png 24 | images/group.png 25 | 26 | 27 | -------------------------------------------------------------------------------- /diagramtextitem.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "diagramtextitem.h" 52 | #include "diagramscene.h" 53 | #include 54 | #include 55 | 56 | //! [0] 57 | DiagramTextItem::DiagramTextItem(QGraphicsItem *parent) 58 | : QGraphicsTextItem(parent) 59 | { 60 | setFlag(QGraphicsItem::ItemIsMovable); 61 | setFlag(QGraphicsItem::ItemIsSelectable); 62 | positionLastTime = QPointF(0, 0); 63 | } 64 | 65 | DiagramTextItem* DiagramTextItem::clone() { 66 | DiagramTextItem* cloned = new DiagramTextItem(nullptr); 67 | cloned->setPlainText(toPlainText()); 68 | cloned->setFont(font()); 69 | cloned->setTextWidth(textWidth()); 70 | cloned->setDefaultTextColor(defaultTextColor()); 71 | cloned->setPos(scenePos()); 72 | cloned->setZValue(zValue()); 73 | return cloned; 74 | } 75 | 76 | QVariant DiagramTextItem::itemChange(GraphicsItemChange change, 77 | const QVariant &value) 78 | { 79 | if (change == QGraphicsItem::ItemSelectedHasChanged) 80 | emit selectedChange(this); 81 | return value; 82 | } 83 | 84 | void DiagramTextItem::focusInEvent(QFocusEvent* event) { 85 | qDebug() << "start editing"; 86 | if (positionLastTime == QPointF(0, 0)) 87 | // initialize positionLastTime to insertion position 88 | positionLastTime = scenePos(); 89 | QGraphicsTextItem::focusInEvent(event); 90 | } 91 | 92 | void DiagramTextItem::focusOutEvent(QFocusEvent *event) { 93 | setTextInteractionFlags(Qt::NoTextInteraction); 94 | qDebug() << "after editing" << this; 95 | if (contentLastTime == toPlainText()) { 96 | contentHasChanged = false; 97 | } else { 98 | contentLastTime = toPlainText(); 99 | contentHasChanged = true; 100 | } 101 | emit lostFocus(this); 102 | QGraphicsTextItem::focusOutEvent(event); 103 | } 104 | 105 | void DiagramTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { 106 | if (textInteractionFlags() == Qt::NoTextInteraction) { 107 | setTextInteractionFlags(Qt::TextEditorInteraction); 108 | } 109 | QGraphicsTextItem::mouseDoubleClickEvent(event); 110 | } 111 | 112 | void DiagramTextItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { 113 | qDebug() << "text begin move"; 114 | QGraphicsTextItem::mousePressEvent(event); 115 | } 116 | 117 | void DiagramTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { 118 | if (scenePos() != positionLastTime) { 119 | qDebug() << scenePos() << "::" << positionLastTime; 120 | isMoved = true; 121 | } 122 | positionLastTime = scenePos(); 123 | qDebug() << "text end moving"; 124 | QGraphicsTextItem::mouseReleaseEvent(event); 125 | } 126 | -------------------------------------------------------------------------------- /diagramtextitem.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef DIAGRAMTEXTITEM_H 52 | #define DIAGRAMTEXTITEM_H 53 | 54 | #include 55 | #include 56 | 57 | QT_BEGIN_NAMESPACE 58 | class QFocusEvent; 59 | class QGraphicsItem; 60 | class QGraphicsScene; 61 | class QGraphicsSceneMouseEvent; 62 | QT_END_NAMESPACE 63 | 64 | class DiagramTextItem : public QGraphicsTextItem 65 | { 66 | Q_OBJECT 67 | 68 | public: 69 | enum { Type = UserType + 3 }; 70 | 71 | DiagramTextItem(QGraphicsItem *parent = nullptr); 72 | 73 | int type() const override { return Type; } 74 | 75 | bool contentIsUpdated() { return contentHasChanged; } 76 | bool positionIsUpdated() { return isMoved; } 77 | void setUpdated() { isMoved = false; } 78 | DiagramTextItem* clone(); 79 | 80 | signals: 81 | void lostFocus(DiagramTextItem *item); 82 | void selectedChange(QGraphicsItem *item); 83 | 84 | protected: 85 | QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; 86 | void focusInEvent(QFocusEvent* event) override; 87 | void focusOutEvent(QFocusEvent *event) override; 88 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; 89 | void mousePressEvent(QGraphicsSceneMouseEvent *event) override; 90 | void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; 91 | 92 | private: 93 | QString contentLastTime; 94 | QPointF positionLastTime; 95 | bool isMoved = false; 96 | bool contentHasChanged = false; 97 | }; 98 | 99 | #endif // DIAGRAMTEXTITEM_H 100 | -------------------------------------------------------------------------------- /diagramview.cpp: -------------------------------------------------------------------------------- 1 | #include "diagramview.h" 2 | #include 3 | #include 4 | #include "diagramitem.h" 5 | #include "diagramtextitem.h" 6 | 7 | DiagramView::DiagramView(QGraphicsScene* scene, QWidget* parent) 8 | : QGraphicsView(scene, parent) { 9 | setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); 10 | setDragMode(QGraphicsView::RubberBandDrag); 11 | } 12 | 13 | 14 | void DiagramView::keyPressEvent(QKeyEvent* event) { 15 | if ((event->modifiers() & Qt::KeyboardModifier::ControlModifier) != 0) { 16 | setDragMode(DragMode::ScrollHandDrag); 17 | } 18 | QGraphicsView::keyPressEvent(event); 19 | } 20 | 21 | void DiagramView::keyReleaseEvent(QKeyEvent* event) { 22 | if ((event->modifiers() & Qt::KeyboardModifier::ControlModifier) == 0) { 23 | setDragMode(DragMode::RubberBandDrag); 24 | } 25 | } 26 | 27 | void DiagramView::mouseReleaseEvent(QMouseEvent* event) { 28 | QGraphicsView::mouseReleaseEvent(event); 29 | bool needsEmit = false; 30 | foreach(QGraphicsItem* item, scene()->selectedItems()) { 31 | if (item->type() == DiagramItem::Type) { 32 | DiagramItem* p = qgraphicsitem_cast(item); 33 | if (p->isMoved) { 34 | needsEmit = true; 35 | p->isMoved = false; 36 | } 37 | if (p->isResized) { 38 | needsEmit = true; 39 | p->isResized = false; 40 | } 41 | } else if (item->type() == DiagramTextItem::Type) { 42 | DiagramTextItem* p = qgraphicsitem_cast(item); 43 | if (p->positionIsUpdated()) { 44 | qDebug() << "isUpdated!"; 45 | needsEmit = true; 46 | p->setUpdated(); 47 | } 48 | } 49 | } 50 | if (needsEmit) emit needsUndoBackup(); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /diagramview.h: -------------------------------------------------------------------------------- 1 | #ifndef DIAGRAMVIEW_H 2 | #define DIAGRAMVIEW_H 3 | #include 4 | 5 | class DiagramView : public QGraphicsView { 6 | Q_OBJECT 7 | public: 8 | DiagramView(QGraphicsScene *scene, QWidget *parent = Q_NULLPTR); 9 | signals: 10 | void needsUndoBackup(); 11 | protected: 12 | void keyPressEvent(QKeyEvent* event) override; 13 | void keyReleaseEvent(QKeyEvent* event) override; 14 | void mouseReleaseEvent(QMouseEvent* event) override; 15 | }; 16 | 17 | #endif // DIAGRAMVIEW_H 18 | -------------------------------------------------------------------------------- /images/background1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/background1.png -------------------------------------------------------------------------------- /images/background2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/background2.png -------------------------------------------------------------------------------- /images/background3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/background3.png -------------------------------------------------------------------------------- /images/background4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/background4.png -------------------------------------------------------------------------------- /images/bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/bold.png -------------------------------------------------------------------------------- /images/bringtofront.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/bringtofront.png -------------------------------------------------------------------------------- /images/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/copy.png -------------------------------------------------------------------------------- /images/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/cut.png -------------------------------------------------------------------------------- /images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/delete.png -------------------------------------------------------------------------------- /images/floodfill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/floodfill.png -------------------------------------------------------------------------------- /images/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/group.png -------------------------------------------------------------------------------- /images/italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/italic.png -------------------------------------------------------------------------------- /images/linecolor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/linecolor.png -------------------------------------------------------------------------------- /images/linepointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/linepointer.png -------------------------------------------------------------------------------- /images/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/paste.png -------------------------------------------------------------------------------- /images/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/pointer.png -------------------------------------------------------------------------------- /images/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/redo.png -------------------------------------------------------------------------------- /images/sendtoback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/sendtoback.png -------------------------------------------------------------------------------- /images/textpointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/textpointer.png -------------------------------------------------------------------------------- /images/underline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/underline.png -------------------------------------------------------------------------------- /images/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/undo.png -------------------------------------------------------------------------------- /images/ungroup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/images/ungroup.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "mainwindow.h" 52 | 53 | #include 54 | 55 | int main(int argv, char *args[]) 56 | { 57 | Q_INIT_RESOURCE(diagramscene); 58 | 59 | QApplication app(argv, args); 60 | MainWindow mainWindow; 61 | mainWindow.setGeometry(100, 100, 800, 500); 62 | mainWindow.show(); 63 | 64 | return app.exec(); 65 | } 66 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "arrow.h" 52 | #include "diagramitem.h" 53 | #include "diagramscene.h" 54 | #include "diagramtextitem.h" 55 | #include "mainwindow.h" 56 | 57 | #include 58 | 59 | const int InsertTextButton = 10; 60 | 61 | MainWindow::MainWindow() { 62 | createActions(); 63 | createToolBox(); 64 | createMenus(); 65 | 66 | scene = new DiagramScene(itemMenu, this); 67 | scene->setSceneRect(QRectF(0, 0, 5000, 5000)); 68 | connect(scene, SIGNAL(itemInserted(DiagramItem*)), 69 | this, SLOT(itemInserted(DiagramItem*))); 70 | connect(scene, SIGNAL(textInserted(QGraphicsTextItem*)), 71 | this, SLOT(textInserted(QGraphicsTextItem*))); 72 | connect(scene, SIGNAL(arrowInserted()), 73 | this, SLOT(backupUndostack())); 74 | connect(scene, SIGNAL(textChanged()), 75 | this, SLOT(backupUndostack())); 76 | connect(scene, SIGNAL(itemSelected(QGraphicsItem*)), 77 | this, SLOT(itemSelected(QGraphicsItem*))); 78 | connect(scene, SIGNAL(scaleChanging(int)), 79 | this, SLOT(sceneScaleZooming(int))); 80 | 81 | createToolbars(); 82 | 83 | QHBoxLayout *layout = new QHBoxLayout; 84 | layout->addWidget(toolBox); 85 | view = new DiagramView(scene); 86 | layout->addWidget(view); 87 | 88 | connect(view, SIGNAL(needsUndoBackup()), this, SLOT(backupUndostack())); 89 | 90 | QWidget *widget = new QWidget; 91 | widget->setLayout(layout); 92 | 93 | setCentralWidget(widget); 94 | setWindowTitle(tr("Diagramscene")); 95 | setUnifiedTitleAndToolBarOnMac(true); 96 | 97 | undoStack.backup(QList()); 98 | } 99 | 100 | void MainWindow::backgroundButtonGroupClicked(QAbstractButton *button) 101 | { 102 | QList buttons = backgroundButtonGroup->buttons(); 103 | foreach (QAbstractButton *myButton, buttons) { 104 | if (myButton != button) 105 | button->setChecked(false); 106 | } 107 | QString text = button->text(); 108 | if (text == tr("Blue Grid")) 109 | scene->setBackgroundBrush(QPixmap(":/images/background1.png")); 110 | else if (text == tr("White Grid")) 111 | scene->setBackgroundBrush(QPixmap(":/images/background2.png")); 112 | else if (text == tr("Gray Grid")) 113 | scene->setBackgroundBrush(QPixmap(":/images/background3.png")); 114 | else 115 | scene->setBackgroundBrush(QPixmap(":/images/background4.png")); 116 | 117 | scene->update(); 118 | view->update(); 119 | } 120 | 121 | void MainWindow::buttonGroupClicked(int id) { 122 | QList buttons = buttonGroup->buttons(); 123 | QAbstractButton* clickedButton = buttonGroup->button(id); 124 | 125 | // set other button unchecked 126 | foreach (QAbstractButton *button, buttons) { 127 | if (clickedButton != button) 128 | button->setChecked(false); 129 | } 130 | 131 | // simply set objButton unchecked if already checked 132 | if (!clickedButton->isChecked()) { 133 | scene->setMode(DiagramScene::Mode(pointerTypeGroup->checkedId())); 134 | return; 135 | } 136 | 137 | if (id == InsertTextButton) { 138 | scene->setMode(DiagramScene::InsertText); 139 | } else { 140 | scene->setItemType(DiagramItem::DiagramType(id)); 141 | scene->setMode(DiagramScene::InsertItem); 142 | } 143 | } 144 | 145 | void MainWindow::copyItem() { 146 | foreach(QGraphicsItem* p, pasteBoard) { 147 | delete p; 148 | } 149 | pasteBoard = cloneItems(scene->selectedItems()); 150 | qDebug() << pasteBoard.size(); 151 | } 152 | 153 | void MainWindow::pasteItem() { 154 | QList pasteBoardCopy(cloneItems(pasteBoard)); 155 | foreach(QGraphicsItem* p, scene->items()) p->setSelected(false); 156 | 157 | foreach(QGraphicsItem* item, pasteBoard) { 158 | if (item->type() != Arrow::Type) { 159 | item->setPos(item->scenePos() + QPointF(20, 20)); 160 | item->setZValue(item->zValue() + 0.1); // raise a little bit 161 | } 162 | scene->addItem(item); 163 | item->setSelected(true); 164 | } 165 | pasteBoard.swap(pasteBoardCopy); 166 | if (!scene->selectedItems().empty()) 167 | undoStack.backup(cloneItems(scene->items())); 168 | } 169 | 170 | void MainWindow::cutItem() { 171 | copyItem(); 172 | deleteItem(); 173 | } 174 | 175 | void MainWindow::deleteItem() { 176 | bool needsBackup = !scene->selectedItems().empty(); 177 | scene->deleteItems(scene->selectedItems()); 178 | if (needsBackup) 179 | undoStack.backup(cloneItems(scene->items())); 180 | } 181 | 182 | void MainWindow::undo() { 183 | if (undoStack.isEmpty()) return; 184 | // sweep away all items 185 | scene->deleteItems(scene->items()); 186 | QList undoneItems = cloneItems(undoStack.undo()); 187 | foreach(QGraphicsItem* item, undoneItems) { 188 | qDebug() << item << "--------- add item"; 189 | scene->addItem(item); 190 | } 191 | 192 | // update arrows 193 | foreach(QGraphicsItem* item, undoneItems) { 194 | if (item->type() == Arrow::Type) 195 | qgraphicsitem_cast(item)->updatePosition(); 196 | } 197 | } 198 | 199 | void MainWindow::redo() { 200 | if (undoStack.isFull()) return; 201 | scene->deleteItems(scene->items()); 202 | QList redoneItems = cloneItems(undoStack.redo()); 203 | foreach(QGraphicsItem* item, redoneItems) { 204 | scene->addItem(item); 205 | } 206 | 207 | // update arrows 208 | foreach(QGraphicsItem* item, redoneItems) { 209 | if (item->type() == Arrow::Type) 210 | qgraphicsitem_cast(item)->updatePosition(); 211 | } 212 | } 213 | 214 | void MainWindow::groupItems() { 215 | QGraphicsItemGroup* group = scene->createItemGroup(scene->selectedItems()); 216 | group->setFlag(QGraphicsItem::ItemIsMovable, true); 217 | group->setFlag(QGraphicsItem::ItemIsSelectable, true); 218 | group->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); 219 | scene->addItem(group); 220 | backupUndostack(); 221 | } 222 | 223 | void MainWindow::ungroupItems() { 224 | foreach(QGraphicsItem* p, scene->selectedItems()) { 225 | if (p->type() == QGraphicsItemGroup::Type) { 226 | scene->destroyItemGroup(qgraphicsitem_cast(p)); 227 | } 228 | } 229 | backupUndostack(); 230 | } 231 | 232 | void MainWindow::pointerGroupClicked(int) 233 | { 234 | // set all buttons in toolbox unchecked 235 | foreach(QAbstractButton* b, buttonGroup->buttons()) { 236 | b->setChecked(false); 237 | } 238 | scene->setMode(DiagramScene::Mode(pointerTypeGroup->checkedId())); 239 | } 240 | 241 | void MainWindow::bringToFront() 242 | { 243 | if (scene->selectedItems().isEmpty()) 244 | return; 245 | 246 | QGraphicsItem *selectedItem = scene->selectedItems().first(); 247 | QList overlapItems = selectedItem->collidingItems(); 248 | 249 | qreal zValue = 0; 250 | foreach (QGraphicsItem *item, overlapItems) { 251 | if (item->zValue() >= zValue && item->type() == DiagramItem::Type) 252 | zValue = item->zValue() + 0.1; 253 | } 254 | selectedItem->setZValue(zValue); 255 | backupUndostack(); 256 | } 257 | //! [5] 258 | 259 | //! [6] 260 | void MainWindow::sendToBack() 261 | { 262 | if (scene->selectedItems().isEmpty()) 263 | return; 264 | 265 | QGraphicsItem *selectedItem = scene->selectedItems().first(); 266 | QList overlapItems = selectedItem->collidingItems(); 267 | 268 | qreal zValue = 0; 269 | foreach (QGraphicsItem *item, overlapItems) { 270 | if (item->zValue() <= zValue && item->type() == DiagramItem::Type) 271 | zValue = item->zValue() - 0.1; 272 | } 273 | selectedItem->setZValue(zValue); 274 | backupUndostack(); 275 | } 276 | //! [6] 277 | 278 | //! [7] 279 | void MainWindow::itemInserted(DiagramItem *item) 280 | { 281 | pointerTypeGroup->button(int(DiagramScene::MoveItem))->setChecked(true); 282 | scene->setMode(DiagramScene::Mode(pointerTypeGroup->checkedId())); 283 | buttonGroup->button(int(item->diagramType()))->setChecked(false); 284 | backupUndostack(); 285 | } 286 | //! [7] 287 | 288 | //! [8] 289 | void MainWindow::textInserted(QGraphicsTextItem *) 290 | { 291 | buttonGroup->button(InsertTextButton)->setChecked(false); 292 | scene->setMode(DiagramScene::Mode(pointerTypeGroup->checkedId())); 293 | } 294 | 295 | void MainWindow::backupUndostack() { 296 | undoStack.backup(cloneItems(scene->items())); 297 | } 298 | //! [8] 299 | 300 | //! [9] 301 | void MainWindow::currentFontChanged(const QFont &) 302 | { 303 | handleFontChange(); 304 | } 305 | //! [9] 306 | 307 | //! [10] 308 | void MainWindow::fontSizeChanged(const QString &) 309 | { 310 | handleFontChange(); 311 | } 312 | //! [10] 313 | 314 | //! [11] 315 | void MainWindow::sceneScaleChanged(const QString &scale) 316 | { 317 | double newScale = scale.toDouble() / 100.0; 318 | QMatrix oldMatrix = view->matrix(); 319 | view->resetMatrix(); 320 | view->translate(oldMatrix.dx(), oldMatrix.dy()); 321 | view->scale(newScale, newScale); 322 | } 323 | 324 | void MainWindow::sceneScaleZooming(int delta) { 325 | int changingPercent = delta > 0 ? 10 : -10; 326 | QString comboText = sceneScaleCombo->currentText(); 327 | int newScale = comboText.toInt() + changingPercent; 328 | 329 | if (newScale > 0 && newScale <= 200) { 330 | sceneScaleCombo->setCurrentText(QString().number(newScale)); 331 | } 332 | } 333 | //! [11] 334 | 335 | //! [12] 336 | void MainWindow::textColorChanged() 337 | { 338 | textAction = qobject_cast(sender()); 339 | fontColorToolButton->setIcon(createColorToolButtonIcon( 340 | ":/images/textpointer.png", 341 | qvariant_cast(textAction->data()))); 342 | textButtonTriggered(); 343 | undoStack.backup(cloneItems(scene->items())); 344 | } 345 | //! [12] 346 | 347 | //! [13] 348 | void MainWindow::itemColorChanged() 349 | { 350 | fillAction = qobject_cast(sender()); 351 | fillColorToolButton->setIcon(createColorToolButtonIcon( 352 | ":/images/floodfill.png", 353 | qvariant_cast(fillAction->data()))); 354 | fillButtonTriggered(); 355 | undoStack.backup(cloneItems(scene->items())); 356 | } 357 | //! [13] 358 | 359 | //! [14] 360 | void MainWindow::lineColorChanged() 361 | { 362 | lineAction = qobject_cast(sender()); 363 | lineColorToolButton->setIcon(createColorToolButtonIcon( 364 | ":/images/linecolor.png", 365 | qvariant_cast(lineAction->data()))); 366 | lineButtonTriggered(); 367 | } 368 | //! [14] 369 | 370 | //! [15] 371 | void MainWindow::textButtonTriggered() 372 | { 373 | scene->setTextColor(qvariant_cast(textAction->data())); 374 | } 375 | //! [15] 376 | 377 | //! [16] 378 | void MainWindow::fillButtonTriggered() 379 | { 380 | scene->setItemColor(qvariant_cast(fillAction->data())); 381 | } 382 | //! [16] 383 | 384 | //! [17] 385 | void MainWindow::lineButtonTriggered() 386 | { 387 | scene->setLineColor(qvariant_cast(lineAction->data())); 388 | } 389 | //! [17] 390 | 391 | //! [18] 392 | void MainWindow::handleFontChange() 393 | { 394 | QFont font = fontCombo->currentFont(); 395 | font.setPointSize(fontSizeCombo->currentText().toInt()); 396 | font.setWeight(boldAction->isChecked() ? QFont::Bold : QFont::Normal); 397 | font.setItalic(italicAction->isChecked()); 398 | font.setUnderline(underlineAction->isChecked()); 399 | 400 | scene->setFont(font); 401 | } 402 | //! [18] 403 | 404 | //! [19] 405 | void MainWindow::itemSelected(QGraphicsItem *item) 406 | { 407 | DiagramTextItem *textItem = 408 | qgraphicsitem_cast(item); 409 | 410 | QFont font = textItem->font(); 411 | //fontCombo->setCurrentFont(font); 412 | fontSizeCombo->setEditText(QString().setNum(font.pointSize())); 413 | boldAction->setChecked(font.weight() == QFont::Bold); 414 | italicAction->setChecked(font.italic()); 415 | underlineAction->setChecked(font.underline()); 416 | } 417 | //! [19] 418 | 419 | //! [20] 420 | void MainWindow::about() 421 | { 422 | QMessageBox::about(this, tr("About Diagram Scene"), 423 | tr("A drawing tool based on Qt Example.")); 424 | } 425 | 426 | void MainWindow::createToolBox() 427 | { 428 | buttonGroup = new QButtonGroup(this); 429 | buttonGroup->setExclusive(false); 430 | connect(buttonGroup, SIGNAL(buttonClicked(int)), 431 | this, SLOT(buttonGroupClicked(int))); 432 | QGridLayout *layout = new QGridLayout; 433 | layout->addWidget(createCellWidget(tr("Conditional"), DiagramItem::Conditional), 0, 0); 434 | layout->addWidget(createCellWidget(tr("Process"), DiagramItem::Step),0, 1); 435 | layout->addWidget(createCellWidget(tr("Input/Output"), DiagramItem::Io), 1, 0); 436 | layout->addWidget(createCellWidget(tr("Start/End"), DiagramItem::StartEnd), 1, 1); 437 | 438 | QToolButton *textButton = new QToolButton; 439 | textButton->setCheckable(true); 440 | buttonGroup->addButton(textButton, InsertTextButton); 441 | textButton->setIcon(QIcon(QPixmap(":/images/textpointer.png"))); 442 | textButton->setIconSize(QSize(50, 50)); 443 | QGridLayout *textLayout = new QGridLayout; 444 | textLayout->addWidget(textButton, 0, 0, Qt::AlignHCenter); 445 | textLayout->addWidget(new QLabel(tr("Text")), 1, 0, Qt::AlignCenter); 446 | QWidget *textWidget = new QWidget; 447 | textWidget->setLayout(textLayout); 448 | layout->addWidget(textWidget, 2, 0); 449 | 450 | layout->setRowStretch(3, 10); 451 | layout->setColumnStretch(2, 10); 452 | 453 | QWidget *itemWidget = new QWidget; 454 | itemWidget->setLayout(layout); 455 | 456 | backgroundButtonGroup = new QButtonGroup(this); 457 | connect(backgroundButtonGroup, SIGNAL(buttonClicked(QAbstractButton*)), 458 | this, SLOT(backgroundButtonGroupClicked(QAbstractButton*))); 459 | 460 | QGridLayout *backgroundLayout = new QGridLayout; 461 | backgroundLayout->addWidget(createBackgroundCellWidget(tr("Blue Grid"), 462 | ":/images/background1.png"), 0, 0); 463 | backgroundLayout->addWidget(createBackgroundCellWidget(tr("White Grid"), 464 | ":/images/background2.png"), 0, 1); 465 | backgroundLayout->addWidget(createBackgroundCellWidget(tr("Gray Grid"), 466 | ":/images/background3.png"), 1, 0); 467 | backgroundLayout->addWidget(createBackgroundCellWidget(tr("No Grid"), 468 | ":/images/background4.png"), 1, 1); 469 | 470 | backgroundLayout->setRowStretch(2, 10); 471 | backgroundLayout->setColumnStretch(2, 10); 472 | 473 | QWidget *backgroundWidget = new QWidget; 474 | backgroundWidget->setLayout(backgroundLayout); 475 | 476 | toolBox = new QToolBox; 477 | toolBox->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored)); 478 | toolBox->setMinimumWidth(itemWidget->sizeHint().width()); 479 | toolBox->addItem(itemWidget, tr("Basic Flowchart Shapes")); 480 | toolBox->addItem(backgroundWidget, tr("Backgrounds")); 481 | } 482 | 483 | void MainWindow::createActions() 484 | { 485 | toFrontAction = new QAction(QIcon(":/images/bringtofront.png"), 486 | tr("Bring to &Front"), this); 487 | toFrontAction->setShortcut(tr("Ctrl+F")); 488 | toFrontAction->setStatusTip(tr("Bring item to front")); 489 | connect(toFrontAction, SIGNAL(triggered()), this, SLOT(bringToFront())); 490 | 491 | sendBackAction = new QAction(QIcon(":/images/sendtoback.png"), tr("Send to &Back"), this); 492 | sendBackAction->setShortcut(tr("Ctrl+T")); 493 | sendBackAction->setStatusTip(tr("Send item to back")); 494 | connect(sendBackAction, SIGNAL(triggered()), this, SLOT(sendToBack())); 495 | 496 | deleteAction = new QAction(QIcon(":/images/delete.png"), tr("&Delete"), this); 497 | deleteAction->setShortcut(tr("Delete")); 498 | deleteAction->setStatusTip(tr("Delete item from diagram")); 499 | connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteItem())); 500 | 501 | exitAction = new QAction(tr("E&xit"), this); 502 | exitAction->setShortcuts(QKeySequence::Quit); 503 | exitAction->setStatusTip(tr("Quit Scenediagram example")); 504 | connect(exitAction, SIGNAL(triggered()), this, SLOT(close())); 505 | 506 | boldAction = new QAction(tr("Bold"), this); 507 | boldAction->setCheckable(true); 508 | QPixmap pixmap(":/images/bold.png"); 509 | boldAction->setIcon(QIcon(pixmap)); 510 | boldAction->setShortcut(tr("Ctrl+B")); 511 | connect(boldAction, SIGNAL(triggered()), this, SLOT(handleFontChange())); 512 | 513 | italicAction = new QAction(QIcon(":/images/italic.png"), tr("Italic"), this); 514 | italicAction->setCheckable(true); 515 | italicAction->setShortcut(tr("Ctrl+I")); 516 | connect(italicAction, SIGNAL(triggered()), this, SLOT(handleFontChange())); 517 | 518 | underlineAction = new QAction(QIcon(":/images/underline.png"), tr("Underline"), this); 519 | underlineAction->setCheckable(true); 520 | underlineAction->setShortcut(tr("Ctrl+U")); 521 | connect(underlineAction, SIGNAL(triggered()), this, SLOT(handleFontChange())); 522 | 523 | aboutAction = new QAction(tr("A&bout"), this); 524 | aboutAction->setShortcut(tr("F1")); 525 | connect(aboutAction, SIGNAL(triggered()), this, SLOT(about())); 526 | 527 | copyAction = new QAction(QIcon(":/images/copy.png"), tr("C&opy"), this); 528 | copyAction->setShortcut(tr("Ctrl+C")); 529 | copyAction->setStatusTip(tr("Copy items from diagram")); 530 | connect(copyAction, SIGNAL(triggered()), this, SLOT(copyItem())); 531 | 532 | pasteAction = new QAction(QIcon(":/images/paste.png"), tr("P&aste"), this); 533 | pasteAction->setShortcut(tr("Ctrl+V")); 534 | pasteAction->setStatusTip(tr("Paste items from copyboard to diagram")); 535 | connect(pasteAction, SIGNAL(triggered()), this, SLOT(pasteItem())); 536 | 537 | cutAction = new QAction(QIcon(":/images/cut.png"), tr("C&ut"), this); 538 | cutAction->setShortcut(tr("Ctrl+X")); 539 | cutAction->setStatusTip(tr("Cut items from diagram")); 540 | connect(cutAction, SIGNAL(triggered()), this, SLOT(cutItem())); 541 | 542 | undoAction = new QAction(QIcon(":images/undo.png"), tr("U&ndo"), this); 543 | undoAction->setShortcut(tr("Ctrl+Z")); 544 | undoAction->setStatusTip(tr("Undo last operation")); 545 | connect(undoAction, SIGNAL(triggered()), this, SLOT(undo())); 546 | 547 | redoAction = new QAction(QIcon(":images/redo.png"), tr("R&edo"), this); 548 | redoAction->setShortcut(tr("Ctrl+Shift+Z")); 549 | redoAction->setStatusTip(tr("Redo last operation")); 550 | connect(redoAction, SIGNAL(triggered()), this, SLOT(redo())); 551 | 552 | groupAction = new QAction(QIcon(":images/group.png"), tr("G&roup"), this); 553 | groupAction->setStatusTip(tr("Group graphic items ")); 554 | connect(groupAction, SIGNAL(triggered()), this, SLOT(groupItems())); 555 | 556 | ungroupAction = new QAction(QIcon(":images/ungroup.png"), tr("U&ngroup"), this); 557 | ungroupAction->setStatusTip(tr("Ungroup graphic items")); 558 | connect(ungroupAction, SIGNAL(triggered()), this, SLOT(ungroupItems())); 559 | } 560 | 561 | void MainWindow::createMenus() 562 | { 563 | fileMenu = menuBar()->addMenu(tr("&File")); 564 | fileMenu->addAction(exitAction); 565 | 566 | itemMenu = menuBar()->addMenu(tr("&Item")); 567 | itemMenu->addAction(copyAction); 568 | itemMenu->addAction(cutAction); 569 | itemMenu->addAction(pasteAction); 570 | itemMenu->addAction(deleteAction); 571 | itemMenu->addSeparator(); 572 | itemMenu->addAction(undoAction); 573 | itemMenu->addAction(redoAction); 574 | itemMenu->addSeparator(); 575 | itemMenu->addAction(groupAction); 576 | itemMenu->addAction(ungroupAction); 577 | itemMenu->addSeparator(); 578 | itemMenu->addAction(toFrontAction); 579 | itemMenu->addAction(sendBackAction); 580 | 581 | aboutMenu = menuBar()->addMenu(tr("&Help")); 582 | aboutMenu->addAction(aboutAction); 583 | } 584 | 585 | void MainWindow::createToolbars() 586 | { 587 | editToolBar = addToolBar(tr("Edit")); 588 | editToolBar->addAction(copyAction); 589 | editToolBar->addAction(cutAction); 590 | editToolBar->addAction(pasteAction); 591 | editToolBar->addAction(deleteAction); 592 | editToolBar->addAction(undoAction); 593 | editToolBar->addAction(redoAction); 594 | editToolBar->addAction(toFrontAction); 595 | editToolBar->addAction(sendBackAction); 596 | editToolBar->addAction(groupAction); 597 | editToolBar->addAction(ungroupAction); 598 | removeToolBar(editToolBar); 599 | addToolBar(Qt::LeftToolBarArea, editToolBar); 600 | editToolBar->show(); 601 | 602 | fontCombo = new QFontComboBox(); 603 | connect(fontCombo, SIGNAL(currentFontChanged(QFont)), 604 | this, SLOT(currentFontChanged(QFont))); 605 | fontCombo->setEditable(false); 606 | 607 | fontSizeCombo = new QComboBox; 608 | fontSizeCombo->setEditable(true); 609 | for (int i = 8; i < 30; i = i + 2) 610 | fontSizeCombo->addItem(QString().setNum(i)); 611 | QIntValidator *validator = new QIntValidator(2, 64, this); 612 | fontSizeCombo->setValidator(validator); 613 | connect(fontSizeCombo, SIGNAL(currentIndexChanged(QString)), 614 | this, SLOT(fontSizeChanged(QString))); 615 | 616 | fontColorToolButton = new QToolButton; 617 | fontColorToolButton->setPopupMode(QToolButton::MenuButtonPopup); 618 | fontColorToolButton->setMenu(createColorMenu(SLOT(textColorChanged()), Qt::black)); 619 | textAction = fontColorToolButton->menu()->defaultAction(); 620 | fontColorToolButton->setIcon(createColorToolButtonIcon(":/images/textpointer.png", Qt::black)); 621 | fontColorToolButton->setAutoFillBackground(true); 622 | connect(fontColorToolButton, SIGNAL(clicked()), 623 | this, SLOT(textButtonTriggered())); 624 | 625 | //! [26] 626 | fillColorToolButton = new QToolButton; 627 | fillColorToolButton->setPopupMode(QToolButton::MenuButtonPopup); 628 | fillColorToolButton->setMenu(createColorMenu(SLOT(itemColorChanged()), Qt::white)); 629 | fillAction = fillColorToolButton->menu()->defaultAction(); 630 | fillColorToolButton->setIcon(createColorToolButtonIcon( 631 | ":/images/floodfill.png", Qt::white)); 632 | connect(fillColorToolButton, SIGNAL(clicked()), 633 | this, SLOT(fillButtonTriggered())); 634 | //! [26] 635 | 636 | lineColorToolButton = new QToolButton; 637 | lineColorToolButton->setPopupMode(QToolButton::MenuButtonPopup); 638 | lineColorToolButton->setMenu(createColorMenu(SLOT(lineColorChanged()), Qt::black)); 639 | lineAction = lineColorToolButton->menu()->defaultAction(); 640 | lineColorToolButton->setIcon(createColorToolButtonIcon( 641 | ":/images/linecolor.png", Qt::black)); 642 | connect(lineColorToolButton, SIGNAL(clicked()), 643 | this, SLOT(lineButtonTriggered())); 644 | 645 | textToolBar = addToolBar(tr("Font")); 646 | textToolBar->addWidget(fontCombo); 647 | textToolBar->addWidget(fontSizeCombo); 648 | textToolBar->addAction(boldAction); 649 | textToolBar->addAction(italicAction); 650 | textToolBar->addAction(underlineAction); 651 | 652 | colorToolBar = addToolBar(tr("Color")); 653 | colorToolBar->addWidget(fontColorToolButton); 654 | colorToolBar->addWidget(fillColorToolButton); 655 | colorToolBar->addWidget(lineColorToolButton); 656 | 657 | QToolButton *pointerButton = new QToolButton; 658 | pointerButton->setCheckable(true); 659 | pointerButton->setChecked(true); 660 | pointerButton->setIcon(QIcon(":/images/pointer.png")); 661 | QToolButton *linePointerButton = new QToolButton; 662 | linePointerButton->setCheckable(true); 663 | linePointerButton->setIcon(QIcon(":/images/linepointer.png")); 664 | 665 | pointerTypeGroup = new QButtonGroup(this); 666 | pointerTypeGroup->addButton(pointerButton, int(DiagramScene::MoveItem)); 667 | pointerTypeGroup->addButton(linePointerButton, int(DiagramScene::InsertLine)); 668 | connect(pointerTypeGroup, SIGNAL(buttonClicked(int)), 669 | this, SLOT(pointerGroupClicked(int))); 670 | 671 | sceneScaleCombo = new QComboBox; 672 | QStringList scales; 673 | scales << tr("50") << tr("75") << tr("100") << tr("125") << tr("150"); 674 | sceneScaleCombo->addItems(scales); 675 | sceneScaleCombo->setCurrentIndex(2); 676 | sceneScaleCombo->setEditable(true); 677 | QIntValidator *scaleValidator = new QIntValidator(1, 200, this); 678 | sceneScaleCombo->setValidator(scaleValidator); 679 | connect(sceneScaleCombo, SIGNAL(currentTextChanged(QString)), 680 | this, SLOT(sceneScaleChanged(QString))); 681 | QLabel* percentLabel = new QLabel(tr("%"), this); 682 | 683 | pointerToolbar = addToolBar(tr("Pointer type")); 684 | pointerToolbar->addWidget(pointerButton); 685 | pointerToolbar->addWidget(linePointerButton); 686 | pointerToolbar->addWidget(sceneScaleCombo); 687 | pointerToolbar->addWidget(percentLabel); 688 | //! [27] 689 | } 690 | //! [27] 691 | 692 | //! [28] 693 | QWidget *MainWindow::createBackgroundCellWidget(const QString &text, const QString &image) 694 | { 695 | QToolButton *button = new QToolButton; 696 | button->setText(text); 697 | button->setIcon(QIcon(image)); 698 | button->setIconSize(QSize(50, 50)); 699 | button->setCheckable(true); 700 | backgroundButtonGroup->addButton(button); 701 | 702 | QGridLayout *layout = new QGridLayout; 703 | layout->addWidget(button, 0, 0, Qt::AlignHCenter); 704 | layout->addWidget(new QLabel(text), 1, 0, Qt::AlignCenter); 705 | 706 | QWidget *widget = new QWidget; 707 | widget->setLayout(layout); 708 | 709 | return widget; 710 | } 711 | //! [28] 712 | 713 | //! [29] 714 | QWidget *MainWindow::createCellWidget(const QString &text, DiagramItem::DiagramType type) 715 | { 716 | 717 | DiagramItem item(type, itemMenu); 718 | QIcon icon(item.image()); 719 | 720 | QToolButton *button = new QToolButton; 721 | button->setIcon(icon); 722 | button->setIconSize(QSize(50, 50)); 723 | button->setCheckable(true); 724 | buttonGroup->addButton(button, int(type)); 725 | 726 | QGridLayout *layout = new QGridLayout; 727 | layout->addWidget(button, 0, 0, Qt::AlignHCenter); 728 | layout->addWidget(new QLabel(text), 1, 0, Qt::AlignCenter); 729 | 730 | QWidget *widget = new QWidget; 731 | widget->setLayout(layout); 732 | 733 | return widget; 734 | } 735 | //! [29] 736 | 737 | //! [30] 738 | QMenu *MainWindow::createColorMenu(const char *slot, QColor defaultColor) 739 | { 740 | QList colors; 741 | colors << Qt::black << Qt::white << Qt::red << Qt::blue << Qt::yellow; 742 | QStringList names; 743 | names << tr("black") << tr("white") << tr("red") << tr("blue") 744 | << tr("yellow"); 745 | 746 | QMenu *colorMenu = new QMenu(this); 747 | for (int i = 0; i < colors.count(); ++i) { 748 | QAction *action = new QAction(names.at(i), this); 749 | action->setData(colors.at(i)); 750 | action->setIcon(createColorIcon(colors.at(i))); 751 | connect(action, SIGNAL(triggered()), this, slot); 752 | colorMenu->addAction(action); 753 | if (colors.at(i) == defaultColor) 754 | colorMenu->setDefaultAction(action); 755 | } 756 | return colorMenu; 757 | } 758 | //! [30] 759 | 760 | //! [31] 761 | QIcon MainWindow::createColorToolButtonIcon(const QString &imageFile, QColor color) 762 | { 763 | QPixmap pixmap(50, 80); 764 | pixmap.fill(Qt::transparent); 765 | QPainter painter(&pixmap); 766 | QPixmap image(imageFile); 767 | // Draw icon centred horizontally on button. 768 | QRect target(4, 0, 42, 43); 769 | QRect source(0, 0, 42, 43); 770 | painter.fillRect(QRect(0, 60, 50, 80), color); 771 | painter.drawPixmap(target, image, source); 772 | 773 | return QIcon(pixmap); 774 | } 775 | //! [31] 776 | 777 | //! [32] 778 | QIcon MainWindow::createColorIcon(QColor color) 779 | { 780 | QPixmap pixmap(20, 20); 781 | QPainter painter(&pixmap); 782 | painter.setPen(Qt::NoPen); 783 | painter.fillRect(QRect(0, 0, 20, 20), color); 784 | 785 | return QIcon(pixmap); 786 | } 787 | 788 | QList MainWindow::cloneItems(const QList& items) { 789 | QHash copyMap; 790 | foreach (QGraphicsItem* item, items) { 791 | if (item->type() == DiagramItem::Type) { 792 | copyMap[item] = qgraphicsitem_cast(item)->clone(); 793 | } else if (item->type() == DiagramTextItem::Type) { 794 | copyMap[item] = qgraphicsitem_cast(item)->clone(); 795 | } 796 | } 797 | 798 | // connect DiagramItem with new arrow 799 | foreach (QGraphicsItem* item, items) { 800 | if (item->type() == Arrow::Type) { 801 | Arrow* arrow = qgraphicsitem_cast(item); 802 | DiagramItem* copiedStartItem = 803 | qgraphicsitem_cast(copyMap.value(arrow->startItem(), nullptr)); 804 | DiagramItem* copiedEndItem = 805 | qgraphicsitem_cast(copyMap.value(arrow->endItem(), nullptr)); 806 | 807 | if (copiedStartItem == nullptr || copiedEndItem == nullptr) continue; 808 | 809 | Arrow* newArrow = new Arrow(copiedStartItem, copiedEndItem, nullptr); 810 | newArrow->setColor(arrow->getColor()); 811 | 812 | copiedStartItem->addArrow(newArrow); 813 | copiedEndItem->addArrow(newArrow); 814 | newArrow->setZValue(-1000.0); 815 | 816 | copyMap[item] = newArrow; 817 | } 818 | } 819 | return copyMap.values(); 820 | } 821 | //! [32] 822 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef MAINWINDOW_H 52 | #define MAINWINDOW_H 53 | 54 | #include "diagramitem.h" 55 | 56 | #include 57 | #include "diagramview.h" 58 | #include 59 | #include "undosystem.h" 60 | 61 | class DiagramScene; 62 | 63 | QT_BEGIN_NAMESPACE 64 | class QAction; 65 | class QToolBox; 66 | class QSpinBox; 67 | class QComboBox; 68 | class QFontComboBox; 69 | class QButtonGroup; 70 | class QLineEdit; 71 | class QGraphicsTextItem; 72 | class QFont; 73 | class QToolButton; 74 | class QAbstractButton; 75 | class QGraphicsView; 76 | QT_END_NAMESPACE 77 | 78 | //! [0] 79 | class MainWindow : public QMainWindow 80 | { 81 | Q_OBJECT 82 | 83 | public: 84 | MainWindow(); 85 | 86 | private slots: 87 | void backgroundButtonGroupClicked(QAbstractButton *button); 88 | void buttonGroupClicked(int id); 89 | void copyItem(); 90 | void pasteItem(); 91 | void cutItem(); 92 | void deleteItem(); 93 | void undo(); 94 | void redo(); 95 | void groupItems(); 96 | void ungroupItems(); 97 | void pointerGroupClicked(int id); 98 | void bringToFront(); 99 | void sendToBack(); 100 | void itemInserted(DiagramItem *item); 101 | void textInserted(QGraphicsTextItem *item); 102 | void backupUndostack(); 103 | void currentFontChanged(const QFont &font); 104 | void fontSizeChanged(const QString &size); 105 | void sceneScaleChanged(const QString &scale); 106 | void sceneScaleZooming(int delta); 107 | 108 | void textColorChanged(); 109 | void itemColorChanged(); 110 | void lineColorChanged(); 111 | void textButtonTriggered(); 112 | void fillButtonTriggered(); 113 | void lineButtonTriggered(); 114 | void handleFontChange(); 115 | void itemSelected(QGraphicsItem *item); 116 | void about(); 117 | 118 | private: 119 | void createToolBox(); 120 | void createActions(); 121 | void createMenus(); 122 | void createToolbars(); 123 | QWidget *createBackgroundCellWidget(const QString &text, 124 | const QString &image); 125 | QWidget *createCellWidget(const QString &text, 126 | DiagramItem::DiagramType type); 127 | QMenu *createColorMenu(const char *slot, QColor defaultColor); 128 | QIcon createColorToolButtonIcon(const QString &image, QColor color); 129 | QIcon createColorIcon(QColor color); 130 | 131 | QList cloneItems(QList const& items); 132 | 133 | DiagramScene *scene; 134 | QGraphicsView *view; 135 | 136 | QList pasteBoard; 137 | UndoSystem undoStack; 138 | 139 | QAction *exitAction; 140 | QAction *addAction; 141 | QAction *deleteAction; 142 | QAction *copyAction; 143 | QAction *pasteAction; 144 | QAction *cutAction; 145 | QAction *undoAction; 146 | QAction *redoAction; 147 | 148 | QAction *toFrontAction; 149 | QAction *sendBackAction; 150 | QAction *groupAction; 151 | QAction *ungroupAction; 152 | 153 | QAction *aboutAction; 154 | 155 | QMenu *fileMenu; 156 | QMenu *itemMenu; 157 | QMenu *aboutMenu; 158 | 159 | QToolBar *textToolBar; 160 | QToolBar *editToolBar; 161 | QToolBar *colorToolBar; 162 | QToolBar *pointerToolbar; 163 | 164 | QComboBox *sceneScaleCombo; 165 | QComboBox *itemColorCombo; 166 | QComboBox *textColorCombo; 167 | QComboBox *fontSizeCombo; 168 | QFontComboBox *fontCombo; 169 | 170 | QToolBox *toolBox; 171 | QButtonGroup *buttonGroup; 172 | QButtonGroup *pointerTypeGroup; 173 | QButtonGroup *backgroundButtonGroup; 174 | QToolButton *fontColorToolButton; 175 | QToolButton *fillColorToolButton; 176 | QToolButton *lineColorToolButton; 177 | QAction *boldAction; 178 | QAction *underlineAction; 179 | QAction *italicAction; 180 | QAction *textAction; 181 | QAction *fillAction; 182 | QAction *lineAction; 183 | }; 184 | //! [0] 185 | 186 | #endif // MAINWINDOW_H 187 | -------------------------------------------------------------------------------- /screenshots/screenshot-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangenta/Qt-DrawingTool/f601897967dbb8c1eae92304c2cab956fe092199/screenshots/screenshot-001.png -------------------------------------------------------------------------------- /undosystem.cpp: -------------------------------------------------------------------------------- 1 | #include "undosystem.h" 2 | #include 3 | 4 | void UndoSystem::backup(const QList&& items) { 5 | qDebug() << "inside backup." << items.size(); 6 | int stackSize = itemsStack.length(); 7 | if (currentIndex < stackSize - 1) { 8 | for (int i = currentIndex + 1; i < stackSize; ++i) { 9 | free(itemsStack[i]); 10 | } 11 | itemsStack.erase(itemsStack.begin() + currentIndex + 1, itemsStack.end()); 12 | } 13 | 14 | itemsStack.push_back(items); 15 | currentIndex++; 16 | } 17 | 18 | QList UndoSystem::undo() { 19 | return itemsStack[--currentIndex]; 20 | } 21 | 22 | QList UndoSystem::redo() { 23 | return itemsStack[++currentIndex]; 24 | } 25 | 26 | void UndoSystem::free(QList const& items) { 27 | foreach(QGraphicsItem* p, items) { 28 | delete p; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /undosystem.h: -------------------------------------------------------------------------------- 1 | #ifndef UNDOSYSTEM_H 2 | #define UNDOSYSTEM_H 3 | #include "diagramitem.h" 4 | #include "diagramscene.h" 5 | 6 | class UndoSystem { 7 | public: 8 | void backup(QList const&& items); 9 | QList undo(); 10 | QList redo(); 11 | bool isEmpty() {return currentIndex < 1;} 12 | bool isFull() {return currentIndex + 1 == itemsStack.length();} 13 | 14 | private: 15 | void free(QList const& items); 16 | QList> itemsStack; 17 | int currentIndex = -1; 18 | }; 19 | 20 | #endif // UNDOSYSTEM_H 21 | --------------------------------------------------------------------------------