| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the documentation of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:FDL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Free Documentation License Usage |
| ** 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. Please review the following information to ensure |
| ** the GNU Free Documentation License version 1.3 requirements |
| ** will be met: https://www.gnu.org/licenses/fdl-1.3.html. |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| /*! |
| \example graphicsview/dragdroprobot |
| \title Drag and Drop Robot Example |
| \ingroup examples-graphicsview |
| \brief Demonstrates how to drag and drop items in a graphics view. |
| |
| 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 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 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 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 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 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 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 graphicsview/dragdroprobot/robot.cpp 4 |
| |
| \c RobotHead has a rather plain constructor that simply forwards to |
| \c RobotPart's constructor. |
| |
| \snippet 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 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 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 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 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 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 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 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 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 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 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 graphicsview/dragdroprobot/coloritem.cpp 0 |
| |
| \c ColorItem's constructor assigns an opaque random color to its color |
| member by making use of \l QRandomGenerator. 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 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 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 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 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 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 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 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 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 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 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 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. |
| */ |
| |