# Copyright 2023 (c) Anna Schumaker. """A Python-based ListStore implementation.""" import bisect import typing from gi.repository import GObject from gi.repository import Gio class ListStore(GObject.GObject, Gio.ListModel): """A ListStore using Python lists.""" n_items = GObject.Property(type=int) def __init__(self): """Initialize a ListStore.""" super().__init__() self.items = [] def __contains__(self, item: GObject.GObject) -> bool: """Check if the item is contained in the list.""" return self.index(item) is not None def do_get_item_type(self) -> GObject.GType: """Get the type of items that can be stored.""" return GObject.GObject.__gtype__ def do_get_n_items(self) -> int: """Get the length of the list.""" return len(self.items) def do_get_item(self, index: int) -> GObject.GObject: """Get the item at the specific index.""" if index < len(self.items): return self.items[index] def append(self, item: GObject.GObject) -> bool: """Append an item to the list.""" pos = len(self.items) self.items.append(item) self.items_changed(pos, 0, 1) return True def clear(self) -> None: """Clear the list.""" if size := len(self.items): self.items.clear() self.items_changed(0, size, 0) def extend(self, items: typing.Iterable) -> None: """Append multiple items to the list.""" pos = len(self.items) self.items.extend(items) if (added := len(self.items) - pos) > 0: self.items_changed(pos, 0, added) def index(self, item: GObject.GObject) -> int | None: """Find the index of an item in the list.""" try: return self.items.index(item) except ValueError: return None def insert(self, index: int, item: GObject.GObject) -> bool: """Insert an item into the list.""" index = max(0, min(len(self.items), index)) self.items.insert(index, item) self.items_changed(index, 0, 1) return True def items_changed(self, index: int, removed: int, added: int): """Notify that the items in the list have changed.""" self.n_items = len(self.items) super().items_changed(index, removed, added) def pop(self, index: int) -> GObject.GObject: """Remove and return an item by index.""" n_items = len(self.items) if n_items > 0 and index < len(self.items): item = self.items.pop(index) self.items_changed(index, 1, 0) return item def remove(self, item: GObject.GObject) -> bool: """Remove an item from the list.""" if (index := self.index(item)) is not None: return self.pop(index) is not None return False class SortedList(ListStore): """A ListStore that keeps objects in a sorted order.""" def __init__(self, key_func: typing.Callable): """Initialize a SortedList.""" super().__init__() self.key_func = key_func def __bisect(self, item: GObject.GObject) -> tuple[bool, int]: item_key = self.key_func(item) pos = bisect.bisect_left(self.items, item_key, key=self.key_func) if pos < self.n_items: cur_key = self.key_func(self.items[pos]) return (item_key == cur_key, pos) return (False, pos) def append(self, item: GObject.GObject) -> bool: """Add an item to the list.""" (found, pos) = self.__bisect(item) return super().insert(pos, item) if not found else False def extend(self, items: typing.Iterable) -> None: """Add multiple items to the list.""" self.items.extend(items) if len(self.items) != self.n_items: self.items.sort(key=self.key_func) self.items_changed(0, self.n_items, len(self.items)) def index(self, item: GObject.GObject) -> int | None: """Find the index of an item in the list.""" (found, pos) = self.__bisect(item) return pos if found else None def insert(self, index: int, item: GObject.GObject) -> bool: """Insert an item into the list.""" return self.append(item)