Monday 9 November 2020

How to get the drop target when the drop occurs outside the application?

I have a python qt5 program with a QTreeWidget. Each QTreeWidgetItem represents files. I have the logic working correctly within mouseMoveEvent so that QDrag CopyAction passes the file mime data successfully to windows when the user drags it to windows explorer or the desktop. However, I need to know what that target path is. The QDrag target() function only returns a widget if the user drags within the application. If the user drags outside the application (like the desktop), then target() returns None.

Is there anyway to know the path the user dragged to in windows?

The reason I need to know the target path is because the file will actually be generated at runtime and the generation can take as long as a minute to complete. In this mvce example I am using touch command to instantly create an empty file so the user doesn't have to wait or see a long delay. Then after the actual file is generated a minute later, I want to replace the empty file with the actual file. My application can then tell the user that the file is now available. I can not pre-generate the file because there will be many in the list and each will take minutes to generate.

Here is my mvce (minimum reproducible example):

example.py

#!python3

import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QTreeWidgetItem
from main_ui import Ui_MainWindow   

class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self, parent = None):
        super(MainWindow, self).__init__(parent)   # initialization of the superclass
        self.setupUi(self)   # setup the GUI --> function generated by pyuic4

        self.tree1.blockSignals(True)  

        self.tree1.clear()
        self.tree1.setHeaderHidden(True)
        self.tree1.setColumnCount(2)
        self.tree1.setRootIsDecorated(True)  # show expansion icon

        level1 = QTreeWidgetItem(self.tree1)

        level1.setText(0, "root")
        level1.setExpanded(True)

        level2 = QTreeWidgetItem(level1)
        level2.setExpanded(False)
        level2.setText(0, "dragme to desktop")
        level2.setExpanded(True)

        self.tree1.show()

        self.tree1.resizeColumnToContents(0)

        self.tree1.blockSignals(False)  

        self.show()

if __name__ == "__main__":

    app = QApplication(sys.argv)

    myapp = MainWindow()      # instantiate the main window
    rc = app.exec_()
    sys.exit(rc)   # exit with the same return code of Qt application

main.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>415</width>
    <height>451</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="mytree" name="tree1">
      <property name="baseSize">
       <size>
        <width>0</width>
        <height>0</height>
       </size>
      </property>
      <property name="styleSheet">
       <string notr="true"/>
      </property>
      <property name="verticalScrollBarPolicy">
       <enum>Qt::ScrollBarAlwaysOff</enum>
      </property>
      <property name="horizontalScrollBarPolicy">
       <enum>Qt::ScrollBarAlwaysOff</enum>
      </property>
      <property name="autoScrollMargin">
       <number>16</number>
      </property>
      <property name="showDropIndicator" stdset="0">
       <bool>false</bool>
      </property>
      <property name="dragEnabled">
       <bool>true</bool>
      </property>
      <property name="dragDropMode">
       <enum>QAbstractItemView::DragDrop</enum>
      </property>
      <property name="alternatingRowColors">
       <bool>false</bool>
      </property>
      <property name="verticalScrollMode">
       <enum>QAbstractItemView::ScrollPerPixel</enum>
      </property>
      <property name="uniformRowHeights">
       <bool>true</bool>
      </property>
      <property name="columnCount">
       <number>0</number>
      </property>
      <attribute name="headerVisible">
       <bool>true</bool>
      </attribute>
      <attribute name="headerCascadingSectionResizes">
       <bool>false</bool>
      </attribute>
      <attribute name="headerDefaultSectionSize">
       <number>100</number>
      </attribute>
      <attribute name="headerStretchLastSection">
       <bool>false</bool>
      </attribute>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <customwidgets>
  <customwidget>
   <class>mytree</class>
   <extends>QTreeWidget</extends>
   <header>mytree.h</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

mytree.py

#!python3

import tempfile
from pathlib import Path
from PyQt5 import QtGui 
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt

class mytree(QtWidgets.QTreeWidget):   # mytree is now a subclass of qlineedit class

    def __init__(self, parent=None):
        super(mytree, self).__init__(parent)  # The use of "super" is a slightly better method of calling the parent for initialization
        self.setDragEnabled(True)

    def mouseMoveEvent(self, event):  # is called whenever the mouse moves while a mouse button is held down
        super(mytree, self).mouseMoveEvent(event)   #propagate

        tempdir = tempfile.gettempdir() # temp directory aka %temp%
        realfile = tempdir + "/empty.txt"
        Path(realfile).touch()  # create empty file

        fileurl = "file:///%s" % realfile  # build url
        url = QtCore.QUrl(fileurl)
        urllist = []
        urllist.append(url)  # only 1 file 

        drag = QtGui.QDrag(self)

        md = QtCore.QMimeData()
        md.setUrls(urllist)
        drag.setMimeData(md)    
        result = drag.exec_(Qt.CopyAction)     

        source = drag.source()
        print("source = %s" % source)
        target = drag.target()   # returns widget if inside the application, returns None if windows desktop ... etc.
        print("target = %s" % target)


from How to get the drop target when the drop occurs outside the application?

No comments:

Post a Comment