0

I'm creating a widget to explore and manage files inside my qt application. To construct that, I'm using a QFileSystemModel and a QListView with the IconMode view mode.

It should allow moving files (items) into folders (other items) with the QListView.

My question is how do I implement this?

First, I'm trying to override the supportedDragActions and supportedDropActions functions from ContentFileSystemModel to allow Move and Copy actions. Also, I override the flags function to enable drag and drop. Finally, I override the canDropMimeData and dropMimeData to check if they are running, but it looks like they are not.

The first problem is that the model does not allow to drop an item file into the QListView area once it displays a prohibited icon in the cursor (shown in the image below).

First, I have to set the model to allow the drops of items in folders. After that, I can implement the code to transfer the dragged items into the folder.

enter image description here

Code ready to reproduce the problem:

import sys
import os

from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *


class ContentFileSystemModel(QFileSystemModel):

    def __init__(self):
        super(ContentFileSystemModel, self).__init__()

    def supportedDragActions(self) -> Qt.DropActions:
        print("supportedDragActions")
        return Qt.MoveAction | super(ContentFileSystemModel, self).supportedDragActions() | Qt.CopyAction

    def supportedDropActions(self) -> Qt.DropActions:
        print("supportedDropActions")
        return Qt.MoveAction | super(ContentFileSystemModel, self).supportedDropActions() | Qt.CopyAction

    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
        defaultFlags = super(ContentFileSystemModel, self).flags(index)
        if not index.isValid():
            return defaultFlags
        fileInfo = self.fileInfo(index)
        # The target
        if fileInfo.isDir():
            # Allowed drop
            return Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | defaultFlags
        # The source: should be directory( in that case)
        elif fileInfo.isFile():
            # Allowed drag
            return Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | defaultFlags
        return defaultFlags

    def canDropMimeData(self, data: QMimeData, action: Qt.DropAction,
                        row: int, column: int, parent: QModelIndex) -> bool:
        print("canDropMimeData")
        return True

    def dropMimeData(self, data: QMimeData, action: Qt.DropAction,
                     row: int, column: int, parent: QModelIndex) -> bool:
        print("dropMimeData")
        return True


def main(argv):
    app = QApplication(sys.argv)

    path = "C:\\Users\\Me\\Desktop"
    file_system_model = ContentFileSystemModel()
    file_system_model.setRootPath(path)
    file_system_model.setReadOnly(False)

    lv_file_manager = QListView()
    lv_file_manager.setModel(file_system_model)
    lv_file_manager.setViewMode(QListView.IconMode)
    lv_file_manager.setRootIndex(file_system_model.index(path))
    lv_file_manager.setResizeMode(QListView.Adjust)

    lv_file_manager.setMovement(QListView.Static)
    lv_file_manager.setSelectionMode(QAbstractItemView.ExtendedSelection)
    lv_file_manager.setWrapping(True)
    lv_file_manager.setAcceptDrops(True)
    lv_file_manager.setDragEnabled(True)
    lv_file_manager.setDropIndicatorShown(True)
    lv_file_manager.setUniformItemSizes(True)
    lv_file_manager.setDragDropMode(QAbstractItemView.InternalMove)
    lv_file_manager.setFlow(QListView.LeftToRight)

    lv_file_manager.show()
    app.exec_()


if __name__ == "__main__":
    main(sys.argv)
E.G. Cortes
  • 65
  • 12

1 Answers1

1

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()
musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Thanks, musicamante. When I remove that line, the items can be moved freely to other places in the `QListView`. However, once I would like to allow moving items only into folders, it is an undesirable feature. Anyway, it is progress once the model allows dropping the items and its call the `canDropMimeData` function. I'll try to restrict this drop only into folders. I'll report here any progress. – E.G. Cortes Dec 27 '20 at 04:12
  • If you want to get more control on the drop target, you must override the `dropEvent` on the view. I've written a small file manager on my own some time ago with Qt, so if you could better clarify how your program should behave depending on the situation, I can give you some suggestions and possibly edit the answer. – musicamante Dec 27 '20 at 04:26
  • I want to allow dragging items (files and folders) only into other folder items. When the user drops it somewhere other than a folder, nothing should happen. When the user drops it in a folder, it should be moved into there. – E.G. Cortes Dec 27 '20 at 05:48
  • When I say moved, I mean remove the files from the current folder and put them in the folder where they were dropped. – E.G. Cortes Dec 27 '20 at 05:51