summaryrefslogtreecommitdiffstats
path: root/doc/src/examples/dragdroprobot.qdoc
blob: f60fdc1ff3ac82e25bb9161d0859f6a167a84149 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** GNU Free Documentation License
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms
** and conditions contained in a signed written agreement between you
** and Nokia.
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
    \example graphicsview/dragdroprobot
    \title Drag and Drop Robot Example

    The Drag and Drop Robot example shows how to implement Drag and Drop in a
    QGraphicsItem subclass, as well as how to animate items using Qt's
    \l{Animation Framework}.

    \image dragdroprobot-example.png

    Graphics View provides the QGraphicsScene class for managing and
    interacting with a large number of custom-made 2D graphical items derived
    from the QGraphicsItem class, and a QGraphicsView widget for visualizing
    the items, with support for zooming and rotation.

    This example consists of a \c Robot class, a \c ColorItem class, and a main
    function: the \c Robot class describes a simple robot consisting of several
    \c RobotPart derived limbs, including \c RobotHead and \c RobotLimb, the \c
    ColorItem class provides a draggable colored ellipse, and the \c main()
    function provides the main application window.

    We will first review the \c Robot class to see how to assemble the
    different parts so that they can be individually rotated and animated using
    QPropertyAnimation, and we will then review the \c ColorItem class to
    demonstrate how to implement Drag and Drop between items. Finally we will
    review the main() function to see how we can put all the pieces together,
    to form the final application.

    \section1 Robot Class Definition

    The robot consists of three main classes: the \c RobotHead, the \c
    RobotTorso, and the \c RobotLimb, which is used for the upper and lower
    arms and legs. All parts derive from the \c RobotPart class, which in turn
    inherits \c QGraphicsObject. The \c Robot class itself has no visual
    appearance and serves only as a root node for the robot.

    Let's start with the \c RobotPart class declaration.

    \snippet examples/graphicsview/dragdroprobot/robot.h 0

    This base class inherits QGraphicsObject. QGraphicsObject provides signals
    and slots through inheriting QObject, and it also declares QGraphicsItem's
    properties using Q_PROPERTY, which makes the properties accessible for
    QPropertyAnimation.

    RobotPart also implements the three most important event handlers for
    accepting drop events:
    \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()},
    \l{QGraphicsItem::dragLeaveEvent()}{dragLeaveEvent()}, and
    \l{QGraphicsItem::dropEvent()}{dropEvent()}.

    The color is stored as a member variable, along with the \c dragOver
    variable, which we will use later to indicate visually that the limb can
    accept colors that are is dragged onto it.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 0

    \c RobotPart's constructor initializes the dragOver member and sets the
    color to Qt::lightGray. In the constructor body we enable support for
    accepting drop events by calling
    \l{QGraphicsItem::setAcceptDrops()}{setAcceptDrops(true)}.

    The rest of this class's implementation is to support Drag and Drop.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 1

    The \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()} handler is called
    when a Drag and Drop element is dragged into the robot part's area.

    The handler implementation determines whether or not this item as a whole
    can accept the mime data assiciated with the incoming drag object. \c
    RobotPart provides a base behavior for all parts that accepts color drops.
    So if the incoming drag object contains a color, the event is accepted, we
    set \c dragOver to \c true and call update() to help provide positive
    visual feedback to the user; otherwise the event is ignored, which in turn
    allows the event to propagate to parent elements.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 2

    The \l{QGraphicsItem::dragLeaveEvent()}{dragLeaveEvent()} handler is called
    when a Drag and Drop element is dragged away from the robot part's area.
    Our implementation simply resets \e dragOver to false and calls
    \l{QGraphicsItem::update()}{update()} to help provide visual feedback that
    the drag has left this item.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 3

    The \l{QGraphicsItem::dropEvent()}{dropEvent()} handler is called when a
    Drag and Drop element is dropped onto an item (i.e., when the mouse button
    is released over the item while dragging).

    We reset \c dragOver to false, assign the item's new color, and call
    \l{QGraphicsItem::update()}{update()}.

    The declaration and implementation of \c RobotHead, \c RobotTorso, and \c
    RobotLimb are practically identical. We will review \c RobotHead in detail,
    as this class has one minor difference, and leave the other classes as an
    exercise for the reader.

    \snippet examples/graphicsview/dragdroprobot/robot.h 1

    The \c RobotHead class inherits \c RobotPart and provides the necessary
    implementations of \l{QGraphicsItem::boundingRect()}{boundingRect()} and
    \l{QGraphicsItem::paint()}{paint()}. It also reimplements
    \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()} and dropEvent() to
    provide special handling of image drops.

    The class contains a private pixmap member that we can use to implement
    support for accepting image drops.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 4

    \c RobotHead has a rather plain constructor that simply forwards to
    \c RobotPart's constructor.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 5

    The \l{QGraphicsItem::boundingRect()}{boundingRect()} reimplementation
    returns the extents for the head. Because we want the center of rotation to
    be the bottom center of the item, we have chosen a bounding rectangle that
    starts at (-15, -50) and extends to 30 units wide and 50 units tall. When
    rotating the head, the "neck" will stay still while the top of the head
    tilts from side to side.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 6

    In \l{QGraphicsItem::paint()}{paint()} we draw the actual head. The
    implementation is split into two sections; if an image has been dropped
    onto the head, we draw the image, otherwise we draw a round rectangular
    robot head with simple vector graphics.

    For performance reasons, depending on the complexity of what is painted, it
    can often be faster to draw the head as an image rather than using a
    sequence of vector operations.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 7

    The robot head can accept image drops. In order to support this, its
    reimplementation of \l{QGraphicsItem::dragEnterEvent()}{dragEnterEvent()}
    checks if the drag object contains image data, and if it does, then the
    event is accepted. Otherwise we fall back to the base \c RobotPart
    implementation.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 8

    To follow up on image support, we must also implement
    \l{QGraphicsItem::dropEvent()}{dropEvent()}. We check if the drag object
    contains image data, and if it does, we store this data as a member pixmap
    and call \l{QGraphicsItem::update()}{update()}. This pixmap is used inside
    the \l{QGraphicsItem::paint()}{paint()} implementation that we reviewed
    before.

    \c RobotTorso and \c RobotLimb are similar to \c RobotHead, so let's
    skip directly to the \c Robot class.

    \snippet examples/graphicsview/dragdroprobot/robot.h 4

    The \c Robot class also inherits \c RobotPart, and like the other parts it
    also implements \l{QGraphicsItem::boundingRect()}{boundingRect()} and
    \l{QGraphicsItem::paint()}{paint()}. It provides a rather special
    implementation, though:

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 9

    Because the \c Robot class is only used as a base node for the rest of the
    robot, it has no visual representation. Its
    \l{QGraphicsItem::boundingRect()}{boundingRect()} implementation can
    therefore return a null QRectF, and its paint() function does nothing.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 10

    The constructor starts by setting the flag
    \l{QGraphicsItem::ItemHasNoContents}{ItemHasNoContents}, which is a minor
    optimization for items that have no visual appearance.

    We then construct all the robot parts (head, torso, and upper/lower arms
    and legs). The stacking order is very important, and we use the
    parent-child hierarchy to ensure the elements rotate and move properly. We
    construct the torso first, as this is the root element. We then construct
    the head and pass the torso to \c HeadItem's constructor. This will make
    the head a child of the torso; if you rotate the torso, the head will
    follow. The same pattern is applied to the rest of the limbs.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 11

    Each robot part is carefully positioned. For example, the upper left arm is
    moved precisely to the top-left area of the torso, and the upper right arm
    is moved to the top-right area.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 12

    The next section creates all animation objects. This snippet shows the two
    animations that operate on the head's scale and rotation. The two
    QPropertyAnimation instances simply set the object, property, and
    respective start and end values.

    All animations are controlled by one top-level parallel animation group.
    The scale and rotation animations are added to this group.

    The rest of the animations are defined in a similar way.

    \snippet examples/graphicsview/dragdroprobot/robot.cpp 13

    Finally we set an easing curve and duration on each animation, ensure the
    toplevel animation group loops forever, and start the toplevel animation.

    \section1 ColorItem Class Definition

    The \c ColorItem class represents a circular item that can be pressed to
    drag colors onto robot parts.

    \snippet examples/graphicsview/dragdroprobot/coloritem.h 0

    This class is very simple. It does not use animations, and has no need for
    properties nor signals and slots, so to save resources, it's most natural
    that it inherits QGraphicsItem (as opposed to QGraphicsObject).

    It declares the mandatory \l{QGraphicsItem::boundingRect()}{boundingRect()}
    and \l{QGraphicsItem::paint()}{paint()} functions, and adds
    reimplementations of
    \l{QGraphicsItem::mousePressEvent()}{mousePressEvent()},
    \l{QGraphicsItem::mouseMoveEvent()}{mouseMoveEvent()}, and
    \l{QGraphicsItem::mouseReleaseEvent()}{mouseReleaseEvent()}. It contains a
    single private color member.

    Let's take a look at its implementation.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 0

    \c ColorItem's constructor assigns an opaque random color to its color
    member by making use of qrand(). For improved usability, it assigns a
    tooltip that provides a useful hint to the user, and it also sets a
    suitable cursor. This ensures that the cursor will chance to
    Qt::OpenHandCursor when the mouse pointer hovers over the item.

    Finally, we call
    \l{QGraphicsItem::setAcceptedMouseButtons()}{setAcceptedMouseButtons()} to
    ensure that this item can only process Qt::LeftButton. This simplifies the
    mouse event handlers greatly, as we can always assume that only the left
    mouse button is pressed and released.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 1

    The item's bounding rect is a fixed 30x30 units centered around the item's
    origin (0, 0), and adjusted by 0.5 units in all directions to allow a
    scalable pen to draw its outline. For a final visual touch the bounds
    also compensate with a few units down and to the right to make room
    for a simple dropshadow.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 2

    The \l{QGraphicsItem::paint()}{paint()} implementation draws an ellipse
    with a 1-unit black outline, a plain color fill, and a dark gray
    dropshadow.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 3

    The \l{QGraphicsItem::mousePressEvent()}{mousePressEvent()} handler is
    called when you press the mouse button inside the item's area. Our
    implementation simply sets the cursor to Qt::ClosedHandCursor.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 4

    The \l{QGraphicsItem::mouseReleaseEvent()}{mouseReleaseEvent()} handler is
    called when you release the mouse button after having pressed it inside an
    item's area. Our implementation sets the cursor back to Qt::OpenHandCursor.
    The mouse press and release event handlers together provide useful visual
    feedback to the user: when you move the mouse pointer over a \c CircleItem,
    the cursor changes to an open hand. Pressing the item will show a closed
    hand cursor. Releasing will restore to an open hand cursor again.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 5

    The \l{QGraphicsItem::mouseMoveEvent()}{mouseMoveEvent()} handler is called
    when you move the mouse around after pressing the mouse button inside the
    \c ColorItem's area. This implementation provides the most important piece
    of logic for \c CircleItem: the code that starts and manages drags.

    The implementation starts by checking if the mouse has been dragged far
    enough to eliminate mouse jitter noise. We only want to start a drag if the
    mouse has been dragged farther than the application start drag distance.

    Continuing, we create a QDrag object, passing the event
    \l{QGraphicsSceneEvent::widget()}{widget} (i.e., the QGraphicsView
    viewport) to its constructor. Qt will ensure that this object is deleted at
    the right time. We also create a QMimeData instance that can contain our
    color or image data, and assign this to the drag object.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 6

    This snippet has a somewhat random outcome: once in a while, a special
    image is assigned to the drag object's mime data. The pixmap is also
    assiged as the drag object's pixmap. This will ensure that you can see the
    image that is being dragged as a pixmap under the mouse cursor.

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 7

    Otherwise, and this is the most common outcome, a simple color is assigned
    to the drag object's mime data. We render this \c ColorItem into a new
    pixmap to give the user visual feedback that the color is being "dragged".

    \snippet examples/graphicsview/dragdroprobot/coloritem.cpp 8

    Finally we execute the drag. QDrag::exec() will reenter the event loop, and
    only exit if the drag has either been dropped, or canceled. In any case we
    reset the cursor to Qt::OpenHandCursor.

    \section1 The main() Function

    Now that the \c Robot and \c ColorItem classes are complete, we can put all
    the pieces together inside the main() function.

    \snippet examples/graphicsview/dragdroprobot/main.cpp 0

    We start off by constructing QApplication, and initializing the random
    number generator. This ensures that the color items have different colors
    every time the application starts.

    \snippet examples/graphicsview/dragdroprobot/main.cpp 1

    We construct a fixed size scene, and create 10 \c ColorItem instances
    arranged in a circle. Each item is added to the scene.

    In the center of this circle we create one \c Robot instance. The
    robot is scaled and moved up a few units. It is then added to the scene.

    \snippet examples/graphicsview/dragdroprobot/main.cpp 2

    Finally we create a QGraphicsView window, and assign the scene to it.

    For increased visual quality, we enable antialiasing. We also choose to use
    bounding rectangle updates to simplify visual update handling.
    The view is given a fixed sand-colored background, and a window title.

    We then show the view. The animations start immediately after
    control enters the event loop.
*/