I'm trying to implement a file directory using PyQt5. I'd like to incorporate drag and drop functionality within this tree to support both internal and external files (i.e. if I had some files on my desktop I'd like to be able to drop them into a folder in my PyQt view). This is what I have currently:
from PyQt5.QtWidgets import QTreeView,QFileSystemModel,QApplication,
QMenu, QAbstractItemView
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from src import config
class Tree(QTreeView):
def __init__(self):
QTreeView.__init__(self)
cfg = config.get()
model = QFileSystemModel()
model.setRootPath("/Users/")
self.setModel(model)
self.setRootIndex(model.index("/Users/"))
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.open_menu)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setDropIndicatorShown(True)
def open_menu(self):
menu = QMenu()
menu.addAction("Create new folder")
menu.exec_(QCursor.pos())
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Main()
w.show()
sys.exit(app.exec_())
With the above code I can show a directory and its contents. I can drag an item but dropping it does nothing and no drop indication is seen. It's unclear how to
a. get drag and drop working within the view and
b. get it working with items outside of that context (like from the desktop for example).
According to the docs:
readOnly : bool
This property holds whether the directory model allows writing to the file system
If this property is set to false, the directory model will allow
renaming, copying and deleting of files and directories.
This property is true by default
so if you want to be able to move files you must set it to False:
model.setReadOnly(False)
You must overwrite the dragEnterEvent
method and if there is a QUrl
relative to a local resource you must accept it.
Then you have to overwrite the dropEvent
method and if the event does not have source then it implies that it comes from an external source as a local file, then a logic is implemented to move files or directories checking if the file or directory exists or not creating the new one path.
class Tree(QTreeView):
def __init__(self):
QTreeView.__init__(self)
model = QFileSystemModel()
model.setRootPath(QDir.currentPath())
self.setModel(model)
self.setRootIndex(model.index(QDir.currentPath()))
model.setReadOnly(False)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setDropIndicatorShown(True)
def dragEnterEvent(self, event):
m = event.mimeData()
if m.hasUrls():
for url in m.urls():
if url.isLocalFile():
event.accept()
return
event.ignore()
def dropEvent(self, event):
if event.source():
QTreeView.dropEvent(self, event)
else:
ix = self.indexAt(event.pos())
if not self.model().isDir(ix):
ix = ix.parent()
pathDir = self.model().filePath(ix)
m = event.mimeData()
if m.hasUrls():
urlLocals = [url for url in m.urls() if url.isLocalFile()]
accepted = False
for urlLocal in urlLocals:
path = urlLocal.toLocalFile()
info = QFileInfo(path)
n_path = QDir(pathDir).filePath(info.fileName())
o_path = info.absoluteFilePath()
if n_path == o_path:
continue
if info.isDir():
QDir().rename(o_path, n_path)
else:
qfile = QFile(o_path)
if QFile(n_path).exists():
n_path += "(copy)"
qfile.rename(n_path)
accepted = True
if accepted:
event.acceptProposedAction()
In your tree view subclass you must implement dragEnterEvent
dragMoveEvent
and dropEvent
.
class Tree(QTreeView):
def __init__(self):
QTreeView.__init__(self)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
# to get a list of files:
drop_list = []
for url in event.mimeData().urls():
drop_list.append(str(url.toLocalFile()))
# handle the list here
else:
event.ignore()