[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/dragdroprobot
|
---|
| 30 | \title Drag and Drop Robot Example
|
---|
| 31 |
|
---|
[846] | 32 | This GraphicsView example shows how to implement Drag and Drop in a
|
---|
| 33 | QGraphicsItem subclass, as well as how to animate items using Qt's
|
---|
| 34 | \l{Animation Framework}.
|
---|
[2] | 35 |
|
---|
| 36 | \image dragdroprobot-example.png
|
---|
[846] | 37 |
|
---|
| 38 | Graphics View provides the QGraphicsScene class for managing and
|
---|
| 39 | interacting with a large number of custom-made 2D graphical items derived
|
---|
| 40 | from the QGraphicsItem class, and a QGraphicsView widget for visualizing
|
---|
| 41 | the items, with support for zooming and rotation.
|
---|
| 42 |
|
---|
| 43 | This example consists of a \c Robot class, a \c ColorItem class, and a main
|
---|
| 44 | function: the \c Robot class describes a simple robot consisting of several
|
---|
| 45 | \c RobotPart derived limbs, including \c RobotHead and \c RobotLimb, the \c
|
---|
| 46 | ColorItem class provides a draggable colored ellipse, and the \c main()
|
---|
| 47 | function provides the main application window.
|
---|
| 48 |
|
---|
| 49 | We will first review the \c Robot class to see how to assemble the
|
---|
| 50 | different parts so that they can be individually rotated and animated using
|
---|
| 51 | QPropertyAnimation, and we will then review the \c ColorItem class to
|
---|
| 52 | demonstrate how to implement Drag and Drop between items. Finally we will
|
---|
| 53 | review the main() function to see how we can put all the pieces together,
|
---|
| 54 | to form the final application.
|
---|
| 55 |
|
---|
| 56 | \section1 Robot Class Definition
|
---|
| 57 |
|
---|
| 58 | The robot consists of three main classes: the \c RobotHead, the \c
|
---|
| 59 | RobotTorso, and the \c RobotLimb, which is used for the upper and lower
|
---|
| 60 | arms and legs. All parts derive from the \c RobotPart class, which in turn
|
---|
| 61 | inherits \c QGraphicsObject. The \c Robot class itself has no visual
|
---|
| 62 | appearance and serves only as a root node for the robot.
|
---|
| 63 |
|
---|
| 64 | Let's start with the \c RobotPart class declaration.
|
---|
| 65 |
|
---|
| 66 | \snippet examples/graphicsview/dragdroprobot/robot.h 0
|
---|
| 67 |
|
---|
| 68 | This base class inherits QGraphicsObject. QGraphicsObject provides signals
|
---|
| 69 | and slots through inheriting QObject, and it also declares QGraphicsItem's
|
---|
| 70 | properties using Q_PROPERTY, which makes the properties accessible for
|
---|
| 71 | QPropertyAnimation.
|
---|
| 72 |
|
---|
| 73 | RobotPart also implements the three most important event handlers for
|
---|
| 74 | accepting drop events:
|
---|
| 75 | \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()},
|
---|
| 76 | \l{QGraphicsItem::dragLeaveEvent()}{dragLeaveEvent()}, and
|
---|
| 77 | \l{QGraphicsItem::dropEvent()}{dropEvent()}.
|
---|
| 78 |
|
---|
| 79 | The color is stored as a member variable, along with the \c dragOver
|
---|
| 80 | variable, which we will use later to indicate visually that the limb can
|
---|
| 81 | accept colors that are is dragged onto it.
|
---|
| 82 |
|
---|
| 83 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 0
|
---|
| 84 |
|
---|
| 85 | \c RobotPart's constructor initializes the dragOver member and sets the
|
---|
| 86 | color to Qt::lightGray. In the constructor body we enable support for
|
---|
| 87 | accepting drop events by calling
|
---|
| 88 | \l{QGraphicsItem::setAcceptDrops()}{setAcceptDrops(true)}.
|
---|
| 89 |
|
---|
| 90 | The rest of this class's implementation is to support Drag and Drop.
|
---|
| 91 |
|
---|
| 92 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 1
|
---|
| 93 |
|
---|
| 94 | The \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()} handler is called
|
---|
| 95 | when a Drag and Drop element is dragged into the robot part's area.
|
---|
| 96 |
|
---|
| 97 | The handler implementation determines whether or not this item as a whole
|
---|
| 98 | can accept the mime data assiciated with the incoming drag object. \c
|
---|
| 99 | RobotPart provides a base behavior for all parts that accepts color drops.
|
---|
| 100 | So if the incoming drag object contains a color, the event is accepted, we
|
---|
| 101 | set \c dragOver to \c true and call update() to help provide positive
|
---|
| 102 | visual feedback to the user; otherwise the event is ignored, which in turn
|
---|
| 103 | allows the event to propagate to parent elements.
|
---|
| 104 |
|
---|
| 105 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 2
|
---|
| 106 |
|
---|
| 107 | The \l{QGraphicsItem::dragLeaveEvent()}{dragLeaveEvent()} handler is called
|
---|
| 108 | when a Drag and Drop element is dragged away from the robot part's area.
|
---|
| 109 | Our implementation simply resets \e dragOver to false and calls
|
---|
| 110 | \l{QGraphicsItem::update()}{update()} to help provide visual feedback that
|
---|
| 111 | the drag has left this item.
|
---|
| 112 |
|
---|
| 113 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 3
|
---|
| 114 |
|
---|
| 115 | The \l{QGraphicsItem::dropEvent()}{dropEvent()} handler is called when a
|
---|
| 116 | Drag and Drop element is dropped onto an item (i.e., when the mouse button
|
---|
| 117 | is released over the item while dragging).
|
---|
| 118 |
|
---|
| 119 | We reset \c dragOver to false, assign the item's new color, and call
|
---|
| 120 | \l{QGraphicsItem::update()}{update()}.
|
---|
| 121 |
|
---|
| 122 | The declaration and implementation of \c RobotHead, \c RobotTorso, and \c
|
---|
| 123 | RobotLimb are practically identical. We will review \c RobotHead in detail,
|
---|
| 124 | as this class has one minor difference, and leave the other classes as an
|
---|
| 125 | exercise for the reader.
|
---|
| 126 |
|
---|
| 127 | \snippet examples/graphicsview/dragdroprobot/robot.h 1
|
---|
| 128 |
|
---|
| 129 | The \c RobotHead class inherits \c RobotPart and provides the necessary
|
---|
| 130 | implementations of \l{QGraphicsItem::boundingRect()}{boundingRect()} and
|
---|
| 131 | \l{QGraphicsItem::paint()}{paint()}. It also reimplements
|
---|
| 132 | \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()} and dropEvent() to
|
---|
| 133 | provide special handling of image drops.
|
---|
| 134 |
|
---|
| 135 | The class contains a private pixmap member that we can use to implement
|
---|
| 136 | support for accepting image drops.
|
---|
| 137 |
|
---|
| 138 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 4
|
---|
| 139 |
|
---|
| 140 | \c RobotHead has a rather plain constructor that simply forwards to
|
---|
| 141 | \c RobotPart's constructor.
|
---|
| 142 |
|
---|
| 143 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 5
|
---|
| 144 |
|
---|
| 145 | The \l{QGraphicsItem::boundingRect()}{boundingRect()} reimplementation
|
---|
| 146 | returns the extents for the head. Because we want the center of rotation to
|
---|
| 147 | be the bottom center of the item, we have chosen a bounding rectangle that
|
---|
| 148 | starts at (-15, -50) and extends to 30 units wide and 50 units tall. When
|
---|
| 149 | rotating the head, the "neck" will stay still while the top of the head
|
---|
| 150 | tilts from side to side.
|
---|
| 151 |
|
---|
| 152 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 6
|
---|
| 153 |
|
---|
| 154 | In \l{QGraphicsItem::paint()}{paint()} we draw the actual head. The
|
---|
| 155 | implementation is split into two sections; if an image has been dropped
|
---|
| 156 | onto the head, we draw the image, otherwise we draw a round rectangular
|
---|
| 157 | robot head with simple vector graphics.
|
---|
| 158 |
|
---|
| 159 | For performance reasons, depending on the complexity of what is painted, it
|
---|
| 160 | can often be faster to draw the head as an image rather than using a
|
---|
| 161 | sequence of vector operations.
|
---|
| 162 |
|
---|
| 163 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 7
|
---|
| 164 |
|
---|
| 165 | The robot head can accept image drops. In order to support this, its
|
---|
| 166 | reimplementation of \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()}
|
---|
| 167 | checks if the drag object contains image data, and if it does, then the
|
---|
| 168 | event is accepted. Otherwise we fall back to the base \c RobotPart
|
---|
| 169 | implementation.
|
---|
| 170 |
|
---|
| 171 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 8
|
---|
| 172 |
|
---|
| 173 | To follow up on image support, we must also implement
|
---|
| 174 | \l{QGraphicsItem::dropEvent()}{dropEvent()}. We check if the drag object
|
---|
| 175 | contains image data, and if it does, we store this data as a member pixmap
|
---|
| 176 | and call \l{QGraphicsItem::update()}{update()}. This pixmap is used inside
|
---|
| 177 | the \l{QGraphicsItem::paint()}{paint()} implementation that we reviewed
|
---|
| 178 | before.
|
---|
| 179 |
|
---|
| 180 | \c RobotTorso and \c RobotLimb are similar to \c RobotHead, so let's
|
---|
| 181 | skip directly to the \c Robot class.
|
---|
| 182 |
|
---|
| 183 | \snippet examples/graphicsview/dragdroprobot/robot.h 4
|
---|
| 184 |
|
---|
| 185 | The \c Robot class also inherits \c RobotPart, and like the other parts it
|
---|
| 186 | also implements \l{QGraphicsItem::boundingRect()}{boundingRect()} and
|
---|
| 187 | \l{QGraphicsItem::paint()}{paint()}. It provides a rather special
|
---|
| 188 | implementation, though:
|
---|
| 189 |
|
---|
| 190 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 9
|
---|
| 191 |
|
---|
| 192 | Because the \c Robot class is only used as a base node for the rest of the
|
---|
| 193 | robot, it has no visual representation. Its
|
---|
| 194 | \l{QGraphicsItem::boundingRect()}{boundingRect()} implementation can
|
---|
| 195 | therefore return a null QRectF, and its paint() function does nothing.
|
---|
| 196 |
|
---|
| 197 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 10
|
---|
| 198 |
|
---|
| 199 | The constuctor starts by setting the flag
|
---|
| 200 | \l{QGraphicsItem::ItemHasNoContents}{ItemHasNoContents}, which is a minor
|
---|
| 201 | optimization for items that have no visual appearance.
|
---|
| 202 |
|
---|
| 203 | We then construct all the robot parts (head, torso, and upper/lower arms
|
---|
| 204 | and legs). The stacking order is very important, and we use the
|
---|
| 205 | parent-child hierarchy to ensure the elements rotate and move properly. We
|
---|
| 206 | construct the torso first, as this is the root element. We then construct
|
---|
| 207 | the head and pass the torso to \c HeadItem's constructor. This will make
|
---|
| 208 | the head a child of the torso; if you rotate the torso, the head will
|
---|
| 209 | follow. The same pattern is applied to the rest of the limbs.
|
---|
| 210 |
|
---|
| 211 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 11
|
---|
| 212 |
|
---|
| 213 | Each robot part is carefully positioned. For example, the upper left arm is
|
---|
| 214 | moved precisely to the top-left area of the torso, and the upper right arm
|
---|
| 215 | is moved to the top-right area.
|
---|
| 216 |
|
---|
| 217 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 12
|
---|
| 218 |
|
---|
| 219 | The next section creates all animation objects. This snippet shows the two
|
---|
| 220 | animations that operate on the head's scale and rotation. The two
|
---|
| 221 | QPropertyAnimation instances simply set the object, property, and
|
---|
| 222 | respective start and end values.
|
---|
| 223 |
|
---|
| 224 | All animations are controlled by one top-level parallel animation group.
|
---|
| 225 | The scale and rotation animations are added to this group.
|
---|
| 226 |
|
---|
| 227 | The rest of the animations are defined in a similar way.
|
---|
| 228 |
|
---|
| 229 | \snippet examples/graphicsview/dragdroprobot/robot.cpp 13
|
---|
| 230 |
|
---|
| 231 | Finally we set an easing curve and duration on each animation, ensure the
|
---|
| 232 | toplevel animation group loops forever, and start the toplevel animation.
|
---|
| 233 |
|
---|
| 234 | \section1 ColorItem Class Definition
|
---|
| 235 |
|
---|
| 236 | The \c ColorItem class represents a circular item that can be pressed to
|
---|
| 237 | drag colors onto robot parts.
|
---|
| 238 |
|
---|
| 239 | \snippet examples/graphicsview/dragdroprobot/coloritem.h 0
|
---|
| 240 |
|
---|
| 241 | This class is very simple. It does not use animations, and has no need for
|
---|
| 242 | properties nor signals and slots, so to save resources, it's most natural
|
---|
| 243 | that it inherits QGraphicsItem (as opposed to QGraphicsObject).
|
---|
| 244 |
|
---|
| 245 | It declares the mandatory \l{QGraphicsItem::boundingRect()}{boundingRect()}
|
---|
| 246 | and \l{QGraphicsItem::paint()}{paint()} functions, and adds
|
---|
| 247 | reimplementations of
|
---|
| 248 | \l{QGraphicsItem::mousePressEvent()}{mousePressEvent()},
|
---|
| 249 | \l{QGraphicsItem::mouseMoveEvent()}{mouseMoveEvent()}, and
|
---|
| 250 | \l{QGraphicsItem::mouseReleaseEvent()}{mouseReleaseEvent()}. It contains a
|
---|
| 251 | single private color member.
|
---|
| 252 |
|
---|
| 253 | Let's take a look at its implementation.
|
---|
| 254 |
|
---|
| 255 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 0
|
---|
| 256 |
|
---|
| 257 | \c ColorItem's constructor assigns an opaque random color to its color
|
---|
| 258 | member by making use of qrand(). For improved usability, it assigns a
|
---|
| 259 | tooltip that provides a useful hint to the user, and it also sets a
|
---|
| 260 | suitable cursor. This ensures that the cursor will chance to
|
---|
| 261 | Qt::OpenHandCursor when the mouse pointer hovers over the item.
|
---|
| 262 |
|
---|
| 263 | Finally, we call
|
---|
| 264 | \l{QGraphicsItem::setAcceptedMouseButtons()}{setAcceptedMouseButtons()} to
|
---|
| 265 | ensure that this item can only process Qt::LeftButton. This simplifies the
|
---|
| 266 | mouse event handlers greatly, as we can always assume that only the left
|
---|
| 267 | mouse button is pressed and released.
|
---|
| 268 |
|
---|
| 269 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 1
|
---|
| 270 |
|
---|
| 271 | The item's bounding rect is a fixed 30x30 units centered around the item's
|
---|
| 272 | origin (0, 0), and adjusted by 0.5 units in all directions to allow a
|
---|
| 273 | scalable pen to draw its outline. For a final visual touch the bounds
|
---|
| 274 | also compensate with a few units down and to the right to make room
|
---|
| 275 | for a simple dropshadow.
|
---|
| 276 |
|
---|
| 277 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 2
|
---|
| 278 |
|
---|
| 279 | The \l{QGraphicsItem::paint()}{paint()} implementation draws an ellipse
|
---|
| 280 | with a 1-unit black outline, a plain color fill, and a dark gray
|
---|
| 281 | dropshadow.
|
---|
| 282 |
|
---|
| 283 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 3
|
---|
| 284 |
|
---|
| 285 | The \l{QGraphicsItem::mousePressEvent()}{mousePressEvent()} handler is
|
---|
| 286 | called when you press the mouse button inside the item's area. Our
|
---|
| 287 | implementation simply sets the cursor to Qt::ClosedHandCursor.
|
---|
| 288 |
|
---|
| 289 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 4
|
---|
| 290 |
|
---|
| 291 | The \l{QGraphicsItem::mouseReleaseEvent()}{mouseReleaseEvent()} handler is
|
---|
| 292 | called when you release the mouse button after having pressed it inside an
|
---|
| 293 | item's area. Our implementation sets the cursor back to Qt::OpenHandCursor.
|
---|
| 294 | The mouse press and release event handlers together provide useful visual
|
---|
| 295 | feedback to the user: when you move the mouse pointer over a \c CircleItem,
|
---|
| 296 | the cursor changes to an open hand. Pressing the item will show a closed
|
---|
| 297 | hand cursor. Releasing will restore to an open hand cursor again.
|
---|
| 298 |
|
---|
| 299 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 5
|
---|
| 300 |
|
---|
| 301 | The \l{QGraphicsItem::mouseMoveEvent()}{mouseMoveEvent()} handler is called
|
---|
| 302 | when you move the mouse around after pressing the mouse button inside the
|
---|
| 303 | \c ColorItem's area. This implementation provides the most important piece
|
---|
| 304 | of logic for \c CircleItem: the code that starts and manages drags.
|
---|
| 305 |
|
---|
| 306 | The implementation starts by checking if the mouse has been dragged far
|
---|
| 307 | enough to eliminate mouse jitter noise. We only want to start a drag if the
|
---|
| 308 | mouse has been dragged farther than the application start drag distance.
|
---|
| 309 |
|
---|
| 310 | Continuing, we create a QDrag object, passing the event
|
---|
| 311 | \l{QGraphicsSceneEvent::widget()}{widget} (i.e., the QGraphicsView
|
---|
| 312 | viewport) to its constructor. Qt will ensure that this object is deleted at
|
---|
| 313 | the right time. We also create a QMimeData instance that can contain our
|
---|
| 314 | color or image data, and assign this to the drag object.
|
---|
| 315 |
|
---|
| 316 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 6
|
---|
| 317 |
|
---|
| 318 | This snippet has a somewhat random outcome: once in a while, a special
|
---|
| 319 | image is assigned to the drag object's mime data. The pixmap is also
|
---|
| 320 | assiged as the drag object's pixmap. This will ensure that you can see the
|
---|
| 321 | image that is being dragged as a pixmap under the mouse cursor.
|
---|
| 322 |
|
---|
| 323 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 7
|
---|
| 324 |
|
---|
| 325 | Otherwise, and this is the most common outcome, a simple color is assigned
|
---|
| 326 | to the drag object's mime data. We render this \c ColorItem into a new
|
---|
| 327 | pixmap to give the user visual feedback that the color is being "dragged".
|
---|
| 328 |
|
---|
| 329 | \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 8
|
---|
| 330 |
|
---|
| 331 | Finally we execute the drag. QDrag::exec() will reenter the event loop, and
|
---|
| 332 | only exit if the drag has either been dropped, or canceled. In any case we
|
---|
| 333 | reset the cursor to Qt::OpenHandCursor.
|
---|
| 334 |
|
---|
| 335 | \section1 The main() Function
|
---|
| 336 |
|
---|
| 337 | Now that the \c Robot and \c ColorItem classes are complete, we can put all
|
---|
| 338 | the pieces together inside the main() function.
|
---|
| 339 |
|
---|
| 340 | \snippet examples/graphicsview/dragdroprobot/main.cpp 0
|
---|
| 341 |
|
---|
| 342 | We start off by constructing QApplication, and initializing the random
|
---|
| 343 | number generator. This ensures that the color items have different colors
|
---|
| 344 | every time the application starts.
|
---|
| 345 |
|
---|
| 346 | \snippet examples/graphicsview/dragdroprobot/main.cpp 1
|
---|
| 347 |
|
---|
| 348 | We construct a fixed size scene, and create 10 \c ColorItem instances
|
---|
| 349 | arranged in a circle. Each item is added to the scene.
|
---|
| 350 |
|
---|
| 351 | In the center of this circle we create one \c Robot instance. The
|
---|
| 352 | robot is scaled and moved up a few units. It is then added to the scene.
|
---|
| 353 |
|
---|
| 354 | \snippet examples/graphicsview/dragdroprobot/main.cpp 2
|
---|
| 355 |
|
---|
| 356 | Finally we create a QGraphicsView window, and assign the scene to it.
|
---|
| 357 |
|
---|
| 358 | For increased visual quality, we enable antialiasing. We also choose to use
|
---|
| 359 | bounding rectangle updates to simplify visual update handling.
|
---|
| 360 | The view is given a fixed sand-colored background, and a window title.
|
---|
| 361 |
|
---|
| 362 | We then show the view. The animations start immediately after
|
---|
| 363 | control enters the event loop.
|
---|
[2] | 364 | */
|
---|
[846] | 365 |
|
---|