From f475390b287dbdb2a456361faee476d13b2f0c9d Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 17:02:34 -0400 Subject: [PATCH] Add batch copy --- src/snapshot_page/snapshot_page.blp | 56 +++++++++++++ src/snapshot_page/snapshot_page.py | 120 +++++++++++++++++++++++----- 2 files changed, 157 insertions(+), 19 deletions(-) diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 92b2ee1..245026e 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -129,6 +129,44 @@ template $SnapshotPage : Adw.BreakpointBin { } } } + [bottom] + Revealer { + reveal-child: bind select_button.active; + transition-type: slide_up; + [center] + Box bottom_bar { + styles ["toolbar"] + hexpand: true; + homogeneous: true; + Button select_all_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "selection-mode-symbolic"; + label: _("Select All"); + can-shrink: true; + } + } + Button copy_button { + sensitive: false; + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; + } + } + MenuButton more_button { + sensitive: false; + popover: more_popover; + styles ["raised"] + Adw.ButtonContent { + icon-name: "view-more-symbolic"; + label: _("More"); + can-shrink: true; + } + } + } + } } } ; @@ -178,3 +216,21 @@ template $SnapshotPage : Adw.BreakpointBin { } } } + +Popover more_popover { + styles ["menu"] + ListBox more_menu { + Label new_snapshots { + label: _("New Snapshots"); + halign: start; + } + Label apply_snapshots { + label: _("Apply Snapshots"); + halign: start; + } + Label trash_snapshots { + label: _("Trash Snapshots"); + halign: start; + } + } +} diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index f86d7e8..eacb057 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -32,10 +32,12 @@ class LeftoverSnapshotRow(Adw.ActionRow): class SnapshotPage(Adw.BreakpointBin): __gtype_name__ = "SnapshotPage" gtc = Gtk.Template.Child - + toast_overlay = gtc() + sidebar_navpage = gtc() search_entry = gtc() search_bar = gtc() + select_button = gtc() active_box = gtc() active_listbox = gtc() leftover_box = gtc() @@ -52,55 +54,63 @@ class SnapshotPage(Adw.BreakpointBin): status_stack = gtc() loading_view = gtc() snapshotting_view = gtc() - + select_all_button = gtc() + copy_button = gtc() + more_button = gtc() + more_menu = gtc() + new_snapshots = gtc() + apply_snapshots = gtc() + trash_snapshots = gtc() + # Referred to in the main window # It is used to determine if a new page should be made or not # This must be set to the created object from within the class's __init__ method instance = None page_name = "snapshots" - + def sort_snapshots(self, *args): self.active_snapshot_paks.clear() self.leftover_snapshots.clear() bad_folders = [] - + if not os.path.exists(HostInfo.snapshots_path): try: os.makedirs(HostInfo.snapshots_path) except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not load Snapshots"), str(e)).toast) return - + for folder in os.listdir(HostInfo.snapshots_path): if folder.count('.') < 2 or ' ' in folder: bad_folders.append(folder) continue - + has_tar = False for file in os.listdir(f"{HostInfo.snapshots_path}{folder}"): if file.endswith(".tar.zst"): has_tar = True break - + if not has_tar: bad_folders.append(folder) continue - + try: pak = HostInfo.id_to_flatpak[folder] self.active_snapshot_paks.append(pak) except KeyError: self.leftover_snapshots.append(folder) - + for folder in bad_folders: try: subprocess.run(['gio', 'trash', f'{HostInfo.snapshots_path}{folder}']) except Exception: pass - + def generate_active_list(self): for pak in self.active_snapshot_paks: row = AppRow(pak) + row.check_button.connect("toggled", lambda *_, _row=row: self.row_select_handler(_row)) self.active_listbox.append(row) if len(self.active_snapshot_paks) > 0: @@ -109,12 +119,13 @@ class SnapshotPage(Adw.BreakpointBin): # self.active_listbox.select_row(first_row) else: self.active_box.set_visible(False) - + def generate_leftover_list(self): for folder in self.leftover_snapshots: row = LeftoverSnapshotRow(folder) + row.check_button.connect("toggled", lambda *_, _row=row: self.row_select_handler(_row)) self.leftover_listbox.append(row) - + if len(self.leftover_snapshots) > 0: self.leftover_box.set_visible(True) if len(self.active_snapshot_paks) == 0: @@ -123,17 +134,17 @@ class SnapshotPage(Adw.BreakpointBin): # self.leftover_listbox.select_row(first_row) else: self.leftover_box.set_visible(False) - + def active_select_handler(self, listbox, row, should_show_content=True, refresh=False): self.leftover_listbox.select_row(None) self.list_page.set_snapshots(row.package, refresh) self.split_view.set_show_content(should_show_content) - + def leftover_select_handler(self, listbox, row, should_show_content=True, refresh=False): self.active_listbox.select_row(None) self.list_page.set_snapshots(row.folder, refresh) self.split_view.set_show_content(should_show_content) - + def select_first_row(self): if row := self.active_listbox.get_row_at_index(0): self.active_listbox.select_row(row) @@ -156,14 +167,14 @@ class SnapshotPage(Adw.BreakpointBin): toast = Adw.Toast(title=_("No snapshots for {}").format(package.info['name']), button_label=_("New")) toast.connect("button-clicked", lambda *_: dialog.present(HostInfo.main_window)) self.toast_overlay.add_toast(toast) - + def start_loading(self): self.status_stack.set_visible_child(self.loading_view) self.active_box.set_visible(True) self.active_listbox.remove_all() self.leftover_box.set_visible(True) self.leftover_listbox.remove_all() - + def end_loading(self): def callback(*args): self.new_snapshot_dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh) @@ -176,8 +187,10 @@ class SnapshotPage(Adw.BreakpointBin): GLib.idle_add(lambda *_: self.stack.set_visible_child(self.scrolled_window)) GLib.idle_add(lambda *_: self.status_stack.set_visible_child(self.split_view)) + self.selected_active_rows.clear() + self.selected_leftover_rows.clear() Gio.Task.new(None, None, callback).run_in_thread(self.sort_snapshots) - + def open_snapshots_folder(self, button): try: Gio.AppInfo.launch_default_for_uri(f"file://{HostInfo.snapshots_path}", None) @@ -188,7 +201,7 @@ class SnapshotPage(Adw.BreakpointBin): def on_cancel(self): for worker in self.new_snapshot_dialog.workers: worker.do_cancel("manual_cancel") - + def on_new(self, *args): self.new_snapshot_dialog.present(HostInfo.main_window) @@ -228,6 +241,70 @@ class SnapshotPage(Adw.BreakpointBin): return row1.package.info['name'].lower() > row2.package.info['name'].lower() else: return row1.name.lower() > row2.name.lower() + + def set_selection_mode(self, *args): + enable = self.select_button.get_active() + i = 0 + while row := self.active_listbox.get_row_at_index(i): + i += 1 + row.check_button.set_visible(enable) + if not enable: + row.check_button.set_active(False) + + i = 0 + while row := self.leftover_listbox.get_row_at_index(i): + i += 1 + row.check_button.set_visible(enable) + if not enable: + row.check_button.set_active(False) + + def select_all_handler(self, *args): + i = 0 + while row := self.active_listbox.get_row_at_index(i): + i += 1 + row.check_button.set_active(True) + + i = 0 + while row := self.leftover_listbox.get_row_at_index(i): + i += 1 + row.check_button.set_active(True) + + def row_select_handler(self, row): + if type(row) is AppRow: + if row.check_button.get_active(): + self.selected_active_rows.append(row) + elif row in self.selected_active_rows: + self.selected_active_rows.remove(row) + elif type(row) is LeftoverSnapshotRow: + if row.check_button.get_active(): + self.selected_leftover_rows.append(row) + elif row in self.selected_leftover_rows: + self.selected_leftover_rows.remove(row) + + total_active = len(self.selected_active_rows) + total_leftover = len(self.selected_leftover_rows) + total = total_active + total_leftover + self.sidebar_navpage.set_title(_("{} Selected").format(total_active + total_leftover) if total > 0 else _("Snapshots")) + self.copy_button.set_sensitive(total > 0) + self.more_button.set_sensitive(total > 0) + + def select_copy_handler(self, *args): + to_copy = "" + i = 0 + while row := self.active_listbox.get_row_at_index(i): + i += 1 + if row.check_button.get_active(): + to_copy += f"{HostInfo.snapshots_path}{row.package.info['id']}\n" + + i = 0 + while row := self.leftover_listbox.get_row_at_index(i): + i += 1 + if row.check_button.get_active(): + to_copy += f"{HostInfo.snapshots_path}{row.folder}\n" + + to_copy = to_copy[0:-1] + HostInfo.clipboard.set(to_copy) + self.toast_overlay.add_toast(Adw.Toast(title=_("Copied Snapshot Paths"))) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -236,6 +313,8 @@ class SnapshotPage(Adw.BreakpointBin): self.__class__.instance = self self.main_window = main_window self.active_snapshot_paks = [] + self.selected_active_rows = [] + self.selected_leftover_rows = [] # self.active_rows = [] self.leftover_snapshots = [] # self.leftover_rows = [] @@ -250,6 +329,9 @@ class SnapshotPage(Adw.BreakpointBin): self.status_new_button.connect("clicked", self.on_new) self.new_button.connect("clicked", self.on_new) self.search_entry.connect("search-changed", self.on_search) + self.select_button.connect("toggled", self.set_selection_mode) + self.select_all_button.connect("clicked", self.select_all_handler) + self.copy_button.connect("clicked", self.select_copy_handler) # Apply self.search_bar.set_key_capture_widget(HostInfo.main_window)