Sergey Stasishin
Sergey Stasishin

Reputation: 309

QListView external drop doesn't work

I'm trying to implement drag&drop items (plain/text) from one QListView to another. The dragging starts well (I even able to drop items to another applications that accept text drops), but my second QListView doesn't accept drops for some reason. Here is how the list view configured:

ui->lessonsListView->setAcceptDrops(true);
ui->lessonsListView->setDropIndicatorShown(true);
ui->lessonsListView->setDragDropMode(QAbstractItemView::DropOnly);
ui->lessonsListView->setDragDropOverwriteMode(true);

The proxy model for this listView implements the next methods:

Qt::ItemFlags LessonsProxyModel::flags(const QModelIndex &index) const
{
    qDebug() << __FUNCTION__;
    return Qt::ItemIsDropEnabled | QSortFilterProxyModel::flags(index);
}

Qt::DropActions LessonsProxyModel::supportedDropActions() const
{
    qDebug() << __FUNCTION__;
    return Qt::MoveAction;
}

bool LessonsProxyModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    qDebug() << __FUNCTION__;
    Q_UNUSED(action);
    Q_UNUSED(row);
    Q_UNUSED(column);

    if (!data->hasFormat("text/plain") || !parent.isValid())
        return false;

    return true;
}

bool LessonsProxyModel::dropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    qDebug() << __FUNCTION__;
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    emit dataDropped(data, parent);

    return true;
}

From the application output I see that only supportedDropActions() and flags() are called. Neither canDropMimeData() nor dropMimeData() ever called. What am I doing wrong? Any hints will be appreciated.

Thank you!

EDITED:

Just in case: below is source code for listView and model from those drag is initiated: listView setup:

ui->abonsListView->setDragEnabled(true);

proxyModel code:

Qt::ItemFlags AbonsProxyModel::flags(const QModelIndex &index) const
{
    return Qt::ItemIsDragEnabled | QSortFilterProxyModel::flags(index);
}

Qt::DropActions AbonsProxyModel::supportedDragActions() const
{
    qDebug() << __FUNCTION__;
    return Qt::MoveAction;
}

QStringList AbonsProxyModel::mimeTypes() const
{
    qDebug() << __FUNCTION__;
    QStringList types;
    types << "text/plain";
    return types;
}

QMimeData *AbonsProxyModel::mimeData(const QModelIndexList &indexes) const
{
    qDebug() << __FUNCTION__;
    QMimeData *mimeData = new QMimeData();

    foreach (const QModelIndex &index, indexes)
        if (index.isValid())
        {
            mimeData->setText(data(index, AbonsModel::Id).toString());
            qDebug() << __FUNCTION__;
            return mimeData;
        }

    return mimeData;
}

Upvotes: 2

Views: 2106

Answers (2)

Sergey Stasishin
Sergey Stasishin

Reputation: 309

I've finally found an answer! When I started writing this code I copypasted some pieces from Qt docs Using Drag and Drop with Item Views and in this article they just missed const specifier for reimplementation of canDropMimeData(). Hence my version of canDropMimeData() became non-virtual and QListView just called the method from base class QAbstractProxyModel. I've added const - and all is working like a charm without any subclassing.

Upvotes: 2

Marcus
Marcus

Reputation: 1703

I have not much experience with proxy models. Going through the docs, I think I have an rough idea of what these two functions are. These functions do not get called automatically. You have to "call" them from your view.

Firstly, checking if you have the right data is the model's work. So these are two convenience functions in the model to ease your work. These are model specific functions and not generic functions and hence are never defined for an abstract model.

Consider this scenario. From somewhere a QMimeData object is dropped into your view. How do you know if this data is of the right type? Whose work is it to decide if the data is of the right type?

In the Model-View framework, View does only painting work. It's upto the Model to decide things like how to process the data, what to show, what is acceptable and what is not. So when a piece of data is dropped on the View you need to send it to the Model to check if the dropped data satisfies the Model's requirement. The hook to do this work is bool Model::canDropMimeData(...).

If it is acceptable, then the processing of the data will also need to be done by the Model itself. Again you need to send the data to the Model. The hook for this is bool Model::dropMimeData(...).

How do you know if the data was processed successfully? Check return value! If the data was processes successfully, then you most probably need to update your View.

So in addition to the code you have above, you MUST override the dragEnterEvent, dragMoveEvent and dropEvent of the receiving View so that you can call canDropMimeData(...) and dropMimeData(...)

/* You need to allow the drag to enter the widget/view. */
/* This is a must. */
void TargetView::dragEnterEvent( QDragEnterEvent *deEvent ) {

    deEvent->acceptProposedAction();
};

/* This is optional. Use this to check if the data can be */
/* dropped at the current mouse position. Example: In a */
/* FileSystem View, it makes no sense to drop a bunch of */
/* files on top of a file, but makes sense it you drop it */
/* in an empty space or on a folder */
void TargetView::dragMoveEvent( QDragMoveEvent *dmEvent ) {
    /* You can do something like this */
    if ( indexAt( dmEvent->pos() ).isValid() ) {
        /* You cannot drop it on an existing index */
        /* If you ignore it, the cursor changes to */
        /* 'don't drop' image */
        dmEvent->ignore();
    }

    else {
        /* Empty space. Tell the user to feel free */
        /* to perform the drop. We accept the event */
        /* Cursor shows the drag+drop icon */
        dmEvent->accept();
    }
};

void TargetView::dropEvent( QDropEvent *dpEvent ) {
    /* Here is where you call canDropMimeData and dropMimeData */
    QMimeData *mData = dpEvent->mimeData();
    if ( model->canDropMimeData( mData, ..., ..., ..., ... ) ) {
        /* Add the data to the model */
        bool updated = model->dropMimeData( mData, ..., ..., ..., ... );
        if ( updated ) {
            /* Intimate the view to update itself */
            update();
        }
    }
};

Upvotes: 0

Related Questions