Backtracing from QSortFilterProxyModel

23 Views Asked by At

I am using a simple QTableView/QAbstractTabmeModel/QSortFilterProxyModel setup (edited for brevity):

rom collections import namedtuple
from typing import Optional

from PyQt6 import uic
from PyQt6.QtCore import QAbstractTableModel, pyqtSlot, Qt, QSortFilterProxyModel
from PyQt6.QtWidgets import QWidget, QTableView, QHeaderView, QButtonGroup, QPushButton

import Fandom


class IModel(QAbstractTableModel):
    _column = namedtuple('_column', "name func hint align")

    def __init__(self, columns: [_column]):
        self._columns = columns
        super().__init__()
        self._rows = []
        self.select()

    def data(self, index, role=...):
        match role:
            case Qt.ItemDataRole.DisplayRole:
                row = self._rows[index.row()]
                return self._columns[index.column()].func(row)
            case Qt.ItemDataRole.TextAlignmentRole:
                return self._columns[index.column()].align
        return None

    def headerData(self, section, orientation, role=...):
        if orientation == Qt.Orientation.Horizontal:
            match role:
                case Qt.ItemDataRole.DisplayRole:
                    return self._columns[section].name
        return None

    def rowCount(self, parent=...):
        return len(self._rows)

    def columnCount(self, parent=...):
        return len(self._columns)

    def select(self, what=None):
        self.beginResetModel()
        self._rows = []
        self.endResetModel()

    def set_hints(self, view: QTableView):
        header = view.horizontalHeader()
        for i, x in enumerate(self._columns):
            header.setSectionResizeMode(i, x.hint)


class ItemModel(IModel):
    def __init__(self):
        super().__init__([
            IModel._column('ID', lambda x: x['ID'],
                           QHeaderView.ResizeMode.ResizeToContents,
                           Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter),
            IModel._column('Name', lambda x: x['Name'],
                           QHeaderView.ResizeMode.ResizeToContents,
                           Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter),
            IModel._column('Description', lambda x: x['desc'],
                           QHeaderView.ResizeMode.ResizeToContents,
                           Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter),
        ])

    def select(self, what='ALL'):
        self.beginResetModel()
        items = [
                 {'ID': 1, 'Name': 'foo', 'desc': 'Something'},
                 {'ID': 2, 'Name': 'fie', 'desc': 'Something else'},
                 {'ID': 3, 'Name': 'faa', 'desc': 'Another'},
                 {'ID': 4, 'Name': 'fum', 'desc': 'Another one'},
                ]
        self._rows = items
        self.endResetModel()

    def id(self, idx: int):
        return self._rows[idx]['ID']

    def value(self, idx: int):
        return self._rows[idx]

class Storage(QWidget):
    def __init__(self, *args, **kwargs):
        self.items: Optional[QTableView] = None
        self.storage: Optional[QTableView] = None
        super().__init__(*args, **kwargs)
        uic.loadUi("Storage.ui", self)
        self.item_model = ItemModel()
        self.item_proxy = QSortFilterProxyModel()
        self.item_proxy.setSourceModel(self.item_model)
        self.items.setModel(self.item_proxy)
        self.item_model.set_hints(self.items)
        self.items.setSortingEnabled(True)
        self.items.sortByColumn(1, Qt.SortOrder.AscendingOrder)
        self.items.reset()

    @pyqtSlot()
    def on_add_clicked(self):
        sel = self.items.currentIndex()
        if sel.isValid():
            idx = self.item_model.id(sel.row())
            print(idx)

    @pyqtSlot()
    def on_del_clicked(self):
        sel = self.items.currentIndex()
        if sel.isValid():
            idx = self.item_model.id(sel.row())
            print(idx)


if __name__ == '__main__':
    from PyQt6.QtWidgets import QMainWindow, QApplication

    class MainWindow(QMainWindow):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.storage = Storage()
            self.setCentralWidget(self.storage)


    app = QApplication([])
    win = MainWindow()
    win.show()

    from sys import exit
    exit(app.exec())

Problem is in on_[add|del]_clicked() I need to map back from sorted rows (returned by self.items.current_index()) to the index in the original, unsorted ItemModel._rows. How can I achieve this?

1

There are 1 best solutions below

0
On

I found out. I need to use QSortFilterProxyModel.mapToSource() in my case something like:

    @pyqtSlot()
    def on_del_clicked(self):
        sel = self.items.currentIndex()              <-- this is proxy index
        if sel.isValid():
            orig = self.item_proxy.mapToSource(sel)  <-- this is model index
            idx = self.item_model.id(orig.row())     <-- use as needed
            self.storage_model.edit(idx, -1)