self.list_item.get_first_child().set_text(label)
-class FileItem(GObject.GObject):
+class GallerySlot(Gtk.Button):
+ """Slot in Gallery representing a GalleryItem."""
+
+ def __init__(self, item, on_click_file=None):
+ super().__init__()
+ self.add_css_class('slot')
+ self.set_hexpand(True)
+ self.item = item
+ self.item.slot = self
+ if on_click_file:
+ self.connect('clicked', on_click_file)
+
+ def mark(self, css_class, do_add=True):
+ """Add or remove css_class from self."""
+ if do_add:
+ self.add_css_class(css_class)
+ else:
+ self.remove_css_class(css_class)
+
+ def update_widget(self, slot_size, margin, is_in_vp):
+ """(Un-)Load content if (not) is_in_vp, update geometry, CSS classes"""
+ new_content = None
+ if isinstance(self.item, ImgItem):
+ if is_in_vp and not isinstance(self.item, Gtk.Image):
+ new_content = Gtk.Image.new_from_file(self.item.full_path)
+ if self.item.with_others:
+ new_content.set_vexpand(True)
+ box = Gtk.Box(orientation=OR_V)
+ box.append(new_content)
+ msg = 'and one or more other images of this configuration'
+ box.append(Gtk.Label(label=msg))
+ new_content = box
+ elif (not is_in_vp) and not isinstance(self.item, Gtk.Label):
+ new_content = Gtk.Label(label='?')
+ elif self.get_child() is None:
+ label = self.item.name if isinstance(self.item, DirItem) else '+'
+ new_content = Gtk.Label(label=label)
+ if new_content:
+ self.set_child(new_content)
+ side_margin = margin // 2
+ if side_margin:
+ for s in ('bottom', 'top', 'start', 'end'):
+ setattr(self.get_child().props, f'margin_{s}', side_margin)
+ self.get_child().set_size_request(slot_size, slot_size)
+ if isinstance(self.item, ImgItem):
+ self.mark('bookmarked', self.item.bookmarked)
+
+
+class GalleryItem(GObject.GObject):
"""Gallery representation of filesystem entry, base to DirItem, ImgItem."""
+ slot: GallerySlot
def __init__(self, path, name):
super().__init__()
self.full_path = path_join(path, self.name)
-class DirItem(FileItem):
+class DirItem(GalleryItem):
"""Gallery representation of filesystem entry for directory."""
def __init__(self, path, name, is_parent=False):
self.full_path = path
-class ImgItem(FileItem):
+class ImgItem(GalleryItem):
"""Gallery representation of filesystem entry for image file."""
def __init__(self, path, name, last_mod_time, cache):
super().__init__(path, name)
self.last_mod_time = last_mod_time
self.bookmarked = False
+ self.with_others = False
for param_name in GEN_PARAMS:
if param_name in GEN_PARAMS_STR:
setattr(self, param_name.lower(), '')
self.slot.mark('bookmarked', positive)
-class GallerySlot(Gtk.Button):
- """Slot in Gallery representing a FileItem."""
-
- def __init__(self, item, on_click_file):
- super().__init__()
- self.add_css_class('slot')
- self.set_hexpand(True)
- self.item = item
- self.item.slot = self
- self.connect('clicked', on_click_file)
-
- def mark(self, css_class, do_add=True):
- """Add or remove css_class from self."""
- if do_add:
- self.add_css_class(css_class)
- else:
- self.remove_css_class(css_class)
-
- def update_widget(self, slot_size, margin, is_in_vp):
- """(Un-)Load content if (not) is_in_vp, update geometry, CSS classes"""
- new_content = None
- if self.get_child() is None and isinstance(self.item, DirItem):
- new_content = Gtk.Label(label=self.item.name)
- elif isinstance(self.item, ImgItem):
- if is_in_vp and not isinstance(self.item, Gtk.Image):
- new_content = Gtk.Image.new_from_file(self.item.full_path)
- elif (not is_in_vp) and not isinstance(self.item, Gtk.Label):
- new_content = Gtk.Label(label='?')
- if new_content:
- self.set_child(new_content)
- side_margin = margin // 2
- if side_margin:
- for s in ('bottom', 'top', 'start', 'end'):
- setattr(self.get_child().props, f'margin_{s}', side_margin)
- self.get_child().set_size_request(slot_size, slot_size)
- if isinstance(self.item, ImgItem):
- self.mark('bookmarked', self.item.bookmarked)
-
-
class Gallery:
- """Representation of FileItems below a directory."""
+ """Representation of GalleryItems below a directory."""
def __init__(self,
sort_order,
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._slot_margin = GALLERY_SLOT_MARGIN
self._grid = None
self._force_width, self._force_height = 0, 0
self.slots = None
self.dir_entries = []
+ self.dir_entries_filtered_sorted = []
self.selected_idx = 0
self._fixed_frame = Gtk.Fixed(hexpand=True, vexpand=True)
if unselect_old:
self.slots[self.selected_idx].mark('selected', False)
self.selected_idx = new_idx
- self.slots[self.selected_idx].mark('selected', True)
- self.slots[self.selected_idx].grab_focus()
+ if self.slots:
+ self.slots[self.selected_idx].mark('selected', True)
+ self.slots[self.selected_idx].grab_focus()
self._on_selection_change()
def build_and_show(self, suggested_selection=None):
self._on_hit_item()
return f
+ def build_rows_by_attrs(remaining_attrs, items_of_parent_attr_value):
+ if not items_of_parent_attr_value:
+ return
+ attr_name, attr_values = remaining_attrs[0]
+ if 1 == len(remaining_attrs):
+ row = [None] * len(attr_values)
+ for item in items_of_parent_attr_value:
+ item_val = getattr(item, attr_name)
+ idx_item_val_in_attr_values = attr_values.index(item_val)
+ if row[idx_item_val_in_attr_values]:
+ item.with_others = True
+ row[idx_item_val_in_attr_values] = item
+ for i_col, item in enumerate(row):
+ if item:
+ slot = GallerySlot(item, item_clicker(i_slot_ref[0]))
+ else:
+ slot = GallerySlot(GalleryItem('', '')) # dummy
+ self.slots += [slot]
+ i_slot_ref[0] += 1
+ self._grid.attach(slot, i_col, i_row_ref[0], 1, 1)
+ i_row_ref[0] += 1
+ return
+ for attr_value in attr_values:
+ items_of_attr_value = [x for x in items_of_parent_attr_value
+ if attr_value == getattr(x, attr_name)]
+ build_rows_by_attrs(remaining_attrs[1:], items_of_attr_value)
+
if self._grid:
self._fixed_frame.remove(self._grid)
self.slots = []
self._grid = Gtk.Grid()
self._fixed_frame.put(self._grid, 0, 0)
- i_row, i_col = 0, 0
- for i, item in enumerate(sorted([entry for entry in self.dir_entries
- if self._filter_func(entry)],
- key=cmp_to_key(self._sort_cmp))):
- if self.per_row == i_col:
- i_col = 0
- i_row += 1
- slot = GallerySlot(item, item_clicker(i))
- self._grid.attach(slot, i_col, i_row, 1, 1)
- self.slots += [slot]
- i_col += 1
+ entries_filtered = [entry for entry in self.dir_entries
+ if self._filter_func(entry)]
+ if self.per_row_by_first_sorter:
+ self.show_dirs = False
+ sort_attrs = []
+ for sorter in reversed(self._sort_order):
+ values = set()
+ for item in [x for x in entries_filtered
+ if isinstance(x, ImgItem)]:
+ val = None
+ if hasattr(item, sorter.name):
+ val = getattr(item, sorter.name)
+ values.add(val)
+ sort_attrs += [(sorter.name, sorted(list(values)))]
+ i_row_ref = [0]
+ i_slot_ref = [0]
+ 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:
+ i_col = 0
+ i_row += 1
+ slot = GallerySlot(item, item_clicker(i))
+ self._grid.attach(slot, i_col, i_row, 1, 1)
+ self.slots += [slot]
+ i_col += 1
self._on_grid_built()
self.selected_idx = 0
self._update_view()
"""Return how many diff. values for sort_attr in (un-)filtered store"""
diversities = [0, 0]
for i, store in enumerate([self.dir_entries,
- [s.item for s in self.slots]]):
+ self.dir_entries_filtered_sorted]):
values = set()
for item in store:
if isinstance(item, ImgItem):
return -1
if isinstance(b, DirItem) and not isinstance(a, DirItem):
return +1
- # apply ._sort_order within DirItems and FileItems (separately)
+ # apply ._sort_order within DirItems and ImgItems (separately)
ret = 0
for key in [sorter.name for sorter in self._sort_order]:
a_cmp = None
sort_store: Gtk.ListStore
sort_selection: Gtk.SingleSelection
prev_key: list
- button_activate_sort: Gtk.Button
counter: Gtk.Label
+ btn_activate_sort: Gtk.Button
+ 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)
self.app = app
def init_navbar():
- def add_button(label_, on_click, parent_box):
- btn = Gtk.Button(label=label_)
- btn.connect('clicked', on_click)
- parent_box.append(btn)
+
+ def add_button(label_, on_click, checkbox=False):
+ btn = (Gtk.CheckButton(label=label_) if checkbox
+ else Gtk.Button(label=label_))
+ btn.connect('toggled' if checkbox else 'clicked', on_click)
+ navbar.append(btn)
+ return btn
+
navbar = Gtk.Box(orientation=OR_H)
self.counter = Gtk.Label()
navbar.append(self.counter)
- add_button('sidebar', lambda _: self.toggle_side_box(), navbar)
- add_button('reload', lambda _: self.load_directory(), navbar)
+ 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('less', lambda _: self.inc_per_row(-1), navbar)
- add_button('more', lambda _: self.inc_per_row(+1), navbar)
- btn = Gtk.CheckButton(label='show directories')
- btn.connect('toggled', self.reset_show_dirs)
- navbar.append(btn)
- btn = Gtk.CheckButton(label='recurse directories')
- btn.connect('toggled', self.reset_recurse)
- navbar.append(btn)
+ 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():
sort_box = Gtk.Box(orientation=OR_V)
sort_box.append(Gtk.Label(label='** sort order **'))
sort_box.append(selector)
- self.button_activate_sort = Gtk.Button(label='activate')
- self.button_activate_sort.props.sensitive = False
- self.button_activate_sort.connect(
+ self.btn_activate_sort = Gtk.Button(label='activate')
+ self.btn_activate_sort.props.sensitive = False
+ self.btn_activate_sort.connect(
'clicked', lambda _: self.activate_sort_order())
- sort_box.append(self.button_activate_sort)
+ sort_box.append(self.btn_activate_sort)
return sort_box
def init_key_control():
sorter = self.sort_store.get_item(i)
sorter.list_item.remove_css_class('temp')
self.app.sort_order += [sorter]
- self.button_activate_sort.props.sensitive = False
+ self.btn_activate_sort.props.sensitive = False
old_selection = self.gallery.selected_item
self.gallery.build_and_show(old_selection)
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(self.gallery.selected_item)
+
def reset_show_dirs(self, button):
"""By button's .active, in-/exclude directories from gallery view."""
self.gallery.show_dirs = button.props.active
for i in range(self.sort_store.get_n_items()):
sort_item = self.sort_store.get_item(i)
sort_item.list_item.add_css_class('temp')
- self.button_activate_sort.props.sensitive = True
+ self.btn_activate_sort.props.sensitive = True
def move_selection_in_sort_order(self, direction):
"""Move sort order selection by direction (-1 or +1)."""
bookmarked = 'BOOKMARK' if selected_item.bookmarked else ''
self.metadata.set_text(
'\n'.join([title, bookmarked] + params_strs))
- total = len(self.gallery.slots)
+ total = len(self.gallery.dir_entries_filtered_sorted)
self.counter.set_text(f' {self.gallery.selected_idx + 1} of {total} ')
def handle_keypress(self, keyval):