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 "graphwidget.h" |
52 | #include "edge.h" |
53 | #include "node.h" |
54 | |
55 | #include <math.h> |
56 | |
57 | #include <QKeyEvent> |
58 | #include <QRandomGenerator> |
59 | |
60 | //! [0] |
61 | GraphWidget::GraphWidget(QWidget *parent) |
62 | : QGraphicsView(parent) |
63 | { |
64 | QGraphicsScene *scene = new QGraphicsScene(this); |
65 | scene->setItemIndexMethod(QGraphicsScene::NoIndex); |
66 | scene->setSceneRect(x: -200, y: -200, w: 400, h: 400); |
67 | setScene(scene); |
68 | setCacheMode(CacheBackground); |
69 | setViewportUpdateMode(BoundingRectViewportUpdate); |
70 | setRenderHint(hint: QPainter::Antialiasing); |
71 | setTransformationAnchor(AnchorUnderMouse); |
72 | scale(sx: qreal(0.8), sy: qreal(0.8)); |
73 | setMinimumSize(minw: 400, minh: 400); |
74 | setWindowTitle(tr(s: "Elastic Nodes" )); |
75 | //! [0] |
76 | |
77 | //! [1] |
78 | Node *node1 = new Node(this); |
79 | Node *node2 = new Node(this); |
80 | Node *node3 = new Node(this); |
81 | Node *node4 = new Node(this); |
82 | centerNode = new Node(this); |
83 | Node *node6 = new Node(this); |
84 | Node *node7 = new Node(this); |
85 | Node *node8 = new Node(this); |
86 | Node *node9 = new Node(this); |
87 | scene->addItem(item: node1); |
88 | scene->addItem(item: node2); |
89 | scene->addItem(item: node3); |
90 | scene->addItem(item: node4); |
91 | scene->addItem(item: centerNode); |
92 | scene->addItem(item: node6); |
93 | scene->addItem(item: node7); |
94 | scene->addItem(item: node8); |
95 | scene->addItem(item: node9); |
96 | scene->addItem(item: new Edge(node1, node2)); |
97 | scene->addItem(item: new Edge(node2, node3)); |
98 | scene->addItem(item: new Edge(node2, centerNode)); |
99 | scene->addItem(item: new Edge(node3, node6)); |
100 | scene->addItem(item: new Edge(node4, node1)); |
101 | scene->addItem(item: new Edge(node4, centerNode)); |
102 | scene->addItem(item: new Edge(centerNode, node6)); |
103 | scene->addItem(item: new Edge(centerNode, node8)); |
104 | scene->addItem(item: new Edge(node6, node9)); |
105 | scene->addItem(item: new Edge(node7, node4)); |
106 | scene->addItem(item: new Edge(node8, node7)); |
107 | scene->addItem(item: new Edge(node9, node8)); |
108 | |
109 | node1->setPos(ax: -50, ay: -50); |
110 | node2->setPos(ax: 0, ay: -50); |
111 | node3->setPos(ax: 50, ay: -50); |
112 | node4->setPos(ax: -50, ay: 0); |
113 | centerNode->setPos(ax: 0, ay: 0); |
114 | node6->setPos(ax: 50, ay: 0); |
115 | node7->setPos(ax: -50, ay: 50); |
116 | node8->setPos(ax: 0, ay: 50); |
117 | node9->setPos(ax: 50, ay: 50); |
118 | } |
119 | //! [1] |
120 | |
121 | //! [2] |
122 | void GraphWidget::itemMoved() |
123 | { |
124 | if (!timerId) |
125 | timerId = startTimer(interval: 1000 / 25); |
126 | } |
127 | //! [2] |
128 | |
129 | //! [3] |
130 | void GraphWidget::keyPressEvent(QKeyEvent *event) |
131 | { |
132 | switch (event->key()) { |
133 | case Qt::Key_Up: |
134 | centerNode->moveBy(dx: 0, dy: -20); |
135 | break; |
136 | case Qt::Key_Down: |
137 | centerNode->moveBy(dx: 0, dy: 20); |
138 | break; |
139 | case Qt::Key_Left: |
140 | centerNode->moveBy(dx: -20, dy: 0); |
141 | break; |
142 | case Qt::Key_Right: |
143 | centerNode->moveBy(dx: 20, dy: 0); |
144 | break; |
145 | case Qt::Key_Plus: |
146 | zoomIn(); |
147 | break; |
148 | case Qt::Key_Minus: |
149 | zoomOut(); |
150 | break; |
151 | case Qt::Key_Space: |
152 | case Qt::Key_Enter: |
153 | shuffle(); |
154 | break; |
155 | default: |
156 | QGraphicsView::keyPressEvent(event); |
157 | } |
158 | } |
159 | //! [3] |
160 | |
161 | //! [4] |
162 | void GraphWidget::timerEvent(QTimerEvent *event) |
163 | { |
164 | Q_UNUSED(event); |
165 | |
166 | QVector<Node *> nodes; |
167 | const QList<QGraphicsItem *> items = scene()->items(); |
168 | for (QGraphicsItem *item : items) { |
169 | if (Node *node = qgraphicsitem_cast<Node *>(item)) |
170 | nodes << node; |
171 | } |
172 | |
173 | for (Node *node : qAsConst(t&: nodes)) |
174 | node->calculateForces(); |
175 | |
176 | bool itemsMoved = false; |
177 | for (Node *node : qAsConst(t&: nodes)) { |
178 | if (node->advancePosition()) |
179 | itemsMoved = true; |
180 | } |
181 | |
182 | if (!itemsMoved) { |
183 | killTimer(id: timerId); |
184 | timerId = 0; |
185 | } |
186 | } |
187 | //! [4] |
188 | |
189 | #if QT_CONFIG(wheelevent) |
190 | //! [5] |
191 | void GraphWidget::wheelEvent(QWheelEvent *event) |
192 | { |
193 | scaleView(scaleFactor: pow(x: 2., y: -event->angleDelta().y() / 240.0)); |
194 | } |
195 | //! [5] |
196 | #endif |
197 | |
198 | //! [6] |
199 | void GraphWidget::drawBackground(QPainter *painter, const QRectF &rect) |
200 | { |
201 | Q_UNUSED(rect); |
202 | |
203 | // Shadow |
204 | QRectF sceneRect = this->sceneRect(); |
205 | QRectF rightShadow(sceneRect.right(), sceneRect.top() + 5, 5, sceneRect.height()); |
206 | QRectF bottomShadow(sceneRect.left() + 5, sceneRect.bottom(), sceneRect.width(), 5); |
207 | if (rightShadow.intersects(r: rect) || rightShadow.contains(r: rect)) |
208 | painter->fillRect(r: rightShadow, c: Qt::darkGray); |
209 | if (bottomShadow.intersects(r: rect) || bottomShadow.contains(r: rect)) |
210 | painter->fillRect(r: bottomShadow, c: Qt::darkGray); |
211 | |
212 | // Fill |
213 | QLinearGradient gradient(sceneRect.topLeft(), sceneRect.bottomRight()); |
214 | gradient.setColorAt(pos: 0, color: Qt::white); |
215 | gradient.setColorAt(pos: 1, color: Qt::lightGray); |
216 | painter->fillRect(rect.intersected(r: sceneRect), gradient); |
217 | painter->setBrush(Qt::NoBrush); |
218 | painter->drawRect(rect: sceneRect); |
219 | |
220 | // Text |
221 | QRectF textRect(sceneRect.left() + 4, sceneRect.top() + 4, |
222 | sceneRect.width() - 4, sceneRect.height() - 4); |
223 | QString message(tr(s: "Click and drag the nodes around, and zoom with the mouse " |
224 | "wheel or the '+' and '-' keys" )); |
225 | |
226 | QFont font = painter->font(); |
227 | font.setBold(true); |
228 | font.setPointSize(14); |
229 | painter->setFont(font); |
230 | painter->setPen(Qt::lightGray); |
231 | painter->drawText(r: textRect.translated(dx: 2, dy: 2), text: message); |
232 | painter->setPen(Qt::black); |
233 | painter->drawText(r: textRect, text: message); |
234 | } |
235 | //! [6] |
236 | |
237 | //! [7] |
238 | void GraphWidget::scaleView(qreal scaleFactor) |
239 | { |
240 | qreal factor = transform().scale(sx: scaleFactor, sy: scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width(); |
241 | if (factor < 0.07 || factor > 100) |
242 | return; |
243 | |
244 | scale(sx: scaleFactor, sy: scaleFactor); |
245 | } |
246 | //! [7] |
247 | |
248 | void GraphWidget::shuffle() |
249 | { |
250 | const QList<QGraphicsItem *> items = scene()->items(); |
251 | for (QGraphicsItem *item : items) { |
252 | if (qgraphicsitem_cast<Node *>(item)) |
253 | item->setPos(ax: -150 + QRandomGenerator::global()->bounded(highest: 300), ay: -150 + QRandomGenerator::global()->bounded(highest: 300)); |
254 | } |
255 | } |
256 | |
257 | void GraphWidget::zoomIn() |
258 | { |
259 | scaleView(scaleFactor: qreal(1.2)); |
260 | } |
261 | |
262 | void GraphWidget::zoomOut() |
263 | { |
264 | scaleView(scaleFactor: 1 / qreal(1.2)); |
265 | } |
266 | |