1 | /****************************************************************************
|
---|
2 | **
|
---|
3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
---|
4 | ** All rights reserved.
|
---|
5 | ** Contact: Nokia Corporation (qt-info@nokia.com)
|
---|
6 | **
|
---|
7 | ** This file is part of the documentation of the Qt Toolkit.
|
---|
8 | **
|
---|
9 | ** $QT_BEGIN_LICENSE:FDL$
|
---|
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
|
---|
13 | ** Software or, alternatively, in accordance with the terms contained in a
|
---|
14 | ** written agreement between you and Nokia.
|
---|
15 | **
|
---|
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.
|
---|
21 | **
|
---|
22 | ** If you have questions regarding the use of this file, please contact
|
---|
23 | ** Nokia at qt-info@nokia.com.
|
---|
24 | ** $QT_END_LICENSE$
|
---|
25 | **
|
---|
26 | ****************************************************************************/
|
---|
27 |
|
---|
28 | /*!
|
---|
29 | \example graphicsview/elasticnodes
|
---|
30 | \title Elastic Nodes Example
|
---|
31 |
|
---|
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.
|
---|
37 |
|
---|
38 | \image elasticnodes-example.png
|
---|
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.
|
---|
430 | */
|
---|