Add keyboard shortcuts!

This commit is contained in:
heliguy4599
2024-10-12 22:43:23 -04:00
parent c81ca758e3
commit 40be21887d
10 changed files with 312 additions and 197 deletions

View File

@@ -1,70 +1,24 @@
using Gtk 4.0;
ShortcutsWindow help_overlay {
modal: true;
ShortcutsSection {
section-name: "shortcuts";
// max-height: 8;
ShortcutsGroup {
title: _("App Management");
ShortcutsShortcut {
title: _("Search");
action-name: "app.search";
}
ShortcutsShortcut {
title: _("Set Filters");
action-name: "app.set-filter";
}
ShortcutsShortcut {
title: _("Refresh");
action-name: "app.refresh";
}
ShortcutsShortcut {
title: _("Toggle Selection Mode");
action-name: "app.toggle-batch-mode";
}
}
ShortcutsGroup {
title: _("More Functions");
ShortcutsShortcut {
title: _("Manage Leftover Data");
action-name: "app.manage-data-folders";
}
ShortcutsShortcut {
title: _("Manage Remotes");
action-name: "app.show-remotes-window";
}
ShortcutsShortcut {
title: _("Install From File");
action-name: "app.install-from-file";
}
}
ShortcutsGroup {
title: _("General");
ShortcutsShortcut {
title: _("Open Menu");
action-name: "app.open-menu";
}
ShortcutsShortcut {
title: _("Show Shortcuts");
action-name: "win.show-help-overlay";
}
ShortcutsShortcut {
title: _("Quit");
action-name: "app.quit";
}
}
}
modal: true;
ShortcutsSection {
section-name: "shortcuts";
// max-height: 8;
ShortcutsGroup {
title: _("General");
ShortcutsShortcut {
title: _("Open Menu");
action-name: "app.open-menu";
}
ShortcutsShortcut {
title: _("Show Shortcuts");
action-name: "win.show-help-overlay";
}
ShortcutsShortcut {
title: _("Quit");
action-name: "app.quit";
}
}
}
}

View File

@@ -32,10 +32,10 @@ from .const import Config
class WarehouseApplication(Adw.Application):
"""The main application singleton class."""
troubleshooting = "OS: {os}\nWarehouse version: {wv}\nGTK: {gtk}\nlibadwaita: {adw}\nApp ID: {app_id}\nProfile: {profile}\nLanguage: {lang}"
version = Config.VERSION
def __init__(self):
super().__init__(
application_id="io.github.flattool.Warehouse",
@@ -43,9 +43,6 @@ class WarehouseApplication(Adw.Application):
)
self.create_action("about", self.on_about_action)
self.create_action("preferences", self.on_preferences_action)
self.create_action("quit", lambda *_: self.quit(), ["<primary>q"])
self.create_action("refresh", self.on_refresh_shortcut, ["<primary>r", "F5"])
self.create_action("open-menu", lambda *_: self.props.active_window.main_menu.popup(), ["F10"])
self.create_action("show-packages-page", lambda *_: self.props.active_window.switch_page_shortcut_handler("p"), ["<primary>p"])
self.create_action("show-remotes-page", lambda *_: self.props.active_window.switch_page_shortcut_handler("m"), ["<primary>m"])
@@ -53,8 +50,20 @@ class WarehouseApplication(Adw.Application):
self.create_action("show-snapshots-page", lambda *_: self.props.active_window.switch_page_shortcut_handler("s"), ["<primary>s"])
self.create_action("show-install-page", lambda *_: self.props.active_window.switch_page_shortcut_handler("i"), ["<primary>i"])
self.create_action("quit", lambda *_: self.quit(), ["<primary>q"])
self.create_action("refresh", lambda *_: self.props.active_window.refresh_handler(), ["<primary>r", "F5"])
self.create_action("open-menu", lambda *_: self.props.active_window.main_menu.popup(), ["F10"])
self.create_action("open-files", self.on_open_files_shortcut, ["<primary>o"])
self.create_action("toggle-select-mode", self.on_toggle_select_mode_shortcut, ["<primary>b", "<primary>Return", "<primary>KP_Enter"])
self.create_action("toggle-search-mode", self.on_toggle_search_mode_shortcut, ["<primary>f"])
self.create_action("filter", self.on_filter_shortcut, ["<primary>t"])
self.create_action("new", self.on_new_shortcut, ["<primary>n"])
self.create_action("delete", self.on_delete_shortcut, ["BackSpace", "Delete"])
self.create_action("active-data-view", lambda *_: self.on_data_view_shortcut(True), ["<Alt>1"])
self.create_action("leftover-data-view", lambda *_: self.on_data_view_shortcut(False), ["<Alt>2"])
self.is_dialog_open = False
gtk_version = (
str(Gtk.MAJOR_VERSION)
+ "."
@@ -71,7 +80,7 @@ class WarehouseApplication(Adw.Application):
)
os_string = GLib.get_os_info("NAME") + " " + GLib.get_os_info("VERSION")
lang = GLib.environ_getenv(GLib.get_environ(), "LANG")
self.troubleshooting = self.troubleshooting.format(
os=os_string,
wv=self.version,
@@ -82,33 +91,108 @@ class WarehouseApplication(Adw.Application):
lang=lang,
)
def on_refresh_shortcut(self, *args):
self.props.active_window.refresh_handler()
# def file_callback(self, object, result):
# window = self.props.active_window
# try:
# file = object.open_finish(result)
# window.install_file(file.get_path())
# except GLib.GError:
# pass
# def install_from_file(self, widget, _a):
# window = self.props.active_window
# filter = Gtk.FileFilter(name=_("Flatpaks"))
# filter.add_suffix("flatpak")
# filter.add_suffix("flatpakref")
# 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(window, None, self.file_callback)
def on_open_files_shortcut(self, *args):
window = self.props.active_window
def file_choose_callback(object, result):
files = object.open_multiple_finish(result)
window.on_file_drop(None, files, None, None)
file_filter = Gtk.FileFilter(name=_("Flatpaks & Remotes"))
file_filter.add_suffix("flatpak")
file_filter.add_suffix("flatpakref")
file_filter.add_suffix("flatpakrepo")
filters = Gio.ListStore.new(Gtk.FileFilter)
filters.append(file_filter)
file_chooser = Gtk.FileDialog()
file_chooser.set_filters(filters)
file_chooser.set_default_filter(file_filter)
file_chooser.open_multiple(window, None, file_choose_callback)
def on_toggle_select_mode_shortcut(self, *args):
try:
button = self.props.active_window.stack.get_visible_child().select_button
button.set_active(not button.get_active())
except AttributeError:
pass
def on_toggle_search_mode_shortcut(self, *args):
try:
button = self.props.active_window.stack.get_visible_child().search_button
button.set_active(not button.get_active())
except AttributeError:
pass
def on_filter_shortcut(self, *args):
try:
button = self.props.active_window.stack.get_visible_child().filter_button
button.set_active(not button.get_active())
except AttributeError:
pass
try:
button = self.props.active_window.stack.get_visible_child().sort_button
button.set_active(True)
except AttributeError:
pass
try:
button = self.props.active_window.stack.get_visible_child().show_disabled_button
if button.get_visible():
button.set_active(not button.get_active())
except AttributeError:
pass
def on_new_shortcut(self, *args):
page = self.props.active_window.stack.get_visible_child()
try:
page.new_custom_handler()
except AttributeError:
pass
try:
page.on_new()
except AttributeError:
pass
def on_delete_shortcut(self, *args):
page = self.props.active_window.stack.get_visible_child()
try:
if not page.select_button.get_active():
return
page.selection_uninstall()
except AttributeError:
pass
try:
if not page.select_button.get_active():
return
page.trash_handler()
except AttributeError:
pass
try:
if not page.select_button.get_active():
return
page.select_trash_handler()
except AttributeError:
pass
def on_data_view_shortcut(self, is_active):
page = self.props.active_window.stack.get_visible_child()
try:
adp = page.adp
ldp = page.ldp
page.stack.set_visible_child(adp if is_active else ldp)
except AttributeError:
pass
def do_activate(self):
"""Called when the application is activated.
We raise the application's main window, creating it if
necessary.
"""
@@ -116,7 +200,7 @@ class WarehouseApplication(Adw.Application):
if not win:
win = WarehouseWindow(application=self)
win.present()
def on_about_action(self, widget, _a):
"""Callback for the app.about action."""
about = Adw.AboutDialog(
@@ -156,18 +240,17 @@ class WarehouseApplication(Adw.Application):
],
)
about.present(self.props.active_window)
def on_preferences_action(self, widget, _):
"""Callback for the app.preferences action."""
print("app.preferences action activated")
def create_action(self, name, callback, shortcuts=None):
"""Add an application action.
Args:
name: the name of the action
callback: the function to be called when the action is
activated
callback: the function to be called when the action is activated
shortcuts: an optional list of accelerators
"""
action = Gio.SimpleAction.new(name, None)
@@ -175,7 +258,7 @@ class WarehouseApplication(Adw.Application):
self.add_action(action)
if shortcuts:
self.set_accels_for_action(f"app.{name}", shortcuts)
def main(version):
"""The application's entry point."""
app = WarehouseApplication()

View File

@@ -1,4 +1,4 @@
from gi.repository import Adw, Gtk, GLib, Gio
from gi.repository import Adw, Gtk, GLib, Gio, Gdk
from .host_info import HostInfo
from .app_row import AppRow
from .error_toast import ErrorToast
@@ -47,13 +47,13 @@ class PackagesPage(Adw.BreakpointBin):
uninstall_button = gtc()
properties_page = gtc()
filters_page = 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 = "packages"
def set_status(self, to_set):
if to_set is self.scrolled_window:
@@ -61,12 +61,12 @@ class PackagesPage(Adw.BreakpointBin):
self.select_button.set_sensitive(True)
self.filter_button.set_sensitive(True)
self.filters_page.set_sensitive(True)
self.search_button.set_sensitive(True)
self.search_entry.set_editable(True)
else:
self.select_button.set_sensitive(False)
if to_set is self.no_packages:
self.properties_page.stack.set_visible_child(self.properties_page.error_tbv)
self.filter_button.set_sensitive(False)
@@ -78,7 +78,7 @@ class PackagesPage(Adw.BreakpointBin):
self.filters_page.set_sensitive(True)
if not self.packages_split.get_collapsed():
self.filter_button.set_active(True)
if to_set is self.no_results:
self.filters_page.set_sensitive(False)
@@ -93,7 +93,7 @@ class PackagesPage(Adw.BreakpointBin):
else:
self.stack.set_visible_child(self.packages_split)
self.status_stack.set_visible_child(to_set)
def apply_filters(self):
i = 0
show_apps = self.filter_settings.get_boolean("show-apps")
@@ -112,20 +112,20 @@ class PackagesPage(Adw.BreakpointBin):
visible = False
if runtimes_list != "all" and (row.package.is_runtime or row.package.dependant_runtime and not row.package.dependant_runtime.info["ref"] in runtimes_list):
visible = False
row.set_visible(visible)
if visible:
total_visible += 1
else:
row.check_button.set_active(False)
if total_visible == 0:
self.set_status(self.no_filter_results)
else:
GLib.idle_add(lambda *_: self.set_status(self.scrolled_window))
if self.current_row_for_properties and not self.current_row_for_properties.get_visible():
self.select_first_visible_row()
def select_first_visible_row(self):
first_visible_row = None
i = 0
@@ -135,11 +135,11 @@ class PackagesPage(Adw.BreakpointBin):
first_visible_row = row
self.current_row_for_properties = row
break
if not first_visible_row is None:
self.packages_list_box.select_row(first_visible_row)
self.properties_page.set_properties(first_visible_row.package)
def row_select_handler(self, row):
if row.check_button.get_active():
self.selected_rows.append(row)
@@ -154,17 +154,17 @@ class PackagesPage(Adw.BreakpointBin):
self.packages_navpage.set_title(_("Packages"))
self.copy_button.set_sensitive(False)
self.uninstall_button.set_sensitive(False)
def select_all_handler(self, *args):
i = 0
while row := self.packages_list_box.get_row_at_index(i):
i += 1
row.check_button.set_active(row.get_visible())
def row_rclick_handler(self, row):
self.select_button.set_active(True)
GLib.idle_add(lambda *_, button=row.check_button: button.set_active(not button.get_active()))
def generate_list(self, *args):
self.properties_page.nav_view.pop_to_page(self.properties_page.inner_nav_page)
self.packages_list_box.remove_all()
@@ -175,7 +175,7 @@ class PackagesPage(Adw.BreakpointBin):
if len(HostInfo.flatpaks) == 0:
self.set_status(self.no_packages)
return
for package in HostInfo.flatpaks:
row = AppRow(package, self.row_rclick_handler)
package.app_row = row
@@ -191,19 +191,19 @@ class PackagesPage(Adw.BreakpointBin):
self.packages_toast_overlay.add_toast(ErrorToast(_("Error getting Flatpak '{}'").format(package.info["name"]), str(e)).toast)
self.packages_list_box.append(row)
self.apply_filters()
self.select_first_visible_row()
self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top
def row_activate_handler(self, list_box, row):
self.properties_page.set_properties(row.package)
self.properties_page.nav_view.pop()
self.packages_split.set_show_content(True)
self.filter_button.set_active(False)
self.current_row_for_properties = row
def filter_func(self, row):
search_text = self.search_entry.get_text().lower()
title = row.get_title().lower()
@@ -211,14 +211,14 @@ class PackagesPage(Adw.BreakpointBin):
if row.get_visible() and (search_text in title or search_text in subtitle):
self.is_result = True
return True
def set_selection_mode(self, is_enabled):
i = 0
while row := self.packages_list_box.get_row_at_index(i):
i += 1
GLib.idle_add(row.check_button.set_active, False)
GLib.idle_add(row.check_button.set_visible, is_enabled)
def selection_copy(self, box, row):
self.copy_pop.popdown()
info = ""
@@ -233,7 +233,7 @@ class PackagesPage(Adw.BreakpointBin):
case self.copy_refs:
info = "ref"
feedback = _("Refs")
to_copy = []
for row in self.selected_rows:
to_copy.append(row.package.info[info])
@@ -243,8 +243,11 @@ class PackagesPage(Adw.BreakpointBin):
self.packages_toast_overlay.add_toast(Adw.Toast(title=_("Copied {}").format(feedback)))
except Exception as e:
self.packages_toast_overlay.add_toast(ErrorToast(_("Could not copy {}").format(feedback), str(e)).toast)
def selection_uninstall(self, *args):
if len(self.selected_rows) < 1:
return
def on_response(should_trash):
GLib.idle_add(lambda *_: self.set_status(self.uninstalling))
error = [None]
@@ -256,7 +259,7 @@ class PackagesPage(Adw.BreakpointBin):
cmd.append(row.package.info["ref"])
if should_trash and os.path.exists(row.package.data_path):
to_trash.append(row.package.data_path)
try:
subprocess.run(cmd, check=True, capture_output=True)
if should_trash and len(to_trash) > 0:
@@ -265,7 +268,7 @@ class PackagesPage(Adw.BreakpointBin):
error[0] = cpe
except Exception as e:
error[0] = e
def callback(*args):
self.main_window.refresh_handler()
HostInfo.main_window.remove_refresh_lockout("batch uninstalling packages")
@@ -274,49 +277,57 @@ class PackagesPage(Adw.BreakpointBin):
GLib.idle_add(lambda *args: self.packages_toast_overlay.add_toast(ErrorToast(_("Could not uninstall packages"), details).toast))
else:
GLib.idle_add(lambda *args: self.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled Packages"))))
Gio.Task.new(None, None, callback).run_in_thread(thread)
dialog = UninstallDialog(on_response, True)
dialog.present(self.main_window)
def start_loading(self):
self.packages_navpage.set_title(_("Packages"))
self.select_button.set_active(False)
self.set_status(self.loading_packages)
def end_loading(self):
GLib.idle_add(lambda *_: self.generate_list())
def select_button_handler(self, button):
self.set_selection_mode(button.get_active())
def filter_button_handler(self, button):
if button.get_active():
self.content_stack.set_visible_child(self.filters_page)
self.packages_split.set_show_content(True)
else:
self.content_stack.set_visible_child(self.properties_page)
self.packages_split.set_show_content(False)
def filter_page_handler(self, *args):
if self.packages_split.get_collapsed() and not self.packages_split.get_show_content():
self.filter_button.set_active(False)
def on_invalidate(self, row):
current_status = self.status_stack.get_visible_child()
if not current_status is self.no_results:
self.prev_status = current_status
self.is_result = False
self.packages_list_box.invalidate_filter()
if self.is_result:
self.set_status(self.prev_status)
else:
self.set_status(self.no_results)
def sort_func(self, row1, row2):
return row1.package.info["name"].lower() > row2.package.info["name"].lower()
def key_handler(self, controller, keyval, keycode, state):
if keyval == Gdk.KEY_Escape:
if self.select_button.get_active():
self.select_button.set_active(False)
elif self.filter_button.get_active():
self.filter_button.set_active(False)
def __init__(self, main_window, **kwargs):
super().__init__(**kwargs)
@@ -334,8 +345,10 @@ class PackagesPage(Adw.BreakpointBin):
self.prev_status = None
self.selected_rows = []
self.current_row_for_properties = None
event_controller = Gtk.EventControllerKey()
# Apply
self.add_controller(event_controller)
self.loading_view.set_content(self.loading_packages)
self.packages_list_box.set_filter_func(self.filter_func)
self.packages_list_box.set_sort_func(self.sort_func)
@@ -344,6 +357,7 @@ class PackagesPage(Adw.BreakpointBin):
self.__class__.instance = self
# Connections
event_controller.connect("key-pressed", self.key_handler)
self.search_entry.connect("search-changed", self.on_invalidate)
self.search_bar.set_key_capture_widget(main_window)
self.packages_list_box.connect("row-activated", self.row_activate_handler)

View File

@@ -4,30 +4,39 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango
class UninstallDialog(Adw.AlertDialog):
__gtype_name__ = "UninstallDialog"
gtc = Gtk.Template.Child
group = gtc()
trash = gtc()
is_open = False
def on_response(self, dialog, response):
self.__class__.is_open = False
if response != "continue":
return
self.continue_callback(self.trash.get_active())
def present(self, *args, **kwargs):
if self.__class__.is_open:
return
self.__class__.is_open = True
super().present(*args, **kwargs)
def __init__(self, continue_callback, show_trash_option, package_name=None, **kwargs):
super().__init__(**kwargs)
if package_name:
self.set_heading(GLib.markup_escape_text(_("Uninstall {}?").format(package_name)))
self.set_body(GLib.markup_escape_text(_("It will not be possible to use {} after removal").format(package_name)))
else:
self.set_heading(GLib.markup_escape_text(_("Uninstall Packages?")))
self.set_body(GLib.markup_escape_text(_("It will not be possible to use these packages after removal")))
self.continue_callback = continue_callback
self.add_response("cancel", _("Cancel"))
self.add_response("continue", _("Uninstall"))
self.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
self.connect("response", self.on_response)
self.group.set_title(GLib.markup_escape_text(_("App Settings & Content")))
self.group.set_visible(show_trash_option)
self.group.set_visible(show_trash_option)

View File

@@ -18,6 +18,7 @@ class AddRemoteDialog(Adw.Dialog):
name_row = gtc()
url_row = gtc()
installation_chooser = gtc()
is_open = False
def on_apply(self, *args):
self.parent_page.status_stack.set_visible_child(self.parent_page.adding_view)
@@ -75,6 +76,16 @@ class AddRemoteDialog(Adw.Dialog):
self.apply_button.set_sensitive(self.title_passes and self.name_passes and self.url_passes)
def present(self, *args, **kwargs):
if self.__class__.is_open:
return
self.__class__.is_open = True
super().present(*args, **kwargs)
def on_close(self, *args):
self.__class__.is_open = False
def __init__(self, main_window, parent_page, remote_info=None, **kwargs):
super().__init__(**kwargs)
@@ -112,6 +123,7 @@ class AddRemoteDialog(Adw.Dialog):
self.apply_button.set_sensitive(False)
# Connections
self.connect("closed", self.on_close)
self.cancel_button.connect("clicked", lambda *_: self.close())
self.apply_button.connect("clicked", self.on_apply)
self.title_row.connect("changed", self.check_entries)

View File

@@ -260,6 +260,9 @@ class RemotesPage(Adw.NavigationPage):
total_visible += 1
self.none_visible.set_visible(total_visible == 0)
def new_custom_handler(self, *args):
AddRemoteDialog(self.main_window, self).present(self.main_window)
def __init__(self, main_window, **kwargs):
super().__init__(**kwargs)
@@ -274,7 +277,7 @@ class RemotesPage(Adw.NavigationPage):
# Connections
self.file_remote_row.connect("activated", lambda *_: self.add_file_handler())
self.custom_remote_row.connect("activated", lambda *_: AddRemoteDialog(main_window, self).present(main_window))
self.custom_remote_row.connect("activated", self.new_custom_handler)
self.search_entry.connect("search-changed", self.on_search)
self.show_disabled_button.connect("toggled", self.show_disabled_handler)

View File

@@ -24,6 +24,7 @@ class NewSnapshotDialog(Adw.Dialog):
scrolled_window = gtc()
no_results = gtc()
stack = gtc()
is_open = False
def row_gesture_handler(self, row):
row.check_button.set_active(not row.check_button.get_active())
@@ -68,6 +69,7 @@ class NewSnapshotDialog(Adw.Dialog):
return False
def on_close(self, *args):
self.__class__.is_open = False
self.search_button.set_active(False)
for row in self.selected_rows.copy():
GLib.idle_add(lambda *_, row=row: row.check_button.set_active(False))
@@ -149,16 +151,20 @@ class NewSnapshotDialog(Adw.Dialog):
row.set_activatable(False)
self.selected_rows.append(row)
self.listbox.append(row)
def enter_handler(self, *args):
if self.create_button.get_sensitive():
self.create_button.activate()
def present(self, *args, **kwargs):
if self.__class__.is_open:
return
super().present(*args, **kwargs)
self.__class__.is_open = True
if not self.search_button.get_visible():
self.name_entry.grab_focus()
def __init__(self, snapshot_page, loading_status, on_done=None, packages=None, **kwargs):
super().__init__(**kwargs)

View File

@@ -8,7 +8,7 @@ import os, subprocess, json, re
class SnapshotBox(Gtk.Box):
__gtype_name__ = "SnapshotBox"
gtc = Gtk.Template.Child
title = gtc()
date = gtc()
version = gtc()
@@ -18,7 +18,7 @@ class SnapshotBox(Gtk.Box):
rename_entry = gtc()
apply_rename = gtc()
trash_button = gtc()
def create_json(self):
try:
data = {
@@ -31,7 +31,7 @@ class SnapshotBox(Gtk.Box):
except Exception as e:
self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast)
def update_json(self, key, value):
try:
with open(self.json_path, 'r+') as file:
@@ -40,14 +40,14 @@ class SnapshotBox(Gtk.Box):
file.seek(0)
json.dump(data, file, indent=4)
file.truncate()
except Exception as e:
self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast)
def load_from_json(self):
if not os.path.exists(self.json_path):
self.create_json()
try:
with open(self.json_path, 'r') as file:
data = json.load(file)
@@ -59,15 +59,15 @@ class SnapshotBox(Gtk.Box):
except Exception as e:
self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast)
def on_rename(self, widget):
if not self.valid_checker():
return
self.update_json('name', self.rename_entry.get_text().strip())
self.load_from_json()
self.rename_menu.popdown()
def valid_checker(self, *args):
text = self.rename_entry.get_text().strip()
valid = not ("/" in text or "\0" in text) and len(text) > 0
@@ -78,10 +78,13 @@ class SnapshotBox(Gtk.Box):
self.rename_entry.add_css_class("error")
return valid
def on_trash(self, button):
error = [None]
path = f"{self.snapshots_path}{self.folder}"
if self.snapshot_page.is_trash_dialog_open:
return
def thread(*args):
try:
subprocess.run(['gio', 'trash', path], capture_output=True, text=True, check=True)
@@ -89,21 +92,23 @@ class SnapshotBox(Gtk.Box):
error[0] = cpe.stderr
except Exception as e:
error[0] = str(e)
def callback(*args):
if not error[0] is None:
self.toast_overlay.add_toast(ErrorToast(_("Could not trash snapshot"), error[0]).toast)
return
self.parent_page.on_trash()
self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed snapshot")))
def on_response(_, response):
self.snapshot_page.is_trash_dialog_open = False
if response != "continue":
return
Gio.Task.new(None, None, callback).run_in_thread(thread)
self.snapshot_page.is_trash_dialog_open = True
dialog = Adw.AlertDialog(heading=_("Trash Snapshot?"), body=_("This snapshot will be sent to the trash"))
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("continue", _("Trash"))
@@ -128,7 +133,7 @@ class SnapshotBox(Gtk.Box):
return False # Stop the timeout
else:
return True # Continue the timeout
def on_apply(self, button):
def on_response(dialog, response):
if response != "continue":
@@ -141,7 +146,7 @@ class SnapshotBox(Gtk.Box):
self.snapshot_page.workers.append(self.worker)
self.worker.extract()
GLib.timeout_add(200, self.get_fraction)
has_data = os.path.exists(self.worker.new_path)
dialog = Adw.AlertDialog(
heading=_("Apply Snapshot?"),
@@ -154,7 +159,7 @@ class SnapshotBox(Gtk.Box):
def __init__(self, parent_page, folder, snapshots_path, toast_overlay, **kwargs):
super().__init__(**kwargs)
self.snapshot_page = parent_page.parent_page
self.toast_overlay = toast_overlay
self.app_id = snapshots_path.split('/')[-2].strip()
@@ -163,11 +168,11 @@ class SnapshotBox(Gtk.Box):
new_path=f"{HostInfo.home}/.var/app/{self.app_id}/",
toast_overlay=self.toast_overlay,
)
split_folder = folder.split('_')
if len(split_folder) < 2:
return
self.parent_page = parent_page
self.folder = folder
self.snapshots_path = snapshots_path

View File

@@ -1,4 +1,4 @@
from gi.repository import Adw, Gtk, GLib, Gio
from gi.repository import Adw, Gtk, GLib, Gio, Gdk
from .host_info import HostInfo
from .error_toast import ErrorToast
from .app_row import AppRow
@@ -12,7 +12,7 @@ import os, subprocess
class LeftoverSnapshotRow(Adw.ActionRow):
__gtype_name__ = "LeftoverSnapshotRow"
def idle_stuff(self):
self.set_title(self.name)
icon = Gtk.Image.new_from_icon_name("application-x-executable-symbolic")
@@ -22,7 +22,7 @@ class LeftoverSnapshotRow(Adw.ActionRow):
def gesture_handler(self, *args):
self.on_long_press(self)
def __init__(self, folder, on_long_press, **kwargs):
super().__init__(**kwargs)
@@ -44,7 +44,7 @@ class LeftoverSnapshotRow(Adw.ActionRow):
# Connections
self.rclick_gesture.connect("released", self.gesture_handler)
self.long_press_gesture.connect("pressed", self.gesture_handler)
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_page.ui")
class SnapshotPage(Adw.BreakpointBin):
__gtype_name__ = "SnapshotPage"
@@ -87,6 +87,7 @@ class SnapshotPage(Adw.BreakpointBin):
# This must be set to the created object from within the class's __init__ method
instance = None
page_name = "snapshots"
is_trash_dialog_open = False
def sort_snapshots(self, *args):
self.active_snapshot_paks.clear()
@@ -126,11 +127,11 @@ class SnapshotPage(Adw.BreakpointBin):
subprocess.run(['gio', 'trash', f'{HostInfo.snapshots_path}{folder}'])
except Exception:
pass
def long_press_handler(self, row):
self.select_button.set_active(True)
row.check_button.set_active(not row.check_button.get_active())
def generate_active_list(self):
for pak in self.active_snapshot_paks:
row = AppRow(pak, self.long_press_handler)
@@ -359,7 +360,7 @@ class SnapshotPage(Adw.BreakpointBin):
if len(packages) == 0:
self.toast_overlay.add_toast(Adw.Toast(title=_("No apps in your selection can be snapshotted")))
return
self.new_snapshot_dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh, packages)
self.new_snapshot_dialog.present(HostInfo.main_window)
@@ -378,7 +379,7 @@ class SnapshotPage(Adw.BreakpointBin):
id_to_tar[app_id] = tarlist
if len(tarlist) < 1:
id_to_tar.pop(app_id, None)
return id_to_tar
def get_total_fraction(self):
@@ -422,7 +423,7 @@ class SnapshotPage(Adw.BreakpointBin):
biggest_tar = tar
id_to_tar[app_id] = tar
for app_id, tar in id_to_tar.items():
worker = TarWorker(
existing_path=f"{HostInfo.snapshots_path}{app_id}/{tar}",
@@ -431,7 +432,7 @@ class SnapshotPage(Adw.BreakpointBin):
)
self.workers.append(worker)
worker.extract()
if len(self.workers) > 0:
self.snapshotting_status.title_label.set_label(_("Applying Snapshots"))
self.snapshotting_status.progress_bar.set_fraction(0.0)
@@ -456,7 +457,14 @@ class SnapshotPage(Adw.BreakpointBin):
AttemptInstallDialog(package_names, lambda is_valid: self.select_button.set_active(not is_valid))
def select_trash_handler(self):
if (
self.is_trash_dialog_open
or len(self.selected_active_rows) + len(self.selected_leftover_rows) < 1
):
return
def on_response(dialog, response):
self.is_trash_dialog_open = False
to_trash = []
if response != "continue":
return
@@ -474,7 +482,8 @@ class SnapshotPage(Adw.BreakpointBin):
self.toast_overlay.add_toast(Adw.Toast(title=_("Trashed snapshots")))
except subprocess.CalledProcessError as cpe:
self.toast_overlay.add_toast(ErrorToast(_("Could not trash snapshots"), cpe.stderr).toast)
self.is_trash_dialog_open = True
dialog = Adw.AlertDialog(heading=_("Trash Snapshots?"), body=_("These apps' snapshots will be sent to the trash"))
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("continue", _("Trash"))
@@ -494,7 +503,11 @@ class SnapshotPage(Adw.BreakpointBin):
self.install_handler()
case self.trash_snapshots:
self.select_trash_handler()
def key_handler(self, controller, keyval, keycode, state):
if keyval == Gdk.KEY_Escape:
self.select_button.set_active(False)
def __init__(self, main_window, **kwargs):
super().__init__(**kwargs)
@@ -509,8 +522,19 @@ class SnapshotPage(Adw.BreakpointBin):
self.list_page = SnapshotsListPage(self)
self.snapshotting_status = LoadingStatus("Initial Title", _("This could take a while"), True, self.on_cancel)
self.new_snapshot_dialog = None
event_controller = Gtk.EventControllerKey()
# Apply
self.add_controller(event_controller)
self.search_bar.set_key_capture_widget(HostInfo.main_window)
self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment")))
self.snapshotting_view.set_content(self.snapshotting_status)
self.split_view.set_content(self.list_page)
self.active_listbox.set_sort_func(self.sort_func)
self.leftover_listbox.set_sort_func(self.sort_func)
# Connections
event_controller.connect("key-pressed", self.key_handler)
self.active_listbox.connect("row-activated", self.active_select_handler)
self.leftover_listbox.connect("row-activated", self.leftover_select_handler)
self.open_button.connect("clicked", self.open_snapshots_folder)
@@ -522,11 +546,3 @@ class SnapshotPage(Adw.BreakpointBin):
self.select_all_button.connect("clicked", self.select_all_handler)
self.copy_button.connect("clicked", self.select_copy_handler)
self.more_menu.connect("row-activated", self.more_menu_handler)
# Apply
self.search_bar.set_key_capture_widget(HostInfo.main_window)
self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment")))
self.snapshotting_view.set_content(self.snapshotting_status)
self.split_view.set_content(self.list_page)
self.active_listbox.set_sort_func(self.sort_func)
self.leftover_listbox.set_sort_func(self.sort_func)

View File

@@ -1,4 +1,4 @@
from gi.repository import Adw, Gtk, GLib, Gio, Pango
from gi.repository import Adw, Gtk, GLib, Gio, Gdk
from .error_toast import ErrorToast
from .data_box import DataBox
from .data_subpage import DataSubpage
@@ -12,7 +12,7 @@ import os, subprocess
class UserDataPage(Adw.BreakpointBin):
__gtype_name__ = 'UserDataPage'
gtc = Gtk.Template.Child
bpt = gtc()
status_stack = gtc()
loading_view = gtc()
@@ -52,6 +52,7 @@ class UserDataPage(Adw.BreakpointBin):
page_name = "user-data"
data_path = f"{HostInfo.home}/.var/app"
bpt_is_applied = False
is_trash_dialog_open = False
def sort_data(self, *args):
self.data_flatpaks.clear()
@@ -156,6 +157,7 @@ class UserDataPage(Adw.BreakpointBin):
def trash_handler(self, *args):
error = [None]
child = self.stack.get_visible_child()
def thread(path):
cmd = ['gio', 'trash'] + path
@@ -175,10 +177,10 @@ class UserDataPage(Adw.BreakpointBin):
self.toast_overlay.add_toast(Adw.Toast(title=_("Trashed data")))
def on_response(dialog, response):
self.is_trash_dialog_open = False
if response != "continue":
return
child = self.stack.get_visible_child()
to_trash = []
for box in child.selected_boxes:
to_trash.append(box.data_path)
@@ -191,6 +193,10 @@ class UserDataPage(Adw.BreakpointBin):
child.set_visible_child(child.loading_data)
Gio.Task.new(None, None, callback).run_in_thread(lambda *_: thread(to_trash))
if len(child.selected_boxes) < 1 or self.is_trash_dialog_open:
return
self.is_trash_dialog_open = True
dialog = Adw.AlertDialog(heading=_("Trash Data?"), body=_("Data will be sent to the trash"))
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("continue", _("Continue"))
@@ -230,6 +236,10 @@ class UserDataPage(Adw.BreakpointBin):
self.install_handler()
case self.more_trash:
self.trash_handler()
def key_handler(self, controller, keyval, keycode, state):
if keyval == Gdk.KEY_Escape:
self.select_button.set_active(False)
def __init__(self, main_window, **kwargs):
super().__init__(**kwargs)
@@ -243,17 +253,19 @@ class UserDataPage(Adw.BreakpointBin):
self.leftover_data = []
self.total_items = 0
self.settings = Gio.Settings.new("io.github.flattool.Warehouse.data_page")
self.sort_modes_to_buttons = {
"name": self.sort_name,
"id": self.sort_id,
"size": self.sort_size,
}
self.buttons_to_sort_modes = {}
event_controller = Gtk.EventControllerKey()
# Apply
self.add_controller(event_controller)
for key, button in self.sort_modes_to_buttons.items():
self.buttons_to_sort_modes[button] = key
# Apply
self.stack.add_titled_with_icon(
child=self.adp,
name="active",
@@ -268,6 +280,7 @@ class UserDataPage(Adw.BreakpointBin):
)
# Connections
event_controller.connect("key-pressed", self.key_handler)
self.open_button.connect("clicked", self.open_data_folder)
self.stack.connect("notify::visible-child", self.view_change_handler)
self.select_button.connect("toggled", self.select_toggle_handler)