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:
parent
41fdd19d2c
commit
5ab13e3e16
|
@ -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
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -18,57 +21,32 @@ class SortFilterProxy(QSortFilterProxyModel):
|
||||||
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,
|
||||||
|
reverse: bool = False,
|
||||||
parent: QObject = None) -> None:
|
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._sort_by_role = ""
|
|
||||||
self.sortByRole = sort_by_role
|
self.sortByRole = sort_by_role
|
||||||
self.ascending = ascending
|
|
||||||
|
|
||||||
self._filter_by_role = ""
|
|
||||||
self.filterByRole = filter_by_role
|
self.filterByRole = filter_by_role
|
||||||
|
self.sort_func = sort_func
|
||||||
|
self.filter_func = filter_func
|
||||||
|
self.reverse = reverse
|
||||||
|
|
||||||
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:
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -174,8 +181,8 @@ 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(
|
||||||
|
@ -214,8 +221,8 @@ 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(
|
||||||
|
@ -257,8 +264,8 @@ 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(
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user