CopperSpice API  1.9.1
Drag and Drop

Using Drag and Drop in Item Views

The CopperSpice drag and drop system is fully supported by the model/view architecture. Items in lists, tables, and trees can be dragged within the views and data can be imported and exported as MIME-encoded data. Drag and drop operations are not initially enabled.

To allow items to be dragged, certain properties of the view need to be enabled and the model must also allow dragging.

For a model to support dragging requires implementing one method and to support dropping required implementing three additional methods. These methods are all implemented in the QStandardItemModel.

Refer to the Inheriting from a Model for more information about enabling drag and drop support in custom models.

Enabling Drag and Drop

For custom models the method for QAbstractItemModel::flags() must be implemented. The return value is an enum which indicates if the item is a valid source for dragging or a target for dropping. For example, a custom model which inherits from QAbstractListModel can enable drag and drop for every item by returning flags containing Qt::ItemIsDragEnabled and Qt::ItemIsDropEnabled.

Items can be dropped into the top level of a view but dragging is only enabled for valid items.

Qt::ItemFlags MyDragDropListModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QStringListModel::flags(index);
if (index.isValid()) {
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
} else {
return Qt::ItemIsDropEnabled | defaultFlags;
}
}

Accepting Dropped Items

When a drag and drop operation in a view is requested, the model will be queried to determine which operations it supports and the MIME types it will accept. This query is made by calling the QAbstractItemModel::supportedDropActions() and QAbstractItemModel::mimeTypes() methods. If a model does not provide an implementation of these methods the default implementation from QAbstractItemModel will be used and only supports copy operations and the default internal MIME type.

When serialized data is dropped in a view the data is inserted in the current model by calling QAbstractItemModel::dropMimeData(). The default implementation of this method will never overwrite data in the model. Instead, this method inserts the new item(s) as a sibling of a existing item or as a child of an existing item.

To support drag operations it will be necessary to implement the following methods which remove data from the model.

  • removeRows()
  • removeRow()
  • removeColumns()
  • removeColumn()

To use the QAbstractItemModel default implementation and support the built-in MIME type, custom models must implement the following four methods.

insertRows() Enables the model to insert new data
insertColumns()
setData() Allows the new rows and columns to be populated with items
setItemData() Provides efficient support for populating new items

To accept other MIME types these methods must be implemented.

supportedDropActions() Return a combination of the Qt::DropAction values which indicate the types of drag and drop operations this model accepts
mimeTypes() Return a list of MIME types which the model will accept
dropMimeData() Decodes the data received by the drop operation, determines where in the model it should be added, and inserts new rows and columns

If the implementation of dropMimeData() changes the contents of a model, by inserting or removing rows or columns, or by modifying existing data, the model must emit all relevant signals.

Custom Model to handle Dropped Data

The following example shows an implementation of QAbstractItemModel::dropMimeData() which is part of a custom model. The class MyDragDropListModel inherits from QStringListModel and the view would be a simple one column list of strings.

The implementation of this method first checks if the operation is valid and the data was supplied in a format which can be used. If the data does not have the correct built-in MIME type or column number for the drop is invalid, the method returns false and the drop does not happen. In this example the dropped data can be inserted between existing items, before the first item, or after the last item.

bool MyDragDropListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
// validation check
if (action == Qt::IgnoreAction) {
return true;
}
if (! data->hasFormat("application/vnd.text.list")) {
// vendor mime type
return false;
}
if (column > 0) {
return false;
}
// where should the drop data be inserted
int beginRow;
if (row != -1) {
beginRow = row;
} else if (parent.isValid()) {
beginRow = parent.row();
} else {
beginRow = rowCount(QModelIndex());
}
// decode the data and insert the result into the model (see below)
}

Refer to Decode Imported Data

Encoding Exported Data

When items are dragged from a model they must be encoded into some format corresponding to one or more MIME types. Models declare the MIME types they can export by implementing the QAbstractItemModel::mimeTypes() method which must return a list of MIME types.

This example shows a model which only provides support for the plain text MIME type.

QStringList MyDragDropListModel::mimeTypes() const
{
QStringList types = { "text/plain" };
return types;
}

The model must also implement the method mimeData() to encode the data in the required format. The following code shows how each item of data, corresponding to a given list of indexes, is encoded as plain text and stored in a QMimeData object.

QMimeData *MyDragDropListModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}

Since a list of model indexes is supplied this approach is general enough to be used in both hierarchical and non-hierarchical models. Custom data types must be declared as meta objects and stream operators must be implemented for them.

Decoding Imported Data

The last part of implementing the MyDragDropListModel::dropMimeData() method is to decode the dropped data and then insert it into the model. The following code shows only these two parts.

// decode dropped data
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (! stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}
// insert result into the model
insertRows(beginRow, rows, QModelIndex());
for (const QString &text : newItems) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
++beginRow;
}
return true;