Drag and Drop not working in `QFileModelSystem`

324 Views Asked by At

I'm trying to make a drag and drop behavior in QFileSystemModel but because I have no experience in making a drag and drop before, I tried it first on QTreeView. (I attached the video of the behavior)

enter image description here

Now that I'm fine with the behavior I want, I then just changed the model to QFileSystemModel but sadly It's not working. So I tried to read the QFileSystemModel, QTreeView, and Drag and Drop from Qt and I ended up with the code below:

The code I ended up with:

import os
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class MQTreeView(QTreeView):
    def __init__(self, model):
        super().__init__()
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        # self.setDragDropMode(QAbstractItemView.InternalMove)
        self.setModel(model)
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setRootIndex(model.index(os.path.dirname(os.path.abspath("__file__"))))
        self.setDefaultDropAction(Qt.MoveAction)
        self.viewport().setAcceptDrops(True)

    def dragEnterEvent(self, event):
        m = event.mimeData()
        if m.hasUrls():
            event.accept()
            return
        event.ignore()
        # return super().dragEnterEvent(event)

    def dropEvent(self, event):
        print("[drop event] - dropped")
        if event.source():
            QTreeView.dropEvent(self, event)
        else:
            ix = self.indexAt(event.pos())
            model = self.model()

            if ix.isValid():
                if not model.isDir(ix):
                    ix = ix.parent()      # In case of folder/Dir
                pathDir = model.filePath(ix)
            else:
                # for empty drag and drop
                pathDir = model.rootPath()

            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)
                        print(f"added -> {info.fileName()}")

                    accepted = True
                if accepted:
                    event.acceptProposedAction()

        # return super().dropEvent(event)

class AppDemo(QWidget):
    def __init__(self):
        super().__init__()
        # -- right -- #
        self.model1 = QFileSystemModel()
        self.model1.setRootPath(os.path.dirname(os.path.abspath("__file__")))

        self.view1 = MQTreeView(self.model1)

        # -- left -- #
        self.model2 = QFileSystemModel()
        self.model2.setRootPath(os.path.dirname(os.path.abspath("__file__")))

        self.view2 = MQTreeView(self.model2)

        # -- layout -- #
        layout = QHBoxLayout(self)
        layout.addWidget(self.view1)
        layout.addWidget(self.view2)

app = QApplication(sys.argv)
main = AppDemo()
main.show()
app.exec_()

The code above is still not doing the behavior I want but I'm pretty sure that something else is wrong and it is not with the overridden function (dragEnterEvent and dropEvent). My best guess is that I didn't set properly the correct way QTreeView accepts the drops although I'm not really sure.

My Question: What is wrong with my Implementation? Is it the way I accept drops or it is something else?

1

There are 1 best solutions below

0
On BEST ANSWER

Found what's wrong! I didn't override the dragMoveEvent method. I need to override the dragMoveEvent to make sure that the drag will not be forbidden.

I need to accept all drag event in the dragEnterEvent:

def dragEnterEvent(self, event):
    event.accept()

Then I need to filter the events in the dragMoveEvent:

def dragMoveEvent(self, event):
    m = event.mimeData()
    if m.hasUrls():
        event.accept()
        print("[dropEnterEvent] - event accepted")
        return
    event.ignore()

I attached the video and code of the working behavior below.

enter image description here

The final implementation:

import os
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class MQTreeView(QTreeView):
    def __init__(self, model, path):
        super().__init__()

        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setModel(model)
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setRootIndex(model.index(path))
        self.setDefaultDropAction(Qt.MoveAction)
        self.viewport().setAcceptDrops(True)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        m = event.mimeData()
        if m.hasUrls():
            event.accept()
            print("[dropEnterEvent] - event accepted")
            return
        event.ignore()

    def dropEvent(self, event):
        print("[drop event] - dropped")
        if event.source():
            ix = self.indexAt(event.pos())
            model = self.model()

            if ix.isValid():
                if not model.isDir(ix):
                    ix = ix.parent()
                pathDir = model.filePath(ix)
            else:
                # for empty drag and drop
                pathDir = model.rootPath()

            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)
                    destination = QDir(pathDir).filePath(info.fileName())
                    source = info.absoluteFilePath()
                    if destination == source:
                        continue  # means they are in the same folder
                    if info.isDir():
                        QDir().rename(source, destination)
                    else:
                        qfile = QFile(source)
                        if QFile(destination).exists():
                            n_info = QFileInfo(destination)
                            
                            destination = n_info.canonicalPath() + QDir.separator() + n_info.completeBaseName() + " (copy)"
                            if n_info.completeSuffix():   # for moving files without suffix
                                destination += "." + n_info.completeSuffix()

                        qfile.rename(destination)
                        print(f"added -> {info.fileName()}")  # for debugging

                    accepted = True
                if accepted:
                    event.acceptProposedAction()

class AppDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        cwd = "test/"
        nw = "test copy/"

        # -- right -- #
        self.model1 = QFileSystemModel()
        self.model1.setRootPath(os.path.dirname(cwd))

        self.view1 = MQTreeView(self.model1, cwd)

        # -- left -- #
        self.model2 = QFileSystemModel()
        self.model2.setRootPath(os.path.dirname(nw))

        self.view2 = MQTreeView(self.model2, nw)

        # -- layout -- #
        layout = QHBoxLayout(self)
        layout.addWidget(self.view1)
        layout.addWidget(self.view2)

app = QApplication(sys.argv)
main = AppDemo()
main.show()
app.exec_()