user122121
user122121

Reputation: 130

How to print the file path when the mouse hovers over an item in this QTreeView with QFileSystemModel

I'm making a custom QTreeView with QFileSystem model, and I have a MouseMoveEvent set up to print the path of the item that is hovered over.

I'm way down the rabbit hole and doing all kinds of weird things to make this work.

Here is the latest minimal reproducible code:

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

class MainWindow(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.resize(500, 300)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        self.myList = CustomTreeWidget()
        self.myList.model.setRootPath("/Volumes/Home/User/Desktop/testsrc")
        self.myList.setObjectName("/Volumes/Home/User/Desktop/testsrc")
        self.layout.addWidget(self.myList)

class CustomTreeWidget(QTreeView):

    def __init__(self):
        super().__init__()
        self.model = QFileSystemModel()
        self.model.setFilter(QDir.NoDotAndDotDot | QDir.Files)
        self.setModel(self.model)
        self.setAlternatingRowColors(True)
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setIndentation(0)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.setMouseTracking(True)
        self.model.directoryLoaded.connect(self._runwhenloaded)

    def _runwhenloaded(self):
        self.setRootIndex(self.model.index(self.objectName()))
        self.model.setRootPath(self.objectName())

    def mouseMoveEvent(self, event):
        prev = ""
        if self.selectedIndexes():
            prev = self.selectedIndexes()[0]
        x = event.x()
        y = event.y()
        self.setSelection(QRect(x, y, 1, 1), QItemSelectionModel.ClearAndSelect)
        self.setCurrentIndex(self.selectedIndexes()[0])
        print(self.model.filePath(self.currentIndex()))
        if prev:
            self.setCurrentIndex(prev)
        # pos = QCursor.pos()
        # indexat = self.indexAt(pos).row() # why -1?
        # print(indexat) # why -1?
        # print(self.indexAt(pos).row())


if __name__ == "__main__":
    app =  QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())    

Obviously this example is not proper at all, as it destroys multiple selections and scrolls to the previously selected item whenever the mouse moves, and just a hack in general.

I've gone through many iterations and read everything I could put my hands on but I can't figure it out.

The closest answer seems to be HERE, but it's in C and I don't understand it.

So the question is: How to print the file path when the mouse hovers over an item in this QTreeView?

Upvotes: 0

Views: 284

Answers (1)

eyllanesc
eyllanesc

Reputation: 244301

A possible solution is to create an event filter that tracks the hover events and, according to that information, emits a signal that has the QModelIndex:

import sys

from PyQt5.QtCore import (
    pyqtSignal,
    pyqtSlot,
    Qt,
    QDir,
    QEvent,
    QModelIndex,
    QObject,
    QPersistentModelIndex,
    QStandardPaths,
)
from PyQt5.QtWidgets import (
    QAbstractItemView,
    QApplication,
    QFileSystemModel,
    QMainWindow,
    QTreeView,
)
from PyQt5 import sip


class HoverViewHelper(QObject):
    hovered = pyqtSignal(QModelIndex)

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

        self._current_index = QPersistentModelIndex()
        if not isinstance(view, QAbstractItemView):
            raise TypeError(f"The {view} must be of type QAbstractItemView")
        self._view = view

        self.view.viewport().setAttribute(Qt.WA_Hover)
        self.view.viewport().installEventFilter(self)

    @property
    def view(self):
        return self._view

    def eventFilter(self, obj, event):
        if sip.isdeleted(self.view):
            return True
        if self.view.viewport() is obj:
            if event.type() in (QEvent.HoverMove, QEvent.HoverEnter):
                p = event.pos()
                index = self.view.indexAt(p)
                self._update_index(index)
            elif event.type() == QEvent.HoverLeave:
                if self._current_index.isValid():
                    self._update_index(QModelIndex())
        return super().eventFilter(obj, event)

    def _update_index(self, index):
        pindex = QPersistentModelIndex(index)
        if pindex != self._current_index:
            self._current_index = pindex
            self.hovered.emit(QModelIndex(self._current_index))


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.rootPath())

        self.view = QTreeView()
        self.view.setModel(self.model)
        path = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
        self.view.setRootIndex(self.model.index(path))

        self.setCentralWidget(self.view)

        helper = HoverViewHelper(self.view)
        helper.hovered.connect(self.handle_hovered)

    @pyqtSlot(QModelIndex)
    def handle_hovered(self, index):
        if not index.isValid():
            return
        path = self.model.filePath(index)
        print(f"path: {path}")


def main():

    app = QApplication(sys.argv)

    w = MainWindow()
    w.resize(640, 480)
    w.show()

    app.exec_()


if __name__ == "__main__":
    main()

Upvotes: 2

Related Questions