Search code examples
qtdrag-and-dropqgraphicsviewqgraphicssceneqevent

Detect Drag on Drop over all Child QWidgets


I'm trying to implement Drag and Drop behavior, across my whole application, I would like to execute some action when a drop occurs, no mater where in the MainWindow. The Problem that I'm facing is that in my MainWindow, I have a Widget which contains a QGraphicsView, which again contains a QGraphicsScene, I can detect the drop anywhere in the application via EventFilter, except in the in the View and Scene. Is it possible to achieve some kind of global drag and drop behavior which I can detect from the MainWindow? I'm trying to avoid spreading the Logic across all my visible Widgets.

This is the drag and drop logic in my MainWindow, works, as said, for all drag and drops that happen not over the View/Scene:

void MainWindow::dropEvent(QDropEvent *event)
{
    auto pixmap = QPixmap(event->mimeData()->urls().first().toString().remove("file://"));
    if(!pixmap.isNull()) {
        load(pixmap);
    }

    event->acceptProposedAction();
}

void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
    }
}

For the drag and drops over the View/Scene I've tried to install following event filter:

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    if(event->type() == QEvent::DragEnter) {
        qDebug("Enter");
    } else if(event->type() == QEvent::Drop) {
        qDebug("Drop");
    } else if(event->type() == QEvent::GraphicsSceneDrop)   {
        qDebug("Scene drop");
    } else if (event->type() >= 159 && event->type() <= 168) {
        qDebug("Any Scene Event");
    }
    return QObject::eventFilter(obj, event);
}

And apply it in the MainWindow ctor, the only thing that I detect is an enter when it enters the view.

...
setAcceptDrops(true);
mChildWidget->installEventFilter(this);
mChildWidget->setAcceptDrops(true);
...

Solution

  • It looks like I was facing two issues here. The first one is explained in this question here Accepting drops on a QGraphicsScene. The QGraphicsScene ignores DragAndDrops that are not happening over items. Basically you can enable drag and drop but only for evey item individually, which still leaves the arae that is not covered by an item. The solution to this issue seems to be to overwrite dragMoveEvent

    void MyQGprahicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
    {
        Q_UNUSED(event)
    
        // Overriding default dragMoveEvent in order to accept drops without items under them
    }
    

    The second issue was that i was facing is that I was trying to apply the eventFilter to Child widget, but it looks like I had to apply it to qApp, after which everything seemed to work, probably because qApp is the top QObject where all events will land when not handled.

    #include "DragAndDropHandler.h"
    
    bool DragAndDropHandler::eventFilter(QObject *obj, QEvent *event)
    {
        if(event->type() == QEvent::DragEnter) {
            handleDragEnter(dynamic_cast<QDragEnterEvent *>(event));
        } else if(event->type() == QEvent::Drop) {
            handleDrop(dynamic_cast<QDropEvent *>(event));
        } else if(event->type() == QEvent::GraphicsSceneDrop) {
            handleDrop(dynamic_cast<QGraphicsSceneDragDropEvent *>(event));
        } else if (event->type() ==  QEvent::GraphicsSceneDragEnter) {
            handleDragEnter(dynamic_cast<QGraphicsSceneDragDropEvent *>(event));
        }
        return QObject::eventFilter(obj, event);
    }
    
    void DragAndDropHandler::handleDragEnter(QDragEnterEvent *event)
    {
        if (event->mimeData()->hasUrls()) {
            event->acceptProposedAction();
        }
    }
    
    void DragAndDropHandler::handleDragEnter(QGraphicsSceneDragDropEvent *event)
    {
        if (event->mimeData()->hasUrls()) {
            event->acceptProposedAction();
        }
    }
    
    void DragAndDropHandler::handleDrop(QDropEvent *event)
    {
        auto path = getUrlFromMimeData(event->mimeData());
        event->acceptProposedAction();
        emit imageDropped(path);
    }
    
    void DragAndDropHandler::handleDrop(QGraphicsSceneDragDropEvent *event)
    {
        auto path = getUrlFromMimeData(event->mimeData());
        event->acceptProposedAction();
        emit imageDropped(path);
    }
    
    QString DragAndDropHandler::getUrlFromMimeData(const QMimeData *mimeData) const
    {
        return mimeData->urls().first().toString().remove("file://");
    }
    

    And, in the constructor of my MainWindow:

    setAcceptDrops(true);
    qApp->installEventFilter(mDragAndDropHandler);
    connect(mDragAndDropHandler, &DragAndDropHandler::imageDropped, this, &MainWindow::loadImageFromFile);