From: Christian Heller Date: Tue, 12 Nov 2024 14:16:19 +0000 (+0100) Subject: Browser: Refactor filtering. X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/balance?a=commitdiff_plain;h=f1d3513867f533449f3b753bb6185c721854f875;p=stable_plom Browser: Refactor filtering. --- diff --git a/browser.py b/browser.py index dd84621..63b20ec 100755 --- a/browser.py +++ b/browser.py @@ -170,7 +170,7 @@ class SorterAndFilterer(GObject.GObject): def __init__(self, name: str) -> None: super().__init__() self.name = name - self.filter = '' + self.filter_text = '' def setup_on_bind(self, widget: Gtk.Box, @@ -195,21 +195,74 @@ class SorterAndFilterer(GObject.GObject): def filter_activate() -> None: self.widget.filter_input.remove_css_class('temp') - self.filter = self.widget.filter_input.get_buffer().get_text() + self.filter_text = self.widget.filter_input.get_buffer().get_text() on_filter_activate() filter_buffer = self.widget.filter_input.get_buffer() - filter_buffer.set_text(self.filter, -1) # triggers 'temp' class set, - self.widget.filter_input.remove_css_class('temp') # that's why … - self.widget.filter_input.connect( - 'activate', - lambda _: filter_activate()) + filter_buffer.set_text(self.filter_text, -1) # triggers 'temp' class + self.widget.filter_input.remove_css_class('temp') # set, that's why … + self.widget.filter_input.connect('activate', + lambda _: filter_activate()) filter_buffer.connect( - 'inserted_text', - lambda a, b, c, d: self.widget.filter_input.add_css_class('temp')) + 'inserted_text', + lambda a, b, c, d: self.widget.filter_input.add_css_class('temp')) filter_buffer.connect( - 'deleted_text', - lambda a, b, c: self.widget.filter_input.add_css_class('temp')) + 'deleted_text', + lambda a, b, c: self.widget.filter_input.add_css_class('temp')) + + def passes_filter(self, value: str | int | float) -> bool: + """Return if value passes filter defined by .name and .filter_text.""" + number_attributes = (set(s.lower() for s in GEN_PARAMS_INT) | + set(s.lower() for s in GEN_PARAMS_FLOAT) | + {'bookmarked'}) + if value is None: + return False + if self.name not in number_attributes: + assert isinstance(value, str) + return bool(re_search(self.filter_text, value)) + assert isinstance(value, (int, float)) + use_float = self.name in {s.lower() for s in GEN_PARAMS_FLOAT} + numbers_or, unequal = (set(),) * 2 + less_than, less_or_equal, more_or_equal, more_than = (None,) * 4 + for constraint_string in self.filter_text.split(','): + toks = constraint_string.split() + if len(toks) == 1: + tok = toks[0] + if tok[0] in '<>!': # operator sans space after: split, re-try + if '=' == tok[1]: + toks = [tok[:2], tok[2:]] + else: + toks = [tok[:1], tok[1:]] + else: + pattern_number = float(tok) if use_float else int(tok) + numbers_or.add(pattern_number) + if len(toks) == 2: # assume operator followed by number + pattern_number = float(toks[1]) if use_float else int(toks[1]) + if toks[0] == '!=': + unequal.add(pattern_number) + elif toks[0] == '<': + if less_than is None or less_than >= pattern_number: + less_than = pattern_number + elif toks[0] == '<=': + if less_or_equal is None or less_or_equal > pattern_number: + less_or_equal = pattern_number + elif toks[0] == '>=': + if more_or_equal is None or more_or_equal < pattern_number: + more_or_equal = pattern_number + elif toks[0] == '>': + if more_than is None or more_than <= pattern_number: + more_than = pattern_number + if value in numbers_or: + return True + if len(numbers_or) > 0 and (less_than == less_or_equal == + more_or_equal == more_than): + return False + if value in unequal: + return False + return ((less_than is None or value < less_than) + and (less_or_equal is None or value <= less_or_equal) + and (more_or_equal is None or value >= more_or_equal) + and (more_than is None or value > more_than)) class SorterAndFiltererOrder: @@ -261,6 +314,12 @@ class SorterAndFiltererOrder: """Create new, mirroring order in store.""" return cls(cls._list_from_store(store)) + def by_name(self, name: str) -> Optional[SorterAndFilterer]: + """Return included SorterAndFilterer of name.""" + for s in [s for s in self._list if name == s.name]: + return s + return None + def copy(self) -> Self: """Create new, of equal order.""" return self.__class__(self._list[:]) @@ -271,8 +330,9 @@ class SorterAndFiltererOrder: def remove(self, sorter_name: str) -> None: """Remove sorter of sorter_name from self.""" - for sorter in [s for s in self._list if sorter_name == s.name]: - self._list.remove(sorter) + candidate = self.by_name(sorter_name) + assert candidate is not None + self._list.remove(candidate) def update_from_store(self, store: Gio.ListStore) -> None: """Update self from store.""" @@ -854,87 +914,17 @@ class Gallery: """(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: - 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 val is None: - return False - for filterer in [f for f in self._sort_order - if f.name == attr_name]: - if attr_name in number_attributes: - if not passes_number_filter(attr_name, filterer.filter, - val): - return False - elif not re_search(filterer.filter, val): - return False - return True - def update_items_attrs() -> None: self.items_attrs.clear() def separate_items_attrs(basic_items_attrs) -> ItemsAttrs: items_attrs: ItemsAttrs = {} for attr_name, vals in basic_items_attrs.items(): + sorter = self._sort_order.by_name(attr_name) items_attrs[attr_name] = {'incl': [], 'excl': []} for v in vals: - k = 'incl' if passes_filter(attr_name, v) else 'excl' + k = ('incl' if (not sorter or sorter.passes_filter(v)) + else 'excl') items_attrs[attr_name][k] += [v] return items_attrs