From 5ab13e3e16f8dc657d0a63eead07be0c61c1e738 Mon Sep 17 00:00:00 2001 From: miruka Date: Thu, 16 May 2019 15:39:44 -0400 Subject: [PATCH] Improve SortProxyFilter, room members filtering - Simplify SortProxyFilter - Better custom filtering algorithm - Rename "ascending" (default True) to "reverse" (default False) - Add "Filter members" field to RoomSidePane MembersView --- harmonyqml/backend/client.py | 9 ++ harmonyqml/backend/model/items.py | 2 - harmonyqml/backend/model/sort_filter_proxy.py | 127 ++++++++---------- harmonyqml/backend/signal_manager.py | 29 ++-- .../Chat/RoomSidePane/MembersView.qml | 20 ++- harmonyqml/components/UI.qml | 2 +- 6 files changed, 99 insertions(+), 90 deletions(-) diff --git a/harmonyqml/backend/client.py b/harmonyqml/backend/client.py index 41af8959..122c9a5b 100644 --- a/harmonyqml/backend/client.py +++ b/harmonyqml/backend/client.py @@ -338,3 +338,12 @@ class Client(QObject): @pyqtSlot(str, result=bool) def roomHasUnknownDevices(self, room_id: str) -> bool: return self.nio.room_contains_unverified(room_id) + + + @pyqtSlot(str, str, str) + def setMemberFilter(self, room_category: str, room_id: str, pattern: str + ) -> None: + self.manager.backend.accounts[self.userId]\ + .roomCategories[room_category]\ + .rooms[room_id]\ + .sortedMembers.filter = pattern diff --git a/harmonyqml/backend/model/items.py b/harmonyqml/backend/model/items.py index acfdd208..104bf263 100644 --- a/harmonyqml/backend/model/items.py +++ b/harmonyqml/backend/model/items.py @@ -51,8 +51,6 @@ class RoomMember(ListItem): userId: str = "" -# ---------- - class RoomEvent(ListItem): _required_init_values = {"eventId", "type", "dict", "dateTime"} _constant = {"type"} diff --git a/harmonyqml/backend/model/sort_filter_proxy.py b/harmonyqml/backend/model/sort_filter_proxy.py index 757108ca..e7cfca16 100644 --- a/harmonyqml/backend/model/sort_filter_proxy.py +++ b/harmonyqml/backend/model/sort_filter_proxy.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Optional +from typing import Callable, Dict, Optional from PyQt5.QtCore import ( QModelIndex, QObject, QSortFilterProxyModel, Qt, pyqtProperty, pyqtSignal, @@ -6,7 +6,10 @@ from PyQt5.QtCore import ( ) from .list_model import ListModel +from .list_item import ListItem +SortCallable = Callable[["SortFilterProxy", ListItem, ListItem], bool] +FilterCallable = Callable[["SortFilterProxy", ListItem], bool] class SortFilterProxy(QSortFilterProxyModel): sortByRoleChanged = pyqtSignal() @@ -16,59 +19,34 @@ class SortFilterProxy(QSortFilterProxyModel): def __init__(self, source_model: ListModel, - sort_by_role: str = "", - filter_by_role: str = "", - ascending: bool = True, - sort_func: Optional[Callable[[Any, Any], bool]] = None, - parent: QObject = None) -> None: + sort_by_role: str = "", + filter_by_role: str = "", + sort_func: Optional[SortCallable] = None, + filter_func: Optional[FilterCallable] = None, + reverse: bool = False, + parent: QObject = None) -> None: + + error = "{} and {}: only one can be set" + if (sort_by_role and sort_func): + raise TypeError(error.format("sort_by_role", "sort_func")) + if (filter_by_role and filter_func): + raise TypeError(error.format("filter_by_role", "filter_func")) + super().__init__(parent) self.setDynamicSortFilter(False) - self.setFilterCaseSensitivity(Qt.CaseInsensitive) self.setSourceModel(source_model) - source_model.rolesSet.connect(self._set_internal_sort_filter_role) source_model.countChanged.connect(self.countChanged.emit) source_model.changed.connect(self._apply_sort) + source_model.changed.connect(self.invalidateFilter) - self.sort_func = sort_func - - self._sort_by_role = "" - self.sortByRole = sort_by_role - self.ascending = ascending - - self._filter_by_role = "" - self.filterByRole = filter_by_role + self.sortByRole = sort_by_role + self.filterByRole = filter_by_role + self.sort_func = sort_func + self.filter_func = filter_func + self.reverse = reverse self._filter = None - self.filterChanged.connect( - lambda: self.countChanged.emit(self.rowCount()) - ) - - - # Sorting and filtering - - @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._set_internal_sort_filter_role() - self.sortByRoleChanged.emit() - - - @pyqtProperty(str, notify=filterByRoleChanged) - def filterByRole(self) -> str: - return self._filter_by_role - - - @filterByRole.setter # type: ignore - def filterByRole(self, role: str) -> None: - self._filter_by_role = role - self._set_internal_sort_filter_role() - self.filterByRoleChanged.emit() @pyqtProperty(str, notify=filterChanged) @@ -79,40 +57,20 @@ class SortFilterProxy(QSortFilterProxyModel): @filter.setter # type: ignore def filter(self, pattern: str) -> None: self._filter = pattern - self.setFilterWildcard(pattern or "*") + self.invalidateFilter() self.filterChanged.emit() + self.countChanged.emit(self.rowCount()) - def _set_internal_sort_filter_role(self) -> None: - numbers = self.sourceModel().roleNumbers() - try: - self.setSortRole(numbers[self.sortByRole]) - except (AttributeError, KeyError): - # Model doesn't have its roles set yet (empty model), or no - # self.sortByRole passed - pass + # Sorting/filtering methods override - try: - self.setFilterRole(numbers[self.filterByRole]) - except (AttributeError, KeyError): - pass - - - def _apply_sort(self) -> None: - order = Qt.AscendingOrder if self.ascending else Qt.DescendingOrder - self.sort(0, order) - - - # Sorting/filtering implementations - - - def lessThan(self, source_left: QModelIndex, source_right: QModelIndex + def lessThan(self, index_left: QModelIndex, index_right: QModelIndex ) -> bool: - left = self.sourceModel()[source_left.row()] - right = self.sourceModel()[source_right.row()] + left = self.sourceModel()[index_left.row()] + right = self.sourceModel()[index_right.row()] if self.sort_func: - return self.sort_func(left, right) + return self.sort_func(self, left, right) role = self.sortByRole try: @@ -121,6 +79,31 @@ class SortFilterProxy(QSortFilterProxyModel): return False + def filterAcceptsRow(self, row_index: int, _: QModelIndex) -> bool: + item = self.sourceModel()[row_index] + + if self.filter_func: + return self.filter_func(self, item) + + return self.filterMatches(getattr(item, self.filterByRole)) + + + # Implementations + + + def _apply_sort(self) -> None: + order = Qt.DescendingOrder if self.reverse else Qt.AscendingOrder + self.sort(0, order) + + + def filterMatches(self, string: str) -> bool: + if not self.filter: + return True + + string = string.lower() + return all(word in string for word in self.filter.lower().split()) + + # The rest def __repr__(self) -> str: diff --git a/harmonyqml/backend/signal_manager.py b/harmonyqml/backend/signal_manager.py index 1d08f20d..9720e67e 100644 --- a/harmonyqml/backend/signal_manager.py +++ b/harmonyqml/backend/signal_manager.py @@ -70,7 +70,7 @@ class SignalManager(QObject): source_model = room_categories_kwargs[i]["rooms"], sort_by_role = "lastEventDateTime", filter_by_role = "displayName", - ascending = False, + reverse = True, ) room_categories_kwargs[i]["sortedRooms"] = proxy @@ -153,11 +153,18 @@ class SignalManager(QObject): ) - def _members_sort_func(self, left: RoomMember, right: RoomMember) -> bool: + def _members_sort_func(self, _, left: RoomMember, right: RoomMember + ) -> bool: users = self.backend.users return users[left.userId].displayName < users[right.userId].displayName + def _members_filter_func(self, proxy: SortFilterProxy, member: RoomMember + ) -> bool: + users = self.backend.users + return proxy.filterMatches(users[member.userId].displayName.value) + + def onRoomInvited(self, client: Client, room_id: str, @@ -173,9 +180,9 @@ class SignalManager(QObject): members = ListModel() sorted_members = SortFilterProxy( - source_model = members, - filter_by_role = "displayName", - sort_func = self._members_sort_func, + source_model = members, + sort_func = self._members_sort_func, + filter_func = self._members_filter_func, ) categories["Invites"].rooms.upsert( @@ -213,9 +220,9 @@ class SignalManager(QObject): members = ListModel() sorted_members = SortFilterProxy( - source_model = members, - filter_by_role = "displayName", - sort_func = self._members_sort_func, + source_model = members, + sort_func = self._members_sort_func, + filter_func = self._members_filter_func, ) categories["Rooms"].rooms.upsert( @@ -256,9 +263,9 @@ class SignalManager(QObject): members = ListModel() sorted_members = SortFilterProxy( - source_model = members, - sort_by_role = "displayName", - filter_by_role = "displayName", + source_model = members, + sort_func = self._members_sort_func, + filter_func = self._members_filter_func, ) categories["Left"].rooms.upsert( diff --git a/harmonyqml/components/Chat/RoomSidePane/MembersView.qml b/harmonyqml/components/Chat/RoomSidePane/MembersView.qml index 6c0b8fe8..70f32c3d 100644 --- a/harmonyqml/components/Chat/RoomSidePane/MembersView.qml +++ b/harmonyqml/components/Chat/RoomSidePane/MembersView.qml @@ -5,15 +5,14 @@ import "../../Base" HColumnLayout { property int normalSpacing: 8 - Layout.leftMargin: roomSidePane.collapsed ? 0 : normalSpacing - Layout.rightMargin: Layout.leftMargin - HListView { id: memberList - spacing: parent.Layout.leftMargin + spacing: normalSpacing topMargin: spacing bottomMargin: topMargin + Layout.leftMargin: roomSidePane.collapsed ? 0 : normalSpacing + Layout.rightMargin: Layout.leftMargin Behavior on spacing { NumberAnimation { duration: HStyle.animationDuration } @@ -26,4 +25,17 @@ HColumnLayout { Layout.fillHeight: true } + + HTextField { + id: filterField + placeholderText: qsTr("Filter members") + backgroundColor: HStyle.sidePane.filterRooms.background + + onTextChanged: Backend.clients.get(chatPage.userId).setMemberFilter( + chatPage.category, chatPage.roomId, text + ) + + Layout.fillWidth: true + Layout.preferredHeight: HStyle.bottomElementsHeight + } } diff --git a/harmonyqml/components/UI.qml b/harmonyqml/components/UI.qml index 081a2be7..adb54d22 100644 --- a/harmonyqml/components/UI.qml +++ b/harmonyqml/components/UI.qml @@ -73,7 +73,7 @@ Item { if (pageStack.initialPageSet) { return } pageStack.initialPageSet = true showPage(accountsLoggedIn ? "Default" : "SignIn") - //if (accountsLoggedIn) { initialRoomTimer.start() } + if (accountsLoggedIn) { initialRoomTimer.start() } } Timer {