From cddcf0f304b1f92681d1c4dae336ad6c44bf6c89 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Mon, 30 Sep 2024 13:16:31 +0200 Subject: [PATCH] Move all table config setters into dedicated sidebar box. --- browser.py | 177 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 101 insertions(+), 76 deletions(-) diff --git a/browser.py b/browser.py index 63cff44..410fe3d 100755 --- a/browser.py +++ b/browser.py @@ -83,11 +83,12 @@ class Sorter(GObject.GObject): lambda a, b, c: filter_entry.add_css_class('temp')) -class Sorting(): +class TableConfig(): """Representation of sort and filtering settings.""" _gallery_update = None _gallery_items_attrs_full = None _gallery_items_attrs_filtered = None + _gallery_set_by_1st_sorter = None def __init__(self, sort_order): @@ -125,28 +126,52 @@ class Sorting(): def select_sort_order(_a, _b, _c): if self._last_selected: self._last_selected.get_last_child().hide() - list_item = self.selection.props.selected_item.widget + list_item = self._selection.props.selected_item.widget list_item.get_parent().grab_focus() self._last_selected = list_item + def on_by_1st_sorter_toggle(btn): + self._btn_per_row.set_sensitive(not btn.props.active) + self._btn_show_dirs.set_sensitive(not btn.props.active) + if btn.props.active: + self._btn_show_dirs.set_active(False) + self._gallery_set_by_1st_sorter(btn.props.active) + self.order = sort_order self.filter_inputs = {} self._last_selected = None - self.store = Gio.ListStore(item_type=Sorter) - self.selection = Gtk.SingleSelection.new(self.store) - self.selection.connect('selection-changed', select_sort_order) + self._store = Gio.ListStore(item_type=Sorter) + self._selection = Gtk.SingleSelection.new(self._store) + self._selection.connect('selection-changed', select_sort_order) factory = Gtk.SignalListItemFactory() factory.connect('setup', setup_sort_order_item) factory.connect('bind', bind_sort_order_item) - self.view = Gtk.ListView(model=self.selection, factory=factory) + self.view = Gtk.ListView(model=self._selection, factory=factory) + + self._btn_show_dirs = Gtk.CheckButton(label='show') + self._btn_recurse_dirs = Gtk.CheckButton(label='recurse') + self._btn_reload_dir = Gtk.Button(label='reload') + self._btn_by_1st_sorter = Gtk.CheckButton(label='by 1st sorter') + self._btn_by_1st_sorter.connect('toggled', on_by_1st_sorter_toggle) + self._btn_per_row = Gtk.SpinButton.new_with_range(1, 9, 1) + + dirs_box = Gtk.Box(orientation=OR_H) + dirs_box.append(Gtk.Label(label='directories:')) + dirs_box.append(self._btn_show_dirs) + dirs_box.append(self._btn_recurse_dirs) + dirs_box.append(self._btn_reload_dir) + + per_row_box = Gtk.Box(orientation=OR_H) + per_row_box.append(Gtk.Label(label='cols/row:')) + per_row_box.append(self._btn_by_1st_sorter) + per_row_box.append(self._btn_per_row) + self.box = Gtk.Box(orientation=OR_V) - self.box.append(Gtk.Label(label='sort order', use_markup=True)) + title = Gtk.Label(label='table config', use_markup=True) + self.box.append(title) + self.box.append(dirs_box) self.box.append(self.view) - self.btn_activate = Gtk.Button(label='activate') - self.btn_activate.props.sensitive = False - self.btn_activate.connect( - 'clicked', lambda _: self.activate_order()) - self.box.append(self.btn_activate) + self.box.append(per_row_box) @classmethod def from_suggestion(cls, suggestion_fused): @@ -168,26 +193,36 @@ class Sorting(): sort_order.reverse() return cls(sort_order) - def bind_gallery(self, on_update, items_attrs_full, items_attrs_filtered): + def bind_gallery(self, on_update, update_per_row, per_row_initial, reload, + toggle_showdirs, toggle_recurse, set_by_1st_sorter, + items_attrs_full, items_attrs_filtered): """Connect to Gallery interfaces where necessary.""" self._gallery_update = on_update + self._btn_show_dirs.connect('toggled', toggle_showdirs) + self._btn_recurse_dirs.connect('toggled', toggle_recurse) + self._btn_reload_dir.connect('clicked', reload) + self._gallery_set_by_1st_sorter = set_by_1st_sorter self._gallery_items_attrs_full = items_attrs_full self._gallery_items_attrs_filtered = items_attrs_filtered + self._btn_per_row.set_value(per_row_initial) + self._btn_per_row.connect( + 'value-changed', + lambda btn: update_per_row(btn.get_value_as_int())) def move_selection(self, direction): """Move sort order selection by direction (-1 or +1).""" min_idx, max_idx = 0, len(self.order) - 1 - cur_idx = self.selection.props.selected + cur_idx = self._selection.props.selected if (1 == direction and cur_idx < max_idx)\ or (-1 == direction and cur_idx > min_idx): - self.selection.props.selected = cur_idx + direction + self._selection.props.selected = cur_idx + direction def move_sorter(self, direction): """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)] - cur_idx = self.selection.props.selected + for i in range(self._store.get_n_items()): + tmp_sort_order += [self._store.get_item(i)] + cur_idx = self._selection.props.selected selected = tmp_sort_order[cur_idx] if direction == -1 and cur_idx > 0: prev_i = cur_idx - 1 @@ -202,28 +237,26 @@ class Sorting(): else: # to catch movement beyond limits return self.update_box(tmp_sort_order, cur_idx + direction) - self.selection.props.selected = cur_idx + direction - for i in range(self.store.get_n_items()): - sort_item = self.store.get_item(i) + self._selection.props.selected = cur_idx + direction + for i in range(self._store.get_n_items()): + sort_item = self._store.get_item(i) sort_item.widget.add_css_class('temp') - self.btn_activate.props.sensitive = True def update_box(self, alt_order=None, cur_selection=0): """Rebuild .store from .order, or alt_order if provided.""" sort_order = alt_order if alt_order else self.order - self.store.remove_all() + self._store.remove_all() for sorter in sort_order: - self.store.append(sorter) - self.selection.props.selected = cur_selection + self._store.append(sorter) + self._selection.props.selected = cur_selection def activate_order(self): """Write sort order box order into .order, mark finalized.""" self.order.clear() - for i in range(self.store.get_n_items()): - sorter = self.store.get_item(i) + for i in range(self._store.get_n_items()): + sorter = self._store.get_item(i) sorter.widget.remove_css_class('temp') self.order += [sorter] - self.btn_activate.props.sensitive = False self._gallery_update() @@ -349,8 +382,8 @@ class Gallery: self._on_selection_change = on_selection_change self.show_dirs = False - self.per_row_by_first_sorter = False - self.per_row = GALLERY_PER_ROW_DEFAULT + self.per_row_by_1st_sorter = False + self._per_row = GALLERY_PER_ROW_DEFAULT self._slot_margin = GALLERY_SLOT_MARGIN self._grid = None self._force_width, self._force_height = 0, 0 @@ -387,6 +420,20 @@ class Gallery: self._update_view(refocus=False, force=True) return True + def get_per_row(self): + """Wrapper to ._per_row to (for setting) discourage direct access.""" + return self._per_row + + def update_per_row(self, val): + """Wrapper to setting ._per_row to include call to .build_and_show.""" + self._per_row = val + self.build_and_show() + + def set_by_1st_sorter(self, val): + """On .per_row_by_1st_sorter update, also call .build_and_show.""" + self.per_row_by_1st_sorter = val + self.build_and_show() + @property def selected_item(self): """Return slot.item at self.selected_idx.""" @@ -548,7 +595,7 @@ class Gallery: self.slots = [] self._grid = Gtk.Grid() self._fixed_frame.put(self._grid, 0, 0) - if self.per_row_by_first_sorter: + if self.per_row_by_1st_sorter: self.show_dirs = False sort_attrs = [] for sorter in reversed(self._sort_order): @@ -556,14 +603,14 @@ class Gallery: self.items_attrs_filtered[sorter.name])] i_row_ref = [0] i_slot_ref = [0] - self.per_row = len(sort_attrs[-1][1]) + self._per_row = len(sort_attrs[-1][1]) build_rows_by_attrs(sort_attrs, entries_filtered) else: self.dir_entries_filtered_sorted = sorted( entries_filtered, key=cmp_to_key(self._sort_cmp)) i_row, i_col = 0, 0 for i, item in enumerate(self.dir_entries_filtered_sorted): - if self.per_row == i_col: + if self._per_row == i_col: i_col = 0 i_row += 1 slot = GallerySlot(item, item_clicker(i)) @@ -590,10 +637,10 @@ class Gallery: def move_selection(self, x_inc, y_inc, buf_end): """Move .selection, update its dependencies, redraw gallery.""" min_idx, max_idx = 0, len(self.slots) - 1 - if -1 == y_inc and self.selected_idx >= self.per_row: - new_idx = self.selected_idx - self.per_row - elif 1 == y_inc and self.selected_idx <= max_idx - self.per_row: - new_idx = self.selected_idx + self.per_row + if -1 == y_inc and self.selected_idx >= self._per_row: + new_idx = self.selected_idx - self._per_row + elif 1 == y_inc and self.selected_idx <= max_idx - self._per_row: + new_idx = self.selected_idx + self._per_row elif -1 == x_inc and self.selected_idx > 0: new_idx = self.selected_idx - 1 elif 1 == x_inc and self.selected_idx < max_idx: @@ -623,18 +670,18 @@ class Gallery: vp_bottom = vp_top + self._viewport_height vp_width = (self._force_width if self._force_width else self._viewport.get_width()) - max_slot_width = (vp_width // self.per_row) - self._slot_margin + max_slot_width = (vp_width // self._per_row) - self._slot_margin prefered_slot_height = self._viewport_height - self._slot_margin slot_size = min(prefered_slot_height, max_slot_width) for idx, slot in enumerate(self.slots): - slot_top = (idx // self.per_row) * (slot_size + self._slot_margin) + slot_top = (idx // self._per_row) * (slot_size + self._slot_margin) slot_bottom = slot_top + slot_size in_vp = (slot_bottom >= vp_top and slot_top <= vp_bottom) slot.update_widget(slot_size, self._slot_margin, in_vp) if self._col_headers_grid: self._col_headers_frame.remove(self._col_headers_grid) self._col_headers_grid = None - if self.per_row_by_first_sorter: + if self.per_row_by_1st_sorter: self._col_headers_grid = Gtk.Grid() self._col_headers_frame.put(self._col_headers_grid, 0, 0) attr_values = self.items_attrs_filtered[self._sort_order[0].name] @@ -647,7 +694,7 @@ class Gallery: return focused_idx = self.selected_idx full_slot_height = slot_size + self._slot_margin - focused_slot_top = (focused_idx // self.per_row) * full_slot_height + focused_slot_top = (focused_idx // self._per_row) * full_slot_height focused_slot_bottom = focused_slot_top + slot_size if focused_slot_top < vp_top: vp_scroll.set_value(focused_slot_top) @@ -698,9 +745,6 @@ class MainWindow(Gtk.Window): metadata: Gtk.TextBuffer prev_key: list counter: Gtk.Label - btn_dec_per_row: Gtk.Button - btn_inc_per_row: Gtk.Button - btn_show_dirs: Gtk.Button def __init__(self, app, **kwargs): super().__init__(**kwargs) @@ -719,16 +763,6 @@ class MainWindow(Gtk.Window): self.counter = Gtk.Label() navbar.append(self.counter) add_button('sidebar', lambda _: self.toggle_side_box()) - add_button('reload', lambda _: self.load_directory()) - self.btn_show_dirs = add_button('show directories', - self.reset_show_dirs, True) - add_button('recurse directories ', self.reset_recurse, True) - navbar.append(Gtk.Label(label=' per row: ')) - add_button('by first sorter', self.reset_row_by_first_sorter, True) - self.btn_dec_per_row = add_button('less', - lambda _: self.inc_per_row(-1)) - self.btn_inc_per_row = add_button('more', - lambda _: self.inc_per_row(+1)) return navbar def init_metadata_box(): @@ -771,10 +805,6 @@ class MainWindow(Gtk.Window): on_hit_item=self.hit_gallery_item, on_grid_built=self.app.sort.update_box, on_selection_change=self.update_metadata_on_gallery_selection) - self.app.sort.bind_gallery( - on_update=self.gallery.build_and_show, - items_attrs_full=self.gallery.items_attrs, - items_attrs_filtered=self.gallery.items_attrs_filtered) self.recurse_dirs = False setup_css() @@ -797,11 +827,23 @@ class MainWindow(Gtk.Window): self.load_directory(update_gallery_view=False) self.connect('notify::focus-widget', lambda _, __: self.on_focus_change()) + self.app.sort.bind_gallery( + on_update=self.gallery.build_and_show, + reload=lambda _: self.load_directory(), + update_per_row=self.gallery.update_per_row, + per_row_initial=self.gallery.get_per_row(), + toggle_showdirs=self.reset_show_dirs, + toggle_recurse=self.reset_recurse, + set_by_1st_sorter=self.gallery.set_by_1st_sorter, + items_attrs_full=self.gallery.items_attrs, + items_attrs_filtered=self.gallery.items_attrs_filtered) GLib.idle_add(self.gallery.build_and_show) def on_focus_change(self): """Handle reactions on focus changes in .gallery and .sort.""" focused = self.get_focus() + if not focused: + return if isinstance(focused, GallerySlot): self.gallery.on_focus_slot(focused) elif focused.get_parent() == self.app.sort.view: @@ -907,28 +949,11 @@ class MainWindow(Gtk.Window): side_box_width = self.side_box.measure(OR_H, -1).natural self.gallery.on_resize(self.get_width() - side_box_width) - def reset_row_by_first_sorter(self, button): - """By button's .active, (un-)mirror top sorter in each gallery row.""" - self.gallery.per_row_by_first_sorter = button.props.active - self.btn_inc_per_row.set_sensitive(not button.props.active) - self.btn_dec_per_row.set_sensitive(not button.props.active) - self.btn_show_dirs.set_sensitive(not button.props.active) - if button.props.active: - self.btn_show_dirs.set_active(False) - self.btn_show_dirs.set_sensitive(not button.props.active) - self.gallery.build_and_show() - def reset_show_dirs(self, button): """By button's .active, in-/exclude directories from gallery view.""" self.gallery.show_dirs = button.props.active self.load_directory() - def inc_per_row(self, increment): - """Change by increment how many items max to display in gallery row.""" - if self.gallery.per_row + increment > 0: - self.gallery.per_row += increment - self.gallery.build_and_show() - def reset_recurse(self, button): """By button's .active, de-/activate recursion on image collection.""" self.recurse_dirs = button.props.active @@ -996,7 +1021,7 @@ class Application(Gtk.Application): parser.add_argument('-s', '--sort-order', default=SORT_DEFAULT) opts = parser.parse_args() self.img_dir_absolute = abspath(opts.directory) - self.sort = Sorting.from_suggestion(opts.sort_order) + self.sort = TableConfig.from_suggestion(opts.sort_order) def do_activate(self, *args, **kwargs): """Parse arguments, start window, keep it open.""" -- 2.30.2