home · contact · privacy
Browser: Refactor filtering.
authorChristian Heller <c.heller@plomlompom.de>
Tue, 12 Nov 2024 14:16:19 +0000 (15:16 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 12 Nov 2024 14:16:19 +0000 (15:16 +0100)
browser.py

index dd84621e6951a5c03b5da9a5f19b51a8bf190899..63b20ec2a33d3ce1e11e957313c27df07b778dc5 100755 (executable)
@@ -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