blob: 101d0fd390ef2b987f99704735c8d66f6aabdff8 [file] [log] [blame]
/****************************************************************************
**
** 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 draganddrop/fridgemagnets
\title Fridge Magnets Example
\brief The Fridge Magnets example illustrates how to move around several types of
MIME-encoded data with drag and drop.
The Fridge Magnets example shows how to supply more than one type
of MIME-encoded data with a drag and drop operation.
\image fridgemagnets-example.png
With this application the user can play around with a collection
of fridge magnets, using drag and drop to form new sentences from
the words on the magnets. The example consists of two classes:
\list
\li \c DragLabel is a custom widget representing one
single fridge magnet.
\li \c DragWidget provides the main application window.
\endlist
We will first take a look at the \c DragLabel class, then we will
examine the \c DragWidget class.
\section1 DragLabel Class Definition
Each fridge magnet is represented by an instance of the \c
DragLabel class:
\snippet draganddrop/fridgemagnets/draglabel.h 0
Each instance of this QLabel subclass will be used to display an
pixmap generated from a text string. Since we cannot store both
text and a pixmap in a standard label, we declare a private variable
to hold the original text, and we define an additional member
function to allow it to be accessed.
\section1 DragLabel Class Implementation
In the \c DragLabel constructor, we first create a QImage object
on which we will draw the fridge magnet's text and frame:
\snippet draganddrop/fridgemagnets/draglabel.cpp 0
Its size depends on the current font size, and its format is
QImage::Format_ARGB32_Premultiplied; i.e., the image is stored
using a premultiplied 32-bit ARGB format (0xAARRGGBB).
We then construct a font object that uses the application's
default font, and set its style strategy. The style strategy tells
the font matching algorithm what type of fonts should be used to
find an appropriate default family. The QFont::ForceOutline forces
the use of outline fonts.
To draw the text and frame onto the image, we use the QPainter
class. QPainter provides highly optimized methods to do most of
the drawing GUI programs require. It can draw everything from
simple lines to complex shapes like pies and chords. It can also
draw aligned text and pixmaps.
\snippet draganddrop/fridgemagnets/draglabel.cpp 1
A painter can be activated by passing a paint device to the
constructor, or by using the \l{QPainter::}{begin()} method as we
do in this example. The \l{QPainter::}{end()} method deactivates
it. Note that the latter function is called automatically upon
destruction when the painter is actived by its constructor. The
QPainter::Antialiasing render hint ensures that the paint engine
will antialias the edges of primitives if possible.
When the painting is done, we convert our image to a pixmap using
QPixmap's \l {QPixmap::}{fromImage()} method. This method also
takes an optional flags argument, and converts the given image to
a pixmap using the specified flags to control the conversion (the
flags argument is a bitwise-OR of the Qt::ImageConversionFlags;
passing 0 for flags sets all the default options).
\snippet draganddrop/fridgemagnets/draglabel.cpp 2
Finally, we set the label's \l{QLabel::pixmap}{pixmap property}
and store the label's text for later use.
\e{Note that setting the pixmap clears any previous content, including
any text previously set using QLabel::setText(), and disables
the label widget's buddy shortcut, if any.}
\section1 DragWidget Class Definition
The \c DragWidget class inherits QWidget, providing support for
drag and drop operations:
\snippet draganddrop/fridgemagnets/dragwidget.h 0
To make the widget responsive to drag and drop operations, we simply
reimplement the \l{QWidget::}{dragEnterEvent()},
\l{QWidget::}{dragMoveEvent()} and \l{QWidget::}{dropEvent()} event
handlers inherited from QWidget.
We also reimplement \l{QWidget::}{mousePressEvent()} to make the
widget responsive to mouse clicks. This is where we will write code
to start drag and drop operations.
\section1 DragWidget Class Implementation
In the constructor, we first open the file containing the words on
our fridge magnets:
\snippet draganddrop/fridgemagnets/dragwidget.cpp 0
QFile is an I/O device for reading and writing text and binary
files and resources, and may be used by itself or in combination
with QTextStream or QDataStream. We have chosen to read the
contents of the file using the QTextStream class that provides a
convenient interface for reading and writing text.
We then create the fridge magnets. As long as there is data (the
QTextStream::atEnd() method returns true if there is no more data
to be read from the stream), we read one line at a time using
QTextStream's \l {QTextStream::}{readLine()} method.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 1
For each line, we create a \c DragLabel object using the read line
as text, we calculate its position and ensure that it is visible by
calling the QWidget::show() method. We set the Qt::WA_DeleteOnClose
attribute on each label to ensure that any unused labels will be
deleted; we will need to create new labels and delete old ones when
they are dragged around, and this ensures that the example does not
leak memory.
We also set the \c FridgeMagnets widget's palette, minimum size
and window title.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 2
Finally, to enable our user to move the fridge magnets around, we
must also set the \c FridgeMagnets widget's
\l{QWidget::acceptDrops}{acceptDrops} property.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 3
Setting this property to true announces to the system that this
widget \e may be able to accept drop events (events that are sent
when drag and drop actions are completed). Later, we will
implement the functions that ensure that the widget accepts the
drop events it is interested in.
\section2 Dragging
Let's take a look at the \l{QWidget::}{mousePressEvent()} event
handler, where drag and drop operations begin:
\snippet draganddrop/fridgemagnets/dragwidget.cpp 13
\snippet draganddrop/fridgemagnets/dragwidget.cpp 14
Mouse events occur when a mouse button is pressed or released
inside a widget, or when the mouse cursor is moved. By
reimplementing the \l{QWidget::}{mousePressEvent()} method we
ensure that we will receive mouse press events for the widget
containing the fridge magnets.
Whenever we receive such an event, we first check to see if the
position of the click coincides with one of the labels. If not,
we simply return.
If the user clicked a label, we determine the position of the
\e{hot spot} (the position of the click relative to the top-left
corner of the label). We create a byte array to store the label's
text and the hot spot, and we use a QDataStream object to stream
the data into the byte array.
With all the information in place, we create a new QMimeData object.
As mentioned above, QMimeData objects associate the data that they
hold with the corresponding MIME types to ensure that information
can be safely transferred between applications. The
\l{QMimeData::}{setData()} method sets the data associated with a
given MIME type. In our case, we associate our item data with the
custom \c application/x-fridgemagnet type.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 15
Note that we also associate the magnet's text with the
\c text/plain MIME type using QMimeData's \l{QMimeData::}{setText()}
method. Below, we will see how our widget detects both these MIME
types with its event handlers.
Finally, we create a QDrag object. It is the QDrag class that
handles most of the details of a drag and drop operation,
providing support for MIME-based drag and drop data transfer. The
data to be transferred by the drag and drop operation is contained
in a QMimeData object. When we call QDrag's
\l{QDrag::}{setMimeData()} method the ownership of our item data is
transferred to the QDrag object.
We call the \l{QDrag::}{setPixmap()} function to set the pixmap used
to represent the data during the drag and drop operation.
Typically, this pixmap shows an icon that represents the MIME type
of the data being transferred, but any pixmap can be used. In this
example, we simply use the pixmap used by the label itself to make
it look like the fridge magnet itself is being moved.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 16
We also specify the cursor's hot spot, its position relative to the
top-level corner of the drag pixmap, to be the point we calculated
above. This makes the process of dragging the label feel more natural
because the cursor always points to the same place on the label
during the drag operation.
We start the drag operation using QDrag's \l{QDrag::}{exec()} function,
requesting that the magnet is copied when the drag is completed.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 17
The function returns the drop action actually performed by the user
(this can be either a copy or a move action in this case); if this
action is equal to Qt::MoveAction we will close the activated
fridge magnet widget because we will create a new one to replace it
(see the \l{drop}{dropEvent()} implementation). Otherwise, if
the drop is outside our main widget, we simply show the widget in
its original position.
\section2 Dropping
When a a drag and drop action enters our widget, we will receive a
drag enter \e event. QDragEnterEvent inherits most of its
functionality from QDragMoveEvent, which in turn inherits most of
its functionality from QDropEvent. Note that we must accept this
event in order to receive the drag move events that are sent while
the drag and drop action is in progress. The drag enter event is
always immediately followed by a drag move event.
In our \c dragEnterEvent() implementation, we first determine
whether we support the event's MIME type or not:
\snippet draganddrop/fridgemagnets/dragwidget.cpp 4
\snippet draganddrop/fridgemagnets/dragwidget.cpp 5
\snippet draganddrop/fridgemagnets/dragwidget.cpp 6
If the type is \c application/x-fridgemagnet and the event
origins from any of this application's fridge magnet widgets, we
first set the event's drop action using the
QDropEvent::setDropAction() method. An event's drop action is the
action to be performed on the data by the target. Qt::MoveAction
indicates that the data is moved from the source to the target.
Then we call the event's \l {QDragMoveEvent::}{accept()} method to
indicate that we have handled the event. In general, unaccepted
events might be propagated to the parent widget. If the event
origins from any other widget, we simply accept the proposed
action.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 7
We also accept the proposed action if the event's MIME type is \c
text/plain, i.e., if QMimeData::hasText() returns true. If the
event has any other type, on the other hand, we call the event's
\l {QDragMoveEvent::}{ignore()} method allowing the event to be
propagated further.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 8
Drag move events occur when the cursor enters a widget, when it
moves within the widget, and when a modifier key is pressed on the
keyboard while the widget has focus. Our widget will receive drag
move events repeatedly while a drag is within its boundaries. We
reimplement the \l {QWidget::}{dragMoveEvent()} method, and
examine the event in the exact same way as we did with drag enter
events.
Note that the \l{QWidget::}{dropEvent()} event handler behaves
slightly differently: We first get hold of the event's MIME
data.
\target drop
\snippet draganddrop/fridgemagnets/dragwidget.cpp 9
The QMimeData class provides a container for data that
records information about its MIME type. QMimeData objects
associate the data that they hold with the corresponding MIME
types to ensure that information can be safely transferred between
applications, and copied around within the same application.
We retrieve the data associated with the \c application/x-fridgemagnet
MIME type using a data stream in order to create a new \c DragLabel
object.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 10
The QDataStream class provides serialization of binary data to a
QIODevice (a data stream is a binary stream of encoded information
which is completely independent of the host computer's operating
system, CPU or byte order).
Finally, we create a label and move it to the event's position:
\snippet draganddrop/fridgemagnets/dragwidget.cpp 11
If the source of the event is also the widget receiving the
drop event, we set the event's drop action to Qt::MoveAction and
call the event's \l{QDragMoveEvent::}{accept()}
method. Otherwise, we simply accept the proposed action. This
means that labels are moved rather than copied in the same
window. However, if we drag a label to a second instance of the
Fridge Magnets example, the default action is to copy it, leaving
the original in the first instance.
If the event's MIME type is \c text/plain (i.e., if
QMimeData::hasText() returns true) we retrieve its text and split
it into words. For each word we create a new \c DragLabel action,
and show it at the event's position plus an offset depending on
the number of words in the text. In the end we accept the proposed
action. This lets the user drop selected text from a text editor or
Web browser onto the widget to add more fridge magnets.
\snippet draganddrop/fridgemagnets/dragwidget.cpp 12
If the event has any other type, we call the event's
\l{QDragMoveEvent::}{ignore()} method allowing the event to be
propagated further.
\section1 Summary
We set our main widget's \l{QWidget::}{acceptDrops} property
and reimplemented QWidget's \l{QWidget::}{dragEnterEvent()},
\l{QWidget::}{dragMoveEvent()} and \l{QWidget::}{dropEvent()} event
handlers to support content dropped on our widget.
In addition, we reimplemented the \l{QWidget::}{mousePressEvent()}
function to let the user pick up fridge magnets in the first place.
Because data is communicated using drag and drop operations and
encoded using MIME types, you can run more than one instance of this
example, and transfer magnets between them.
*/