Ignore:
Timestamp:
May 5, 2011, 5:36:53 AM (14 years ago)
Author:
Dmitry A. Kuminov
Message:

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk

  • trunk/doc/src/examples/elasticnodes.qdoc

    r651 r846  
    11/****************************************************************************
    22**
    3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
     3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
    44** All rights reserved.
    55** Contact: Nokia Corporation (qt-info@nokia.com)
     
    77** This file is part of the documentation of the Qt Toolkit.
    88**
    9 ** $QT_BEGIN_LICENSE:LGPL$
     9** $QT_BEGIN_LICENSE:FDL$
    1010** Commercial Usage
    1111** Licensees holding valid Qt Commercial licenses may use this file in
    1212** accordance with the Qt Commercial License Agreement provided with the
    13 ** Software or, alternatively, in accordance with the terms contained in
    14 ** a written agreement between you and Nokia.
    15 **
    16 ** GNU Lesser General Public License Usage
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
    18 ** General Public License version 2.1 as published by the Free Software
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
    20 ** packaging of this file.  Please review the following information to
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
    23 **
    24 ** In addition, as a special exception, Nokia gives you certain additional
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
    27 **
    28 ** GNU General Public License Usage
    29 ** Alternatively, this file may be used under the terms of the GNU
    30 ** General Public License version 3.0 as published by the Free Software
    31 ** Foundation and appearing in the file LICENSE.GPL included in the
    32 ** packaging of this file.  Please review the following information to
    33 ** ensure the GNU General Public License version 3.0 requirements will be
    34 ** met: http://www.gnu.org/copyleft/gpl.html.
     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.
    3521**
    3622** If you have questions regarding the use of this file, please contact
     
    4430    \title Elastic Nodes Example
    4531
    46     This GraphicsView example shows how to implement edges between nodes in a graph.
     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.
    4737
    4838    \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.
    49430*/
Note: See TracChangeset for help on using the changeset viewer.