How to implement a Qt MVC proxy model to re-arrange item/indices in a tree view?

54 views Asked by At

How to implement a Qt MVC proxy model to re-arrange item/indices in a tree view ?

Hi,

I believe to have a fundamental Qt MVC question about proxy models. And I am struggling to get a working implementation working.

I have some non-qt data that I want display in two separate views. With the specialty that the underlying data is the same, and it's just the relations that are displayed differently. I also have no control over the Ui design goal and it's structure.

For example:
I have some children entities which are predominantly associated with their respective parents. At the same time the children can also be associated with their place in a scene.

Family view (family-child-model):

├── Doe
│   └── John_Doe
├── West
│   ├── Hattie_West
│   └── North_West
└── Smith

Scene view (child-in-scene-model (proxyModel)):

├── backyard
│   ├── pool
│   │   └── John_Doe
│   ├── tree
│   │   └── Hattie_West
│   └── treehouse
│       └── Hattie_West
└── house
    └── kitchen
        └── North_West

Here the family view takes my original QStandartItemModel, which holds the family-child name relations. The children data objects are aware, where they are in the scene. They can query their scene location from some sort of node tree (non-qt data!).

Proxy-model-route:

I am using python, the qtpy shim and the PySide6 bindings for this.
Now instead of creating two different QStandartItemModel models, which would need to be managed separately, I want to map the family-child-model child indices, to the child-in-scene-model indices via a proxy model. So the child-in-scene-model can simply be passed to a tree view without having to re-structure QStandardItems around.

e.G.

...
family_child_view = QtWidgets.QTreeView(self)
child_in_scene_view = QtWidgets.QTreeView(self)

family_child_model = QtGui.QStandardItemModel()
child_in_scene_model = MyRearrangeModel()
child_in_scene_model.setSourceModel(family_child_model)

family_child_view.setModel(family_child_model)
child_in_scene_view.setModel(child_in_scene_model)
...

So, in it's simplest form I want to create new parent QModelIndex(s) for the leaves of the family view.

Question:

In principle, is this a valid thing to try and solve via a proxy model implementation ?

My motivation for that is, when editing and selecting child items, I should get the selection model between the proxy and the base model for free and working. That way I would not have to manage the child leave's so much for every new "change scene-view request. For example the scene view is likely to change in structure. So I hope that writing another proxy model would be all that it takes to address that.

Based on some initial answers and suggestions I would like to provide some basic python boilerplate, to show what I am struggling with. It's mainly creating parent indices for a proxy child index inside a proxy model. Either when subclassing QIdentityProxyModel or even QAbstractProxyModel.

Simple code example:

To end up with something like this simple end result goal

I am trying to workout how to overwrite the QIdentityProxyModel data index parent mapFromSource and mapToSource methods.

import sys
from qtpy.QtCore import Qt, QAbstractProxyModel, QIdentityProxyModel, QModelIndex
from qtpy.QtGui import QStandardItemModel, QStandardItem
from qtpy.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTreeView



class RearrangeProxyModel(QIdentityProxyModel):
    def __init__(self, source_model):
        super().__init__()
        self.setSourceModel(source_model)

    def mapFromSource(self, index):
        """Leaves every index unchanged except for the child_10 index.
        
        For the child_10 index it introduces two parent level nodes.
        root_10 and second_relation_10
        """
        if not index.isValid():
            return QModelIndex()
        
        if index.data() == 'child_10':
            # TODO create two new parent indices
            # to create a new relation the resembles:
            # |- root_10                    <- new_parent
            # |   |- second_relation_10     <- new parent
            # |       |- parent_10
            # |           |- child_10
            
            # Use self.parent and self.createIndex
            pass

        return QIdentityProxyModel.mapFromSource(self, index)

    def data(self, index, role=Qt.DisplayRole):
        source = self.mapToSource(index)
        if source.isValid():
            return source.data(role)
        return None

class TestUI(QMainWindow):
    def __init__(self):
        super().__init__()

        self.init_ui()

    def init_ui(self):
        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)

        layout = QVBoxLayout(central_widget)

        original_model = QStandardItemModel()
        proxy_model = RearrangeProxyModel(original_model)
        
        # Populate the original model with some data
        self.populate_model(original_model)
        
        # Create ListViews
        original_view = QTreeView(self)
        proxy_view = QTreeView(self)

        # Set models for ListViews
        original_view.setModel(original_model)
        proxy_view.setModel(proxy_model)

        # Add ListViews to the layout
        layout.addWidget(original_view)
        layout.addWidget(proxy_view)
        
        original_view.repaint()
        proxy_view.repaint()

        self.original_model = original_model
        self.proxy_model = proxy_model

        original_view.expandAll()
        proxy_view.expandAll()
        

    @staticmethod
    def populate_model(model):
        for i in range(9,12):
            item = QStandardItem(f"parent_{i}")
            child_item = QStandardItem(f"child_{i}")
            item.appendRow([child_item])
            # NOTE returns None
            model.appendRow(item)


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

    ui.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Where one attempt to get it working with the mapFromSource method.

def mapFromSource(self, index):
        """Leaves every index unchanged except for the child_10 index."""
        if not index.isValid():
            return QModelIndex()
        
        if index.data() == 'parent_10':
            second_relation_10 = self.createIndex(1,0)
            new_idx = self.createIndex(0,0, second_relation_10.internalPointer())
            return new_idx

        return QIdentityProxyModel.mapFromSource(self, index)

When using new_idx = self.createIndex(0,0, second_relation_10.internalId()) it's seems to give me a strange result in the view.

0

There are 0 answers