From: Christian Heller Date: Sat, 9 Nov 2024 06:16:09 +0000 (+0100) Subject: Browser.py: Some code re-organization to prepare next steps. X-Git-Url: https://plomlompom.com/repos/%7B%7B%20web_path%20%7D%7D/decks/static/templates?a=commitdiff_plain;h=f9062aac9d36adcec791f7ddb72960db372f2c59;p=stable_plom Browser.py: Some code re-organization to prepare next steps. --- diff --git a/browser.py b/browser.py index ff99733..b880b20 100755 --- a/browser.py +++ b/browser.py @@ -25,6 +25,7 @@ from stable.gen_params import (GenParams, GEN_PARAMS_FLOAT, # noqa: E402 GEN_PARAMS_INT, GEN_PARAMS_STR, # noqa: E402 GEN_PARAMS) # noqa: E402 +BasicItemsAttrs = dict[str, set[str]] AttrVals: TypeAlias = list[str] AttrValsByVisibility: TypeAlias = dict[str, AttrVals] ItemsAttrs: TypeAlias = dict[str, AttrValsByVisibility] @@ -656,7 +657,7 @@ class Gallery: self._img_dir_path = '' self._shall_load = False - self._shall_build = False + self._shall_build_grid = False self._shall_redraw = False self._shall_scroll_to_focus = False self._shall_select = False @@ -668,6 +669,7 @@ class Gallery: self._slots_geometry = GallerySlotsGeometry() self.dir_entries: list[GalleryItem] = [] + self._basic_items_attrs: BasicItemsAttrs = {} self.items_attrs: ItemsAttrs = {} self.selected_idx = 0 self.slots: list[GallerySlot] = [] @@ -693,21 +695,25 @@ class Gallery: if not self._img_dir_path: return True if self._shall_load: + self._shall_load = False self._load_directory() - if self._shall_build: - self._build() + if self._shall_build_grid: + self._shall_build_grid = False + self._build_grid() if self._shall_select: - self._set_selection(self.selected_idx) + self._shall_select = False + self._assert_selection() if self._shall_redraw: wait_time_passed = datetime.now() - self._start_redraw_wait if wait_time_passed > redraw_wait_time: + self._shall_redraw = False self._redraw_and_check_focus() return True def handle_scroll(_) -> None: self._start_redraw_wait = datetime.now() self._shall_scroll_to_focus = False - self._shall_redraw = True + self.request_update() # only request redraw redraw_wait_time = timedelta(milliseconds=GALLERY_REDRAW_WAIT_MS) self._start_redraw_wait = datetime.now() - redraw_wait_time @@ -734,13 +740,26 @@ class Gallery: if val is not None and getattr(self, attr_name) != val: setattr(self, attr_name, val) if attr_name in {'_recurse_dirs', '_img_dir_path'}: - self._load_directory() + self.request_update(load=True) else: - self.request_update(build=True) + self.request_update(build_grid=True) + + def _make_basic_items_attrs(self, + entries: list[GalleryItem] + ) -> BasicItemsAttrs: + basic_items_attrs = {} + for attr_name in (s.name for s in self._sort_order): + vals: set[str] = set() + for entry in [e for e in entries if isinstance(e, ImgItem)]: + val = (getattr(entry, attr_name) + if hasattr(entry, attr_name) else None) + if val is not None: + vals.add(val) + basic_items_attrs[attr_name] = vals + return basic_items_attrs def _load_directory(self) -> None: - """Rewrite .dir_entries from ._img_dir_path, trigger rebuild.""" - self._shall_load = False + """(Re-)build .dir_entries from ._img_dir_path, ._basic_items_attrs.""" self.dir_entries.clear() bookmarks = self._bookmarks_db.as_copy() cache = self._cache_db.as_ref() @@ -784,8 +803,9 @@ class Gallery: read_directory(path) read_directory(self._img_dir_path, make_parent=True) + self._basic_items_attrs = self._make_basic_items_attrs( + self.dir_entries) self._cache_db.write() - self.request_update(build=True) @property def selected_item(self) -> Optional[GalleryItem]: @@ -797,124 +817,116 @@ class Gallery: self._set_selection(self.slots.index(slot)) self.request_update(scroll_to_focus=True) + def _assert_selection(self) -> None: + if self.slots: + self.slots[self.selected_idx].mark('selected', True) + self.slots[self.selected_idx].grab_focus() + def _set_selection(self, new_idx: int) -> None: """Set self.selected_idx, mark slot as 'selected', unmark old one.""" - self._shall_select = False - # in ._build(), directly before we are called, no slot will be + # in ._build_grid(), directly before we are called, no slot will be # CSS-marked 'selected', so .mark('selected', False) would tolerably - # happen without effect; where called from ._build() however, an old - # .selected_idx might point beyond _any_ of the new .slots, the + # happen without effect; where called from ._build_grid() however, an + # old .selected_idx might point beyond _any_ of the new .slots, the # IndexError of which we still want to avoid if self.selected_idx < len(self.slots): self.slots[self.selected_idx].mark('selected', False) self.selected_idx = new_idx - if self.slots: - self.slots[self.selected_idx].mark('selected', True) - self.slots[self.selected_idx].grab_focus() + self._assert_selection() self._on_selection_change() - def _passes_filter(self, attr_name: str, val: str) -> bool: - number_attributes = (set(s.lower() for s in GEN_PARAMS_INT) | - set(s.lower() for s in GEN_PARAMS_FLOAT) | - {'bookmarked'}) - - def _passes_number_filter(attr_name, pattern, val): - use_float = attr_name in {s.lower() for s in GEN_PARAMS_FLOAT} - constraint_strings = pattern.split(',') - numbers_or = set() - unequal = set() - less_than = None - less_or_equal = None - more_or_equal = None - more_than = None - for constraint_string in constraint_strings: - toks = constraint_string.split() - if len(toks) == 1: - tok = toks[0] - if tok[0] in '<>!': - if '=' == tok[1]: - toks = [tok[:2], tok[2:]] + def _build_grid(self) -> None: + """(Re-)build slot grid from .dir_entries, filters, layout settings.""" + old_selected_item: Optional[GalleryItem] = self.selected_item + + def passes_filter(attr_name: str, val: str) -> bool: + number_attributes = (set(s.lower() for s in GEN_PARAMS_INT) | + set(s.lower() for s in GEN_PARAMS_FLOAT) | + {'bookmarked'}) + + def passes_number_filter(attr_name, pattern, val): + use_float = attr_name in {s.lower() for s in GEN_PARAMS_FLOAT} + constraint_strings = pattern.split(',') + numbers_or = set() + unequal = set() + less_than = None + less_or_equal = None + more_or_equal = None + more_than = None + for constraint_string in constraint_strings: + toks = constraint_string.split() + if len(toks) == 1: + tok = toks[0] + if tok[0] in '<>!': + if '=' == tok[1]: + toks = [tok[:2], tok[2:]] + else: + toks = [tok[:1], tok[1:]] else: - toks = [tok[:1], tok[1:]] - else: - value = float(tok) if use_float else int(tok) - numbers_or.add(value) - if len(toks) == 2: - value = float(toks[1]) if use_float else int(toks[1]) - if toks[0] == '!=': - unequal.add(value) - elif toks[0] == '<': - if less_than is None or less_than >= value: - less_than = value - elif toks[0] == '<=': - if less_or_equal is None or less_or_equal > value: - less_or_equal = value - elif toks[0] == '>=': - if more_or_equal is None or more_or_equal < value: - more_or_equal = value - elif toks[0] == '>': - if more_than is None or more_than <= value: - more_than = value - if val in numbers_or: + value = float(tok) if use_float else int(tok) + numbers_or.add(value) + if len(toks) == 2: + value = float(toks[1]) if use_float else int(toks[1]) + if toks[0] == '!=': + unequal.add(value) + elif toks[0] == '<': + if less_than is None or less_than >= value: + less_than = value + elif toks[0] == '<=': + if less_or_equal is None or less_or_equal > value: + less_or_equal = value + elif toks[0] == '>=': + if more_or_equal is None or more_or_equal < value: + more_or_equal = value + elif toks[0] == '>': + if more_than is None or more_than <= value: + more_than = value + if val in numbers_or: + return True + if len(numbers_or) > 0 and (less_than == less_or_equal == + more_or_equal == more_than): + return False + if val in unequal: + return False + if (less_than is not None + and val >= less_than)\ + or (less_or_equal is not None + and val > less_or_equal)\ + or (more_or_equal is not None + and val < more_or_equal)\ + or (more_than is not None + and val <= more_than): + return False return True - if len(numbers_or) > 0 and (less_than == less_or_equal == - more_or_equal == more_than): - return False - if val in unequal: + + if attr_name not in self._filter_inputs: + return True + if val is None: return False - if (less_than is not None - and val >= less_than)\ - or (less_or_equal is not None - and val > less_or_equal)\ - or (more_or_equal is not None - and val < more_or_equal)\ - or (more_than is not None - and val <= more_than): + pattern = self._filter_inputs[attr_name] + if attr_name in number_attributes: + if not passes_number_filter(attr_name, pattern, val): + return False + elif not re_search(pattern, val): return False return True - if attr_name not in self._filter_inputs: - return True - if val is None: - return False - pattern = self._filter_inputs[attr_name] - if attr_name in number_attributes: - if not _passes_number_filter(attr_name, pattern, val): - return False - elif not re_search(pattern, val): - return False - return True - - def _build(self) -> None: - """(Re-)build slot grid from .dir_entries, filters, layout settings.""" - self._shall_build = False - old_selected_item: Optional[GalleryItem] = self.selected_item - - def build_items_attrs() -> None: + def update_items_attrs() -> None: self.items_attrs.clear() - def collect_and_split_attr_vals( - entries: list[GalleryItem] - ) -> ItemsAttrs: - items_attrs_tmp: ItemsAttrs = {} - for attr_name in (s.name for s in self._sort_order): - items_attrs_tmp[attr_name] = {'incl': [], 'excl': []} - vals: set[str] = set() - for entry in [e for e in entries - if isinstance(e, ImgItem)]: - val = (getattr(entry, attr_name) - if hasattr(entry, attr_name) else None) - if val is not None: - vals.add(val) + def separate_items_attrs(basic_items_attrs) -> ItemsAttrs: + items_attrs: ItemsAttrs = {} + for attr_name, vals in basic_items_attrs.items(): + items_attrs[attr_name] = {'incl': [], 'excl': []} for v in vals: - k = ('incl' if self._passes_filter(attr_name, v) - else 'excl') - items_attrs_tmp[attr_name][k] += [v] - return items_attrs_tmp - - items_attrs_tmp_1 = collect_and_split_attr_vals(self.dir_entries) - filtered_entries = filter_entries(items_attrs_tmp_1) - items_attrs_tmp_2 = collect_and_split_attr_vals(filtered_entries) + k = ('incl' if passes_filter(attr_name, v) else 'excl') + items_attrs[attr_name][k] += [v] + return items_attrs + + items_attrs_tmp_1 = separate_items_attrs(self._basic_items_attrs) + filtered = filter_entries(items_attrs_tmp_1) + reduced_basic_items_attrs = self._make_basic_items_attrs(filtered) + items_attrs_tmp_2 = separate_items_attrs(reduced_basic_items_attrs) for attr_name in (s.name for s in self._sort_order): final_values: AttrValsByVisibility = {'incl': [], 'semi': []} final_values['excl'] = items_attrs_tmp_1[attr_name]['excl'] @@ -943,7 +955,7 @@ class Gallery: entries_filtered += [entry] return entries_filtered - def build_grid(entries_filtered: list[GalleryItem]) -> None: + def build(entries_filtered: list[GalleryItem]) -> None: i_row_ref, i_slot_ref = [0], [0] if self._grid.get_parent(): self._fixed_frame.remove(self._grid) @@ -964,8 +976,9 @@ class Gallery: attr_name, attr_values = remaining[0] if 1 == len(remaining): for i, attr in enumerate(ancestors): - vlabel = VerticalLabel(f'{attr[0]}: {attr[1]}', - self._slots_geometry) + parent_attr_name, parent_attr_value = attr + txt = f'{parent_attr_name}: {parent_attr_value}' + vlabel = VerticalLabel(txt, self._slots_geometry) self._grid.attach(vlabel, i, i_row_ref[0], 1, 1) row: list[Optional[GalleryItem]] row = [None] * len(attr_values) @@ -1029,9 +1042,9 @@ class Gallery: i_col += 1 self.update_config_box() - build_items_attrs() + update_items_attrs() entries_filtered = filter_entries(self.items_attrs) - build_grid(entries_filtered) + build(entries_filtered) new_idx = 0 if old_selected_item is not None: for i, slot in enumerate(self.slots): @@ -1043,19 +1056,15 @@ class Gallery: def request_update(self, select: bool = False, scroll_to_focus: bool = False, - build: bool = False, + build_grid: bool = False, load: bool = False ) -> None: """Set ._shall_… to trigger updates on next relevant interval.""" self._shall_redraw = True - if scroll_to_focus or build or select: - self._shall_select = True - if scroll_to_focus or build: - self._shall_scroll_to_focus = True - if build: - self._shall_build = True - if load: - self._shall_load = True + self._shall_select |= select or scroll_to_focus or build_grid or load + self._shall_scroll_to_focus |= scroll_to_focus or build_grid or load + self._shall_build_grid |= build_grid or load + self._shall_load |= load def move_selection(self, x_inc: Optional[int],