Fix room sorting for good

Do it the right way with a QSortFilterProxyModel.
This commit is contained in:
miruka
2019-05-06 13:07:00 -04:00
parent 0e5c5619cf
commit cdf6190cba
11 changed files with 178 additions and 70 deletions

View File

@@ -1,6 +1,6 @@
from typing import Any, Dict, List, Optional
from PyQt5.QtCore import QDateTime
from PyQt5.QtCore import QDateTime, QSortFilterProxyModel
from .list_item import ListItem
from .list_model import ListModel
@@ -20,24 +20,26 @@ class Room(ListItem):
_required_init_values = {"roomId", "displayName"}
_constant = {"roomId"}
roomId: str = ""
displayName: str = ""
topic: Optional[str] = None
typingUsers: List[str] = []
roomId: str = ""
displayName: str = ""
topic: Optional[str] = None
typingUsers: List[str] = []
lastEventDateTime: Optional[QDateTime] = None
inviter: Optional[Dict[str, str]] = None
leftEvent: Optional[Dict[str, str]] = None
class RoomCategory(ListItem):
_required_init_values = {"name", "rooms"}
_constant = {"rooms"}
_required_init_values = {"name", "rooms", "sortedRooms"}
_constant = {"rooms", "sortedRooms"}
name: str = ""
# Must be provided at init, else it will be the same object
# for every RoomCategory
rooms: ListModel = ListModel()
rooms: ListModel = ListModel()
sortedRooms: QSortFilterProxyModel = QSortFilterProxyModel()
class Account(ListItem):

View File

@@ -118,7 +118,7 @@ class ListItem(QObject, metaclass=_ListItemMeta):
# Set properties from provided positional arguments
for prop, value in zip(self._props, args):
setattr(self, f"_{prop}", value)
setattr(self, f"_{prop}", self._set_parent(value))
already_set.add(prop)
# Set properties from provided keyword arguments
@@ -129,7 +129,7 @@ class ListItem(QObject, metaclass=_ListItemMeta):
if prop not in self._props:
raise TypeError(f"{method} got an unexpected keyword "
f"argument {prop!r}")
setattr(self, f"_{prop}", value)
setattr(self, f"_{prop}", self._set_parent(value))
already_set.add(prop)
# Check for required init arguments not provided
@@ -140,7 +140,13 @@ class ListItem(QObject, metaclass=_ListItemMeta):
# Set default values for properties not provided in arguments
for prop in set(self._props) - already_set:
setattr(self, f"_{prop}", self._props[prop][1])
setattr(self, f"_{prop}", self._set_parent(self._props[prop][1]))
def _set_parent(self, value: Any) -> Any:
if isinstance(value, QObject):
value.setParent(self)
return value
def __repr__(self) -> str:

View File

@@ -89,10 +89,15 @@ class ListModel(QAbstractListModel):
return self._data[0].mainKey if self._data else None
def roleNumbers(self) -> Dict[str, int]:
return {name: Qt.UserRole + i
for i, name in enumerate(self.roles, 1)} \
if self._data else {}
def roleNames(self) -> Dict[int, bytes]:
return {Qt.UserRole + i: bytes(f, "utf-8")
for i, f in enumerate(self.roles, 1)} \
return {Qt.UserRole + i: bytes(name, "utf-8")
for i, name in enumerate(self.roles, 1)} \
if self._data else {}
@@ -219,8 +224,10 @@ class ListModel(QAbstractListModel):
except AttributeError: # constant/not settable
pass
qidx = QAbstractListModel.index(self, i_index, 0)
self.dataChanged.emit(qidx, qidx, self.roleNames())
qidx = QAbstractListModel.index(self, i_index, 0)
updated = [number for name, number in self.roleNumbers().items()
if name not in ignore_roles]
self.dataChanged.emit(qidx, qidx, updated)
self.changed.emit()
return i_index
@@ -265,7 +272,7 @@ class ListModel(QAbstractListModel):
setattr(self[i_index], prop, value)
qidx = QAbstractListModel.index(self, i_index, 0)
self.dataChanged.emit(qidx, qidx, self.roleNames())
self.dataChanged.emit(qidx, qidx, (self.roleNumbers()[prop],))
self.changed.emit()

View File

@@ -0,0 +1,69 @@
from typing import Dict
from PyQt5.QtCore import (
QObject, QSortFilterProxyModel, Qt, pyqtProperty, pyqtSignal, pyqtSlot
)
from .list_model import ListModel
class SortFilterProxy(QSortFilterProxyModel):
sortByRoleChanged = pyqtSignal()
def __init__(self,
source_model: ListModel,
sort_by_role: str,
ascending: bool = True,
parent: QObject = None) -> None:
super().__init__(parent)
self.setDynamicSortFilter(False)
self.ascending = ascending
self.sortByRoleChanged.connect(self._set_sort_role)
self.setSourceModel(source_model)
source_model.rolesSet.connect(self._set_sort_role)
source_model.changed.connect(self._sort)
self._sort_by_role = sort_by_role
self._set_sort_role()
@pyqtProperty(str, notify=sortByRoleChanged)
def sortByRole(self) -> str:
return self._sort_by_role
@sortByRole.setter # type: ignore
def sortByRole(self, role: str) -> None:
self._sort_by_role = role
self.sortByRoleChanged.emit()
def _set_sort_role(self) -> None:
numbers = self.sourceModel().roleNumbers()
try:
self.setSortRole(numbers[self._sort_by_role])
except KeyError:
pass # Model doesn't have its roles set yet (empty model)
def __repr__(self) -> str:
return "%s(sortByRole=%r, sourceModel=%r)" % (
type(self).__name__, self.sortByRole, self.sourceModel(),
)
@pyqtSlot(result=str)
def repr(self) -> str:
return self.__repr__()
def roleNames(self) -> Dict[int, bytes]:
return self.sourceModel().roleNames()
def _sort(self) -> None:
order = Qt.AscendingOrder if self.ascending else Qt.DescendingOrder
self.sort(0, order)