Skip to content

Commit

Permalink
moved to lists
Browse files Browse the repository at this point in the history
  • Loading branch information
JoschD committed Dec 5, 2023
1 parent d88f774 commit d1016af
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 46 deletions.
118 changes: 74 additions & 44 deletions omc3_gui/segment_by_segment/main_model.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import enum
from dataclasses import dataclass
import logging
from typing import Any, Dict, Sequence
from typing import Any, Dict, Hashable, List, Protocol, Sequence

from qtpy import QtCore
from qtpy.QtCore import Qt
Expand All @@ -18,57 +18,87 @@ class Settings:
pass


class ItemDictModel:
""" Mixin-Class for a class that has a dictionary of items. """
class Item(Protocol):
""" Protocol for a class that has an 'id'-property.
As used in :class:`omc3_gui.segment_by_segment.main_model.UniqueItemListModel`,
this id defines whether two items are "the same", i.e. only one of them
can be present in the model.
Example: For the Segments, this should be the name, as if we have two segements
with the same name, running them overwrites each other.
"""
id: Hashable


class UniqueItemListModel:
""" Mixin-Class for a class that has a dictionary of items.
Note: I have considered using QAbstractItemModel/QStandardItemModel,
but I do not need all the features it provides, so this should be easier
and allows for keeping items unique (jdilly, 2023).
All items need to have an 'id'-property.
"""

def __init__(self):
self.items = {}
self._items: List[Item] = []

def try_emit(self, emit: bool = True):
def try_emit_change(self, emit: bool = True):
""" Emits a dataChanged-signal if the model has changed, and if the
class provides such a signal. """
if not emit:
return

if hasattr(self, "dataChanged"):
# TODO: return which data has actually changed?
try:
idx_start = self.index(0) # list
idx_end = self.index(len(self.items)-1)
idx_end = self.index(self.rowCount()-1)
except TypeError:
idx_start = self.index(0, 0) # table
idx_end = self.index(self.rowCount()-1, self.columnCount()-1)
self.headerDataChanged.emit(Qt.Horizontal, 0, self.columnCount()-1)
self.dataChanged.emit(idx_start, idx_end)

def update_item(self, item, old_name: str = None):
if old_name is not None:
self.items.pop(old_name)
self.items[str(item)] = item
self.try_emit()

def add_item(self, item, emit: bool = True):
name = str(item)
if name in self.items.keys():
raise ValueError(f"Item {name} already exists")
self.items[name] = item
self.try_emit(emit)

def add_items(self, items: Sequence):
def add_item(self, item: Item, emit: bool = True):
""" Adds an item to the model,
if an item with the same id is not already present. """
if item.id in [i.id for i in self._items]:
raise ValueError(f"Item {item.id} already exists")
self._items.append(item)
self.try_emit_change(emit)

def add_items(self, items: Sequence[Item]):
""" Adds all items from a list to the model,
for which items with the same id are not already present. """
already_exist_items = []
for item in items:
self.add_item(item, emit=False)
self.try_emit()

def remove_item(self, item, emit: bool = True):
self.items.pop(str(item))
self.try_emit(emit)
try:
self.add_item(item, emit=False)
except ValueError:
already_exist_items.append(item)
self.try_emit_change()
if already_exist_items:
raise ValueError(f"Items already exist: {already_exist_items}")

def remove_item(self, item: Item, emit: bool = True):
""" Removes an item from the model. """
self._items.remove(item)
self.try_emit_change(emit)

def remove_items(self, items: Sequence):
def remove_items(self, items: Sequence[Item]):
""" Removes all items from a list from the model, if they exist. """
do_not_exist_items = []
for item in items:
self.remove_item(item, emit=False)
self.try_emit()
try:
self.remove_item(item, emit=False)
except ValueError:
do_not_exist_items.append(item)
self.try_emit_change()
if do_not_exist_items:
raise ValueError(f"Items do not exist: {do_not_exist_items}")

def remove_all_items(self):
self.items = {}
self.try_emit()
def clear(self):
""" Removes all items from the model. """
self.items = []
self.try_emit_change()

def remove_item_at(self, index: int):
self.remove_item(self.get_item_at(index))
Expand All @@ -77,19 +107,19 @@ def remove_items_at(self, indices: Sequence):
self.remove_items([self.get_item_at(index) for index in indices])

def get_item_at(self, index: int) -> Any:
return list(self.items.values())[index]
return self._items[index]

def get_index(self, item: Any) -> QtCore.QModelIndex:
idx_item = list(self.items.keys()).index(str(item))
def get_index(self, item: Item) -> QtCore.QModelIndex:
idx_item = self._items.index(item)
try:
return self.index(idx_item)
except TypeError:
return self.index(idx_item, 0)


class MeasurementListModel(QtCore.QAbstractListModel, ItemDictModel):
class MeasurementListModel(QtCore.QAbstractListModel, UniqueItemListModel):

items: Dict[str, OpticsMeasurement] # for the IDE
_items: Dict[str, OpticsMeasurement] # only for the IDE

class ColorIDs(enum.IntEnum):
NONE = 0
Expand All @@ -112,7 +142,7 @@ def get_color(cls, meas: OpticsMeasurement) -> int:

def __init__(self, *args, **kwargs):
super(QtCore.QAbstractListModel, self).__init__(*args, **kwargs)
super(ItemDictModel, self).__init__()
super(UniqueItemListModel, self).__init__()

def data(self, index: QtCore.QModelIndex, role: int = Qt.DisplayRole):

Expand All @@ -131,27 +161,27 @@ def data(self, index: QtCore.QModelIndex, role: int = Qt.DisplayRole):
return meas

def rowCount(self, index: QtCore.QModelIndex = None):
return len(self.items)
return len(self._items)


class SegmentTableModel(QtCore.QAbstractTableModel, ItemDictModel):
class SegmentTableModel(QtCore.QAbstractTableModel, UniqueItemListModel):

_COLUMNS = {0: "Segment", 1: "Start", 2: "End"}
_COLUMNS_MAP = {0: "name", 1: "start", 2: "end"}

items: Dict[str, SegmentModel]
_items: Dict[str, SegmentModel] # only for the IDE

def __init__(self, *args, **kwargs):
super(QtCore.QAbstractTableModel, self).__init__(*args, **kwargs)
super(ItemDictModel, self).__init__()
super(UniqueItemListModel, self).__init__()

def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._COLUMNS[section]
return super().headerData(section, orientation, role)

def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.items)
return len(self._items)

def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._COLUMNS)
Expand Down Expand Up @@ -179,7 +209,7 @@ def setData(self, index, value, role):

setattr(segment, self._COLUMNS_MAP[j], value)
if segment.name != old_name:
self.update_item(segment, old_name=old_name)
self.update_item(segment, old_id=old_name)
else:
self.dataChanged.emit(index, index)
return True
Expand Down
9 changes: 7 additions & 2 deletions omc3_gui/segment_by_segment/measurement_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ def __post_init__(self):

# Visualization ------------------------------------------------------------
def display(self) -> str:
return str(self.measurement_dir.name)
return self.measurement_dir.name

@property
def id(self) -> str:
""" Unique identifier for the measurement, used in the GUI. """
return self.measurement_dir.name

@classmethod
def get_label(cls, name: str) -> str:
Expand All @@ -68,7 +73,7 @@ def tooltip(self) -> str:
as to be used in a tool-tip. """
parts = [
(self.get_label(f.name), getattr(self, f.name)) for f in fields(self)
if f.name not in ("elements", "segments")
if not f.name.startswith("_")
]
l = max(len(name) for name, _ in parts)
return "\n".join(f"{name:{l}s}: {value}" for name, value in parts if value is not None)
Expand Down
5 changes: 5 additions & 0 deletions omc3_gui/segment_by_segment/segment_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ def to_input_string(self):
if self.is_element():
return self.name
return f"{self.name},{self.start},{self.end}"

@property
def id(self) -> str:
""" Unique identifier for the measurement, used in the GUI. """
return self.name

0 comments on commit d1016af

Please sign in to comment.