diff --git a/src/backend/backend.py b/src/backend/backend.py index 5ba71ab6..1b8f4303 100644 --- a/src/backend/backend.py +++ b/src/backend/backend.py @@ -314,6 +314,9 @@ class Backend: async def set_substring_filter(self, model_id: SyncId, value: str) -> None: + if isinstance(model_id, list): # QML can't pass tuples + model_id = tuple(model_id) + model = Model.proxies[model_id] if not isinstance(model, FieldSubstringFilter): diff --git a/src/backend/models/model_store.py b/src/backend/models/model_store.py index 9fd1d881..ff7f72d1 100644 --- a/src/backend/models/model_store.py +++ b/src/backend/models/model_store.py @@ -2,10 +2,11 @@ from collections import UserDict from dataclasses import dataclass, field -from typing import Dict +from typing import Dict, Type from . import SyncId from .model import Model +from .special_models import FilteredMembers @dataclass(frozen=True) @@ -23,7 +24,16 @@ class ModelStore(UserDict): def __missing__(self, key: SyncId) -> Model: """When accessing a non-existent model, create and return it.""" - model = Model(sync_id=key) + is_tuple = isinstance(key, tuple) + + model: Model + + if is_tuple and len(key) == 3 and key[2] == "filtered_members": + model = FilteredMembers(user_id=key[0], room_id=key[1]) + else: + model = Model(sync_id=key) + print( key, model) + self.data[key] = model return model @@ -35,3 +45,10 @@ class ModelStore(UserDict): type(self).__name__, "\n ".join(sorted(str(v) for v in self.values())), ) + + + async def ensure_exists_from_qml(self, sync_id: SyncId) -> None: + if isinstance(sync_id, list): # QML can't pass tuples + sync_id = tuple(sync_id) + + self[sync_id] # will call __missing__ if needed diff --git a/src/backend/models/special_models.py b/src/backend/models/special_models.py index 567876f7..7c788945 100644 --- a/src/backend/models/special_models.py +++ b/src/backend/models/special_models.py @@ -37,3 +37,16 @@ class MatchingAccounts(ModelFilter): (r for r in self.all_rooms.values() if r.for_account == item.id), False, ) + + +class FilteredMembers(FieldSubstringFilter): + def __init__(self, user_id: str, room_id: str) -> None: + self.user_id = user_id + self.room_id = room_id + sync_id = (user_id, room_id, "filtered_members") + + super().__init__(sync_id=sync_id, fields=("display_name",)) + + + def accept_source(self, source: Model) -> bool: + return source.sync_id == (self.user_id, self.room_id, "members") diff --git a/src/gui/ModelStore.qml b/src/gui/ModelStore.qml index 0b4a6154..d9fe1c34 100644 --- a/src/gui/ModelStore.qml +++ b/src/gui/ModelStore.qml @@ -38,9 +38,12 @@ QtObject { function get(...modelId) { if (modelId.length === 1) modelId = modelId[0] - if (! privates.store[modelId]) + if (! privates.store[modelId]) { + privates.py.callCoro("models.ensure_exists_from_qml", [modelId]) + privates.store[modelId] = privates.model.createObject(this, {modelId}) + } return privates.store[modelId] } diff --git a/src/gui/Pages/Chat/RoomPane/MemberView.qml b/src/gui/Pages/Chat/RoomPane/MemberView.qml index c28930f8..f5f7e119 100644 --- a/src/gui/Pages/Chat/RoomPane/MemberView.qml +++ b/src/gui/Pages/Chat/RoomPane/MemberView.qml @@ -7,28 +7,25 @@ import "../../../Base" HColumnLayout { readonly property alias keybindFocusItem: filterField - + readonly property var modelSyncId: + [chat.userId, chat.roomId, "filtered_members"] HListView { id: memberList clip: true add: null // See the XXX comment in HListView.qml - model: HStringFilterModel { - sourceModel: ModelStore.get(chat.userId, chat.roomId, "members") - field: "display_name" - filter: filterField.text + model: ModelStore.get(modelSyncId) - delegate: MemberDelegate { - id: member - width: memberList.width - ListView.onAdd: ParallelAnimation { - HNumberAnimation { - target: member; property: "opacity"; from: 0; to: 1; - } - HNumberAnimation { - target: member; property: "scale"; from: 0; to: 1; - } + delegate: MemberDelegate { + id: member + width: memberList.width + ListView.onAdd: ParallelAnimation { + HNumberAnimation { + target: member; property: "opacity"; from: 0; to: 1; + } + HNumberAnimation { + target: member; property: "scale"; from: 0; to: 1; } } } @@ -70,6 +67,9 @@ HColumnLayout { // declared normally Component.onCompleted: placeholderText = qsTr("Filter members") + onTextChanged: + py.callCoro("set_substring_filter", [modelSyncId, text]) + Behavior on opacity { HNumberAnimation {} } }