[2] | 1 | /****************************************************************************
|
---|
| 2 | **
|
---|
[846] | 3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
---|
[561] | 4 | ** All rights reserved.
|
---|
| 5 | ** Contact: Nokia Corporation (qt-info@nokia.com)
|
---|
[2] | 6 | **
|
---|
| 7 | ** This file is part of the documentation of the Qt Toolkit.
|
---|
| 8 | **
|
---|
[846] | 9 | ** $QT_BEGIN_LICENSE:FDL$
|
---|
[2] | 10 | ** Commercial Usage
|
---|
| 11 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
| 12 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
[846] | 13 | ** Software or, alternatively, in accordance with the terms contained in a
|
---|
| 14 | ** written agreement between you and Nokia.
|
---|
[2] | 15 | **
|
---|
[846] | 16 | ** GNU Free Documentation License
|
---|
| 17 | ** Alternatively, this file may be used under the terms of the GNU Free
|
---|
| 18 | ** Documentation License version 1.3 as published by the Free Software
|
---|
| 19 | ** Foundation and appearing in the file included in the packaging of this
|
---|
| 20 | ** file.
|
---|
[2] | 21 | **
|
---|
[561] | 22 | ** If you have questions regarding the use of this file, please contact
|
---|
| 23 | ** Nokia at qt-info@nokia.com.
|
---|
[2] | 24 | ** $QT_END_LICENSE$
|
---|
| 25 | **
|
---|
| 26 | ****************************************************************************/
|
---|
| 27 |
|
---|
| 28 | /*!
|
---|
| 29 | \example graphicsview/elasticnodes
|
---|
| 30 | \title Elastic Nodes Example
|
---|
| 31 |
|
---|
[846] | 32 | This GraphicsView example shows how to implement edges between nodes in a
|
---|
| 33 | graph, with basic interaction. You can click to drag a node around, and
|
---|
| 34 | zoom in and out using the mouse wheel or the keyboard. Hitting the space
|
---|
| 35 | bar will randomize the nodes. The example is also resolution independent;
|
---|
| 36 | as you zoom in, the graphics remain crisp.
|
---|
[2] | 37 |
|
---|
| 38 | \image elasticnodes-example.png
|
---|
[846] | 39 |
|
---|
| 40 | Graphics View provides the QGraphicsScene class for managing and
|
---|
| 41 | interacting with a large number of custom-made 2D graphical items derived
|
---|
| 42 | from the QGraphicsItem class, and a QGraphicsView widget for visualizing
|
---|
| 43 | the items, with support for zooming and rotation.
|
---|
| 44 |
|
---|
| 45 | This example consists of a \c Node class, an \c Edge class, a \c
|
---|
| 46 | GraphWidget test, and a \c main function: the \c Node class represents
|
---|
| 47 | draggable yellow nodes in a grid, the \c Edge class represents the lines
|
---|
| 48 | between the nodes, the \c GraphWidget class represents the application
|
---|
| 49 | window, and the \c main() function creates and shows this window, and runs
|
---|
| 50 | the event loop.
|
---|
| 51 |
|
---|
| 52 | \section1 Node Class Definition
|
---|
| 53 |
|
---|
| 54 | The \c Node class serves three purposes:
|
---|
| 55 |
|
---|
| 56 | \list
|
---|
| 57 | \o Painting a yellow gradient "ball" in two states: sunken and raised.
|
---|
| 58 | \o Managing connections to other nodes.
|
---|
| 59 | \o Calculating forces pulling and pushing the nodes in the grid.
|
---|
| 60 | \endlist
|
---|
| 61 |
|
---|
| 62 | Let's start by looking at the \c Node class declaration.
|
---|
| 63 |
|
---|
| 64 | \snippet examples/graphicsview/elasticnodes/node.h 0
|
---|
| 65 |
|
---|
| 66 | The \c Node class inherits QGraphicsItem, and reimplements the two
|
---|
| 67 | mandatory functions \l{QGraphicsItem::boundingRect()}{boundingRect()} and
|
---|
| 68 | \l{QGraphicsItem::paint()}{paint()} to provide its visual appearance. It
|
---|
| 69 | also reimplements \l{QGraphicsItem::shape()}{shape()} to ensure its hit
|
---|
| 70 | area has an elliptic shape (as opposed to the default bounding rectangle).
|
---|
| 71 |
|
---|
| 72 | For edge management purposes, the node provides a simple API for adding
|
---|
| 73 | edges to a node, and for listing all connected edges.
|
---|
| 74 |
|
---|
| 75 | The \l{QGraphicsItem::advance()}{advance()} reimplementation is called
|
---|
| 76 | whenever the scene's state advances by one step. The calculateForces()
|
---|
| 77 | function is called to calculate the forces that push and pull on this node
|
---|
| 78 | and its neighbors.
|
---|
| 79 |
|
---|
| 80 | The \c Node class also reimplements
|
---|
| 81 | \l{QGraphicsItem::itemChange()}{itemChange()} to react to state changes (in
|
---|
| 82 | this case, position changes), and
|
---|
| 83 | \l{QGraphicsItem::mousePressEvent()}{mousePressEvent()} and
|
---|
| 84 | \l{QGraphicsItem::mouseReleaseEvent()}{mouseReleaseEvent()} to update the
|
---|
| 85 | item's visual appearance.
|
---|
| 86 |
|
---|
| 87 | We will start reviewing the \c Node implementation by looking at its
|
---|
| 88 | constructor:
|
---|
| 89 |
|
---|
| 90 | \snippet examples/graphicsview/elasticnodes/node.cpp 0
|
---|
| 91 |
|
---|
| 92 | In the constructor, we set the
|
---|
| 93 | \l{QGraphicsItem::ItemIsMovable}{ItemIsMovable} flag to allow the item to
|
---|
| 94 | move in response to mouse dragging, and
|
---|
| 95 | \l{QGraphicsItem::ItemSendsGeometryChanges}{ItemSendsGeometryChanges} to
|
---|
| 96 | enable \l{QGraphicsItem::itemChange()}{itemChange()} notifications for
|
---|
| 97 | position and transformation changes. We also enable
|
---|
| 98 | \l{QGraphicsItem::DeviceCoordinateCache}{DeviceCoordinateCache} to speed up
|
---|
| 99 | rendering performance. To ensure that the nodes are always stacked on top
|
---|
| 100 | of edges, we finally set the item's Z value to -1.
|
---|
| 101 |
|
---|
| 102 | \c Node's constructor takes a \c GraphWidget pointer and stores this as a
|
---|
| 103 | member variable. We will revisit this pointer later on.
|
---|
| 104 |
|
---|
| 105 | \snippet examples/graphicsview/elasticnodes/node.cpp 1
|
---|
| 106 |
|
---|
| 107 | The addEdge() function adds the input edge to a list of attached edges. The
|
---|
| 108 | edge is then adjusted so that the end points for the edge match the
|
---|
| 109 | positions of the source and destination nodes.
|
---|
| 110 |
|
---|
| 111 | The edges() function simply returns the list of attached edges.
|
---|
| 112 |
|
---|
| 113 | \snippet examples/graphicsview/elasticnodes/node.cpp 2
|
---|
| 114 |
|
---|
| 115 | There are two ways to move a node. The \c calculateForces() function
|
---|
| 116 | implements the elastic effect that pulls and pushes on nodes in the grid.
|
---|
| 117 | In addition, the user can directly move one node around with the mouse.
|
---|
| 118 | Because we do not want the two approaches to operate at the same time on
|
---|
| 119 | the same node, we start \c calculateForces() by checking if this \c Node is
|
---|
| 120 | the current mouse grabber item (i.e., QGraphicsScene::mouseGrabberItem()).
|
---|
| 121 | Because we need to find all neighboring (but not necessarily connected)
|
---|
| 122 | nodes, we also make sure the item is part of a scene in the first place.
|
---|
| 123 |
|
---|
| 124 | \snippet examples/graphicsview/elasticnodes/node.cpp 3
|
---|
| 125 |
|
---|
| 126 | The "elastic" effect comes from an algorithm that applies pushing and
|
---|
| 127 | pulling forces. The effect is impressive, and surprisingly simple to
|
---|
| 128 | implement.
|
---|
| 129 |
|
---|
| 130 | The algorithm has two steps: the first is to calculate the forces that push
|
---|
| 131 | the nodes apart, and the second is to subtract the forces that pull the
|
---|
| 132 | nodes together. First we need to find all the nodes in the graph. We call
|
---|
| 133 | QGraphicsScene::items() to find all items in the scene, and then use
|
---|
| 134 | qgraphicsitem_cast() to look for \c Node instances.
|
---|
| 135 |
|
---|
| 136 | We make use of \l{QGraphicsItem::mapFromItem()}{mapFromItem()} to create a
|
---|
| 137 | temporary vector pointing from this node to each other node, in \l{The
|
---|
| 138 | Graphics View Coordinate System}{local coordinates}. We use the decomposed
|
---|
| 139 | components of this vector to determine the direction and strength of force
|
---|
| 140 | that should apply to the node. The forces accumulate for each node, and are
|
---|
| 141 | then adjusted so that the closest nodes are given the strongest force, with
|
---|
| 142 | rapid degradation when distance increases. The sum of all forces is stored
|
---|
| 143 | in \c xvel (X-velocity) and \c yvel (Y-velocity).
|
---|
| 144 |
|
---|
| 145 | \snippet examples/graphicsview/elasticnodes/node.cpp 4
|
---|
| 146 |
|
---|
| 147 | The edges between the nodes represent forces that pull the nodes together.
|
---|
| 148 | By visiting each edge that is connected to this node, we can use a similar
|
---|
| 149 | approach as above to find the direction and strength of all pulling forces.
|
---|
| 150 | These forces are subtracted from \c xvel and \c yvel.
|
---|
| 151 |
|
---|
| 152 | \snippet examples/graphicsview/elasticnodes/node.cpp 5
|
---|
| 153 |
|
---|
| 154 | In theory, the sum of pushing and pulling forces should stabilize to
|
---|
| 155 | precisely 0. In practise, however, they never do. To circumvent errors in
|
---|
| 156 | numerical precision, we simply force the sum of forces to be 0 when they
|
---|
| 157 | are less than 0.1.
|
---|
| 158 |
|
---|
| 159 | \snippet examples/graphicsview/elasticnodes/node.cpp 6
|
---|
| 160 |
|
---|
| 161 | The final step of \c calculateForces() determines the node's new position.
|
---|
| 162 | We add the force to the node's current position. We also make sure the new
|
---|
| 163 | position stays inside of our defined boundaries. We don't actually move the
|
---|
| 164 | item in this function; that's done in a separate step, from \c advance().
|
---|
| 165 |
|
---|
| 166 | \snippet examples/graphicsview/elasticnodes/node.cpp 7
|
---|
| 167 |
|
---|
| 168 | The \c advance() function updates the item's current position. It is called
|
---|
| 169 | from \c GraphWidget::timerEvent(). If the node's position changed, the
|
---|
| 170 | function returns true; otherwise false is returned.
|
---|
| 171 |
|
---|
| 172 | \snippet examples/graphicsview/elasticnodes/node.cpp 8
|
---|
| 173 |
|
---|
| 174 | The \c Node's bounding rectangle is a 20x20 sized rectangle centered around
|
---|
| 175 | its origin (0, 0), adjusted by 2 units in all directions to compensate for
|
---|
| 176 | the node's outline stroke, and by 3 units down and to the right to make
|
---|
| 177 | room for a simple drop shadow.
|
---|
| 178 |
|
---|
| 179 | \snippet examples/graphicsview/elasticnodes/node.cpp 9
|
---|
| 180 |
|
---|
| 181 | The shape is a simple ellipse. This ensures that you must click inside the
|
---|
| 182 | node's elliptic shape in order to drag it around. You can test this effect
|
---|
| 183 | by running the example, and zooming far in so that the nodes are very
|
---|
| 184 | large. Without reimplementing \l{QGraphicsItem::shape()}{shape()}, the
|
---|
| 185 | item's hit area would be identical to its bounding rectangle (i.e.,
|
---|
| 186 | rectangular).
|
---|
| 187 |
|
---|
| 188 | \snippet examples/graphicsview/elasticnodes/node.cpp 10
|
---|
| 189 |
|
---|
| 190 | This function implements the node's painting. We start by drawing a simple
|
---|
| 191 | dark gray elliptic drop shadow at (-7, -7), that is, (3, 3) units down and
|
---|
| 192 | to the right from the top-left corner (-10, -10) of the ellipse.
|
---|
| 193 |
|
---|
| 194 | We then draw an ellipse with a radial gradient fill. This fill is either
|
---|
| 195 | Qt::yellow to Qt::darkYellow when raised, or the opposite when sunken. In
|
---|
| 196 | sunken state we also shift the center and focal point by (3, 3) to
|
---|
| 197 | emphasize the impression that something has been pushed down.
|
---|
| 198 |
|
---|
| 199 | Drawing filled ellipses with gradients can be quite slow, especially when
|
---|
| 200 | using complex gradients such as QRadialGradient. This is why this example
|
---|
| 201 | uses \l{QGraphicsItem::DeviceCoordinateCache}{DeviceCoordinateCache}, a
|
---|
| 202 | simple yet effective measure that prevents unnecessary redrawing.
|
---|
| 203 |
|
---|
| 204 | \snippet examples/graphicsview/elasticnodes/node.cpp 11
|
---|
| 205 |
|
---|
| 206 | We reimplement \l{QGraphicsItem::itemChange()}{itemChange()} to adjust the
|
---|
| 207 | position of all connected edges, and to notify the scene that an item has
|
---|
| 208 | moved (i.e., "something has happened"). This will trigger new force
|
---|
| 209 | calculations.
|
---|
| 210 |
|
---|
| 211 | This notification is the only reason why the nodes need to keep a pointer
|
---|
| 212 | back to the \c GraphWidget. Another approach could be to provide such
|
---|
| 213 | notification using a signal; in such case, \c Node would need to inherit
|
---|
| 214 | from QGraphicsObject.
|
---|
| 215 |
|
---|
| 216 | \snippet examples/graphicsview/elasticnodes/node.cpp 12
|
---|
| 217 |
|
---|
| 218 | Because we have set the \l{QGraphicsItem::ItemIsMovable}{ItemIsMovable}
|
---|
| 219 | flag, we don't need to implement the logic that moves the node according to
|
---|
| 220 | mouse input; this is already provided for us. We still need to reimplement
|
---|
| 221 | the mouse press and release handlers, though, to update the nodes' visual
|
---|
| 222 | appearance (i.e., sunken or raised).
|
---|
| 223 |
|
---|
| 224 | \section1 Edge Class Definition
|
---|
| 225 |
|
---|
| 226 | The \c Edge class represents the arrow-lines between the nodes in this
|
---|
| 227 | example. The class is very simple: it maintains a source- and destination
|
---|
| 228 | node pointer, and provides an \c adjust() function that makes sure the line
|
---|
| 229 | starts at the position of the source, and ends at the position of the
|
---|
| 230 | destination. The edges are the only items that change continuously as
|
---|
| 231 | forces pull and push on the nodes.
|
---|
| 232 |
|
---|
| 233 | Let's take a look at the class declaration:
|
---|
| 234 |
|
---|
| 235 | \snippet examples/graphicsview/elasticnodes/edge.h 0
|
---|
| 236 |
|
---|
| 237 | \c Edge inherits from QGraphicsItem, as it's a simple class that has no use
|
---|
| 238 | for signals, slots, and properties (compare to QGraphicsObject).
|
---|
| 239 |
|
---|
| 240 | The constructor takes two node pointers as input. Both pointers are
|
---|
| 241 | mandatory in this example. We also provide get-functions for each node.
|
---|
| 242 |
|
---|
| 243 | The \c adjust() function repositions the edge, and the item also implements
|
---|
| 244 | \l{QGraphicsItem::boundingRect()}{boundingRect()} and
|
---|
| 245 | \{QGraphicsItem::paint()}{paint()}.
|
---|
| 246 |
|
---|
| 247 | We will now review its implementation.
|
---|
| 248 |
|
---|
| 249 | \snippet examples/graphicsview/elasticnodes/edge.cpp 0
|
---|
| 250 |
|
---|
| 251 | The \c Edge constructor initializes its \c arrowSize data member to 10 units;
|
---|
| 252 | this determines the size of the arrow which is drawn in
|
---|
| 253 | \l{QGraphicsItem::paint()}{paint()}.
|
---|
| 254 |
|
---|
| 255 | In the constructor body, we call
|
---|
| 256 | \l{QGraphicsItem::setAcceptedMouseButtons()}{setAcceptedMouseButtons(0)}.
|
---|
| 257 | This ensures that the edge items are not considered for mouse input at all
|
---|
| 258 | (i.e., you cannot click the edges). Then, the source and destination
|
---|
| 259 | pointers are updated, this edge is registered with each node, and we call
|
---|
| 260 | \c adjust() to update this edge's start end end position.
|
---|
| 261 |
|
---|
| 262 | \snippet examples/graphicsview/elasticnodes/edge.cpp 1
|
---|
| 263 |
|
---|
| 264 | The source and destination get-functions simply return the respective
|
---|
| 265 | pointers.
|
---|
| 266 |
|
---|
| 267 | \snippet examples/graphicsview/elasticnodes/edge.cpp 2
|
---|
| 268 |
|
---|
| 269 | In \c adjust(), we define two points: \c sourcePoint, and \c destPoint,
|
---|
| 270 | pointing at the source and destination nodes' origins respectively. Each
|
---|
| 271 | point is calculated using \l{The Graphics View Coordinate System}{local
|
---|
| 272 | coordinates}.
|
---|
| 273 |
|
---|
| 274 | We want the tip of the edge's arrows to point to the exact outline of the
|
---|
| 275 | nodes, as opposed to the center of the nodes. To find this point, we first
|
---|
| 276 | decompose the vector pointing from the center of the source to the center
|
---|
| 277 | of the destination node into X and Y, and then normalize the components by
|
---|
| 278 | dividing by the length of the vector. This gives us an X and Y unit delta
|
---|
| 279 | that, when multiplied by the radius of the node (which is 10), gives us the
|
---|
| 280 | offset that must be added to one point of the edge, and subtracted from the
|
---|
| 281 | other.
|
---|
| 282 |
|
---|
| 283 | If the length of the vector is less than 20 (i.e., if two nodes overlap),
|
---|
| 284 | then we fix the source and destination pointer at the center of the source
|
---|
| 285 | node. In practise this case is very hard to reproduce manually, as the
|
---|
| 286 | forces between the two nodes is then at its maximum.
|
---|
| 287 |
|
---|
| 288 | It's important to notice that we call
|
---|
| 289 | \l{QGraphicsItem::prepareGeometryChange()}{prepareGeometryChange()} in this
|
---|
| 290 | function. The reason is that the variables \c sourcePoint and \c destPoint
|
---|
| 291 | are used directly when painting, and they are returned from the
|
---|
| 292 | \l{QGraphicsItem::boundingRect()}{boundingRect()} reimplementation. We must
|
---|
| 293 | always call
|
---|
| 294 | \l{QGraphicsItem::prepareGeometryChange()}{prepareGeometryChange()} before
|
---|
| 295 | changing what \l{QGraphicsItem::boundingRect()}{boundingRect()} returns,
|
---|
| 296 | and before these variables can be used by
|
---|
| 297 | \l{QGraphicsItem::paint()}{paint()}, to keep Graphics View's internal
|
---|
| 298 | bookkeeping clean. It's safest to call this function once, immediately
|
---|
| 299 | before any such variable is modified.
|
---|
| 300 |
|
---|
| 301 | \snippet examples/graphicsview/elasticnodes/edge.cpp 3
|
---|
| 302 |
|
---|
| 303 | The edge's bounding rectangle is defined as the smallest rectangle that
|
---|
| 304 | includes both the start and the end point of the edge. Because we draw an
|
---|
| 305 | arrow on each edge, we also need to compensate by adjusting with half the
|
---|
| 306 | arrow size and half the pen width in all directions. The pen is used to
|
---|
| 307 | draw the outline of the arrow, and we can assume that half of the outline
|
---|
| 308 | can be drawn outside of the arrow's area, and half will be drawn inside.
|
---|
| 309 |
|
---|
| 310 | \snippet examples/graphicsview/elasticnodes/edge.cpp 4
|
---|
| 311 |
|
---|
| 312 | We start the reimplementation of \l{QGraphicsItem::paint()}{paint()} by
|
---|
| 313 | checking a few preconditions. Firstly, if either the source or destination
|
---|
| 314 | node is not set, then we return immediately; there is nothing to draw.
|
---|
| 315 |
|
---|
| 316 | At the same time, we check if the length of the edge is approximately 0,
|
---|
| 317 | and if it is, then we also return.
|
---|
| 318 |
|
---|
| 319 | \snippet examples/graphicsview/elasticnodes/edge.cpp 5
|
---|
| 320 |
|
---|
| 321 | We draw the line using a pen that has round joins and caps. If you run the
|
---|
| 322 | example, zoom in and study the edge in detail, you will see that there are
|
---|
| 323 | no sharp/square edges.
|
---|
| 324 |
|
---|
| 325 | \snippet examples/graphicsview/elasticnodes/edge.cpp 6
|
---|
| 326 |
|
---|
| 327 | We proceed to drawing one arrow at each end of the edge. Each arrow is
|
---|
| 328 | drawn as a polygon with a black fill. The coordinates for the arrow are
|
---|
| 329 | determined using simple trigonometry.
|
---|
| 330 |
|
---|
| 331 | \section1 GraphWidget Class Definition
|
---|
| 332 |
|
---|
| 333 | \c GraphWidget is a subclass of QGraphicsView, which provides the main
|
---|
| 334 | window with scrollbars.
|
---|
| 335 |
|
---|
| 336 | \snippet examples/graphicsview/elasticnodes/graphwidget.h 0
|
---|
| 337 |
|
---|
| 338 | The class provides a basic constructor that initializes the scene, an \c
|
---|
| 339 | itemMoved() function to notify changes in the scene's node graph, a few
|
---|
| 340 | event handlers, a reimplementation of
|
---|
| 341 | \l{QGraphicsView::drawBackground()}{drawBackground()}, and a helper
|
---|
| 342 | function for scaling the view by using the mouse wheel or keyboard.
|
---|
| 343 |
|
---|
| 344 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 0
|
---|
| 345 |
|
---|
| 346 | \c GraphicsWidget's constructor creates the scene, and because most items
|
---|
| 347 | move around most of the time, it sets QGraphicsScene::NoIndex. The scene
|
---|
| 348 | then gets a fixed \l{QGraphicsScene::sceneRect}{scene rectangle}, and is
|
---|
| 349 | assigned to the \c GraphWidget view.
|
---|
| 350 |
|
---|
| 351 | The view enables QGraphicsView::CacheBackground to cache rendering of its
|
---|
| 352 | static, and somewhat complex, background. Because the graph renders a close
|
---|
| 353 | collection of small items that all move around, it's unnecessary for
|
---|
| 354 | Graphics View to waste time finding accurate update regions, so we set the
|
---|
| 355 | QGraphicsView::BoundingRectViewportUpdate viewport update mode. The default
|
---|
| 356 | would work fine, but this mode is noticably faster for this example.
|
---|
| 357 |
|
---|
| 358 | To improve rendering quality, we set QPainter::Antialiasing.
|
---|
| 359 |
|
---|
| 360 | The transformation anchor decides how the view should scroll when you
|
---|
| 361 | transform the view, or in our case, when we zoom in or out. We have chosen
|
---|
| 362 | QGraphicsView::AnchorUnderMouse, which centers the view on the point under
|
---|
| 363 | the mouse cursor. This makes it easy to zoom towards a point in the scene
|
---|
| 364 | by moving the mouse over it, and then rolling the mouse wheel.
|
---|
| 365 |
|
---|
| 366 | Finally we give the window a minimum size that matches the scene's default
|
---|
| 367 | size, and set a suitable window title.
|
---|
| 368 |
|
---|
| 369 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 1
|
---|
| 370 |
|
---|
| 371 | The last part of the constructor creates the grid of nodes and edges, and
|
---|
| 372 | gives each node an initial position.
|
---|
| 373 |
|
---|
| 374 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 2
|
---|
| 375 |
|
---|
| 376 | \c GraphWidget is notified of node movement through this \c itemMoved()
|
---|
| 377 | function. Its job is simply to restart the main timer in case it's not
|
---|
| 378 | running already. The timer is designed to stop when the graph stabilizes,
|
---|
| 379 | and start once it's unstable again.
|
---|
| 380 |
|
---|
| 381 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 3
|
---|
| 382 |
|
---|
| 383 | This is \c GraphWidget's key event handler. The arrow keys move the center
|
---|
| 384 | node around, the '+' and '-' keys zoom in and out by calling \c
|
---|
| 385 | scaleView(), and the enter and space keys randomize the positions of the
|
---|
| 386 | nodes. All other key events (e.g., page up and page down) are handled by
|
---|
| 387 | QGraphicsView's default implementation.
|
---|
| 388 |
|
---|
| 389 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 4
|
---|
| 390 |
|
---|
| 391 | The timer event handler's job is to run the whole force calculation
|
---|
| 392 | machinery as a smooth animation. Each time the timer is triggered, the
|
---|
| 393 | handler will find all nodes in the scene, and call \c
|
---|
| 394 | Node::calculateForces() on each node, one at a time. Then, in a final step
|
---|
| 395 | it will call \c Node::advance() to move all nodes to their new positions.
|
---|
| 396 | By checking the return value of \c advance(), we can decide if the grid
|
---|
| 397 | stabilized (i.e., no nodes moved). If so, we can stop the timer.
|
---|
| 398 |
|
---|
| 399 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 5
|
---|
| 400 |
|
---|
| 401 | In the wheel event handler, we convert the mouse wheel delta to a scale
|
---|
| 402 | factor, and pass this factor to \c scaleView(). This approach takes into
|
---|
| 403 | account the speed that the wheel is rolled. The faster you roll the mouse
|
---|
| 404 | wheel, the faster the view will zoom.
|
---|
| 405 |
|
---|
| 406 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 6
|
---|
| 407 |
|
---|
| 408 | The view's background is rendered in a reimplementation of
|
---|
| 409 | QGraphicsView::drawBackground(). We draw a large rectangle filled with a
|
---|
| 410 | linear gradient, add a drop shadow, and then render text on top. The text
|
---|
| 411 | is rendered twice for a simple drop-shadow effect.
|
---|
| 412 |
|
---|
| 413 | This background rendering is quite expensive; this is why the view enables
|
---|
| 414 | QGraphicsView::CacheBackground.
|
---|
| 415 |
|
---|
| 416 | \snippet examples/graphicsview/elasticnodes/graphwidget.cpp 7
|
---|
| 417 |
|
---|
| 418 | The \c scaleView() helper function checks that the scale factor stays
|
---|
| 419 | within certain limits (i.e., you cannot zoom too far in nor too far out),
|
---|
| 420 | and then applies this scale to the view.
|
---|
| 421 |
|
---|
| 422 | \section1 The main() Function
|
---|
| 423 |
|
---|
| 424 | In contrast to the complexity of the rest of this example, the \c main()
|
---|
| 425 | function is very simple: We create a QApplication instance, seed the
|
---|
| 426 | randomizer using qsrand(), and then create and show an instance of \c
|
---|
| 427 | GraphWidget. Because all nodes in the grid are moved initially, the \c
|
---|
| 428 | GraphWidget timer will start immediately after control has returned to the
|
---|
| 429 | event loop.
|
---|
[2] | 430 | */
|
---|