Qt 4.X: How to Implement Drag-And-Drop Onto the Desktop or into a Folder

Qt 4.x: how to implement drag-and-drop onto the desktop or into a folder?

Look at QMimeData and its documentation, it has a virtual function

virtual QVariant retrieveData  ( const QString & mimetype, QVariant::Type type ) const

this means to do you drag to the outside you implement this functions accordingly

class DeferredMimeData : public QMimeData
{
DeferredMimeData(QString downloadFilename) : m_filename(downloadFilename)

virtual QVariant retrieveData (const QString & mimetype, QVariant::Type type) const
{
if (mimetype matches expected && type matches expected)
{
perform download with m_filename
}
}
}

The delayed encoding examples shows this principle.

You will probably also have to override hasFormat and formats to provide the appropriate types, application/octet-stream probably being the one that might get you the most play, you will probably have to read up on how windows specifically handles drag and drop using mime types.

I don't know how you will supply the file name under which the file is saved, but you will probably have to get into the windows side of things. Looking at the source of QWindowsMime might also help. There might me a multiple step process where you will get requests for text/uri-list data for the filenames and then application/octet-stream for the data.

Hope this helps

How can I drag to the desktop with Qt?

As far as i know there is no cross-platform solution without using temporary files.
For a Windows-only solution you must set the appropriate MIME data:

if(mimeType == "FileName")
{
QString filename;
// YOUR CODE HERE
mimeData->setData("FileName", filename.toLatin1());
}

if(mimeType == "FileNameW")
{
QString filename;
// YOUR CODE HERE
mimeData->setData("FileNameW", QByteArray((const char*) (filename.utf16()), DDFileName.size() * 2));
}
if(mimeType == "FileContents")
{
QByteArray data;
// YOUR CORE HERE
mimeData->setData("FileContents", data);
}
if(mimeType == "FileGroupDescriptorW")
{
QString filename;
// YOUR CODE HERE
FILEGROUPDESCRIPTOR desc;
desc.cItems = 1;
desc.fgd[0].dwFlags = FD_PROGRESSUI;
wcscpy_s(desc.fgd[0].cFileName, filename.toStdWString().c_str());
// make a deep copy here
mimeData->setData("FileGroupDescriptorW", QByteArray((const char*)&desc,
sizeof(FILEGROUPDESCRIPTOR)));
}

How to implement a drag'n drop from a qt application to a file system folder?

That's one glaring issue in Qt's drag and drop scheme: it doesn't provide the equivalent of OS X's "promised data" drag. And there's really no good way to work around it. The dragging code is not informed of where the drop is, so there's no way to copy the data after the fact. And as you've found out, retrieve data is called right at the moment the drag starts, which doesn't help when it takes a long time to prepare the data.

Right now I'd say hacking it with platform specific code is the only way to do it. Unfortunately, I don't have any experience on that.

How to drag and drop files items inside a folder item using QFileSystemModel and QListView?

You are setting the wrong movement property, since you're using Static:

The items cannot be moved by the user.

When using the IconMode, that property is automatically set to Free, so you can just remove the following line:

lv_file_manager.setMovement(QListView.Static)

The other important implementations are in the model's canDropMimeData() (which must return True if the target is a writable directory) and dropMimeData() (that will actually move the files).

The final step is to override the dragMoveEvent() to prevent moving icons around the current view.

Note that the following changes have also been made:

  • flags() should not return ItemIsDragEnabled if the target is a file;
  • setAcceptDrops(True) and setDragEnabled(True) are not required as they are automatically set when the movement is not Static (which is the case when using the IconMode as explained above);
  • setDragDropMode() is also not required;
class ContentFileSystemModel(QFileSystemModel):
# ...
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
defaultFlags = super(ContentFileSystemModel, self).flags(index)
if not index.isValid():
return defaultFlags
fileInfo = self.fileInfo(index)
if fileInfo.isDir():
return Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | defaultFlags
elif fileInfo.isFile():
# files should *not* be drop enabled
return Qt.ItemIsDragEnabled | defaultFlags
return defaultFlags

def canDropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
if row < 0 and column < 0:
target = self.fileInfo(parent)
else:
target = self.fileInfo(self.index(row, column, parent))
return target.isDir() and target.isWritable()

def dropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
if row < 0 and column < 0:
targetDir = QDir(self.fileInfo(parent).absoluteFilePath())
else:
targetDir = QDir(self.fileInfo(self.index(row, column, parent)).absoluteFilePath())
dataList = []
# first check if the source is writable (so that we can move it)
# and that it doesn't already exist on the target path
for url in data.text().splitlines():
path = QUrl(url).toLocalFile()
fileObject = QFile(path)
if not fileObject.permissions() & QFile.WriteUser:
return False
targetPath = targetDir.absoluteFilePath(QFileInfo(path).fileName())
if targetDir.exists(targetPath):
return False
dataList.append((fileObject, targetPath))
# actually move the objects, you might want to add some feedback
# if movement failed (eg, no space left) and eventually undo the
# whole operation
for fileObject, targetPath in dataList:
if not fileObject.rename(targetPath):
return False
return True

class FileView(QListView):
def dragMoveEvent(self, event):
# accept drag movements only if the target supports drops
if self.model().flags(self.indexAt(event.pos())) & Qt.ItemIsDropEnabled:
super().dragMoveEvent(event)
else:
event.ignore()

def main(argv):
# ...
lv_file_manager = FileView()

Drag anything onto the desktop

You should look at writing Shell Extensions. Here's an article to get you started

http://www.codeproject.com/KB/shell/shellextguide1.aspx

Here's another questions that has some relevant answers

Override Drag and drop for Shell Extension

PyQt5 drag and drop into system file explorer (with delayed encoding)?

Here's what I ended up with for dragging and dropping files from PyQt5 into file explorer, and only having the file write once the mouse is released to finalize the drop.


import time

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt

import tempfile
import os

# Use win32api on Windows because the pynput and mouse packages cause lag
# https://github.com/moses-palmer/pynput/issues/390
if os.name == 'nt':
import win32api

def mouse_pressed():
return win32api.GetKeyState(0x01) not in [0, 1]
else:
import mouse

def mouse_pressed():
return mouse.is_pressed()

class DelayedMimeData(QtCore.QMimeData):
def __init__(self):
super().__init__()
self.callbacks = []

def add_callback(self, callback):
self.callbacks.append(callback)

def retrieveData(self, mime_type: str, preferred_type: QtCore.QVariant.Type):
mp = mouse_pressed()
if not mp:
for callback in self.callbacks.copy():
self.callbacks.remove(callback)
callback()

return QtCore.QMimeData.retrieveData(self, mime_type, preferred_type)

class Navigator(QtWidgets.QTreeWidget):
def __init__(self):
super().__init__()

self.setHeaderLabels(["Name"])
QtWidgets.QTreeWidgetItem(self, ['Test1'])
QtWidgets.QTreeWidgetItem(self, ['Test2'])
QtWidgets.QTreeWidgetItem(self, ['Test3'])

self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDragDropMode(self.DragDrop)
self.setDefaultDropAction(Qt.MoveAction)
self.setSelectionMode(self.ExtendedSelection)
self.setSelectionBehavior(self.SelectRows)

self.setContextMenuPolicy(Qt.CustomContextMenu)

def startDrag(self, actions):
drag = QtGui.QDrag(self)
names = [item.text(0) for item in self.selectedItems()]
mime = DelayedMimeData()
path_list = []
for name in names:
path = os.path.join(tempfile.gettempdir(), 'DragTest', name + '.txt')
os.makedirs(os.path.dirname(path), exist_ok=True)
print(path)

def write_to_file(path=path, name=name, widget=self):
with open(path, 'w+') as f:
print("Writing large file(s)...")
time.sleep(2) # Sleep to simulate long file write
f.write(f"Contents of {name}")

mime.add_callback(write_to_file)
path_list.append(QtCore.QUrl.fromLocalFile(path))

mime.setUrls(path_list)
mime.setData('application/x-qabstractitemmodeldatalist',
self.mimeData(self.selectedItems()).data('application/x-qabstractitemmodeldatalist'))
drag.setMimeData(mime)
drag.exec_(Qt.MoveAction)
super().startDrag(actions)

app = QtWidgets.QApplication([])

nav = Navigator()
nav.show()
app.exec_()


Related Topics



Leave a reply



Submit