Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reject external files dragged in a DropArea without breaking the DropArea

Tags:

qt

qml

qtquick2

In my application I'm displaying a list of audio files and the user can drag an external file to add it to the list. I want to be able to refuse the drag if no file in the list is supported by my application.

The issue is that when I call drag.accepted = false; in onEntered of my DropArea then it becomes completely unresponsive to any other event.

Here is some sample code showing the issue. If you drag an MP3 in the window you see that it works. Then if you drag any other file it won't work, as expected. But then dragging an MP3 file back will not work either.

import QtQuick 2.1
import QtQuick.Window 2.0

ApplicationWindow {
  title: qsTr("Hello World")
  width: 640
  height: 480

  DropArea {
    anchors.fill: parent
    onEntered: {
      console.log("[Droparea] entered");

      // Ensure at least one file is supported before accepted the drag
      var validFile = false;
      for(var i = 0; i < drag.urls.length; i++) {
        if(validateFileExtension(drag.urls[i])) {
          validFile = true;
          break;
        }
      }

      if(!validFile) {
        console.log("No valid files, refusing drag event");
        drag.accepted = false;
        return false;
      }
    }

    onExited: {
      console.log("[Droparea] entered");

    }

    onDropped: {
      console.log("[Droparea] dropped");
    }

    // Only MP3s
    function validateFileExtension(filePath) {
      var extension = filePath.split('.').pop();
      var valid = false;

      if(extension == "mp3") {
        valid = true;
      }

      return valid;
    }
  }

  Text {
    id: textDrop
    anchors.centerIn: parent
    text: "Please drag element"
  }

}

Is there a bug in the DropArea or did I misunderstood something? I know I can filter the files in the onDropped but then you loose the visual feedback you get on OSX when dragging file on an area that does not accept them.

like image 939
koopajah Avatar asked Apr 01 '14 13:04

koopajah


1 Answers

It has been a known bug for a long time. A patch has been submitted and after been stalled for several months is now merged into 5.6 branch.


Anyone who wants to use this functionality MUST upgrade to Qt 5.6 or MANULLY integrate the available patch into his/her Qt version.


QQuickDropAreaPrivate, contained in DropArea, updates the containsDrag flag to true when a dragEnterEvent occurs, emitting the entered signal. It updates containsDrag to false when adragLeaveEvent occurs, emitting an exited signal. However, when the drag event is not accepted dragLeaveEvent is never called, leaving the private object in a incosistent state. Each subsequent dragEnterEvent is discarded since containsDrag is still true, i.e. the previous drag event is still considered active and the entered is no more emitted.

Since the issue is related to an interaction between private APIs and usage of the public APIs, the problem does not affect filtering using keys. Unfortunately, this approach does not seem to fit for the presented use case.

A quite partial workaround is to use a MouseArea along with the DropArea. The latter disables itself when a rejection occurs while the former enables back the DropArea for the next drop. This workaround covers the common case in which a wrong item is dropped inside the DropArea, which is the most common and intuitive for an end user. Releasing the wrong item outside the DropArea invalidate the mechanism (until the next drop).

Here's the code:

import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Window 2.0

ApplicationWindow {
    title: qsTr("Hello World")
    width: 640
    height: 480
    visible: true

    MouseArea {
        anchors.fill: parent
        hoverEnabled: true
        enabled: !drop.enabled
        onContainsMouseChanged: drop.enabled = true
    }

    DropArea {
        id: drop
        anchors.fill: parent

        onEntered: {
            console.log("[Droparea] entered");
            // Ensure at least one file is supported before accepted the drag
            for(var i = 0; i < drag.urls.length; i++)
                if(validateFileExtension(drag.urls[i]))
                    return
            console.log("No valid files, refusing drag event")
            drag.accept()
            drop.enabled = false
        }

        onExited: console.log("[Droparea] exited")

        onDropped: console.log("[Droparea] dropped")

        // Only MP3s
        function validateFileExtension(filePath) {
            return filePath.split('.').pop() == "mp3"
        }
    }

    Text {
        id: textDrop
        anchors.centerIn: parent
        text: "Please drag element"
    }
}
like image 93
BaCaRoZzo Avatar answered Oct 31 '22 09:10

BaCaRoZzo