CopperSpice API  1.9.2
Drag and Drop

Classes used to transfer data between applications. More...

Classes

class  QDragEnterEvent
 Event which is sent to a widget when a drag and drop action enters it More...
 
class  QDragLeaveEvent
 Event that is sent to a widget when a drag and drop action leaves it More...
 
class  QDragMoveEvent
 Event which is sent while a drag and drop action is in progress More...
 
class  QDropEvent
 Event which is sent when a drag and drop action is completed More...
 

Detailed Description

Drag and drop is a common UI feature which allows transferring data between applications. Drag and drop is similar to the clipboard's cut and paste mechanism. This section describes the basic drag and drop system and describes how to enable it in custom widgets.

Drag and drop operations are also supported by item views and the graphics view system. More information is available in Drag and Drop and Graphics View System.

Configuration

The QApplication object provides some properties that are related to drag and drop operations:

These quantities provide sensible default values for you to use if you provide drag and drop support in your widgets.

Dragging

To start a drag, create a QDrag object, and call its exec() function. In most applications, it is a good idea to begin a drag and drop operation only after a mouse button has been pressed and the cursor has been moved a certain distance. However, the simplest way to enable dragging from a widget is to reimplement the widget's mousePressEvent() and start a drag and drop operation:

void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && iconLabel->geometry().contains(event->pos())) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(commentEdit->toPlainText());
drag->setMimeData(mimeData);
drag->setPixmap(iconPixmap);
Qt::DropAction dropAction = drag->exec();
// ...
}
}

Although the user may take some time to complete the dragging operation, as far as the application is concerned the exec() function is a blocking function which returns with one of the values described in Qt::DropAction. These indicate how the operation ended, and are described in more detail below.

The exec() method does not block the main event loop.

For widgets that need to distinguish between mouse clicks and drags, it is useful to reimplement the widget's mousePressEvent() method to record to start position of the drag as shown below.

void DragWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
dragStartPosition = event->pos();
}
}

Later, in mouseMoveEvent(), we can determine whether a drag should begin, and construct a drag object to handle the operation.

void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton)) {
return;
}
if ((event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance()) {
return;
}
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData(mimeType, data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
// ...
}

This particular approach uses the QPoint::manhattanLength() function to get a rough estimate of the distance between where the mouse click occurred and the current cursor position. This function trades accuracy for speed, and is usually suitable for this purpose.

Dropping

To be able to receive media dropped on a widget, call setAcceptDrops(true) for the widget, and reimplement the dragEnterEvent() and dropEvent() event handler functions. For example, the following code enables drop events in the constructor of a QWidget subclass, making it possible to usefully implement drop event handlers.

Window::Window(QWidget *parent)
: QWidget(parent)
{
setAcceptDrops(true);
}

The dragEnterEvent() function is typically used to inform CopperSpice about the types of data that the widget accepts. You must reimplement this function if you want to receive either QDragMoveEvent or QDropEvent in your reimplementations of dragMoveEvent() and dropEvent(). The following code shows how dragEnterEvent() can be reimplemented to tell the drag and drop system that we can only handle plain text.

void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain")) {
event->acceptProposedAction();
}
}

The dropEvent() is used to unpack dropped data and handle it in way that is suitable for your application. In the following code, the text supplied in the event is passed to a QTextBrowser and a QComboBox is filled with the list of MIME types that are used to describe the data.

void Window::dropEvent(QDropEvent *event)
{
textBrowser->setPlainText(event->mimeData()->text());
mimeTypeCombo->clear();
mimeTypeCombo->addItems(event->mimeData()->formats());
event->acceptProposedAction();
}

In this case, we accept the proposed action without checking what it is. In a real world application, it may be necessary to return from the dropEvent() function without accepting the proposed action or handling the data if the action is not relevant. For example, we may choose to ignore Qt::LinkAction actions if we do not support links to external sources in our application.

Overriding Proposed Actions

We can ignor the proposed action and perform some other action on the data. To do this, call the event object's setDropAction() with the preferred action from Qt::DropAction before calling accept(). This ensures the replacement drop action is used instead of the proposed action.

For some applications reimplementing dragMoveEvent() and dragLeaveEvent() will let you make certain parts of your widgets sensitive to drop events and provide more control over the drag and drop process.

Subclassing Complex Widgets

Certain standard CopperSpice widgets provide their own support for drag and drop. When subclassing these widgets, it may be necessary to reimplement dragMoveEvent() in addition to dragEnterEvent() and dropEvent() to prevent the base class from providing default drag and drop handling, and to handle any special cases you are interested in.

Drag and Drop Actions

One way to drag and drop is where the target receives a copy of the data and the source will decide if the original data should be deleted. To use the copy approach pass Qt::CopyAction to QDrag::start(). The target may also choose to handle other actions like Qt::MoveAction and Qt::LinkAction. If the source calls QDrag::exec() and it returns Qt::MoveAction the source will be responsible for deleting the original data.

QMimeData and QDrag objects created by the source should not be deleted. They will be deleted auotmatically. The target is responsible for taking ownership of the data sent in the drag and drop operation.

Refer to the section on Drop Actions for more information.

Adding New Drag and Drop Types

Drag and drop is not limited to text or images. To drag information between applications, both programs must be able to negotiate a common data format they can use to exchange data. The Internet Assigned Numbers Authority (IANA) provides a list of MIME media types. Using a standard MIME type maximizes the interoperability of your application with other software. Refer to the MIME types for more information.

A QDrag object is constructed by the source program contains a list of MIME types. This list is sorted from most appropriate to least appropriate. The drop target must use one of these MIME types to receive the data stored in the QDrag object. There are several methods in QMimeData to support the most commonly used MIME types.

To implement drag and drop actions for a data type which is not supported in QDrag, check to see if there is an existing MIME type defined by the IANA. To support a custom type set the data in the QMimeData object by calling setData(). Pass the full MIME type and a QByteArray containing the data. The following example uses a pixmap and stores it as a PNG in a QMimeData object.

QByteArray output;
QBuffer outputBuffer(&output);
outputBuffer.open(QIODevice::WriteOnly);
imageLabel->pixmap()->toImage().save(&outputBuffer, "PNG");
mimeData->setData("image/png", output);

Drop Actions

When using drag and drop users can either copy an item or move the item to a new location. The application will not know whether the user wants to cut or copy the item until the drop operation occurs. This makes no difference when dragging between applications, however an application it is important to check which drag action was used.

In this example the mouseMoveEvent() is reimplemented for a custom widget. This method is invoked while the mouse is being moved to capture information and begin a drag operation. The implementation of this method allow the user to drag and drop by copying the item or moving the item.

void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
if (! (event->buttons() & Qt::LeftButton)) {
return;
}
if ((event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance()) {
return;
}
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData(mimeType, data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
// ...
}

The action returned by the call to exec() may default to a CopyAction if the information is dropped into another application. However if it is dropped on another widget in the same application, we might obtain a different drop action. The proposed drop actions can be filtered in a widget's dragMoveEvent() method.

When a drop occurs on our custom widget the dropEvent() method is called.

void DragWidget::dropEvent(QDropEvent *event)
{
if (event->source() == this && event->possibleActions() & Qt::MoveAction) {
return;
}
if (event->proposedAction() == Qt::MoveAction) {
// process the data from the event
event->acceptProposedAction();
} else if (event->proposedAction() == Qt::CopyAction) {
// process the data from the event
event->acceptProposedAction();
} else {
// ignore the drop
return;
}
}

If you want to override the default drop action, call possibleActions() and ensure the desired drop action is available in the return value. Then set the drop action by calling setDropAction(), then call accept().

>Drop Rectangles

If a custom widget only supports a drop operation in a specific region the widget must override the dragMoveEvent() method. The implementation of this method should only accept the action if the cursor is in the valid region. The dragMoveEvent() can also be used if you need to give visual feedback during a drag and drop operation, to scroll the window, or whatever is appropriate.

The following code presumes m_dropFrame was declared in the class MyWindow and points to a valid QFrame.

// QFrame *m_dropFrame declared in class MyWindow
void MyWindow::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("text/plain") && event->answerRect().intersects(m_dropFrame->geometry())) {
event->acceptProposedAction();
}
}

Clipboard

Applications can communicate with each other by putting data on the clipboard. The QMimeData class is used to represent data which is transferred to and from the clipboard. To put data on the clipboard use the setText(), setImage(), and setPixmap() methods for common data types. These methods are similar to ones in the QMimeData class except they also have an additional argument which is used to control where the data is stored.

If the enum QClipboard::Clipboard is specified then data is placed on the clipboard. If QClipboard::Selection is specified the data is placed in the mouse selection (on X11 only). By default the data will be saved to the clipboard.

As an example, the contents of a QLineEdit can be copied to the clipboard using the following code.

clipboard->setText(lineEdit->text(), QClipboard::Clipboard);

Data with different MIME types can also be put on the clipboard. Construct a QMimeData object and set data with setData() in the way described in the previous section. This object can then be put on the clipboard by calling setMimeData().

The QClipboard class can notify the application about changes to the data it contains via its dataChanged() signal. For example, we can monitor the clipboard by connecting this signal to a slot in a widget.

connect(clipboard, SIGNAL(dataChanged()), this, SLOT(updateClipboard()));

The slot connected to this signal can read the data on the clipboard using one of the MIME types.

void ClipWindow::updateClipboard()
{
QStringList formats = clipboard->mimeData()->formats();
QByteArray data = clipboard->mimeData()->data(format);
// ...
}

The selectionChanged() signal can be used on X11 to monitor the mouse selection.

Interoperating with Other Applications

On X11 the public XDND protocol is used, on Windows the OLE standard is used, and OS X the Carbon Drag Manager is used. On X11, XDND uses MIME, so no translation is necessary. The API is the same regardless of the platform.

On Windows, MIME-aware applications can communicate by using clipboard format names that are MIME types. Some Windows applications use MIME naming conventions for their clipboard formats. Internally CopperSpice uses QWindowsMime and QMacPasteboardMime for translating proprietary clipboard formats to and from MIME types.

On X11 CopperSpice also supports drops via the Motif Drag and Drop Protocol.