diff --git a/src/gtk/help-overlay.blp b/src/gtk/help-overlay.blp index 7a3137a..a3fa83e 100644 --- a/src/gtk/help-overlay.blp +++ b/src/gtk/help-overlay.blp @@ -5,7 +5,7 @@ ShortcutsWindow help_overlay { ShortcutsSection { section-name: "shortcuts"; - max-height: 7; + max-height: 8; ShortcutsGroup { title: C_("shortcut window", "App Management"); @@ -56,6 +56,11 @@ ShortcutsWindow help_overlay { ShortcutsGroup { title: C_("shortcut window", "General"); + ShortcutsShortcut { + title: C_("shortcut window", "Open Menu"); + action-name: "app.open-menu"; + } + ShortcutsShortcut { title: C_("shortcut window", "Show Shortcuts"); action-name: "win.show-help-overlay"; diff --git a/src/main.py b/src/main.py index e17774d..889e24d 100644 --- a/src/main.py +++ b/src/main.py @@ -50,6 +50,7 @@ class WarehouseApplication(Adw.Application): self.create_action("show-remotes-window", self.show_remotes_shortcut, ["m"]) self.create_action("set-filter", self.filters_shortcut, ["t"]) self.create_action("install-from-file", self.install_from_file, ["o"]) + self.create_action("open-menu", self.main_menu_shortcut, ["F10"]) def batch_mode_shortcut(self, widget, _): button = self.props.active_window.batch_mode_button @@ -75,6 +76,10 @@ class WarehouseApplication(Adw.Application): window = self.props.active_window window.filterWindowKeyboardHandler(window) + def main_menu_shortcut(self, widget, _): + window = self.props.active_window + window.main_menu.set_active(True) + def file_callback(self, object, result): window = self.props.active_window try: diff --git a/src/popular_remotes.blp b/src/popular_remotes.blp index f5bd1f3..2e3c77e 100644 --- a/src/popular_remotes.blp +++ b/src/popular_remotes.blp @@ -3,7 +3,7 @@ using Adw 1; template PopularRemotesWindow : Adw.Window { default-width: 450; - default-height: 613; + default-height: 530; title: ""; Adw.ToolbarView main_toolbar_view { @@ -16,7 +16,7 @@ template PopularRemotesWindow : Adw.Window { Adw.StatusPage { valign: start; title: _("Add a Remote"); - description: _("Choose from a list of popular remotes."); + description: _("Choose from a list of popular remotes or add a new one."); Adw.Clamp { Box { orientation: vertical; @@ -31,6 +31,15 @@ template PopularRemotesWindow : Adw.Window { valign: start; selection-mode: none; styles["boxed-list"] + + Adw.ActionRow add_from_file { + title: _("Add a Repo File"); + activatable: true; + } + Adw.ActionRow custom_remote { + title: _("Add a Custom Remote"); + activatable: true; + } } } } diff --git a/src/popular_remotes_window.py b/src/popular_remotes_window.py index 79b3008..50558d4 100644 --- a/src/popular_remotes_window.py +++ b/src/popular_remotes_window.py @@ -12,34 +12,52 @@ class PopularRemotesWindow(Adw.Window): list_of_remotes = Gtk.Template.Child() custom_list = Gtk.Template.Child() toast_overlay = Gtk.Template.Child() + add_from_file = Gtk.Template.Child() + custom_remote = Gtk.Template.Child() def key_handler(self, _a, event, _c, _d): if event == Gdk.KEY_Escape: self.close() + def file_callback(self, object, result): + try: + file = object.open_finish(result) + self.parent_window.addRemoteFromFile(file.get_path()) + self.close() + except GLib.GError: + pass + + def addFromFileHandler(self, widet): + filter = Gtk.FileFilter(name=_("Flatpaks Repos")) + filter.add_suffix("flatpakrepo") + filters = Gio.ListStore.new(Gtk.FileFilter) + filters.append(filter) + file_chooser = Gtk.FileDialog() + file_chooser.set_filters(filters) + file_chooser.set_default_filter(filter) + file_chooser.open(self, None, self.file_callback) + def generate_list(self): self.host_remotes = self.my_utils.getHostRemotes() self.list_of_remotes.remove_all() - self.custom_list.remove_all() host_remotes_names = [] for i in range(len(self.remotes)): remote_row = Adw.ActionRow(activatable=True) remote_row.set_title(self.remotes[i][0]) remote_row.set_subtitle(self.remotes[i][3]) - image = Gtk.Image.new_from_icon_name("right-large-symbolic") - remote_row.add_suffix(image) + remote_row.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic")) remote_row.connect("activated", self.parent_window.add_handler, self.remotes[i][1], self.remotes[i][2]) remote_row.connect("activated", lambda *_: self.close()) self.list_of_remotes.append(remote_row) - image2 = Gtk.Image.new_from_icon_name("right-large-symbolic") - custom_remote = Adw.ActionRow(activatable=True) - custom_remote.set_title(_("Add a Custom Remote")) - custom_remote.add_suffix(image2) - custom_remote.connect("activated", self.parent_window.add_handler) - custom_remote.connect("activated", lambda *_: self.close()) - self.custom_list.append(custom_remote) + self.add_from_file.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic")) + self.add_from_file.connect("activated", self.addFromFileHandler) + #self.add_from_file.connect("activated", lambda *_: self.close()) + + self.custom_remote.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic")) + self.custom_remote.connect("activated", self.parent_window.add_handler) + self.custom_remote.connect("activated", lambda *_: self.close()) if not self.list_of_remotes.get_row_at_index(0): self.list_of_remotes.set_visible(False) diff --git a/src/remotes_window.py b/src/remotes_window.py index 69b0a86..e42abfd 100644 --- a/src/remotes_window.py +++ b/src/remotes_window.py @@ -106,7 +106,7 @@ class RemotesWindow(Adw.Window): def addRemoteThread(self, command): try: subprocess.run(command, capture_output=True, check=True, env=self.new_env) - except Exception as e: + except subprocess.CalledProcessError as e: self.toast_overlay.add_toast(Adw.Toast.new(_("Could not add {}").format(self.name_to_add))) print(e) @@ -235,6 +235,86 @@ class RemotesWindow(Adw.Window): if link != "": url_update(url_entry) + def addRemoteFromFileThread(self, filepath, system_or_user, name): + try: + subprocess.run(['flatpak-spawn', '--host', 'flatpak', 'remote-add', name, filepath, f"--{system_or_user}"], capture_output=True, check=True, env=self.new_env) + self.toast_overlay.add_toast(Adw.Toast.new(_("{} successfully added").format(name))) + except subprocess.CalledProcessError as e: + self.toast_overlay.add_toast(Adw.Toast.new(_("Could not add {}").format(self.name_to_add))) + print(e) + + def addRemoteFromFile(self, filepath): + def response(dialog, response, _a): + if response == "cancel": + self.should_pulse = False + return + + self.progress_bar.set_visible(True) + user_or_system = "user" + if system_check.get_active(): + user_or_system = "system" + + task = Gio.Task.new(None, None, self.addRemoteCallback) + task.run_in_thread(lambda *_: self.addRemoteFromFileThread(filepath, user_or_system, name_row.get_text())) + + def name_update(widget): + is_enabled = True + self.name_to_add = widget.get_text() + name_pattern = re.compile(r'^[a-zA-Z\-]+$') + if not name_pattern.match(self.name_to_add): + is_enabled = False + + if is_enabled: + widget.remove_css_class("error") + else: + widget.add_css_class("error") + + if len(self.name_to_add) == 0: + is_enabled = False + + dialog.set_response_enabled("continue", is_enabled) + + self.should_pulse = True + self.mainPulser() + + name = filepath.split('/') + name = name[len(name) - 1] + + dialog = Adw.MessageDialog.new(self, _("Add {}?").format(name)) + dialog.set_close_response("cancel") + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Add")) + dialog.set_response_enabled("continue", False) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) + dialog.connect("response", response, dialog.choose_finish) + + # Create Widgets + options_box = Gtk.Box(orientation="vertical") + options_list = Gtk.ListBox(selection_mode="none", margin_top=15) + name_row = Adw.EntryRow(title=_("Name")) + name_row.connect("changed", name_update) + user_row = Adw.ActionRow(title=_("User"), subtitle=_("The app will be available to only you")) + system_row = Adw.ActionRow(title=_("System"), subtitle=_("The app will be available to every user on the system")) + user_check = Gtk.CheckButton() + system_check = Gtk.CheckButton() + + # Apply Widgets + user_row.add_prefix(user_check) + user_row.set_activatable_widget(user_check) + system_row.add_prefix(system_check) + system_row.set_activatable_widget(system_check) + user_check.set_group(system_check) + options_list.append(name_row) + options_list.append(user_row) + options_list.append(system_row) + options_box.append(options_list) + dialog.set_extra_child(options_box) + + # Calls + user_check.set_active(True) + options_list.add_css_class("boxed-list") + Gtk.Window.present(dialog) + def showPopularRemotes(self, widget): remotes = [ @@ -259,10 +339,7 @@ class RemotesWindow(Adw.Window): if remotes[i][1] not in host_remotes_names: non_added_remotes.append(remotes[i]) - if len(non_added_remotes) > 0: - PopularRemotesWindow(self, non_added_remotes).present() - else: - self.add_handler(widget) + PopularRemotesWindow(self, non_added_remotes).present() def __init__(self, main_window, **kwargs): super().__init__(**kwargs) diff --git a/src/window.blp b/src/window.blp index cf712cb..0a39d57 100644 --- a/src/window.blp +++ b/src/window.blp @@ -27,7 +27,7 @@ template WarehouseWindow : Adw.ApplicationWindow { } [end] - MenuButton { + MenuButton main_menu { icon-name: "open-menu-symbolic"; tooltip-text: _("Main Menu"); menu-model: primary_menu; diff --git a/src/window.py b/src/window.py index 9617e41..5350488 100644 --- a/src/window.py +++ b/src/window.py @@ -19,11 +19,13 @@ import os import pathlib import subprocess +import re from gi.repository import Adw, Gdk, Gio, GLib, Gtk from .properties_window import show_properties_window from .filter_window import FilterWindow from .common import myUtils +from .remotes_window import RemotesWindow @Gtk.Template(resource_path="/io/github/flattool/Warehouse/window.ui") class WarehouseWindow(Adw.ApplicationWindow): @@ -48,6 +50,7 @@ class WarehouseWindow(Adw.ApplicationWindow): main_toolbar_view = Gtk.Template.Child() filter_button = Gtk.Template.Child() scrolled_window = Gtk.Template.Child() + main_menu = Gtk.Template.Child() main_progress_bar = Gtk.ProgressBar(visible=False, pulse_step=0.7, can_target=False) main_progress_bar.add_css_class("osd") @@ -503,17 +506,20 @@ class WarehouseWindow(Adw.ApplicationWindow): total_visible += 1 if total_visible > 0: - #self.main_stack.set_visible_child(self.main_box) self.windowSetEmpty(False) else: - # self.main_stack.set_visible_child(self.no_flatpaks) self.windowSetEmpty(True) self.filter_button.set_sensitive(True) def installCallback(self, _a, _b): self.main_progress_bar.set_visible(False) self.should_pulse = False - self.refresh_list_of_flatpaks(self, False) + + if self.my_utils.install_success: + self.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully"))) + self.refresh_list_of_flatpaks(self, False) + else: + self.toast_overlay.add_toast(Adw.Toast.new(_("Could not install app"))) def installThread(self, filepath, user_or_system): self.my_utils.installFlatpak([filepath], None, user_or_system) @@ -570,7 +576,15 @@ class WarehouseWindow(Adw.ApplicationWindow): Gtk.Window.present(dialog) def drop_callback(self, target, _x, _y, _data): - print(target.get_value().get_path()) + filepath = target.get_value().get_path() + if filepath.endswith(".flatpak") or filepath.endswith(".flatpakref"): + self.install_file(filepath) + elif filepath.endswith(".flatpakrepo"): + remotes_window = RemotesWindow(self) + remotes_window.present() + remotes_window.addRemoteFromFile(filepath) + else: + self.toast_overlay.add_toast(Adw.Toast.new(_("File type not supported"))) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -584,6 +598,9 @@ class WarehouseWindow(Adw.ApplicationWindow): self.settings.bind("is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT) self.settings.bind("is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT) + self.new_env = dict( os.environ ) + self.new_env['LC_ALL'] = 'C' + if self.host_flatpaks == [['', '']]: self.windowSetEmpty(True) return