From: Christian Heller Date: Sat, 2 Nov 2024 14:34:47 +0000 (+0100) Subject: Browser: Refactor SorterAndFilterer order list treatment. X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/decks/blog?a=commitdiff_plain;h=5bce029aea826df49ad11ab2e72a35c2d7cd1a41;p=stable_plom Browser: Refactor SorterAndFilterer order list treatment. --- diff --git a/browser.py b/browser.py index 503321a..2f2892b 100755 --- a/browser.py +++ b/browser.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Browser for image files.""" from json import dump as json_dump, load as json_load -from typing import TypeAlias, Callable, Optional +from typing import TypeAlias, Callable, Optional, Self from functools import cmp_to_key from re import search as re_search from os import listdir @@ -136,22 +136,8 @@ class Application(Gtk.Application): self.img_dir_absolute = abspath(opts.directory) self.bookmarks_db = JsonDb(BOOKMARKS_PATH) self.cache_db = JsonDb(CACHE_PATH) - - sort_suggestion = opts.sort_order.split(',') - names = [p.lower() for p in GEN_PARAMS] + ['bookmarked'] - self.sort_order = [] - for name in names: - self.sort_order += [SorterAndFilterer(name)] - new_sort_order = [] - do_reverse = '-' in sort_suggestion - for pattern in sort_suggestion: - for sorter in [sorter for sorter in self.sort_order - if sorter.name.startswith(pattern)]: - self.sort_order.remove(sorter) - new_sort_order += [sorter] - self.sort_order = new_sort_order + self.sort_order - if do_reverse: - self.sort_order.reverse() + self.sort_order = SorterAndFiltererOrder.from_suggestion( + opts.sort_order.split(',')) def do_activate(self, *args, **kwargs) -> None: """Parse arguments, start window, keep it open.""" @@ -201,6 +187,78 @@ class SorterAndFilterer(GObject.GObject): lambda a, b, c: self.widget.filter.add_css_class('temp')) +class SorterAndFiltererOrder: + """Represents sorted list of SorterAndFilterer items.""" + + def __init__(self, as_list: list[SorterAndFilterer]) -> None: + self._list = as_list + + def __eq__(self, other): + return self._list == other._list + + def __len__(self) -> int: + return len(self._list) + + def __getitem__(self, idx: int) -> SorterAndFilterer: + return self._list[idx] + + def __iter__(self): + return self._list.__iter__() + + @staticmethod + def _list_from_store(store) -> list[SorterAndFilterer]: + order = [] + for i in range(store.get_n_items()): + order += [store.get_item(i)] + return order + + @classmethod + def from_suggestion(cls, suggestion: list[str]) -> Self: + """Create new, interpreting order of strings in suggestion.""" + names = [p.lower() for p in GEN_PARAMS] + ['bookmarked'] + order = [] + for name in names: + order += [SorterAndFilterer(name)] + new_order = [] + do_reverse = '-' in suggestion + for pattern in suggestion: + for sorter in [sorter for sorter in order + if sorter.name.startswith(pattern)]: + order.remove(sorter) + new_order += [sorter] + order = new_order + order + if do_reverse: + order.reverse() + return cls(order) + + @classmethod + def from_store(cls, store: Gio.ListStore) -> Self: + """Create new, mirroring order in store.""" + return cls(cls._list_from_store(store)) + + def copy(self) -> Self: + """Create new, of equal order.""" + return self.__class__(self._list[:]) + + def update_from_store(self, store: Gio.ListStore) -> None: + """Update self from store.""" + self._list = self._list_from_store(store) + + def into_store(self, store: Gio.ListStore) -> None: + """Update store to represent self.""" + store.remove_all() + for sorter in self: + store.append(sorter) + + def switch_at(self, selected_idx: int, forward: bool) -> None: + """Switch elements at selected_idx and its neighbor.""" + selected = self[selected_idx] + other_idx = selected_idx + (1 if forward else -1) + other = self[other_idx] + self._list[other_idx] = selected + self._list[selected_idx] = other + + class GalleryItem(GObject.GObject): """Gallery representation of filesystem entry, base to DirItem, ImgItem.""" _to_hash = ['name', 'full_path'] @@ -364,7 +422,8 @@ class GalleryConfig(): def __init__(self, box: Gtk.Box, - sort_order: list[SorterAndFilterer], + # sort_order: list[SorterAndFilterer], + sort_order: SorterAndFiltererOrder, request_update: Callable, update_settings: Callable, items_attrs: ItemsAttrs, @@ -418,19 +477,14 @@ class GalleryConfig(): self._btn_show_dirs.set_active(False) def apply_config() -> None: - new_order = [] - for i in range(self._store.get_n_items()): - sorter = self._store.get_item(i) + self.order.update_from_store(self._store) + for sorter in self.order: sorter.widget.remove_css_class('temp') - new_order += [sorter] - if self.order != new_order: - self.order.clear() - self.order += new_order[:] self._gallery_update_settings( per_row=self._btn_per_row.get_value_as_int(), by_1st=self._btn_by_1st.get_active(), show_dirs=self._btn_show_dirs.get_active(), - sort_order=self.order[:], + sort_order=self.order.copy(), filter_inputs=self.filter_inputs.copy(), recurse_dirs=self._btn_recurse.get_active()) self._gallery_request_update(select=True) @@ -507,21 +561,12 @@ class GalleryConfig(): def move_sorter(self, direction: int) -> None: """Move selected item in sort order view, ensure temporary state.""" - tmp_sort_order = [] - for i in range(self._store.get_n_items()): - tmp_sort_order += [self._store.get_item(i)] + tmp_sort_order = SorterAndFiltererOrder.from_store(self._store) cur_idx = self._sort_sel.props.selected - selected = tmp_sort_order[cur_idx] if direction == -1 and cur_idx > 0: - prev_i = cur_idx - 1 - old_prev = tmp_sort_order[prev_i] - tmp_sort_order[prev_i] = selected - tmp_sort_order[cur_idx] = old_prev + tmp_sort_order.switch_at(cur_idx, forward=False) elif direction == 1 and cur_idx < (len(tmp_sort_order) - 1): - next_i = cur_idx + 1 - old_next = tmp_sort_order[next_i] - tmp_sort_order[next_i] = selected - tmp_sort_order[cur_idx] = old_next + tmp_sort_order.switch_at(cur_idx, forward=True) else: # to catch movement beyond limits return self.update_box(tmp_sort_order, cur_idx + direction) @@ -531,14 +576,12 @@ class GalleryConfig(): sort_item.widget.add_css_class('temp') def update_box(self, - alt_order: Optional[list[SorterAndFilterer]] = None, + alt_order: Optional[SorterAndFiltererOrder] = None, cur_selection: int = 0 ) -> None: """Rebuild sorter listing in box from .order, or alt_order if set.""" sort_order = alt_order if alt_order else self.order - self._store.remove_all() - for sorter in sort_order: - self._store.append(sorter) + sort_order.into_store(self._store) self._sort_sel.props.selected = cur_selection @@ -593,7 +636,7 @@ class Gallery: self._on_hit_item = on_hit_item self._on_selection_change = on_selection_change self._bookmarks_db, self._cache_db = bookmarks_db, cache_db - self._sort_order: list[SorterAndFilterer] = [] + self._sort_order = SorterAndFiltererOrder([]) self._filter_inputs: FilterInputs = {} self._img_dir_path = None @@ -662,7 +705,7 @@ class Gallery: show_dirs: Optional[bool] = None, recurse_dirs: Optional[bool] = None, img_dir_path: Optional[str] = None, - sort_order: Optional[list[SorterAndFilterer]] = None, + sort_order: Optional[SorterAndFiltererOrder] = None, filter_inputs: Optional[FilterInputs] = None ) -> None: """Set Gallery setup fields, request appropriate updates.""" @@ -1213,7 +1256,7 @@ class MainWindow(Gtk.Window): self.gallery.update_config_box = self.conf.update_box GLib.idle_add(lambda: self.gallery.update_settings( img_dir_path=self.app.img_dir_absolute, - sort_order=self.conf.order[:], + sort_order=self.conf.order.copy(), filter_inputs=self.conf.filter_inputs.copy())) def on_focus_change(self) -> None: