#!/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
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."""
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']
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,
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)
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)
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
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
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."""
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: