diff --git a/TODO.md b/TODO.md index 418bc958..b41d11b6 100644 --- a/TODO.md +++ b/TODO.md @@ -148,7 +148,6 @@ ## Backend - Saving the room settings -- Optimize Model item replacement - Refetch profile after manual profile change, don't wait for a room event - Better config file format diff --git a/src/backend/models/model.py b/src/backend/models/model.py index ea53f749..d6208a73 100644 --- a/src/backend/models/model.py +++ b/src/backend/models/model.py @@ -8,9 +8,7 @@ from typing import ( from blist import blist -from ..pyotherside_events import ( - ModelCleared, ModelItemDeleted, ModelItemInserted, -) +from ..pyotherside_events import ModelCleared, ModelItemDeleted, ModelItemSet from . import SyncId if TYPE_CHECKING: @@ -61,36 +59,49 @@ class Model(MutableMapping): def __setitem__(self, key, value: "ModelItem") -> None: - """Merge new item with an existing one if possible, else add it. - - If an existing item with the passed `key` is found, its fields will be - updated with the passed `ModelItem`'s fields. - In other cases, the item is simply added to the model. - - This also sets the `ModelItem.parent_model` hidden attributes on - the passed item. - """ - with self._write_lock: existing = self._data.get(key) new = value - if existing: - for field in new.__dataclass_fields__: # type: ignore - # The same shared item is in _sorted_data, no need to find - # and modify it explicitely. - setattr(existing, field, getattr(new, field)) - return + # Collect changed fields + + changed_fields = {} + + for field in new.__dataclass_fields__: # type: ignore + changed = True + + if existing: + changed = getattr(new, field) != getattr(existing, field) + + if changed: + changed_fields[field] = new.serialize_field(field) + + # Set parent model on new item if self.sync_id: new.parent_model = self - self._data[key] = new - index = bisect(self._sorted_data, new) - self._sorted_data.insert(index, new) + # Insert into sorted data - if self.sync_id: - ModelItemInserted(self.sync_id, index, new) + index_then = None + + if existing: + index_then = self._sorted_data.index(existing) + del self._sorted_data[index_then] + + index_now = bisect(self._sorted_data, new) + self._sorted_data.insert(index_now, new) + + # Insert into dict data + + self._data[key] = new + + # Emit PyOtherSide event + + if self.sync_id and (index_then != index_now or changed_fields): + ModelItemSet( + self.sync_id, index_then, index_now, changed_fields, + ) def __delitem__(self, key) -> None: diff --git a/src/backend/models/model_item.py b/src/backend/models/model_item.py index a71bdea1..ab48955b 100644 --- a/src/backend/models/model_item.py +++ b/src/backend/models/model_item.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional -from ..pyotherside_events import ModelItemFieldChanged +from ..pyotherside_events import ModelItemSet from ..utils import serialize_value_for_qml if TYPE_CHECKING: @@ -38,17 +38,19 @@ class ModelItem: with self.parent_model._write_lock: super().__setattr__(name, value) - old_index = self.parent_model._sorted_data.index(self) + if self.parent_model.sync_id: + index_then = self.parent_model._sorted_data.index(self) + self.parent_model._sorted_data.sort() - new_index = self.parent_model._sorted_data.index(self) if self.parent_model.sync_id: - ModelItemFieldChanged( + index_now = self.parent_model._sorted_data.index(self) + + ModelItemSet( self.parent_model.sync_id, - old_index, - new_index, - name, - self.serialize_field(name), + index_then, + index_now, + {name: self.serialize_field(name)}, ) diff --git a/src/backend/pyotherside_events.py b/src/backend/pyotherside_events.py index 5a1b8486..a7233662 100644 --- a/src/backend/pyotherside_events.py +++ b/src/backend/pyotherside_events.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Dict, Optional import pyotherside @@ -9,7 +9,6 @@ from .utils import serialize_value_for_qml if TYPE_CHECKING: from .models import SyncId - from .models.model_item import ModelItem @dataclass @@ -70,21 +69,12 @@ class ModelEvent(PyOtherSideEvent): @dataclass -class ModelItemInserted(ModelEvent): - """Indicate a `ModelItem` insertion into a `Backend` `Model`.""" +class ModelItemSet(ModelEvent): + """Indicate `ModelItem` insert or field changes in a `Backend` `Model`.""" - index: int = field() - item: "ModelItem" = field() - - -@dataclass -class ModelItemFieldChanged(ModelEvent): - """Indicate a `ModelItem`'s field value change in a `Backend` `Model`.""" - - item_index_then: int = field() - item_index_now: int = field() - changed_field: str = field() - field_value: Any = field() + index_then: Optional[int] = field() + index_now: int = field() + fields: Dict[str, Any] = field() @dataclass diff --git a/src/gui/PythonBridge/Privates/EventHandlers.qml b/src/gui/PythonBridge/Privates/EventHandlers.qml index b98ebc14..1d74628c 100644 --- a/src/gui/PythonBridge/Privates/EventHandlers.qml +++ b/src/gui/PythonBridge/Privates/EventHandlers.qml @@ -49,23 +49,24 @@ QtObject { } - function onModelItemInserted(syncId, index, item) { - // print("insert", syncId, index, item) - ModelStore.get(syncId).insert(index, item) - } + function onModelItemSet(syncId, indexThen, indexNow, changedFields){ + if (indexThen === undefined) { + print("insert", syncId, indexThen, indexNow, + JSON.stringify(changedFields)) + ModelStore.get(syncId).insert(indexNow, changedFields) - - function onModelItemFieldChanged(syncId, oldIndex, newIndex, field, value){ - // print("change", syncId, oldIndex, newIndex, field, value) - const model = ModelStore.get(syncId) - model.setProperty(oldIndex, field, value) - - if (oldIndex !== newIndex) model.move(oldIndex, newIndex, 1) + } else { + print("set", syncId, indexThen, indexNow, + JSON.stringify(changedFields)) + const model = ModelStore.get(syncId) + model.set(indexThen, changedFields) + if (indexThen !== indexNow) model.move(indexThen, indexNow, 1) + } } function onModelItemDeleted(syncId, index) { - // print("del", syncId, index) + // print("delete", syncId, index) ModelStore.get(syncId).remove(index) }