diff --git a/src/main.py b/src/main.py index cd3a93f..415cba5 100644 --- a/src/main.py +++ b/src/main.py @@ -29,6 +29,7 @@ from gi.repository import Gtk, Gio, Adw, GLib from .window import WarehouseWindow from .remotes_window import RemotesWindow from .orphans_window import OrphansWindow +from .search_install_window import SearchInstallWindow class WarehouseApplication(Adw.Application): """The main application singleton class.""" @@ -51,6 +52,10 @@ class WarehouseApplication(Adw.Application): 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"]) + self.create_action("open-search-install", self.open_search_install, ["i"]) + + def open_search_install(self, widget, _): + SearchInstallWindow(self.props.active_window).present() def batch_mode_shortcut(self, widget, _): button = self.props.active_window.batch_mode_button diff --git a/src/meson.build b/src/meson.build index 92b7e8a..36f0549 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,6 +11,7 @@ blueprints = custom_target('blueprints', 'popular_remotes.blp', 'remotes.blp', 'downgrade.blp', + 'search_install.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -56,7 +57,9 @@ warehouse_sources = [ 'style.css', 'remotes.blp', 'downgrade_window.py', - 'downgrade.blp' + 'downgrade.blp', + 'search_install.blp', + 'search_install_window.py', ] install_data(warehouse_sources, install_dir: moduledir) diff --git a/src/search_install.blp b/src/search_install.blp new file mode 100644 index 0000000..c993fa8 --- /dev/null +++ b/src/search_install.blp @@ -0,0 +1,80 @@ +using Gtk 4.0; +using Adw 1; + +template SearchInstallWindow : Adw.Window { + default-width: 500; + default-height: 450; + title: _("Install From Online"); + modal: true; + + Adw.ToolbarView main_toolbar_view { + [top] + HeaderBar header_bar { + show-title-buttons: false; + [start] + Button cancel_button { + label: _("Cancel"); + } + [end] + Button install_button { + label: _("Install"); + styles["suggested-action"] + } + } + [top] + Adw.Clamp { + Box { + margin-top: 6; + margin-start: 6; + margin-end: 6; + styles["linked"] + [start] + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search for Flatpaks, press Enter to search."); + } + [start] + ToggleButton remotes_dropdown { + label: "Remote Name"; + } + } + } + content: + Adw.ToastOverlay toast_overlay { + Stack main_stack { + Overlay main_overlay { + [overlay] + ProgressBar progress_bar { + pulse-step: 0.7; + can-target: false; + styles["osd"] + } + ScrolledWindow { + Adw.Clamp { + ListBox results_list_box { + margin-top: 12; + margin-bottom: 12; + margin-start: 12; + margin-end: 12; + hexpand: true; + valign: start; + selection-mode: none; + styles["boxed-list"] + } + } + } + } + Adw.StatusPage no_results { + icon-name: "error-symbolic"; + title: _("No matches found"); + description: _("Try a different search term"); + } + Adw.StatusPage too_many { + icon-name: "error-symbolic"; + title: _("Too many results"); + description: _("Try being more specific with your search"); + } + } + }; + } +} \ No newline at end of file diff --git a/src/search_install_window.py b/src/search_install_window.py new file mode 100644 index 0000000..cdb751e --- /dev/null +++ b/src/search_install_window.py @@ -0,0 +1,84 @@ +from gi.repository import Gtk, Adw, GLib, Gdk, Gio +from .common import myUtils +import subprocess +import os +import pathlib + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/search_install.ui") +class SearchInstallWindow (Adw.Window): + __gtype_name__ = "SearchInstallWindow" + + results_list_box = Gtk.Template.Child() + main_stack = Gtk.Template.Child() + main_overlay = Gtk.Template.Child() + no_results = Gtk.Template.Child() + too_many = Gtk.Template.Child() + cancel_button = Gtk.Template.Child() + # search_bar = Gtk.Template.Child() + search_entry = Gtk.Template.Child() + + def searchResponse(self, a, b): + self.results_list_box.remove_all() + print(self.search_results) + if len(self.search_results) == 1 and len(self.search_results[0]) == 1: + self.main_stack.set_visible_child(self.no_results) + return + if len(self.search_results) > 50: + self.main_stack.set_visible_child(self.too_many) + return + self.main_stack.set_visible_child(self.main_overlay) + for i in range(len(self.search_results)): + row = Adw.ActionRow(title=GLib.markup_escape_text(self.search_results[i][0]), subtitle=self.search_results[i][2]) + check = Gtk.CheckButton() + check.add_css_class("selection-mode") + label = Gtk.Label(label=self.search_results[i][3], justify=Gtk.Justification.RIGHT, wrap=True, hexpand=True) + row.add_suffix(label) + row.add_suffix(check) + row.set_activatable_widget(check) + self.results_list_box.append(row) + + def searchThread(self): + output = subprocess.run(["flatpak-spawn", "--host", "flatpak", "search", "--columns=all", self.to_search], capture_output=True, text=True, env=self.new_env).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) + data = sorted(data, key=lambda item: item[0].lower()) + self.search_results = data + + def onSearch(self, widget): + self.to_search = widget.get_text() + if len(self.to_search) < 1 or " " in self.to_search: + self.results_list_box.remove_all() + self.main_stack.set_visible_child(self.no_results) + return + task = Gio.Task.new(None, None, self.searchResponse) + task.run_in_thread(lambda *_: self.searchThread()) + + def key_handler(self, _a, event, _c, _d): + if event == Gdk.KEY_Escape: + self.close() + + def __init__(self, parent_window, **kwargs): + super().__init__(**kwargs) + + # Create Variables + self.my_utils = myUtils(self) + self.search_results = [] + self.to_search = "" + self.new_env = dict( os.environ ) + self.new_env['LC_ALL'] = 'C' + event_controller = Gtk.EventControllerKey() + event_controller.connect("key-pressed", self.key_handler) + self.cancel_button.connect("clicked", lambda *_: self.close()) + + # Apply Widgets + self.add_controller(event_controller) + self.set_transient_for(parent_window) + # self.search_bar.connect_entry(self.search_entry) + self.search_entry.connect("activate", self.onSearch) + self.search_entry.connect("changed", lambda *_: self.search_entry.grab_focus()) + # self.search_entry.set_key_capture_widget(self.results_list_box) + self.search_entry.grab_focus() \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index e352131..3e448fd 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -7,6 +7,7 @@ popular_remotes.ui remotes.ui downgrade.ui + search_install.ui style.css gtk/help-overlay.ui diff --git a/src/window.blp b/src/window.blp index 77a92fe..29a44e4 100644 --- a/src/window.blp +++ b/src/window.blp @@ -127,6 +127,11 @@ menu primary_menu { action: "app.show-remotes-window"; } + item { + label: _("Install from Search"); + action: "app.open-search-install"; + } + item { label: _("_Keyboard Shortcuts"); action: "win.show-help-overlay";