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
This commit is contained in:
miruka 2019-05-16 15:39:44 -04:00
parent 41fdd19d2c
commit 5ab13e3e16
6 changed files with 99 additions and 90 deletions

View File

@ -338,3 +338,12 @@ class Client(QObject):
@pyqtSlot(str, result=bool) @pyqtSlot(str, result=bool)
def roomHasUnknownDevices(self, room_id: str) -> bool: def roomHasUnknownDevices(self, room_id: str) -> bool:
return self.nio.room_contains_unverified(room_id) 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

View File

@ -51,8 +51,6 @@ class RoomMember(ListItem):
userId: str = "" userId: str = ""
# ----------
class RoomEvent(ListItem): class RoomEvent(ListItem):
_required_init_values = {"eventId", "type", "dict", "dateTime"} _required_init_values = {"eventId", "type", "dict", "dateTime"}
_constant = {"type"} _constant = {"type"}

View File

@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, Optional from typing import Callable, Dict, Optional
from PyQt5.QtCore import ( from PyQt5.QtCore import (
QModelIndex, QObject, QSortFilterProxyModel, Qt, pyqtProperty, pyqtSignal, QModelIndex, QObject, QSortFilterProxyModel, Qt, pyqtProperty, pyqtSignal,
@ -6,7 +6,10 @@ from PyQt5.QtCore import (
) )
from .list_model import ListModel from .list_model import ListModel
from .list_item import ListItem
SortCallable = Callable[["SortFilterProxy", ListItem, ListItem], bool]
FilterCallable = Callable[["SortFilterProxy", ListItem], bool]
class SortFilterProxy(QSortFilterProxyModel): class SortFilterProxy(QSortFilterProxyModel):
sortByRoleChanged = pyqtSignal() sortByRoleChanged = pyqtSignal()
@ -16,59 +19,34 @@ class SortFilterProxy(QSortFilterProxyModel):
def __init__(self, def __init__(self,
source_model: ListModel, source_model: ListModel,
sort_by_role: str = "", sort_by_role: str = "",
filter_by_role: str = "", filter_by_role: str = "",
ascending: bool = True, sort_func: Optional[SortCallable] = None,
sort_func: Optional[Callable[[Any, Any], bool]] = None, filter_func: Optional[FilterCallable] = None,
parent: QObject = None) -> 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) super().__init__(parent)
self.setDynamicSortFilter(False) self.setDynamicSortFilter(False)
self.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.setSourceModel(source_model) self.setSourceModel(source_model)
source_model.rolesSet.connect(self._set_internal_sort_filter_role)
source_model.countChanged.connect(self.countChanged.emit) source_model.countChanged.connect(self.countChanged.emit)
source_model.changed.connect(self._apply_sort) source_model.changed.connect(self._apply_sort)
source_model.changed.connect(self.invalidateFilter)
self.sort_func = sort_func self.sortByRole = sort_by_role
self.filterByRole = filter_by_role
self._sort_by_role = "" self.sort_func = sort_func
self.sortByRole = sort_by_role self.filter_func = filter_func
self.ascending = ascending self.reverse = reverse
self._filter_by_role = ""
self.filterByRole = filter_by_role
self._filter = None 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) @pyqtProperty(str, notify=filterChanged)
@ -79,40 +57,20 @@ class SortFilterProxy(QSortFilterProxyModel):
@filter.setter # type: ignore @filter.setter # type: ignore
def filter(self, pattern: str) -> None: def filter(self, pattern: str) -> None:
self._filter = pattern self._filter = pattern
self.setFilterWildcard(pattern or "*") self.invalidateFilter()
self.filterChanged.emit() self.filterChanged.emit()
self.countChanged.emit(self.rowCount())
def _set_internal_sort_filter_role(self) -> None: # Sorting/filtering methods override
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
try: def lessThan(self, index_left: QModelIndex, index_right: QModelIndex
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
) -> bool: ) -> bool:
left = self.sourceModel()[source_left.row()] left = self.sourceModel()[index_left.row()]
right = self.sourceModel()[source_right.row()] right = self.sourceModel()[index_right.row()]
if self.sort_func: if self.sort_func:
return self.sort_func(left, right) return self.sort_func(self, left, right)
role = self.sortByRole role = self.sortByRole
try: try:
@ -121,6 +79,31 @@ class SortFilterProxy(QSortFilterProxyModel):
return False 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 # The rest
def __repr__(self) -> str: def __repr__(self) -> str:

View File

@ -70,7 +70,7 @@ class SignalManager(QObject):
source_model = room_categories_kwargs[i]["rooms"], source_model = room_categories_kwargs[i]["rooms"],
sort_by_role = "lastEventDateTime", sort_by_role = "lastEventDateTime",
filter_by_role = "displayName", filter_by_role = "displayName",
ascending = False, reverse = True,
) )
room_categories_kwargs[i]["sortedRooms"] = proxy 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 users = self.backend.users
return users[left.userId].displayName < users[right.userId].displayName 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, def onRoomInvited(self,
client: Client, client: Client,
room_id: str, room_id: str,
@ -173,9 +180,9 @@ class SignalManager(QObject):
members = ListModel() members = ListModel()
sorted_members = SortFilterProxy( sorted_members = SortFilterProxy(
source_model = members, source_model = members,
filter_by_role = "displayName", sort_func = self._members_sort_func,
sort_func = self._members_sort_func, filter_func = self._members_filter_func,
) )
categories["Invites"].rooms.upsert( categories["Invites"].rooms.upsert(
@ -213,9 +220,9 @@ class SignalManager(QObject):
members = ListModel() members = ListModel()
sorted_members = SortFilterProxy( sorted_members = SortFilterProxy(
source_model = members, source_model = members,
filter_by_role = "displayName", sort_func = self._members_sort_func,
sort_func = self._members_sort_func, filter_func = self._members_filter_func,
) )
categories["Rooms"].rooms.upsert( categories["Rooms"].rooms.upsert(
@ -256,9 +263,9 @@ class SignalManager(QObject):
members = ListModel() members = ListModel()
sorted_members = SortFilterProxy( sorted_members = SortFilterProxy(
source_model = members, source_model = members,
sort_by_role = "displayName", sort_func = self._members_sort_func,
filter_by_role = "displayName", filter_func = self._members_filter_func,
) )
categories["Left"].rooms.upsert( categories["Left"].rooms.upsert(

View File

@ -5,15 +5,14 @@ import "../../Base"
HColumnLayout { HColumnLayout {
property int normalSpacing: 8 property int normalSpacing: 8
Layout.leftMargin: roomSidePane.collapsed ? 0 : normalSpacing
Layout.rightMargin: Layout.leftMargin
HListView { HListView {
id: memberList id: memberList
spacing: parent.Layout.leftMargin spacing: normalSpacing
topMargin: spacing topMargin: spacing
bottomMargin: topMargin bottomMargin: topMargin
Layout.leftMargin: roomSidePane.collapsed ? 0 : normalSpacing
Layout.rightMargin: Layout.leftMargin
Behavior on spacing { Behavior on spacing {
NumberAnimation { duration: HStyle.animationDuration } NumberAnimation { duration: HStyle.animationDuration }
@ -26,4 +25,17 @@ HColumnLayout {
Layout.fillHeight: true 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
}
} }

View File

@ -73,7 +73,7 @@ Item {
if (pageStack.initialPageSet) { return } if (pageStack.initialPageSet) { return }
pageStack.initialPageSet = true pageStack.initialPageSet = true
showPage(accountsLoggedIn ? "Default" : "SignIn") showPage(accountsLoggedIn ? "Default" : "SignIn")
//if (accountsLoggedIn) { initialRoomTimer.start() } if (accountsLoggedIn) { initialRoomTimer.start() }
} }
Timer { Timer {