Update and add missing new docstrings
This commit is contained in:
parent
cc1403974c
commit
8c9b5267e9
4
TODO.md
4
TODO.md
|
@ -1,5 +1,8 @@
|
|||
# TODO
|
||||
|
||||
- pdb
|
||||
- remove await_model_item
|
||||
- uplaod/download use kwargs
|
||||
- highlight messages being responded to
|
||||
- highlight messages with mention
|
||||
- add room members loading indicator
|
||||
|
@ -9,7 +12,6 @@
|
|||
it won't be visible in timeline no matter what the user config is
|
||||
- fix: there are rooms without messages on first sync
|
||||
|
||||
- update docstrings
|
||||
- update flatpak nio required version
|
||||
- final test
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ class Backend:
|
|||
synchronized between the Python backend and the QML UI.
|
||||
The models should only ever be modified from the backend.
|
||||
|
||||
If a non-existent key is accessed, it is creating with an
|
||||
If a non-existent key is accessed, it is created and an
|
||||
associated `Model` and returned.
|
||||
|
||||
The mapping keys are the `Model`'s synchronization ID,
|
||||
|
@ -66,6 +66,17 @@ class Backend:
|
|||
- `("<user_id>", "<room_id>", "events")`: state events and messages
|
||||
in the room `room_id` that our account `user_id` is part of.
|
||||
|
||||
Special models:
|
||||
|
||||
- `"all_rooms"`: See `models.special_models.AllRooms` docstring
|
||||
|
||||
- `"matching_accounts"`
|
||||
See `models.special_models.MatchingAccounts` docstring
|
||||
|
||||
- `("<user_id>", "<room_id>", "filtered_members")`:
|
||||
See `models.special_models.FilteredMembers` docstring
|
||||
|
||||
|
||||
clients: A `{user_id: MatrixClient}` dict for the logged-in clients
|
||||
we managed. Every client is logged to one matrix account.
|
||||
|
||||
|
@ -321,6 +332,11 @@ class Backend:
|
|||
|
||||
|
||||
async def set_substring_filter(self, model_id: SyncId, value: str) -> None:
|
||||
"""Set a FieldSubstringFilter model's filter property.
|
||||
|
||||
This should only be called from QML.
|
||||
"""
|
||||
|
||||
if isinstance(model_id, list): # QML can't pass tuples
|
||||
model_id = tuple(model_id)
|
||||
|
||||
|
@ -333,4 +349,8 @@ class Backend:
|
|||
|
||||
|
||||
async def set_account_collapse(self, user_id: str, collapse: bool) -> None:
|
||||
"""Call `set_account_collapse()` on the `all_rooms` model.
|
||||
|
||||
This should only be called from QML.
|
||||
"""
|
||||
self.models["all_rooms"].set_account_collapse(user_id, collapse)
|
||||
|
|
|
@ -1242,7 +1242,7 @@ class MatrixClient(nio.AsyncClient):
|
|||
left: bool = False,
|
||||
force_register_members: bool = False,
|
||||
) -> None:
|
||||
"""Register a `nio.MatrixRoom` as a `Room` object in our model."""
|
||||
"""Register/update a `nio.MatrixRoom` as a `models.items.Room`."""
|
||||
|
||||
# Add room
|
||||
inviter = getattr(room, "inviter", "") or ""
|
||||
|
@ -1310,6 +1310,7 @@ class MatrixClient(nio.AsyncClient):
|
|||
|
||||
|
||||
async def add_member(self, room: nio.MatrixRoom, user_id: str) -> None:
|
||||
"""Register/update a room member into our models."""
|
||||
member = room.users[user_id]
|
||||
|
||||
self.models[self.user_id, room.room_id, "members"][user_id] = Member(
|
||||
|
@ -1328,6 +1329,7 @@ class MatrixClient(nio.AsyncClient):
|
|||
|
||||
|
||||
async def remove_member(self, room: nio.MatrixRoom, user_id: str) -> None:
|
||||
"""Remove a room member from our models."""
|
||||
self.models[self.user_id, room.room_id, "members"].pop(user_id, None)
|
||||
HTML.rooms_user_id_names[room.room_id].pop(user_id, None)
|
||||
|
||||
|
@ -1403,7 +1405,7 @@ class MatrixClient(nio.AsyncClient):
|
|||
override_fetch_profile: Optional[bool] = None,
|
||||
**fields,
|
||||
) -> None:
|
||||
"""Register a `nio.Event` as a `Event` object in our model."""
|
||||
"""Register/update a `nio.Event` as a `models.items.Event` object."""
|
||||
|
||||
await self.register_nio_room(room)
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class ModelFilter(ModelProxy):
|
||||
"""Filter data from one or more source models."""
|
||||
|
||||
def __init__(self, sync_id: SyncId) -> None:
|
||||
self.filtered_out: Dict[Tuple[Optional[SyncId], str], "ModelItem"] = {}
|
||||
self.items_changed_callbacks: List[Callable[[], None]] = []
|
||||
|
@ -20,6 +22,7 @@ class ModelFilter(ModelProxy):
|
|||
|
||||
|
||||
def accept_item(self, item: "ModelItem") -> bool:
|
||||
"""Return whether an item should be present or filtered out."""
|
||||
return True
|
||||
|
||||
|
||||
|
@ -72,6 +75,8 @@ class ModelFilter(ModelProxy):
|
|||
self,
|
||||
only_if: Optional[Callable[["ModelItem"], bool]] = None,
|
||||
) -> None:
|
||||
"""Recheck every item to decide if they should be filtered out."""
|
||||
|
||||
with self._write_lock:
|
||||
take_out = []
|
||||
bring_back = []
|
||||
|
@ -103,6 +108,17 @@ class ModelFilter(ModelProxy):
|
|||
|
||||
|
||||
class FieldSubstringFilter(ModelFilter):
|
||||
"""Filter source models based on if their fields matches a string.
|
||||
|
||||
This is used for filter fields in QML: the user enters some text and only
|
||||
items with a certain field (typically `display_name`) that contain the
|
||||
words of the text (can be partial, e.g. "red" matches "red" or "tired")
|
||||
will be shown.
|
||||
|
||||
Matching is done using "smart case": insensitive if the filter text is
|
||||
all lowercase, sensitive otherwise.
|
||||
"""
|
||||
|
||||
def __init__(self, sync_id: SyncId, fields: Collection[str]) -> None:
|
||||
self.fields: Collection[str] = fields
|
||||
self._filter: str = ""
|
||||
|
|
|
@ -181,6 +181,12 @@ class Model(MutableMapping):
|
|||
|
||||
@contextmanager
|
||||
def batch_remove(self):
|
||||
"""Context manager that accumulates item removal events.
|
||||
|
||||
When the context manager exits, sequences of removed items are grouped
|
||||
and one `ModelItemDeleted` pyotherside event is fired per sequence.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._active_batch_remove_indice = []
|
||||
yield None
|
||||
|
|
|
@ -22,7 +22,11 @@ class ModelStore(UserDict):
|
|||
|
||||
|
||||
def __missing__(self, key: SyncId) -> Model:
|
||||
"""When accessing a non-existent model, create and return it."""
|
||||
"""When accessing a non-existent model, create and return it.
|
||||
|
||||
Special models rather than a generic `Model` object may be returned
|
||||
depending on the passed key.
|
||||
"""
|
||||
|
||||
is_tuple = isinstance(key, tuple)
|
||||
|
||||
|
@ -51,6 +55,8 @@ class ModelStore(UserDict):
|
|||
|
||||
|
||||
async def ensure_exists_from_qml(self, sync_id: SyncId) -> None:
|
||||
"""Create model if it doesn't exist. Should only be called by QML."""
|
||||
|
||||
if isinstance(sync_id, list): # QML can't pass tuples
|
||||
sync_id = tuple(sync_id)
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class ModelProxy(Model):
|
||||
"""Proxies data from one or more `Model` objects."""
|
||||
|
||||
def __init__(self, sync_id: SyncId) -> None:
|
||||
super().__init__(sync_id)
|
||||
self.take_items_ownership = False
|
||||
|
@ -22,10 +24,20 @@ class ModelProxy(Model):
|
|||
|
||||
|
||||
def accept_source(self, source: Model) -> bool:
|
||||
"""Return whether passed `Model` should be proxied by this proxy."""
|
||||
return True
|
||||
|
||||
|
||||
def convert_item(self, item: "ModelItem") -> "ModelItem":
|
||||
"""Take a source `ModelItem`, return an appropriate one for proxy.
|
||||
|
||||
By default, this returns the passed item unchanged.
|
||||
|
||||
Due to QML `ListModel` restrictions, if multiple source models
|
||||
containing different subclasses of `ModelItem` are proxied,
|
||||
they should be converted to a same `ModelItem`
|
||||
subclass by overriding this function.
|
||||
"""
|
||||
return item
|
||||
|
||||
|
||||
|
@ -36,17 +48,23 @@ class ModelProxy(Model):
|
|||
value: "ModelItem",
|
||||
_changed_fields: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Called when a source model item is added or changed."""
|
||||
|
||||
if self.accept_source(source):
|
||||
value = self.convert_item(value)
|
||||
self.__setitem__((source.sync_id, key), value, _changed_fields)
|
||||
|
||||
|
||||
def source_item_deleted(self, source: Model, key) -> None:
|
||||
"""Called when a source model item is removed."""
|
||||
|
||||
if self.accept_source(source):
|
||||
del self[source.sync_id, key]
|
||||
|
||||
|
||||
def source_cleared(self, source: Model) -> None:
|
||||
"""Called when a source model is cleared."""
|
||||
|
||||
if self.accept_source(source):
|
||||
with self.batch_remove():
|
||||
for source_sync_id, key in self.copy():
|
||||
|
|
|
@ -10,6 +10,8 @@ from .model_item import ModelItem
|
|||
|
||||
|
||||
class AllRooms(FieldSubstringFilter):
|
||||
"""Flat filtered list of all accounts and their rooms."""
|
||||
|
||||
def __init__(self, accounts: Model) -> None:
|
||||
super().__init__(sync_id="all_rooms", fields=("display_name",))
|
||||
self.items_changed_callbacks.append(self.refilter_accounts)
|
||||
|
@ -20,6 +22,8 @@ class AllRooms(FieldSubstringFilter):
|
|||
|
||||
|
||||
def set_account_collapse(self, user_id: str, collapsed: bool) -> None:
|
||||
"""Set whether the rooms for an account should be filtered out."""
|
||||
|
||||
def only_if(item):
|
||||
return item.type is Room and item.for_account == user_id
|
||||
|
||||
|
@ -74,6 +78,10 @@ class AllRooms(FieldSubstringFilter):
|
|||
|
||||
|
||||
class MatchingAccounts(ModelFilter):
|
||||
"""List of our accounts in `AllRooms` with at least one matching room if
|
||||
a `filter` is set, else list of all accounts.
|
||||
"""
|
||||
|
||||
def __init__(self, all_rooms: AllRooms) -> None:
|
||||
self.all_rooms = all_rooms
|
||||
self.all_rooms.items_changed_callbacks.append(self.refilter)
|
||||
|
@ -96,6 +104,8 @@ class MatchingAccounts(ModelFilter):
|
|||
|
||||
|
||||
class FilteredMembers(FieldSubstringFilter):
|
||||
"""Filtered list of members for a room."""
|
||||
|
||||
def __init__(self, user_id: str, room_id: str) -> None:
|
||||
self.user_id = user_id
|
||||
self.room_id = room_id
|
||||
|
|
Loading…
Reference in New Issue
Block a user