diff --git a/src/main.py b/src/main.py index 9d74084..967c53e 100644 --- a/src/main.py +++ b/src/main.py @@ -26,7 +26,6 @@ gi.require_version('Adw', '1') from gi.repository import Gtk, Gio, Adw, GLib from .window import FlattoolGuiWindow -from .orphans_window import OrphansWindow class FlattoolGuiApplication(Adw.Application): @@ -62,7 +61,7 @@ class FlattoolGuiApplication(Adw.Application): self.props.active_window.batch_select_all_handler(select_button) def manage_data_shortcut(self, widget, _): - OrphansWindow(self.props.active_window) + self.props.active_window.orphans_window() def refresh_list_shortcut(self, widget, _): self.props.active_window.refresh_list_of_flatpaks(widget, True) diff --git a/src/orphans_window.py b/src/orphans_window.py index 408348b..5809b0e 100644 --- a/src/orphans_window.py +++ b/src/orphans_window.py @@ -2,234 +2,5 @@ from gi.repository import Gtk, Adw, GLib, Gdk, Gio import subprocess import os -class OrphansWindow: - window_title = _("Manage Leftover Data") - orphans_window = Adw.Window(title=window_title) - orphans_window.set_default_size(350, 450) - orphans_window.set_modal(True) - orphans_window.set_resizable(True) - orphans_scroll = Gtk.ScrolledWindow() - orphans_toast_overlay = Adw.ToastOverlay() - orphans_stack = Gtk.Stack() - orphans_overlay = Gtk.Overlay() - orphans_stack.add_child(orphans_overlay) - orphans_toast_overlay.set_child(orphans_stack) - - orphans_progress_bar = Gtk.ProgressBar(visible=False, pulse_step=0.7) - orphans_progress_bar.add_css_class("osd") - orphans_overlay.add_overlay(orphans_progress_bar) - - orphans_overlay.set_child(orphans_scroll) - orphans_toolbar_view = Adw.ToolbarView() - orphans_title_bar = Gtk.HeaderBar() - orphans_action_bar = Gtk.ActionBar() - orphans_toolbar_view.add_top_bar(orphans_title_bar) - orphans_toolbar_view.add_bottom_bar(orphans_action_bar) - orphans_toolbar_view.set_content(orphans_toast_overlay) - orphans_window.set_content(orphans_toolbar_view) - orphans_list = Gtk.ListBox(selection_mode="none", valign=Gtk.Align.START, margin_top=6, margin_bottom=6, margin_start=12, margin_end=12) - orphans_list.add_css_class("boxed-list") - orphans_scroll.set_child(orphans_list) - no_data = Adw.StatusPage(icon_name="check-plain-symbolic", title=_("No Data"), description=_("There is no leftover user data")) - orphans_stack.add_child(no_data) - total_selected = 0 - selected_rows = [] - should_pulse = False - total_to_install = 0 - - def __init__(self, window): - self.orphans_window.set_transient_for(window) - self.generate_list(window, False) - self.orphans_window.present() - - def orphans_pulser(self): - self.should_pulse - if should_pulse: - self.orphans_progress_bar.pulse() - GLib.timeout_add(500, self.orphans_pulser) - - def toggle_button_handler(self, button): - if button.get_active(): - generate_list(button, True) - else: - generate_list(button, False) - - def generate_list(self, widget, is_select_all): - orphans_window.set_title(window_title) - total_selected = 0 - selected_rows = [] - trash_button.set_sensitive(False) - install_button.set_sensitive(False) - - orphans_list.remove_all() - file_list = os.listdir(window.user_data_path) - id_list = [] - - for i in range(len(window.host_flatpaks)): - id_list.append(window.host_flatpaks[i][2]) - - row_index = -1 - for i in range(len(file_list)): - if not file_list[i] in id_list: - row_index += 1 - select_orphans_tickbox = Gtk.CheckButton(halign=Gtk.Align.CENTER) - orphans_row = Adw.ActionRow(title=GLib.markup_escape_text(file_list[i]), subtitle=_("~") + window.get_size_format(window.get_directory_size(f"{window.user_data_path}{file_list[i]}"))) - orphans_row.add_suffix(select_orphans_tickbox) - orphans_row.set_activatable_widget(select_orphans_tickbox) - select_orphans_tickbox.connect("toggled", selection_handler, orphans_row.get_title()) - if is_select_all == True: - select_orphans_tickbox.set_active(True) - orphans_list.append(orphans_row) - if not orphans_list.get_row_at_index(0): - orphans_stack.set_visible_child(no_data) - orphans_action_bar.set_revealed(False) - - def key_handler(self, _a, event, _c, _d): - if event == Gdk.KEY_Escape: - orphans_window.close() - elif event == Gdk.KEY_Delete or event == Gdk.KEY_BackSpace: - trash_button_handler(event) - - def trash_button_handler(self, widget): - if total_selected == 0: - return 1 - show_success = True - for i in range(len(selected_rows)): - path = f"{window.user_data_path}{selected_rows[i]}" - try: - subprocess.run(["flatpak-spawn", "--host", "gio", "trash", path], capture_output=False, check=True) - except: - orphans_toast_overlay.add_toast(Adw.Toast.new(_("Can't trash {}").format(selected_rows[i]))) - show_success = False - select_all_button.set_active(False) - - if show_success: - orphans_toast_overlay.add_toast(Adw.Toast.new(_("Trashed data"))) - - generate_list(widget, False) - - handler_id = 0 - def install_callback(self, *_args): - total_to_install = total_to_install - 1 - - if total_to_install == 0: - if window.install_success: - orphans_toast_overlay.add_toast(Adw.Toast.new(_("Installed all apps"))) - else: - orphans_toast_overlay.add_toast(Adw.Toast.new(_("Some apps didn't install"))) - select_all_button.set_active(False) - self.orphans_progress_bar.set_visible(False) - should_pulse = False - window.refresh_list_of_flatpaks(None, False) - generate_list(None, False) - orphans_toolbar_view.set_sensitive(True) - orphans_window.disconnect(handler_id) # Make window able to close - - def thread_func(self, command): - try: - subprocess.run(command, capture_output=False, check=True) - except subprocess.CalledProcessError: - window.install_success = False - - def install_on_response(_a, response_id, _b): - if response_id == "cancel": - should_pulse = False - self.orphans_progress_bar.set_visible(False) - return 1 - - orphans_toast_overlay.add_toast(Adw.Toast.new(_("This could take some time"))) - orphans_toolbar_view.set_sensitive(False) - handler_id = orphans_window.connect('close-request', lambda event: True) # Make window unable to close - total_to_install = len(selected_rows) - - self.orphans_progress_bar.set_visible(True) - for i in range(len(selected_rows)): - remote = response_id.split("_") - command = ["flatpak-spawn", "--host", "flatpak", "install", "-y", remote[0]] - if "user" in remote[1]: - command.append("--user") - else: - command.append("--system") - command.append(selected_rows[i]) - - task = Gio.Task.new(None, None, install_callback) - task.run_in_thread(lambda _task, _obj, _data, _cancellable, cmd=command: thread_func(cmd)) - - def install_button_handler(self, widget): - window.install_success = True - should_pulse = True - self.orphans_pulser() - - def get_host_remotes(): - output = subprocess.run(["flatpak-spawn", "--host", "flatpak", "remotes"], capture_output=True, text=True).stdout - lines = output.strip().split("\n") - columns = lines[0].split("\t") - data = [columns] - for line in lines[1:]: - row = line.split("\t") - data.append(row) - return data - - host_remotes = get_host_remotes() - - dialog = Adw.MessageDialog.new(window, _("Choose a Remote")) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.connect("response", install_on_response, dialog.choose_finish) - dialog.set_transient_for(orphans_window) - if len(host_remotes) > 1: - dialog.set_body(_("Choose the Flatpak Remote Repository where attempted app downloads will be from.")) - for i in range(len(host_remotes)): - remote_name = host_remotes[i][0] - remote_option = host_remotes[i][1] - dialog.add_response(f"{remote_name}_{remote_option}", f"{remote_name} {remote_option}") - dialog.set_response_appearance(f"{remote_name}_{remote_option}", Adw.ResponseAppearance.SUGGESTED,) - else: - remote_name = host_remotes[0][0] - remote_option = host_remotes[0][1] - dialog.set_heading("Attempt to Install Matching Flatpaks?") - dialog.add_response(f"{remote_name}_{remote_option}", _("Continue")) - Gtk.Window.present(dialog) - - event_controller = Gtk.EventControllerKey() - event_controller.connect("key-pressed", key_handler) - orphans_window.add_controller(event_controller) - - select_all_button = Gtk.ToggleButton(label=_("Select All")) - select_all_button.connect("toggled", toggle_button_handler) - orphans_action_bar.pack_start(select_all_button) - - trash_button = Gtk.Button(label="Trash", valign=Gtk.Align.CENTER, tooltip_text=_("Trash Selected")) - trash_button.add_css_class("destructive-action") - trash_button.connect("clicked", trash_button_handler) - orphans_action_bar.pack_end(trash_button) - - install_button = Gtk.Button(label="Install", valign=Gtk.Align.CENTER, tooltip_text=_("Attempt to Install Selected")) - install_button.connect("clicked", install_button_handler) - install_button.set_visible(False) - orphans_action_bar.pack_end(install_button) - test = subprocess.run(["flatpak-spawn", "--host", "flatpak", "remotes"], capture_output=True, text=True).stdout - for char in test: - if char.isalnum(): - install_button.set_visible(True) - - def selection_handler(self, tickbox, file): - global total_selected - global selected_rows - if tickbox.get_active() == True: - total_selected += 1 - selected_rows.append(file) - else: - total_selected -= 1 - to_find = file - selected_rows.remove(to_find) - - if total_selected == 0: - orphans_window.set_title(window_title) - trash_button.set_sensitive(False) - install_button.set_sensitive(False) - select_all_button.set_active(False) - else: - orphans_window.set_title(_("{} Selected").format(total_selected)) - trash_button.set_sensitive(True) - install_button.set_sensitive(True) +def show_orphans_window(): + x = 12 # place holder until I properly seperate the files \ No newline at end of file diff --git a/src/window.py b/src/window.py index 7888950..c428bbe 100644 --- a/src/window.py +++ b/src/window.py @@ -22,6 +22,7 @@ import subprocess from gi.repository import Adw, Gdk, Gio, GLib, Gtk from .show_properties_window import show_properties_window +from .orphans_window import show_orphans_window @Gtk.Template(resource_path="/io/github/heliguy4599/FlattoolGUI/window.ui") class FlattoolGuiWindow(Adw.ApplicationWindow): @@ -150,7 +151,7 @@ class FlattoolGuiWindow(Adw.ApplicationWindow): task = Gio.Task.new(None, None, uninstall_callback) task.run_in_thread(thread_func) - def uninstall_button_handler(self, _widget, index): + def uninstall_flatpak(self, _widget, index): self.should_pulse = True self.main_pulser() self.uninstall_success = True @@ -170,7 +171,254 @@ class FlattoolGuiWindow(Adw.ApplicationWindow): dialog.connect("response", self.uninstall_response, dialog.choose_finish, index) Gtk.Window.present(dialog) - + def orphans_window(self): + global window_title + window_title = _("Manage Leftover Data") + orphans_window = Adw.Window(title=window_title) + orphans_window.set_default_size(350, 450) + # orphans_window.set_size_request(250, 0) + orphans_window.set_modal(True) + orphans_window.set_resizable(True) + orphans_window.set_transient_for(self) + orphans_scroll = Gtk.ScrolledWindow() + orphans_toast_overlay = Adw.ToastOverlay() + orphans_stack = Gtk.Stack() + orphans_overlay = Gtk.Overlay() + orphans_stack.add_child(orphans_overlay) + orphans_toast_overlay.set_child(orphans_stack) + + orphans_progress_bar = Gtk.ProgressBar(visible=False, pulse_step=0.7) + orphans_progress_bar.add_css_class("osd") + orphans_overlay.add_overlay(orphans_progress_bar) + + orphans_overlay.set_child(orphans_scroll) + orphans_toolbar_view = Adw.ToolbarView() + orphans_title_bar = Gtk.HeaderBar() + orphans_action_bar = Gtk.ActionBar() + orphans_toolbar_view.add_top_bar(orphans_title_bar) + orphans_toolbar_view.add_bottom_bar(orphans_action_bar) + orphans_toolbar_view.set_content(orphans_toast_overlay) + orphans_window.set_content(orphans_toolbar_view) + orphans_list = Gtk.ListBox(selection_mode="none", valign=Gtk.Align.START, margin_top=6, margin_bottom=6, margin_start=12, margin_end=12) + orphans_list.add_css_class("boxed-list") + orphans_scroll.set_child(orphans_list) + no_data = Adw.StatusPage(icon_name="check-plain-symbolic", title=_("No Data"), description=_("There is no leftover user data")) + orphans_stack.add_child(no_data) + global total_selected + total_selected = 0 + global selected_rows + selected_rows = [] + should_pulse = False + total_to_install = 0 + show_orphans_window() + + def orphans_pulser(): + nonlocal should_pulse + if should_pulse: + orphans_progress_bar.pulse() + GLib.timeout_add(500, orphans_pulser) + + def toggle_button_handler(button): + if button.get_active(): + generate_list(button, True) + else: + generate_list(button, False) + + def generate_list(widget, is_select_all): + global window_title + orphans_window.set_title(window_title) + global total_selected + total_selected = 0 + global selected_rows + selected_rows = [] + trash_button.set_sensitive(False) + install_button.set_sensitive(False) + + orphans_list.remove_all() + file_list = os.listdir(self.user_data_path) + id_list = [] + + for i in range(len(self.host_flatpaks)): + id_list.append(self.host_flatpaks[i][2]) + + row_index = -1 + for i in range(len(file_list)): + if not file_list[i] in id_list: + row_index += 1 + select_orphans_tickbox = Gtk.CheckButton(halign=Gtk.Align.CENTER) + orphans_row = Adw.ActionRow(title=GLib.markup_escape_text(file_list[i]), subtitle=_("~") + self.get_size_format(self.get_directory_size(f"{self.user_data_path}{file_list[i]}"))) + orphans_row.add_suffix(select_orphans_tickbox) + orphans_row.set_activatable_widget(select_orphans_tickbox) + select_orphans_tickbox.connect("toggled", selection_handler, orphans_row.get_title()) + if is_select_all == True: + select_orphans_tickbox.set_active(True) + orphans_list.append(orphans_row) + if not orphans_list.get_row_at_index(0): + orphans_stack.set_visible_child(no_data) + orphans_action_bar.set_revealed(False) + + def key_handler(_a, event, _c, _d): + if event == Gdk.KEY_Escape: + orphans_window.close() + elif event == Gdk.KEY_Delete or event == Gdk.KEY_BackSpace: + trash_button_handler(event) + + def trash_button_handler(widget): + if total_selected == 0: + return 1 + show_success = True + for i in range(len(selected_rows)): + path = f"{self.user_data_path}{selected_rows[i]}" + try: + subprocess.run(["flatpak-spawn", "--host", "gio", "trash", path], capture_output=False, check=True) + except: + orphans_toast_overlay.add_toast(Adw.Toast.new(_("Can't trash {}").format(selected_rows[i]))) + show_success = False + select_all_button.set_active(False) + + if show_success: + orphans_toast_overlay.add_toast(Adw.Toast.new(_("Trashed data"))) + + generate_list(widget, False) + + handler_id = 0 + def install_callback(*_args): + nonlocal total_to_install + total_to_install = total_to_install - 1 + nonlocal should_pulse + nonlocal handler_id + + if total_to_install == 0: + if self.install_success: + orphans_toast_overlay.add_toast(Adw.Toast.new(_("Installed all apps"))) + else: + orphans_toast_overlay.add_toast(Adw.Toast.new(_("Some apps didn't install"))) + select_all_button.set_active(False) + orphans_progress_bar.set_visible(False) + should_pulse = False + self.refresh_list_of_flatpaks(None, False) + generate_list(None, False) + nonlocal orphans_toolbar_view + orphans_toolbar_view.set_sensitive(True) + orphans_window.disconnect(handler_id) # Make window able to close + + def thread_func(command): + try: + subprocess.run(command, capture_output=False, check=True) + except subprocess.CalledProcessError: + self.install_success = False + + def install_on_response(_a, response_id, _b): + nonlocal should_pulse + if response_id == "cancel": + should_pulse = False + orphans_progress_bar.set_visible(False) + return 1 + + orphans_toast_overlay.add_toast(Adw.Toast.new(_("This could take some time"))) + nonlocal orphans_toolbar_view + orphans_toolbar_view.set_sensitive(False) + nonlocal handler_id + handler_id = orphans_window.connect('close-request', lambda event: True) # Make window unable to close + nonlocal total_to_install + total_to_install = len(selected_rows) + + orphans_progress_bar.set_visible(True) + for i in range(len(selected_rows)): + remote = response_id.split("_") + command = ["flatpak-spawn", "--host", "flatpak", "install", "-y", remote[0]] + if "user" in remote[1]: + command.append("--user") + else: + command.append("--system") + command.append(selected_rows[i]) + + task = Gio.Task.new(None, None, install_callback) + task.run_in_thread(lambda _task, _obj, _data, _cancellable, cmd=command: thread_func(cmd)) + + def install_button_handler(widget): + self.install_success = True + nonlocal should_pulse + should_pulse = True + orphans_pulser() + + def get_host_remotes(): + output = subprocess.run(["flatpak-spawn", "--host", "flatpak", "remotes"], capture_output=True, text=True).stdout + lines = output.strip().split("\n") + columns = lines[0].split("\t") + data = [columns] + for line in lines[1:]: + row = line.split("\t") + data.append(row) + return data + + host_remotes = get_host_remotes() + + dialog = Adw.MessageDialog.new(self, _("Choose a Remote")) + dialog.set_close_response("cancel") + dialog.add_response("cancel", _("Cancel")) + dialog.connect("response", install_on_response, dialog.choose_finish) + dialog.set_transient_for(orphans_window) + if len(host_remotes) > 1: + dialog.set_body(_("Choose the Flatpak Remote Repository where attempted app downloads will be from.")) + for i in range(len(host_remotes)): + remote_name = host_remotes[i][0] + remote_option = host_remotes[i][1] + dialog.add_response(f"{remote_name}_{remote_option}", f"{remote_name} {remote_option}") + dialog.set_response_appearance(f"{remote_name}_{remote_option}", Adw.ResponseAppearance.SUGGESTED,) + else: + remote_name = host_remotes[0][0] + remote_option = host_remotes[0][1] + dialog.set_heading("Attempt to Install Matching Flatpaks?") + dialog.add_response(f"{remote_name}_{remote_option}", _("Continue")) + Gtk.Window.present(dialog) + + event_controller = Gtk.EventControllerKey() + event_controller.connect("key-pressed", key_handler) + orphans_window.add_controller(event_controller) + + select_all_button = Gtk.ToggleButton(label=_("Select All")) + select_all_button.connect("toggled", toggle_button_handler) + orphans_action_bar.pack_start(select_all_button) + + trash_button = Gtk.Button(label="Trash", valign=Gtk.Align.CENTER, tooltip_text=_("Trash Selected")) + trash_button.add_css_class("destructive-action") + trash_button.connect("clicked", trash_button_handler) + orphans_action_bar.pack_end(trash_button) + + install_button = Gtk.Button(label="Install", valign=Gtk.Align.CENTER, tooltip_text=_("Attempt to Install Selected")) + install_button.connect("clicked", install_button_handler) + install_button.set_visible(False) + orphans_action_bar.pack_end(install_button) + test = subprocess.run(["flatpak-spawn", "--host", "flatpak", "remotes"], capture_output=True, text=True).stdout + for char in test: + if char.isalnum(): + install_button.set_visible(True) + + def selection_handler(tickbox, file): + global total_selected + global selected_rows + if tickbox.get_active() == True: + total_selected += 1 + selected_rows.append(file) + else: + total_selected -= 1 + to_find = file + selected_rows.remove(to_find) + + if total_selected == 0: + orphans_window.set_title(window_title) + trash_button.set_sensitive(False) + install_button.set_sensitive(False) + select_all_button.set_active(False) + else: + orphans_window.set_title(_("{} Selected").format(total_selected)) + trash_button.set_sensitive(True) + install_button.set_sensitive(True) + + generate_list(self, False) + orphans_window.present() + selected_host_flatpak_indexes = [] def find_app_icon(self, app_id): @@ -230,7 +478,7 @@ class FlattoolGuiWindow(Adw.ApplicationWindow): trash_button = Gtk.Button(icon_name="user-trash-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("Uninstall {}").format(app_name)) trash_button.add_css_class("flat") - trash_button.connect("clicked", self.uninstall_button_handler, index) + trash_button.connect("clicked", self.uninstall_flatpak, index) flatpak_row.add_suffix(trash_button) properties_button = Gtk.Button(icon_name="info-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("View Properties"))