From 69d6a7952de71ebda59aac961a18157cb749bb2d Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 3 Jul 2024 15:02:56 -0400 Subject: [PATCH 001/332] Update profile and version --- io.github.flattool.Warehouse.json | 2 +- meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/io.github.flattool.Warehouse.json b/io.github.flattool.Warehouse.json index 2e0bb8c..c06b077 100644 --- a/io.github.flattool.Warehouse.json +++ b/io.github.flattool.Warehouse.json @@ -44,7 +44,7 @@ "name" : "warehouse", "builddir" : true, "buildsystem" : "meson", - "config-opts": [ "-Dprofile=default" ], + "config-opts": [ "-Dprofile=development" ], "sources" : [ { "type" : "dir", diff --git a/meson.build b/meson.build index e0810ca..e34c212 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('warehouse', - version: '1.6.3', + version: '2.0.0', meson_version: '>= 0.62.0', default_options: [ 'warning_level=2', 'werror=false', ], ) From 342c2d4194f46e34eca777a4e0cddfa29deb5b89 Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 3 Jul 2024 16:35:39 -0400 Subject: [PATCH 002/332] Fresh start Remove pretty much everything in preparation of complete overhaul for 2.X --- data/ui/downgrade.blp | 84 --- data/ui/filter.blp | 83 --- data/ui/orphans.blp | 135 ----- data/ui/popular_remotes.blp | 59 --- data/ui/properties.blp | 164 ------ data/ui/remotes.blp | 95 ---- data/ui/search_install.blp | 193 ------- data/ui/snapshots.blp | 90 ---- data/ui/window.blp | 299 ----------- po/POTFILES | 21 +- po/warehouse.pot | 923 +-------------------------------- src/app_row_widget.py | 381 -------------- src/common.py | 527 ------------------- src/downgrade_window.py | 168 ------ src/filter_window.py | 226 -------- src/main.py | 14 - src/main_window/window.blp | 73 +++ src/main_window/window.py | 64 +++ src/meson.build | 36 +- src/orphans_window.py | 353 ------------- src/properties_window.py | 217 -------- src/remotes_window.py | 710 ------------------------- src/search_install_window.py | 266 ---------- src/snapshots_window.py | 281 ---------- src/warehouse.gresource.xml | 9 +- src/warehouse.in | 2 +- src/window.py | 970 ----------------------------------- 27 files changed, 171 insertions(+), 6272 deletions(-) delete mode 100644 data/ui/downgrade.blp delete mode 100644 data/ui/filter.blp delete mode 100644 data/ui/orphans.blp delete mode 100644 data/ui/popular_remotes.blp delete mode 100644 data/ui/properties.blp delete mode 100644 data/ui/remotes.blp delete mode 100644 data/ui/search_install.blp delete mode 100644 data/ui/snapshots.blp delete mode 100644 data/ui/window.blp delete mode 100644 src/app_row_widget.py delete mode 100644 src/common.py delete mode 100644 src/downgrade_window.py delete mode 100644 src/filter_window.py create mode 100644 src/main_window/window.blp create mode 100644 src/main_window/window.py delete mode 100644 src/orphans_window.py delete mode 100644 src/properties_window.py delete mode 100644 src/remotes_window.py delete mode 100644 src/search_install_window.py delete mode 100644 src/snapshots_window.py delete mode 100644 src/window.py diff --git a/data/ui/downgrade.blp b/data/ui/downgrade.blp deleted file mode 100644 index 9454a82..0000000 --- a/data/ui/downgrade.blp +++ /dev/null @@ -1,84 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $DowngradeWindow: Adw.Dialog { - content-width: 500; - content-height: 450; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - } - - [bottom] - ActionBar action_bar { - revealed: false; - - [center] - Button apply_button { - visible: false; - valign: end; - halign: center; - margin-top: 6; - margin-bottom: 6; - Adw.ButtonContent { - label: _("Downgrade"); - icon-name: "arrow-turn-left-down-symbolic"; - } - - styles [ - "suggested-action", - "pill" - ] - } - } - - content: Adw.ToastOverlay toast_overlay { - Stack main_stack { - Box loading { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label loading_label { - label: _("Fetching Releases"); - styles [ - "title-1", - "title" - ] - } - - Label { - label: _("This could take a while"); - styles ["description", "body"] - } - } - - Adw.PreferencesPage outerbox { - Adw.PreferencesGroup { - Adw.SwitchRow mask_row { - title: _("Disable Updates"); - active: true; - } - } - - Adw.PreferencesGroup versions_group { - title: _("Select a Release"); - description: _("This will uninstall the current release and install the chosen one instead. Note that downgrading can cause issues."); - } - } - } - }; - } -} diff --git a/data/ui/filter.blp b/data/ui/filter.blp deleted file mode 100644 index 093ec0c..0000000 --- a/data/ui/filter.blp +++ /dev/null @@ -1,83 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $FilterWindow: Adw.Dialog { - title: _("Set Filters"); - content-width: 500; - content-height: 450; - - child: - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - } - - content: Adw.ToastOverlay toast_overlay { - Stack main_stack { - Overlay main_overlay { - ScrolledWindow scrolled_window { - vexpand: true; - - Adw.Clamp { - Box outerbox { - orientation: vertical; - - ListBox install_type_list { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - - Adw.ActionRow apps_row { - title: _("Show Apps"); - - Switch show_apps_switch { - valign: center; - } - - activatable-widget: show_apps_switch; - } - - Adw.ActionRow show_runtimes_row { - title: _("Show Runtimes"); - - Switch show_runtimes_switch { - valign: center; - } - - activatable-widget: show_runtimes_switch; - } - - Adw.ExpanderRow remotes_expander { - enable-expansion: true; - title: _("Filter by Remotes"); - } - - Adw.ExpanderRow runtimes_expander { - enable-expansion: true; - title: _("Filter by Runtimes"); - } - } - - Button reset_button { - visible: true; - margin-bottom: 18; - halign: center; - label: _("Reset Filters"); - styles ["pill"] - } - } - } - } - } - } - }; - }; -} diff --git a/data/ui/orphans.blp b/data/ui/orphans.blp deleted file mode 100644 index 0bb2b37..0000000 --- a/data/ui/orphans.blp +++ /dev/null @@ -1,135 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $OrphansWindow: Adw.Dialog { - content-width: 500; - content-height: 450; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - [start] - ToggleButton search_button { - icon-name: "system-search-symbolic"; - tooltip-text: _("Search List"); - } - - [end] - Button oepn_folder_button { - icon-name: "document-open-symbolic"; - tooltip-text: _("Open Data Folder"); - } - } - - [top] - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: main_toolbar_view; - - Adw.Clamp { - maximum-size: 577; - hexpand: true; - - SearchEntry search_entry {} - } - } - - content: Adw.ToastOverlay toast_overlay { - Overlay main_overlay { - Stack main_stack { - Box main_box { - orientation: vertical; - - ScrolledWindow scrolled_window { - vexpand: true; - - Adw.Clamp { - ListBox list_of_data { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - } - } - } - - Box installing { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Installing"); - - styles [ - "title-1", - "title" - ] - } - - Label installing_status { - label: ""; - justify: center; - - styles [ - "description", - "body" - ] - } - } - - Adw.StatusPage no_data { - icon-name: "check-plain-symbolic"; - title: _("No Leftover Data"); - description: _("There is no leftover user data"); - } - - Adw.StatusPage no_results { - icon-name: "system-search-symbolic"; - title: _("No Results Found"); - description: _("Try a different search term"); - } - } - } - }; - - [bottom] - ActionBar action_bar { - [start] - ToggleButton select_all_button { - label: _("Select All"); - } - - [end] - Button trash_button { - label: _("Trash"); - sensitive: false; - } - - [end] - Button install_button { - label: _("Install"); - sensitive: false; - } - } - } -} diff --git a/data/ui/popular_remotes.blp b/data/ui/popular_remotes.blp deleted file mode 100644 index 5625e21..0000000 --- a/data/ui/popular_remotes.blp +++ /dev/null @@ -1,59 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $PopularRemotesWindow: Adw.Window { - default-width: 450; - default-height: 530; - title: ""; - - Adw.ToolbarView main_toolbar_view { - [top] - HeaderBar header_bar {} - - content: Adw.ToastOverlay toast_overlay { - vexpand: true; - - Adw.StatusPage { - valign: start; - title: _("Add Remote"); - description: _("Choose from a list of popular remotes or add a new one"); - - Adw.Clamp { - Box { - orientation: vertical; - - ListBox list_of_remotes { - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - - ListBox custom_list { - hexpand: true; - 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/data/ui/properties.blp b/data/ui/properties.blp deleted file mode 100644 index 8d52f58..0000000 --- a/data/ui/properties.blp +++ /dev/null @@ -1,164 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $PropertiesWindow: Adw.Dialog { - content-width: 350; - content-height: 999999; - title: _("Properties"); - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar {} - - content: Adw.ToastOverlay toast_overlay { - Box { - orientation: vertical; - - Adw.Banner eol_app_banner {} - - Adw.Banner eol_runtime_banner {} - - Adw.Banner mask_banner {} - - ScrolledWindow { - Adw.Clamp { - Box { - orientation: vertical; - hexpand: false; - vexpand: true; - spacing: 12; - margin-top: 12; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - - Image app_icon { - pixel-size: 100; - - styles [ - "icon-dropshadow" - ] - } - - Label name { - wrap: true; - - styles [ - "title-1" - ] - } - - Button description_button { - styles [ - "title-4", - "flat" - ] - - Box { - spacing: 12; - - Label description { - halign: start; - wrap: true; - hexpand: true; - } - Image { - icon-name: "edit-copy-symbolic"; - } - } - } - - Adw.PreferencesGroup upper { - Adw.ActionRow data_row { - title: _("Loading User Data"); - - [suffix] - Button open_data { - icon-name: "document-open-symbolic"; - tooltip-text: _("Open User Data Folder"); - valign: center; - visible: false; - - styles [ - "flat" - ] - } - - [suffix] - Button trash_data { - icon-name: "user-trash-symbolic"; - tooltip-text: _("Trash User Data"); - valign: center; - visible: false; - - styles [ - "flat" - ] - } - - [suffix] - Spinner spinner { - spinning: true; - } - - styles["property"] - } - - Adw.ActionRow view_apps { - title: _("Show Apps Using This Runtime"); - activatable: true; - visible: false; - - [suffix] - Image { - icon-name: "funnel-symbolic"; - } - } - - Adw.ActionRow runtime { - title: _("Runtime"); - - [suffix] - Button runtime_properties { - icon-name: "info-symbolic"; - tooltip-text: _("View Properties"); - valign: center; - - styles [ - "flat" - ] - } - - [suffix] - Button runtime_copy { - icon-name: "edit-copy-symbolic"; - tooltip-text: _("Copy"); - valign: center; - - styles [ - "flat" - ] - } - - styles["property"] - } - - Adw.ActionRow details { - title: _("Show Details in Store"); - activatable: true; - - [suffix] - Image { - icon-name: "adw-external-link-symbolic"; - } - } - } - - Adw.PreferencesGroup lower {} - } - } - } - } - }; - } -} diff --git a/data/ui/remotes.blp b/data/ui/remotes.blp deleted file mode 100644 index ed42b67..0000000 --- a/data/ui/remotes.blp +++ /dev/null @@ -1,95 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $RemotesWindow: Adw.Dialog { - title: _("Manage Remotes"); - content-width: 500; - content-height: 450; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - Button refresh { - icon-name: "view-refresh-symbolic"; - tooltip-text: _("Refresh List of Remotes"); - } - } - - content: Adw.ToastOverlay toast_overlay { - Stack stack { - - Adw.PreferencesPage main_group { - Adw.PreferencesGroup remotes_list { - title: _("Installed Remotes"); - header-suffix: ToggleButton show_disabled_button { - Adw.ButtonContent show_disabled_button_button_content { - icon-name: "eye-not-looking-symbolic"; - label: _("Show Disabled"); - styles["flat"] - } - // spacing: 6; - // margin-end: 6; - // Label { - // label: _("Show Disabled"); - // styles["heading", "h4"] - // } - // Switch show_disabled { - // valign: center; - // } - }; - Adw.ActionRow no_remotes { - title: _("No Remotes Found"); - } - } - Adw.PreferencesGroup popular_remotes_list { - title: _("Add a Popular Remote"); - visible: false; - } - Adw.PreferencesGroup manual_remotes_list { - title: _("Add Other Remotes"); - Adw.ActionRow add_from_file { - title: _("Add a Repo File"); - activatable: true; - } - - Adw.ActionRow custom_remote { - title: _("Add a Custom Remote"); - activatable: true; - } - } - } - - Box adding { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Adding Remote"); - - styles [ - "title-1", - "title" - ] - } - - Label { - label: _("This should only take a moment"); - styles ["description", "body"] - } - } - } - }; - } -} diff --git a/data/ui/search_install.blp b/data/ui/search_install.blp deleted file mode 100644 index 7d06b35..0000000 --- a/data/ui/search_install.blp +++ /dev/null @@ -1,193 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $SearchInstallWindow: Adw.Dialog { - content-width: 500; - content-height: 450; - title: ""; - - Adw.ToolbarView main_toolbar_view { - content: - Stack outer_stack { - Adw.NavigationView nav_view { - Adw.NavigationPage search_page { - title: _("Search Criteria"); - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView { - [top] - Adw.HeaderBar { - } - content: - Adw.StatusPage { - title: _("Choose a Remote to Search"); - valign: start; - child: - Adw.Clamp { - ListBox remotes_list { - selection-mode: none; - styles ["boxed-list"] - } - }; - }; - } - } - } - Adw.NavigationPage results_page { - title: _("Results"); - Adw.ToolbarView { - [top] - Adw.HeaderBar { - } - [bottom] - ActionBar action_bar { - revealed: false; - [center] - Button install_button { - margin-top: 6; - margin-bottom: 6; - styles[ - "pill", - "suggested-action" - ] - - Adw.ButtonContent { - label: _("Install"); - icon-name: "plus-large-symbolic"; - } - } - } - [top] - Adw.Clamp { - Box search_box { - margin-top: 4; - margin-start: 12; - margin-end: 12; - margin-bottom: 6; - SearchEntry search_entry { - hexpand: true; - } - Button search_button { - icon-name: "right-large-symbolic"; - tooltip-text: _("Start Search"); - } - styles ["linked"] - } - } - content: - Stack inner_stack { - - Adw.StatusPage blank_page { - title: _("Search for Flatpaks"); - icon-name: "flatpak-symbolic"; - description: _("Search for Flatpaks that you want to install"); - } - - Adw.StatusPage no_results { - icon-name: "system-search-symbolic"; - title: _("No Results Found"); - description: _("Try a different search term"); - } - - Box loading_page { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Searching"); - - styles [ - "title-1", - "title" - ] - } - } - - Adw.StatusPage too_many { - icon-name: "error-symbolic"; - title: _("Too Many Results"); - description: _("Try being more specific with your search"); - } - - ScrolledWindow results_scroll { - vexpand: true; - Adw.Clamp { - ListBox results_list { - margin-top: 6; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - styles ["boxed-list"] - } - } - } - }; - } - } - } - Adw.ToolbarView installing { - [top] - Adw.HeaderBar { - } - content: - Overlay overlay { - [overlay] - ProgressBar progress_bar { - visible: false; - can-target: false; - styles ["osd"] - } - Box { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Installing"); - - styles [ - "title-1", - "title" - ] - } - - Label installing_status { - label: ""; - justify: center; - - styles [ - "description", - "body" - ] - } - } - }; - } - }; - } -} diff --git a/data/ui/snapshots.blp b/data/ui/snapshots.blp deleted file mode 100644 index 6946529..0000000 --- a/data/ui/snapshots.blp +++ /dev/null @@ -1,90 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $SnapshotsWindow: Adw.Dialog { - content-width: 500; - content-height: 455; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - [end] - Button open_folder_button { - icon-name: "document-open-symbolic"; - tooltip-text: _("Open Snapshots Folder"); - } - } - - [bottom] - ActionBar action_bar { - [center] - Button new_snapshot { - halign: center; - sensitive: bind action_bar.revealed; - margin-top: 6; - margin-bottom: 6; - styles[ - "pill", - "suggested-action" - ] - - Adw.ButtonContent { - label: _("New Snapshot"); - icon-name: "plus-large-symbolic"; - } - } - } - - content: Adw.ToastOverlay toast_overlay { - Stack main_stack { - ScrolledWindow outerbox { - Adw.Clamp { - ListBox snapshots_group { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - } - } - - Box loading { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label loading_label { - styles [ - "title-1", - "title" - ] - } - } - - - Adw.StatusPage no_snapshots { - title: _("No Snapshots"); - description: _("Snapshots are backups of the app's user data. They can be reapplied at any time."); - icon-name: "clock-alt-symbolic"; - } - } - }; - } -} diff --git a/data/ui/window.blp b/data/ui/window.blp deleted file mode 100644 index 591666f..0000000 --- a/data/ui/window.blp +++ /dev/null @@ -1,299 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $WarehouseWindow: Adw.ApplicationWindow { - title: "Warehouse"; - Adw.ToolbarView main_toolbar_view { - [top] - HeaderBar header_bar { - [start] - ToggleButton search_button { - icon-name: "system-search-symbolic"; - tooltip-text: _("Search List"); - } - - [start] - Button filter_button { - icon-name: "funnel-symbolic"; - tooltip-text: _("Filter List"); - } - - [end] - MenuButton main_menu { - icon-name: "open-menu-symbolic"; - tooltip-text: _("Main Menu"); - menu-model: primary_menu; - } - - [end] - ToggleButton batch_mode_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Toggle Selection Mode"); - } - } - - [top] - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: main_toolbar_view; - - Adw.Clamp { - maximum-size: 577; - hexpand: true; - - SearchEntry search_entry {} - } - } - - content: Adw.ToastOverlay toast_overlay { - Overlay main_overlay { - Stack main_stack { - Adw.StatusPage loading_flatpaks { - icon-name: "clock-alt-symbolic"; - title: _("Loading Flatpaks"); - description: _("This should only take a moment"); - } - - Box main_box { - orientation: vertical; - - ScrolledWindow scrolled_window { - vexpand: true; - - Adw.Clamp { - ListBox flatpaks_list_box { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - } - } - } - - Box installing { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Installing"); - - styles [ - "title-1", - "title" - ] - } - - Label { - label: _("This could take a while"); - styles ["description", "body"] - } - } - - Box uninstalling { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Uninstalling"); - styles ["title-1", "title"] - } - - Label uninstalling_status { - label: ""; - justify: center; - styles ["description", "body"] - } - } - - Box snapshotting { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Creating Snapshots"); - styles ["title-1", "title"] - } - - Label { - label: _("This could take a while"); - styles ["description", "body"] - } - } - - Adw.StatusPage no_flatpaks { - icon-name: "error-symbolic"; - title: _("No Flatpaks Found"); - description: _("Warehouse cannot see the list of installed Flatpaks or the system has no Flatpaks installed"); - } - - Adw.StatusPage no_matches { - icon-name: "funnel-symbolic"; - title: _("No Flatpaks Match Filters"); - description: _("No installed Flatpak matches all of the currently applied filters"); - [child] - Button reset_filters_button { - label: _("Reset Filters"); - halign: center; - styles["pill"] - } - } - - Adw.StatusPage no_results { - icon-name: "system-search-symbolic"; - title: _("No Results Found"); - description: _("Try a different search term"); - } - - Adw.StatusPage refreshing { - icon-name: "arrow-circular-top-right-symbolic"; - title: _("Refreshing List"); - description: _("This should only take a moment"); - } - } - } - }; - - [bottom] - ActionBar batch_mode_bar { - revealed: false; - - [start] - ToggleButton batch_select_all_button { - label: _("Select All"); - } - - [end] - Button batch_uninstall_button { - icon-name: "cross-filled-symbolic"; - tooltip-text: _("Uninstall Selected Apps"); - } - - [end] - Button batch_clean_button { - icon-name: "user-trash-symbolic"; - tooltip-text: _("Send Selected Apps' Data to the Trash"); - } - - [end] - MenuButton batch_copy_button { - icon-name: "edit-copy-symbolic"; - tooltip-text: _("Open Copy Menu"); - menu-model: copy_menu; - } - - [end] - Button batch_snapshot_button { - icon-name: "clock-alt-symbolic"; - tooltip-text: _("Snapshot Selected Apps' Data"); - visible: true; - } - } - } -} - -menu primary_menu { - section { - item { - label: _("Manage Leftover Data…"); - action: "app.manage-data-folders"; - } - - /*item { - label: _("_Preferences"); - action: "app.preferences"; - }*/ - item { - label: _("Manage Remotes…"); - action: "app.show-remotes-window"; - } - } - section { - item { - label: _("Install From File…"); - action: "app.install-from-file"; - } - - item { - label: _("Install From The Web…"); - action: "app.open-search-install"; - } - } - section { - item { - label: _("Refresh List"); - action: "app.refresh-list"; - } - item { - label: _("_Keyboard Shortcuts"); - action: "win.show-help-overlay"; - } - - item { - label: _("_About Warehouse"); - action: "app.about"; - } - } -} - -menu copy_menu { - section { - item { - label: _("Copy Names"); - action: "win.copy-names"; - } - - item { - label: _("Copy IDs"); - action: "win.copy-ids"; - } - - item { - label: _("Copy Refs"); - action: "win.copy-refs"; - } - } -} \ No newline at end of file diff --git a/po/POTFILES b/po/POTFILES index 2fdd683..491b278 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,23 +1,8 @@ data/io.github.flattool.Warehouse.desktop.in data/io.github.flattool.Warehouse.metainfo.xml.in data/io.github.flattool.Warehouse.gschema.xml -data/ui/downgrade.blp -data/ui/filter.blp -data/ui/orphans.blp -data/ui/popular_remotes.blp -data/ui/properties.blp -data/ui/remotes.blp -data/ui/search_install.blp -data/ui/snapshots.blp -data/ui/window.blp -src/app_row_widget.py +src/main_window/window.blp +src/main_window/window.py src/common.py -src/downgrade_window.py -src/filter_window.py src/gtk/help-overlay.blp -src/main.py -src/orphans_window.py -src/properties_window.py -src/remotes_window.py -src/snapshots_window.py -src/window.py +src/main.py \ No newline at end of file diff --git a/po/warehouse.pot b/po/warehouse.pot index e49ec7c..8ffd7c8 100644 --- a/po/warehouse.pot +++ b/po/warehouse.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: io.github.flattool.heliguy.Warehouse\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-03 14:05-0400\n" +"POT-Creation-Date: 2024-07-03 16:27-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -65,488 +65,44 @@ msgstr "" msgid "Reinstall apps that have leftover data" msgstr "" -#: data/ui/downgrade.blp:25 src/app_row_widget.py:360 -msgid "Downgrade" -msgstr "" - -#: data/ui/downgrade.blp:55 -msgid "Fetching Releases" -msgstr "" - -#: data/ui/downgrade.blp:63 data/ui/window.blp:107 data/ui/window.blp:162 -msgid "This could take a while" -msgstr "" - -#: data/ui/downgrade.blp:71 src/app_row_widget.py:299 src/window.py:494 -msgid "Disable Updates" -msgstr "" - -#: data/ui/downgrade.blp:77 -msgid "Select a Release" -msgstr "" - -#: data/ui/downgrade.blp:78 -msgid "" -"This will uninstall the current release and install the chosen one instead. " -"Note that downgrading can cause issues." -msgstr "" - -#: data/ui/filter.blp:5 src/gtk/help-overlay.blp:19 -msgid "Set Filters" -msgstr "" - -#: data/ui/filter.blp:39 -msgid "Show Apps" -msgstr "" - -#: data/ui/filter.blp:49 -msgid "Show Runtimes" -msgstr "" - -#: data/ui/filter.blp:60 -msgid "Filter by Remotes" -msgstr "" - -#: data/ui/filter.blp:65 -msgid "Filter by Runtimes" -msgstr "" - -#: data/ui/filter.blp:73 data/ui/window.blp:179 -msgid "Reset Filters" -msgstr "" - -#: data/ui/orphans.blp:14 data/ui/window.blp:12 -msgid "Search List" -msgstr "" - -#: data/ui/orphans.blp:20 -msgid "Open Data Folder" -msgstr "" - -#: data/ui/orphans.blp:81 data/ui/search_install.blp:171 data/ui/window.blp:98 -msgid "Installing" -msgstr "" - -#: data/ui/orphans.blp:102 -msgid "No Leftover Data" -msgstr "" - -#: data/ui/orphans.blp:103 -msgid "There is no leftover user data" -msgstr "" - -#: data/ui/orphans.blp:108 data/ui/search_install.blp:87 data/ui/window.blp:187 -msgid "No Results Found" -msgstr "" - -#: data/ui/orphans.blp:109 data/ui/search_install.blp:88 data/ui/window.blp:188 -msgid "Try a different search term" -msgstr "" - -#: data/ui/orphans.blp:119 data/ui/window.blp:206 -msgid "Select All" -msgstr "" - -#: data/ui/orphans.blp:124 src/window.py:199 src/window.py:281 -msgid "Trash" -msgstr "" - -#: data/ui/orphans.blp:130 data/ui/search_install.blp:54 -#: src/orphans_window.py:122 src/window.py:839 -msgid "Install" -msgstr "" - -#: data/ui/popular_remotes.blp:18 -msgid "Add Remote" -msgstr "" - -#: data/ui/popular_remotes.blp:19 -msgid "Choose from a list of popular remotes or add a new one" -msgstr "" - -#: data/ui/popular_remotes.blp:45 data/ui/remotes.blp:51 -msgid "Add a Repo File" -msgstr "" - -#: data/ui/popular_remotes.blp:50 data/ui/remotes.blp:56 -msgid "Add a Custom Remote" -msgstr "" - -#: data/ui/properties.blp:7 -msgid "Properties" -msgstr "" - -#: data/ui/properties.blp:73 -msgid "Loading User Data" -msgstr "" - -#: data/ui/properties.blp:78 src/app_row_widget.py:268 -#: src/orphans_window.py:258 -msgid "Open User Data Folder" -msgstr "" - -#: data/ui/properties.blp:90 src/app_row_widget.py:281 -msgid "Trash User Data" -msgstr "" - -#: data/ui/properties.blp:108 -msgid "Show Apps Using This Runtime" -msgstr "" - -#: data/ui/properties.blp:119 -msgid "Runtime" -msgstr "" - -#: data/ui/properties.blp:124 src/app_row_widget.py:156 -msgid "View Properties" -msgstr "" - -#: data/ui/properties.blp:135 src/app_row_widget.py:234 src/window.py:562 -msgid "Copy" -msgstr "" - -#: data/ui/properties.blp:147 -msgid "Show Details in Store" -msgstr "" - -#: data/ui/remotes.blp:5 src/gtk/help-overlay.blp:42 -msgid "Manage Remotes" -msgstr "" - -#: data/ui/remotes.blp:14 -msgid "Refresh List of Remotes" -msgstr "" - -#: data/ui/remotes.blp:23 -msgid "Installed Remotes" -msgstr "" - -#: data/ui/remotes.blp:27 -msgid "Show Disabled" -msgstr "" - -#: data/ui/remotes.blp:41 -msgid "No Remotes Found" -msgstr "" - -#: data/ui/remotes.blp:45 -msgid "Add a Popular Remote" -msgstr "" - -#: data/ui/remotes.blp:49 -msgid "Add Other Remotes" -msgstr "" - -#: data/ui/remotes.blp:79 -msgid "Adding Remote" -msgstr "" - -#: data/ui/remotes.blp:88 data/ui/window.blp:54 data/ui/window.blp:194 -msgid "This should only take a moment" -msgstr "" - -#: data/ui/search_install.blp:14 -msgid "Search Criteria" -msgstr "" - -#: data/ui/search_install.blp:22 -msgid "Choose a Remote to Search" -msgstr "" - -#: data/ui/search_install.blp:36 -msgid "Results" -msgstr "" - -#: data/ui/search_install.blp:71 -msgid "Start Search" -msgstr "" - -#: data/ui/search_install.blp:80 -msgid "Search for Flatpaks" -msgstr "" - -#: data/ui/search_install.blp:82 -msgid "Search for Flatpaks that you want to install" -msgstr "" - -#: data/ui/search_install.blp:108 -msgid "Searching" -msgstr "" - -#: data/ui/search_install.blp:119 -msgid "Too Many Results" -msgstr "" - -#: data/ui/search_install.blp:120 -msgid "Try being more specific with your search" -msgstr "" - -#: data/ui/snapshots.blp:14 -msgid "Open Snapshots Folder" -msgstr "" - -#: data/ui/snapshots.blp:32 -msgid "New Snapshot" -msgstr "" - -#: data/ui/snapshots.blp:83 -msgid "No Snapshots" -msgstr "" - -#: data/ui/snapshots.blp:84 -msgid "" -"Snapshots are backups of the app's user data. They can be reapplied at any " -"time." -msgstr "" - -#: data/ui/window.blp:18 -msgid "Filter List" -msgstr "" - -#: data/ui/window.blp:24 +#: src/main_window/window.blp:15 msgid "Main Menu" msgstr "" -#: data/ui/window.blp:31 src/gtk/help-overlay.blp:29 -msgid "Toggle Selection Mode" -msgstr "" - -#: data/ui/window.blp:53 -msgid "Loading Flatpaks" -msgstr "" - -#: data/ui/window.blp:129 -msgid "Uninstalling" -msgstr "" - -#: data/ui/window.blp:157 -msgid "Creating Snapshots" -msgstr "" - -#: data/ui/window.blp:169 -msgid "No Flatpaks Found" -msgstr "" - -#: data/ui/window.blp:170 -msgid "" -"Warehouse cannot see the list of installed Flatpaks or the system has no " -"Flatpaks installed" -msgstr "" - -#: data/ui/window.blp:175 -msgid "No Flatpaks Match Filters" -msgstr "" - -#: data/ui/window.blp:176 -msgid "No installed Flatpak matches all of the currently applied filters" -msgstr "" - -#: data/ui/window.blp:193 -msgid "Refreshing List" -msgstr "" - -#: data/ui/window.blp:212 -msgid "Uninstall Selected Apps" -msgstr "" - -#: data/ui/window.blp:218 -msgid "Send Selected Apps' Data to the Trash" -msgstr "" - -#: data/ui/window.blp:224 -msgid "Open Copy Menu" -msgstr "" - -#: data/ui/window.blp:231 -msgid "Snapshot Selected Apps' Data" -msgstr "" - -#: data/ui/window.blp:241 +#: src/main_window/window.blp:34 msgid "Manage Leftover Data…" msgstr "" -#: data/ui/window.blp:250 +#: src/main_window/window.blp:43 msgid "Manage Remotes…" msgstr "" -#: data/ui/window.blp:256 +#: src/main_window/window.blp:49 msgid "Install From File…" msgstr "" -#: data/ui/window.blp:261 +#: src/main_window/window.blp:54 msgid "Install From The Web…" msgstr "" -#: data/ui/window.blp:267 +#: src/main_window/window.blp:60 msgid "Refresh List" msgstr "" -#: data/ui/window.blp:271 +#: src/main_window/window.blp:64 msgid "_Keyboard Shortcuts" msgstr "" -#: data/ui/window.blp:276 +#: src/main_window/window.blp:69 msgid "_About Warehouse" msgstr "" -#: data/ui/window.blp:285 -msgid "Copy Names" -msgstr "" - -#: data/ui/window.blp:290 -msgid "Copy IDs" -msgstr "" - -#: data/ui/window.blp:295 -msgid "Copy Refs" -msgstr "" - -#: src/app_row_widget.py:82 -msgid "Updates Disabled" -msgstr "" - -#: src/app_row_widget.py:87 src/properties_window.py:212 -msgid "{} is masked and will not be updated" -msgstr "" - -#: src/app_row_widget.py:94 -msgid "Auto Removal Disabled" -msgstr "" - -#: src/app_row_widget.py:99 -msgid "" -"{} is pinned and will not be auto removed even when it's required by no app" -msgstr "" - -#: src/app_row_widget.py:106 -msgid "App EOL" -msgstr "" - -#: src/app_row_widget.py:112 src/properties_window.py:195 -msgid "" -"{} has reached its End of Life and will not receive any security updates" -msgstr "" - -#: src/app_row_widget.py:123 -msgid "Runtime EOL" -msgstr "" - -#: src/app_row_widget.py:129 src/properties_window.py:203 -msgid "" -"{}'s runtime has reached its End of Life and will not receive any security " -"updates" -msgstr "" - -#: src/app_row_widget.py:165 src/orphans_window.py:266 -msgid "Select" -msgstr "" - -#: src/app_row_widget.py:176 src/remotes_window.py:227 -msgid "View More" -msgstr "" - -#: src/app_row_widget.py:197 -msgid "Copied name" -msgstr "" - -#: src/app_row_widget.py:201 -msgid "Copy Name" -msgstr "" - -#: src/app_row_widget.py:206 -msgid "Copied ID" -msgstr "" - -#: src/app_row_widget.py:211 -msgid "Copy ID" -msgstr "" - -#: src/app_row_widget.py:216 -msgid "Copied ref" -msgstr "" - -#: src/app_row_widget.py:221 -msgid "Copy Ref" -msgstr "" - -#: src/app_row_widget.py:227 -msgid "Copied launch command" -msgstr "" - -#: src/app_row_widget.py:231 -msgid "Copy Launch Command" -msgstr "" - -#: src/app_row_widget.py:243 -msgid "Opened {}" -msgstr "" - -#: src/app_row_widget.py:246 -msgid "Open" -msgstr "" - -#: src/app_row_widget.py:256 src/window.py:229 src/window.py:302 -msgid "Uninstall" -msgstr "" - -#: src/app_row_widget.py:311 -msgid "Enable Updates" -msgstr "" - -#: src/app_row_widget.py:324 src/window.py:546 -msgid "Disable Auto Removal" -msgstr "" - -#: src/app_row_widget.py:336 -msgid "Enable Auto Removal" -msgstr "" - -#: src/app_row_widget.py:350 -msgid "Manage Snapshots" -msgstr "" - #: src/common.py:325 src/common.py:399 msgid "" "Working on {}\n" "{} out of {}" msgstr "" -#: src/downgrade_window.py:88 -msgid "Commit Hash: {}" -msgstr "" - -#: src/downgrade_window.py:110 -msgid "Could not downgrade {}" -msgstr "" - -#: src/downgrade_window.py:128 -msgid "Downgrading…" -msgstr "" - -#: src/downgrade_window.py:151 -msgid "Downgrade {}" -msgstr "" - -#: src/downgrade_window.py:159 -msgid "Ensure that {} will never be updated to a newer version" -msgstr "" - -#: src/filter_window.py:43 src/filter_window.py:49 -msgid "{} selected" -msgstr "" - -#: src/filter_window.py:135 src/orphans_window.py:150 src/remotes_window.py:307 -msgid "User wide" -msgstr "" - -#: src/filter_window.py:137 src/orphans_window.py:152 src/remotes_window.py:309 -msgid "System wide" -msgstr "" - -#: src/filter_window.py:139 src/orphans_window.py:154 src/remotes_window.py:311 -msgid "Unknown install type" -msgstr "" - #: src/gtk/help-overlay.blp:11 msgid "App Management" msgstr "" @@ -555,18 +111,30 @@ msgstr "" msgid "Search" msgstr "" +#: src/gtk/help-overlay.blp:19 +msgid "Set Filters" +msgstr "" + #: src/gtk/help-overlay.blp:24 msgid "Refresh" msgstr "" +#: src/gtk/help-overlay.blp:29 +msgid "Toggle Selection Mode" +msgstr "" + #: src/gtk/help-overlay.blp:34 msgid "More Functions" msgstr "" -#: src/gtk/help-overlay.blp:37 src/orphans_window.py:30 +#: src/gtk/help-overlay.blp:37 msgid "Manage Leftover Data" msgstr "" +#: src/gtk/help-overlay.blp:42 +msgid "Manage Remotes" +msgstr "" + #: src/gtk/help-overlay.blp:47 msgid "Install From File" msgstr "" @@ -587,458 +155,19 @@ msgstr "" msgid "Quit" msgstr "" -#: src/main.py:149 +#: src/main.py:145 msgid "Flatpaks" msgstr "" #. Translators: do one of the following, one per line: Your Name, Your Name , Your Name https://websi.te -#: src/main.py:191 +#: src/main.py:187 msgid "translator-credits" msgstr "" -#: src/main.py:197 +#: src/main.py:193 msgid "Donate" msgstr "" -#: src/main.py:199 +#: src/main.py:195 msgid "Contributors" msgstr "" - -#: src/orphans_window.py:80 src/window.py:810 -msgid "Installed successfully" -msgstr "" - -#: src/orphans_window.py:83 -msgid "Could not install some apps" -msgstr "" - -#: src/orphans_window.py:117 -msgid "Attempt to Install?" -msgstr "" - -#: src/orphans_window.py:118 -msgid "Warehouse will attempt to install apps matching the selected data." -msgstr "" - -#: src/orphans_window.py:121 src/orphans_window.py:189 -#: src/properties_window.py:156 src/remotes_window.py:86 -#: src/remotes_window.py:167 src/remotes_window.py:450 -#: src/remotes_window.py:621 src/snapshots_window.py:128 -#: src/snapshots_window.py:234 src/window.py:228 src/window.py:301 -#: src/window.py:446 src/window.py:492 src/window.py:543 src/window.py:645 -#: src/window.py:715 src/window.py:838 -msgid "Cancel" -msgstr "" - -#: src/orphans_window.py:185 -msgid "Trash folders?" -msgstr "" - -#: src/orphans_window.py:185 -msgid "These folders will be sent to the trash." -msgstr "" - -#: src/orphans_window.py:190 -msgid "Continue" -msgstr "" - -#: src/orphans_window.py:198 src/properties_window.py:46 -#: src/snapshots_window.py:245 src/window.py:424 -msgid "Could not open folder" -msgstr "" - -#: src/orphans_window.py:216 -msgid "Could not manage data" -msgstr "" - -#: src/properties_window.py:40 src/remotes_window.py:212 -msgid "Copied {}" -msgstr "" - -#: src/properties_window.py:52 -msgid "Could not show details" -msgstr "" - -#: src/properties_window.py:58 -msgid "User Data" -msgstr "" - -#: src/properties_window.py:69 -msgid "Description" -msgstr "" - -#: src/properties_window.py:84 src/properties_window.py:144 -msgid "No User Data" -msgstr "" - -#: src/properties_window.py:135 -msgid "Could not show properties" -msgstr "" - -#: src/properties_window.py:143 src/window.py:438 -msgid "Trashed user data" -msgstr "" - -#: src/properties_window.py:150 src/window.py:433 -msgid "Could not trash user data" -msgstr "" - -#: src/properties_window.py:154 src/window.py:441 -msgid "Send {}'s User Data to the Trash?" -msgstr "" - -#: src/properties_window.py:158 src/window.py:448 src/window.py:646 -msgid "Trash Data" -msgstr "" - -#: src/remotes_window.py:71 -msgid "Could not remove {}" -msgstr "" - -#: src/remotes_window.py:81 src/remotes_window.py:162 -msgid "Any installed apps from {} will stop receiving updates" -msgstr "" - -#: src/remotes_window.py:84 -msgid "Remove {}?" -msgstr "" - -#: src/remotes_window.py:87 src/remotes_window.py:272 -msgid "Remove" -msgstr "" - -#: src/remotes_window.py:114 -msgid "Could not enable {}" -msgstr "" - -#: src/remotes_window.py:142 -msgid "Could not disable {}" -msgstr "" - -#: src/remotes_window.py:165 -msgid "Disable {}?" -msgstr "" - -#: src/remotes_window.py:168 src/remotes_window.py:263 -msgid "Disable" -msgstr "" - -#: src/remotes_window.py:179 -msgid "Could not view apps" -msgstr "" - -#: src/remotes_window.py:240 -msgid "Set Filter" -msgstr "" - -#: src/remotes_window.py:254 -msgid "Enable" -msgstr "" - -#: src/remotes_window.py:288 -msgid "Copy remote name" -msgstr "" - -#: src/remotes_window.py:302 -msgid "Disabled" -msgstr "" - -#: src/remotes_window.py:339 -msgid "The open source, pay-what-you-want app store from elementary" -msgstr "" - -#: src/remotes_window.py:345 -msgid "Central repository of Flatpak applications" -msgstr "" - -#: src/remotes_window.py:351 -msgid "Beta builds of Flatpak applications" -msgstr "" - -#: src/remotes_window.py:357 -msgid "Flatpaks packaged by Fedora Linux" -msgstr "" - -#: src/remotes_window.py:363 -msgid "The latest beta GNOME Apps and Runtimes" -msgstr "" - -#: src/remotes_window.py:369 -msgid "Beta KDE Apps and Runtimes" -msgstr "" - -#: src/remotes_window.py:375 -msgid "Central repository of the WebKit Developer and Runtime SDK" -msgstr "" - -#: src/remotes_window.py:411 src/remotes_window.py:573 -msgid "Could not add {}" -msgstr "" - -#: src/remotes_window.py:448 -msgid "Add Flatpak Remote" -msgstr "" - -#: src/remotes_window.py:451 src/remotes_window.py:622 -msgid "Add" -msgstr "" - -#: src/remotes_window.py:505 src/remotes_window.py:630 -msgid "Name" -msgstr "" - -#: src/remotes_window.py:509 -msgid "URL" -msgstr "" - -#: src/remotes_window.py:521 src/remotes_window.py:633 src/window.py:847 -msgid "User" -msgstr "" - -#: src/remotes_window.py:521 src/remotes_window.py:633 -msgid "Remote will be available to only you" -msgstr "" - -#: src/remotes_window.py:529 src/remotes_window.py:636 src/window.py:850 -msgid "System" -msgstr "" - -#: src/remotes_window.py:530 src/remotes_window.py:637 -msgid "Remote will be available to every user on the system" -msgstr "" - -#: src/remotes_window.py:569 -msgid "{} successfully added" -msgstr "" - -#: src/remotes_window.py:619 -msgid "Add {}?" -msgstr "" - -#: src/remotes_window.py:667 -msgid "Flatpak Repos" -msgstr "" - -#: src/snapshots_window.py:48 -msgid "There is no User Data to Snapshot" -msgstr "" - -#: src/snapshots_window.py:87 -msgid "Version {}" -msgstr "" - -#: src/snapshots_window.py:95 src/snapshots_window.py:236 -msgid "Apply Snapshot" -msgstr "" - -#: src/snapshots_window.py:101 src/snapshots_window.py:130 -msgid "Trash Snapshot" -msgstr "" - -#: src/snapshots_window.py:121 -msgid "Could not trash snapshot" -msgstr "" - -#: src/snapshots_window.py:125 -msgid "Trash Snapshot?" -msgstr "" - -#: src/snapshots_window.py:126 -msgid "This snapshot and its contents will be sent to the trash." -msgstr "" - -#: src/snapshots_window.py:148 -msgid "Could not create snapshot" -msgstr "" - -#: src/snapshots_window.py:157 -msgid "Creating Snapshot…" -msgstr "" - -#: src/snapshots_window.py:191 src/snapshots_window.py:208 -#: src/snapshots_window.py:215 -msgid "Could not apply snapshot" -msgstr "" - -#: src/snapshots_window.py:194 -msgid "Snapshot applied" -msgstr "" - -#: src/snapshots_window.py:221 -msgid "Applying Snapshot…" -msgstr "" - -#: src/snapshots_window.py:229 -msgid "Apply Snapshot?" -msgstr "" - -#: src/snapshots_window.py:230 -msgid "Applying this snapshot will trash any current user data for {}." -msgstr "" - -#. Window stuffs -#: src/snapshots_window.py:280 -msgid "{} Snapshots" -msgstr "" - -#: src/window.py:111 -msgid "Uninstalled successfully" -msgstr "" - -#: src/window.py:114 -msgid "Could not uninstall some apps" -msgstr "" - -#: src/window.py:170 -msgid "Uninstall Selected Apps?" -msgstr "" - -#: src/window.py:171 -msgid "It will not be possible to use these apps after removal." -msgstr "" - -#: src/window.py:191 src/window.py:273 -msgid "App Settings & Data" -msgstr "" - -#: src/window.py:195 src/window.py:277 -msgid "Keep" -msgstr "" - -#: src/window.py:196 -msgid "Allow restoring these apps' settings and content" -msgstr "" - -#: src/window.py:200 -msgid "Send these apps' settings and content to the trash" -msgstr "" - -#: src/window.py:236 -msgid "Cannot uninstall while already uninstalling" -msgstr "" - -#: src/window.py:265 -msgid "Uninstall {}?" -msgstr "" - -#: src/window.py:266 -msgid "It will not be possible to use {} after removal." -msgstr "" - -#: src/window.py:278 -msgid "Allow restoring this app's settings and content" -msgstr "" - -#: src/window.py:282 -msgid "Send this app's settings and content to the trash" -msgstr "" - -#: src/window.py:444 -msgid "Your files and data for this app will be sent to the trash." -msgstr "" - -#: src/window.py:463 -msgid "Could not disable updates for {}" -msgstr "" - -#: src/window.py:485 -msgid "Disable Updates for {}?" -msgstr "" - -#: src/window.py:489 -msgid "" -"This will mask {} ensuring it will never recieve any feature or security " -"updates." -msgstr "" - -#: src/window.py:511 -msgid "Could not enable auto removal" -msgstr "" - -#: src/window.py:514 -msgid "Could not disable auto removal" -msgstr "" - -#: src/window.py:537 -msgid "Disable Automatic Removal for {}?" -msgstr "" - -#: src/window.py:540 -msgid "" -"This will pin {} ensuring it well never be removed automatically, even if no " -"app depends on it." -msgstr "" - -#: src/window.py:560 -msgid "Could not Run App" -msgstr "" - -#: src/window.py:568 -msgid "OK" -msgstr "" - -#: src/window.py:620 -msgid "{} has no data to trash" -msgstr "" - -#: src/window.py:626 -msgid "Could not trash {}'s data" -msgstr "" - -#: src/window.py:641 -msgid "Trash Selected Apps' User Data?" -msgstr "" - -#: src/window.py:642 -msgid "Your files and data for these apps will be sent to the trash." -msgstr "" - -#: src/window.py:692 -msgid "Could not snapshot some apps" -msgstr "" - -#: src/window.py:709 -msgid "Create Snapshots?" -msgstr "" - -#: src/window.py:711 -msgid "" -"Snapshots are backups of the app's user data. They can be reapplied at any " -"time. This could take a while." -msgstr "" - -#: src/window.py:716 -msgid "Create Snapshots" -msgstr "" - -#: src/window.py:771 -msgid "Copied selected app names" -msgstr "" - -#: src/window.py:786 -msgid "Copied selected app IDs" -msgstr "" - -#: src/window.py:801 -msgid "Copied selected app refs" -msgstr "" - -#: src/window.py:812 -msgid "Could not install app" -msgstr "" - -#: src/window.py:836 -msgid "Install {}?" -msgstr "" - -#: src/window.py:847 -msgid "The app will be available to only you" -msgstr "" - -#: src/window.py:851 -msgid "The app will be available to every user on the system" -msgstr "" - -#: src/window.py:881 -msgid "File type not supported" -msgstr "" diff --git a/src/app_row_widget.py b/src/app_row_widget.py deleted file mode 100644 index 716322d..0000000 --- a/src/app_row_widget.py +++ /dev/null @@ -1,381 +0,0 @@ -import os -import pathlib -import subprocess -import re - -from gi.repository import Adw, Gdk, Gio, GLib, Gtk -from .properties_window import PropertiesWindow -from .downgrade_window import DowngradeWindow -from .snapshots_window import SnapshotsWindow -from .filter_window import FilterWindow -from .common import myUtils - - -class AppRow(Adw.ActionRow): - def set_selectable(self, is_selectable): - self.tickbox.set_active(False) - self.tickbox.set_visible(is_selectable) - self.row_menu.set_visible(not is_selectable) - self.set_activatable(is_selectable) - - def set_is_visible(self, is_visible): - self.set_visible(is_visible) - self.set_selectable(False) - - def info_button_show_or_hide(self): - self.info_button.set_visible(False) - - if self.mask_label.get_visible() == True: - self.info_button.set_visible(True) - return - - if self.pin_label.get_visible() == True: - self.info_button.set_visible(True) - return - - if self.eol_app_label.get_visible() == True: - self.info_button.set_visible(True) - return - - if self.eol_runtime_label.get_visible() == True: - self.info_button.set_visible(True) - return - - def set_masked(self, is_masked): - self.mask_label.set_visible(is_masked) - self.info_button_show_or_hide() - - def __init__(self, parent_window, host_flatpaks, index, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils(parent_window) - - current_flatpak = host_flatpaks[index] - - self.index = index - self.app_name = current_flatpak[0] - self.app_id = current_flatpak[2] - self.app_version = current_flatpak[3] - self.origin_remote = current_flatpak[6] - self.install_type = current_flatpak[7] - self.app_ref = current_flatpak[8] - self.dependent_runtime = current_flatpak[13] - - self.set_title(self.app_name) - self.set_subtitle(self.app_id) - self.add_prefix(self.my_utils.find_app_icon(self.app_id)) - - self.is_runtime = False - if len(current_flatpak[13]) == 0: - self.is_runtime = True - - info_box = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, - valign=Gtk.Align.CENTER, - halign=Gtk.Align.CENTER, - hexpand=True, - vexpand=True, - spacing=6, - ) - - # justify=Gtk.Justification.RIGHT - self.mask_label = Gtk.Label( - label=_("Updates Disabled"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_("{} is masked and will not be updated").format( - self.app_name - ), - ) - self.mask_label.add_css_class("warning") - - self.pin_label = Gtk.Label( - label=_("Auto Removal Disabled"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_("{} is pinned and will not be auto removed even when it's required by no app").format( - self.app_name - ) - ) - self.pin_label.add_css_class("warning") - - self.eol_app_label = Gtk.Label( - label=_("App EOL"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_( - "{} has reached its End of Life and will not receive any security updates" - ).format(self.app_name), - ) - self.eol_app_label.add_css_class("error") - info_box.append(self.eol_app_label) - if "eol" in parent_window.host_flatpaks[index][12]: - # EOL = End Of Life, meaning the app will not be updated - # justify=Gtk.Justification.RIGHT - self.eol_app_label.set_visible(True) - - self.eol_runtime_label = Gtk.Label( - label=_("Runtime EOL"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_( - "{}'s runtime has reached its End of Life and will not receive any security updates" - ).format(self.app_name), - ) - self.eol_runtime_label.add_css_class("error") - info_box.append(self.eol_runtime_label) - if current_flatpak[13] in parent_window.eol_list: - # EOL = End Of Life, meaning the runtime will not be updated - # justify=Gtk.Justification.RIGHT - self.eol_runtime_label.set_visible(True) - - info_pop = Gtk.Popover() - info_pop.set_child(info_box) - self.info_button = Gtk.MenuButton( - visible=False, - valign=Gtk.Align.CENTER, - popover=info_pop, - icon_name="software-update-urgent-symbolic", - ) - self.info_button.add_css_class("flat") - - info_box.append(self.mask_label) - info_box.append(self.pin_label) - self.add_suffix(self.info_button) - - properties_button = Gtk.Button( - icon_name="info-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("View Properties"), - ) - properties_button.add_css_class("flat") - properties_button.connect( - "clicked", lambda *_: PropertiesWindow(index, host_flatpaks, parent_window) - ) - self.add_suffix(properties_button) - - self.tickbox = Gtk.CheckButton( - visible=False, tooltip_text=_("Select") - ) # visible=self.in_batch_mode - self.tickbox.add_css_class("selection-mode") - self.tickbox.connect("toggled", parent_window.row_select_handler) - self.add_suffix(self.tickbox) - self.set_activatable_widget(self.tickbox) - self.set_activatable(False) - - self.row_menu = Gtk.MenuButton( - icon_name="view-more-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("View More"), - ) - self.row_menu.add_css_class("flat") - row_menu_model = Gio.Menu() - copy_menu_model = Gio.Menu() - advanced_menu_model = Gio.Menu() - self.add_suffix(self.row_menu) - - self.is_pinned = False - - if "user" in self.install_type: - if f"runtime/{self.app_ref}" in parent_window.user_pins: - self.is_pinned = True - - if "system" in self.install_type: - if f"runtime/{self.app_ref}" in parent_window.system_pins: - self.is_pinned = True - - parent_window.create_action( - ("copy-name" + str(index)), - lambda *_, name=self.app_name, toast=_( - "Copied name" - ): parent_window.copy_item(name, toast), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy Name"), f"win.copy-name{index}") - ) - - parent_window.create_action( - ("copy-id" + str(index)), - lambda *_, id=self.app_id, toast=_("Copied ID"): parent_window.copy_item( - id, toast - ), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy ID"), f"win.copy-id{index}") - ) - - parent_window.create_action( - ("copy-ref" + str(index)), - lambda *_, ref=self.app_ref, toast=_("Copied ref"): parent_window.copy_item( - ref, toast - ), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy Ref"), f"win.copy-ref{index}") - ) - - parent_window.create_action( - ("copy-command" + str(index)), - lambda *_, ref=self.app_ref, toast=_( - "Copied launch command" - ): parent_window.copy_item(f"flatpak run {ref}", toast), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy Launch Command"), f"win.copy-command{index}") - ) - - row_menu_model.append_submenu(_("Copy"), copy_menu_model) - - if ( - "runtime" not in parent_window.host_flatpaks[index][12] - and self.app_id != "io.github.flattool.Warehouse" - ): - parent_window.create_action( - ("run" + str(index)), - lambda *_a, ref=self.app_ref, name=self.app_name: parent_window.run_app_thread( - ref, _("Opened {}").format(name) - ), - ) - run_item = Gio.MenuItem.new(_("Open"), f"win.run{index}") - row_menu_model.append_item(run_item) - - if self.app_id != "io.github.flattool.Warehouse": - parent_window.create_action( - ("uninstall" + str(index)), - lambda *_: parent_window.uninstall_button_handler( - self, self.app_name, self.app_ref, self.app_id - ), - ) - uninstall_item = Gio.MenuItem.new(_("Uninstall"), f"win.uninstall{index}") - row_menu_model.append_item(uninstall_item) - - data_menu_model = Gio.Menu() - - parent_window.create_action( - ("open-data" + str(index)), - lambda *_, path=( - parent_window.user_data_path + self.app_id - ): parent_window.open_data_folder(path), - ) - open_data_item = Gio.MenuItem.new( - _("Open User Data Folder"), f"win.open-data{index}" - ) - open_data_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - data_menu_model.append_item(open_data_item) - - parent_window.create_action( - ("trash" + str(index)), - lambda *_, name=self.app_name, id=self.app_id, index=index: parent_window.trash_data( - name, id, index - ), - ) - trash_item = Gio.MenuItem.new(_("Trash User Data"), f"win.trash{index}") - trash_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - data_menu_model.append_item(trash_item) - - row_menu_model.append_section(None, data_menu_model) - - if not os.path.exists(parent_window.user_data_path + self.app_id): - parent_window.lookup_action(f"open-data{self.index}").set_enabled(False) - parent_window.lookup_action(f"trash{self.index}").set_enabled(False) - - parent_window.create_action( - ("mask" + str(index)), - lambda *_, id=self.app_id, type=self.install_type, index=index: parent_window.mask_flatpak( - self - ), - ) - mask_item = Gio.MenuItem.new(_("Disable Updates"), f"win.mask{index}") - mask_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(mask_item) - - parent_window.create_action( - ("unmask" + str(index)), - lambda *_, id=self.app_id, type=self.install_type, index=index: parent_window.mask_flatpak( - self - ), - ) - unmask_item = Gio.MenuItem.new(_("Enable Updates"), f"win.unmask{index}") - unmask_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(unmask_item) - - if "runtime" in parent_window.host_flatpaks[index][12]: - parent_window.create_action( - ("pin" + str(index)), - lambda *_, d=self.app_id, type=self.install_type, index=index: parent_window.pin_flatpak( - self - ), - ) - pin_item = Gio.MenuItem.new(_("Disable Auto Removal"), f"win.pin{index}") - pin_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(pin_item) - - parent_window.create_action( - ("unpin" + str(index)), - lambda *_, d=self.app_id, type=self.install_type, index=index: parent_window.pin_flatpak( - self - ), - ) - unpin_item = Gio.MenuItem.new(_("Enable Auto Removal"), f"win.unpin{index}") - unpin_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(unpin_item) - - if "runtime" not in parent_window.host_flatpaks[index][12]: - parent_window.create_action( - ("snapshot" + str(index)), - lambda *_, row=current_flatpak: SnapshotsWindow( - parent_window, row - ), - ) - snapshot_item = Gio.MenuItem.new( - _("Manage Snapshots"), f"win.snapshot{index}" - ) - advanced_menu_model.append_item(snapshot_item) - - parent_window.create_action( - ("downgrade" + str(index)), - lambda *_, row=current_flatpak, index=index: DowngradeWindow( - parent_window, row, index - ), - ) - downgrade_item = Gio.MenuItem.new(_("Downgrade"), f"win.downgrade{index}") - advanced_menu_model.append_item(downgrade_item) - - if ( - self.app_id in parent_window.system_mask_list - or self.app_id in parent_window.user_mask_list - ): - self.mask_label.set_visible(True) - parent_window.lookup_action(f"mask{index}").set_enabled(False) - else: - parent_window.lookup_action(f"unmask{index}").set_enabled(False) - - if self.is_runtime and self.is_pinned: - parent_window.lookup_action(f"pin{index}").set_enabled(False) - elif self.is_runtime: - parent_window.lookup_action(f"unpin{index}").set_enabled(False) - self.pin_label.set_visible(self.is_pinned) - - row_menu_model.append_section(None, advanced_menu_model) - self.row_menu.set_menu_model(row_menu_model) - - self.info_button_show_or_hide() \ No newline at end of file diff --git a/src/common.py b/src/common.py deleted file mode 100644 index 9b58f61..0000000 --- a/src/common.py +++ /dev/null @@ -1,527 +0,0 @@ -from gi.repository import GLib, Gtk, Adw, Gio # , Gdk -import os -import subprocess -import pathlib -import time - - -class myUtils: - def __init__(self, window, **kwargs): - self.parent_window = window - self.host_home = str(pathlib.Path.home()) - self.user_data_path = self.host_home + "/.var/app/" - self.install_success = True - self.uninstall_success = True - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - self.is_dialog_open = False - - def trash_folder(self, path): - if not os.path.exists(path): - print("error in common.trashFolder: path does not exists. path =", path) - return 1 - try: - subprocess.run( - ["gio", "trash", path], - capture_output=False, - check=True, - env=self.new_env, - ) - return 0 - except subprocess.CalledProcessError as e: - print("error in common.trashFolder: CalledProcessError:", e) - return 2 - - def get_size_with_format(self, path): - return self.get_size_format(self.get_dir_size(path)) - - def get_size_format(self, b): - factor = 1000 - suffix = "B" - for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: - if b < factor: - return f"{b:.1f}{unit}{suffix}" - b /= factor - return f"{b:.1f}{suffix}" - - def get_dir_size(self, directory): - """Returns the `directory` size in bytes.""" - total = 0 - try: - # print("[+] Getting the size of", directory) - for entry in os.scandir(directory): - if entry.is_symlink(): - continue # Skip symlinks - if entry.is_file(): - # if it's a file, use stat() function - total += entry.stat().st_size - elif entry.is_dir(): - # if it's a directory, recursively call this function - try: - total += self.get_dir_size(entry.path) - except FileNotFoundError: - pass - except NotADirectoryError: - # if `directory` isn't a directory, get the file size then - return os.path.getsize(directory) - except PermissionError: - # if for whatever reason we can't open the folder, return 0 - return 0 - if total == 0: - return 0 - # Adding 4000 seems to make it more accurate to whatever data we can't scan from within the sandbox - return total + 4000 - - def find_app_icon(self, app_id): - icon_theme = Gtk.IconTheme.new() - icon_theme.add_search_path("/var/lib/flatpak/exports/share/icons/") - icon_theme.add_search_path( - self.host_home + "/.local/share/flatpak/exports/share/icons" - ) - - try: - icon_path = ( - icon_theme.lookup_icon( - app_id, None, 512, 1, self.parent_window.get_direction(), 0 - ) - .get_file() - .get_path() - ) - except GLib.GError: - icon_path = None - if icon_path: - image = Gtk.Image.new_from_file(icon_path) - image.set_icon_size(Gtk.IconSize.LARGE) - image.add_css_class("icon-dropshadow") - else: - image = Gtk.Image.new_from_icon_name("application-x-executable-symbolic") - image.set_icon_size(Gtk.IconSize.LARGE) - return image - - def get_host_updates(self): - list = [] - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "update"], - 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) - - for i in range(len(data)): - if data[i][0].find(".") == 2: - list.append(data[i][2]) - - return list - - def get_host_system_pins(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "pin"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - data = output.strip().split("\n") - for i in range(len(data)): - data[i] = data[i].strip() - return data - - def get_host_user_pins(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "pin", "--user"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - data = output.strip().split("\n") - for i in range(len(data)): - data[i] = data[i].strip() - return data - - def get_host_remotes(self): - output = subprocess.run( - [ - "flatpak-spawn", - "--host", - "flatpak", - "remotes", - "--columns=all", - "--show-disabled", - ], - 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) - return data - - def get_host_flatpaks(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "list", "--columns=all"], - 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) - - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "list", "--columns=runtime"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.split("\n") - for i in range(len(data)): - data[i].append(lines[i]) - sorted_array = sorted(data, key=lambda item: item[0].lower()) - return sorted_array - - def get_flatpak_info(self, ref, install_type): - output = subprocess.run( - [ - "flatpak-spawn", "--host", "sh", "-c", - f"flatpak info {ref} --{install_type}" - ], - 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(": ", 1) - for i in range(len(row)): - row[i] = row[i].strip() - data.append(row) - info = {} - info["name"] = data[0][0] - for i in range(2, len(data)): - if data[i][0] == '': - continue - info[data[i][0]] = data[i][1] - return info - - def get_dependent_runtimes(self): - paks = self.get_host_flatpaks() - dependent_runtimes = [] - for i in range(len(paks)): - current = paks[i] - try: - if current[13] not in dependent_runtimes and current[13] != "": - dependent_runtimes.append(current[13]) - except: - print("Could not get dependent runtime") - return sorted(dependent_runtimes) - - def get_host_masks(self, user_or_system): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "mask", f"--{user_or_system}"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.strip().split("\n") - for i in range(len(lines)): - lines[i] = lines[i].strip() - return lines - - def mask_flatpak(self, app_id, user_or_system, remove=False): - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "mask", - f"--{user_or_system}", - app_id, - ] - if remove: - command.append("--remove") - response = "" - try: - response = subprocess.run( - command, capture_output=True, text=True, env=self.new_env - ) - except subprocess.CalledProcessError as e: - print(f"Error setting mask for {app_id}:\n", e) - return 1 - if len(response.stderr) > 0: - return 1 - return 0 - - def downgrade_flatpak(self, id, ref, commit, install_type="system", mask=False, mask_list=None): - unmask_cmd = f"flatpak mask --remove --{install_type} {id} && " - update_cmd = f"flatpak updated {ref} --commit={commit} --{install_type} -y" - to_run_cmd = "" - if id in mask_list: - to_run_cmd += unmask_cmd - to_run_cmd += update_cmd - if mask: - to_run_cmd += f" && flatpak mask --{install_type} {id}" - command = [ - "flatpak-spawn", - "--host", "pkexec", - "sh", "-c", - to_run_cmd, - ] - if install_type == "user": - command.remove("pkexec") - try: - subprocess.run( - command, capture_output=True, text=True, env=self.new_env, check=True - ) - except subprocess.CalledProcessError as e: - print("Error in common.downgrade_flatpak:", e.stderr) - return 1 - return 0 - - def uninstall_flatpak( - self, ref_arr, type_arr, should_trash, progress_bar=None, status_label=None - ): - self.uninstall_success = True - print(ref_arr) - to_uninstall = [] - for i in range(len(ref_arr)): - to_uninstall.append([ref_arr[i], type_arr[i]]) - - apps = [] - fails = [] - for i in range(len(to_uninstall)): - ref = to_uninstall[i][0] - id = to_uninstall[i][0].split("/")[0] - app_type = to_uninstall[i][1] - apps.append([ref, id, app_type]) - # apps array guide: [app_ref, app_id, user_or_system_install] - - for i in range(len(apps)): - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remove", - "-y", - f"--{apps[i][2]}", - apps[i][0], - ] - try: - print(apps) - if status_label: - GLib.idle_add( - status_label.set_label, - _("Working on {}\n{} out of {}").format( - apps[i][0], i + 1, len(apps) - ), - ) - subprocess.run( - command, capture_output=False, check=True, env=self.new_env - ) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, (i + 1.0) / len(ref_arr)) - except subprocess.CalledProcessError: - fails.append(apps[i]) - - if len(fails) > 0: # Run this only if there is 1 or more non uninstalled apps - pk_command = [ - "flatpak-spawn", - "--host", - "pkexec", - "flatpak", - "remove", - "-y", - "--system", - ] - print("second uninstall process") - for i in range(len(fails)): - if fails[i][2] == "user": - self.uninstall_success = False - continue # Skip if app is a user install app - - pk_command.append(fails[i][0]) - try: - print(pk_command) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, 0.9) - subprocess.run( - pk_command, capture_output=False, check=True, env=self.new_env - ) - except subprocess.CalledProcessError: - self.uninstall_success = False - - if should_trash: - host_paks = self.get_host_flatpaks() - host_refs = [] - for i in range(len(host_paks)): - host_refs.append(host_paks[i][8]) - - for i in range(len(apps)): - if apps[i][0] in host_refs: - print(f"{apps[i][1]} is still installed") - else: - self.trash_folder(f"{self.user_data_path}{apps[i][1]}") - - if progress_bar: - GLib.idle_add(progress_bar.set_visible, False) - GLib.idle_add(progress_bar.set_fraction, 0.0) - - def install_flatpak( - self, app_arr, remote, user_or_system, progress_bar=None, status_label=None - ): - self.install_success = True - fails = [] - - for i in range(len(app_arr)): - command = ["flatpak-spawn", "--host", "flatpak", "install"] - if remote != None: - command.append(remote) - command.append(f"--{user_or_system}") - command.append("-y") - command.append(app_arr[i]) - try: - if status_label: - GLib.idle_add( - status_label.set_label, - _("Working on {}\n{} out of {}").format( - app_arr[i], i + 1, len(app_arr) - ), - ) - subprocess.run( - command, capture_output=False, check=True, env=self.new_env - ) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, (i + 1.0) / len(app_arr)) - except subprocess.CalledProcessError: - fails.append(app_arr[i]) - - if (len(fails) > 0) and (user_or_system == "system"): - pk_command = [ - "flatpak-spawn", - "--host", - "pkexec", - "flatpak", - "install", - remote, - f"--{user_or_system}", - "-y", - ] - for i in range(len(fails)): - pk_command.append(fails[i]) - try: - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, 0.9) - subprocess.run( - pk_command, capture_output=False, check=True, env=self.new_env - ) - except subprocess.CalledProcessError: - self.install_success = False - - if (len(fails) > 0) and (user_or_system == "user"): - self.install_success = False - - if progress_bar: - GLib.idle_add(progress_bar.set_visible, False) - GLib.idle_add(progress_bar.set_fraction, 0.0) - - def run_app(self, ref): - self.run_app_error = False - self.run_app_error_message = "" - try: - subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "run", ref], - check=True, - env=self.new_env, - start_new_session=True, - capture_output=True, - ) - except subprocess.CalledProcessError as e: - self.run_app_error_message = e.stderr.decode() - self.run_app_error = True - - def get_install_type(self, type_arr): - if "disabled" in type_arr: - return "disabled" - if "user" in type_arr: - return "user" - if "system" in type_arr: - return "system" - - def snapshot_apps( - self, - epoch, - app_snapshot_path_arr, - app_version_arr, - app_user_data_arr, - progress_bar=None, - ): - if not ( - len(app_snapshot_path_arr) == len(app_version_arr) == len(app_user_data_arr) - ): - print( - "error in common.snapshotApp: the lengths of app_snapshot_path_arr, app_version_arr, and app_user_data_arr do not match." - ) - return 1 - - fails = [] - - for i in range(len(app_snapshot_path_arr)): - snapshot_path = app_snapshot_path_arr[i] - version = app_version_arr[i] - user_data = app_user_data_arr[i] - command = [ - "tar", - "cafv", - f"{snapshot_path}{epoch}_{version}.tar.zst", - "-C", - f"{user_data}", - ".", - ] - - try: - if not os.path.exists(snapshot_path): - file = Gio.File.new_for_path(snapshot_path) - file.make_directory() - subprocess.run(command, check=True, env=self.new_env) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add( - progress_bar.set_fraction, - (i + 1.0) / len(app_snapshot_path_arr), - ) - except subprocess.CalledProcessError as e: - print("error in common.snapshotApp:", e) - fails.append(user_data) - - if ( - int(time.time()) == epoch - ): # Wait 1s if the snapshot is made too quickly, to prevent overriding a snapshot file - subprocess.run(["sleep", "1s"]) - - if progress_bar: - GLib.idle_add(progress_bar.set_visible, False) - GLib.idle_add(progress_bar.set_fraction, 0.0) - - if len(fails) > 0: - print("These paths could not be archived:") - for i in range(fails): - print(fails[i]) - print("") - return 1 - else: - return 0 diff --git a/src/downgrade_window.py b/src/downgrade_window.py deleted file mode 100644 index b085cd0..0000000 --- a/src/downgrade_window.py +++ /dev/null @@ -1,168 +0,0 @@ -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/../data/ui/downgrade.ui") -class DowngradeWindow(Adw.Dialog): - __gtype_name__ = "DowngradeWindow" - - new_env = dict(os.environ) - new_env["LC_ALL"] = "C" - - apply_button = Gtk.Template.Child() - versions_group = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - mask_row = Gtk.Template.Child() - main_toolbar_view = Gtk.Template.Child() - loading = Gtk.Template.Child() - loading_label = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - outerbox = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - - def selection_handler(self, button, index): - self.action_bar.set_revealed(True) - if button.get_active(): - self.commit_to_use = self.versions[index][0] - - def get_commits(self): - output = subprocess.run( - [ - "flatpak-spawn", "--host", "sh", "-c", - f"LC_ALL=C flatpak remote-info --log {self.remote} {self.app_ref} --{self.install_type}" - ], - 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[0].strip()) - - commits = [] - changes = [] - dates = [] - for i in range(len(data)): - line = data[i] - - if "Commit:" in line: - commits.append(line.replace("Commit: ", "")) - - if "Subject:" in line: - changes.append(line.replace("Subject: ", "")) - - if "Date:" in line: - dates.append(line.replace("Date: ", "")) - - for i in range(len(commits)): - self.versions.append([commits[i], changes[i], dates[i]]) - - def commits_callback(self): - group_button = Gtk.CheckButton(visible=False) - self.versions_group.add(group_button) - for i in range(len(self.versions)): - version = self.versions[i] - date_time = version[2].split(" ") - date = date_time[0].split("-") - offset = date_time[2][:3] + ":" + date_time[2][3:] - time = date_time[1].split(":") - display_time = GLib.DateTime.new( - GLib.TimeZone.new(offset), - int(date[0]), - int(date[1]), - int(date[2]), - int(time[0]), - int(time[1]), - int(time[2]), - ) - display_time = display_time.format("%x %X") - change = version[1].split("(") - row = Adw.ActionRow( - title=GLib.markup_escape_text(change[0]), subtitle=str(display_time) - ) - row.set_tooltip_text(_("Commit Hash: {}").format(version[0])) - select = Gtk.CheckButton() - select.connect("toggled", self.selection_handler, i) - select.set_group(group_button) - - version.append(select) - row.set_activatable_widget(select) - row.add_prefix(select) - self.versions_group.add(row) - self.main_stack.set_visible_child(self.outerbox) - self.apply_button.set_visible(True) - self.mask_row.grab_focus() # Don't know why, but I need this in order for escape to close the window - - def generate_list(self): - task = Gio.Task.new(None, None, lambda *_: self.commits_callback()) - task.run_in_thread(lambda *_: self.get_commits()) - - def downgrade_callack(self): - self.set_can_close(True) - - if self.response != 0: - self.parent_window.toast_overlay.add_toast( - Adw.Toast.new(_("Could not downgrade {}").format(self.app_name)) - ) - self.apply_button.set_sensitive(True) - - self.parent_window.refresh_list_of_flatpaks(self) - self.close() - - def downgrade_thread(self): - mask_list = None - if self.install_type == "system": - mask_list = self.parent_window.system_mask_list - if self.install_type == "user": - mask_list = self.parent_window.user_mask_list - self.response = self.my_utils.downgrade_flatpak( - self.app_id, self.app_ref, self.commit_to_use, self.install_type, self.mask_row.get_active(), mask_list - ) - - def on_apply(self): - self.loading_label.set_label(_("Downgrading…")) - self.set_can_close(False) - self.main_stack.set_visible_child(self.loading) - self.apply_button.set_visible(False) - - task = Gio.Task.new(None, None, lambda *_: self.downgrade_callack()) - task.run_in_thread(lambda *_: self.downgrade_thread()) - - def __init__(self, parent_window, flatpak_row, index, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.my_utils = myUtils(self) - self.app_name = flatpak_row[0] - self.app_id = flatpak_row[2] - self.remote = flatpak_row[6] - self.install_type = flatpak_row[7] - self.app_ref = flatpak_row[8] - self.versions = [] - self.commit_to_use = "" - self.parent_window = parent_window - self.flatpak_row = flatpak_row - self.response = 0 - self.window_title = _("Downgrade {}").format(self.app_name) - self.index = index - - # Connections - self.apply_button.connect("clicked", lambda *_: self.on_apply()) - - # Apply - self.mask_row.set_subtitle( - _("Ensure that {} will never be updated to a newer version").format( - self.app_name - ) - ) - - self.set_title(self.window_title) - - self.generate_list() - - self.present(parent_window) diff --git a/src/filter_window.py b/src/filter_window.py deleted file mode 100644 index 1af34eb..0000000 --- a/src/filter_window.py +++ /dev/null @@ -1,226 +0,0 @@ -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/../data/ui/filter.ui") -class FilterWindow(Adw.Dialog): - __gtype_name__ = "FilterWindow" - - show_apps_switch = Gtk.Template.Child() - show_runtimes_switch = Gtk.Template.Child() - remotes_expander = Gtk.Template.Child() - runtimes_expander = Gtk.Template.Child() - reset_button = Gtk.Template.Child() - - is_open = False - - def gsettings_bool_set(self, key, value): - self.settings.set_boolean(key, value) - self.check_is_resetable() - self.main_window.apply_filter() - - def check_is_resetable(self): - if not self.show_apps_switch.get_active(): - self.reset_button.set_sensitive(True) - return - if self.show_runtimes_switch.get_active(): - self.reset_button.set_sensitive(True) - return - if self.total_remotes_selected != 0: - self.reset_button.set_sensitive(True) - return - if self.total_runtimes_selected != 0: - self.reset_button.set_sensitive(True) - return - self.reset_button.set_sensitive(False) - - def row_subtitle_updater(self): - if self.total_runtimes_selected > 0: - self.runtimes_expander.set_subtitle( - _("{} selected").format(self.total_runtimes_selected) - ) - else: - self.runtimes_expander.set_subtitle("") - if self.total_remotes_selected > 0: - self.remotes_expander.set_subtitle( - _("{} selected").format(self.total_remotes_selected) - ) - else: - self.remotes_expander.set_subtitle("") - - def reset_filter_gsettings(self): - self.show_apps_switch.set_active(True) - self.show_runtimes_switch.set_active(False) - for button in self.remote_checkboxes: - button.set_active(False) - for button in self.runtime_checkboxes: - button.set_active(False) - for key in self.settings.list_keys(): - self.settings.reset(key) - self.total_remotes_selected = 0 - self.total_runtimes_selected = 0 - self.row_subtitle_updater() - self.reset_button.set_sensitive(False) - - def runtime_handler(self, button, runtime): - if button.get_active(): - self.total_runtimes_selected += 1 - self.runtimes_string = self.runtimes_string.replace("all", "") - self.runtimes_string += f"{runtime}," - else: - self.total_runtimes_selected -= 1 - self.runtimes_string = self.runtimes_string.replace(f"{runtime},", "") - if len(self.runtimes_string) < 1: - self.runtimes_string += "all" - self.settings.set_string("runtimes-list", self.runtimes_string) - self.check_is_resetable() - self.row_subtitle_updater() - self.main_window.apply_filter() - - def remote_handler(self, button, remote, install_type): - if button.get_active(): - self.total_remotes_selected += 1 - self.remotes_string = self.remotes_string.replace("all", "") - self.remotes_string += f"{remote}<>{install_type};" - else: - self.total_remotes_selected -= 1 - self.remotes_string = self.remotes_string.replace( - f"{remote}<>{install_type};", "" - ) - if len(self.remotes_string) < 1: - self.remotes_string += "all" - self.settings.set_string("remotes-list", self.remotes_string) - self.check_is_resetable() - self.row_subtitle_updater() - self.main_window.apply_filter() - - def generate_remotes(self): - if ( - len(self.host_remotes) < 2 - ): # Don't give the ability to filter by remotes if there is only 1 - self.remotes_expander.set_visible(False) - - total = 0 - for i in range(len(self.host_remotes)): - try: - name = self.host_remotes[i][0] - title = self.host_remotes[i][1] - url = self.host_remotes[i][2] - install_type = self.my_utils.get_install_type(self.host_remotes[i][7]) - remote_row = Adw.ActionRow(title=title) - if "disabled" in install_type: - continue - total += 1 - if title == "-": - remote_row.set_title(name) - self.remotes_expander.add_row(remote_row) - label = Gtk.Label(label=("{} wide").format(install_type)) - label.add_css_class("subtitle") - remote_check = Gtk.CheckButton() - if name in self.remotes_string: - remote_check.set_active(True) - self.total_remotes_selected += 1 - remote_check.connect( - "toggled", - lambda button=remote_check, remote=name, install_type=install_type: self.remote_handler( - button, remote, install_type - ), - ) - self.remote_checkboxes.append(remote_check) - - if "user" in install_type: - remote_row.set_subtitle(_("User wide")) - elif "system" in install_type: - remote_row.set_subtitle(_("System wide")) - else: - remote_row.set_subtitle(_("Unknown install type")) - - remote_row.add_suffix(remote_check) - remote_row.set_activatable_widget(remote_check) - except Exception as e: - print( - "error at filter_window.generate_remotes: Could not make remote row. error", - e, - ) - - self.row_subtitle_updater() - if total < 2: - self.remotes_expander.set_visible(False) - - def generate_runtimes(self): - if ( - len(self.dependent_runtimes) < 2 - ): # Don't give the ability to filter by runtimes if there is only 1 - self.runtimes_expander.set_visible(False) - - for current in self.dependent_runtimes: - runtime_row = Adw.ActionRow(title=current) - runtime_check = Gtk.CheckButton() - if current in self.runtimes_string: - runtime_check.set_active(True) - self.total_runtimes_selected += 1 - runtime_check.connect( - "toggled", - lambda button=runtime_check, runtime=current: self.runtime_handler( - button, runtime - ), - ) - self.runtime_checkboxes.append(runtime_check) - runtime_row.add_suffix(runtime_check) - runtime_row.set_activatable_widget(runtime_check) - self.runtimes_expander.add_row(runtime_row) - self.row_subtitle_updater() - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.main_window = main_window - self.my_utils = myUtils(self) - self.host_remotes = self.my_utils.get_host_remotes() - self.dependent_runtimes = self.my_utils.get_dependent_runtimes() - self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - self.remotes_string = self.settings.get_string("remotes-list") - self.runtimes_string = self.settings.get_string("runtimes-list") - self.remote_checkboxes = [] - self.runtime_checkboxes = [] - self.total_remotes_selected = 0 - self.total_runtimes_selected = 0 - - self.show_apps_switch.set_active(self.settings.get_boolean("show-apps")) - self.show_runtimes_switch.set_active(self.settings.get_boolean("show-runtimes")) - - # Connections - self.show_apps_switch.connect( - "state-set", - lambda button, state: self.gsettings_bool_set("show-apps", state), - ) - self.show_runtimes_switch.connect( - "state-set", - lambda button, state: self.gsettings_bool_set("show-runtimes", state), - ) - self.reset_button.connect("clicked", lambda *_: self.reset_filter_gsettings()) - - # Calls - if self.host_remotes[0][0] == "": - self.remotes_expander.set_visible(False) - else: - self.generate_remotes() - - if self.dependent_runtimes == []: - self.runtimes_expander.set_visible(False) - else: - self.generate_runtimes() - self.check_is_resetable() - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True diff --git a/src/main.py b/src/main.py index 1b3553e..85ec197 100644 --- a/src/main.py +++ b/src/main.py @@ -27,13 +27,7 @@ gi.require_version("Adw", "1") from gi.repository import Gtk, Gio, Adw, GLib from .window import WarehouseWindow -from .remotes_window import RemotesWindow -from .orphans_window import OrphansWindow -from .filter_window import FilterWindow -from .search_install_window import SearchInstallWindow from .const import Config -from .common import myUtils - class WarehouseApplication(Adw.Application): """The main application singleton class.""" @@ -104,14 +98,6 @@ class WarehouseApplication(Adw.Application): lang=lang, ) - self.my_utils = myUtils(self) - total = 0 - for rem in self.my_utils.get_host_remotes(): - if self.my_utils.get_install_type(rem[7]) != "disabled": - total += 1 - if total < 1: - self.lookup_action(f"open-search-install").set_enabled(False) - def open_search_install(self, widget, _): SearchInstallWindow(self.props.active_window) diff --git a/src/main_window/window.blp b/src/main_window/window.blp new file mode 100644 index 0000000..2fa24bd --- /dev/null +++ b/src/main_window/window.blp @@ -0,0 +1,73 @@ +using Gtk 4.0; +using Adw 1; + +template $WarehouseWindow: Adw.ApplicationWindow { + title: "Warehouse"; + content: + Adw.OverlaySplitView { + sidebar: + Adw.ToolbarView main_toolbar_view { + [top] + Adw.HeaderBar header_bar { + [end] + MenuButton main_menu { + icon-name: "open-menu-symbolic"; + tooltip-text: _("Main Menu"); + menu-model: primary_menu; + } + } + } + ; + content: + Adw.ToolbarView { + [top] + Adw.HeaderBar {} + } + ; + } + ; +} + +menu primary_menu { + section { + item { + label: _("Manage Leftover Data…"); + action: "app.manage-data-folders"; + } + + /*item { + label: _("_Preferences"); + action: "app.preferences"; + }*/ + item { + label: _("Manage Remotes…"); + action: "app.show-remotes-window"; + } + } + section { + item { + label: _("Install From File…"); + action: "app.install-from-file"; + } + + item { + label: _("Install From The Web…"); + action: "app.open-search-install"; + } + } + section { + item { + label: _("Refresh List"); + action: "app.refresh-list"; + } + item { + label: _("_Keyboard Shortcuts"); + action: "win.show-help-overlay"; + } + + item { + label: _("_About Warehouse"); + action: "app.about"; + } + } +} \ No newline at end of file diff --git a/src/main_window/window.py b/src/main_window/window.py new file mode 100644 index 0000000..19a69cd --- /dev/null +++ b/src/main_window/window.py @@ -0,0 +1,64 @@ +# window.py +# +# Copyright 2023 Heliguy +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-only + +import os +import pathlib +import subprocess +import re +import time + +from gi.repository import Adw, Gdk, Gio, GLib, Gtk +from .const import Config + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui") +class WarehouseWindow(Adw.ApplicationWindow): + __gtype_name__ = "WarehouseWindow" + + def key_handler(self, controller, keyval, keycode, state): + if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK: + self.close() + if keyval == Gdk.KEY_Escape: + self.batch_mode_button.set_active(False) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.set_size_request(360, 360) + self.settings = Gio.Settings.new("io.github.flattool.Warehouse") + self.settings.bind( + "window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT + ) + self.settings.bind( + "window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT + ) + self.settings.bind( + "is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT + ) + self.settings.bind( + "is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT + ) + + event_controller = Gtk.EventControllerKey() + event_controller.connect("key-pressed", self.key_handler) + self.add_controller(event_controller) + + file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) + # file_drop.connect("drop", self.drop_callback) + # self.scrolled_window.add_controller(file_drop) + + if Config.DEVEL: + self.add_css_class("devel") diff --git a/src/meson.build b/src/meson.build index 8039d31..cbb0a39 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,18 +1,11 @@ pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) -moduledir = join_paths(pkgdatadir, 'flattool_gui') +moduledir = join_paths(pkgdatadir, 'Warehouse') gnome = import('gnome') blueprints = custom_target('blueprints', input: files( 'gtk/help-overlay.blp', - '../data/ui/window.blp', - '../data/ui/orphans.blp', - '../data/ui/filter.blp', - '../data/ui/remotes.blp', - '../data/ui/downgrade.blp', - '../data/ui/search_install.blp', - '../data/ui/snapshots.blp', - '../data/ui/properties.blp', + 'main_window/window.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -54,31 +47,8 @@ configure_file( warehouse_sources = [ '__init__.py', 'main.py', - 'common.py', - 'window.py', - 'app_row_widget.py', + 'main_window/window.py', '../data/style.css', - - 'properties_window.py', - '../data/ui/properties.blp', - - 'orphans_window.py', - '../data/ui/orphans.blp', - - 'remotes_window.py', - '../data/ui/remotes.blp', - - 'filter_window.py', - '../data/ui/filter.blp', - - 'downgrade_window.py', - '../data/ui/downgrade.blp', - - 'search_install_window.py', - '../data/ui/search_install.blp', - - 'snapshots_window.py', - '../data/ui/snapshots.blp', ] configure_file( diff --git a/src/orphans_window.py b/src/orphans_window.py deleted file mode 100644 index 89c407b..0000000 --- a/src/orphans_window.py +++ /dev/null @@ -1,353 +0,0 @@ -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/../data/ui/orphans.ui") -class OrphansWindow(Adw.Dialog): - __gtype_name__ = "OrphansWindow" - - list_of_data = Gtk.Template.Child() - install_button = Gtk.Template.Child() - trash_button = Gtk.Template.Child() - select_all_button = Gtk.Template.Child() - main_overlay = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - no_data = Gtk.Template.Child() - no_results = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - search_button = Gtk.Template.Child() - search_bar = Gtk.Template.Child() - search_entry = Gtk.Template.Child() - oepn_folder_button = Gtk.Template.Child() - installing = Gtk.Template.Child() - main_box = Gtk.Template.Child() - installing_status = Gtk.Template.Child() - - window_title = _("Manage Leftover Data") - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - should_select_all = False - selected_remote = "" - selected_remote_install_type = "" - is_result = False - is_installing = False - is_open = False - - def key_handler(self, controller, keyval, keycode, state): - if keyval == Gdk.KEY_Escape or ( - keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK - ): - self.close() - - def selection_handler(self, widget, dir_name): - if widget.get_active(): - self.selected_dirs.append(dir_name) - else: - self.selected_dirs.remove(dir_name) - - if len(self.selected_dirs) == 0: - self.set_title( - self.window_title - ) # Set the window title back to what it was when there are no selected dirs - else: - self.set_title( - ("{} selected").format(str(len(self.selected_dirs))) - ) # Set the window title to the amount of selected dirs - - if len(self.selected_dirs) == 0: - self.install_button.set_sensitive(False) - self.trash_button.set_sensitive(False) - else: - self.install_button.set_sensitive(True) - self.trash_button.set_sensitive(True) - - def select_all_handler(self, button): - for check in self.check_buttons: - check.set_active(button.get_active()) - - def install_callback(self, *_args): - self.is_installing = False - self.generate_list() - self.progress_bar.set_visible(False) - self.app_window.refresh_list_of_flatpaks(self) - self.set_can_close(True) # Make window able to close - self.search_button.set_sensitive(True) - if self.my_utils.install_success: - self.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully"))) - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not install some apps")) - ) - - def install_handler(self): - self.is_installing = True - self.main_stack.set_visible_child(self.installing) - self.search_button.set_sensitive(False) - self.set_title(self.window_title) - self.keep_checking = True - task = Gio.Task.new(None, None, self.install_callback) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable, id_list=self.selected_dirs, remote=self.selected_remote, app_type=self.selected_remote_type, progress_bar=self.progress_bar, status_label=self.installing_status: self.my_utils.install_flatpak( - id_list, remote, app_type, progress_bar, status_label - ) - ) - - def install_button_handler(self, button): - def remote_select_handler(button, index): - if not button.get_active(): - return - self.selected_remote = self.host_remotes[index][0] - self.selected_remote_type = self.my_utils.get_install_type( - self.host_remotes[index][7] - ) - - def on_response(dialog, response_id, _function): - if response_id == "cancel": - return - self.install_handler() - self.progress_bar.set_visible(True) - self.action_bar.set_visible(False) - self.set_can_close(False) # Make window unable to close - - dialog = Adw.AlertDialog.new( - _("Attempt to Install?"), - _("Warehouse will attempt to install apps matching the selected data."), - ) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Install")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - - height = 65 * len(self.host_remotes) - max = 400 - if height > max: - height = max - remotes_scroll = Gtk.ScrolledWindow(vexpand=True, min_content_height=height) - remote_list = Gtk.ListBox(selection_mode="none", valign="start") - remotes_scroll.set_child(remote_list) - remote_list.add_css_class("boxed-list") - - total_added = 0 - remote_select_buttons = [] - for i in range(len(self.host_remotes)): - title = self.host_remotes[i][1] - name = self.host_remotes[i][0] - type_arr = self.host_remotes[i][7] - if "disabled" in type_arr: - continue - remote_row = Adw.ActionRow(title=title) - remote_select = Gtk.CheckButton() - remote_select_buttons.append(remote_select) - remote_select.connect("toggled", remote_select_handler, i) - remote_row.set_activatable_widget(remote_select) - - type = self.my_utils.get_install_type(type_arr) - if type == "user": - remote_row.set_subtitle(_("User wide")) - elif type == "system": - remote_row.set_subtitle(_("System wide")) - else: - remote_row.set_subtitle(_("Unknown install type")) - - if remote_row.get_title() == "-": - remote_row.set_title(self.host_remotes[i][0]) - - if total_added > 0: - remote_select.set_group(remote_select_buttons[0]) - - remote_row.add_prefix(remote_select) - remote_list.append(remote_row) - total_added += 1 - - remote_select_buttons[0].set_active(True) - - if total_added > 1: - dialog.set_extra_child(remotes_scroll) - - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def trash_handler(self, button): - def on_response(dialog, response_id, _function): - if response_id == "cancel": - return - for i in range(len(self.selected_dirs)): - path = self.user_data_path + self.selected_dirs[i] - self.my_utils.trash_folder(path) - self.select_all_button.set_active(False) - self.generate_list() - - dialog = Adw.AlertDialog.new( - _("Trash folders?"), _("These folders will be sent to the trash.") - ) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Continue")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.present(self) - - def open_button_handler(self, _widget, path=user_data_path): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) - except GLib.GError: - selt.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def size_callback(self, row_index): - row = self.list_of_data.get_row_at_index(row_index) - row.set_subtitle(f"~{self.data_rows[row_index][1]}") - - def size_thread(self, index, path): - size = self.my_utils.get_size_with_format(path) - self.data_rows[index].append(size) - - # Create the list of folders in the window - def generate_list(self): - self.data_rows = [] - self.check_buttons = [] - self.host_flatpaks = self.my_utils.get_host_flatpaks() - - if self.host_flatpaks == [["", ""]]: - self.app_window.toast_overlay.add_toast( - Adw.Toast.new(_("Could not manage data")) - ) - self.this_just_crashes_the_window_so_it_doesnt_open() - return - - self.list_of_data.remove_all() - self.selected_dirs = [] - self.set_title(self.window_title) - dir_list = os.listdir(self.user_data_path) - - # This is a list that only holds IDs of install flatpaks - id_list = [] - for i in range(len(self.host_flatpaks)): - try: - id_list.append(self.host_flatpaks[i][2]) - except: - print("Could not get data") - - for i in range(len(dir_list)): - dir_name = dir_list[i] - - # Skip item if it has a matching flatpak - if dir_name in id_list: - continue - - # Create row element - dir_row = Adw.ActionRow(title=dir_name) - self.data_rows.append([dir_row]) - path = self.user_data_path + dir_name - index = len(self.data_rows) - 1 - task = Gio.Task.new( - None, None, lambda *_, index=index: self.size_callback(index) - ) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable, *_, index=index: self.size_thread( - index, path - ) - ) - - open_row_button = Gtk.Button( - icon_name="document-open-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("Open User Data Folder"), - ) - open_row_button.add_css_class("flat") - open_row_button.connect( - "clicked", self.open_button_handler, (self.user_data_path + dir_name) - ) - dir_row.add_suffix(open_row_button) - - select_button = Gtk.CheckButton(tooltip_text=_("Select")) - self.check_buttons.append(select_button) - select_button.add_css_class("selection-mode") - select_button.connect("toggled", self.selection_handler, dir_name) - dir_row.add_suffix(select_button) - dir_row.set_activatable_widget(select_button) - - # Add row to list - self.list_of_data.append(dir_row) - - if self.list_of_data.get_row_at_index(0) == None: - self.main_stack.set_visible_child(self.no_data) - self.action_bar.set_visible(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.action_bar.set_visible(True) - - def filter_func(self, row): - if self.search_entry.get_text().lower() in row.get_title().lower(): - self.is_result = True - return True - - def on_invalidate(self, row): - if self.is_installing: - return - if self.list_of_data.get_row_at_index(0) == None: - self.main_stack.set_visible_child(self.no_data) - self.action_bar.set_visible(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.action_bar.set_visible(True) - - self.is_result = False - self.list_of_data.invalidate_filter() - if self.is_result == False: - self.main_stack.set_visible_child(self.no_results) - self.action_bar.set_visible(False) - - def on_change(self, prop, prop2): - if self.is_installing: - return - if self.search_bar.get_search_mode() == False: - if self.list_of_data.get_row_at_index(0) == None: - self.main_stack.set_visible_child(self.no_data) - self.action_bar.set_visible(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.action_bar.set_visible(True) - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils( - self - ) # Access common utils and set the window to this window - self.host_remotes = self.my_utils.get_host_remotes() - self.host_flatpaks = self.my_utils.get_host_flatpaks() - - self.progress_bar = Gtk.ProgressBar(visible=False) - self.progress_bar.add_css_class("osd") - self.app_window = main_window - - self.generate_list() - - event_controller = Gtk.EventControllerKey() - event_controller.connect("key-pressed", self.key_handler) - self.add_controller(event_controller) - - self.install_button.connect("clicked", self.install_button_handler) - if self.host_remotes[0][0] == "": - self.install_button.set_visible(False) - self.trash_button.connect("clicked", self.trash_handler) - self.select_all_button.connect("toggled", self.select_all_handler) - self.main_overlay.add_overlay(self.progress_bar) - - self.list_of_data.set_filter_func(self.filter_func) - self.search_entry.connect("search-changed", self.on_invalidate) - self.search_bar.connect("notify", self.on_change) - self.search_bar.connect_entry(self.search_entry) - self.oepn_folder_button.connect("clicked", self.open_button_handler) - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True diff --git a/src/properties_window.py b/src/properties_window.py deleted file mode 100644 index f71000e..0000000 --- a/src/properties_window.py +++ /dev/null @@ -1,217 +0,0 @@ -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/../data/ui/properties.ui") -class PropertiesWindow(Adw.Dialog): - __gtype_name__ = "PropertiesWindow" - - new_env = dict(os.environ) - new_env["LC_ALL"] = "C" - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - - toast_overlay = Gtk.Template.Child() - upper = Gtk.Template.Child() - app_icon = Gtk.Template.Child() - data_row = Gtk.Template.Child() - open_data = Gtk.Template.Child() - trash_data = Gtk.Template.Child() - spinner = Gtk.Template.Child() - details = Gtk.Template.Child() - view_apps = Gtk.Template.Child() - runtime = Gtk.Template.Child() - runtime_properties = Gtk.Template.Child() - runtime_copy = Gtk.Template.Child() - lower = Gtk.Template.Child() - eol_app_banner = Gtk.Template.Child() - eol_runtime_banner = Gtk.Template.Child() - mask_banner = Gtk.Template.Child() - name = Gtk.Template.Child() - description = Gtk.Template.Child() - description_button = Gtk.Template.Child() - - def copy_item(self, to_copy, to_toast=None): - self.get_clipboard().set(to_copy) - if to_toast: - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied {}").format(to_toast))) - - def open_button_handler(self, widget): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{self.user_data_path}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def show_details(self, widget): - try: - Gio.AppInfo.launch_default_for_uri(f"appstream://{self.app_id}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not show details"))) - - def get_size_callback(self, *args): - self.open_data.set_visible(True) - self.open_data.connect("clicked", self.open_button_handler) - self.trash_data.set_visible(True) - self.data_row.set_title(_("User Data")) - self.data_row.set_subtitle(f"~{self.size}") - self.spinner.set_visible(False) - - def get_size_thread(self, *args): - self.size = self.my_utils.get_size_with_format(self.user_data_path) - - def generate_upper(self): - self.description_button.connect( - "clicked", - lambda *_a: self.copy_item( - self.description.get_label(), _("Description") - ) - ) - image = self.my_utils.find_app_icon(self.app_id) - self.runtime.set_subtitle(self.current_flatpak[13]) - if image.get_paintable() == None: - self.app_icon.set_from_icon_name(image.get_icon_name()) - else: - self.app_icon.set_from_paintable(image.get_paintable()) - - if os.path.exists(self.user_data_path): - task = Gio.Task.new(None, None, self.get_size_callback) - task.run_in_thread(self.get_size_thread) - else: - self.data_row.set_title("") - self.data_row.set_subtitle(_("No User Data")) - self.spinner.set_visible(False) - - if "runtime" in self.current_flatpak[12]: - # Pak is a runtime - self.runtime.set_visible(False) - if self.app_ref in self.parent_window.dependent_runtimes: - self.view_apps.set_visible(True) - - def generate_lower(self): - info = self.my_utils.get_flatpak_info(self.app_ref, self.install_type) - name_desc = info["name"].split(" - ") - self.name.set_label((name_desc[0])) - try: - self.description.set_label((name_desc[1])) - except: - pass - for key in info.keys(): - if key == "name": - continue - row = Adw.ActionRow( - title=GLib.markup_escape_text(key), - subtitle=GLib.markup_escape_text(info[key]), - activatable=True, - ) - row.add_suffix(Gtk.Image.new_from_icon_name("edit-copy-symbolic")) - row.add_css_class("property") - row.connect( - "activated", - lambda *_a, row=row: self.copy_item( - row.get_subtitle(), row.get_title() - ), - ) - self.lower.add(row) - - def view_apps_handler(self, widget): - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - for key in settings.list_keys(): - settings.reset(key) - settings.set_string("runtimes-list", f"{self.app_ref},") - self.parent_window.apply_filter() - self.close() - - def show_properties_handler(self): - runtime = self.current_flatpak[13] - for i in range(len(self.host_flatpaks)): - # open the properties when the flatpak matches the runtime *and* installation type - if runtime in self.host_flatpaks[i][8] and self.install_type in self.host_flatpaks[i][12]: - PropertiesWindow(i, self.host_flatpaks, self.parent_window) - self.close() - return - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not show properties"))) - - def trash_data_handler(self): - def on_response(_none, response, widget): - if response == "cancel": - return - - if self.my_utils.trash_folder(self.user_data_path) == 0: - self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data"))) - self.data_row.set_title(_("No User Data")) - self.data_row.set_subtitle("") - self.open_data.set_visible(False) - self.trash_data.set_visible(False) - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not trash user data")) - ) - - dialog = Adw.AlertDialog.new( - _("Send {}'s User Data to the Trash?").format(self.app_name) - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Trash Data")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def __init__(self, flatpak_index, host_flatpaks, parent_window, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils(self) - self.current_flatpak = host_flatpaks[flatpak_index] - self.parent_window = parent_window - self.host_flatpaks = host_flatpaks - self.flatpak_index = flatpak_index - - self.app_name = self.current_flatpak[0] - self.app_id = self.current_flatpak[2] - self.origin_remote = self.current_flatpak[6] - self.install_type = self.current_flatpak[7] - self.app_ref = self.current_flatpak[8] - self.user_data_path += self.app_id - - self.details.connect("activated", self.show_details) - self.runtime_copy.connect( - "clicked", - lambda *_: self.copy_item( - self.runtime.get_subtitle(), self.runtime.get_title() - ), - ) - self.runtime_properties.connect( - "clicked", lambda *_: self.show_properties_handler() - ) - self.view_apps.connect("activated", self.view_apps_handler) - self.trash_data.connect("clicked", lambda *_: self.trash_data_handler()) - - if "eol" in self.current_flatpak[12]: - self.eol_app_banner.set_revealed(True) - self.eol_app_banner.set_title( - _( - "{} has reached its End of Life and will not receive any security updates" - ).format(self.app_name) - ) - - if self.current_flatpak[13] in parent_window.eol_list: - self.eol_runtime_banner.set_revealed(True) - self.eol_runtime_banner.set_title( - _( - "{}'s runtime has reached its End of Life and will not receive any security updates" - ).format(self.app_name) - ) - - if self.app_id in self.my_utils.get_host_masks( - "system" - ) or self.app_id in self.my_utils.get_host_masks("user"): - self.mask_banner.set_revealed(True) - self.mask_banner.set_title( - _("{} is masked and will not be updated").format(self.app_name) - ) - - self.generate_upper() - self.generate_lower() - self.present(parent_window) diff --git a/src/remotes_window.py b/src/remotes_window.py deleted file mode 100644 index 0aa61c0..0000000 --- a/src/remotes_window.py +++ /dev/null @@ -1,710 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import re - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/remotes.ui") -class RemotesWindow(Adw.Dialog): - __gtype_name__ = "RemotesWindow" - - remotes_list = Gtk.Template.Child() - stack = Gtk.Template.Child() - main_group = Gtk.Template.Child() - no_remotes = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - popular_remotes_list = Gtk.Template.Child() - add_from_file = Gtk.Template.Child() - custom_remote = Gtk.Template.Child() - refresh = Gtk.Template.Child() - adding = Gtk.Template.Child() - show_disabled_button = Gtk.Template.Child() - show_disabled_button_button_content = Gtk.Template.Child() - show_disabled = False - - is_open = False - rows_in_list = [] - rows_in_popular_list = [] - - def make_toast(self, text): - self.toast_overlay.add_toast(Adw.Toast.new(text)) - - def get_host_flatpaks(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "list", "--columns=all"], - 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) - return data - - def remove_on_response(self, _dialog, response_id, _function, index): - if response_id == "cancel": - return - - name = self.host_remotes[index][0] - title = self.host_remotes[index][1] - install_type = self.host_remotes[index][7] - if "user" in install_type: - install_type = "user" - if "system" in install_type: - install_type = "system" - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-delete", - "--force", - name, - f"--{install_type}", - ] - try: - subprocess.run(command, capture_output=True, check=True, env=self.new_env) - except subprocess.CalledProcessError as e: - self.make_toast(_("Could not remove {}").format(title)) - print("error in remotes_window.remove_on_response: CalledProcessError:", e) - self.generate_list() - - def remove_handler(self, _widget, index, popoever): - popoever.popdown() - name = self.host_remotes[index][0] - title = self.host_remotes[index][1] - install_type = self.host_remotes[index][7] - - body_text = _("Any installed apps from {} will stop receiving updates").format( - name - ) - dialog = Adw.AlertDialog.new(_("Remove {}?").format(title), body_text) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Remove")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", self.remove_on_response, dialog.choose_finish, index) - dialog.present(self) - - def enable_handler(self, button, index): - name = self.host_remotes[index][0] - typeArr = self.host_remotes[index][7] - type = "" - if "system" in typeArr: - type = "system" - else: - type = "user" - - try: - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-modify", - name, - f"--{type}", - "--enable", - ] - subprocess.run(command, capture_output=False, check=True, env=self.new_env) - except subprocess.CalledProcessError as e: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not enable {}").format(name)) - ) - print( - f"error in remotes_window.enable_handler: could not enable remote {name}:", - e, - ) - - self.generate_list() - - def disable_handler(self, button, index, popoever): - def disable_response(_a, response, _b): - if response == "cancel": - return - try: - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-modify", - name, - f"--{type}", - "--disable", - ] - subprocess.run( - command, capture_output=False, check=True, env=self.new_env - ) - except subprocess.CalledProcessError as e: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not disable {}").format(name)) - ) - print( - f"error in remotes_window.enable_handler: could not disable remote {name}:", - e, - ) - - self.generate_list() - - name = self.host_remotes[index][0] - title = self.host_remotes[index][1] - typeArr = self.host_remotes[index][7] - type = "" - if "system" in typeArr: - type = "system" - else: - type = "user" - - popoever.popdown() - - body_text = _("Any installed apps from {} will stop receiving updates").format( - name - ) - dialog = Adw.AlertDialog.new(_("Disable {}?").format(title), body_text) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Disable")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", disable_response, dialog.choose_finish) - dialog.present(self) - - def view_paks(self, type, remote): - if "user" in type: - type = "user" - elif "system" in type: - type = "system" - else: - self.make_toast(_("Could not view apps").format(to_copy)) - print( - "error in remotes_window.view_apps(): remote installation type is not either system or user. type is:", - type, - ) - return - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - for key in settings.list_keys(): - settings.reset(key) - settings.set_string("remotes-list", f"{remote}<>{type};") - settings.set_boolean("show-runtimes", True) - self.main_window.apply_filter() - self.close() - - def generate_list(self): - if self.show_disabled_button.get_active(): - self.show_disabled_button_button_content.set_icon_name( - "eye-open-negative-filled-symbolic" - ) - else: - self.show_disabled_button_button_content.set_icon_name( - "eye-not-looking-symbolic" - ) - - self.host_remotes = self.my_utils.get_host_remotes() - self.host_flatpaks = self.get_host_flatpaks() - for i in range(len(self.rows_in_list)): - self.remotes_list.remove(self.rows_in_list[i]) - - self.rows_in_list = [] - - def rowCopyHandler(widget, to_copy): - self.main_window.clipboard.set(to_copy) - self.make_toast(_("Copied {}").format(to_copy)) - - self.no_remotes.set_visible(True) - - has_disabled = False - for i in range(len(self.host_remotes)): - try: - name = self.host_remotes[i][0] - title = self.host_remotes[i][1] - install_type = self.host_remotes[i][7] - remote_row = Adw.ActionRow(title=title) - - more = Gtk.MenuButton( - icon_name="view-more-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("View More"), - ) - more.add_css_class("flat") - options = Gtk.Popover() - options_box = Gtk.Box( - halign=Gtk.Align.CENTER, - valign=Gtk.Align.CENTER, - orientation=Gtk.Orientation.VERTICAL, - ) - - filter_button = Gtk.Button() - filter_button.set_child( - Adw.ButtonContent( - icon_name="funnel-symbolic", label=_("Set Filter") - ) - ) - filter_button.add_css_class("flat") - filter_button.connect( - "clicked", - lambda *_, i=i: self.view_paks( - self.host_remotes[i][7], self.host_remotes[i][0] - ), - ) - - enable_button = Gtk.Button(visible=False) - enable_button.set_child( - Adw.ButtonContent( - icon_name="eye-open-negative-filled-symbolic", label=_("Enable") - ) - ) - enable_button.add_css_class("flat") - enable_button.connect("clicked", self.enable_handler, i) - - disable_button = Gtk.Button() - disable_button.set_child( - Adw.ButtonContent( - icon_name="eye-not-looking-symbolic", label=_("Disable") - ) - ) - disable_button.add_css_class("flat") - disable_button.connect("clicked", self.disable_handler, i, options) - - remove_button = Gtk.Button() - remove_button.set_child( - Adw.ButtonContent( - icon_name="user-trash-symbolic", label=_("Remove") - ) - ) - remove_button.add_css_class("flat") - remove_button.connect("clicked", self.remove_handler, i, options) - - options_box.append(filter_button) - options_box.append(enable_button) - options_box.append(disable_button) - options_box.append(remove_button) - options.set_child(options_box) - more.set_popover(options) - - copy_button = Gtk.Button( - icon_name="edit-copy-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("Copy remote name"), - ) - copy_button.add_css_class("flat") - copy_button.connect("clicked", rowCopyHandler, name) - - remote_row.add_suffix(copy_button) - remote_row.add_suffix(more) - - install_type = self.my_utils.get_install_type(install_type) - if install_type == "disabled": - has_disabled = True - if not self.show_disabled_button.get_active(): - continue - - remote_row.set_subtitle(_("Disabled")) - enable_button.set_visible(True) - disable_button.set_visible(False) - remote_row.add_css_class("warning") - elif install_type == "user": - remote_row.set_subtitle(_("User wide")) - elif install_type == "system": - remote_row.set_subtitle(_("System wide")) - else: - remote_row.set_subtitle(_("Unknown install type")) - - url = self.host_remotes[i][2] - if title == "-": - remote_row.set_title(name) - self.remotes_list.add(remote_row) - # subprocess.run(['wget', f'{self.host_remotes[i][11]}']) Idea to display remote icons... Need internet connection. Not sure if that is worth it - self.rows_in_list.append(remote_row) - self.no_remotes.set_visible(False) - except Exception as e: - print( - "error in remotes_window.generate_list: could not add remote. error:", - e, - ) - self.show_disabled_button.set_visible(has_disabled) - - # Popular remotes - for i in range(len(self.rows_in_popular_list)): - self.popular_remotes_list.remove(self.rows_in_popular_list[i]) - - self.rows_in_popular_list = [] - - remotes = [ - # [Name to show in GUI, Name of remote for system, Link to repo to add, Description of remote] - [ - "AppCenter", - "appcenter", - "https://flatpak.elementary.io/repo.flatpakrepo", - _("The open source, pay-what-you-want app store from elementary"), - ], - [ - "Flathub", - "flathub", - "https://dl.flathub.org/repo/flathub.flatpakrepo", - _("Central repository of Flatpak applications"), - ], - [ - "Flathub beta", - "flathub-beta", - "https://flathub.org/beta-repo/flathub-beta.flatpakrepo", - _("Beta builds of Flatpak applications"), - ], - [ - "Fedora", - "fedora", - "oci+https://registry.fedoraproject.org", - _("Flatpaks packaged by Fedora Linux"), - ], - [ - "GNOME Nightly", - "gnome-nightly", - "https://nightly.gnome.org/gnome-nightly.flatpakrepo", - _("The latest beta GNOME Apps and Runtimes"), - ], - [ - "KDE Testing Applications", - "kdeapps", - "https://distribute.kde.org/kdeapps.flatpakrepo", - _("Beta KDE Apps and Runtimes"), - ], - [ - "WebKit Developer SDK", - "webkit-sdk", - "https://software.igalia.com/flatpak-refs/webkit-sdk.flatpakrepo", - _("Central repository of the WebKit Developer and Runtime SDK"), - ], - ] - - host_remotes = self.my_utils.get_host_remotes() - host_remotes_names = [] - - total_added = 0 - - for i in range(len(self.host_remotes)): - host_remotes_names.append(self.host_remotes[i][0]) - - for i in range(len(remotes)): - if remotes[i][1] in host_remotes_names: - continue - - total_added += 1 - row = Adw.ActionRow( - title=remotes[i][0], subtitle=(remotes[i][2]), activatable=True - ) - row.connect("activated", self.add_handler, remotes[i][1], remotes[i][2]) - row.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic")) - self.rows_in_popular_list.append(row) - self.popular_remotes_list.add(row) - - self.popular_remotes_list.set_visible(total_added > 0) - - def addRemoteCallback(self, _a, _b): - self.generate_list() - self.stack.set_visible_child(self.main_group) - - def addRemoteThread(self, command): - try: - subprocess.run(command, capture_output=True, check=True, env=self.new_env) - except subprocess.CalledProcessError as e: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not add {}").format(self.name_to_add)) - ) - print( - "error in remotes_window.addRemoteThread: could not add remote. error:", - e, - ) - - def on_add_response(self, _dialog, response_id, _function, row): - if response_id == "cancel": - self.should_pulse = False - return - - self.stack.set_visible_child(self.adding) - - install_type = "--user" - if not self.add_as_user: - install_type = "--system" - - self.name_to_add = self.name_to_add.strip() - self.url_to_add = self.url_to_add.strip() - - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-add", - "--if-not-exists", - self.name_to_add, - self.url_to_add, - install_type, - ] - task = Gio.Task.new(None, None, self.addRemoteCallback) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable: self.addRemoteThread(command) - ) - - def add_handler(self, row, name="", link=""): - dialog = Adw.AlertDialog.new(_("Add Flatpak Remote")) - 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) - - def name_update(widget): - is_enabled = True - self.name_to_add = widget.get_text() - name_pattern = re.compile(r"^[a-zA-Z0-9\-._]+$") - 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 - - confirm_enabler(is_enabled) - - def url_update(widget): - is_enabled = True - self.url_to_add = widget.get_text() - url_pattern = re.compile(r"^[a-zA-Z0-9\-._~:/?#[\]@!$&\'()*+,;=]+$") - if not url_pattern.match(self.url_to_add): - is_enabled = False - - if is_enabled: - widget.remove_css_class("error") - else: - widget.add_css_class("error") - - if len(self.url_to_add) == 0: - is_enabled = False - - confirm_enabler(is_enabled) - - def confirm_enabler(is_enabled): - if len(self.name_to_add) == 0 or len(self.url_to_add) == 0: - is_enabled = False - dialog.set_response_enabled("continue", is_enabled) - - def set_user(widget): - self.add_as_user = widget.get_active() - - self.name_to_add = "" - self.url_to_add = "" - self.add_as_user = True - - info_box = Gtk.Box(orientation="vertical") - entry_list = Gtk.ListBox(selection_mode="none", margin_bottom=12) - entry_list.add_css_class("boxed-list") - - name_entry = Adw.EntryRow(title=_("Name")) - name_entry.set_text(name) - name_entry.connect("changed", name_update) - - url_entry = Adw.EntryRow(title=_("URL")) - url_entry.set_text(link) - url_entry.connect("changed", url_update) - - entry_list.append(name_entry) - entry_list.append(url_entry) - info_box.append(entry_list) - - install_type_list = Gtk.ListBox(selection_mode="none") - install_type_list.add_css_class("boxed-list") - - user_row = Adw.ActionRow( - title=_("User"), subtitle=_("Remote will be available to only you") - ) - user_check = Gtk.CheckButton(active=True) - user_check.connect("toggled", set_user) - user_row.add_prefix(user_check) - user_row.set_activatable_widget(user_check) - - system_row = Adw.ActionRow( - title=_("System"), - subtitle=_("Remote will be available to every user on the system"), - ) - system_check = Gtk.CheckButton() - system_row.add_prefix(system_check) - system_check.set_group(user_check) - system_row.set_activatable_widget(system_check) - - install_type_list.append(user_row) - install_type_list.append(system_row) - - info_box.append(install_type_list) - - dialog.set_extra_child(info_box) - dialog.connect("response", self.on_add_response, dialog.choose_finish, row) - dialog.present(self) - - if name != "": - name_update(name_entry) - if link != "": - url_update(url_entry) - - def add_remote_file_thread(self, filepath, system_or_user, name): - try: - subprocess.run( - [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-add", - "--if-not-exists", - 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( - "error in remotes_window.addRemoteFromFileThread: could not add remote. error:", - e, - ) - - def add_remote_file(self, filepath): - def response(dialog, response, _a): - if response == "cancel": - self.should_pulse = False - return - - 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.add_remote_file_thread( - 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 - - name = filepath.split("/") - name = name[len(name) - 1] - - dialog = Adw.AlertDialog.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=_("Remote will be available to only you") - ) - system_row = Adw.ActionRow( - title=_("System"), - subtitle=_("Remote 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") - dialog.present(self) - - def file_callback(self, object, result): - try: - file = object.open_finish(result) - self.add_remote_file(file.get_path()) - except GLib.GError: - pass - - def add_file_handler(self): - filter = Gtk.FileFilter(name=_("Flatpak 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.main_window, None, self.file_callback) - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.my_utils = myUtils(self) - self.host_remotes = [] - self.host_flatpaks = [] - self.main_window = main_window - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - self.should_pulse = False - - self.refresh.connect("clicked", lambda *_: self.generate_list()) - - self.add_from_file.add_suffix( - Gtk.Image.new_from_icon_name("right-large-symbolic") - ) - self.add_from_file.connect("activated", lambda *_: self.add_file_handler()) - self.custom_remote.add_suffix( - Gtk.Image.new_from_icon_name("right-large-symbolic") - ) - self.custom_remote.connect("activated", self.add_handler) - self.show_disabled_button.connect("clicked", lambda *_: self.generate_list()) - - # Calls - self.generate_list() - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True \ No newline at end of file diff --git a/src/search_install_window.py b/src/search_install_window.py deleted file mode 100644 index 7fe9983..0000000 --- a/src/search_install_window.py +++ /dev/null @@ -1,266 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib - - -class RemoteRow(Adw.ActionRow): - def __init__(self, remote, **kwargs): - super().__init__(**kwargs) - my_utils = myUtils(self) - self.install_type = my_utils.get_install_type(remote[7]) - if self.install_type == "disabled": - self.set_visible(False) - return - self.set_activatable(True) - self.remote = remote - if remote[1] == "-": - self.set_title(remote[0]) - else: - self.set_title(remote[1]) - self.set_subtitle(_("{} wide").format(self.install_type)) - self.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic")) - - -class ResultRow(Adw.ActionRow): - def __init__(self, flatpak, **kwargs): - super().__init__(**kwargs) - my_utils = myUtils(self) - name = flatpak[0] - description = flatpak[1] - app_id = flatpak[2] - version = flatpak[3] - self.flatpak = flatpak - self.set_title(GLib.markup_escape_text(f"{name}")) - self.set_subtitle(GLib.markup_escape_text(f"{app_id}\n{description}")) - self.check = Gtk.CheckButton() - self.check.add_css_class("selection-mode") - self.add_suffix( - Gtk.Label( - label=GLib.markup_escape_text(version), - wrap=True, - hexpand=True, - justify=Gtk.Justification.RIGHT, - ) - ) - self.add_suffix(self.check) - self.set_activatable_widget(self.check) - - -@Gtk.Template( - resource_path="/io/github/flattool/Warehouse/../data/ui/search_install.ui" -) -class SearchInstallWindow(Adw.Dialog): - __gtype_name__ = "SearchInstallWindow" - - nav_view = Gtk.Template.Child() - search_page = Gtk.Template.Child() - results_page = Gtk.Template.Child() - remotes_list = Gtk.Template.Child() - search_entry = Gtk.Template.Child() - blank_page = Gtk.Template.Child() - inner_stack = Gtk.Template.Child() - outer_stack = Gtk.Template.Child() - loading_page = Gtk.Template.Child() - results_scroll = Gtk.Template.Child() - results_list = Gtk.Template.Child() - too_many = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - search_button = Gtk.Template.Child() - no_results = Gtk.Template.Child() - install_button = Gtk.Template.Child() - installing = Gtk.Template.Child() - installing_status = Gtk.Template.Child() - search_box = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - progress_bar = Gtk.Template.Child() - - is_open = False - - def reset(self): - self.results = [] - self.results_list.remove_all() - self.inner_stack.set_visible_child(self.blank_page) - - def check_handler(self, button, row): - if button.get_active(): - self.selected.append(row.flatpak) - else: - self.selected.remove(row.flatpak) - if len(self.selected) == 0: - self.set_title(self.title) - self.action_bar.set_revealed(False) - else: - self.set_title(_("{} Selected").format(len(self.selected))) - self.action_bar.set_revealed(True) - - def generate_remotes_list(self): - total = 0 - for rem in self.host_remotes: - if self.my_utils.get_install_type(rem[7]) != "disabled": - total += 1 - if total < 2: - self.nav_view.push(self.results_page) - self.results_page.set_can_pop(False) - - for rem in self.host_remotes: - if self.my_utils.get_install_type(rem[7]) != "disabled": - self.search_remote = rem[0] - self.install_type = self.my_utils.get_install_type(rem[7]) - break - - if self.host_remotes[0][1] == "-": - self.title = _("Search {}").format(self.host_remotes[0][0]) - else: - self.title = _("Search {}").format(self.host_remotes[0][1]) - - self.set_title(self.title) - self.search_entry.set_placeholder_text( - _("Search {}").format(self.search_remote) - ) - self.search_entry.grab_focus() - return - - self.nav_view.connect("popped", lambda *_: self.set_title("")) - for remote in self.host_remotes: - row = RemoteRow(remote) - row.connect("activated", self.remote_choice) - self.remotes_list.append(row) - - def generate_results_list(self): - for pak in self.results: - row = ResultRow(pak) - row.check.set_active(row.flatpak in self.selected) - row.check.connect("toggled", self.check_handler, row) - row.set_tooltip_text(row.flatpak[2]) - if self.search_remote in row.flatpak[5].split(","): - self.results_list.append(row) - if self.results_list.get_row_at_index(0): - self.inner_stack.set_visible_child(self.results_scroll) - else: - self.inner_stack.set_visible_child(self.no_results) - - def remote_choice(self, row): - self.reset() - self.selected = [] - self.install_type = row.install_type - self.search_remote = row.remote[0] - self.search_entry.set_placeholder_text( - _("Search {}").format(self.search_remote) - ) - self.title = _("Search {}").format(row.get_title()) - self.set_title(self.title) - self.nav_view.push(self.results_page) - self.search_entry.grab_focus() - self.action_bar.set_revealed(len(self.selected) > 0) - - def search_handler(self, *args): - self.cancel_search.cancel() - self.reset() - self.inner_stack.set_visible_child(self.loading_page) - query = self.search_entry.get_text().strip() - if query == "": - self.inner_stack.set_visible_child(self.blank_page) - return - - def search_thread(*args): - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "search", - "--columns=all", - query, - ] - output = ( - subprocess.run( - command, capture_output=True, text=True, env=self.new_env - ) - .stdout.strip() - .split("\n") - ) - for elm in output: - self.results.append(elm.split("\t")) - - def done(*args): - if len(self.results) > 50: - self.inner_stack.set_visible_child(self.too_many) - return - if ["No matches found"] in self.results: - self.inner_stack.set_visible_child(self.no_results) - return - self.generate_results_list() - - task = Gio.Task.new(None, self.cancel_search, done) - task.run_in_thread(search_thread) - - def install_handler(self, *args): - paks = [] - for pak in self.selected: - paks.append(pak[2]) - self.outer_stack.set_visible_child(self.installing) - self.set_title(_("Install From The Web")) - - def thread(*args): - self.my_utils.install_flatpak( - paks, - self.search_remote, - self.install_type, - self.progress_bar, - self.installing_status, - ) - - def done(*args): - self.main_window.refresh_list_of_flatpaks(None) - # Make window able to close - self.set_can_close(True) - if self.my_utils.install_success: - self.close() - self.main_window.toast_overlay.add_toast( - Adw.Toast.new(_("Installed successfully")) - ) - else: - self.progress_bar.set_visible(False) - self.nav_view.pop() - self.outer_stack.set_visible_child(self.nav_view) - self.toast_overlay.add_toast( - Adw.Toast.new(_("Some apps didn't install")) - ) - - # Make window unable to close - self.set_can_close(False) - task = Gio.Task.new(None, None, done) - task.run_in_thread(thread) - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.my_utils = myUtils(self) - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - self.host_remotes = self.my_utils.get_host_remotes() - self.main_window = main_window - self.results = [] - self.selected = [] - self.search_remote = "" - self.install_type = "" - self.title = _("Install From The Web") - - self.cancel_search = Gio.Cancellable() - self.search_entry.connect("activate", self.search_handler) - self.search_button.connect("clicked", self.search_handler) - self.install_button.connect("clicked", self.install_handler) - - # Apply Widgets - self.generate_remotes_list() - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True \ No newline at end of file diff --git a/src/snapshots_window.py b/src/snapshots_window.py deleted file mode 100644 index 62efcd3..0000000 --- a/src/snapshots_window.py +++ /dev/null @@ -1,281 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib -import time - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/snapshots.ui") -class SnapshotsWindow(Adw.Dialog): - __gtype_name__ = "SnapshotsWindow" - - new_env = dict(os.environ) - new_env["LC_ALL"] = "C" - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - snapshots_path = ( - host_home + "/.var/app/io.github.flattool.Warehouse/data/Snapshots/" - ) - - snapshots_group = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - no_snapshots = Gtk.Template.Child() - new_snapshot = Gtk.Template.Child() - open_folder_button = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - outerbox = Gtk.Template.Child() - loading = Gtk.Template.Child() - loading_label = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - - def show_list_or_empty(self): - # Make window able to close - self.set_can_close(True) - - self.action_bar.set_revealed(True) - if os.path.exists(self.snapshots_of_app_path): - if len(os.listdir(self.snapshots_of_app_path)) > 0: - self.main_stack.set_visible_child(self.outerbox) - return "list" - self.open_folder_button.set_sensitive(False) - self.main_stack.set_visible_child(self.no_snapshots) - return "empty" - - def generate_list(self): - if not os.path.exists(self.app_user_data): - self.new_snapshot.set_sensitive(False) - self.new_snapshot.set_tooltip_text(_("There is no User Data to Snapshot")) - - if self.show_list_or_empty() == "empty": - return - - snapshot_files = os.listdir(self.snapshots_of_app_path) - to_trash = [] - - for i in range(len(snapshot_files)): - if not snapshot_files[i].endswith(".tar.zst"): - # Find all files that aren't snapshots - to_trash.append(snapshot_files[i]) - - for i in range(len(to_trash)): - # Trash all files that aren't snapshots - a = self.my_utils.trash_folder(f"{self.snapshots_of_app_path}{to_trash[i]}") - if a == 0: - snapshot_files.remove(to_trash[i]) - - if len(snapshot_files) == 0: - self.main_stack.set_visible_child(self.no_snapshots) - return - - for i in range(len(snapshot_files)): - self.create_row(snapshot_files[i]) - - def create_row(self, file): - def size_thread(*args): - size = self.my_utils.get_size_with_format(self.snapshots_of_app_path + file) - GLib.idle_add(lambda *_a: row.set_subtitle(f"~{size}")) - - split_file = file.removesuffix(".tar.zst").split("_") - time = GLib.DateTime.new_from_unix_local(int(split_file[0])).format("%x %X") - row = Adw.ActionRow(title=time) - - task = Gio.Task() - task.run_in_thread(size_thread) - - label = Gtk.Label( - label=_("Version {}").format(split_file[1]), - hexpand=True, - wrap=True, - justify=Gtk.Justification.RIGHT, - ) - row.add_suffix(label) - - apply = Gtk.Button(icon_name="check-plain-symbolic", valign=Gtk.Align.CENTER) - apply.set_tooltip_text(_("Apply Snapshot")) - apply.connect("clicked", self.apply_snapshot, file, row) - apply.add_css_class("flat") - row.add_suffix(apply) - - trash = Gtk.Button(icon_name="user-trash-symbolic", valign=Gtk.Align.CENTER) - trash.set_tooltip_text(_("Trash Snapshot")) - trash.connect("clicked", self.trash_snapshot, file, row) - trash.add_css_class("flat") - row.add_suffix(trash) - self.snapshots_group.insert(row, 0) - self.main_stack.set_visible_child(self.outerbox) - self.open_folder_button.set_sensitive(True) - - def trash_snapshot(self, button, file, row): - def on_response(dialog, response, func): - if response == "cancel": - return - a = self.my_utils.trash_folder(self.snapshots_of_app_path + file) - if a == 0: - self.snapshots_group.remove(row) - if not self.snapshots_group.get_row_at_index(0): - self.my_utils.trash_folder(self.snapshots_of_app_path) - self.show_list_or_empty() - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not trash snapshot")) - ) - - dialog = Adw.AlertDialog.new( - _("Trash Snapshot?"), - _("This snapshot and its contents will be sent to the trash."), - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Trash Snapshot")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def create_snapshot(self): - epoch = int(time.time()) - - def thread(): - response = self.my_utils.snapshot_apps( - epoch, - [self.snapshots_of_app_path], - [self.app_version], - [self.app_user_data], - ) - if response != 0: - GLib.idle_add( - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not create snapshot")) - ) - ) - return - if self.show_list_or_empty() == "list": - self.create_row(f"{epoch}_{self.app_version}.tar.zst") - - # Make window unable to close - self.set_can_close(False) - self.loading_label.set_label(_("Creating Snapshot…")) - self.action_bar.set_revealed(False) - self.main_stack.set_visible_child(self.loading) - - task = Gio.Task() - task.run_in_thread(lambda *_: thread()) - - def apply_snapshot(self, button, file, row): - self.applied = False - - def thread(): - try: - subprocess.run( - [ - "tar", - "--zstd", - "-xvf", - f"{self.snapshots_of_app_path}{file}", - "-C", - f"{self.app_user_data}", - ], - check=True, - env=self.new_env, - ) - self.applied = True - except subprocess.CalledProcessError as e: - print( - "error in snapshots_window.apply_snapshot.thread: CalledProcessError:", - e, - ) - - def callback(): - if not self.applied: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not apply snapshot")) - ) - else: - self.toast_overlay.add_toast(Adw.Toast.new(_("Snapshot applied"))) - - self.new_snapshot.set_tooltip_text("") - self.show_list_or_empty() - - def on_response(dialog, response, func): - if response == "cancel": - return - to_apply = self.snapshots_of_app_path + file - to_trash = self.app_user_data - if os.path.exists(to_trash): - a = self.my_utils.trash_folder(to_trash) - if a != 0: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not apply snapshot")) - ) - return - data = Gio.File.new_for_path(self.app_user_data) - data.make_directory() - if not os.path.exists(data.get_path()): - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not apply snapshot")) - ) - return - - # Make window unable to close - self.set_can_close(False) - self.loading_label.set_label(_("Applying Snapshot…")) - self.action_bar.set_revealed(False) - self.main_stack.set_visible_child(self.loading) - - task = Gio.Task.new(None, None, lambda *_: callback()) - task.run_in_thread(lambda *_: thread()) - - dialog = Adw.AlertDialog.new( - _("Apply Snapshot?"), - _("Applying this snapshot will trash any current user data for {}.").format( - self.app_name - ), - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Apply Snapshot")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def open_button_handler(self, widget, path): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def __init__(self, parent_window, flatpak_row, **kwargs): - super().__init__(**kwargs) - - # Variables - self.my_utils = myUtils(self) - self.app_name = flatpak_row[0] - self.app_id = flatpak_row[2] - self.app_version = flatpak_row[3] - self.app_ref = flatpak_row[8] - self.snapshots_of_app_path = self.snapshots_path + self.app_id + "/" - self.app_user_data = self.user_data_path + self.app_id + "/" - self.parent_window = parent_window - - if ( - self.app_version == "" - or self.app_version == "-" - or self.app_version == None - ): - self.app_version = 0.0 - - if not os.path.exists(self.snapshots_path): - # Create snapshots folder if none exists - file = Gio.File.new_for_path(self.snapshots_path) - file.make_directory() - - # Calls - self.generate_list() - self.open_folder_button.connect( - "clicked", self.open_button_handler, self.snapshots_of_app_path - ) - self.new_snapshot.connect("clicked", lambda *_: self.create_snapshot()) - - # Window stuffs - self.set_title(_("{} Snapshots").format(self.app_name)) - self.present(parent_window) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index abbf591..403e1c6 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -1,15 +1,8 @@ - ../data/ui/window.ui - ../data/ui/orphans.ui - ../data/ui/filter.ui - ../data/ui/remotes.ui - ../data/ui/downgrade.ui - ../data/ui/search_install.ui - ../data/ui/snapshots.ui - ../data/ui/properties.ui ../data/style.css + main_window/window.ui gtk/help-overlay.ui diff --git a/src/warehouse.in b/src/warehouse.in index fdff899..7590638 100755 --- a/src/warehouse.in +++ b/src/warehouse.in @@ -42,5 +42,5 @@ if __name__ == '__main__': resource = Gio.Resource.load(os.path.join(pkgdatadir, 'warehouse.gresource')) resource._register() - from flattool_gui import main + from Warehouse import main sys.exit(main.main(VERSION)) diff --git a/src/window.py b/src/window.py deleted file mode 100644 index 6e54360..0000000 --- a/src/window.py +++ /dev/null @@ -1,970 +0,0 @@ -# window.py -# -# Copyright 2023 Heliguy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, version 3 of the License only. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# SPDX-License-Identifier: GPL-3.0-only - -import os -import pathlib -import subprocess -import re -import time - -from gi.repository import Adw, Gdk, Gio, GLib, Gtk -# from .properties_window import PropertiesWindow -from .filter_window import FilterWindow -from .common import myUtils -from .remotes_window import RemotesWindow -from .downgrade_window import DowngradeWindow -from .snapshots_window import SnapshotsWindow -from .const import Config - -from .app_row_widget import AppRow - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/window.ui") -class WarehouseWindow(Adw.ApplicationWindow): - __gtype_name__ = "WarehouseWindow" - main_window_title = "Warehouse" - flatpaks_list_box = Gtk.Template.Child() - search_entry = Gtk.Template.Child() - search_button = Gtk.Template.Child() - search_bar = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - no_flatpaks = Gtk.Template.Child() - no_results = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - batch_mode_button = Gtk.Template.Child() - batch_mode_bar = Gtk.Template.Child() - batch_select_all_button = Gtk.Template.Child() - batch_uninstall_button = Gtk.Template.Child() - batch_clean_button = Gtk.Template.Child() - batch_copy_button = Gtk.Template.Child() - batch_snapshot_button = Gtk.Template.Child() - main_box = Gtk.Template.Child() - main_overlay = Gtk.Template.Child() - main_toolbar_view = Gtk.Template.Child() - filter_button = Gtk.Template.Child() - scrolled_window = Gtk.Template.Child() - main_menu = Gtk.Template.Child() - installing = Gtk.Template.Child() - uninstalling = Gtk.Template.Child() - snapshotting = Gtk.Template.Child() - loading_flatpaks = Gtk.Template.Child() - no_matches = Gtk.Template.Child() - reset_filters_button = Gtk.Template.Child() - uninstalling_status = Gtk.Template.Child() - refreshing = Gtk.Template.Child() - - main_progress_bar = Gtk.ProgressBar(visible=False, can_target=False) - main_progress_bar.add_css_class("osd") - clipboard = Gdk.Display.get_default().get_clipboard() - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - in_batch_mode = False - should_select_all = False - host_flatpaks = None - install_success = True - no_close = None - re_get_flatpaks = False - currently_uninstalling = False - is_result = False - is_empty = False - total_selected = 0 - - def filter_func(self, row): - if (self.search_entry.get_text().lower() in row.get_title().lower()) or ( - self.search_entry.get_text().lower() in row.get_subtitle().lower() - ): - self.is_result = True - return True - - def uninstall_buttons_enable(self, should_enable): - if self.currently_uninstalling: - return - if not should_enable: - self.batch_uninstall_button.set_sensitive(False) - - def uninstall_flatpak_callback(self, _a, _b): - self.currently_uninstalling = False - self.refresh_list_of_flatpaks(_a) - self.main_toolbar_view.set_sensitive(True) - self.disconnect(self.no_close) - self.uninstall_buttons_enable(True) - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - self.batch_actions_enable(False) - if self.my_utils.uninstall_success: - self.refresh_list_of_flatpaks(self) - self.toast_overlay.add_toast(Adw.Toast.new(_("Uninstalled successfully"))) - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not uninstall some apps")) - ) - - def uninstall_flatpak_thread(self, ref_arr, id_arr, type_arr, should_trash): - self.my_utils.uninstall_flatpak( - ref_arr, - type_arr, - should_trash, - self.main_progress_bar, - self.uninstalling_status, - ) - - def uninstall_flatpak(self, should_trash): - ref_arr = [] - id_arr = [] - type_arr = [] - self.currently_uninstalling = True - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - if current.tickbox.get_active() == True: - ref_arr.append(current.app_ref) - id_arr.append(current.app_id) - type_arr.append(current.install_type) - i += 1 - self.set_title(self.main_window_title) - task = Gio.Task.new(None, None, self.uninstall_flatpak_callback) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable, ref_arr=ref_arr, id_arr=id_arr, type_arr=type_arr, should_trash=should_trash: self.uninstall_flatpak_thread( - ref_arr, id_arr, type_arr, should_trash - ) - ) - - def batch_uninstall_button_handler(self, _widget): - has_user_data = False - - def batch_uninstall_response(_idk, response_id, _widget): - if response_id == "cancel": - return 1 - - try: - should_trash = trash_check.get_active() - except: - should_trash = False - - self.uninstall_buttons_enable(False) - - self.no_close = self.connect( - "close-request", lambda event: True - ) # Make window unable to close - self.main_stack.set_visible_child(self.uninstalling) - self.search_button.set_sensitive(False) - self.uninstall_flatpak(should_trash) - - # Create Widgets - dialog = Adw.AlertDialog.new( - _("Uninstall Selected Apps?"), - _("It will not be possible to use these apps after removal."), - ) - - # Check to see if at least one app in the list has user data - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active() and os.path.exists( - f"{self.user_data_path}{current.app_id}" - ): - has_user_data = True - break - - if has_user_data: - # Create Widgets - options_box = Gtk.Box(orientation="vertical") - header = Gtk.Label( - label=_("App Settings & Data"), halign="start", margin_top=10 - ) - options_list = Gtk.ListBox(selection_mode="none", margin_top=15) - keep_data = Adw.ActionRow( - title=_("Keep"), - subtitle=_("Allow restoring these apps' settings and content"), - ) - trash_data = Adw.ActionRow( - title=_("Trash"), - subtitle=_("Send these apps' settings and content to the trash"), - ) - keep_check = Gtk.CheckButton() - trash_check = Gtk.CheckButton() - - # Apply Widgets - keep_data.add_prefix(keep_check) - keep_data.set_activatable_widget(keep_check) - trash_data.add_prefix(trash_check) - trash_data.set_activatable_widget(trash_check) - keep_check.set_group(trash_check) - options_list.append(keep_data) - options_list.append(trash_data) - options_box.append(header) - options_box.append(options_list) - dialog.set_extra_child(options_box) - - # Calls - keep_check.set_active(True) - options_list.add_css_class("boxed-list") - header.add_css_class("heading") - header.add_css_class("h4") - - # Connections - dialog.connect("response", batch_uninstall_response, dialog.choose_finish) - - # Calls - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Uninstall")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.present(self) - - def uninstall_button_handler(self, row, name, ref, id): - if self.currently_uninstalling: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Cannot uninstall while already uninstalling")) - ) - return - - def uninstall_response(_idk, response_id, _widget): - if response_id == "cancel": - return 1 - - try: - should_trash = trash_check.get_active() - except: - should_trash = False - - if response_id == "purge": - should_trash = True - - self.uninstall_buttons_enable(False) - - self.no_close = self.connect( - "close-request", lambda event: True - ) # Make window unable to close - self.main_stack.set_visible_child(self.uninstalling) - self.search_button.set_sensitive(False) - self.uninstall_flatpak(should_trash) - - row.tickbox.set_active(True) - - # Create Widgets - dialog = Adw.AlertDialog.new( - _("Uninstall {}?").format(name), - _("It will not be possible to use {} after removal.").format(name), - ) - - if os.path.exists(f"{self.user_data_path}{id}"): - # Create Widgets for Trash - options_box = Gtk.Box(orientation="vertical") - header = Gtk.Label( - label=_("App Settings & Data"), halign="start", margin_top=10 - ) - options_list = Gtk.ListBox(selection_mode="none", margin_top=15) - keep_data = Adw.ActionRow( - title=_("Keep"), - subtitle=_("Allow restoring this app's settings and content"), - ) - trash_data = Adw.ActionRow( - title=_("Trash"), - subtitle=_("Send this app's settings and content to the trash"), - ) - keep_check = Gtk.CheckButton(active=True) - trash_check = Gtk.CheckButton() - - # Apply Widgets for Trash - keep_data.add_prefix(keep_check) - keep_data.set_activatable_widget(keep_check) - trash_data.add_prefix(trash_check) - trash_data.set_activatable_widget(trash_check) - keep_check.set_group(trash_check) - dialog.set_extra_child(options_box) - options_list.append(keep_data) - options_list.append(trash_data) - options_box.append(header) - options_box.append(options_list) - options_list.add_css_class("boxed-list") - - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Uninstall")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", uninstall_response, dialog.choose_finish) - dialog.present(self) - - def window_set_empty(self, is_empty): - self.batch_mode_button.set_sensitive(not is_empty) - self.search_button.set_sensitive(not is_empty) - self.filter_button.set_sensitive(not is_empty) - self.is_empty = is_empty - - if is_empty: - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.no_flatpaks) - self.search_button.set_sensitive(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - - def create_row(self, index): - row = AppRow(self, self.host_flatpaks, index) - if row.app_id == "io.github.flattool.Warehouse": - row.tickbox.set_sensitive(False) - self.flatpaks_list_box.insert(row, index) - - def generate_list_of_flatpaks(self): - self.host_flatpaks = self.my_utils.get_host_flatpaks() - self.dependent_runtimes = self.my_utils.get_dependent_runtimes() - self.set_title(self.main_window_title) - self.eol_list = [] - self.system_mask_list = self.my_utils.get_host_masks("system") - self.user_mask_list = self.my_utils.get_host_masks("user") - - for index in range(len(self.host_flatpaks)): - try: - if "eol" in self.host_flatpaks[index][12]: - self.eol_list.append(self.host_flatpaks[index][8]) - except: - print("Could not find EOL") - - for index in range(len(self.host_flatpaks)): - self.create_row(index) - - # self.windowSetEmpty(not self.flatpaks_list_box.get_row_at_index(0)) - self.batch_actions_enable(False) - self.main_stack.set_visible_child(self.main_box) - self.apply_filter() - - # Stop list window from opening with the list at the bottom by focusing the first visible row item - for index in range(len(self.host_flatpaks)): - if self.flatpaks_list_box.get_row_at_index(index).is_visible(): - self.flatpaks_list_box.get_row_at_index(index).grab_focus() - break - - def refresh_list_of_flatpaks(self, widget): - if self.currently_uninstalling: - return - - # I hate this so much... - def callback(*args): - self.flatpaks_list_box.remove_all() - self.generate_list_of_flatpaks() - self.batch_mode_button.set_active(False) - self.total_selected = 0 - - def runner(*args): - import time - time.sleep(0.2) - - self.main_stack.set_visible_child(self.refreshing) - task = Gio.Task.new(None, None, callback) - task.run_in_thread(runner) - - def reset_filters(self): - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - for key in settings.list_keys(): - settings.reset(key) - self.apply_filter() - - def apply_filter(self): - self.batch_mode_button.set_active(False) - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - show_apps = settings.get_boolean("show-apps") - show_runtimes = settings.get_boolean("show-runtimes") - remotes_list = settings.get_string("remotes-list").split(",") - runtimes_list = settings.get_string("runtimes-list").split(",") - total_visible = 0 - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - id = current.app_id - dependant = current.dependent_runtime - remote = f"{current.origin_remote}<>{current.install_type};" - is_runtime = current.is_runtime - - visible = True - if (not show_apps) and (not is_runtime): - visible = False - - if (not show_runtimes) and is_runtime: - visible = False - - if (not "all" in remotes_list) and (not remote in remotes_list): - visible = False - - if (not "all" in runtimes_list) and (not dependant in runtimes_list): - visible = False - - current.set_is_visible(visible) - total_visible += visible - i += 1 - if (total_visible == 0) or (runtimes_list != ["all"] and show_runtimes): - self.window_set_empty(True) - self.main_stack.set_visible_child(self.no_matches) - else: - self.window_set_empty(False) - self.main_stack.set_visible_child(self.main_box) - - def open_data_folder(self, path): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def trash_data(self, name, id, index): - def on_continue(dialog, response): - if response == "cancel": - return - result = self.my_utils.trash_folder(f"{self.user_data_path}{id}") - if result != 0: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not trash user data")) - ) - return - self.lookup_action(f"open-data{index}").set_enabled(False) - self.lookup_action(f"trash{index}").set_enabled(False) - self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data"))) - - dialog = Adw.AlertDialog.new( - _("Send {}'s User Data to the Trash?").format(name) - ) - dialog.set_body( - _("Your files and data for this app will be sent to the trash.") - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Trash Data")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_continue) - dialog.present(self) - - def mask_flatpak(self, row): - is_masked = ( - row.mask_label.get_visible() - ) # Check the visibility of the mask label to see if the flatpak is masked - result = [] - - def callback(): - if result[0] == 1: - self.toast_overlay.add_toast( - Adw.Toast.new( - _("Could not disable updates for {}").format(row.app_name) - ) - ) - return - row.set_masked(not is_masked) - self.lookup_action(f"mask{row.index}").set_enabled(is_masked) - self.lookup_action(f"unmask{row.index}").set_enabled(not is_masked) - - def on_continue(dialog, response): - if response == "cancel": - return - task = Gio.Task.new(None, None, lambda *_: callback()) - task.run_in_thread( - lambda *_: result.append( - self.my_utils.mask_flatpak(row.app_id, row.install_type, is_masked) - ) - ) - - if is_masked: - on_continue(self, None) - else: - dialog = Adw.AlertDialog.new( - _("Disable Updates for {}?").format(row.app_name) - ) - dialog.set_body( - _( - "This will mask {} ensuring it will never recieve any feature or security updates." - ).format(row.app_name) - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Disable Updates")) - dialog.connect("response", on_continue) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.present(self) - - def pin_flatpak(self, row): - def thread(*args): - command = f"flatpak-spawn --host flatpak pin --{row.install_type} runtime/{row.app_ref}" - if row.is_pinned: - command += " --remove" - response = subprocess.run( - command, - capture_output=True, - text=True, - shell=True - ).stderr - if response != "" and row.is_pinned: - GLib.idle_add(self.toast_overlay.add_toast(Adw.Toast.new(_("Could not enable auto removal")))) - return - elif response != "": - GLib.idle_add(self.toast_overlay.add_toast(Adw.Toast.new(_("Could not disable auto removal")))) - return - row.is_pinned = not row.is_pinned - GLib.idle_add(lambda *_, row=row: self.lookup_action(f"pin{row.index}").set_enabled(not row.is_pinned)) - GLib.idle_add(lambda *_, row=row: self.lookup_action(f"unpin{row.index}").set_enabled(row.is_pinned)) - GLib.idle_add(lambda *_, row=row: row.pin_label.set_visible(row.is_pinned)) - GLib.idle_add(lambda *_, row=row: row.info_button_show_or_hide()) - - def callback(*args): - print("done") - - def on_continue(dialog, response): - if response == "cancel": - return - - task = Gio.Task.new(None, None, None) - task.run_in_thread(thread) - - if row.is_pinned: - on_continue(self, None) - else: - dialog = Adw.AlertDialog.new( - _( - "Disable Automatic Removal for {}?" - ).format(row.app_name), - _( - "This will pin {} ensuring it well never be removed automatically, even if no app depends on it." - ).format(row.app_name), - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.connect("response", on_continue) - dialog.add_response("continue", _("Disable Auto Removal")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - dialog.present(self) - - def copy_item(self, to_copy, to_toast=None): - self.clipboard.set(to_copy) - if to_toast: - self.toast_overlay.add_toast(Adw.Toast.new(to_toast)) - - def run_callback(self, _a, _b): - if not self.my_utils.run_app_error: - return - - error = self.my_utils.run_app_error_message - dialog = Adw.AlertDialog.new(_("Could not Run App"), error) - copy_button = Gtk.Button( - label=_("Copy"), halign=Gtk.Align.CENTER, margin_top=12 - ) - copy_button.add_css_class("pill") - copy_button.add_css_class("suggested-action") - copy_button.connect("clicked", lambda *_: self.clipboard.set(error)) - dialog.set_extra_child(copy_button) - dialog.add_response("ok", _("OK")) - dialog.set_close_response("ok") - dialog.present(self) - - def run_app_thread(self, ref, to_toast=None): - self.run_app_error = False - task = Gio.Task.new(None, None, self.run_callback) - task.run_in_thread(lambda *_: self.my_utils.run_app(ref)) - if to_toast: - self.toast_overlay.add_toast(Adw.Toast.new(to_toast)) - - def batch_mode_handler(self, widget): - batch_mode = widget.get_active() - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - current.set_selectable(batch_mode) - i += 1 - self.in_batch_mode = batch_mode - self.batch_mode_bar.set_revealed(batch_mode) - - if not widget.get_active(): - self.batch_select_all_button.set_active(False) - - def key_handler(self, controller, keyval, keycode, state): - if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK: - self.close() - if keyval == Gdk.KEY_Escape: - self.batch_mode_button.set_active(False) - - def batch_actions_enable(self, should_enable): - self.batch_copy_button.set_sensitive(should_enable) - self.batch_clean_button.set_sensitive(should_enable) - self.batch_snapshot_button.set_sensitive(should_enable) - if not self.currently_uninstalling: - self.batch_uninstall_button.set_sensitive(should_enable) - - def on_batch_clean_response(self, dialog, response, _a): - if response == "cancel": - return - i = 0 - trashReturnCodes = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active() == False: - continue - trash = self.my_utils.trash_folder(f"{self.user_data_path}{current.app_id}") - if trash == 1: - self.toast_overlay.add_toast( - Adw.Toast.new(_("{} has no data to trash").format(current.app_name)) - ) - continue - if trash == 2: - self.toast_overlay.add_toast( - Adw.Toast.new( - _("Could not trash {}'s data").format(current.app_name) - ) - ) - continue - self.lookup_action(f"open-data{current.index}").set_enabled( - False - ) # Disable the Open User Data dropdown option when the data was deleted - self.lookup_action(f"trash{current.index}").set_enabled( - False - ) # Disable the Trash User Data dropdown option when the data was deleted - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def batch_clean_handler(self, widget): - dialog = Adw.AlertDialog.new( - _("Trash Selected Apps' User Data?"), - _("Your files and data for these apps will be sent to the trash."), - ) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Trash Data")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", self.on_batch_clean_response, dialog.choose_finish) - dialog.present(self) - - def select_all_handler(self, widget): - self.set_select_all(widget.get_active()) - - def batch_snapshot_handler(self, widget): - def batch_snapshot_response(dialog, response, _a): - if response == "cancel": - return - i = 0 - snapshots_path = ( - self.host_home - + "/.var/app/io.github.flattool.Warehouse/data/Snapshots/" - ) - snapshot_arr = [] - app_ver_arr = [] - app_data_arr = [] - epoch = int(time.time()) - self.no_close = self.connect( - "close-request", lambda event: True - ) # Make window unable to close - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current.tickbox.get_active() == False: - continue - if not os.path.exists(f"{self.user_data_path}{current.app_id}"): - continue - snapshot_arr.append(snapshots_path + current.app_id + "/") - app_ver_arr.append(current.app_version) - app_data_arr.append(f"{self.user_data_path}{current.app_id}") - - def thread(): - capture = self.my_utils.snapshot_apps( - epoch, - snapshot_arr, - app_ver_arr, - app_data_arr, - self.main_progress_bar, - ) - if capture != 0: - GLib.idle_add( - lambda *_: self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not snapshot some apps")) - ) - ) - - def callback(*args): - self.main_stack.set_visible_child(self.main_box) - self.disconnect(self.no_close) - self.search_button.set_sensitive(True) - - self.search_button.set_sensitive(False) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.snapshotting) - task = Gio.Task.new(None, None, callback) - task.run_in_thread(lambda *_: thread()) - - dialog = Adw.AlertDialog.new( - _("Create Snapshots?"), - _( - "Snapshots are backups of the app's user data. They can be reapplied at any time. This could take a while." - ), - ) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Create Snapshots")) - dialog.connect("response", batch_snapshot_response, dialog.choose_finish) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - dialog.present(self) - - def set_select_all(self, should_select_all): - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - if (current.get_visible() == True) and ( - current.app_id != "io.github.flattool.Warehouse" - ): - current.tickbox.set_active(should_select_all) - i += 1 - - def row_select_handler(self, tickbox): - if tickbox.get_active() == True: - self.total_selected += 1 - else: - self.total_selected -= 1 - - if self.total_selected == 0: - buttons_enable = False - self.set_title(self.main_window_title) - self.batch_actions_enable(False) - else: - self.set_title(f"{self.total_selected} Selected") - self.batch_actions_enable(True) - - def create_action(self, name, callback, shortcuts=None): - """Add a window action. - - Args: - name: the name of the action - callback: the function to be called when the action is - activated - shortcuts: an optional list of accelerators - """ - action = Gio.SimpleAction.new(name, None) - action.connect("activate", callback) - self.add_action(action) - if shortcuts: - self.set_accels_for_action(f"app.{name}", shortcuts) - - def copy_names(self, widget, _a): - to_copy = "" - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active(): - to_copy += f"{current.app_name}\n" - self.clipboard.set(to_copy) - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app names"))) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def copy_IDs(self, widget, _a): - to_copy = "" - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active(): - to_copy += f"{current.app_id}\n" - self.clipboard.set(to_copy) - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app IDs"))) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def copy_refs(self, widget, _a): - to_copy = "" - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active(): - to_copy += f"{current.app_ref}\n" - self.clipboard.set(to_copy) - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app refs"))) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def install_callback(self, _a, _b): - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - if self.my_utils.install_success: - self.refresh_list_of_flatpaks(self) - self.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully"))) - else: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not install app"))) - - def install_thread(self, filepath, user_or_system): - self.my_utils.install_flatpak( - [filepath], None, user_or_system, self.main_progress_bar - ) - - def install_file(self, filepath): - def response(dialog, response, _a): - if response == "cancel": - return - - self.main_stack.set_visible_child(self.installing) - self.search_button.set_sensitive(False) - user_or_system = "user" - if system_check.get_active(): - user_or_system = "system" - - task = Gio.Task.new(None, None, self.install_callback) - task.run_in_thread(lambda *_: self.install_thread(filepath, user_or_system)) - - name = filepath.split("/") - name = name[len(name) - 1] - - dialog = Adw.AlertDialog.new(_("Install {}?").format(name)) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Install")) - 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) - 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(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") - dialog.present(self) - - def drop_callback(self, target, _x, _y, _data): - 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.add_remote_file(filepath) - else: - self.toast_overlay.add_toast(Adw.Toast.new(_("File type not supported"))) - - def on_invalidate(self, row): - if self.is_empty: - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.no_flatpaks) - self.search_button.set_sensitive(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - - self.is_result = False - self.flatpaks_list_box.invalidate_filter() - if self.is_result == False: - self.main_stack.set_visible_child(self.no_results) - self.search_button.set_sensitive(False) - - def on_change(self, prop, prop2): - if self.search_bar.get_search_mode() == False: - if self.is_empty: - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.no_flatpaks) - self.search_button.set_sensitive(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils(self) - self.set_size_request(360, 360) - self.settings = Gio.Settings.new("io.github.flattool.Warehouse") - self.settings.bind( - "window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT - ) - - self.system_pins = self.my_utils.get_host_system_pins() - self.user_pins = self.my_utils.get_host_user_pins() - - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - - if self.host_flatpaks == [["", ""]]: - self.window_set_empty(True) - return - - self.flatpaks_list_box.set_filter_func(self.filter_func) - - task = Gio.Task() - task.run_in_thread( - lambda *_: GLib.idle_add(lambda *_: self.generate_list_of_flatpaks()) - ) - - self.search_entry.connect("search-changed", self.on_invalidate) - self.search_bar.connect_entry(self.search_entry) - self.search_bar.connect("notify", self.on_change) - self.filter_button.connect("clicked", lambda *_: FilterWindow(self)) - self.batch_mode_button.connect("toggled", self.batch_mode_handler) - self.batch_clean_button.connect("clicked", self.batch_clean_handler) - self.batch_uninstall_button.connect( - "clicked", self.batch_uninstall_button_handler - ) - self.batch_select_all_button.connect("clicked", self.select_all_handler) - self.batch_snapshot_button.connect("clicked", self.batch_snapshot_handler) - self.reset_filters_button.connect("clicked", lambda *_: self.reset_filters()) - self.batch_actions_enable(False) - event_controller = Gtk.EventControllerKey() - event_controller.connect("key-pressed", self.key_handler) - self.add_controller(event_controller) - self.main_overlay.add_overlay(self.main_progress_bar) - - self.create_action("copy-names", self.copy_names) - self.create_action("copy-ids", self.copy_IDs) - self.create_action("copy-refs", self.copy_refs) - - file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) - file_drop.connect("drop", self.drop_callback) - self.scrolled_window.add_controller(file_drop) - - if Config.DEVEL: - self.add_css_class("devel") From b5e50b0255c23a6db7de95b29247a3eec8ab4f00 Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 4 Jul 2024 01:13:37 -0400 Subject: [PATCH 003/332] Initial work for getting host information --- io.github.flattool.Warehouse.json | 3 +- src/host_info.py | 130 ++++++++++++++++++++++++++++++ src/main.py | 54 ------------- src/main_window/window.blp | 9 ++- src/main_window/window.py | 7 ++ src/meson.build | 1 + 6 files changed, 146 insertions(+), 58 deletions(-) create mode 100644 src/host_info.py diff --git a/io.github.flattool.Warehouse.json b/io.github.flattool.Warehouse.json index c06b077..fbd819e 100644 --- a/io.github.flattool.Warehouse.json +++ b/io.github.flattool.Warehouse.json @@ -12,7 +12,8 @@ "--talk-name=org.freedesktop.Flatpak", "--filesystem=/var/lib/flatpak/:ro", "--filesystem=~/.local/share/flatpak/:ro", - "--filesystem=~/.var/app/" + "--filesystem=~/.var/app/", + "--filesystem=host-etc" ], "cleanup" : [ "/include", diff --git a/src/host_info.py b/src/host_info.py new file mode 100644 index 0000000..f5f40bd --- /dev/null +++ b/src/host_info.py @@ -0,0 +1,130 @@ +import gi, subprocess, os, pathlib + +gi.require_version('Gtk', '4.0') + +from gi.repository import Gio, Gtk, GLib + +home = f"{pathlib.Path.home()}" +icon_theme = Gtk.IconTheme.new() +icon_theme.add_search_path(f"{home}/.local/share/flatpak/exports/share/icons") +direction = Gtk.Image().get_direction() + +class Flatpak: + def __init__(self, columns): + self.is_runtime = "runtime" in columns[12] + self.info = { + "name": columns[0], + "description": columns[1], + "id": columns[2], + "version": columns[3], + "branch": columns[4], + "arch": columns[5], + "origin": columns[6], + "ref": columns[8], + "installed_size": columns[11], + "options": columns[12], + } + self.data_path = f"{home}/{columns[2]}" + installation = columns[7] + if len(i := installation.split(' ')) > 1: + self.info["installation"] = i[1].replace("(", "").replace(")", "") + else: + self.info["installation"] = installation + + try: + self.icon_path = ( + icon_theme.lookup_icon( + self.info["id"], None, 512, 1, direction, 0 + ) + .get_file() + .get_path() + ) + except GLib.GError as e: + print(e) + icon_path = None + + +class Remote: + def __init__(self, name, installation): + self.name = name + self.installation = installation + +class HostInfo: + home = home + + # Get all possible installation icon theme dirs + output = subprocess.run( + ['flatpak-spawn', '--host', + 'flatpak', '--installations'], + text=True, + capture_output=True, + ).stdout + lines = output.strip().split("\n") + for i in lines: + icon_theme.add_search_path(f"{i}/exports/share/icons") + + flatpaks = [] + @classmethod + def get_flatpaks(this, callback=None): + # Callback is a function to run after the host flatpaks are found + this.flatpaks.clear() + + def thread(task, *args): + output = subprocess.run( + ['flatpak-spawn', '--host', + 'flatpak', 'list', '--columns=all'], + text=True, + capture_output=True, + ).stdout + lines = output.strip().split("\n") + for i in lines: + this.flatpaks.append(Flatpak(i.split("\t"))) + + Gio.Task.new(None, None, callback).run_in_thread(thread) + + remotes = [] + installations = [] + @classmethod + def get_remotes(this, callback=None): + # Callback is a function to run after the host remotes are found + this.remotes.clear() + this.installations.clear() + + def thread(task, *args): + + # Get all config files for any extra installations + custom_install_config_path = "/run/host/etc/flatpak/installations.d" + if os.path.exists(custom_install_config_path): + for file in os.listdir(custom_install_config_path): + with open(f"{custom_install_config_path}/{file}", "r") as f: + for line in f: + if line.startswith("[Installation"): + # Get specifically the installation name itself + this.installations.append(line.replace("[Installation \"", "").replace("\"]", "").strip()) + + def remote_info(installation): + cmd = ['flatpak-spawn', '--host', + 'flatpak', 'remotes'] + if installation == "user": + cmd.append("--user") + elif installation == "system": + cmd.append("--system") + else: + cmd.append(f"--installation={installation}") + output = subprocess.run( + cmd, text=True, + capture_output=True, + ).stdout + lines = output.strip().split("\n") + for i in lines: + if i != "": + this.remotes.append(Remote(i.strip(), installation)) + if installation == "user" or installation == "system": + this.installations.append(installation) + + for i in this.installations: + remote_info(i) + remote_info("user") + remote_info("system") + + Gio.Task.new(None, None, callback).run_in_thread(thread) \ No newline at end of file diff --git a/src/main.py b/src/main.py index 85ec197..1295026 100644 --- a/src/main.py +++ b/src/main.py @@ -43,31 +43,6 @@ class WarehouseApplication(Adw.Application): self.create_action("quit", lambda *_: self.quit(), ["q"]) self.create_action("about", self.on_about_action) self.create_action("preferences", self.on_preferences_action) - self.create_action("search", self.on_search_action, ["f"]) - self.create_action("manage-data-folders", self.manage_data_shortcut) - self.create_action( - "toggle-batch-mode", - self.batch_mode_shortcut, - ["b", "Return"], - ) - self.create_action( - "toggle-batch-mode-keypad", self.batch_mode_shortcut, ["KP_Enter"] - ) # This action is not added to the shortcuts window - self.create_action( - "manage-data-folders", self.manage_data_shortcut, ["d"] - ) - self.create_action( - "refresh-list", self.refresh_list_shortcut, ["r", "F5"] - ) - 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"]) - self.create_action( - "open-search-install", self.open_search_install, ["i"] - ) self.is_dialog_open = False @@ -98,29 +73,6 @@ class WarehouseApplication(Adw.Application): lang=lang, ) - def open_search_install(self, widget, _): - SearchInstallWindow(self.props.active_window) - - def batch_mode_shortcut(self, widget, _): - button = self.props.active_window.batch_mode_button - button.set_active(not button.get_active()) - - def manage_data_shortcut(self, widget, _): - OrphansWindow(self.props.active_window) - - def refresh_list_shortcut(self, widget, _): - self.props.active_window.refresh_list_of_flatpaks(widget) - - def show_remotes_shortcut(self, widget, _): - RemotesWindow(self.props.active_window) - - def filters_shortcut(self, widget, _): - FilterWindow(self.props.active_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: @@ -197,11 +149,6 @@ class WarehouseApplication(Adw.Application): """Callback for the app.preferences action.""" print("app.preferences action activated") - def on_search_action(self, widget, _): - self.props.active_window.search_bar.set_search_mode( - not self.props.active_window.search_bar.get_search_mode() - ) - def create_action(self, name, callback, shortcuts=None): """Add an application action. @@ -217,7 +164,6 @@ class WarehouseApplication(Adw.Application): if shortcuts: self.set_accels_for_action(f"app.{name}", shortcuts) - def main(version): """The application's entry point.""" app = WarehouseApplication() diff --git a/src/main_window/window.blp b/src/main_window/window.blp index 2fa24bd..e59ba94 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -19,9 +19,12 @@ template $WarehouseWindow: Adw.ApplicationWindow { } ; content: - Adw.ToolbarView { - [top] - Adw.HeaderBar {} + Adw.NavigationPage { + title: "Test"; + Adw.ToolbarView { + [top] + Adw.HeaderBar {} + } } ; } diff --git a/src/main_window/window.py b/src/main_window/window.py index 19a69cd..f3af6bf 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -23,6 +23,7 @@ import re import time from gi.repository import Adw, Gdk, Gio, GLib, Gtk +from .host_info import HostInfo from .const import Config @Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui") @@ -62,3 +63,9 @@ class WarehouseWindow(Adw.ApplicationWindow): if Config.DEVEL: self.add_css_class("devel") + + def guh(*args): + for i in HostInfo.flatpaks: + print(i.info["name"]) + + HostInfo.get_flatpaks() \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index cbb0a39..81f232d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -48,6 +48,7 @@ warehouse_sources = [ '__init__.py', 'main.py', 'main_window/window.py', + 'host_info.py', '../data/style.css', ] From f595d3cec4d71e2ef5fb095677c40f6c738169ab Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 4 Jul 2024 19:12:26 -0400 Subject: [PATCH 004/332] Start triple pane UI and packages list --- src/host_info.py | 3 +- src/main.py | 1 + src/main_window/window.blp | 137 ++++++++++++++++------------ src/main_window/window.py | 17 ++-- src/meson.build | 6 +- src/packages_page/packages_page.blp | 43 +++++++++ src/packages_page/packages_page.py | 31 +++++++ src/warehouse.gresource.xml | 2 + src/widgets/app_row.blp | 15 +++ src/widgets/app_row.py | 16 ++++ 10 files changed, 201 insertions(+), 70 deletions(-) create mode 100644 src/packages_page/packages_page.blp create mode 100644 src/packages_page/packages_page.py create mode 100644 src/widgets/app_row.blp create mode 100644 src/widgets/app_row.py diff --git a/src/host_info.py b/src/host_info.py index f5f40bd..b98ebc3 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -41,7 +41,7 @@ class Flatpak: ) except GLib.GError as e: print(e) - icon_path = None + self.icon_path = None class Remote: @@ -79,6 +79,7 @@ class HostInfo: lines = output.strip().split("\n") for i in lines: this.flatpaks.append(Flatpak(i.split("\t"))) + this.flatpaks = sorted(this.flatpaks, key=lambda flatpak: flatpak.info["name"].lower()) Gio.Task.new(None, None, callback).run_in_thread(thread) diff --git a/src/main.py b/src/main.py index 1295026..c7b0a20 100644 --- a/src/main.py +++ b/src/main.py @@ -26,6 +26,7 @@ gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") from gi.repository import Gtk, Gio, Adw, GLib +from .host_info import HostInfo from .window import WarehouseWindow from .const import Config diff --git a/src/main_window/window.blp b/src/main_window/window.blp index e59ba94..9a2ab85 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -2,75 +2,92 @@ using Gtk 4.0; using Adw 1; template $WarehouseWindow: Adw.ApplicationWindow { - title: "Warehouse"; - content: - Adw.OverlaySplitView { - sidebar: - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - [end] - MenuButton main_menu { - icon-name: "open-menu-symbolic"; - tooltip-text: _("Main Menu"); - menu-model: primary_menu; - } - } + title: "Warehouse"; + Adw.Breakpoint main_breakpoint { + condition ("max-width: 865") + + setters { + main_split.collapsed: true; + main_split.max-sidebar-width: 280; } - ; - content: - Adw.NavigationPage { - title: "Test"; - Adw.ToolbarView { - [top] - Adw.HeaderBar {} - } - } - ; } - ; + content: + Adw.OverlaySplitView main_split { + sidebar-width-fraction: 0.2; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage { + title: "Warehouse"; + Adw.ToolbarView main_toolbar_view { + [top] + Adw.HeaderBar header_bar { + [start] + Button sidebar_button { + label: "SB"; + } + [end] + MenuButton main_menu { + icon-name: "open-menu-symbolic"; + tooltip-text: _("Main Menu"); + menu-model: primary_menu; + } + } + content: + ScrolledWindow { + ListBox { + Adw.ActionRow { + title: "test"; + } + } + } + ; + } + } + ; + } + ; } menu primary_menu { - section { - item { - label: _("Manage Leftover Data…"); - action: "app.manage-data-folders"; - } + section { + item { + label: _("Manage Leftover Data…"); + action: "app.manage-data-folders"; + } - /*item { - label: _("_Preferences"); - action: "app.preferences"; - }*/ - item { - label: _("Manage Remotes…"); - action: "app.show-remotes-window"; - } - } - section { - item { - label: _("Install From File…"); - action: "app.install-from-file"; + /*item { + label: _("_Preferences"); + action: "app.preferences"; + }*/ + item { + label: _("Manage Remotes…"); + action: "app.show-remotes-window"; + } } + section { + item { + label: _("Install From File…"); + action: "app.install-from-file"; + } - item { - label: _("Install From The Web…"); - action: "app.open-search-install"; - } - } - section { - item { - label: _("Refresh List"); - action: "app.refresh-list"; - } - item { - label: _("_Keyboard Shortcuts"); - action: "win.show-help-overlay"; + item { + label: _("Install From The Web…"); + action: "app.open-search-install"; + } } + section { + item { + label: _("Refresh List"); + action: "app.refresh-list"; + } + item { + label: _("_Keyboard Shortcuts"); + action: "win.show-help-overlay"; + } - item { - label: _("_About Warehouse"); - action: "app.about"; + item { + label: _("_About Warehouse"); + action: "app.about"; + } } - } } \ No newline at end of file diff --git a/src/main_window/window.py b/src/main_window/window.py index f3af6bf..a8b4e08 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -23,12 +23,16 @@ import re import time from gi.repository import Adw, Gdk, Gio, GLib, Gtk -from .host_info import HostInfo +from .packages_page import PackagesPage from .const import Config @Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui") class WarehouseWindow(Adw.ApplicationWindow): __gtype_name__ = "WarehouseWindow" + gtc = Gtk.Template.Child + main_breakpoint = gtc() + main_split = gtc() + sidebar_button = gtc() def key_handler(self, controller, keyval, keycode, state): if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK: @@ -61,11 +65,8 @@ class WarehouseWindow(Adw.ApplicationWindow): # file_drop.connect("drop", self.drop_callback) # self.scrolled_window.add_controller(file_drop) + self.main_split.set_content(PackagesPage(self)) + self.sidebar_button.connect("clicked", lambda *_: self.main_split.set_show_sidebar(False)) + if Config.DEVEL: - self.add_css_class("devel") - - def guh(*args): - for i in HostInfo.flatpaks: - print(i.info["name"]) - - HostInfo.get_flatpaks() \ No newline at end of file + self.add_css_class("devel") \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 81f232d..1830fc4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,8 +4,10 @@ gnome = import('gnome') blueprints = custom_target('blueprints', input: files( + 'widgets/app_row.blp', 'gtk/help-overlay.blp', 'main_window/window.blp', + 'packages_page/packages_page.blp' ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -47,8 +49,10 @@ configure_file( warehouse_sources = [ '__init__.py', 'main.py', - 'main_window/window.py', 'host_info.py', + 'widgets/app_row.py', + 'main_window/window.py', + 'packages_page/packages_page.py', '../data/style.css', ] diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp new file mode 100644 index 0000000..3c75834 --- /dev/null +++ b/src/packages_page/packages_page.blp @@ -0,0 +1,43 @@ +using Gtk 4.0; +using Adw 1; + +template $PackagesPage : Adw.BreakpointBin { + width-request: 1; + height-request: 1; + + Adw.Breakpoint packages_bpt { + condition ("max-width: 625") + } + + Adw.NavigationSplitView { + sidebar-width-fraction: 0.4; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage { + title: "Packages"; + Adw.ToolbarView { + [top] + Adw.HeaderBar { + [start] + Button sidebar_button { + label: "SB"; + } + } + ScrolledWindow { + ListBox packages_list_box { + styles ["navigation-sidebar"] + } + } + } + } + ; + content: + Adw.NavigationPage { + title: "Properties"; + Adw.StatusPage { + title: "Properties Panel"; + } + } + ; + } +} \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py new file mode 100644 index 0000000..16cac9a --- /dev/null +++ b/src/packages_page/packages_page.py @@ -0,0 +1,31 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo +from .app_row import AppRow + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") +class PackagesPage(Adw.BreakpointBin): + __gtype_name__ = 'PackagesPage' + gtc = Gtk.Template.Child + packages_list_box = gtc() + sidebar_button = gtc() + + package_rows = [] + + def generate_list(self, *args): + for package in HostInfo.flatpaks: + self.packages_list_box.append(AppRow(package)) + + def row_select_handler(self, list_box, row): + print(row.get_title()) + + def sidebar_button_visibility_handler(self): + self.sidebar_button.set_visible(self.main_window.main_split.get_collapsed() or not self.main_window.main_split.get_show_sidebar()) + + def __init__(self, main_window, **kwargs): + super().__init__(**kwargs) + self.main_window = main_window + HostInfo.get_flatpaks(self.generate_list) + self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) + main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) + main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible) + # self.packages_list_box.connect("row-selected", self.row_select_handler) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 403e1c6..7bf11fc 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -2,7 +2,9 @@ ../data/style.css + widgets/app_row.ui main_window/window.ui + packages_page/packages_page.ui gtk/help-overlay.ui diff --git a/src/widgets/app_row.blp b/src/widgets/app_row.blp new file mode 100644 index 0000000..7db98d9 --- /dev/null +++ b/src/widgets/app_row.blp @@ -0,0 +1,15 @@ +using Gtk 4.0; +using Adw 1; + +template $AppRow : Adw.ActionRow { + [prefix] + Image image { + icon-size: large; + icon-name: "application-x-executable-symbolic"; + } + [suffix] + CheckButton check_button { + styles["selection-mode"] + visible: false; + } +} \ No newline at end of file diff --git a/src/widgets/app_row.py b/src/widgets/app_row.py new file mode 100644 index 0000000..a39a43c --- /dev/null +++ b/src/widgets/app_row.py @@ -0,0 +1,16 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/widgets/app_row.ui") +class AppRow(Adw.ActionRow): + __gtype_name__ = 'AppRow' + gtc = Gtk.Template.Child + image = gtc() + + def __init__(self, package, **kwargs): + super().__init__(**kwargs) + self.package = package + self.set_title(package.info["name"]) + self.set_subtitle(package.info["id"]) + if package.icon_path: + self.image.set_from_file(package.icon_path) \ No newline at end of file From fb308aa35af8f0483aeee78db3aabfa9b1829790 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 03:13:17 -0400 Subject: [PATCH 005/332] Further work on packages page --- src/packages_page/packages_page.blp | 63 +++++++++++++++++------------ src/packages_page/packages_page.py | 12 +++--- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 3c75834..f19edac 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -6,38 +6,49 @@ template $PackagesPage : Adw.BreakpointBin { height-request: 1; Adw.Breakpoint packages_bpt { - condition ("max-width: 625") + condition ("max-width: 600") + + setters { + packages_split.collapsed: true; + } } - Adw.NavigationSplitView { - sidebar-width-fraction: 0.4; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage { - title: "Packages"; - Adw.ToolbarView { - [top] - Adw.HeaderBar { - [start] - Button sidebar_button { - label: "SB"; + Adw.ToastOverlay packages_toast_overlay { + Adw.NavigationSplitView packages_split { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage { + title: "Packages"; + Adw.ToolbarView { + [top] + Adw.HeaderBar { + [start] + Button sidebar_button { + icon-name: "dock-left-symbolic"; + visible: false; + } + [end] + Button refresh_button { + icon-name: "arrow-circular-top-right-symbolic"; + } } - } - ScrolledWindow { - ListBox packages_list_box { - styles ["navigation-sidebar"] + ScrolledWindow { + ListBox packages_list_box { + styles ["navigation-sidebar"] + } } } } - } - ; - content: - Adw.NavigationPage { - title: "Properties"; - Adw.StatusPage { - title: "Properties Panel"; + ; + content: + Adw.NavigationPage { + title: "Properties"; + Adw.StatusPage { + title: "Properties Panel"; + } } - } - ; + ; + } } } \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 16cac9a..f1a367f 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -1,6 +1,7 @@ -from gi.repository import Adw, Gtk, GLib, Gio, Pango +from gi.repository import Adw, Gtk#, GLib, Gio, Pango from .host_info import HostInfo from .app_row import AppRow +from .error_toast import ErrorToast @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") class PackagesPage(Adw.BreakpointBin): @@ -8,19 +9,17 @@ class PackagesPage(Adw.BreakpointBin): gtc = Gtk.Template.Child packages_list_box = gtc() sidebar_button = gtc() - - package_rows = [] + refresh_button = gtc() + packages_toast_overlay = gtc() def generate_list(self, *args): + self.packages_list_box.remove_all() for package in HostInfo.flatpaks: self.packages_list_box.append(AppRow(package)) def row_select_handler(self, list_box, row): print(row.get_title()) - def sidebar_button_visibility_handler(self): - self.sidebar_button.set_visible(self.main_window.main_split.get_collapsed() or not self.main_window.main_split.get_show_sidebar()) - def __init__(self, main_window, **kwargs): super().__init__(**kwargs) self.main_window = main_window @@ -28,4 +27,5 @@ class PackagesPage(Adw.BreakpointBin): self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible) + self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(self.generate_list)) # self.packages_list_box.connect("row-selected", self.row_select_handler) From 4882b31761841b9b1c80161cdc442d9d3b7823cc Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 03:13:34 -0400 Subject: [PATCH 006/332] Introduce new error toast widget --- src/meson.build | 1 + src/widgets/error_toast.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/widgets/error_toast.py diff --git a/src/meson.build b/src/meson.build index 1830fc4..1ece3e5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -51,6 +51,7 @@ warehouse_sources = [ 'main.py', 'host_info.py', 'widgets/app_row.py', + 'widgets/error_toast.py', 'main_window/window.py', 'packages_page/packages_page.py', '../data/style.css', diff --git a/src/widgets/error_toast.py b/src/widgets/error_toast.py new file mode 100644 index 0000000..3406961 --- /dev/null +++ b/src/widgets/error_toast.py @@ -0,0 +1,24 @@ +from gi.repository import Adw, Gtk, Gdk, GLib, Pango +clipboard = Gdk.Display.get_default().get_clipboard() + +class ErrorToast: + def __init__(self, display_msg, error_msg, parent_window, format=True): + + def on_response(dialog, response_id): + if response_id == "copy": + clipboard.set(error_msg) + + popup = Adw.AlertDialog.new(display_msg, None if format else error_msg) + popup.add_response("copy", _("Copy")) + popup.add_response("ok", _("OK")) + popup.connect("response", on_response) + + if format: + lb = Gtk.Label(selectable=True, wrap=True)#, natural_wrap_mode=Gtk.NaturalWrapMode.WORD) + lb.set_markup(f"{GLib.markup_escape_text(error_msg)}") + # lb.set_label(error_msg) + # lb.set_selectable(True) + popup.set_extra_child(lb) + + self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) + self.toast.connect("button-clicked", lambda *_: popup.present(parent_window)) \ No newline at end of file From d440d34a503aa534d3f92a5a98ef4efeea09ae48 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 03:13:54 -0400 Subject: [PATCH 007/332] Use icon for sidebar button --- data/icons/dock-left-symbolic.svg | 2 ++ src/main_window/window.blp | 15 ++++++++++----- src/warehouse.gresource.xml | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 data/icons/dock-left-symbolic.svg diff --git a/data/icons/dock-left-symbolic.svg b/data/icons/dock-left-symbolic.svg new file mode 100644 index 0000000..3c51520 --- /dev/null +++ b/data/icons/dock-left-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/main_window/window.blp b/src/main_window/window.blp index 9a2ab85..4b0a2d2 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -3,18 +3,23 @@ using Adw 1; template $WarehouseWindow: Adw.ApplicationWindow { title: "Warehouse"; + // default-width: 240; + default-width: 865; Adw.Breakpoint main_breakpoint { - condition ("max-width: 865") + condition ("min-width: 865") setters { - main_split.collapsed: true; - main_split.max-sidebar-width: 280; + main_split.collapsed: false; + main_split.max-sidebar-width: 999999999; } } content: Adw.OverlaySplitView main_split { + collapsed: true; + show-sidebar: true; sidebar-width-fraction: 0.2; - max-sidebar-width: 999999999; + // max-sidebar-width: 280; + min-sidebar-width: 250; sidebar: Adw.NavigationPage { title: "Warehouse"; @@ -23,7 +28,7 @@ template $WarehouseWindow: Adw.ApplicationWindow { Adw.HeaderBar header_bar { [start] Button sidebar_button { - label: "SB"; + icon-name: "dock-left-symbolic"; } [end] MenuButton main_menu { diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 7bf11fc..ca66e41 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -32,5 +32,6 @@ ../data/icons/left-large-symbolic.svg ../data/icons/arrow-turn-left-down-symbolic.svg ../data/icons/arrow-circular-top-right-symbolic.svg + ../data/icons/dock-left-symbolic.svg From ba94a6266c0ea133e4b6bdef0191f8619e2a235b Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 15:40:27 -0400 Subject: [PATCH 008/332] Clean up code consistency --- src/main_window/window.blp | 2 ++ src/main_window/window.py | 37 ++++++++++++------------------ src/packages_page/packages_page.py | 18 ++++++++++----- src/widgets/app_row.py | 9 +++++++- src/widgets/error_toast.py | 12 ++++++---- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/main_window/window.blp b/src/main_window/window.blp index 4b0a2d2..6c57d95 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -5,6 +5,8 @@ template $WarehouseWindow: Adw.ApplicationWindow { title: "Warehouse"; // default-width: 240; default-width: 865; + width-request: 360; + height-request: 360; Adw.Breakpoint main_breakpoint { condition ("min-width: 865") diff --git a/src/main_window/window.py b/src/main_window/window.py index a8b4e08..7e5026e 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -42,31 +42,24 @@ class WarehouseWindow(Adw.ApplicationWindow): def __init__(self, **kwargs): super().__init__(**kwargs) - self.set_size_request(360, 360) + + # Extra Object Creation self.settings = Gio.Settings.new("io.github.flattool.Warehouse") - self.settings.bind( - "window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT - ) - event_controller = Gtk.EventControllerKey() - event_controller.connect("key-pressed", self.key_handler) - self.add_controller(event_controller) - file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) - # file_drop.connect("drop", self.drop_callback) + + # Apply + self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT) + self.settings.bind("window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT) + self.settings.bind("is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT) + self.settings.bind("is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT) + self.add_controller(event_controller) # self.scrolled_window.add_controller(file_drop) - self.main_split.set_content(PackagesPage(self)) - self.sidebar_button.connect("clicked", lambda *_: self.main_split.set_show_sidebar(False)) - if Config.DEVEL: - self.add_css_class("devel") \ No newline at end of file + self.add_css_class("devel") + + # Connections + event_controller.connect("key-pressed", self.key_handler) + # file_drop.connect("drop", self.drop_callback) + self.sidebar_button.connect("clicked", lambda *_: self.main_split.set_show_sidebar(False)) \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index f1a367f..1f337f8 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -7,10 +7,10 @@ from .error_toast import ErrorToast class PackagesPage(Adw.BreakpointBin): __gtype_name__ = 'PackagesPage' gtc = Gtk.Template.Child - packages_list_box = gtc() + packages_toast_overlay = gtc() sidebar_button = gtc() refresh_button = gtc() - packages_toast_overlay = gtc() + packages_list_box = gtc() def generate_list(self, *args): self.packages_list_box.remove_all() @@ -22,10 +22,16 @@ class PackagesPage(Adw.BreakpointBin): def __init__(self, main_window, **kwargs): super().__init__(**kwargs) + + # Extra Object Creation self.main_window = main_window - HostInfo.get_flatpaks(self.generate_list) - self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) + + # Apply + HostInfo.get_flatpaks(callback=self.generate_list) + + # Connections main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) - main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible) - self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(self.generate_list)) + # main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible) + self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) + self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(callback=self.generate_list)) # self.packages_list_box.connect("row-selected", self.row_select_handler) diff --git a/src/widgets/app_row.py b/src/widgets/app_row.py index a39a43c..58ae286 100644 --- a/src/widgets/app_row.py +++ b/src/widgets/app_row.py @@ -6,11 +6,18 @@ class AppRow(Adw.ActionRow): __gtype_name__ = 'AppRow' gtc = Gtk.Template.Child image = gtc() + check_button = gtc() def __init__(self, package, **kwargs): super().__init__(**kwargs) + + # Extra Object Creation self.package = package + + # Apply self.set_title(package.info["name"]) self.set_subtitle(package.info["id"]) if package.icon_path: - self.image.set_from_file(package.icon_path) \ No newline at end of file + self.image.set_from_file(package.icon_path) + + # Connections \ No newline at end of file diff --git a/src/widgets/error_toast.py b/src/widgets/error_toast.py index 3406961..caa89db 100644 --- a/src/widgets/error_toast.py +++ b/src/widgets/error_toast.py @@ -8,11 +8,13 @@ class ErrorToast: if response_id == "copy": clipboard.set(error_msg) + # Extra Object Creation + self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) popup = Adw.AlertDialog.new(display_msg, None if format else error_msg) + + # Apply popup.add_response("copy", _("Copy")) popup.add_response("ok", _("OK")) - popup.connect("response", on_response) - if format: lb = Gtk.Label(selectable=True, wrap=True)#, natural_wrap_mode=Gtk.NaturalWrapMode.WORD) lb.set_markup(f"{GLib.markup_escape_text(error_msg)}") @@ -20,5 +22,7 @@ class ErrorToast: # lb.set_selectable(True) popup.set_extra_child(lb) - self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) - self.toast.connect("button-clicked", lambda *_: popup.present(parent_window)) \ No newline at end of file + # Connections + self.toast.connect("button-clicked", lambda *_: popup.present(parent_window)) + popup.connect("response", on_response) + From 5952147417319bb7ea0f7b09638dbd2dd1d5622b Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 21:57:37 -0400 Subject: [PATCH 009/332] Add more icons --- .../arrow-pointing-at-line-down-symbolic.svg | 2 + data/icons/file-manager-symbolic.svg | 2 + data/icons/loupe-large-symbolic.svg | 2 + data/icons/server-pick-symbolic.svg | 2 + data/icons/snapshots-alt-symbolic.svg | 2 + src/main_window/window.blp | 87 ++++++++++++++++++- src/main_window/window.py | 34 +++++++- src/packages_page/packages_page.blp | 63 +++++++++++++- src/packages_page/packages_page.py | 9 +- src/warehouse.gresource.xml | 5 ++ src/widgets/app_row.py | 1 + 11 files changed, 200 insertions(+), 9 deletions(-) create mode 100644 data/icons/arrow-pointing-at-line-down-symbolic.svg create mode 100644 data/icons/file-manager-symbolic.svg create mode 100644 data/icons/loupe-large-symbolic.svg create mode 100644 data/icons/server-pick-symbolic.svg create mode 100644 data/icons/snapshots-alt-symbolic.svg diff --git a/data/icons/arrow-pointing-at-line-down-symbolic.svg b/data/icons/arrow-pointing-at-line-down-symbolic.svg new file mode 100644 index 0000000..eaefb41 --- /dev/null +++ b/data/icons/arrow-pointing-at-line-down-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/file-manager-symbolic.svg b/data/icons/file-manager-symbolic.svg new file mode 100644 index 0000000..db0aa5a --- /dev/null +++ b/data/icons/file-manager-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/loupe-large-symbolic.svg b/data/icons/loupe-large-symbolic.svg new file mode 100644 index 0000000..8472dea --- /dev/null +++ b/data/icons/loupe-large-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/server-pick-symbolic.svg b/data/icons/server-pick-symbolic.svg new file mode 100644 index 0000000..7ea37f1 --- /dev/null +++ b/data/icons/server-pick-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/snapshots-alt-symbolic.svg b/data/icons/snapshots-alt-symbolic.svg new file mode 100644 index 0000000..f6bc5b7 --- /dev/null +++ b/data/icons/snapshots-alt-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/main_window/window.blp b/src/main_window/window.blp index 6c57d95..e95ad4e 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -5,7 +5,7 @@ template $WarehouseWindow: Adw.ApplicationWindow { title: "Warehouse"; // default-width: 240; default-width: 865; - width-request: 360; + width-request: 400; height-request: 360; Adw.Breakpoint main_breakpoint { condition ("min-width: 865") @@ -31,6 +31,7 @@ template $WarehouseWindow: Adw.ApplicationWindow { [start] Button sidebar_button { icon-name: "dock-left-symbolic"; + tooltip-text: _("Hide Sidebar"); } [end] MenuButton main_menu { @@ -41,9 +42,87 @@ template $WarehouseWindow: Adw.ApplicationWindow { } content: ScrolledWindow { - ListBox { - Adw.ActionRow { - title: "test"; + ListBox navigation_row_listbox { + styles ["navigation-sidebar"] + + Box packages_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; + + Image icon { + icon-name: "flatpak-symbolic"; + } + + Label { + label: _("Packages"); + } + } + + Box remotes_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; + + Image { + icon-name: "server-pick-symbolic"; + } + + Label { + label: _("Remotes"); + } + } + + Box user_data_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; + + Image { + icon-name: "file-manager-symbolic"; + } + + Label { + label: _("User Data"); + } + } + + Box snapshots_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; + + Image { + icon-name: "snapshots-alt-symbolic"; + } + + Label { + label: _("Snapshots"); + } + } + + Box install_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; + + Image { + icon-name: "arrow-pointing-at-line-down-symbolic"; + } + + Label { + label: _("Install Packages"); + } } } } diff --git a/src/main_window/window.py b/src/main_window/window.py index 7e5026e..d661159 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -33,13 +33,36 @@ class WarehouseWindow(Adw.ApplicationWindow): main_breakpoint = gtc() main_split = gtc() sidebar_button = gtc() + navigation_row_listbox = gtc() + packages_row = gtc() + remotes_row = gtc() + user_data_row = gtc() + snapshots_row = gtc() + install_row = gtc() def key_handler(self, controller, keyval, keycode, state): if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK: self.close() + if keyval == Gdk.KEY_Escape: self.batch_mode_button.set_active(False) + def navigation_handler(self, _, row, hide_sidebar=True): + row = row.get_child() + page = self.pages[row] + + if hide_sidebar and self.main_split.get_collapsed(): + self.main_split.set_show_sidebar(False) + + if type(self.main_split.get_content()) == page: + # Skip when the user clicks on the row that is already showing the page + return + + if page.instance: + self.main_split.set_content(page.instance) + else: + self.main_split.set_content(page(main_window=self)) + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -47,6 +70,9 @@ class WarehouseWindow(Adw.ApplicationWindow): self.settings = Gio.Settings.new("io.github.flattool.Warehouse") event_controller = Gtk.EventControllerKey() file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) + self.pages = { + self.packages_row: PackagesPage, + } # Apply self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT) @@ -55,11 +81,15 @@ class WarehouseWindow(Adw.ApplicationWindow): self.settings.bind("is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT) self.add_controller(event_controller) # self.scrolled_window.add_controller(file_drop) - self.main_split.set_content(PackagesPage(self)) + # self.main_split.set_content(PackagesPage(self)) if Config.DEVEL: self.add_css_class("devel") # Connections event_controller.connect("key-pressed", self.key_handler) + self.navigation_row_listbox.connect("row-activated", self.navigation_handler) # file_drop.connect("drop", self.drop_callback) - self.sidebar_button.connect("clicked", lambda *_: self.main_split.set_show_sidebar(False)) \ No newline at end of file + self.sidebar_button.connect("clicked", lambda *_: self.main_split.set_show_sidebar(False)) + + self.navigation_row_listbox.get_row_at_index(0).activate() + self.main_split.set_show_sidebar(True) \ No newline at end of file diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index f19edac..df337da 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -20,24 +20,83 @@ template $PackagesPage : Adw.BreakpointBin { sidebar: Adw.NavigationPage { title: "Packages"; - Adw.ToolbarView { + Adw.ToolbarView packages_tbv { [top] Adw.HeaderBar { [start] Button sidebar_button { icon-name: "dock-left-symbolic"; - visible: false; + tooltip-text: _("Show Sidebar"); + } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } + [start] + Button filter_button { + icon-name: "funnel-symbolic"; + tooltip-text: _("Filter Packages"); } [end] Button refresh_button { icon-name: "arrow-circular-top-right-symbolic"; + tooltip-text: _("Refresh List"); } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select Packages"); + } + } + [top] + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + key-capture-widget: packages_tbv; + SearchEntry search_entry { + hexpand: true; + } } ScrolledWindow { ListBox packages_list_box { styles ["navigation-sidebar"] } } + [bottom] + Revealer { + reveal-child: bind select_button.active; + transition-type: slide_up; + [center] + Box { + styles ["toolbar"] + hexpand: true; + homogeneous: true; + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "selection-mode-symbolic"; + label: _("Select All"); + can-shrink: true; + } + } + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; + } + } + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "user-trash-symbolic"; + label: _("Uninstall"); + can-shrink: true; + } + } + } + } } } ; diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 1f337f8..ad9a9a2 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -12,6 +12,11 @@ class PackagesPage(Adw.BreakpointBin): refresh_button = gtc() packages_list_box = 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 + def generate_list(self, *args): self.packages_list_box.remove_all() for package in HostInfo.flatpaks: @@ -28,10 +33,12 @@ class PackagesPage(Adw.BreakpointBin): # Apply HostInfo.get_flatpaks(callback=self.generate_list) + self.__class__.instance = self # Connections main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) - # main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible) + main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(callback=self.generate_list)) # self.packages_list_box.connect("row-selected", self.row_select_handler) + diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index ca66e41..274e010 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -33,5 +33,10 @@ ../data/icons/arrow-turn-left-down-symbolic.svg ../data/icons/arrow-circular-top-right-symbolic.svg ../data/icons/dock-left-symbolic.svg + ../data/icons/server-pick-symbolic.svg + ../data/icons/file-manager-symbolic.svg + ../data/icons/snapshots-alt-symbolic.svg + ../data/icons/arrow-pointing-at-line-down-symbolic.svg + ../data/icons/loupe-large-symbolic.svg diff --git a/src/widgets/app_row.py b/src/widgets/app_row.py index 58ae286..5456b9e 100644 --- a/src/widgets/app_row.py +++ b/src/widgets/app_row.py @@ -19,5 +19,6 @@ class AppRow(Adw.ActionRow): self.set_subtitle(package.info["id"]) if package.icon_path: self.image.set_from_file(package.icon_path) + self.image.add_css_class("icon-dropshadow") # Connections \ No newline at end of file From 3f7d70fddb04ebff237742f810d7a1e4d2cb8bcd Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 22:03:36 -0400 Subject: [PATCH 010/332] Add search to package list --- src/packages_page/packages_page.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index ad9a9a2..cb887ff 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -10,6 +10,7 @@ class PackagesPage(Adw.BreakpointBin): packages_toast_overlay = gtc() sidebar_button = gtc() refresh_button = gtc() + search_entry = gtc() packages_list_box = gtc() # Referred to in the main window @@ -25,6 +26,13 @@ class PackagesPage(Adw.BreakpointBin): def row_select_handler(self, list_box, row): print(row.get_title()) + def filter_func(self, row): + search_text = self.search_entry.get_text().lower() + title = row.get_title().lower() + subtitle = row.get_subtitle().lower() + if search_text in title or search_text in subtitle: + return True + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -33,6 +41,7 @@ class PackagesPage(Adw.BreakpointBin): # Apply HostInfo.get_flatpaks(callback=self.generate_list) + self.packages_list_box.set_filter_func(self.filter_func) self.__class__.instance = self # Connections @@ -40,5 +49,6 @@ class PackagesPage(Adw.BreakpointBin): main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(callback=self.generate_list)) + self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) # self.packages_list_box.connect("row-selected", self.row_select_handler) From 1dca8bd73eb02bdf804386cdaefd9af7d5130b31 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 22:27:12 -0400 Subject: [PATCH 011/332] refine searching --- src/main_window/window.py | 4 ++-- src/packages_page/packages_page.blp | 2 +- src/packages_page/packages_page.py | 6 +++++- src/widgets/app_row.blp | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main_window/window.py b/src/main_window/window.py index d661159..0e06539 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -44,8 +44,8 @@ class WarehouseWindow(Adw.ApplicationWindow): if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK: self.close() - if keyval == Gdk.KEY_Escape: - self.batch_mode_button.set_active(False) + # if keyval == Gdk.KEY_Escape: + # self.batch_mode_button.set_active(False) def navigation_handler(self, _, row, hide_sidebar=True): row = row.get_child() diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index df337da..bb32f2d 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -52,7 +52,7 @@ template $PackagesPage : Adw.BreakpointBin { [top] SearchBar search_bar { search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: packages_tbv; + // key-capture-widget: packages_tbv; SearchEntry search_entry { hexpand: true; } diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index cb887ff..9774d25 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -10,6 +10,7 @@ class PackagesPage(Adw.BreakpointBin): packages_toast_overlay = gtc() sidebar_button = gtc() refresh_button = gtc() + search_bar = gtc() search_entry = gtc() packages_list_box = gtc() @@ -22,6 +23,8 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.remove_all() for package in HostInfo.flatpaks: self.packages_list_box.append(AppRow(package)) + first_row = self.packages_list_box.get_row_at_index(0) + self.packages_list_box.select_row(first_row) def row_select_handler(self, list_box, row): print(row.get_title()) @@ -50,5 +53,6 @@ class PackagesPage(Adw.BreakpointBin): self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(callback=self.generate_list)) self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) - # self.packages_list_box.connect("row-selected", self.row_select_handler) + self.search_bar.set_key_capture_widget(main_window) + self.packages_list_box.connect("row-activated", self.row_select_handler) diff --git a/src/widgets/app_row.blp b/src/widgets/app_row.blp index 7db98d9..0f9df88 100644 --- a/src/widgets/app_row.blp +++ b/src/widgets/app_row.blp @@ -2,6 +2,7 @@ using Gtk 4.0; using Adw 1; template $AppRow : Adw.ActionRow { + activatable: true; [prefix] Image image { icon-size: large; From 3f23d2a666061fd1069827514f4c2a2f0e306988 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 5 Jul 2024 23:15:58 -0400 Subject: [PATCH 012/332] Add properties page --- src/meson.build | 4 +- src/packages_page/packages_page.blp | 7 +-- src/packages_page/packages_page.py | 6 ++- src/properties_page/properties_page.blp | 70 +++++++++++++++++++++++++ src/properties_page/properties_page.py | 23 ++++++++ src/warehouse.gresource.xml | 3 +- 6 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 src/properties_page/properties_page.blp create mode 100644 src/properties_page/properties_page.py diff --git a/src/meson.build b/src/meson.build index 1ece3e5..0e958b6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,7 +7,8 @@ blueprints = custom_target('blueprints', 'widgets/app_row.blp', 'gtk/help-overlay.blp', 'main_window/window.blp', - 'packages_page/packages_page.blp' + 'packages_page/packages_page.blp', + 'properties_page/properties_page.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -54,6 +55,7 @@ warehouse_sources = [ 'widgets/error_toast.py', 'main_window/window.py', 'packages_page/packages_page.py', + 'properties_page/properties_page.py', '../data/style.css', ] diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index bb32f2d..2fb10ea 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -101,12 +101,7 @@ template $PackagesPage : Adw.BreakpointBin { } ; content: - Adw.NavigationPage { - title: "Properties"; - Adw.StatusPage { - title: "Properties Panel"; - } - } + $PropertiesPage properties_page {} ; } } diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 9774d25..cff82fd 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -2,6 +2,7 @@ from gi.repository import Adw, Gtk#, GLib, Gio, Pango from .host_info import HostInfo from .app_row import AppRow from .error_toast import ErrorToast +from .properties_page import PropertiesPage @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") class PackagesPage(Adw.BreakpointBin): @@ -12,7 +13,9 @@ class PackagesPage(Adw.BreakpointBin): refresh_button = gtc() search_bar = gtc() search_entry = gtc() + packages_split = gtc() packages_list_box = gtc() + properties_page = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -27,7 +30,7 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.select_row(first_row) def row_select_handler(self, list_box, row): - print(row.get_title()) + self.properties_page.set_properties(row.package) def filter_func(self, row): search_text = self.search_entry.get_text().lower() @@ -55,4 +58,3 @@ class PackagesPage(Adw.BreakpointBin): self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) self.search_bar.set_key_capture_widget(main_window) self.packages_list_box.connect("row-activated", self.row_select_handler) - diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp new file mode 100644 index 0000000..223239c --- /dev/null +++ b/src/properties_page/properties_page.blp @@ -0,0 +1,70 @@ +using Gtk 4.0; +using Adw 1; + +template $PropertiesPage : Adw.NavigationPage { + title: "Properties"; + Adw.ToolbarView { + [top] + Adw.HeaderBar { + + } + ScrolledWindow { + Adw.Clamp { + Box { + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + orientation: vertical; + halign: fill; + hexpand: true; + + Image app_icon { + pixel-size: 100; + margin-bottom: 18; + icon-name: "application-x-executable-symbolic"; + styles["icon-dropshadow"] + } + + Label name { + styles ["title-1"] + selectable: true; + wrap: true; + wrap-mode: word_char; + justify: center; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + } + + Label description { + styles ["title-4"] + selectable: true; + wrap: true; + wrap-mode: word_char; + justify: center; + margin-bottom: 18; + margin-start: 6; + margin-end: 6; + } + + Box { + spacing: 6; + homogeneous: true; + margin-bottom: 12; + halign: center; + Button open_app_button { + styles ["suggested-action", "pill"] + can-shrink: true; + label: "Open"; + } + Button uninstall_button { + styles ["pill"] + can-shrink: true; + label: "Uninstall"; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py new file mode 100644 index 0000000..6f82bdd --- /dev/null +++ b/src/properties_page/properties_page.py @@ -0,0 +1,23 @@ +from gi.repository import Adw, Gtk#, GLib, Gio, Pango + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") +class PropertiesPage(Adw.NavigationPage): + __gtype_name__ = 'PropertiesPage' + gtc = Gtk.Template.Child + app_icon = gtc() + name = gtc() + description = gtc() + + def set_properties(self, package): + self.set_title(package.info["id"]) + self.name.set_label(package.info["name"]) + pkg_description = package.info["description"] + self.description.set_visible(pkg_description != "") + self.description.set_label(pkg_description) + if package.icon_path: + self.app_icon.set_from_file(package.icon_path) + else: + self.app_icon.set_from_icon_name("application-x-executable-symbolic") + + def __init__(self, **kwargs): + super().__init__(**kwargs) \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 274e010..4648a2d 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -2,10 +2,11 @@ ../data/style.css + gtk/help-overlay.ui widgets/app_row.ui main_window/window.ui packages_page/packages_page.ui - gtk/help-overlay.ui + properties_page/properties_page.ui From 3e75e052608bc291028195b5f98f6eb8024a92eb Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 6 Jul 2024 01:58:36 -0400 Subject: [PATCH 013/332] Add more icons --- data/icons/folder-open-symbolic.svg | 7 +++++++ src/warehouse.gresource.xml | 1 + 2 files changed, 8 insertions(+) create mode 100644 data/icons/folder-open-symbolic.svg diff --git a/data/icons/folder-open-symbolic.svg b/data/icons/folder-open-symbolic.svg new file mode 100644 index 0000000..49db816 --- /dev/null +++ b/data/icons/folder-open-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 4648a2d..25e0e68 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -39,5 +39,6 @@ ../data/icons/snapshots-alt-symbolic.svg ../data/icons/arrow-pointing-at-line-down-symbolic.svg ../data/icons/loupe-large-symbolic.svg + ../data/icons/folder-open-symbolic.svg From 36eed763f0d9011cb84807c6ad7197f742e13768 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 6 Jul 2024 01:58:47 -0400 Subject: [PATCH 014/332] Continue work on properties page --- src/host_info.py | 14 +- src/packages_page/packages_page.blp | 4 +- src/packages_page/packages_page.py | 6 +- src/properties_page/properties_page.blp | 258 ++++++++++++++++++------ src/properties_page/properties_page.py | 52 ++++- src/widgets/error_toast.py | 15 +- 6 files changed, 275 insertions(+), 74 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index b98ebc3..33da007 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -1,6 +1,4 @@ -import gi, subprocess, os, pathlib - -gi.require_version('Gtk', '4.0') +import subprocess, os, pathlib from gi.repository import Gio, Gtk, GLib @@ -10,6 +8,14 @@ icon_theme.add_search_path(f"{home}/.local/share/flatpak/exports/share/icons") direction = Gtk.Image().get_direction() class Flatpak: + def open_data(self): + if not os.path.exists(self.data_path): + return f"Path '{self.data_path}' does not exist" + try: + Gio.AppInfo.launch_default_for_uri(f"file://{self.data_path}", None) + except GLib.GError as e: + return e + def __init__(self, columns): self.is_runtime = "runtime" in columns[12] self.info = { @@ -24,7 +30,7 @@ class Flatpak: "installed_size": columns[11], "options": columns[12], } - self.data_path = f"{home}/{columns[2]}" + self.data_path = f"{home}/.var/app/ {columns[2]}" installation = columns[7] if len(i := installation.split(' ')) > 1: self.info["installation"] = i[1].replace("(", "").replace(")", "") diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 2fb10ea..fcc9cb7 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -10,6 +10,7 @@ template $PackagesPage : Adw.BreakpointBin { setters { packages_split.collapsed: true; + packages_split.show-content: false; } } @@ -100,9 +101,6 @@ template $PackagesPage : Adw.BreakpointBin { } } ; - content: - $PropertiesPage properties_page {} - ; } } } \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index cff82fd..41cc6b9 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -15,7 +15,6 @@ class PackagesPage(Adw.BreakpointBin): search_entry = gtc() packages_split = gtc() packages_list_box = gtc() - properties_page = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -28,9 +27,12 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.append(AppRow(package)) first_row = self.packages_list_box.get_row_at_index(0) self.packages_list_box.select_row(first_row) + self.properties_page.set_properties(first_row.package) def row_select_handler(self, list_box, row): self.properties_page.set_properties(row.package) + # if self.packages_split.get_collapsed(): + self.packages_split.set_show_content(True) def filter_func(self, row): search_text = self.search_entry.get_text().lower() @@ -44,10 +46,12 @@ class PackagesPage(Adw.BreakpointBin): # Extra Object Creation self.main_window = main_window + self.properties_page = PropertiesPage(main_window) # Apply HostInfo.get_flatpaks(callback=self.generate_list) self.packages_list_box.set_filter_func(self.filter_func) + self.packages_split.set_content(self.properties_page) self.__class__.instance = self # Connections diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 223239c..a99dfcc 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -3,64 +3,208 @@ using Adw 1; template $PropertiesPage : Adw.NavigationPage { title: "Properties"; - Adw.ToolbarView { - [top] - Adw.HeaderBar { - - } - ScrolledWindow { - Adw.Clamp { - Box { - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - orientation: vertical; - halign: fill; - hexpand: true; - - Image app_icon { - pixel-size: 100; - margin-bottom: 18; - icon-name: "application-x-executable-symbolic"; - styles["icon-dropshadow"] - } - - Label name { - styles ["title-1"] - selectable: true; - wrap: true; - wrap-mode: word_char; - justify: center; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - } - - Label description { - styles ["title-4"] - selectable: true; - wrap: true; - wrap-mode: word_char; - justify: center; - margin-bottom: 18; - margin-start: 6; - margin-end: 6; - } - - Box { - spacing: 6; - homogeneous: true; - margin-bottom: 12; - halign: center; - Button open_app_button { - styles ["suggested-action", "pill"] - can-shrink: true; - label: "Open"; + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView { + [top] + Adw.HeaderBar { + [end] + MenuButton more_menu { + icon-name: "view-more-symbolic"; + popover: + Popover { + styles ["menu"] + ListBox more_list { + ListBoxRow details { + Label { + label: "Show Details in Store"; + } + } + ListBoxRow view_snapshots { + Label { + label: "View Snapshots"; + } + } + ListBoxRow copy_launch { + Label { + label: "Copy Launch Command"; + } + } + } } - Button uninstall_button { - styles ["pill"] - can-shrink: true; - label: "Uninstall"; + ; + } + } + ScrolledWindow { + Adw.Clamp { + Box { + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + orientation: vertical; + halign: fill; + hexpand: true; + + Image app_icon { + pixel-size: 100; + margin-bottom: 18; + icon-name: "application-x-executable-symbolic"; + styles["icon-dropshadow"] + } + + Label name { + styles ["title-1"] + selectable: true; + wrap: true; + wrap-mode: word_char; + justify: center; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + } + + Label description { + styles ["title-4"] + selectable: true; + wrap: true; + wrap-mode: word_char; + justify: center; + margin-bottom: 18; + margin-start: 6; + margin-end: 6; + } + + Box { + spacing: 6; + homogeneous: true; + margin-bottom: 12; + halign: center; + Button open_app_button { + styles ["suggested-action", "pill"] + can-shrink: true; + label: _("Open"); + } + Button uninstall_button { + styles ["pill"] + can-shrink: true; + label: _("Uninstall"); + } + } + + Box information { + orientation: vertical; + Adw.PreferencesGroup actions { + margin-bottom: 12; + Adw.SwitchRow pin_row { + title: _("Remove When Unused"); + subtitle: _("Pin this runtime so it's never auto removed"); + } + Adw.ActionRow data_row { + title: _("User Data"); + styles["property"] + + [suffix] + Button open_data_button { + styles["flat"] + valign: center; + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data"); + } + + [suffix] + Button trash_data_button { + styles["flat"] + valign: center; + icon-name: "user-trash-symbolic"; + tooltip-text: _("Trash User Data"); + } + } + Adw.ExpanderRow version_row { + title: _("Version"); + styles ["property"] + Adw.SwitchRow mask_row { + title: _("Disable Updates"); + subtitle: _("Mask this package so it's never updated"); + } + Adw.ActionRow downgrade_row { + title: _("Change Version"); + activatable: true; + Image { + icon-name: "right-large-symbolic"; + } + } + } + Adw.ActionRow installed_size_row { + styles ["property"] + title: _("Installed Size"); + } + } + Adw.PreferencesGroup package_info { + margin-bottom: 12; + title: _("Package Information"); + Adw.ActionRow id_row { + styles ["property"] + title: _("Application ID"); + } + Adw.ActionRow ref_row { + styles ["property"] + title: "Ref"; + } + Adw.ActionRow arch_row { + styles ["property"] + title: _("Architecture"); + } + Adw.ActionRow branch_row { + styles ["property"] + title: _("Branch"); + } + Adw.ActionRow license_row { + styles ["property"] + title: _("License"); + } + } + Adw.PreferencesGroup remote_info { + margin-bottom: 12; + title: _("Installation Information"); + Adw.ActionRow runtime_row { + styles ["property"] + title: _("Runtime"); + } + Adw.ActionRow sdk_row { + styles ["property"] + title: "SDK"; + } + Adw.ActionRow origin_row { + styles ["property"] + title: _("Origin"); + } + Adw.ActionRow collection_row { + styles ["property"] + title: _("Collection"); + } + Adw.ActionRow installation_row { + styles ["property"] + title: _("Installation"); + } + } + Adw.PreferencesGroup commit_info { + title: _("Commit Information"); + Adw.ActionRow commit_row { + styles ["property"] + title: "Commit"; + } + Adw.ActionRow parent_row { + styles ["property"] + title: _("Parent"); + } + Adw.ActionRow subject_row { + styles ["property"] + title: _("Subject"); + } + Adw.ActionRow date_row { + styles ["property"] + title: _("Date"); + } + } } } } diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 6f82bdd..498314d 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -1,23 +1,71 @@ from gi.repository import Adw, Gtk#, GLib, Gio, Pango +from .error_toast import ErrorToast @Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") class PropertiesPage(Adw.NavigationPage): __gtype_name__ = 'PropertiesPage' gtc = Gtk.Template.Child + toast_overlay = gtc() app_icon = gtc() name = gtc() description = gtc() + open_app_button = gtc() + uninstall_button = gtc() + + pin_row = gtc() + data_row = gtc() + open_data_button = gtc() + trash_data_button = gtc() + version_row = gtc() + mask_row = gtc() + downgrade_row = gtc() + installed_size_row = gtc() + + id_row = gtc() + ref_row = gtc() + arch_row = gtc() + branch_row = gtc() + license_row = gtc() + + runtime_row = gtc() + sdk_row = gtc() + origin_row = gtc() + collection_row = gtc() + installation_row = gtc() + + commit_row = gtc() + parent_row = gtc() + subject_row = gtc() + date_row = gtc() + + package = None def set_properties(self, package): + if package == self.package: + # Do not update the ui if the same app row is clicked + return + + self.package = package self.set_title(package.info["id"]) self.name.set_label(package.info["name"]) pkg_description = package.info["description"] self.description.set_visible(pkg_description != "") self.description.set_label(pkg_description) + if package.icon_path: self.app_icon.set_from_file(package.icon_path) else: self.app_icon.set_from_icon_name("application-x-executable-symbolic") - def __init__(self, **kwargs): - super().__init__(**kwargs) \ No newline at end of file + def open_data_handler(self, *args): + if error := self.package.open_data(): + self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error), self.main_window).toast) + + def __init__(self, main_window, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + self.main_window = main_window + + # Connections + self.open_data_button.connect("clicked", self.open_data_handler) \ No newline at end of file diff --git a/src/widgets/error_toast.py b/src/widgets/error_toast.py index caa89db..8d94509 100644 --- a/src/widgets/error_toast.py +++ b/src/widgets/error_toast.py @@ -10,17 +10,18 @@ class ErrorToast: # Extra Object Creation self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) - popup = Adw.AlertDialog.new(display_msg, None if format else error_msg) + popup = Adw.AlertDialog.new(display_msg, error_msg) # Apply + print(display_msg) + print(error_msg) popup.add_response("copy", _("Copy")) popup.add_response("ok", _("OK")) - if format: - lb = Gtk.Label(selectable=True, wrap=True)#, natural_wrap_mode=Gtk.NaturalWrapMode.WORD) - lb.set_markup(f"{GLib.markup_escape_text(error_msg)}") - # lb.set_label(error_msg) - # lb.set_selectable(True) - popup.set_extra_child(lb) + lb = Gtk.Label(selectable=True, wrap=True)#, natural_wrap_mode=Gtk.NaturalWrapMode.WORD) + lb.set_markup(f"{GLib.markup_escape_text(error_msg)}") + # lb.set_label(error_msg) + # lb.set_selectable(True) + popup.set_extra_child(lb) # Connections self.toast.connect("button-clicked", lambda *_: popup.present(parent_window)) From 74f0594478a1cc70d142fa34d94af20eaa061a41 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 6 Jul 2024 02:25:41 -0400 Subject: [PATCH 015/332] Work on getting cli info --- src/host_info.py | 23 ++++++++++++++++++++++- src/properties_page/properties_page.py | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/host_info.py b/src/host_info.py index 33da007..68c497d 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -8,6 +8,8 @@ icon_theme.add_search_path(f"{home}/.local/share/flatpak/exports/share/icons") direction = Gtk.Image().get_direction() class Flatpak: + cli_info = None + def open_data(self): if not os.path.exists(self.data_path): return f"Path '{self.data_path}' does not exist" @@ -16,6 +18,25 @@ class Flatpak: except GLib.GError as e: return e + def get_cli_info(self): + if not self.cli_info: + cmd = "LC_ALL=C flatpak info " + installation = self.info["installation"] + + if installation == "user": + cmd += "--user " + elif installation == "system": + cmd += "--system " + else: + cmd += f"--installation={installation} " + + cmd += self.info["ref"] + + output = subprocess.run(['flatpak-spawn', '--host', 'sh', '-c', cmd], text=True, capture_output=True) + print(output) + + return self.cli_info + def __init__(self, columns): self.is_runtime = "runtime" in columns[12] self.info = { @@ -30,7 +51,7 @@ class Flatpak: "installed_size": columns[11], "options": columns[12], } - self.data_path = f"{home}/.var/app/ {columns[2]}" + self.data_path = f"{home}/.var/app/{columns[2]}" installation = columns[7] if len(i := installation.split(' ')) > 1: self.info["installation"] = i[1].replace("(", "").replace(")", "") diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 498314d..b7836f0 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -57,6 +57,8 @@ class PropertiesPage(Adw.NavigationPage): else: self.app_icon.set_from_icon_name("application-x-executable-symbolic") + package.get_cli_info() + def open_data_handler(self, *args): if error := self.package.open_data(): self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error), self.main_window).toast) From 0d160cea019e809d95add9af27c8343ee8f7905f Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 6 Jul 2024 15:49:16 -0400 Subject: [PATCH 016/332] Populate properties panel with correct data --- src/host_info.py | 56 +++++++++++---- src/properties_page/properties_page.blp | 23 +++++-- src/properties_page/properties_page.py | 92 +++++++++++++++++++++++-- src/widgets/error_toast.py | 4 +- 4 files changed, 149 insertions(+), 26 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index 68c497d..66da716 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -8,7 +8,6 @@ icon_theme.add_search_path(f"{home}/.local/share/flatpak/exports/share/icons") direction = Gtk.Image().get_direction() class Flatpak: - cli_info = None def open_data(self): if not os.path.exists(self.data_path): @@ -18,24 +17,52 @@ class Flatpak: except GLib.GError as e: return e + def get_data_size(self, callback=None): + size = [None] + def thread(*args): + sed = "sed 's/K/ KB/; s/M/ MB/; s/G/ GB/; s/T/ TB/; s/P/ PB/;'" + size[0] = subprocess.run(['sh', '-c', f"du -sh {self.data_path} | {sed}"], capture_output=True, text=True).stdout.split("\t")[0] + def on_done(*arg): + if callback: + callback(f"~ {size[0]}") + Gio.Task.new(None, None, on_done).run_in_thread(thread) + + def trash_data(self, callback=None): + def thread(*args): + subprocess.run(['gio', 'trash', f"{self.data_path}"]) + Gio.Task.new(None, None, lambda *_: callback()).run_in_thread(thread) + def get_cli_info(self): - if not self.cli_info: - cmd = "LC_ALL=C flatpak info " - installation = self.info["installation"] + cli_info = {} + cmd = "LC_ALL=C flatpak info " + installation = self.info["installation"] - if installation == "user": - cmd += "--user " - elif installation == "system": - cmd += "--system " - else: - cmd += f"--installation={installation} " + if installation == "user": + cmd += "--user " + elif installation == "system": + cmd += "--system " + else: + cmd += f"--installation={installation} " - cmd += self.info["ref"] + cmd += self.info["ref"] - output = subprocess.run(['flatpak-spawn', '--host', 'sh', '-c', cmd], text=True, capture_output=True) - print(output) + try: + output = subprocess.run(['flatpak-spawn', '--host', 'sh', '-c', cmd], text=True, capture_output=True).stdout + except Exception as e: + raise e - return self.cli_info + lines = output.strip().split("\n") + for i, word in enumerate(lines): + word = word.strip().split(": ", 1) + if len(word) < 2: + continue + + word[0] = word[0].lower() + if "installed" in word[0]: + word[1] = word[1].replace("?", " ") + cli_info[word[0]] = word[1] + + return cli_info def __init__(self, columns): self.is_runtime = "runtime" in columns[12] @@ -52,6 +79,7 @@ class Flatpak: "options": columns[12], } self.data_path = f"{home}/.var/app/{columns[2]}" + self.data_size = -1 installation = columns[7] if len(i := installation.split(' ')) > 1: self.info["installation"] = i[1].replace("(", "").replace(")", "") diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index a99dfcc..f842d20 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -6,7 +6,8 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ToastOverlay toast_overlay { Adw.ToolbarView { [top] - Adw.HeaderBar { + Adw.HeaderBar header_bar { + show-title: false; [end] MenuButton more_menu { icon-name: "view-more-symbolic"; @@ -34,7 +35,7 @@ template $PropertiesPage : Adw.NavigationPage { ; } } - ScrolledWindow { + ScrolledWindow scrolled_window { Adw.Clamp { Box { margin-start: 12; @@ -46,6 +47,7 @@ template $PropertiesPage : Adw.NavigationPage { Image app_icon { pixel-size: 100; + margin-top: 6; margin-bottom: 18; icon-name: "application-x-executable-symbolic"; styles["icon-dropshadow"] @@ -68,7 +70,6 @@ template $PropertiesPage : Adw.NavigationPage { wrap: true; wrap-mode: word_char; justify: center; - margin-bottom: 18; margin-start: 6; margin-end: 6; } @@ -76,6 +77,7 @@ template $PropertiesPage : Adw.NavigationPage { Box { spacing: 6; homogeneous: true; + margin-top: 18; margin-bottom: 12; halign: center; Button open_app_button { @@ -95,8 +97,8 @@ template $PropertiesPage : Adw.NavigationPage { Adw.PreferencesGroup actions { margin-bottom: 12; Adw.SwitchRow pin_row { - title: _("Remove When Unused"); - subtitle: _("Pin this runtime so it's never auto removed"); + title: _("Disable Automactic Removal"); + subtitle: _("Pin this runtime to keep it installed"); } Adw.ActionRow data_row { title: _("User Data"); @@ -117,16 +119,27 @@ template $PropertiesPage : Adw.NavigationPage { icon-name: "user-trash-symbolic"; tooltip-text: _("Trash User Data"); } + + [suffix] + Spinner data_spinner { + spinning: true; + } } Adw.ExpanderRow version_row { title: _("Version"); styles ["property"] + [suffix] + Label mask_label { + label: _("Updates disabled"); + styles["warning"] + } Adw.SwitchRow mask_row { title: _("Disable Updates"); subtitle: _("Mask this package so it's never updated"); } Adw.ActionRow downgrade_row { title: _("Change Version"); + subtitle: _("Upgrade or downgrade this package"); activatable: true; Image { icon-name: "right-large-symbolic"; diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index b7836f0..060dc4d 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -1,11 +1,14 @@ from gi.repository import Adw, Gtk#, GLib, Gio, Pango from .error_toast import ErrorToast +import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") class PropertiesPage(Adw.NavigationPage): __gtype_name__ = 'PropertiesPage' gtc = Gtk.Template.Child toast_overlay = gtc() + header_bar = gtc() + scrolled_window = gtc() app_icon = gtc() name = gtc() description = gtc() @@ -16,6 +19,7 @@ class PropertiesPage(Adw.NavigationPage): data_row = gtc() open_data_button = gtc() trash_data_button = gtc() + data_spinner = gtc() version_row = gtc() mask_row = gtc() downgrade_row = gtc() @@ -40,13 +44,14 @@ class PropertiesPage(Adw.NavigationPage): package = None - def set_properties(self, package): - if package == self.package: + def set_properties(self, package, refresh=False): + if package == self.package and not refresh: # Do not update the ui if the same app row is clicked + print("skip") return self.package = package - self.set_title(package.info["id"]) + self.set_title(_("{} Properties").format(package.info["name"])) self.name.set_label(package.info["name"]) pkg_description = package.info["description"] self.description.set_visible(pkg_description != "") @@ -57,17 +62,94 @@ class PropertiesPage(Adw.NavigationPage): else: self.app_icon.set_from_icon_name("application-x-executable-symbolic") - package.get_cli_info() + self.pin_row.set_visible(package.is_runtime) + self.open_app_button.set_visible(package.is_runtime) + self.open_app_button.set_visible(not package.is_runtime) + if not package.is_runtime: + has_path = os.path.exists(package.data_path) + self.trash_data_button.set_sensitive(has_path) + self.open_data_button.set_sensitive(has_path) + + if has_path: + self.trash_data_button.set_visible(False) + self.open_data_button.set_visible(False) + self.data_spinner.set_visible(True) + self.data_row.set_subtitle(_("Loading User Data")) + + def callback(size): + self.trash_data_button.set_visible(True) + self.open_data_button.set_visible(True) + self.data_spinner.set_visible(False) + self.data_row.set_subtitle(size) + + self.package.get_data_size(lambda size: callback(size)) + else: + self.data_row.set_subtitle(_("No User Data")) + + cli_info = None + try: + cli_info = package.get_cli_info() + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e), self.main_window).toast) + return + + for key, row in self.info_rows.items(): + row.set_visible(False) + + try: + subtitle = cli_info[key] + row.set_subtitle(subtitle) + row.set_visible(True) + except KeyError: + if key == "version": + row.set_visible(True) + row.set_subtitle(_("No version information found")) + continue + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e), self.main_window).toast) + continue def open_data_handler(self, *args): if error := self.package.open_data(): self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error), self.main_window).toast) + def trash_data_handler(self, *args): + def when_done(*args): + self.set_properties(self.package, refresh=True) + self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) + try: + self.package.trash_data(when_done) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e), self.main_window).toast) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.main_window = main_window + self.info_rows = { + "version": self.version_row, + "installed": self.installed_size_row, + + "id": self.id_row, + "ref": self.ref_row, + "arch": self.arch_row, + "branch": self.branch_row, + "license": self.license_row, + + "runtime": self.runtime_row, + "sdk": self.sdk_row, + "origin": self.origin_row, + "collection": self.collection_row, + "installation": self.installation_row, + + "commit": self.commit_row, + "parent": self.parent_row, + "subject": self.subject_row, + "date": self.date_row, + } # Connections - self.open_data_button.connect("clicked", self.open_data_handler) \ No newline at end of file + self.open_data_button.connect("clicked", self.open_data_handler) + self.scrolled_window.get_vadjustment().connect("value-changed", lambda adjustment: self.header_bar.set_show_title(not adjustment.get_value() == 0)) + self.trash_data_button.connect("clicked", self.trash_data_handler) \ No newline at end of file diff --git a/src/widgets/error_toast.py b/src/widgets/error_toast.py index 8d94509..56663ec 100644 --- a/src/widgets/error_toast.py +++ b/src/widgets/error_toast.py @@ -2,7 +2,7 @@ from gi.repository import Adw, Gtk, Gdk, GLib, Pango clipboard = Gdk.Display.get_default().get_clipboard() class ErrorToast: - def __init__(self, display_msg, error_msg, parent_window, format=True): + def __init__(self, display_msg, error_msg, parent_window): def on_response(dialog, response_id): if response_id == "copy": @@ -10,7 +10,7 @@ class ErrorToast: # Extra Object Creation self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) - popup = Adw.AlertDialog.new(display_msg, error_msg) + popup = Adw.AlertDialog.new(display_msg) # Apply print(display_msg) From ad7a7473b073df2d5967e98c939b3e5204e64f97 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 01:11:22 -0400 Subject: [PATCH 017/332] Sync current work --- src/host_info.py | 106 +++++++++++++++--------- src/main_window/window.py | 4 +- src/packages_page/packages_page.py | 8 +- src/properties_page/properties_page.blp | 13 ++- src/properties_page/properties_page.py | 19 +++-- src/widgets/error_toast.py | 8 +- 6 files changed, 104 insertions(+), 54 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index 66da716..f03a6b6 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -1,6 +1,7 @@ import subprocess, os, pathlib -from gi.repository import Gio, Gtk, GLib +from gi.repository import Gio, Gtk, GLib, Adw +# from .app_row import AppRow home = f"{pathlib.Path.home()}" icon_theme = Gtk.IconTheme.new() @@ -28,9 +29,10 @@ class Flatpak: Gio.Task.new(None, None, on_done).run_in_thread(thread) def trash_data(self, callback=None): - def thread(*args): + try: subprocess.run(['gio', 'trash', f"{self.data_path}"]) - Gio.Task.new(None, None, lambda *_: callback()).run_in_thread(thread) + except Exception as e: + raise e def get_cli_info(self): cli_info = {} @@ -86,6 +88,8 @@ class Flatpak: else: self.info["installation"] = installation + self.is_masked = self.info["id"] in HostInfo.masks[self.info["installation"]] + try: self.icon_path = ( icon_theme.lookup_icon( @@ -119,52 +123,27 @@ class HostInfo: icon_theme.add_search_path(f"{i}/exports/share/icons") flatpaks = [] + remotes = [] + installations = [] + masks = {} + pins = {} @classmethod def get_flatpaks(this, callback=None): # Callback is a function to run after the host flatpaks are found this.flatpaks.clear() - - def thread(task, *args): - output = subprocess.run( - ['flatpak-spawn', '--host', - 'flatpak', 'list', '--columns=all'], - text=True, - capture_output=True, - ).stdout - lines = output.strip().split("\n") - for i in lines: - this.flatpaks.append(Flatpak(i.split("\t"))) - this.flatpaks = sorted(this.flatpaks, key=lambda flatpak: flatpak.info["name"].lower()) - - Gio.Task.new(None, None, callback).run_in_thread(thread) - - remotes = [] - installations = [] - @classmethod - def get_remotes(this, callback=None): - # Callback is a function to run after the host remotes are found this.remotes.clear() this.installations.clear() + this.masks.clear() + this.pins.clear() def thread(task, *args): - - # Get all config files for any extra installations - custom_install_config_path = "/run/host/etc/flatpak/installations.d" - if os.path.exists(custom_install_config_path): - for file in os.listdir(custom_install_config_path): - with open(f"{custom_install_config_path}/{file}", "r") as f: - for line in f: - if line.startswith("[Installation"): - # Get specifically the installation name itself - this.installations.append(line.replace("[Installation \"", "").replace("\"]", "").strip()) + # Remotes def remote_info(installation): cmd = ['flatpak-spawn', '--host', 'flatpak', 'remotes'] - if installation == "user": - cmd.append("--user") - elif installation == "system": - cmd.append("--system") + if installation == "user" or installation == "system": + cmd.append(f"--{installation}") else: cmd.append(f"--installation={installation}") output = subprocess.run( @@ -178,9 +157,62 @@ class HostInfo: if installation == "user" or installation == "system": this.installations.append(installation) + # Masks + cmd = ['flatpak-spawn', '--host', + 'flatpak', 'mask',] + if installation == "user" or installation == "system": + cmd.append(f"--{installation}") + else: + cmd.append(f"--installation={installation}") + output = subprocess.run( + cmd, text=True, + capture_output=True, + ).stdout + lines = output.strip().replace(" ", "").split("\n") + if lines[0] != '': + this.masks[installation] = lines + + # Pins + cmd = ['flatpak-spawn', '--host', + 'flatpak', 'pin',] + if installation == "user" or installation == "system": + cmd.append(f"--{installation}") + else: + cmd.append(f"--installation={installation}") + output = subprocess.run( + cmd, text=True, + capture_output=True, + ).stdout + lines = output.strip().replace(" ", "").split("\n") + if lines[0] != '': + this.pins[installation] = lines + + # Installations + # Get all config files for any extra installations + custom_install_config_path = "/run/host/etc/flatpak/installations.d" + if os.path.exists(custom_install_config_path): + for file in os.listdir(custom_install_config_path): + with open(f"{custom_install_config_path}/{file}", "r") as f: + for line in f: + if line.startswith("[Installation"): + # Get specifically the installation name itself + this.installations.append(line.replace("[Installation \"", "").replace("\"]", "").strip()) + for i in this.installations: remote_info(i) remote_info("user") remote_info("system") + # Packages + output = subprocess.run( + ['flatpak-spawn', '--host', + 'flatpak', 'list', '--columns=all'], + text=True, + capture_output=True, + ).stdout + lines = output.strip().split("\n") + for i in lines: + this.flatpaks.append(Flatpak(i.split("\t"))) + this.flatpaks = sorted(this.flatpaks, key=lambda flatpak: flatpak.info["name"].lower()) + Gio.Task.new(None, None, callback).run_in_thread(thread) \ No newline at end of file diff --git a/src/main_window/window.py b/src/main_window/window.py index 0e06539..e968bec 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -25,6 +25,7 @@ import time from gi.repository import Adw, Gdk, Gio, GLib, Gtk from .packages_page import PackagesPage from .const import Config +from .error_toast import ErrorToast @Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui") class WarehouseWindow(Adw.ApplicationWindow): @@ -75,6 +76,7 @@ class WarehouseWindow(Adw.ApplicationWindow): } # Apply + ErrorToast.main_window = self self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT) self.settings.bind("window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT) self.settings.bind("is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT) @@ -92,4 +94,4 @@ class WarehouseWindow(Adw.ApplicationWindow): self.sidebar_button.connect("clicked", lambda *_: self.main_split.set_show_sidebar(False)) self.navigation_row_listbox.get_row_at_index(0).activate() - self.main_split.set_show_sidebar(True) \ No newline at end of file + self.main_split.set_show_sidebar(True) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 41cc6b9..4dcf9e8 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -24,7 +24,12 @@ class PackagesPage(Adw.BreakpointBin): def generate_list(self, *args): self.packages_list_box.remove_all() for package in HostInfo.flatpaks: - self.packages_list_box.append(AppRow(package)) + row = AppRow(package) + app_id = package.info["id"] + installation = package.info["installation"] + if package.is_masked: + row.add_css_class("warning") + self.packages_list_box.append(row) first_row = self.packages_list_box.get_row_at_index(0) self.packages_list_box.select_row(first_row) self.properties_page.set_properties(first_row.package) @@ -50,6 +55,7 @@ class PackagesPage(Adw.BreakpointBin): # Apply HostInfo.get_flatpaks(callback=self.generate_list) + self.packages_list_box.set_filter_func(self.filter_func) self.packages_split.set_content(self.properties_page) self.__class__.instance = self diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index f842d20..4012179 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -17,17 +17,22 @@ template $PropertiesPage : Adw.NavigationPage { ListBox more_list { ListBoxRow details { Label { - label: "Show Details in Store"; + label: _("Show Details in Store"); } } - ListBoxRow view_snapshots { + ListBoxRow reinstall { Label { - label: "View Snapshots"; + label: _("Reinstall"); } } ListBoxRow copy_launch { Label { - label: "Copy Launch Command"; + label: _("Copy Launch Command"); + } + } + ListBoxRow view_snapshots { + Label { + label: _("View Snapshots"); } } } diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 060dc4d..4e22a44 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -21,6 +21,7 @@ class PropertiesPage(Adw.NavigationPage): trash_data_button = gtc() data_spinner = gtc() version_row = gtc() + mask_label = gtc() mask_row = gtc() downgrade_row = gtc() installed_size_row = gtc() @@ -90,7 +91,7 @@ class PropertiesPage(Adw.NavigationPage): try: cli_info = package.get_cli_info() except Exception as e: - self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e), self.main_window).toast) + self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e)).toast) return for key, row in self.info_rows.items(): @@ -106,21 +107,25 @@ class PropertiesPage(Adw.NavigationPage): row.set_subtitle(_("No version information found")) continue except Exception as e: - self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e), self.main_window).toast) + self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e)).toast) continue + self.mask_label.set_visible(package.is_masked) + + def ask_confirmation(self, title, description): + pass + def open_data_handler(self, *args): if error := self.package.open_data(): - self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error), self.main_window).toast) + self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error)).toast) def trash_data_handler(self, *args): - def when_done(*args): + try: + self.package.trash_data() self.set_properties(self.package, refresh=True) self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) - try: - self.package.trash_data(when_done) except Exception as e: - self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e), self.main_window).toast) + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e)).toast) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) diff --git a/src/widgets/error_toast.py b/src/widgets/error_toast.py index 56663ec..8844596 100644 --- a/src/widgets/error_toast.py +++ b/src/widgets/error_toast.py @@ -1,8 +1,9 @@ -from gi.repository import Adw, Gtk, Gdk, GLib, Pango +from gi.repository import Adw, Gtk, Gdk, GLib clipboard = Gdk.Display.get_default().get_clipboard() class ErrorToast: - def __init__(self, display_msg, error_msg, parent_window): + main_window = None + def __init__(self, display_msg, error_msg): def on_response(dialog, response_id): if response_id == "copy": @@ -24,6 +25,5 @@ class ErrorToast: popup.set_extra_child(lb) # Connections - self.toast.connect("button-clicked", lambda *_: popup.present(parent_window)) + self.toast.connect("button-clicked", lambda *_: popup.present(self.main_window)) popup.connect("response", on_response) - From a5558ec97814cb9af514c6b6fdd22a0b0a5943a7 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 15:37:32 -0400 Subject: [PATCH 018/332] Sync current work --- data/icons/error-small-symbolic.svg | 2 ++ data/icons/padlock2-symbolic.svg | 2 ++ data/icons/pin-small-symbolic.svg | 2 ++ src/host_info.py | 32 ++++++++++++------------- src/packages_page/packages_page.blp | 12 +++++++++- src/packages_page/packages_page.py | 10 +++++++- src/properties_page/properties_page.blp | 2 +- src/properties_page/properties_page.py | 6 ++--- src/warehouse.gresource.xml | 3 +++ src/widgets/app_row.blp | 22 +++++++++++++++++ src/widgets/app_row.py | 8 +++---- 11 files changed, 75 insertions(+), 26 deletions(-) create mode 100644 data/icons/error-small-symbolic.svg create mode 100644 data/icons/padlock2-symbolic.svg create mode 100644 data/icons/pin-small-symbolic.svg diff --git a/data/icons/error-small-symbolic.svg b/data/icons/error-small-symbolic.svg new file mode 100644 index 0000000..6a01fc3 --- /dev/null +++ b/data/icons/error-small-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/padlock2-symbolic.svg b/data/icons/padlock2-symbolic.svg new file mode 100644 index 0000000..d385daa --- /dev/null +++ b/data/icons/padlock2-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/pin-small-symbolic.svg b/data/icons/pin-small-symbolic.svg new file mode 100644 index 0000000..d29ea96 --- /dev/null +++ b/data/icons/pin-small-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/host_info.py b/src/host_info.py index f03a6b6..5e93ea4 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -140,22 +140,22 @@ class HostInfo: # Remotes def remote_info(installation): - cmd = ['flatpak-spawn', '--host', - 'flatpak', 'remotes'] - if installation == "user" or installation == "system": - cmd.append(f"--{installation}") - else: - cmd.append(f"--installation={installation}") - output = subprocess.run( - cmd, text=True, - capture_output=True, - ).stdout - lines = output.strip().split("\n") - for i in lines: - if i != "": - this.remotes.append(Remote(i.strip(), installation)) - if installation == "user" or installation == "system": - this.installations.append(installation) + # cmd = ['flatpak-spawn', '--host', + # 'flatpak', 'remotes'] + # if installation == "user" or installation == "system": + # cmd.append(f"--{installation}") + # else: + # cmd.append(f"--installation={installation}") + # output = subprocess.run( + # cmd, text=True, + # capture_output=True, + # ).stdout + # lines = output.strip().split("\n") + # for i in lines: + # if i != "": + # this.remotes.append(Remote(i.strip(), installation)) + # if installation == "user" or installation == "system": + # this.installations.append(installation) # Masks cmd = ['flatpak-spawn', '--host', diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index fcc9cb7..87a7ad7 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -58,7 +58,7 @@ template $PackagesPage : Adw.BreakpointBin { hexpand: true; } } - ScrolledWindow { + ScrolledWindow scrolled_window { ListBox packages_list_box { styles ["navigation-sidebar"] } @@ -103,4 +103,14 @@ template $PackagesPage : Adw.BreakpointBin { ; } } +} + +Adw.StatusPage refresh_status { + title: "Refreshing"; + description: "Refreshing the page lol haha"; + child: + Spinner spinner { + spinning: true; + } + ; } \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 4dcf9e8..b0eb4aa 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -1,4 +1,4 @@ -from gi.repository import Adw, Gtk#, GLib, Gio, Pango +from gi.repository import Adw, Gtk, GLib#, Gio, Pango from .host_info import HostInfo from .app_row import AppRow from .error_toast import ErrorToast @@ -9,12 +9,14 @@ class PackagesPage(Adw.BreakpointBin): __gtype_name__ = 'PackagesPage' gtc = Gtk.Template.Child packages_toast_overlay = gtc() + scrolled_window = gtc() sidebar_button = gtc() refresh_button = gtc() search_bar = gtc() search_entry = gtc() packages_split = gtc() packages_list_box = gtc() + refresh_status = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -33,6 +35,9 @@ class PackagesPage(Adw.BreakpointBin): first_row = self.packages_list_box.get_row_at_index(0) self.packages_list_box.select_row(first_row) self.properties_page.set_properties(first_row.package) + self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top + if self.packages_toast_overlay.get_child() != self.packages_split: + self.packages_toast_overlay.set_child(self.packages_split) def row_select_handler(self, list_box, row): self.properties_page.set_properties(row.package) @@ -64,7 +69,10 @@ class PackagesPage(Adw.BreakpointBin): main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) + + self.refresh_button.connect("clicked", lambda *_: self.packages_toast_overlay.set_child(self.refresh_status)) self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(callback=self.generate_list)) + self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) self.search_bar.set_key_capture_widget(main_window) self.packages_list_box.connect("row-activated", self.row_select_handler) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 4012179..fca788c 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -135,7 +135,7 @@ template $PropertiesPage : Adw.NavigationPage { styles ["property"] [suffix] Label mask_label { - label: _("Updates disabled"); + label: _("Updates Disabled"); styles["warning"] } Adw.SwitchRow mask_row { diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 4e22a44..decde9b 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -1,4 +1,4 @@ -from gi.repository import Adw, Gtk#, GLib, Gio, Pango +from gi.repository import Adw, Gtk,GLib#, Gio, Pango from .error_toast import ErrorToast import subprocess, os @@ -59,9 +59,9 @@ class PropertiesPage(Adw.NavigationPage): self.description.set_label(pkg_description) if package.icon_path: - self.app_icon.set_from_file(package.icon_path) + GLib.idle_add(lambda *_: self.app_icon.set_from_file(package.icon_path)) else: - self.app_icon.set_from_icon_name("application-x-executable-symbolic") + GLib.idle_add(lambda *_: self.app_icon.set_from_icon_name("application-x-executable-symbolic")) self.pin_row.set_visible(package.is_runtime) self.open_app_button.set_visible(package.is_runtime) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 25e0e68..76bd9cf 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -40,5 +40,8 @@ ../data/icons/arrow-pointing-at-line-down-symbolic.svg ../data/icons/loupe-large-symbolic.svg ../data/icons/folder-open-symbolic.svg + ../data/icons/padlock2-symbolic.svg + ../data/icons/pin-small-symbolic.svg + ../data/icons/error-small-symbolic.svg diff --git a/src/widgets/app_row.blp b/src/widgets/app_row.blp index 0f9df88..5b21f44 100644 --- a/src/widgets/app_row.blp +++ b/src/widgets/app_row.blp @@ -9,6 +9,28 @@ template $AppRow : Adw.ActionRow { icon-name: "application-x-executable-symbolic"; } [suffix] + Image eol_package_label { + icon-name: "error-small-symbolic"; + tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + styles["error"] + } + [suffix] + Image eol_runtime_label { + icon-name: "error-small-symbolic"; + tooltip-text: _("This app's runtime is End Of Life, and will not recieve any security updates"); + styles["error"] + } + [suffix] + Image pinned_status_icon { + icon-name: "pin-small-symbolic"; + tooltip-text: _("This runtime will never be automatically removed"); + } + [suffix] + Image masked_status_icon { + icon-name: "software-update-urgent-symbolic"; + tooltip-text: _("Updates are disabled for this package"); + } + [suffix] CheckButton check_button { styles["selection-mode"] visible: false; diff --git a/src/widgets/app_row.py b/src/widgets/app_row.py index 5456b9e..d6a27c0 100644 --- a/src/widgets/app_row.py +++ b/src/widgets/app_row.py @@ -15,10 +15,10 @@ class AppRow(Adw.ActionRow): self.package = package # Apply - self.set_title(package.info["name"]) - self.set_subtitle(package.info["id"]) + GLib.idle_add(lambda *_: self.set_title(package.info["name"])) + GLib.idle_add(lambda *_: self.set_subtitle(package.info["id"])) if package.icon_path: - self.image.set_from_file(package.icon_path) - self.image.add_css_class("icon-dropshadow") + GLib.idle_add(lambda *_: self.image.add_css_class("icon-dropshadow")) + GLib.idle_add(lambda *_: self.image.set_from_file(package.icon_path)) # Connections \ No newline at end of file From 4350c0db2e9c5ef4ebde3e4af2d51a29724a661d Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 21:53:46 -0400 Subject: [PATCH 019/332] add icons --- data/icons/pin-symbolic.svg | 2 ++ src/warehouse.gresource.xml | 1 + 2 files changed, 3 insertions(+) create mode 100644 data/icons/pin-symbolic.svg diff --git a/data/icons/pin-symbolic.svg b/data/icons/pin-symbolic.svg new file mode 100644 index 0000000..138e290 --- /dev/null +++ b/data/icons/pin-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 76bd9cf..305fc9f 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -41,6 +41,7 @@ ../data/icons/loupe-large-symbolic.svg ../data/icons/folder-open-symbolic.svg ../data/icons/padlock2-symbolic.svg + ../data/icons/pin-symbolic.svg ../data/icons/pin-small-symbolic.svg ../data/icons/error-small-symbolic.svg From 6111d4f2d3c1b27daa587692e2188f6db4aaade0 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 21:54:32 -0400 Subject: [PATCH 020/332] Handle masked, pinned, and EOL packages --- src/host_info.py | 34 ++++++++++++++++++++++++++++-- src/packages_page/packages_page.py | 13 ++++++++---- src/widgets/app_row.blp | 14 +++++++----- src/widgets/app_row.py | 4 ++++ 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index 5e93ea4..c5539e2 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -88,7 +88,18 @@ class Flatpak: else: self.info["installation"] = installation - self.is_masked = self.info["id"] in HostInfo.masks[self.info["installation"]] + self.is_eol = "eol=" in self.info["options"] + self.dependant_runtime = None + + try: + self.is_masked = self.info["id"] in HostInfo.masks[self.info["installation"]] + except KeyError: + self.is_masked = False + + try: + self.is_pinned = f"runtime/{self.info['ref']}" in HostInfo.pins[self.info["installation"]] + except KeyError: + self.is_pinned = False try: self.icon_path = ( @@ -123,6 +134,7 @@ class HostInfo: icon_theme.add_search_path(f"{i}/exports/share/icons") flatpaks = [] + ref_to_flatpak = {} remotes = [] installations = [] masks = {} @@ -131,6 +143,7 @@ class HostInfo: def get_flatpaks(this, callback=None): # Callback is a function to run after the host flatpaks are found this.flatpaks.clear() + this.ref_to_flatpak.clear() this.remotes.clear() this.installations.clear() this.masks.clear() @@ -212,7 +225,24 @@ class HostInfo: ).stdout lines = output.strip().split("\n") for i in lines: - this.flatpaks.append(Flatpak(i.split("\t"))) + package = Flatpak(i.split("\t")) + this.flatpaks.append(package) + this.ref_to_flatpak[package.info["ref"]] = package + + # Dependant Runtimes + output = subprocess.run( + ['flatpak-spawn', '--host', + 'flatpak', 'list', '--columns=runtime'], + text=True, + capture_output=True, + ).stdout + lines = output.strip().split("\n") + for index, runtime in enumerate(lines): + package = this.flatpaks[index] + if package.is_runtime: + continue + package.dependant_runtime = this.ref_to_flatpak[runtime] + this.flatpaks = sorted(this.flatpaks, key=lambda flatpak: flatpak.info["name"].lower()) Gio.Task.new(None, None, callback).run_in_thread(thread) \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index b0eb4aa..c8058ac 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -27,11 +27,16 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.remove_all() for package in HostInfo.flatpaks: row = AppRow(package) - app_id = package.info["id"] - installation = package.info["installation"] - if package.is_masked: - row.add_css_class("warning") + row.masked_status_icon.set_visible(package.is_masked) + row.pinned_status_icon.set_visible(package.is_pinned) + row.eol_package_package_status_icon.set_visible(package.is_eol) + try: + if not package.is_runtime: + row.eol_runtime_status_icon.set_visible(package.dependant_runtime.is_eol) + except Exception as e: + self.packages_toast_overlay.add_toast(ErrorToast(_("Error getting Flatpak '{}'").format(package.info["name"]), str(e)).toast) self.packages_list_box.append(row) + first_row = self.packages_list_box.get_row_at_index(0) self.packages_list_box.select_row(first_row) self.properties_page.set_properties(first_row.package) diff --git a/src/widgets/app_row.blp b/src/widgets/app_row.blp index 5b21f44..0d6be8e 100644 --- a/src/widgets/app_row.blp +++ b/src/widgets/app_row.blp @@ -9,26 +9,30 @@ template $AppRow : Adw.ActionRow { icon-name: "application-x-executable-symbolic"; } [suffix] - Image eol_package_label { - icon-name: "error-small-symbolic"; + Image eol_package_package_status_icon { + icon-name: "error-symbolic"; tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + visible: false; styles["error"] } [suffix] - Image eol_runtime_label { - icon-name: "error-small-symbolic"; + Image eol_runtime_status_icon { + icon-name: "error-symbolic"; tooltip-text: _("This app's runtime is End Of Life, and will not recieve any security updates"); + visible: false; styles["error"] } [suffix] Image pinned_status_icon { - icon-name: "pin-small-symbolic"; + icon-name: "pin-symbolic"; tooltip-text: _("This runtime will never be automatically removed"); + visible: false; } [suffix] Image masked_status_icon { icon-name: "software-update-urgent-symbolic"; tooltip-text: _("Updates are disabled for this package"); + visible: false; } [suffix] CheckButton check_button { diff --git a/src/widgets/app_row.py b/src/widgets/app_row.py index d6a27c0..a17a50d 100644 --- a/src/widgets/app_row.py +++ b/src/widgets/app_row.py @@ -6,6 +6,10 @@ class AppRow(Adw.ActionRow): __gtype_name__ = 'AppRow' gtc = Gtk.Template.Child image = gtc() + eol_package_package_status_icon = gtc() + eol_runtime_status_icon = gtc() + pinned_status_icon = gtc() + masked_status_icon = gtc() check_button = gtc() def __init__(self, package, **kwargs): From 3dd63b9d90f6bb0afdef88ad351ef379f87d3ada Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 21:54:49 -0400 Subject: [PATCH 021/332] Display info for masked, pinned, and EOL packages --- src/properties_page/properties_page.blp | 43 ++++++++++++++++++++----- src/properties_page/properties_page.py | 16 +++++++-- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index fca788c..4739859 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -97,14 +97,27 @@ template $PropertiesPage : Adw.NavigationPage { } } + Box eol_box { + margin-bottom: 12; + styles ["card"] + Label status_label { + margin-top: 6; + margin-bottom: 7; + margin-start: 6; + margin-end: 6; + label: _("This package is End Of Life, and will not recieve any security updates"); + styles ["heading", "error"] + halign: center; + hexpand: true; + wrap: true; + justify: center; + } + } + Box information { orientation: vertical; Adw.PreferencesGroup actions { margin-bottom: 12; - Adw.SwitchRow pin_row { - title: _("Disable Automactic Removal"); - subtitle: _("Pin this runtime to keep it installed"); - } Adw.ActionRow data_row { title: _("User Data"); styles["property"] @@ -155,6 +168,24 @@ template $PropertiesPage : Adw.NavigationPage { styles ["property"] title: _("Installed Size"); } + Adw.ActionRow runtime_row { + styles ["property"] + title: _("Runtime"); + activatable: true; + Image eol_package_package_status_icon { + icon-name: "error-symbolic"; + tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + margin-end: 6; + styles["error"] + } + Image { + icon-name: "right-large-symbolic"; + } + } + Adw.SwitchRow pin_row { + title: _("Disable Automactic Removal"); + subtitle: _("Pin this runtime to keep it installed"); + } } Adw.PreferencesGroup package_info { margin-bottom: 12; @@ -183,10 +214,6 @@ template $PropertiesPage : Adw.NavigationPage { Adw.PreferencesGroup remote_info { margin-bottom: 12; title: _("Installation Information"); - Adw.ActionRow runtime_row { - styles ["property"] - title: _("Runtime"); - } Adw.ActionRow sdk_row { styles ["property"] title: "SDK"; diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index decde9b..5072bfa 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -12,6 +12,7 @@ class PropertiesPage(Adw.NavigationPage): app_icon = gtc() name = gtc() description = gtc() + eol_box = gtc() open_app_button = gtc() uninstall_button = gtc() @@ -25,6 +26,8 @@ class PropertiesPage(Adw.NavigationPage): mask_row = gtc() downgrade_row = gtc() installed_size_row = gtc() + runtime_row = gtc() + eol_package_package_status_icon = gtc() id_row = gtc() ref_row = gtc() @@ -32,7 +35,6 @@ class PropertiesPage(Adw.NavigationPage): branch_row = gtc() license_row = gtc() - runtime_row = gtc() sdk_row = gtc() origin_row = gtc() collection_row = gtc() @@ -63,14 +65,23 @@ class PropertiesPage(Adw.NavigationPage): else: GLib.idle_add(lambda *_: self.app_icon.set_from_icon_name("application-x-executable-symbolic")) + self.eol_box.set_visible(package.is_eol) self.pin_row.set_visible(package.is_runtime) self.open_app_button.set_visible(package.is_runtime) self.open_app_button.set_visible(not package.is_runtime) - if not package.is_runtime: + self.data_row.set_visible(not package.is_runtime) + if package.is_runtime: + self.runtime_row.set_visible(False) + else: has_path = os.path.exists(package.data_path) self.trash_data_button.set_sensitive(has_path) self.open_data_button.set_sensitive(has_path) + if not self.package.dependant_runtime is None: + self.runtime_row.set_visible(True) + self.runtime_row.set_subtitle(self.package.dependant_runtime.info["name"]) + self.eol_package_package_status_icon.set_visible(self.package.dependant_runtime.is_eol) + if has_path: self.trash_data_button.set_visible(False) self.open_data_button.set_visible(False) @@ -142,7 +153,6 @@ class PropertiesPage(Adw.NavigationPage): "branch": self.branch_row, "license": self.license_row, - "runtime": self.runtime_row, "sdk": self.sdk_row, "origin": self.origin_row, "collection": self.collection_row, From e297fb3723b263f4ea01273cfec8bcd1f54695da Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 22:24:04 -0400 Subject: [PATCH 022/332] Add ability to open runtime properties from app properties --- src/packages_page/packages_page.py | 2 +- src/properties_page/properties_page.blp | 481 ++++++++++++------------ src/properties_page/properties_page.py | 25 +- 3 files changed, 263 insertions(+), 245 deletions(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index c8058ac..37fbdee 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -46,7 +46,7 @@ class PackagesPage(Adw.BreakpointBin): def row_select_handler(self, list_box, row): self.properties_page.set_properties(row.package) - # if self.packages_split.get_collapsed(): + self.properties_page.nav_view.pop() self.packages_split.set_show_content(True) def filter_func(self, row): diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 4739859..38c001b 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -2,252 +2,255 @@ using Gtk 4.0; using Adw 1; template $PropertiesPage : Adw.NavigationPage { - title: "Properties"; - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView { - [top] - Adw.HeaderBar header_bar { - show-title: false; - [end] - MenuButton more_menu { - icon-name: "view-more-symbolic"; - popover: - Popover { - styles ["menu"] - ListBox more_list { - ListBoxRow details { - Label { - label: _("Show Details in Store"); - } - } - ListBoxRow reinstall { - Label { - label: _("Reinstall"); - } - } - ListBoxRow copy_launch { - Label { - label: _("Copy Launch Command"); - } - } - ListBoxRow view_snapshots { - Label { - label: _("View Snapshots"); - } - } - } - } - ; - } - } - ScrolledWindow scrolled_window { - Adw.Clamp { - Box { - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - orientation: vertical; - halign: fill; - hexpand: true; - - Image app_icon { - pixel-size: 100; - margin-top: 6; - margin-bottom: 18; - icon-name: "application-x-executable-symbolic"; - styles["icon-dropshadow"] - } - - Label name { - styles ["title-1"] - selectable: true; - wrap: true; - wrap-mode: word_char; - justify: center; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - } - - Label description { - styles ["title-4"] - selectable: true; - wrap: true; - wrap-mode: word_char; - justify: center; - margin-start: 6; - margin-end: 6; - } - - Box { - spacing: 6; - homogeneous: true; - margin-top: 18; - margin-bottom: 12; - halign: center; - Button open_app_button { - styles ["suggested-action", "pill"] - can-shrink: true; - label: _("Open"); - } - Button uninstall_button { - styles ["pill"] - can-shrink: true; - label: _("Uninstall"); - } - } - - Box eol_box { - margin-bottom: 12; - styles ["card"] - Label status_label { - margin-top: 6; - margin-bottom: 7; - margin-start: 6; - margin-end: 6; - label: _("This package is End Of Life, and will not recieve any security updates"); - styles ["heading", "error"] - halign: center; - hexpand: true; - wrap: true; - justify: center; - } - } - - Box information { - orientation: vertical; - Adw.PreferencesGroup actions { - margin-bottom: 12; - Adw.ActionRow data_row { - title: _("User Data"); - styles["property"] - - [suffix] - Button open_data_button { - styles["flat"] - valign: center; - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open User Data"); - } - - [suffix] - Button trash_data_button { - styles["flat"] - valign: center; - icon-name: "user-trash-symbolic"; - tooltip-text: _("Trash User Data"); - } - - [suffix] - Spinner data_spinner { - spinning: true; - } - } - Adw.ExpanderRow version_row { - title: _("Version"); - styles ["property"] - [suffix] - Label mask_label { - label: _("Updates Disabled"); - styles["warning"] - } - Adw.SwitchRow mask_row { - title: _("Disable Updates"); - subtitle: _("Mask this package so it's never updated"); - } - Adw.ActionRow downgrade_row { - title: _("Change Version"); - subtitle: _("Upgrade or downgrade this package"); - activatable: true; - Image { - icon-name: "right-large-symbolic"; + title: "Outer Page"; + Adw.NavigationView nav_view { + Adw.NavigationPage inner_nav_page { + title: "Inner Page"; + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar { + show-title: false; + [end] + MenuButton more_menu { + icon-name: "view-more-symbolic"; + popover: + Popover { + styles ["menu"] + ListBox more_list { + ListBoxRow details { + Label { + label: _("Show Details in Store"); + } + } + ListBoxRow reinstall { + Label { + label: _("Reinstall"); + } + } + ListBoxRow copy_launch { + Label { + label: _("Copy Launch Command"); + } + } + ListBoxRow view_snapshots { + Label { + label: _("View Snapshots"); + } } } } - Adw.ActionRow installed_size_row { - styles ["property"] - title: _("Installed Size"); + ; + } + } + ScrolledWindow scrolled_window { + Adw.Clamp { + Box { + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + orientation: vertical; + halign: fill; + hexpand: true; + + Image app_icon { + pixel-size: 100; + margin-top: 6; + margin-bottom: 18; + icon-name: "application-x-executable-symbolic"; + styles["icon-dropshadow"] } - Adw.ActionRow runtime_row { - styles ["property"] - title: _("Runtime"); - activatable: true; - Image eol_package_package_status_icon { - icon-name: "error-symbolic"; - tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + + Label name { + styles ["title-1"] + wrap: true; + wrap-mode: word_char; + justify: center; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + } + + Label description { + styles ["title-4"] + wrap: true; + wrap-mode: word_char; + justify: center; + margin-start: 6; + margin-end: 6; + } + + Box { + spacing: 6; + homogeneous: true; + margin-top: 18; + margin-bottom: 12; + halign: center; + Button open_app_button { + styles ["suggested-action", "pill"] + can-shrink: true; + label: _("Open"); + } + Button uninstall_button { + styles ["pill"] + can-shrink: true; + label: _("Uninstall"); + } + } + + Box eol_box { + margin-bottom: 12; + styles ["card"] + Label status_label { + margin-top: 6; + margin-bottom: 7; + margin-start: 6; margin-end: 6; - styles["error"] - } - Image { - icon-name: "right-large-symbolic"; + label: _("This package is End Of Life, and will not recieve any security updates"); + styles ["heading", "error"] + halign: center; + hexpand: true; + wrap: true; + justify: center; } } - Adw.SwitchRow pin_row { - title: _("Disable Automactic Removal"); - subtitle: _("Pin this runtime to keep it installed"); - } - } - Adw.PreferencesGroup package_info { - margin-bottom: 12; - title: _("Package Information"); - Adw.ActionRow id_row { - styles ["property"] - title: _("Application ID"); - } - Adw.ActionRow ref_row { - styles ["property"] - title: "Ref"; - } - Adw.ActionRow arch_row { - styles ["property"] - title: _("Architecture"); - } - Adw.ActionRow branch_row { - styles ["property"] - title: _("Branch"); - } - Adw.ActionRow license_row { - styles ["property"] - title: _("License"); - } - } - Adw.PreferencesGroup remote_info { - margin-bottom: 12; - title: _("Installation Information"); - Adw.ActionRow sdk_row { - styles ["property"] - title: "SDK"; - } - Adw.ActionRow origin_row { - styles ["property"] - title: _("Origin"); - } - Adw.ActionRow collection_row { - styles ["property"] - title: _("Collection"); - } - Adw.ActionRow installation_row { - styles ["property"] - title: _("Installation"); - } - } - Adw.PreferencesGroup commit_info { - title: _("Commit Information"); - Adw.ActionRow commit_row { - styles ["property"] - title: "Commit"; - } - Adw.ActionRow parent_row { - styles ["property"] - title: _("Parent"); - } - Adw.ActionRow subject_row { - styles ["property"] - title: _("Subject"); - } - Adw.ActionRow date_row { - styles ["property"] - title: _("Date"); + + Box information { + orientation: vertical; + Adw.PreferencesGroup actions { + margin-bottom: 12; + Adw.ActionRow data_row { + title: _("User Data"); + styles["property"] + + [suffix] + Button open_data_button { + styles["flat"] + valign: center; + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data"); + } + + [suffix] + Button trash_data_button { + styles["flat"] + valign: center; + icon-name: "user-trash-symbolic"; + tooltip-text: _("Trash User Data"); + } + + [suffix] + Spinner data_spinner { + spinning: true; + } + } + Adw.ExpanderRow version_row { + title: _("Version"); + styles ["property"] + [suffix] + Label mask_label { + label: _("Updates Disabled"); + styles["warning"] + } + Adw.SwitchRow mask_row { + title: _("Disable Updates"); + subtitle: _("Mask this package so it's never updated"); + } + Adw.ActionRow downgrade_row { + title: _("Change Version"); + subtitle: _("Upgrade or downgrade this package"); + activatable: true; + Image { + icon-name: "right-large-symbolic"; + } + } + } + Adw.ActionRow installed_size_row { + styles ["property"] + title: _("Installed Size"); + } + Adw.ActionRow runtime_row { + styles ["property"] + title: _("Runtime"); + activatable: true; + Image eol_package_package_status_icon { + icon-name: "error-symbolic"; + tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + margin-end: 6; + styles["error"] + } + Image { + icon-name: "right-large-symbolic"; + } + } + Adw.SwitchRow pin_row { + title: _("Disable Automactic Removal"); + subtitle: _("Pin this runtime to keep it installed"); + } + } + Adw.PreferencesGroup package_info { + margin-bottom: 12; + title: _("Package Information"); + Adw.ActionRow id_row { + styles ["property"] + title: _("Application ID"); + } + Adw.ActionRow ref_row { + styles ["property"] + title: "Ref"; + } + Adw.ActionRow arch_row { + styles ["property"] + title: _("Architecture"); + } + Adw.ActionRow branch_row { + styles ["property"] + title: _("Branch"); + } + Adw.ActionRow license_row { + styles ["property"] + title: _("License"); + } + } + Adw.PreferencesGroup remote_info { + margin-bottom: 12; + title: _("Installation Information"); + Adw.ActionRow sdk_row { + styles ["property"] + title: "SDK"; + } + Adw.ActionRow origin_row { + styles ["property"] + title: _("Origin"); + } + Adw.ActionRow collection_row { + styles ["property"] + title: _("Collection"); + } + Adw.ActionRow installation_row { + styles ["property"] + title: _("Installation"); + } + } + Adw.PreferencesGroup commit_info { + title: _("Commit Information"); + Adw.ActionRow commit_row { + styles ["property"] + title: "Commit"; + } + Adw.ActionRow parent_row { + styles ["property"] + title: _("Parent"); + } + Adw.ActionRow subject_row { + styles ["property"] + title: _("Subject"); + } + Adw.ActionRow date_row { + styles ["property"] + title: _("Date"); + } + } } } } diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 5072bfa..427bd7b 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -6,6 +6,8 @@ import subprocess, os class PropertiesPage(Adw.NavigationPage): __gtype_name__ = 'PropertiesPage' gtc = Gtk.Template.Child + nav_view = gtc() + inner_nav_page = gtc() toast_overlay = gtc() header_bar = gtc() scrolled_window = gtc() @@ -50,12 +52,19 @@ class PropertiesPage(Adw.NavigationPage): def set_properties(self, package, refresh=False): if package == self.package and not refresh: # Do not update the ui if the same app row is clicked - print("skip") return - + self.package = package - self.set_title(_("{} Properties").format(package.info["name"])) - self.name.set_label(package.info["name"]) + + pkg_name = package.info["name"] + if pkg_name != "": + self.inner_nav_page.set_title(_("{} Properties").format(package.info["name"])) + self.name.set_visible(True) + self.name.set_label(pkg_name) + else: + self.name.set_visible(False) + self.inner_nav_page.set_title(_("Properties")) + pkg_description = package.info["description"] self.description.set_visible(pkg_description != "") self.description.set_label(pkg_description) @@ -138,6 +147,11 @@ class PropertiesPage(Adw.NavigationPage): except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e)).toast) + def runtime_row_click_handler(self, *args): + new_page = self.__class__(self.main_window) + new_page.set_properties(self.package.dependant_runtime) + self.nav_view.push(new_page) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -167,4 +181,5 @@ class PropertiesPage(Adw.NavigationPage): # Connections self.open_data_button.connect("clicked", self.open_data_handler) self.scrolled_window.get_vadjustment().connect("value-changed", lambda adjustment: self.header_bar.set_show_title(not adjustment.get_value() == 0)) - self.trash_data_button.connect("clicked", self.trash_data_handler) \ No newline at end of file + self.trash_data_button.connect("clicked", self.trash_data_handler) + self.runtime_row.connect("activated", self.runtime_row_click_handler) \ No newline at end of file From f7043a046e47222b373f378314169b5ccadd360a Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 23:20:26 -0400 Subject: [PATCH 023/332] add icons --- data/icons/copy-symbolic.svg | 2 ++ src/warehouse.gresource.xml | 1 + 2 files changed, 3 insertions(+) create mode 100644 data/icons/copy-symbolic.svg diff --git a/data/icons/copy-symbolic.svg b/data/icons/copy-symbolic.svg new file mode 100644 index 0000000..7aad5a3 --- /dev/null +++ b/data/icons/copy-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 305fc9f..ba08bc8 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -44,5 +44,6 @@ ../data/icons/pin-symbolic.svg ../data/icons/pin-small-symbolic.svg ../data/icons/error-small-symbolic.svg + ../data/icons/copy-symbolic.svg From 896be1118d68c30e8a2b6717cefc76fe22a0f8f2 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 23:20:55 -0400 Subject: [PATCH 024/332] enable opening apps, handle trash errors better --- src/host_info.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index c5539e2..8d12599 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -1,6 +1,6 @@ import subprocess, os, pathlib -from gi.repository import Gio, Gtk, GLib, Adw +from gi.repository import Gio, Gtk, GLib, Adw, Gdk # from .app_row import AppRow home = f"{pathlib.Path.home()}" @@ -10,6 +10,20 @@ direction = Gtk.Image().get_direction() class Flatpak: + def open_app(self, callback=None): + self.failed_app_run = None + def thread(*args): + if self.is_runtime: + self.failed_app_run = "error: cannot open a runtime" + try: + subprocess.run(['flatpak-spawn', '--host', 'flatpak', 'run', f"{self.info['ref']}"], capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as cpe: + self.failed_app_run = cpe.stderr + except Exception as e: + self.failed_app_run = e + + Gio.Task.new(None, None, callback).run_in_thread(thread) + def open_data(self): if not os.path.exists(self.data_path): return f"Path '{self.data_path}' does not exist" @@ -30,7 +44,9 @@ class Flatpak: def trash_data(self, callback=None): try: - subprocess.run(['gio', 'trash', f"{self.data_path}"]) + subprocess.run(['gio', 'trash', f"{self.data_path}"], capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as cpe: + raise cpe except Exception as e: raise e @@ -90,6 +106,7 @@ class Flatpak: self.is_eol = "eol=" in self.info["options"] self.dependant_runtime = None + self.failed_app_run = None try: self.is_masked = self.info["id"] in HostInfo.masks[self.info["installation"]] @@ -121,6 +138,7 @@ class Remote: class HostInfo: home = home + clipboard = Gdk.Display.get_default().get_clipboard() # Get all possible installation icon theme dirs output = subprocess.run( From 1ca9f12caa90b835ff5280b2c90f33b4bff0c763 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 23:21:20 -0400 Subject: [PATCH 025/332] Make property rows copyable --- src/properties_page/properties_page.blp | 56 +++++++++++++++++++++++++ src/properties_page/properties_page.py | 28 ++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 38c001b..b212e9d 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -168,6 +168,10 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ActionRow installed_size_row { styles ["property"] title: _("Installed Size"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow runtime_row { styles ["property"] @@ -194,22 +198,42 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ActionRow id_row { styles ["property"] title: _("Application ID"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow ref_row { styles ["property"] title: "Ref"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow arch_row { styles ["property"] title: _("Architecture"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow branch_row { styles ["property"] title: _("Branch"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow license_row { styles ["property"] title: _("License"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } } Adw.PreferencesGroup remote_info { @@ -218,18 +242,34 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ActionRow sdk_row { styles ["property"] title: "SDK"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow origin_row { styles ["property"] title: _("Origin"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow collection_row { styles ["property"] title: _("Collection"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow installation_row { styles ["property"] title: _("Installation"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } } Adw.PreferencesGroup commit_info { @@ -237,18 +277,34 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ActionRow commit_row { styles ["property"] title: "Commit"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow parent_row { styles ["property"] title: _("Parent"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow subject_row { styles ["property"] title: _("Subject"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } Adw.ActionRow date_row { styles ["property"] title: _("Date"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } } } diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 427bd7b..921eee7 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -1,5 +1,6 @@ from gi.repository import Adw, Gtk,GLib#, Gio, Pango from .error_toast import ErrorToast +from .host_info import HostInfo import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") @@ -106,6 +107,7 @@ class PropertiesPage(Adw.NavigationPage): self.package.get_data_size(lambda size: callback(size)) else: self.data_row.set_subtitle(_("No User Data")) + self.data_spinner.set_visible(False) cli_info = None try: @@ -144,14 +146,30 @@ class PropertiesPage(Adw.NavigationPage): self.package.trash_data() self.set_properties(self.package, refresh=True) self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) + except subprocess.CalledProcessError as cpe: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(cpe.stderr)).toast) except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e)).toast) - def runtime_row_click_handler(self, *args): + def runtime_row_handler(self, *args): new_page = self.__class__(self.main_window) new_page.set_properties(self.package.dependant_runtime) self.nav_view.push(new_page) + def open_app_handler(self, *args): + def callback(*args): + if fail := self.package.failed_app_run: + fail = fail.stderr if type(fail) == subprocess.CalledProcessError else fail + self.toast_overlay.add_toast(ErrorToast(_("Could not open {}").format(self.package.info["name"]), str(fail)).toast) + else: + self.toast_overlay.add_toast(Adw.Toast(title=_("Opened {}").format(self.package.info["name"]))) + + self.package.open_app(callback) + + def copy_handler(self, row): + HostInfo.clipboard.set(row.get_subtitle()) + self.toast_overlay.add_toast(Adw.Toast(title=_("Copeid {}").format(row.get_title()))) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -182,4 +200,10 @@ class PropertiesPage(Adw.NavigationPage): self.open_data_button.connect("clicked", self.open_data_handler) self.scrolled_window.get_vadjustment().connect("value-changed", lambda adjustment: self.header_bar.set_show_title(not adjustment.get_value() == 0)) self.trash_data_button.connect("clicked", self.trash_data_handler) - self.runtime_row.connect("activated", self.runtime_row_click_handler) \ No newline at end of file + self.runtime_row.connect("activated", self.runtime_row_handler) + self.open_app_button.connect("clicked", self.open_app_handler) + for key in self.info_rows: + row = self.info_rows[key] + if type(row) != Adw.ActionRow: + continue + row.connect("activated", self.copy_handler) \ No newline at end of file From b9ff6224dc8108b17e5e7146e8a3167720df6843 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 23:21:37 -0400 Subject: [PATCH 026/332] change to use HostInfo clipboard --- src/widgets/error_toast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/error_toast.py b/src/widgets/error_toast.py index 8844596..f22d33b 100644 --- a/src/widgets/error_toast.py +++ b/src/widgets/error_toast.py @@ -1,5 +1,5 @@ from gi.repository import Adw, Gtk, Gdk, GLib -clipboard = Gdk.Display.get_default().get_clipboard() +from .host_info import HostInfo class ErrorToast: main_window = None @@ -7,7 +7,7 @@ class ErrorToast: def on_response(dialog, response_id): if response_id == "copy": - clipboard.set(error_msg) + HostInfo.clipboard.set(error_msg) # Extra Object Creation self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) From 791b154a8d555f0eae1728cbc31fa86780fb1015 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 7 Jul 2024 23:32:47 -0400 Subject: [PATCH 027/332] Show toast when opening an app --- src/properties_page/properties_page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 921eee7..9450a0d 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -157,12 +157,12 @@ class PropertiesPage(Adw.NavigationPage): self.nav_view.push(new_page) def open_app_handler(self, *args): + self.toast_overlay.add_toast(Adw.Toast(title=_("Opened {}").format(self.package.info["name"]))) + def callback(*args): if fail := self.package.failed_app_run: fail = fail.stderr if type(fail) == subprocess.CalledProcessError else fail self.toast_overlay.add_toast(ErrorToast(_("Could not open {}").format(self.package.info["name"]), str(fail)).toast) - else: - self.toast_overlay.add_toast(Adw.Toast(title=_("Opened {}").format(self.package.info["name"]))) self.package.open_app(callback) From 94c097867124b3e1a0d428faca12309893652746 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 8 Jul 2024 15:45:45 -0400 Subject: [PATCH 028/332] Add ability toggle mask for flatpak --- src/host_info.py | 25 ++++++++++++++++++++++++- src/properties_page/properties_page.blp | 16 ++++++++++++++-- src/properties_page/properties_page.py | 24 +++++++++++++++++++++--- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index 8d12599..19dfc07 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -18,7 +18,7 @@ class Flatpak: try: subprocess.run(['flatpak-spawn', '--host', 'flatpak', 'run', f"{self.info['ref']}"], capture_output=True, text=True, check=True) except subprocess.CalledProcessError as cpe: - self.failed_app_run = cpe.stderr + self.failed_app_run = cpe except Exception as e: self.failed_app_run = e @@ -50,6 +50,28 @@ class Flatpak: except Exception as e: raise e + def set_mask(self, should_mask, callback=None): + self.failed_mask = None + def thread(*args): + cmd = ['flatpak-spawn', '--host', 'flatpak', 'mask', self.info["id"]] + installation = self.info["installation"] + if installation == "user" or installation == "system": + cmd.append(f"--{installation}") + else: + cmd.append(f"--installation={installation}") + + if not should_mask: + cmd.append("--remove") + + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + self.failed_mask = cpe + except Exception as e: + self.failed_mask = e + + Gio.Task.new(None, None, callback).run_in_thread(thread) + def get_cli_info(self): cli_info = {} cmd = "LC_ALL=C flatpak info " @@ -107,6 +129,7 @@ class Flatpak: self.is_eol = "eol=" in self.info["options"] self.dependant_runtime = None self.failed_app_run = None + self.failed_mask = None try: self.is_masked = self.info["id"] in HostInfo.masks[self.info["installation"]] diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index b212e9d..ff1eb6f 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -152,9 +152,15 @@ template $PropertiesPage : Adw.NavigationPage { label: _("Updates Disabled"); styles["warning"] } - Adw.SwitchRow mask_row { + Adw.ActionRow mask_row { title: _("Disable Updates"); subtitle: _("Mask this package so it's never updated"); + activatable: true; + Gtk.Switch mask_switch { + valign: center; + can-focus: false; + can-target: false; + } } Adw.ActionRow downgrade_row { title: _("Change Version"); @@ -187,9 +193,15 @@ template $PropertiesPage : Adw.NavigationPage { icon-name: "right-large-symbolic"; } } - Adw.SwitchRow pin_row { + Adw.ActionRow pin_row { title: _("Disable Automactic Removal"); subtitle: _("Pin this runtime to keep it installed"); + activatable: true; + Gtk.Switch pin_switch { + valign: center; + can-focus: false; + can-target: false; + } } } Adw.PreferencesGroup package_info { diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 9450a0d..2b384cf 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -20,6 +20,7 @@ class PropertiesPage(Adw.NavigationPage): uninstall_button = gtc() pin_row = gtc() + pin_switch = gtc() data_row = gtc() open_data_button = gtc() trash_data_button = gtc() @@ -27,6 +28,7 @@ class PropertiesPage(Adw.NavigationPage): version_row = gtc() mask_label = gtc() mask_row = gtc() + mask_switch = gtc() downgrade_row = gtc() installed_size_row = gtc() runtime_row = gtc() @@ -133,9 +135,9 @@ class PropertiesPage(Adw.NavigationPage): continue self.mask_label.set_visible(package.is_masked) + self.mask_switch.set_active(package.is_masked) - def ask_confirmation(self, title, description): - pass + self.pin_switch.set_active(package.is_pinned) def open_data_handler(self, *args): if error := self.package.open_data(): @@ -147,10 +149,25 @@ class PropertiesPage(Adw.NavigationPage): self.set_properties(self.package, refresh=True) self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) except subprocess.CalledProcessError as cpe: - self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(cpe.stderr)).toast) + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), cpe.stderr).toast) except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e)).toast) + def set_mask_handler(self, *args): + state = not self.mask_switch.get_active() + def callback(*args): + if fail := self.package.failed_mask: + response = _("Could not Disable Updates") if state else _("Could not Enable Updates") + fail = fail.stderr if type(fail) == subprocess.CalledProcessError else fail + self.toast_overlay.add_toast(ErrorToast(response, str(fail)).toast) + GLib.idle_add(lambda *_: self.mask_switch.set_active(not state)) + else: + response = _("Disabled Updates") if state else _("Enabled Updates") + self.toast_overlay.add_toast(Adw.Toast(title=_(response))) + GLib.idle_add(lambda *_: self.mask_switch.set_active(state)) + + self.package.set_mask(state, callback) + def runtime_row_handler(self, *args): new_page = self.__class__(self.main_window) new_page.set_properties(self.package.dependant_runtime) @@ -202,6 +219,7 @@ class PropertiesPage(Adw.NavigationPage): self.trash_data_button.connect("clicked", self.trash_data_handler) self.runtime_row.connect("activated", self.runtime_row_handler) self.open_app_button.connect("clicked", self.open_app_handler) + self.mask_row.connect("activated", self.set_mask_handler) for key in self.info_rows: row = self.info_rows[key] if type(row) != Adw.ActionRow: From 91abb72adafd95e6434507d20d00522cb0e3ea20 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 8 Jul 2024 16:08:43 -0400 Subject: [PATCH 029/332] Add ability to toggle pinning of runtime --- src/host_info.py | 25 +++++++++++++++++++++++++ src/properties_page/properties_page.py | 18 +++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/host_info.py b/src/host_info.py index 19dfc07..7c61c40 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -72,6 +72,31 @@ class Flatpak: Gio.Task.new(None, None, callback).run_in_thread(thread) + def set_pin(self, should_pin, callback=None): + self.failed_pin = None + if not self.is_runtime: + self.failed_pin = "Cannot pin an application" + + def thread(*args): + cmd = ['flatpak-spawn', '--host', 'flatpak', 'pin', f"runtime/{self.info['ref']}"] + installation = self.info["installation"] + if installation == "user" or installation == "system": + cmd.append(f"--{installation}") + else: + cmd.append(f"--installation={installation}") + + if not should_pin: + cmd.append("--remove") + + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + self.failed_pin = cpe + except Exception as e: + self.failed_mask = e + + Gio.Task.new(None, None, callback).run_in_thread(thread) + def get_cli_info(self): cli_info = {} cmd = "LC_ALL=C flatpak info " diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 2b384cf..5c6bcad 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -163,11 +163,26 @@ class PropertiesPage(Adw.NavigationPage): GLib.idle_add(lambda *_: self.mask_switch.set_active(not state)) else: response = _("Disabled Updates") if state else _("Enabled Updates") - self.toast_overlay.add_toast(Adw.Toast(title=_(response))) + self.toast_overlay.add_toast(Adw.Toast(title=response)) GLib.idle_add(lambda *_: self.mask_switch.set_active(state)) self.package.set_mask(state, callback) + def set_pin_handler(self, *args): + state = not self.pin_switch.get_active() + def callback(*args): + if fail := self.package.failed_pin: + response = _("Could not Disable Autoremoval") if state else _("Could not Enable Autoremoval") + fail = fail.stderr if type(fail) == subprocess.CalledProcessError else fail + self.toast_overlay.add_toast(ErrorToast(response, str(fail)).toast) + GLib.idle_add(lambda *_: self.pin_switch.set_active(not state)) + else: + response = _("Disabled Autoremoval") if state else _("Enabled Autoremoval") + self.toast_overlay.add_toast(Adw.Toast(title=response)) + GLib.idle_add(lambda *_: self.pin_switch.set_active(state)) + + self.package.set_pin(state, callback) + def runtime_row_handler(self, *args): new_page = self.__class__(self.main_window) new_page.set_properties(self.package.dependant_runtime) @@ -220,6 +235,7 @@ class PropertiesPage(Adw.NavigationPage): self.runtime_row.connect("activated", self.runtime_row_handler) self.open_app_button.connect("clicked", self.open_app_handler) self.mask_row.connect("activated", self.set_mask_handler) + self.pin_row.connect("activated", self.set_pin_handler) for key in self.info_rows: row = self.info_rows[key] if type(row) != Adw.ActionRow: From 5c0a58aef23ea7ffeecbe062aea51589ad783cea Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 8 Jul 2024 19:17:33 -0400 Subject: [PATCH 030/332] some code for selection mode --- src/packages_page/packages_page.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 37fbdee..917394c 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -17,6 +17,7 @@ class PackagesPage(Adw.BreakpointBin): packages_split = gtc() packages_list_box = gtc() refresh_status = gtc() + select_button = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -30,6 +31,7 @@ class PackagesPage(Adw.BreakpointBin): row.masked_status_icon.set_visible(package.is_masked) row.pinned_status_icon.set_visible(package.is_pinned) row.eol_package_package_status_icon.set_visible(package.is_eol) + row.check_button.set_visible(self.select_button.get_active()) try: if not package.is_runtime: row.eol_runtime_status_icon.set_visible(package.dependant_runtime.is_eol) @@ -56,6 +58,16 @@ class PackagesPage(Adw.BreakpointBin): if search_text in title or search_text in subtitle: 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 select_button_handler(self, button): + self.set_selection_mode(button.get_active()) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -81,3 +93,5 @@ class PackagesPage(Adw.BreakpointBin): self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) self.search_bar.set_key_capture_widget(main_window) self.packages_list_box.connect("row-activated", self.row_select_handler) + + self.select_button.connect("clicked", self.select_button_handler) From b1eff69205bc41ee7ee7f188ee7209d21519ccdf Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 8 Jul 2024 19:34:00 -0400 Subject: [PATCH 031/332] fix typo --- src/widgets/app_row.blp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/app_row.blp b/src/widgets/app_row.blp index 0d6be8e..beb9a35 100644 --- a/src/widgets/app_row.blp +++ b/src/widgets/app_row.blp @@ -11,14 +11,14 @@ template $AppRow : Adw.ActionRow { [suffix] Image eol_package_package_status_icon { icon-name: "error-symbolic"; - tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + tooltip-text: _("This package is End Of Life, and will not receive any security updates"); visible: false; styles["error"] } [suffix] Image eol_runtime_status_icon { icon-name: "error-symbolic"; - tooltip-text: _("This app's runtime is End Of Life, and will not recieve any security updates"); + tooltip-text: _("This app's runtime is End Of Life, and will not receive any security updates"); visible: false; styles["error"] } From 9e3561d45420b4636a108dbeea36c868aca85f65 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 8 Jul 2024 21:30:31 -0400 Subject: [PATCH 032/332] add icons --- data/icons/double-ended-arrows-vertical-symbolic.svg | 2 ++ src/warehouse.gresource.xml | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 data/icons/double-ended-arrows-vertical-symbolic.svg diff --git a/data/icons/double-ended-arrows-vertical-symbolic.svg b/data/icons/double-ended-arrows-vertical-symbolic.svg new file mode 100644 index 0000000..c6a52a5 --- /dev/null +++ b/data/icons/double-ended-arrows-vertical-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index ba08bc8..98b64a1 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -7,6 +7,7 @@ main_window/window.ui packages_page/packages_page.ui properties_page/properties_page.ui + change_version_page/change_version_page.ui @@ -45,5 +46,6 @@ ../data/icons/pin-small-symbolic.svg ../data/icons/error-small-symbolic.svg ../data/icons/copy-symbolic.svg + ../data/icons/double-ended-arrows-vertical-symbolic.svg From 0a21625a8c3a5fa33011568a2396587be9177542 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 8 Jul 2024 21:30:48 -0400 Subject: [PATCH 033/332] Add downgrades page --- .../change_version_page.blp | 59 +++++++++++++++ .../change_version_page.py | 73 +++++++++++++++++++ src/meson.build | 2 + src/properties_page/properties_page.blp | 3 +- src/properties_page/properties_page.py | 8 +- 5 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 src/change_version_page/change_version_page.blp create mode 100644 src/change_version_page/change_version_page.py diff --git a/src/change_version_page/change_version_page.blp b/src/change_version_page/change_version_page.blp new file mode 100644 index 0000000..7a87cc6 --- /dev/null +++ b/src/change_version_page/change_version_page.blp @@ -0,0 +1,59 @@ +using Gtk 4.0; +using Adw 1; + +template $ChangeVersionPage : Adw.NavigationPage { + title: _("Change Versions"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + } + Adw.ToastOverlay { + ScrolledWindow scrolled_window { + Adw.Clamp { + Box { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + spacing: 12; + orientation: vertical; + halign: fill; + hexpand: true; + + CheckButton root_group_check_button { + visible: false; + active: true; + } + + Adw.PreferencesGroup mask_group { + Adw.SwitchRow mask_row { + title: _("Disable Updates"); + } + } + + Adw.PreferencesGroup versions_group { + title: _("Select a Release"); + description: _("This will uninstalls the current release and install the chosen one instead. Note that downgrading can cause issues."); + } + } + } + } + } + [bottom] + ActionBar action_bar { + revealed: false; + [center] + Button apply_button { + sensitive: bind action_bar.revealed; + halign: center; + margin-top: 3; + margin-bottom: 3; + Adw.ButtonContent { + label: _("Change Version"); + icon-name: "double-ended-arrows-vertical-symbolic"; + } + styles ["suggested-action", "pill"] + } + } + } +} \ No newline at end of file diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py new file mode 100644 index 0000000..dfe27ba --- /dev/null +++ b/src/change_version_page/change_version_page.py @@ -0,0 +1,73 @@ +from gi.repository import Adw, Gtk,GLib#, Gio, Pango +from .error_toast import ErrorToast +from .host_info import HostInfo +import subprocess, os + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/change_version_page/change_version_page.ui") +class ChangeVersionPage(Adw.NavigationPage): + __gtype_name__ = 'ChangeVersionPage' + gtc = Gtk.Template.Child + scrolled_window = gtc() + root_group_check_button = gtc() + mask_group = gtc() + mask_row = gtc() + versions_group = gtc() + action_bar = gtc() + + def get_commits(self): + cmd = ['flatpak-spawn', '--host', 'sh', '-c'] + script = f"LC_ALL=C flatpak remote-info --log {self.package.info['origin']} {self.package.info['ref']} " + installation = self.package.info["installation"] + if installation == "user" or installation == "system": + script += f"--{installation}" + else: + script += f"--installation={installation}" + + cmd.append(script) + + commits = [] + changes = [] + dates = [] + output = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout + lines = output.strip().split('\n') + for line in lines: + line = line.strip().split(": ", 1) + if len(line) < 2: + continue + elif line[0].startswith("Commit"): + commits.append(line[1]) + elif line[0].startswith("Subject"): + changes.append(line[1]) + elif line[0].startswith("Date"): + dates.append(line[1]) + + if not (len(commits) == len(changes) == len(dates)): + return + + for index, element in enumerate(changes): + row = Adw.ActionRow(title=GLib.markup_escape_text(element), subtitle=GLib.markup_escape_text(dates[index])) + self.versions_group.add(row) + + def __init__(self, main_window, package, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + self.package = package + + # Apply + pkg_name = package.info["name"] + self.set_title(_("{} Versions").format(pkg_name)) + self.mask_row.set_subtitle(_("Ensure that {} will never be updated to a newer version").format(pkg_name)) + self.get_commits() + + # for i in range(10): + # row = Adw.ActionRow(title=f"Update to {i}.0", subtitle="Some dumb nerd shit I don't care about", activatable=True) + # check_button = Gtk.CheckButton() + # check_button.set_group(self.root_group_check_button) + # prev_check = check_button + # row.add_prefix(check_button) + # row.set_activatable_widget(check_button) + # self.versions_group.add(row) + + # Connections + self.root_group_check_button.connect("toggled", lambda *_: self.action_bar.set_revealed(True)) \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 0e958b6..3d23244 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,6 +9,7 @@ blueprints = custom_target('blueprints', 'main_window/window.blp', 'packages_page/packages_page.blp', 'properties_page/properties_page.blp', + 'change_version_page/change_version_page.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -56,6 +57,7 @@ warehouse_sources = [ 'main_window/window.py', 'packages_page/packages_page.py', 'properties_page/properties_page.py', + 'change_version_page/change_version_page.py', '../data/style.css', ] diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index ff1eb6f..84db7ec 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -147,6 +147,7 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ExpanderRow version_row { title: _("Version"); styles ["property"] + expanded: true; [suffix] Label mask_label { label: _("Updates Disabled"); @@ -162,7 +163,7 @@ template $PropertiesPage : Adw.NavigationPage { can-target: false; } } - Adw.ActionRow downgrade_row { + Adw.ActionRow change_version_row { title: _("Change Version"); subtitle: _("Upgrade or downgrade this package"); activatable: true; diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 5c6bcad..c576a7c 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -1,6 +1,7 @@ from gi.repository import Adw, Gtk,GLib#, Gio, Pango from .error_toast import ErrorToast from .host_info import HostInfo +from .change_version_page import ChangeVersionPage import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") @@ -29,7 +30,7 @@ class PropertiesPage(Adw.NavigationPage): mask_label = gtc() mask_row = gtc() mask_switch = gtc() - downgrade_row = gtc() + change_version_row = gtc() installed_size_row = gtc() runtime_row = gtc() eol_package_package_status_icon = gtc() @@ -202,6 +203,10 @@ class PropertiesPage(Adw.NavigationPage): HostInfo.clipboard.set(row.get_subtitle()) self.toast_overlay.add_toast(Adw.Toast(title=_("Copeid {}").format(row.get_title()))) + def change_version_handler(self, row): + page = ChangeVersionPage(self.main_window, self.package) + self.nav_view.push(page) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -236,6 +241,7 @@ class PropertiesPage(Adw.NavigationPage): self.open_app_button.connect("clicked", self.open_app_handler) self.mask_row.connect("activated", self.set_mask_handler) self.pin_row.connect("activated", self.set_pin_handler) + self.change_version_row.connect("activated", self.change_version_handler) for key in self.info_rows: row = self.info_rows[key] if type(row) != Adw.ActionRow: From 4ecfa6b763ba8e008ff76519e1e7ceeb30bbf7f9 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 8 Jul 2024 22:09:22 -0400 Subject: [PATCH 034/332] Display and select commit in change versions page --- .../change_version_page.blp | 2 +- .../change_version_page.py | 71 +++++++++++++------ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/change_version_page/change_version_page.blp b/src/change_version_page/change_version_page.blp index 7a87cc6..3318708 100644 --- a/src/change_version_page/change_version_page.blp +++ b/src/change_version_page/change_version_page.blp @@ -7,7 +7,7 @@ template $ChangeVersionPage : Adw.NavigationPage { [top] Adw.HeaderBar { } - Adw.ToastOverlay { + Adw.ToastOverlay toast_overlay { ScrolledWindow scrolled_window { Adw.Clamp { Box { diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py index dfe27ba..397dcad 100644 --- a/src/change_version_page/change_version_page.py +++ b/src/change_version_page/change_version_page.py @@ -1,4 +1,4 @@ -from gi.repository import Adw, Gtk,GLib#, Gio, Pango +from gi.repository import Adw, Gtk,GLib, Gio from .error_toast import ErrorToast from .host_info import HostInfo import subprocess, os @@ -7,6 +7,7 @@ import subprocess, os class ChangeVersionPage(Adw.NavigationPage): __gtype_name__ = 'ChangeVersionPage' gtc = Gtk.Template.Child + toast_overlay = gtc() scrolled_window = gtc() root_group_check_button = gtc() mask_group = gtc() @@ -14,7 +15,10 @@ class ChangeVersionPage(Adw.NavigationPage): versions_group = gtc() action_bar = gtc() - def get_commits(self): + selected_commit = None + failure = None + + def get_commits(self, *args): cmd = ['flatpak-spawn', '--host', 'sh', '-c'] script = f"LC_ALL=C flatpak remote-info --log {self.package.info['origin']} {self.package.info['ref']} " installation = self.package.info["installation"] @@ -28,25 +32,51 @@ class ChangeVersionPage(Adw.NavigationPage): commits = [] changes = [] dates = [] - output = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout - lines = output.strip().split('\n') - for line in lines: - line = line.strip().split(": ", 1) - if len(line) < 2: - continue - elif line[0].startswith("Commit"): - commits.append(line[1]) - elif line[0].startswith("Subject"): - changes.append(line[1]) - elif line[0].startswith("Date"): - dates.append(line[1]) - - if not (len(commits) == len(changes) == len(dates)): + try: + output = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout + lines = output.strip().split('\n') + for line in lines: + line = line.strip().split(": ", 1) + if len(line) < 2: + continue + elif line[0].startswith("Commit"): + commits.append(line[1]) + elif line[0].startswith("Subject"): + changes.append(line[1]) + elif line[0].startswith("Date"): + dates.append(line[1]) + except subprocess.CalledProcessError as cpe: + self.failure = cpe.stderr + return + except Exception as e: + self.failure = str(e) return - for index, element in enumerate(changes): - row = Adw.ActionRow(title=GLib.markup_escape_text(element), subtitle=GLib.markup_escape_text(dates[index])) - self.versions_group.add(row) + if not (len(commits) == len(changes) == len(dates)): + self.failure = "Commits, Changes, and Dates are not of equivalent length" + return + + def idle(*args): + for index, commit in enumerate(commits): + row = Adw.ActionRow(title=GLib.markup_escape_text(changes[index]), subtitle=f"{GLib.markup_escape_text(commit)}\n{GLib.markup_escape_text(dates[index])}") + check = Gtk.CheckButton() + check.connect("activate", lambda *_, comm=commit: self.set_commit(comm)) + check.set_group(self.root_group_check_button) + row.set_activatable_widget(check) + row.add_prefix(check) + self.versions_group.add(row) + + GLib.idle_add(idle) + + def set_commit(self, commit): + self.selected_commit = commit + + def callback(self, *args): + if not self.failure is None: + self.toast_overlay.add_toast(ErrorToast(_("Could not get versions"), self.failure).toast) + else: + print("yay") + def __init__(self, main_window, package, **kwargs): super().__init__(**kwargs) @@ -58,7 +88,8 @@ class ChangeVersionPage(Adw.NavigationPage): pkg_name = package.info["name"] self.set_title(_("{} Versions").format(pkg_name)) self.mask_row.set_subtitle(_("Ensure that {} will never be updated to a newer version").format(pkg_name)) - self.get_commits() + + Gio.Task.new(None, None, self.callback).run_in_thread(self.get_commits) # for i in range(10): # row = Adw.ActionRow(title=f"Update to {i}.0", subtitle="Some dumb nerd shit I don't care about", activatable=True) From 54223a21f31d4e2e93f4813b6eca0b6a7d0275b5 Mon Sep 17 00:00:00 2001 From: heliguy Date: Tue, 9 Jul 2024 15:52:55 -0400 Subject: [PATCH 035/332] Add live cli parsing, for reference later (not final) --- src/change_version_page/change_version_page.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py index 397dcad..0d27fd0 100644 --- a/src/change_version_page/change_version_page.py +++ b/src/change_version_page/change_version_page.py @@ -14,6 +14,7 @@ class ChangeVersionPage(Adw.NavigationPage): mask_row = gtc() versions_group = gtc() action_bar = gtc() + apply_button = gtc() selected_commit = None failure = None @@ -71,12 +72,19 @@ class ChangeVersionPage(Adw.NavigationPage): def set_commit(self, commit): self.selected_commit = commit - def callback(self, *args): + def get_commits_callback(self, *args): if not self.failure is None: self.toast_overlay.add_toast(ErrorToast(_("Could not get versions"), self.failure).toast) else: print("yay") + def loader_test(self, *args): + def thread(*args): + cmd = subprocess.Popen("for i in {1..20}; do echo $i; sleep 1s; done", shell=True, text=True, stdout=subprocess.PIPE) + for line in cmd.stdout: + line = line.strip() + GLib.idle_add(lambda *_: self.set_title(line)) + Gio.Task.new(None, None, None).run_in_thread(thread) def __init__(self, main_window, package, **kwargs): super().__init__(**kwargs) @@ -89,7 +97,8 @@ class ChangeVersionPage(Adw.NavigationPage): self.set_title(_("{} Versions").format(pkg_name)) self.mask_row.set_subtitle(_("Ensure that {} will never be updated to a newer version").format(pkg_name)) - Gio.Task.new(None, None, self.callback).run_in_thread(self.get_commits) + # Gio.Task.new(None, None, self.get_commits_callback).run_in_thread(self.get_commits) + Gio.Task.new(None, None, self.get_commits_callback).run_in_thread(self.get_commits) # for i in range(10): # row = Adw.ActionRow(title=f"Update to {i}.0", subtitle="Some dumb nerd shit I don't care about", activatable=True) @@ -101,4 +110,5 @@ class ChangeVersionPage(Adw.NavigationPage): # self.versions_group.add(row) # Connections - self.root_group_check_button.connect("toggled", lambda *_: self.action_bar.set_revealed(True)) \ No newline at end of file + self.root_group_check_button.connect("toggled", lambda *_: self.action_bar.set_revealed(True)) + self.apply_button.connect("clicked", self.loader_test) \ No newline at end of file From d947a44969badb121c2510b5b0d8499e869d50b7 Mon Sep 17 00:00:00 2001 From: heliguy Date: Tue, 9 Jul 2024 16:55:19 -0400 Subject: [PATCH 036/332] Add status box widget --- src/main_window/window.py | 1 + src/meson.build | 2 + src/warehouse.gresource.xml | 1 + src/widgets/status_box.blp | 77 +++++++++++++++++++++++++++++++++++++ src/widgets/status_box.py | 26 +++++++++++++ 5 files changed, 107 insertions(+) create mode 100644 src/widgets/status_box.blp create mode 100644 src/widgets/status_box.py diff --git a/src/main_window/window.py b/src/main_window/window.py index e968bec..023e863 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -26,6 +26,7 @@ from gi.repository import Adw, Gdk, Gio, GLib, Gtk from .packages_page import PackagesPage from .const import Config from .error_toast import ErrorToast +from .status_box import StatusBox @Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui") class WarehouseWindow(Adw.ApplicationWindow): diff --git a/src/meson.build b/src/meson.build index 3d23244..16a4967 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,7 @@ gnome = import('gnome') blueprints = custom_target('blueprints', input: files( 'widgets/app_row.blp', + 'widgets/status_box.blp', 'gtk/help-overlay.blp', 'main_window/window.blp', 'packages_page/packages_page.blp', @@ -54,6 +55,7 @@ warehouse_sources = [ 'host_info.py', 'widgets/app_row.py', 'widgets/error_toast.py', + 'widgets/status_box.py', 'main_window/window.py', 'packages_page/packages_page.py', 'properties_page/properties_page.py', diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 98b64a1..cb2aebd 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -8,6 +8,7 @@ packages_page/packages_page.ui properties_page/properties_page.ui change_version_page/change_version_page.ui + widgets/status_box.ui diff --git a/src/widgets/status_box.blp b/src/widgets/status_box.blp new file mode 100644 index 0000000..0799907 --- /dev/null +++ b/src/widgets/status_box.blp @@ -0,0 +1,77 @@ +using Gtk 4.0; +using Adw 1; + +template $StatusBox : Box { + orientation: vertical; + spacing: 10; + margin-top: 40; + margin-bottom: 20; + halign: center; + valign: center; + + margin-start: 24; + margin-end: 24; + + Spinner spinner { + margin-bottom: 35; + width-request: 30; + height-request: 30; + opacity: 0.5; + spinning: true; + } + + Label title { + halign: center; + label: "No Title Set"; + wrap: true; + styles ["title-1"] + } + + Label description { + halign: center; + label: "No Description Set"; + wrap: true; + styles["description", "body"] + } + + Adw.Clamp { + Box progress_box { + margin-top: 36; + spacing: 12; + halign: fill; + + Label mirror_label { + valign: center; + label: bind progress_label.label; + styles ["heading"] + opacity: 0.0; + wrap: true; + wrap-mode: char; + } + + ProgressBar progress_bar { + valign: center; + hexpand: true; + fraction: 0.5; + } + + Label progress_label { + valign: center; + label: "-1%"; + styles ["heading"] + wrap: true; + wrap-mode: char; + } + } + } + + Button cancel_button { + margin-top: 12; + halign: center; + styles ["pill"] + Adw.ButtonContent cancel_button_content { + icon-name: "cross-filled-symbolic"; + label: _("Cancel"); + } + } +} diff --git a/src/widgets/status_box.py b/src/widgets/status_box.py new file mode 100644 index 0000000..df039e0 --- /dev/null +++ b/src/widgets/status_box.py @@ -0,0 +1,26 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/widgets/status_box.ui") +class StatusBox(Gtk.Box): + __gtype_name__ = 'StatusBox' + gtc = Gtk.Template.Child + spinner = gtc() + title = gtc() + description = gtc() + progress_box = gtc() + progress_bar = gtc() + progress_label = gtc() + cancel_button = gtc() + cancel_button_content = gtc() + + def __init__(self, title, description, show_loading_bar=True, on_cancel=None, **kwargs): + super().__init__(**kwargs) + self.title.set_label(title) + self.description.set_label(description) + self.spinner.set_visible(not show_loading_bar) + self.progress_box.set_visible(show_loading_bar) + self.cancel_button.set_visible(on_cancel) + + if on_cancel: + self.cancel_button.connect("clicked", lambda *_: on_cancel()) \ No newline at end of file From 36148b30ad517cc619f2fae711a452b56a754503 Mon Sep 17 00:00:00 2001 From: heliguy Date: Tue, 9 Jul 2024 17:08:37 -0400 Subject: [PATCH 037/332] Add status box to versions page --- .../change_version_page.blp | 60 +++++++++---------- .../change_version_page.py | 6 +- src/widgets/status_box.py | 2 +- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/change_version_page/change_version_page.blp b/src/change_version_page/change_version_page.blp index 3318708..ec9b627 100644 --- a/src/change_version_page/change_version_page.blp +++ b/src/change_version_page/change_version_page.blp @@ -8,36 +8,7 @@ template $ChangeVersionPage : Adw.NavigationPage { Adw.HeaderBar { } Adw.ToastOverlay toast_overlay { - ScrolledWindow scrolled_window { - Adw.Clamp { - Box { - margin-start: 12; - margin-end: 12; - margin-top: 12; - margin-bottom: 12; - spacing: 12; - orientation: vertical; - halign: fill; - hexpand: true; - - CheckButton root_group_check_button { - visible: false; - active: true; - } - - Adw.PreferencesGroup mask_group { - Adw.SwitchRow mask_row { - title: _("Disable Updates"); - } - } - - Adw.PreferencesGroup versions_group { - title: _("Select a Release"); - description: _("This will uninstalls the current release and install the chosen one instead. Note that downgrading can cause issues."); - } - } - } - } + ScrolledWindow scrolled_window {} } [bottom] ActionBar action_bar { @@ -56,4 +27,33 @@ template $ChangeVersionPage : Adw.NavigationPage { } } } +} + +Adw.Clamp versions_clamp { + Box { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + spacing: 12; + orientation: vertical; + halign: fill; + hexpand: true; + + CheckButton root_group_check_button { + visible: false; + active: true; + } + + Adw.PreferencesGroup mask_group { + Adw.SwitchRow mask_row { + title: _("Disable Updates"); + } + } + + Adw.PreferencesGroup versions_group { + title: _("Select a Release"); + description: _("This will uninstalls the current release and install the chosen one instead. Note that downgrading can cause issues."); + } + } } \ No newline at end of file diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py index 0d27fd0..76200d4 100644 --- a/src/change_version_page/change_version_page.py +++ b/src/change_version_page/change_version_page.py @@ -1,6 +1,7 @@ from gi.repository import Adw, Gtk,GLib, Gio from .error_toast import ErrorToast from .host_info import HostInfo +from .status_box import StatusBox import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/change_version_page/change_version_page.ui") @@ -9,6 +10,7 @@ class ChangeVersionPage(Adw.NavigationPage): gtc = Gtk.Template.Child toast_overlay = gtc() scrolled_window = gtc() + versions_clamp = gtc() root_group_check_button = gtc() mask_group = gtc() mask_row = gtc() @@ -76,7 +78,7 @@ class ChangeVersionPage(Adw.NavigationPage): if not self.failure is None: self.toast_overlay.add_toast(ErrorToast(_("Could not get versions"), self.failure).toast) else: - print("yay") + self.scrolled_window.set_child(self.versions_clamp) def loader_test(self, *args): def thread(*args): @@ -97,7 +99,7 @@ class ChangeVersionPage(Adw.NavigationPage): self.set_title(_("{} Versions").format(pkg_name)) self.mask_row.set_subtitle(_("Ensure that {} will never be updated to a newer version").format(pkg_name)) - # Gio.Task.new(None, None, self.get_commits_callback).run_in_thread(self.get_commits) + self.scrolled_window.set_child(StatusBox(_("Fetching Releases"), _("This could take a while"))) Gio.Task.new(None, None, self.get_commits_callback).run_in_thread(self.get_commits) # for i in range(10): diff --git a/src/widgets/status_box.py b/src/widgets/status_box.py index df039e0..5b48599 100644 --- a/src/widgets/status_box.py +++ b/src/widgets/status_box.py @@ -14,7 +14,7 @@ class StatusBox(Gtk.Box): cancel_button = gtc() cancel_button_content = gtc() - def __init__(self, title, description, show_loading_bar=True, on_cancel=None, **kwargs): + def __init__(self, title, description, show_loading_bar=False, on_cancel=None, **kwargs): super().__init__(**kwargs) self.title.set_label(title) self.description.set_label(description) From 9bdbceb1071f5325ec495f14a8a7844074cf28c0 Mon Sep 17 00:00:00 2001 From: heliguy Date: Tue, 9 Jul 2024 18:01:10 -0400 Subject: [PATCH 038/332] Add loading status to packages page --- src/packages_page/packages_page.blp | 176 ++++++++++++++-------------- src/packages_page/packages_page.py | 24 +++- 2 files changed, 105 insertions(+), 95 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 87a7ad7..417479e 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -15,102 +15,100 @@ template $PackagesPage : Adw.BreakpointBin { } Adw.ToastOverlay packages_toast_overlay { - Adw.NavigationSplitView packages_split { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage { - title: "Packages"; - Adw.ToolbarView packages_tbv { - [top] - Adw.HeaderBar { - [start] - Button sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Packages"); - } - [start] - Button filter_button { - icon-name: "funnel-symbolic"; - tooltip-text: _("Filter Packages"); - } - [end] - Button refresh_button { - icon-name: "arrow-circular-top-right-symbolic"; - tooltip-text: _("Refresh List"); - } - [end] - ToggleButton select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select Packages"); - } - } - [top] - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - // key-capture-widget: packages_tbv; - SearchEntry search_entry { - hexpand: true; - } - } - ScrolledWindow scrolled_window { - ListBox packages_list_box { - styles ["navigation-sidebar"] - } - } - [bottom] - Revealer { - reveal-child: bind select_button.active; - transition-type: slide_up; - [center] - Box { - styles ["toolbar"] - hexpand: true; - homogeneous: true; - Button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "selection-mode-symbolic"; - label: _("Select All"); - can-shrink: true; - } + Stack stack { + Adw.ToolbarView status_view { + [top] + Adw.HeaderBar { + show-title: false; + } + } + Adw.NavigationSplitView packages_split { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage packages_navpage { + title: "Packages"; + Adw.ToolbarView packages_tbv { + [top] + Adw.HeaderBar { + [start] + Button sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); } - Button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "edit-copy-symbolic"; - label: _("Copy"); - can-shrink: true; - } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); } - Button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "user-trash-symbolic"; - label: _("Uninstall"); - can-shrink: true; + [start] + Button filter_button { + icon-name: "funnel-symbolic"; + tooltip-text: _("Filter Packages"); + } + [end] + Button refresh_button { + icon-name: "arrow-circular-top-right-symbolic"; + tooltip-text: _("Refresh List"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select Packages"); + } + } + [top] + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + // key-capture-widget: packages_tbv; + SearchEntry search_entry { + hexpand: true; + } + } + ScrolledWindow scrolled_window { + ListBox packages_list_box { + styles ["navigation-sidebar"] + } + } + [bottom] + Revealer { + reveal-child: bind select_button.active; + transition-type: slide_up; + [center] + Box { + styles ["toolbar"] + hexpand: true; + homogeneous: true; + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "selection-mode-symbolic"; + label: _("Select All"); + can-shrink: true; + } + } + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; + } + } + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "user-trash-symbolic"; + label: _("Uninstall"); + can-shrink: true; + } } } } } } - } - ; + ; + } } } -} - -Adw.StatusPage refresh_status { - title: "Refreshing"; - description: "Refreshing the page lol haha"; - child: - Spinner spinner { - spinning: true; - } - ; } \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 917394c..b19c2a9 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -3,12 +3,14 @@ from .host_info import HostInfo from .app_row import AppRow from .error_toast import ErrorToast from .properties_page import PropertiesPage +from .status_box import StatusBox @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") class PackagesPage(Adw.BreakpointBin): __gtype_name__ = 'PackagesPage' gtc = Gtk.Template.Child packages_toast_overlay = gtc() + stack = gtc() scrolled_window = gtc() sidebar_button = gtc() refresh_button = gtc() @@ -16,8 +18,9 @@ class PackagesPage(Adw.BreakpointBin): search_entry = gtc() packages_split = gtc() packages_list_box = gtc() - refresh_status = gtc() select_button = gtc() + packages_navpage = gtc() + status_view = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -43,8 +46,7 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.select_row(first_row) self.properties_page.set_properties(first_row.package) self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top - if self.packages_toast_overlay.get_child() != self.packages_split: - self.packages_toast_overlay.set_child(self.packages_split) + self.stack.set_visible_child(self.packages_split) def row_select_handler(self, list_box, row): self.properties_page.set_properties(row.package) @@ -65,6 +67,16 @@ class PackagesPage(Adw.BreakpointBin): GLib.idle_add(row.check_button.set_active, False) GLib.idle_add(row.check_button.set_visible, is_enabled) + def set_status(self, status_box): + self.stack.set_visible_child(self.status_view) + if self.status_view.get_content() == status_box: + return + self.status_view.set_content(status_box) + + def refresh_button_handler(self, *args): + self.set_status(self.loading_status) + HostInfo.get_flatpaks(callback=self.generate_list) + def select_button_handler(self, button): self.set_selection_mode(button.get_active()) @@ -86,12 +98,12 @@ class PackagesPage(Adw.BreakpointBin): main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) - - self.refresh_button.connect("clicked", lambda *_: self.packages_toast_overlay.set_child(self.refresh_status)) - self.refresh_button.connect("clicked", lambda *_: HostInfo.get_flatpaks(callback=self.generate_list)) self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) self.search_bar.set_key_capture_widget(main_window) self.packages_list_box.connect("row-activated", self.row_select_handler) + self.refresh_button.connect("clicked", self.refresh_button_handler) self.select_button.connect("clicked", self.select_button_handler) + + self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) From b566e761202711a61724e6ca9ac9d737adbbb4a6 Mon Sep 17 00:00:00 2001 From: heliguy Date: Tue, 9 Jul 2024 21:29:07 -0400 Subject: [PATCH 039/332] Add confirmation dialog to trashing of user data --- src/properties_page/properties_page.py | 31 +++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index c576a7c..9da6943 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -145,14 +145,27 @@ class PropertiesPage(Adw.NavigationPage): self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error)).toast) def trash_data_handler(self, *args): - try: - self.package.trash_data() - self.set_properties(self.package, refresh=True) - self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) - except subprocess.CalledProcessError as cpe: - self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), cpe.stderr).toast) - except Exception as e: - self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e)).toast) + def on_choice(_, response): + if response != 'continue': + return + try: + self.package.trash_data() + self.set_properties(self.package, refresh=True) + self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) + except subprocess.CalledProcessError as cpe: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), cpe.stderr).toast) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e)).toast) + + dialog = Adw.AlertDialog( + heading=_("Send {}'s User Data to the Trash?").format(self.package.info["name"]), + body=_("Your settings and data for this app will be sent to the trash") + ) + dialog.add_response('cancel', _("Cancel")) + dialog.add_response('continue', _("Trash Data")) + dialog.connect("response", on_choice) + dialog.set_response_appearance('continue', Adw.ResponseAppearance.DESTRUCTIVE) + dialog.present(self.main_window) def set_mask_handler(self, *args): state = not self.mask_switch.get_active() @@ -233,6 +246,8 @@ class PropertiesPage(Adw.NavigationPage): "date": self.date_row, } + self.__class__.main_window = main_window + # Connections self.open_data_button.connect("clicked", self.open_data_handler) self.scrolled_window.get_vadjustment().connect("value-changed", lambda adjustment: self.header_bar.set_show_title(not adjustment.get_value() == 0)) From e00ec3f86a7fcd5e659c6f25971c8fbb6199f2bd Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 10 Jul 2024 14:53:52 -0400 Subject: [PATCH 040/332] Add filters page ui --- src/filters_page/filters_page.blp | 118 ++++++++++++++++++++++++ src/filters_page/filters_page.py | 11 +++ src/meson.build | 2 + src/packages_page/packages_page.blp | 19 +++- src/packages_page/packages_page.py | 27 +++++- src/properties_page/properties_page.blp | 2 - src/warehouse.gresource.xml | 1 + 7 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 src/filters_page/filters_page.blp create mode 100644 src/filters_page/filters_page.py diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp new file mode 100644 index 0000000..afa07a1 --- /dev/null +++ b/src/filters_page/filters_page.blp @@ -0,0 +1,118 @@ +using Gtk 4.0; +using Adw 1; + +template $FiltersPage : Adw.NavigationPage { + title: _("Filter Packages"); + Adw.ToolbarView { + [top] + Adw.HeaderBar {} + ScrolledWindow { + Adw.Clamp { + Box { + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + orientation: vertical; + halign: fill; + spacing: 24; + Adw.PreferencesGroup { + title: _("Package Type"); + description: _("Show packages of these types"); + header-suffix: + Button all_types_button { + Adw.ButtonContent { + label: _("Select All"); + icon-name: "selection-mode-symbolic"; + } + valign: center; + styles ["flat"] + } + ; + Adw.ActionRow application_row { + title: _("Applications"); + subtitle: _("Packages that can be opened"); + CheckButton app_check { + styles["selection-mode"] + } + activatable-widget: app_check; + } + Adw.ActionRow runtime_row { + title: _("Runtimes"); + subtitle: _("Packages that applications depend on"); + CheckButton runtime-check { + styles["selection-mode"] + } + activatable-widget: runtime-check; + } + } + Adw.PreferencesGroup { + title: _("Remotes"); + description: _("Show packages from these remotes"); + header-suffix: + Button all_remotes_button { + Adw.ButtonContent { + label: _("Select All"); + icon-name: "selection-mode-symbolic"; + } + valign: center; + styles ["flat"] + } + ; + Adw.ActionRow { + title: _("Flathub"); + subtitle: _("Installation: User"); + CheckButton { + styles["selection-mode"] + } + } + Adw.ActionRow { + title: _("Flathub"); + subtitle: _("Installation: System"); + CheckButton { + styles["selection-mode"] + } + } + } + Adw.PreferencesGroup { + title: _("Runtimes"); + description: _("Show apps that use these runtimes"); + header-suffix: + Button all_runtimes_button { + Adw.ButtonContent { + label: _("Select All"); + icon-name: "selection-mode-symbolic"; + } + valign: center; + styles ["flat"] + } + ; + Adw.ActionRow { + title: _("org.freedesktop.Platform.ffmpeg-full/x86_64/23.08"); + CheckButton { + styles["selection-mode"] + } + } + Adw.ActionRow { + title: _("org.gnome.Platform/x86_64/44"); + CheckButton { + styles["selection-mode"] + } + } + Adw.ActionRow { + title: _("org.gnome.Platform/x86_64/45"); + CheckButton { + styles["selection-mode"] + } + } + Adw.ActionRow { + title: _("org.freedesktop.Platform.Compat.i386/x86_64/23.08"); + CheckButton { + styles["selection-mode"] + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py new file mode 100644 index 0000000..8a425d6 --- /dev/null +++ b/src/filters_page/filters_page.py @@ -0,0 +1,11 @@ +from gi.repository import Adw, Gtk, GLib#, Gio, Pango +from .host_info import HostInfo + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/filters_page/filters_page.ui") +class FiltersPage(Adw.NavigationPage): + __gtype_name__ = 'FiltersPage' + gtc = Gtk.Template.Child + + def __init__(self, main_window, packages_page, **kwargs): + super().__init__(**kwargs) + # self.connect("hidden", lambda *_: packages_page.filter_button.set_active(False)) \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 16a4967..9c2023e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,6 +9,7 @@ blueprints = custom_target('blueprints', 'gtk/help-overlay.blp', 'main_window/window.blp', 'packages_page/packages_page.blp', + 'filters_page/filters_page.blp', 'properties_page/properties_page.blp', 'change_version_page/change_version_page.blp', ), @@ -58,6 +59,7 @@ warehouse_sources = [ 'widgets/status_box.py', 'main_window/window.py', 'packages_page/packages_page.py', + 'filters_page/filters_page.py', 'properties_page/properties_page.py', 'change_version_page/change_version_page.py', '../data/style.css', diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 417479e..448927c 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -11,6 +11,7 @@ template $PackagesPage : Adw.BreakpointBin { setters { packages_split.collapsed: true; packages_split.show-content: false; + content_stack.transition-duration: 9999999; } } @@ -42,16 +43,16 @@ template $PackagesPage : Adw.BreakpointBin { tooltip-text: _("Search Packages"); } [start] - Button filter_button { - icon-name: "funnel-symbolic"; - tooltip-text: _("Filter Packages"); - } - [end] Button refresh_button { icon-name: "arrow-circular-top-right-symbolic"; tooltip-text: _("Refresh List"); } [end] + ToggleButton filter_button { + icon-name: "funnel-symbolic"; + tooltip-text: _("Filter Packages"); + } + [end] ToggleButton select_button { icon-name: "selection-mode-symbolic"; tooltip-text: _("Select Packages"); @@ -108,6 +109,14 @@ template $PackagesPage : Adw.BreakpointBin { } } ; + content: + Adw.NavigationPage { + title: "Content Stack"; + Stack content_stack { + transition-type: slide_left_right; + } + } + ; } } } diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index b19c2a9..29e7d84 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -4,15 +4,18 @@ from .app_row import AppRow from .error_toast import ErrorToast from .properties_page import PropertiesPage from .status_box import StatusBox +from .filters_page import FiltersPage @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") class PackagesPage(Adw.BreakpointBin): __gtype_name__ = 'PackagesPage' gtc = Gtk.Template.Child + packages_bpt = gtc() packages_toast_overlay = gtc() stack = gtc() scrolled_window = gtc() sidebar_button = gtc() + filter_button = gtc() refresh_button = gtc() search_bar = gtc() search_entry = gtc() @@ -21,6 +24,7 @@ class PackagesPage(Adw.BreakpointBin): select_button = gtc() packages_navpage = gtc() status_view = gtc() + content_stack = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -52,6 +56,7 @@ class PackagesPage(Adw.BreakpointBin): 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) def filter_func(self, row): search_text = self.search_entry.get_text().lower() @@ -80,18 +85,32 @@ class PackagesPage(Adw.BreakpointBin): 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) + + 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 __init__(self, main_window, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.main_window = main_window self.properties_page = PropertiesPage(main_window) + self.filters_page = FiltersPage(main_window, self) + self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) # Apply HostInfo.get_flatpaks(callback=self.generate_list) self.packages_list_box.set_filter_func(self.filter_func) - self.packages_split.set_content(self.properties_page) + self.content_stack.add_child(self.properties_page) + self.content_stack.add_child(self.filters_page) self.__class__.instance = self # Connections @@ -103,7 +122,7 @@ class PackagesPage(Adw.BreakpointBin): self.search_bar.set_key_capture_widget(main_window) self.packages_list_box.connect("row-activated", self.row_select_handler) self.refresh_button.connect("clicked", self.refresh_button_handler) - self.select_button.connect("clicked", self.select_button_handler) - - self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) + self.filter_button.connect("toggled", self.filter_button_handler) + self.packages_split.connect("notify::show-content", self.filter_page_handler) + self.packages_bpt.connect("apply", self.filter_page_handler) \ No newline at end of file diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 84db7ec..9e47f9f 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -51,7 +51,6 @@ template $PropertiesPage : Adw.NavigationPage { margin-bottom: 12; orientation: vertical; halign: fill; - hexpand: true; Image app_icon { pixel-size: 100; @@ -147,7 +146,6 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ExpanderRow version_row { title: _("Version"); styles ["property"] - expanded: true; [suffix] Label mask_label { label: _("Updates Disabled"); diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index cb2aebd..52f9312 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -6,6 +6,7 @@ widgets/app_row.ui main_window/window.ui packages_page/packages_page.ui + filters_page/filters_page.ui properties_page/properties_page.ui change_version_page/change_version_page.ui widgets/status_box.ui From e04d14102da7cd1a67bb13e7827b2e40bffc9acc Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 10 Jul 2024 15:56:48 -0400 Subject: [PATCH 041/332] mask and pin switches toggle status icons on the rows --- src/host_info.py | 1 + src/packages_page/packages_page.blp | 21 ++++++++++++++++++++- src/packages_page/packages_page.py | 7 ++++--- src/properties_page/properties_page.py | 7 +++++-- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index 7c61c40..2e93d08 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -155,6 +155,7 @@ class Flatpak: self.dependant_runtime = None self.failed_app_run = None self.failed_mask = None + self.app_row = None try: self.is_masked = self.info["id"] in HostInfo.masks[self.info["installation"]] diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 448927c..fb624e3 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -88,13 +88,14 @@ template $PackagesPage : Adw.BreakpointBin { can-shrink: true; } } - Button { + MenuButton copy_button { styles ["raised"] Adw.ButtonContent { icon-name: "edit-copy-symbolic"; label: _("Copy"); can-shrink: true; } + popover: copy_pop; } Button { styles ["raised"] @@ -120,4 +121,22 @@ template $PackagesPage : Adw.BreakpointBin { } } } +} + +Popover copy_pop { + styles ["menu"] + ListBox copy_menu { + Label copy_names { + label: _("Copy Names"); + } + Label copy_ids { + label: _("Copy IDs"); + } + Label copy_refs { + label: _("Copy Refs"); + } + Label copy_data { + label: _("Copy Data Paths"); + } + } } \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 29e7d84..1d89fc3 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -35,6 +35,7 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.remove_all() for package in HostInfo.flatpaks: row = AppRow(package) + package.app_row = row row.masked_status_icon.set_visible(package.is_masked) row.pinned_status_icon.set_visible(package.is_pinned) row.eol_package_package_status_icon.set_visible(package.is_eol) @@ -78,7 +79,7 @@ class PackagesPage(Adw.BreakpointBin): return self.status_view.set_content(status_box) - def refresh_button_handler(self, *args): + def refresh_handler(self, *args): self.set_status(self.loading_status) HostInfo.get_flatpaks(callback=self.generate_list) @@ -101,7 +102,7 @@ class PackagesPage(Adw.BreakpointBin): # Extra Object Creation self.main_window = main_window - self.properties_page = PropertiesPage(main_window) + self.properties_page = PropertiesPage(main_window, self) self.filters_page = FiltersPage(main_window, self) self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) @@ -121,7 +122,7 @@ class PackagesPage(Adw.BreakpointBin): self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) self.search_bar.set_key_capture_widget(main_window) self.packages_list_box.connect("row-activated", self.row_select_handler) - self.refresh_button.connect("clicked", self.refresh_button_handler) + self.refresh_button.connect("clicked", self.refresh_handler) self.select_button.connect("clicked", self.select_button_handler) self.filter_button.connect("toggled", self.filter_button_handler) self.packages_split.connect("notify::show-content", self.filter_page_handler) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 9da6943..1816f32 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -179,6 +179,7 @@ class PropertiesPage(Adw.NavigationPage): response = _("Disabled Updates") if state else _("Enabled Updates") self.toast_overlay.add_toast(Adw.Toast(title=response)) GLib.idle_add(lambda *_: self.mask_switch.set_active(state)) + self.package.app_row.masked_status_icon.set_visible(state) self.package.set_mask(state, callback) @@ -194,11 +195,12 @@ class PropertiesPage(Adw.NavigationPage): response = _("Disabled Autoremoval") if state else _("Enabled Autoremoval") self.toast_overlay.add_toast(Adw.Toast(title=response)) GLib.idle_add(lambda *_: self.pin_switch.set_active(state)) + self.package.app_row.pinned_status_icon.set_visible(state) self.package.set_pin(state, callback) def runtime_row_handler(self, *args): - new_page = self.__class__(self.main_window) + new_page = self.__class__(self.main_window, self.packages_page) new_page.set_properties(self.package.dependant_runtime) self.nav_view.push(new_page) @@ -220,7 +222,7 @@ class PropertiesPage(Adw.NavigationPage): page = ChangeVersionPage(self.main_window, self.package) self.nav_view.push(page) - def __init__(self, main_window, **kwargs): + def __init__(self, main_window, packages_page, **kwargs): super().__init__(**kwargs) # Extra Object Creation @@ -246,6 +248,7 @@ class PropertiesPage(Adw.NavigationPage): "date": self.date_row, } + self.packages_page = packages_page self.__class__.main_window = main_window # Connections From bec7aad6df699f5fbb7552e71e2e969fb057134d Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 10 Jul 2024 22:31:37 -0400 Subject: [PATCH 042/332] Further work on filters page --- src/filters_page/filters_page.blp | 61 +++---------------------- src/filters_page/filters_page.py | 72 +++++++++++++++++++++++++++++- src/host_info.py | 48 ++++++++++++-------- src/packages_page/packages_page.py | 16 ++++--- src/widgets/app_row.py | 15 +++++-- 5 files changed, 128 insertions(+), 84 deletions(-) diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp index afa07a1..9d69141 100644 --- a/src/filters_page/filters_page.blp +++ b/src/filters_page/filters_page.blp @@ -11,6 +11,7 @@ template $FiltersPage : Adw.NavigationPage { Box { margin-start: 12; margin-end: 12; + margin-top: 12; margin-bottom: 12; orientation: vertical; halign: fill; @@ -18,16 +19,6 @@ template $FiltersPage : Adw.NavigationPage { Adw.PreferencesGroup { title: _("Package Type"); description: _("Show packages of these types"); - header-suffix: - Button all_types_button { - Adw.ButtonContent { - label: _("Select All"); - icon-name: "selection-mode-symbolic"; - } - valign: center; - styles ["flat"] - } - ; Adw.ActionRow application_row { title: _("Applications"); subtitle: _("Packages that can be opened"); @@ -39,17 +30,17 @@ template $FiltersPage : Adw.NavigationPage { Adw.ActionRow runtime_row { title: _("Runtimes"); subtitle: _("Packages that applications depend on"); - CheckButton runtime-check { + CheckButton runtime_check { styles["selection-mode"] } - activatable-widget: runtime-check; + activatable-widget: runtime_check; } } - Adw.PreferencesGroup { + Adw.PreferencesGroup remotes_group { title: _("Remotes"); description: _("Show packages from these remotes"); header-suffix: - Button all_remotes_button { + ToggleButton all_remotes_button { Adw.ButtonContent { label: _("Select All"); icon-name: "selection-mode-symbolic"; @@ -58,26 +49,12 @@ template $FiltersPage : Adw.NavigationPage { styles ["flat"] } ; - Adw.ActionRow { - title: _("Flathub"); - subtitle: _("Installation: User"); - CheckButton { - styles["selection-mode"] - } - } - Adw.ActionRow { - title: _("Flathub"); - subtitle: _("Installation: System"); - CheckButton { - styles["selection-mode"] - } - } } - Adw.PreferencesGroup { + Adw.PreferencesGroup runtimes_group { title: _("Runtimes"); description: _("Show apps that use these runtimes"); header-suffix: - Button all_runtimes_button { + ToggleButton all_runtimes_button { Adw.ButtonContent { label: _("Select All"); icon-name: "selection-mode-symbolic"; @@ -86,30 +63,6 @@ template $FiltersPage : Adw.NavigationPage { styles ["flat"] } ; - Adw.ActionRow { - title: _("org.freedesktop.Platform.ffmpeg-full/x86_64/23.08"); - CheckButton { - styles["selection-mode"] - } - } - Adw.ActionRow { - title: _("org.gnome.Platform/x86_64/44"); - CheckButton { - styles["selection-mode"] - } - } - Adw.ActionRow { - title: _("org.gnome.Platform/x86_64/45"); - CheckButton { - styles["selection-mode"] - } - } - Adw.ActionRow { - title: _("org.freedesktop.Platform.Compat.i386/x86_64/23.08"); - CheckButton { - styles["selection-mode"] - } - } } } } diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index 8a425d6..d1948a4 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -1,11 +1,79 @@ -from gi.repository import Adw, Gtk, GLib#, Gio, Pango +from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo +class FilterRow(Adw.ActionRow): + __gtype_name__ = 'FilterRow' + def __init__(self, item=None, installation=None, **kwargs): + super().__init__(**kwargs) + self.item = item + self.installation = installation + self.check_button = Gtk.CheckButton() + self.check_button.add_css_class("selection-mode") + self.add_suffix(self.check_button) + self.set_activatable_widget(self.check_button) + @Gtk.Template(resource_path="/io/github/flattool/Warehouse/filters_page/filters_page.ui") class FiltersPage(Adw.NavigationPage): __gtype_name__ = 'FiltersPage' gtc = Gtk.Template.Child + app_check = gtc() + runtime_check = gtc() + remotes_group = gtc() + runtimes_group = gtc() + all_remotes_button = gtc() + all_runtimes_button = gtc() + + remote_rows = [] + runtime_rows = [] + + def generate_list(self): + for row in self.remote_rows: + self.remotes_group.remove(row) + + for row in self.runtime_rows: + self.runtimes_group.remove(row) + + self.remote_rows.clear() + self.runtime_rows.clear() + + self.app_check.set_active(self.settings.get_boolean("show-apps")) + self.runtime_check.set_active(self.settings.get_boolean("show-runtimes")) + + remotes_string = self.settings.get_string("runtimes-list") + self.all_remotes_button.set_active("all" == remotes_string) + for installation in HostInfo.installations: + try: + for remote in HostInfo.remotes[installation]: + row = FilterRow(remote, installation) + row.set_title(remote.title) + row.set_subtitle(_("Installation: {}").format(installation)) + row.check_button.set_active(self.all_remotes_button.get_active()) + self.remote_rows.append(row) + self.remotes_group.add(row) + except KeyError: + pass + + runtimes_string = self.settings.get_string("runtimes-list") + self.all_runtimes_button.set_active("all" == runtimes_string) + for ref in HostInfo.dependant_runtime_refs: + row = FilterRow(ref) + row.set_title(ref) + row.check_button.set_active(self.all_runtimes_button.get_active()) + self.runtime_rows.append(row) + self.runtimes_group.add(row) + + def select_all_handler(self, rows, button): + for row in rows: + row.check_button.set_active(button.get_active()) def __init__(self, main_window, packages_page, **kwargs): super().__init__(**kwargs) - # self.connect("hidden", lambda *_: packages_page.filter_button.set_active(False)) \ No newline at end of file + + # Extra Objects Creation + self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") + + # Apply + + # Connections + self.all_remotes_button.connect("toggled", lambda *_: self.select_all_handler(self.remote_rows, self.all_remotes_button)) + self.all_runtimes_button.connect("toggled", lambda *_: self.select_all_handler(self.runtime_rows, self.all_runtimes_button)) \ No newline at end of file diff --git a/src/host_info.py b/src/host_info.py index 2e93d08..5dd0332 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -181,9 +181,11 @@ class Flatpak: class Remote: - def __init__(self, name, installation): + def __init__(self, name, title): self.name = name - self.installation = installation + self.title = title + if title == "" or title == "-": + self.title = name class HostInfo: home = home @@ -202,10 +204,11 @@ class HostInfo: flatpaks = [] ref_to_flatpak = {} - remotes = [] + remotes = {} installations = [] masks = {} pins = {} + dependant_runtime_refs = [] @classmethod def get_flatpaks(this, callback=None): # Callback is a function to run after the host flatpaks are found @@ -215,27 +218,29 @@ class HostInfo: this.installations.clear() this.masks.clear() this.pins.clear() + this.dependant_runtime_refs.clear() def thread(task, *args): # Remotes def remote_info(installation): - # cmd = ['flatpak-spawn', '--host', - # 'flatpak', 'remotes'] - # if installation == "user" or installation == "system": - # cmd.append(f"--{installation}") - # else: - # cmd.append(f"--installation={installation}") - # output = subprocess.run( - # cmd, text=True, - # capture_output=True, - # ).stdout - # lines = output.strip().split("\n") - # for i in lines: - # if i != "": - # this.remotes.append(Remote(i.strip(), installation)) - # if installation == "user" or installation == "system": - # this.installations.append(installation) + cmd = ['flatpak-spawn', '--host', + 'flatpak', 'remotes', '--columns=name,title'] + if installation == "user" or installation == "system": + cmd.append(f"--{installation}") + else: + cmd.append(f"--installation={installation}") + output = subprocess.run( + cmd, text=True, + capture_output=True, + ).stdout + lines = output.strip().replace(" ", "").split("\n") + if lines[0] != '': + remote_list = [] + for line in lines: + line = line.split("\t") + remote_list.append(Remote(line[0], line[1])) + this.remotes[installation] = remote_list # Masks cmd = ['flatpak-spawn', '--host', @@ -278,6 +283,8 @@ class HostInfo: # Get specifically the installation name itself this.installations.append(line.replace("[Installation \"", "").replace("\"]", "").strip()) + this.installations.append("user") + this.installations.append("system") for i in this.installations: remote_info(i) remote_info("user") @@ -309,7 +316,10 @@ class HostInfo: if package.is_runtime: continue package.dependant_runtime = this.ref_to_flatpak[runtime] + if not runtime in this.dependant_runtime_refs: + this.dependant_runtime_refs.append(runtime) this.flatpaks = sorted(this.flatpaks, key=lambda flatpak: flatpak.info["name"].lower()) + this.dependant_runtime_refs = sorted(this.dependant_runtime_refs) Gio.Task.new(None, None, callback).run_in_thread(thread) \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 1d89fc3..572ed47 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -33,8 +33,14 @@ class PackagesPage(Adw.BreakpointBin): def generate_list(self, *args): self.packages_list_box.remove_all() + GLib.idle_add(lambda *_: self.filters_page.generate_list()) + first = True for package in HostInfo.flatpaks: - row = AppRow(package) + row = None + if first: + row = AppRow(package, lambda *_: self.stack.set_visible_child(self.packages_split)) + else: + row = AppRow(package) package.app_row = row row.masked_status_icon.set_visible(package.is_masked) row.pinned_status_icon.set_visible(package.is_pinned) @@ -51,7 +57,6 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.select_row(first_row) self.properties_page.set_properties(first_row.package) self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top - self.stack.set_visible_child(self.packages_split) def row_select_handler(self, list_box, row): self.properties_page.set_properties(row.package) @@ -99,6 +104,7 @@ class PackagesPage(Adw.BreakpointBin): def __init__(self, main_window, **kwargs): super().__init__(**kwargs) + HostInfo.get_flatpaks(callback=self.generate_list) # Extra Object Creation self.main_window = main_window @@ -107,8 +113,7 @@ class PackagesPage(Adw.BreakpointBin): self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) # Apply - HostInfo.get_flatpaks(callback=self.generate_list) - + self.set_status(self.loading_status) self.packages_list_box.set_filter_func(self.filter_func) self.content_stack.add_child(self.properties_page) self.content_stack.add_child(self.filters_page) @@ -126,4 +131,5 @@ class PackagesPage(Adw.BreakpointBin): self.select_button.connect("clicked", self.select_button_handler) self.filter_button.connect("toggled", self.filter_button_handler) self.packages_split.connect("notify::show-content", self.filter_page_handler) - self.packages_bpt.connect("apply", self.filter_page_handler) \ No newline at end of file + self.packages_bpt.connect("apply", self.filter_page_handler) + self.filter_button.set_active(True) \ No newline at end of file diff --git a/src/widgets/app_row.py b/src/widgets/app_row.py index a17a50d..0714d0f 100644 --- a/src/widgets/app_row.py +++ b/src/widgets/app_row.py @@ -12,17 +12,24 @@ class AppRow(Adw.ActionRow): masked_status_icon = gtc() check_button = gtc() - def __init__(self, package, **kwargs): + def idle_stuff(self): + if self.package.icon_path: + self.image.add_css_class("icon-dropshadow") + self.image.set_from_file(self.package.icon_path) + + if self.callback: + self.callback() + + def __init__(self, package, callback=None, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.package = package + self.callback = callback # Apply GLib.idle_add(lambda *_: self.set_title(package.info["name"])) GLib.idle_add(lambda *_: self.set_subtitle(package.info["id"])) - if package.icon_path: - GLib.idle_add(lambda *_: self.image.add_css_class("icon-dropshadow")) - GLib.idle_add(lambda *_: self.image.set_from_file(package.icon_path)) + GLib.idle_add(lambda *_: self.idle_stuff()) # Connections \ No newline at end of file From f9629a9b61e31be161d5beeb4560c59607cffb81 Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 11 Jul 2024 23:15:06 -0400 Subject: [PATCH 043/332] add uninstall ability --- src/host_info.py | 33 ++++++++++++++++++++++++++ src/packages_page/packages_page.py | 1 + src/properties_page/properties_page.py | 30 ++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/host_info.py b/src/host_info.py index 5dd0332..c605585 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -97,6 +97,38 @@ class Flatpak: Gio.Task.new(None, None, callback).run_in_thread(thread) + def uninstall(self, callback=None): + self.failed_uninstall = None + + def thread(*args): + prefix = ['flatpak-spawn', '--host'] + cmd = ['flatpak', 'uninstall', '-y', self.info["ref"]] + installation = self.info["installation"] + if installation == "system" or installation == "user": + cmd.append(f"--{installation}") + else: + cmd.append(f"--installation={installation}") + + try: + subprocess.run(prefix + cmd, check=True, text=True) + # print(prefix + cmd) + except subprocess.CalledProcessError as cpe: + if installation == "user": + self.failed_uninstall = cpe + return + + try: + subprocess.run(prefix + ['pkexec'] + cmd, check=True, text=True) + except subprocess.CalledProcessError as cpe2: + self.failed_uninstall = cpe2 + except Exception as e2: + self.failed_uninstall = e2 + + except Exception as e: + self.failed_uninstall = e + + Gio.Task.new(None, None, callback).run_in_thread(thread) + def get_cli_info(self): cli_info = {} cmd = "LC_ALL=C flatpak info " @@ -155,6 +187,7 @@ class Flatpak: self.dependant_runtime = None self.failed_app_run = None self.failed_mask = None + self.failed_uninstall = None self.app_row = None try: diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 572ed47..36902f2 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -111,6 +111,7 @@ class PackagesPage(Adw.BreakpointBin): self.properties_page = PropertiesPage(main_window, self) self.filters_page = FiltersPage(main_window, self) self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) + self.uninstall_status = StatusBox(_("Uninstalling…"), _("This should only take a moment")) # Apply self.set_status(self.loading_status) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 1816f32..9d1c400 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -199,13 +199,40 @@ class PropertiesPage(Adw.NavigationPage): self.package.set_pin(state, callback) + def uninstall_handler(self, *args): + def on_choice(_, response): + if response != 'continue': + return + self.packages_page.set_status(self.packages_page.uninstall_status) + self.package.uninstall(callback) + + def callback(*args): + if fail := self.package.failed_uninstall: + fail = fail.stderr if type(fail) == subprocess.CalledProcessError else fail + self.toast_overlay.add_toast(ErrorToast(_("Could not uninstall"), str(fail)).toast) + self.packages_page.stack.set_visible_child(self.packages_page.packages_split) + else: + self.packages_page.refresh_handler() + self.packages_page.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled {}").format(self.package.info["name"]))) + + # name = self.package.info["name"] + dialog = Adw.AlertDialog( + heading=_("Uninstall {}?").format(guhhh := self.package.info["name"]), + body=_("It will not be possible to use {} after removal.").format(guhhh), + ) + dialog.add_response('cancel', _("Cancel")) + dialog.add_response('continue', _("Uninstall")) + dialog.connect("response", on_choice) + dialog.set_response_appearance('continue', Adw.ResponseAppearance.DESTRUCTIVE) + dialog.present(self.main_window) + def runtime_row_handler(self, *args): new_page = self.__class__(self.main_window, self.packages_page) new_page.set_properties(self.package.dependant_runtime) self.nav_view.push(new_page) def open_app_handler(self, *args): - self.toast_overlay.add_toast(Adw.Toast(title=_("Opened {}").format(self.package.info["name"]))) + self.toast_overlay.add_toast(Adw.Toast(title=_("Openeing {}…").format(self.package.info["name"]))) def callback(*args): if fail := self.package.failed_app_run: @@ -257,6 +284,7 @@ class PropertiesPage(Adw.NavigationPage): self.trash_data_button.connect("clicked", self.trash_data_handler) self.runtime_row.connect("activated", self.runtime_row_handler) self.open_app_button.connect("clicked", self.open_app_handler) + self.uninstall_button.connect("clicked", self.uninstall_handler) self.mask_row.connect("activated", self.set_mask_handler) self.pin_row.connect("activated", self.set_pin_handler) self.change_version_row.connect("activated", self.change_version_handler) From b07ae30eb683fdb673723086235764780b41e957 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 12 Jul 2024 01:09:23 -0400 Subject: [PATCH 044/332] Continue work on filters --- src/filters_page/filters_page.blp | 61 +++++++++++++++++++--------- src/filters_page/filters_page.py | 63 +++++++++++++++++++++-------- src/packages_page/packages_page.blp | 1 + 3 files changed, 90 insertions(+), 35 deletions(-) diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp index 9d69141..be01de2 100644 --- a/src/filters_page/filters_page.blp +++ b/src/filters_page/filters_page.blp @@ -5,7 +5,12 @@ template $FiltersPage : Adw.NavigationPage { title: _("Filter Packages"); Adw.ToolbarView { [top] - Adw.HeaderBar {} + Adw.HeaderBar { + [start] + Button test_button { + label: "Test"; + } + } ScrolledWindow { Adw.Clamp { Box { @@ -17,7 +22,7 @@ template $FiltersPage : Adw.NavigationPage { halign: fill; spacing: 24; Adw.PreferencesGroup { - title: _("Package Type"); + title: _("Filter by Package Type"); description: _("Show packages of these types"); Adw.ActionRow application_row { title: _("Applications"); @@ -37,32 +42,52 @@ template $FiltersPage : Adw.NavigationPage { } } Adw.PreferencesGroup remotes_group { - title: _("Remotes"); - description: _("Show packages from these remotes"); + title: _("Filter by Remotes"); + description: _("Showing packages from all remotes"); header-suffix: - ToggleButton all_remotes_button { - Adw.ButtonContent { - label: _("Select All"); - icon-name: "selection-mode-symbolic"; - } + Switch all_remotes_button { valign: center; - styles ["flat"] } ; + Revealer { + reveal-child: bind all_remotes_button.active; + Box { + orientation: vertical; + spacing: 6; + SearchEntry remotes_search { + placeholder-text: _("Search Remotes"); + } + ListBox remotes_listbox { + selection-mode: none; + sensitive: bind all_remotes_button.active; + styles ["boxed-list"] + } + } + } } Adw.PreferencesGroup runtimes_group { - title: _("Runtimes"); - description: _("Show apps that use these runtimes"); + title: _("Filter by Runtimes"); + description: _("Showing packages using any runtime"); header-suffix: - ToggleButton all_runtimes_button { - Adw.ButtonContent { - label: _("Select All"); - icon-name: "selection-mode-symbolic"; - } + Switch all_runtimes_button { valign: center; - styles ["flat"] } ; + Revealer { + reveal-child: bind all_runtimes_button.active; + Box { + orientation: vertical; + spacing: 6; + SearchEntry runtimes_search { + placeholder-text: _("Search Remotes"); + } + ListBox runtimes_listbox { + selection-mode: none; + sensitive: bind all_runtimes_button.active; + styles ["boxed-list"] + } + } + } } } } diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index d1948a4..180199e 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -16,10 +16,17 @@ class FilterRow(Adw.ActionRow): class FiltersPage(Adw.NavigationPage): __gtype_name__ = 'FiltersPage' gtc = Gtk.Template.Child + + test_button = gtc() + app_check = gtc() runtime_check = gtc() remotes_group = gtc() + remotes_search = gtc() + remotes_listbox = gtc() runtimes_group = gtc() + runtimes_search = gtc() + runtimes_listbox = gtc() all_remotes_button = gtc() all_runtimes_button = gtc() @@ -27,11 +34,11 @@ class FiltersPage(Adw.NavigationPage): runtime_rows = [] def generate_list(self): - for row in self.remote_rows: - self.remotes_group.remove(row) + self.remotes_search.set_text("") + self.runtimes_search.set_text("") - for row in self.runtime_rows: - self.runtimes_group.remove(row) + self.remotes_listbox.remove_all() + self.runtimes_listbox.remove_all() self.remote_rows.clear() self.runtime_rows.clear() @@ -40,31 +47,47 @@ class FiltersPage(Adw.NavigationPage): self.runtime_check.set_active(self.settings.get_boolean("show-runtimes")) remotes_string = self.settings.get_string("runtimes-list") - self.all_remotes_button.set_active("all" == remotes_string) - for installation in HostInfo.installations: + self.all_remotes_button.set_active("all" != remotes_string) + for i, installation in enumerate(HostInfo.installations): try: for remote in HostInfo.remotes[installation]: row = FilterRow(remote, installation) row.set_title(remote.title) row.set_subtitle(_("Installation: {}").format(installation)) - row.check_button.set_active(self.all_remotes_button.get_active()) self.remote_rows.append(row) - self.remotes_group.add(row) + self.remotes_listbox.append(row) except KeyError: pass + self.remotes_search.set_visible(i > 5) runtimes_string = self.settings.get_string("runtimes-list") - self.all_runtimes_button.set_active("all" == runtimes_string) - for ref in HostInfo.dependant_runtime_refs: + self.all_runtimes_button.set_active("all" != runtimes_string) + for j, ref in enumerate(HostInfo.dependant_runtime_refs): row = FilterRow(ref) row.set_title(ref) - row.check_button.set_active(self.all_runtimes_button.get_active()) self.runtime_rows.append(row) - self.runtimes_group.add(row) + self.runtimes_listbox.append(row) + self.runtimes_search.set_visible(j > 5) - def select_all_handler(self, rows, button): - for row in rows: - row.check_button.set_active(button.get_active()) + def all_remotes_handler(self, switch, state): + self.remotes_group.set_description(_("Show packages from these remotes") if state else _("Showing packages from all remotes")) + + def all_runtimes_handler(self, switch, state): + self.runtimes_group.set_description(_("Show apps using these runtimes") if state else _("Showing packages using any runtime")) + + def remotes_filter_func(self, row): + search_text = self.remotes_search.get_text().lower() + return search_text in row.get_title().lower() + + def runtimes_filter_func(self, row): + search_text = self.runtimes_search.get_text().lower() + return search_text in row.get_title().lower() + + def test(self, *args): + print(self.settings.get_boolean("show-apps")) + print(self.settings.get_boolean("show-runtimes")) + print(self.settings.get_string("remotes-list")) + print(self.settings.get_string("runtimes-list")) def __init__(self, main_window, packages_page, **kwargs): super().__init__(**kwargs) @@ -73,7 +96,13 @@ class FiltersPage(Adw.NavigationPage): self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") # Apply + self.remotes_listbox.set_filter_func(self.remotes_filter_func) + self.runtimes_listbox.set_filter_func(self.runtimes_filter_func) # Connections - self.all_remotes_button.connect("toggled", lambda *_: self.select_all_handler(self.remote_rows, self.all_remotes_button)) - self.all_runtimes_button.connect("toggled", lambda *_: self.select_all_handler(self.runtime_rows, self.all_runtimes_button)) \ No newline at end of file + self.test_button.connect("clicked", self.test) + + self.remotes_search.connect("search-changed", lambda *_: self.remotes_listbox.invalidate_filter()) + self.runtimes_search.connect("search-changed", lambda *_: self.runtimes_listbox.invalidate_filter()) + self.all_remotes_button.connect("state-set", self.all_remotes_handler) + self.all_runtimes_button.connect("state-set", self.all_runtimes_handler) \ No newline at end of file diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index fb624e3..46fedef 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -64,6 +64,7 @@ template $PackagesPage : Adw.BreakpointBin { // key-capture-widget: packages_tbv; SearchEntry search_entry { hexpand: true; + placeholder-text: _("Search Packages"); } } ScrolledWindow scrolled_window { From b68be309823e62f9d4df6095e734f24113b20cbd Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 12 Jul 2024 15:31:57 -0400 Subject: [PATCH 045/332] Load all filters from gsettings into the UI --- src/filters_page/filters_page.blp | 60 ++++++++++++++++++++----------- src/filters_page/filters_page.py | 49 +++++++++++-------------- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp index be01de2..6126cf7 100644 --- a/src/filters_page/filters_page.blp +++ b/src/filters_page/filters_page.blp @@ -18,9 +18,9 @@ template $FiltersPage : Adw.NavigationPage { margin-end: 12; margin-top: 12; margin-bottom: 12; + spacing: 24; orientation: vertical; halign: fill; - spacing: 24; Adw.PreferencesGroup { title: _("Filter by Package Type"); description: _("Show packages of these types"); @@ -41,50 +41,68 @@ template $FiltersPage : Adw.NavigationPage { activatable-widget: runtime_check; } } + Adw.PreferencesGroup remotes_group { title: _("Filter by Remotes"); - description: _("Showing packages from all remotes"); + description: _("Showing packages from selected remotes"); header-suffix: Switch all_remotes_button { valign: center; } ; - Revealer { - reveal-child: bind all_remotes_button.active; + Adw.ActionRow { + visible: bind all_remotes_button.active inverted; + [child] Box { orientation: vertical; - spacing: 6; - SearchEntry remotes_search { - placeholder-text: _("Search Remotes"); + Label { + margin-top: 7; + label: _("Showing packages from all remotes"); + wrap: true; + halign: center; + styles ["heading"] } - ListBox remotes_listbox { - selection-mode: none; - sensitive: bind all_remotes_button.active; - styles ["boxed-list"] + Label { + label: _("Enable to show packages from selected remotes"); + margin-start: 16; + margin-end: 16; + margin-bottom: 8; + justify: center; + halign: center; + wrap: true; } } } } + Adw.PreferencesGroup runtimes_group { title: _("Filter by Runtimes"); - description: _("Showing packages using any runtime"); + description: _("Show apps using selected runtimes"); header-suffix: Switch all_runtimes_button { valign: center; } ; - Revealer { - reveal-child: bind all_runtimes_button.active; + Adw.ActionRow { + visible: bind all_runtimes_button.active inverted; + [child] Box { orientation: vertical; - spacing: 6; - SearchEntry runtimes_search { - placeholder-text: _("Search Remotes"); + Label { + margin-top: 7; + label: _("Showing apps using any runtime"); + wrap: true; + halign: center; + styles ["heading"] } - ListBox runtimes_listbox { - selection-mode: none; - sensitive: bind all_runtimes_button.active; - styles ["boxed-list"] + Label { + label: _("Enable to show apps using selected runtimes"); + margin-start: 16; + margin-end: 16; + margin-bottom: 8; + justify: center; + halign: center; + wrap: true; } } } diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index 180199e..e24dc59 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -22,11 +22,7 @@ class FiltersPage(Adw.NavigationPage): app_check = gtc() runtime_check = gtc() remotes_group = gtc() - remotes_search = gtc() - remotes_listbox = gtc() runtimes_group = gtc() - runtimes_search = gtc() - runtimes_listbox = gtc() all_remotes_button = gtc() all_runtimes_button = gtc() @@ -34,11 +30,12 @@ class FiltersPage(Adw.NavigationPage): runtime_rows = [] def generate_list(self): - self.remotes_search.set_text("") - self.runtimes_search.set_text("") - self.remotes_listbox.remove_all() - self.runtimes_listbox.remove_all() + for row in self.remote_rows: + self.remotes_group.remove(row) + + for row in self.runtime_rows: + self.runtimes_group.remove(row) self.remote_rows.clear() self.runtime_rows.clear() @@ -46,42 +43,40 @@ class FiltersPage(Adw.NavigationPage): self.app_check.set_active(self.settings.get_boolean("show-apps")) self.runtime_check.set_active(self.settings.get_boolean("show-runtimes")) - remotes_string = self.settings.get_string("runtimes-list") - self.all_remotes_button.set_active("all" != remotes_string) + remotes_string = self.settings.get_string("remotes-list") for i, installation in enumerate(HostInfo.installations): try: for remote in HostInfo.remotes[installation]: row = FilterRow(remote, installation) row.set_title(remote.title) row.set_subtitle(_("Installation: {}").format(installation)) + row.check_button.set_active(f"{remote.name}<>{installation}" in remotes_string) + row.set_visible(False) self.remote_rows.append(row) - self.remotes_listbox.append(row) + self.remotes_group.add(row) except KeyError: pass - self.remotes_search.set_visible(i > 5) + self.all_remotes_button.set_active("all" != remotes_string) runtimes_string = self.settings.get_string("runtimes-list") - self.all_runtimes_button.set_active("all" != runtimes_string) for j, ref in enumerate(HostInfo.dependant_runtime_refs): row = FilterRow(ref) row.set_title(ref) + row.check_button.set_active(ref in runtimes_string) + row.set_visible(False) self.runtime_rows.append(row) - self.runtimes_listbox.append(row) - self.runtimes_search.set_visible(j > 5) + self.runtimes_group.add(row) + self.all_runtimes_button.set_active("all" != runtimes_string) def all_remotes_handler(self, switch, state): - self.remotes_group.set_description(_("Show packages from these remotes") if state else _("Showing packages from all remotes")) + for row in self.remote_rows: + row.set_visible(state) + row.set_sensitive(state) def all_runtimes_handler(self, switch, state): - self.runtimes_group.set_description(_("Show apps using these runtimes") if state else _("Showing packages using any runtime")) - - def remotes_filter_func(self, row): - search_text = self.remotes_search.get_text().lower() - return search_text in row.get_title().lower() - - def runtimes_filter_func(self, row): - search_text = self.runtimes_search.get_text().lower() - return search_text in row.get_title().lower() + for row in self.runtime_rows: + row.set_visible(state) + row.set_sensitive(state) def test(self, *args): print(self.settings.get_boolean("show-apps")) @@ -96,13 +91,9 @@ class FiltersPage(Adw.NavigationPage): self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") # Apply - self.remotes_listbox.set_filter_func(self.remotes_filter_func) - self.runtimes_listbox.set_filter_func(self.runtimes_filter_func) # Connections self.test_button.connect("clicked", self.test) - self.remotes_search.connect("search-changed", lambda *_: self.remotes_listbox.invalidate_filter()) - self.runtimes_search.connect("search-changed", lambda *_: self.runtimes_listbox.invalidate_filter()) self.all_remotes_button.connect("state-set", self.all_remotes_handler) self.all_runtimes_button.connect("state-set", self.all_runtimes_handler) \ No newline at end of file From 0659f4ed011eb3a8babc3ee0c3bda3ae910a052e Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 12 Jul 2024 16:53:50 -0400 Subject: [PATCH 046/332] Filters page can now set gsettings --- src/filters_page/filters_page.py | 86 +++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index e24dc59..6dd330b 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -29,7 +29,32 @@ class FiltersPage(Adw.NavigationPage): remote_rows = [] runtime_rows = [] + def update_gsettings(self): + if not self.is_settings_settable: + return + self.settings.set_boolean("show-apps", self.show_apps) + self.settings.set_boolean("show-runtimes", self.show_runtimes) + self.settings.set_string("remotes-list", self.remotes_string) + self.settings.set_string("runtimes-list", self.runtimes_string) + self.total_sets += 1 + self.test() + + def remote_row_check_handler(self, row): + if row.check_button.get_active(): + self.remotes_string += f"{row.item.name}<>{row.installation};" + else: + self.remotes_string = self.remotes_string.replace(f"{row.item.name}<>{row.installation};", "") + self.update_gsettings() + + def runtime_row_check_handler(self, row): + if row.check_button.get_active(): + self.runtimes_string += f"{row.item}," + else: + self.runtimes_string = self.runtimes_string.replace(f"{row.item},", "") + self.update_gsettings() + def generate_list(self): + self.is_settings_settable = False for row in self.remote_rows: self.remotes_group.remove(row) @@ -40,60 +65,97 @@ class FiltersPage(Adw.NavigationPage): self.remote_rows.clear() self.runtime_rows.clear() - self.app_check.set_active(self.settings.get_boolean("show-apps")) - self.runtime_check.set_active(self.settings.get_boolean("show-runtimes")) + self.app_check.set_active(self.show_apps) + self.runtime_check.set_active(self.show_runtimes) - remotes_string = self.settings.get_string("remotes-list") for i, installation in enumerate(HostInfo.installations): try: for remote in HostInfo.remotes[installation]: row = FilterRow(remote, installation) row.set_title(remote.title) row.set_subtitle(_("Installation: {}").format(installation)) - row.check_button.set_active(f"{remote.name}<>{installation}" in remotes_string) - row.set_visible(False) + row.check_button.set_active(f"{remote.name}<>{installation}" in self.remotes_string) + row.check_button.connect("toggled", lambda *_, row=row: self.remote_row_check_handler(row)) + row.set_visible(self.all_remotes_button.get_active()) self.remote_rows.append(row) self.remotes_group.add(row) except KeyError: pass - self.all_remotes_button.set_active("all" != remotes_string) + self.all_remotes_button.set_active("all" != self.remotes_string) - runtimes_string = self.settings.get_string("runtimes-list") for j, ref in enumerate(HostInfo.dependant_runtime_refs): row = FilterRow(ref) row.set_title(ref) - row.check_button.set_active(ref in runtimes_string) - row.set_visible(False) + row.check_button.set_active(ref in self.runtimes_string) + row.check_button.connect("toggled", lambda *_, row=row: self.runtime_row_check_handler(row)) + row.set_visible(self.all_runtimes_button.get_active()) self.runtime_rows.append(row) self.runtimes_group.add(row) - self.all_runtimes_button.set_active("all" != runtimes_string) + self.all_runtimes_button.set_active("all" != self.runtimes_string) + self.is_settings_settable = True def all_remotes_handler(self, switch, state): + self.remotes_string = "" + if not state: + self.remotes_string = "all" + for row in self.remote_rows: row.set_visible(state) - row.set_sensitive(state) + if state and row.check_button.get_active(): + self.remotes_string += f"{row.item.name}<>{row.installation};" + elif state: + self.remotes_string.replace(f"{row.item.name}<>{row.installation};", "") + + self.update_gsettings() def all_runtimes_handler(self, switch, state): + self.runtimes_string = "" + if not state: + self.runtimes_string = "all" + for row in self.runtime_rows: row.set_visible(state) - row.set_sensitive(state) + if state and row.check_button.get_active(): + self.runtimes_string += row.item + elif state: + self.runtimes_string.replace + + self.update_gsettings() def test(self, *args): + print('\n-------------------------------------') print(self.settings.get_boolean("show-apps")) print(self.settings.get_boolean("show-runtimes")) print(self.settings.get_string("remotes-list")) print(self.settings.get_string("runtimes-list")) + print("total sets:", self.total_sets) + + def app_check_handler(self, *args): + self.show_apps = self.app_check.get_active() + self.update_gsettings() + + def runtime_check_handler(self, *args): + self.show_runtimes = self.runtime_check.get_active() + self.update_gsettings() def __init__(self, main_window, packages_page, **kwargs): super().__init__(**kwargs) # Extra Objects Creation self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") + self.is_settings_settable = False + self.show_apps = self.settings.get_boolean("show-apps") + self.show_runtimes = self.settings.get_boolean("show-runtimes") + self.remotes_string = self.settings.get_string("remotes-list") + self.runtimes_string = self.settings.get_string("runtimes-list") + self.total_sets = 0 # Apply # Connections self.test_button.connect("clicked", self.test) + self.app_check.connect("toggled", self.app_check_handler) + self.runtime_check.connect("toggled", self.runtime_check_handler) self.all_remotes_button.connect("state-set", self.all_remotes_handler) self.all_runtimes_button.connect("state-set", self.all_runtimes_handler) \ No newline at end of file From 57cfd48061a3056cb9b2a5dbadaeabeff78f29b3 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 12 Jul 2024 18:28:49 -0400 Subject: [PATCH 047/332] Filters now actually filter the list --- src/filters_page/filters_page.blp | 7 +-- src/filters_page/filters_page.py | 70 +++++++++++++++++------------- src/packages_page/packages_page.py | 23 +++++++++- 3 files changed, 64 insertions(+), 36 deletions(-) diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp index 6126cf7..6f32502 100644 --- a/src/filters_page/filters_page.blp +++ b/src/filters_page/filters_page.blp @@ -5,12 +5,7 @@ template $FiltersPage : Adw.NavigationPage { title: _("Filter Packages"); Adw.ToolbarView { [top] - Adw.HeaderBar { - [start] - Button test_button { - label: "Test"; - } - } + Adw.HeaderBar {} ScrolledWindow { Adw.Clamp { Box { diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index 6dd330b..2481896 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -16,7 +16,6 @@ class FilterRow(Adw.ActionRow): class FiltersPage(Adw.NavigationPage): __gtype_name__ = 'FiltersPage' gtc = Gtk.Template.Child - test_button = gtc() app_check = gtc() @@ -33,11 +32,11 @@ class FiltersPage(Adw.NavigationPage): if not self.is_settings_settable: return self.settings.set_boolean("show-apps", self.show_apps) - self.settings.set_boolean("show-runtimes", self.show_runtimes) self.settings.set_string("remotes-list", self.remotes_string) + self.settings.set_boolean("show-runtimes", self.show_runtimes) self.settings.set_string("runtimes-list", self.runtimes_string) - self.total_sets += 1 - self.test() + self.packages_page.apply_filters() + print("set gsettings") def remote_row_check_handler(self, row): if row.check_button.get_active(): @@ -48,26 +47,22 @@ class FiltersPage(Adw.NavigationPage): def runtime_row_check_handler(self, row): if row.check_button.get_active(): - self.runtimes_string += f"{row.item}," + self.runtimes_string += f"{row.item};" else: - self.runtimes_string = self.runtimes_string.replace(f"{row.item},", "") + self.runtimes_string = self.runtimes_string.replace(f"{row.item};", "") self.update_gsettings() - def generate_list(self): - self.is_settings_settable = False - + def generate_remote_filters(self): for row in self.remote_rows: self.remotes_group.remove(row) - - for row in self.runtime_rows: - self.runtimes_group.remove(row) - self.remote_rows.clear() - self.runtime_rows.clear() - - self.app_check.set_active(self.show_apps) - self.runtime_check.set_active(self.show_runtimes) - + if len(HostInfo.remotes) < 2 and len(list(HostInfo.remotes.items())[0][1]) < 2: + self.remotes_group.set_visible(False) + if self.remotes_string != "all": + self.remotes_string = "all" + self.settings.set_string("remotes-list", self.remotes_string) + self.packages_page.apply_filters() + return for i, installation in enumerate(HostInfo.installations): try: for remote in HostInfo.remotes[installation]: @@ -83,6 +78,17 @@ class FiltersPage(Adw.NavigationPage): pass self.all_remotes_button.set_active("all" != self.remotes_string) + def generate_runtime_filters(self): + for row in self.runtime_rows: + self.runtimes_group.remove(row) + self.runtime_rows.clear() + if len(HostInfo.dependant_runtime_refs) < 2: + self.runtimes_group.set_visible(False) + if self.runtimes_string != "all": + self.runtimes_string = "all" + self.settings.set_string("runtimes-list", self.runtimes_string) + self.packages_page.apply_filters() + return for j, ref in enumerate(HostInfo.dependant_runtime_refs): row = FilterRow(ref) row.set_title(ref) @@ -92,6 +98,16 @@ class FiltersPage(Adw.NavigationPage): self.runtime_rows.append(row) self.runtimes_group.add(row) self.all_runtimes_button.set_active("all" != self.runtimes_string) + + def generate_list(self): + self.is_settings_settable = False + + self.app_check.set_active(self.show_apps) + self.runtime_check.set_active(self.show_runtimes) + + self.generate_remote_filters() + self.generate_runtime_filters() + self.is_settings_settable = True def all_remotes_handler(self, switch, state): @@ -116,20 +132,12 @@ class FiltersPage(Adw.NavigationPage): for row in self.runtime_rows: row.set_visible(state) if state and row.check_button.get_active(): - self.runtimes_string += row.item + self.runtimes_string += f"{row.item};" elif state: - self.runtimes_string.replace + self.runtimes_string.replace(f"{row.item};", "") self.update_gsettings() - def test(self, *args): - print('\n-------------------------------------') - print(self.settings.get_boolean("show-apps")) - print(self.settings.get_boolean("show-runtimes")) - print(self.settings.get_string("remotes-list")) - print(self.settings.get_string("runtimes-list")) - print("total sets:", self.total_sets) - def app_check_handler(self, *args): self.show_apps = self.app_check.get_active() self.update_gsettings() @@ -142,15 +150,19 @@ class FiltersPage(Adw.NavigationPage): super().__init__(**kwargs) # Extra Objects Creation + self.packages_page = packages_page self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") self.is_settings_settable = False self.show_apps = self.settings.get_boolean("show-apps") self.show_runtimes = self.settings.get_boolean("show-runtimes") self.remotes_string = self.settings.get_string("remotes-list") self.runtimes_string = self.settings.get_string("runtimes-list") - self.total_sets = 0 # Apply + if "," in self.runtimes_string: + # Convert Warehouse 1.X runtimes filter string from , to ; for item seperationg + self.runtimes_string = self.runtimes_string.replace(",", ";") + self.settings.set_string("runtimes-list", self.runtimes_string) # Connections self.test_button.connect("clicked", self.test) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 36902f2..5be4585 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -1,4 +1,4 @@ -from gi.repository import Adw, Gtk, GLib#, Gio, Pango +from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .app_row import AppRow from .error_toast import ErrorToast @@ -31,6 +31,25 @@ class PackagesPage(Adw.BreakpointBin): # This must be set to the created object from within the class's __init__ method instance = None + def apply_filters(self): + i = 0 + show_apps = self.filter_settings.get_boolean("show-apps") + show_runtimes = self.filter_settings.get_boolean("show-runtimes") + remotes_list = self.filter_settings.get_string("remotes-list") + runtimes_list = self.filter_settings.get_string("runtimes-list") + while row := self.packages_list_box.get_row_at_index(i): + i += 1 + visible = True + if row.package.is_runtime and not show_runtimes: + visible = False + if (not row.package.is_runtime) and (not show_apps): + visible = False + if remotes_list != "all" and not f"{row.package.info['origin']}<>{row.package.info['installation']}" in remotes_list: + 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 + GLib.idle_add(row.set_visible, visible) + def generate_list(self, *args): self.packages_list_box.remove_all() GLib.idle_add(lambda *_: self.filters_page.generate_list()) @@ -57,6 +76,7 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.select_row(first_row) self.properties_page.set_properties(first_row.package) self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top + self.apply_filters() def row_select_handler(self, list_box, row): self.properties_page.set_properties(row.package) @@ -112,6 +132,7 @@ class PackagesPage(Adw.BreakpointBin): self.filters_page = FiltersPage(main_window, self) self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) self.uninstall_status = StatusBox(_("Uninstalling…"), _("This should only take a moment")) + self.filter_settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") # Apply self.set_status(self.loading_status) From ccde24f7d7c9c3d86ec70394a98050869b4bd28e Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 12 Jul 2024 18:29:35 -0400 Subject: [PATCH 048/332] remove testing stuff --- src/filters_page/filters_page.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index 2481896..866fd47 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -16,8 +16,6 @@ class FilterRow(Adw.ActionRow): class FiltersPage(Adw.NavigationPage): __gtype_name__ = 'FiltersPage' gtc = Gtk.Template.Child - test_button = gtc() - app_check = gtc() runtime_check = gtc() remotes_group = gtc() @@ -165,8 +163,6 @@ class FiltersPage(Adw.NavigationPage): self.settings.set_string("runtimes-list", self.runtimes_string) # Connections - self.test_button.connect("clicked", self.test) - self.app_check.connect("toggled", self.app_check_handler) self.runtime_check.connect("toggled", self.runtime_check_handler) self.all_remotes_button.connect("state-set", self.all_remotes_handler) From ce24cbe125133fdf4651557107a3a83bf31620cd Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 12 Jul 2024 21:04:43 -0400 Subject: [PATCH 049/332] organize filters page code --- src/filters_page/filters_page.blp | 8 +-- src/filters_page/filters_page.py | 93 +++++++++++++++--------------- src/packages_page/packages_page.py | 2 +- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp index 6f32502..6eb6119 100644 --- a/src/filters_page/filters_page.blp +++ b/src/filters_page/filters_page.blp @@ -41,12 +41,12 @@ template $FiltersPage : Adw.NavigationPage { title: _("Filter by Remotes"); description: _("Showing packages from selected remotes"); header-suffix: - Switch all_remotes_button { + Switch all_remotes_switch { valign: center; } ; Adw.ActionRow { - visible: bind all_remotes_button.active inverted; + visible: bind all_remotes_switch.active inverted; [child] Box { orientation: vertical; @@ -74,12 +74,12 @@ template $FiltersPage : Adw.NavigationPage { title: _("Filter by Runtimes"); description: _("Show apps using selected runtimes"); header-suffix: - Switch all_runtimes_button { + Switch all_runtimes_switch { valign: center; } ; Adw.ActionRow { - visible: bind all_runtimes_button.active inverted; + visible: bind all_runtimes_switch.active inverted; [child] Box { orientation: vertical; diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index 866fd47..8d25b5e 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -19,9 +19,9 @@ class FiltersPage(Adw.NavigationPage): app_check = gtc() runtime_check = gtc() remotes_group = gtc() + all_remotes_switch = gtc() runtimes_group = gtc() - all_remotes_button = gtc() - all_runtimes_button = gtc() + all_runtimes_switch = gtc() remote_rows = [] runtime_rows = [] @@ -30,11 +30,46 @@ class FiltersPage(Adw.NavigationPage): if not self.is_settings_settable: return self.settings.set_boolean("show-apps", self.show_apps) - self.settings.set_string("remotes-list", self.remotes_string) self.settings.set_boolean("show-runtimes", self.show_runtimes) + self.settings.set_string("remotes-list", self.remotes_string) self.settings.set_string("runtimes-list", self.runtimes_string) self.packages_page.apply_filters() - print("set gsettings") + + def app_check_handler(self, *args): + self.show_apps = self.app_check.get_active() + self.update_gsettings() + + def runtime_check_handler(self, *args): + self.show_runtimes = self.runtime_check.get_active() + self.update_gsettings() + + def all_remotes_handler(self, switch, state): + self.remotes_string = "" + if not state: + self.remotes_string = "all" + + for row in self.remote_rows: + row.set_visible(state) + if state and row.check_button.get_active(): + self.remotes_string += f"{row.item.name}<>{row.installation};" + elif state: + self.remotes_string.replace(f"{row.item.name}<>{row.installation};", "") + + self.update_gsettings() + + def all_runtimes_handler(self, switch, state): + self.runtimes_string = "" + if not state: + self.runtimes_string = "all" + + for row in self.runtime_rows: + row.set_visible(state) + if state and row.check_button.get_active(): + self.runtimes_string += f"{row.item};" + elif state: + self.runtimes_string.replace(f"{row.item};", "") + + self.update_gsettings() def remote_row_check_handler(self, row): if row.check_button.get_active(): @@ -69,12 +104,12 @@ class FiltersPage(Adw.NavigationPage): row.set_subtitle(_("Installation: {}").format(installation)) row.check_button.set_active(f"{remote.name}<>{installation}" in self.remotes_string) row.check_button.connect("toggled", lambda *_, row=row: self.remote_row_check_handler(row)) - row.set_visible(self.all_remotes_button.get_active()) + row.set_visible(self.all_remotes_switch.get_active()) self.remote_rows.append(row) self.remotes_group.add(row) except KeyError: pass - self.all_remotes_button.set_active("all" != self.remotes_string) + self.all_remotes_switch.set_active("all" != self.remotes_string) def generate_runtime_filters(self): for row in self.runtime_rows: @@ -92,12 +127,12 @@ class FiltersPage(Adw.NavigationPage): row.set_title(ref) row.check_button.set_active(ref in self.runtimes_string) row.check_button.connect("toggled", lambda *_, row=row: self.runtime_row_check_handler(row)) - row.set_visible(self.all_runtimes_button.get_active()) + row.set_visible(self.all_runtimes_switch.get_active()) self.runtime_rows.append(row) self.runtimes_group.add(row) - self.all_runtimes_button.set_active("all" != self.runtimes_string) + self.all_runtimes_switch.set_active("all" != self.runtimes_string) - def generate_list(self): + def generate_filters(self): self.is_settings_settable = False self.app_check.set_active(self.show_apps) @@ -108,42 +143,6 @@ class FiltersPage(Adw.NavigationPage): self.is_settings_settable = True - def all_remotes_handler(self, switch, state): - self.remotes_string = "" - if not state: - self.remotes_string = "all" - - for row in self.remote_rows: - row.set_visible(state) - if state and row.check_button.get_active(): - self.remotes_string += f"{row.item.name}<>{row.installation};" - elif state: - self.remotes_string.replace(f"{row.item.name}<>{row.installation};", "") - - self.update_gsettings() - - def all_runtimes_handler(self, switch, state): - self.runtimes_string = "" - if not state: - self.runtimes_string = "all" - - for row in self.runtime_rows: - row.set_visible(state) - if state and row.check_button.get_active(): - self.runtimes_string += f"{row.item};" - elif state: - self.runtimes_string.replace(f"{row.item};", "") - - self.update_gsettings() - - def app_check_handler(self, *args): - self.show_apps = self.app_check.get_active() - self.update_gsettings() - - def runtime_check_handler(self, *args): - self.show_runtimes = self.runtime_check.get_active() - self.update_gsettings() - def __init__(self, main_window, packages_page, **kwargs): super().__init__(**kwargs) @@ -165,5 +164,5 @@ class FiltersPage(Adw.NavigationPage): # Connections self.app_check.connect("toggled", self.app_check_handler) self.runtime_check.connect("toggled", self.runtime_check_handler) - self.all_remotes_button.connect("state-set", self.all_remotes_handler) - self.all_runtimes_button.connect("state-set", self.all_runtimes_handler) \ No newline at end of file + self.all_remotes_switch.connect("state-set", self.all_remotes_handler) + self.all_runtimes_switch.connect("state-set", self.all_runtimes_handler) \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 5be4585..5843d54 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -52,7 +52,7 @@ class PackagesPage(Adw.BreakpointBin): def generate_list(self, *args): self.packages_list_box.remove_all() - GLib.idle_add(lambda *_: self.filters_page.generate_list()) + GLib.idle_add(lambda *_: self.filters_page.generate_filters()) first = True for package in HostInfo.flatpaks: row = None From 573cad17d834442fbc22caf173b9fd6897a99b8a Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 13 Jul 2024 00:45:59 -0400 Subject: [PATCH 050/332] Finalize filters page and add status pages properly --- src/packages_page/packages_page.blp | 199 ++++---- src/packages_page/packages_page.py | 75 ++- src/properties_page/properties_page.blp | 578 ++++++++++++------------ src/properties_page/properties_page.py | 6 +- 4 files changed, 470 insertions(+), 388 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 46fedef..a87fdd9 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -16,110 +16,131 @@ template $PackagesPage : Adw.BreakpointBin { } Adw.ToastOverlay packages_toast_overlay { - Stack stack { - Adw.ToolbarView status_view { - [top] - Adw.HeaderBar { - show-title: false; - } - } - Adw.NavigationSplitView packages_split { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage packages_navpage { - title: "Packages"; - Adw.ToolbarView packages_tbv { - [top] - Adw.HeaderBar { - [start] - Button sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Packages"); - } - [start] - Button refresh_button { - icon-name: "arrow-circular-top-right-symbolic"; - tooltip-text: _("Refresh List"); - } - [end] - ToggleButton filter_button { - icon-name: "funnel-symbolic"; - tooltip-text: _("Filter Packages"); - } - [end] - ToggleButton select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select Packages"); - } + Adw.NavigationSplitView packages_split { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage packages_navpage { + title: "Packages"; + Adw.ToolbarView packages_tbv { + [top] + Adw.HeaderBar { + [start] + Button sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); } - [top] - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - // key-capture-widget: packages_tbv; - SearchEntry search_entry { - hexpand: true; - placeholder-text: _("Search Packages"); - } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } + [start] + Button refresh_button { + icon-name: "arrow-circular-top-right-symbolic"; + tooltip-text: _("Refresh List"); + } + [end] + ToggleButton filter_button { + icon-name: "funnel-symbolic"; + tooltip-text: _("Filter Packages"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select Packages"); + } + } + [top] + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search Packages"); + } + } + Stack status_stack { + Adw.StatusPage loading_packages { + title: _("Loading Packages"); + description: _("This should only take a moment"); + child: + Spinner { + spinning: true; + } + ; } ScrolledWindow scrolled_window { ListBox packages_list_box { styles ["navigation-sidebar"] } } - [bottom] - Revealer { - reveal-child: bind select_button.active; - transition-type: slide_up; - [center] - Box { - styles ["toolbar"] - hexpand: true; - homogeneous: true; - Button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "selection-mode-symbolic"; - label: _("Select All"); - can-shrink: true; - } + Adw.StatusPage uninstalling { + title: _("Uninstalling Packages"); + description: _("This should only take a moment"); + child: + Spinner { + spinning: true; } - MenuButton copy_button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "edit-copy-symbolic"; - label: _("Copy"); - can-shrink: true; - } - popover: copy_pop; + ; + } + Adw.StatusPage no_filter_results { + title: _("No Packages Match Filters"); + description: _("No installed package matches all of the currently applied filters"); + icon-name: "funnel-symbolic"; + } + Adw.StatusPage no_packages { + title: _("No Packages Found"); + description: _("Warehouse cannot see the list of installed packages or your system has no packages installed"); + icon-name: "error-symbolic"; + } + } + [bottom] + Revealer { + reveal-child: bind select_button.active; + transition-type: slide_up; + [center] + Box { + styles ["toolbar"] + hexpand: true; + homogeneous: true; + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "selection-mode-symbolic"; + label: _("Select All"); + can-shrink: true; } - Button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "user-trash-symbolic"; - label: _("Uninstall"); - can-shrink: true; - } + } + MenuButton copy_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; + } + popover: copy_pop; + } + Button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "user-trash-symbolic"; + label: _("Uninstall"); + can-shrink: true; } } } } } - ; - content: - Adw.NavigationPage { - title: "Content Stack"; - Stack content_stack { - transition-type: slide_left_right; - } + } + ; + content: + Adw.NavigationPage { + title: "Content Stack"; + Stack content_stack { + transition-type: slide_left_right; } - ; - } + } + ; } } } diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 5843d54..f586539 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -12,8 +12,12 @@ class PackagesPage(Adw.BreakpointBin): gtc = Gtk.Template.Child packages_bpt = gtc() packages_toast_overlay = gtc() - stack = gtc() + status_stack = gtc() scrolled_window = gtc() + uninstalling = gtc() + loading_packages = gtc() + no_filter_results = gtc() + no_packages = gtc() sidebar_button = gtc() filter_button = gtc() refresh_button = gtc() @@ -23,7 +27,6 @@ class PackagesPage(Adw.BreakpointBin): packages_list_box = gtc() select_button = gtc() packages_navpage = gtc() - status_view = gtc() content_stack = gtc() # Referred to in the main window @@ -31,12 +34,42 @@ class PackagesPage(Adw.BreakpointBin): # This must be set to the created object from within the class's __init__ method instance = None + def set_status(self, to_set): + + if to_set is self.scrolled_window: + self.properties_page.stack.set_visible_child(self.properties_page.nav_view) + self.select_button.set_sensitive(True) + self.filter_button.set_sensitive(True) + self.filters_page.set_sensitive(True) + else: + self.select_button.set_sensitive(False) + + if to_set is self.loading_packages or to_set is self.uninstalling: + self.properties_page.stack.set_visible_child(self.properties_page.loading_tbv) + self.filter_button.set_sensitive(False) + self.filters_page.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) + self.filter_button.set_active(False) + + if to_set is self.no_filter_results: + self.properties_page.stack.set_visible_child(self.properties_page.error_tbv) + self.filter_button.set_sensitive(True) + self.filters_page.set_sensitive(True) + if not self.packages_split.get_collapsed(): + self.filter_button.set_active(True) + + self.status_stack.set_visible_child(to_set) + def apply_filters(self): i = 0 show_apps = self.filter_settings.get_boolean("show-apps") show_runtimes = self.filter_settings.get_boolean("show-runtimes") remotes_list = self.filter_settings.get_string("remotes-list") runtimes_list = self.filter_settings.get_string("runtimes-list") + total_visible = 0 while row := self.packages_list_box.get_row_at_index(i): i += 1 visible = True @@ -49,17 +82,21 @@ class PackagesPage(Adw.BreakpointBin): 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 GLib.idle_add(row.set_visible, visible) + if visible: + total_visible += 1 + if total_visible == 0: + self.set_status(self.no_filter_results) + else: + GLib.idle_add(lambda *_: self.set_status(self.scrolled_window)) def generate_list(self, *args): self.packages_list_box.remove_all() GLib.idle_add(lambda *_: self.filters_page.generate_filters()) - first = True + if len(HostInfo.flatpaks) == 0: + self.set_status(self.no_packages) + return for package in HostInfo.flatpaks: - row = None - if first: - row = AppRow(package, lambda *_: self.stack.set_visible_child(self.packages_split)) - else: - row = AppRow(package) + row = AppRow(package) package.app_row = row row.masked_status_icon.set_visible(package.is_masked) row.pinned_status_icon.set_visible(package.is_pinned) @@ -98,14 +135,9 @@ class PackagesPage(Adw.BreakpointBin): GLib.idle_add(row.check_button.set_active, False) GLib.idle_add(row.check_button.set_visible, is_enabled) - def set_status(self, status_box): - self.stack.set_visible_child(self.status_view) - if self.status_view.get_content() == status_box: - return - self.status_view.set_content(status_box) - def refresh_handler(self, *args): - self.set_status(self.loading_status) + self.select_button.set_active(False) + self.set_status(self.loading_packages) HostInfo.get_flatpaks(callback=self.generate_list) def select_button_handler(self, button): @@ -130,17 +162,21 @@ class PackagesPage(Adw.BreakpointBin): self.main_window = main_window self.properties_page = PropertiesPage(main_window, self) self.filters_page = FiltersPage(main_window, self) - self.loading_status = StatusBox(_("Fetching Packages"), _("This should only take a moment")) - self.uninstall_status = StatusBox(_("Uninstalling…"), _("This should only take a moment")) self.filter_settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") # Apply - self.set_status(self.loading_status) + # self.set_status("loading_packages") self.packages_list_box.set_filter_func(self.filter_func) self.content_stack.add_child(self.properties_page) self.content_stack.add_child(self.filters_page) self.__class__.instance = self + self.loading_packages + self.uninstalling + self.no_filter_results + self.no_packages + self.scrolled_window + # Connections main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) @@ -153,5 +189,4 @@ class PackagesPage(Adw.BreakpointBin): self.select_button.connect("clicked", self.select_button_handler) self.filter_button.connect("toggled", self.filter_button_handler) self.packages_split.connect("notify::show-content", self.filter_page_handler) - self.packages_bpt.connect("apply", self.filter_page_handler) - self.filter_button.set_active(True) \ No newline at end of file + self.packages_bpt.connect("apply", self.filter_page_handler) \ No newline at end of file diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 9e47f9f..3e20f44 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -3,318 +3,340 @@ using Adw 1; template $PropertiesPage : Adw.NavigationPage { title: "Outer Page"; - Adw.NavigationView nav_view { - Adw.NavigationPage inner_nav_page { - title: "Inner Page"; - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView { - [top] - Adw.HeaderBar header_bar { - show-title: false; - [end] - MenuButton more_menu { - icon-name: "view-more-symbolic"; - popover: - Popover { - styles ["menu"] - ListBox more_list { - ListBoxRow details { - Label { - label: _("Show Details in Store"); + Stack stack { + Adw.ToolbarView loading_tbv { + [top] + Adw.HeaderBar { + show-title: false; + } + Adw.StatusPage { + title: _("Loading Properties"); + } + } + Adw.ToolbarView error_tbv { + [top] + Adw.HeaderBar { + show-title: false; + } + Adw.StatusPage { + title: _("Properties Page Unavailable"); + description: _("Cannot show the properties page at this time"); + icon-name: "error-symbolic"; + } + } + Adw.NavigationView nav_view { + Adw.NavigationPage inner_nav_page { + title: "Inner Page"; + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar { + show-title: false; + [end] + MenuButton more_menu { + icon-name: "view-more-symbolic"; + popover: + Popover { + styles ["menu"] + ListBox more_list { + ListBoxRow details { + Label { + label: _("Show Details in Store"); + } } - } - ListBoxRow reinstall { - Label { - label: _("Reinstall"); + ListBoxRow reinstall { + Label { + label: _("Reinstall"); + } } - } - ListBoxRow copy_launch { - Label { - label: _("Copy Launch Command"); + ListBoxRow copy_launch { + Label { + label: _("Copy Launch Command"); + } } - } - ListBoxRow view_snapshots { - Label { - label: _("View Snapshots"); + ListBoxRow view_snapshots { + Label { + label: _("View Snapshots"); + } } } } - } - ; + ; + } } - } - ScrolledWindow scrolled_window { - Adw.Clamp { - Box { - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - orientation: vertical; - halign: fill; - - Image app_icon { - pixel-size: 100; - margin-top: 6; - margin-bottom: 18; - icon-name: "application-x-executable-symbolic"; - styles["icon-dropshadow"] - } - - Label name { - styles ["title-1"] - wrap: true; - wrap-mode: word_char; - justify: center; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - } - - Label description { - styles ["title-4"] - wrap: true; - wrap-mode: word_char; - justify: center; - margin-start: 6; - margin-end: 6; - } - + ScrolledWindow scrolled_window { + Adw.Clamp { Box { - spacing: 6; - homogeneous: true; - margin-top: 18; + margin-start: 12; + margin-end: 12; margin-bottom: 12; - halign: center; - Button open_app_button { - styles ["suggested-action", "pill"] - can-shrink: true; - label: _("Open"); - } - Button uninstall_button { - styles ["pill"] - can-shrink: true; - label: _("Uninstall"); - } - } + orientation: vertical; + halign: fill; - Box eol_box { - margin-bottom: 12; - styles ["card"] - Label status_label { + Image app_icon { + pixel-size: 100; margin-top: 6; - margin-bottom: 7; + margin-bottom: 18; + icon-name: "application-x-executable-symbolic"; + styles["icon-dropshadow"] + } + + Label name { + styles ["title-1"] + wrap: true; + wrap-mode: word_char; + justify: center; + margin-bottom: 12; margin-start: 6; margin-end: 6; - label: _("This package is End Of Life, and will not recieve any security updates"); - styles ["heading", "error"] - halign: center; - hexpand: true; - wrap: true; - justify: center; } - } - Box information { - orientation: vertical; - Adw.PreferencesGroup actions { + Label description { + styles ["title-4"] + wrap: true; + wrap-mode: word_char; + justify: center; + margin-start: 6; + margin-end: 6; + } + + Box { + spacing: 6; + homogeneous: true; + margin-top: 18; margin-bottom: 12; - Adw.ActionRow data_row { - title: _("User Data"); - styles["property"] - - [suffix] - Button open_data_button { - styles["flat"] - valign: center; - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open User Data"); - } - - [suffix] - Button trash_data_button { - styles["flat"] - valign: center; - icon-name: "user-trash-symbolic"; - tooltip-text: _("Trash User Data"); - } - - [suffix] - Spinner data_spinner { - spinning: true; - } + halign: center; + Button open_app_button { + styles ["suggested-action", "pill"] + can-shrink: true; + label: _("Open"); } - Adw.ExpanderRow version_row { - title: _("Version"); - styles ["property"] - [suffix] - Label mask_label { - label: _("Updates Disabled"); - styles["warning"] + Button uninstall_button { + styles ["pill"] + can-shrink: true; + label: _("Uninstall"); + } + } + + Box eol_box { + margin-bottom: 12; + styles ["card"] + Label status_label { + margin-top: 6; + margin-bottom: 7; + margin-start: 6; + margin-end: 6; + label: _("This package is End Of Life, and will not recieve any security updates"); + styles ["heading", "error"] + halign: center; + hexpand: true; + wrap: true; + justify: center; + } + } + + Box information { + orientation: vertical; + Adw.PreferencesGroup actions { + margin-bottom: 12; + Adw.ActionRow data_row { + title: _("User Data"); + styles["property"] + + [suffix] + Button open_data_button { + styles["flat"] + valign: center; + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data"); + } + + [suffix] + Button trash_data_button { + styles["flat"] + valign: center; + icon-name: "user-trash-symbolic"; + tooltip-text: _("Trash User Data"); + } + + [suffix] + Spinner data_spinner { + spinning: true; + } } - Adw.ActionRow mask_row { - title: _("Disable Updates"); - subtitle: _("Mask this package so it's never updated"); + Adw.ExpanderRow version_row { + title: _("Version"); + styles ["property"] + [suffix] + Label mask_label { + label: _("Updates Disabled"); + styles["warning"] + } + Adw.ActionRow mask_row { + title: _("Disable Updates"); + subtitle: _("Mask this package so it's never updated"); + activatable: true; + Gtk.Switch mask_switch { + valign: center; + can-focus: false; + can-target: false; + } + } + Adw.ActionRow change_version_row { + title: _("Change Version"); + subtitle: _("Upgrade or downgrade this package"); + activatable: true; + Image { + icon-name: "right-large-symbolic"; + } + } + } + Adw.ActionRow installed_size_row { + styles ["property"] + title: _("Installed Size"); activatable: true; - Gtk.Switch mask_switch { + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow runtime_row { + styles ["property"] + title: _("Runtime"); + activatable: true; + Image eol_package_package_status_icon { + icon-name: "error-symbolic"; + tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + margin-end: 6; + styles["error"] + } + Image { + icon-name: "right-large-symbolic"; + } + } + Adw.ActionRow pin_row { + title: _("Disable Automactic Removal"); + subtitle: _("Pin this runtime to keep it installed"); + activatable: true; + Gtk.Switch pin_switch { valign: center; can-focus: false; can-target: false; } } - Adw.ActionRow change_version_row { - title: _("Change Version"); - subtitle: _("Upgrade or downgrade this package"); + } + Adw.PreferencesGroup package_info { + margin-bottom: 12; + title: _("Package Information"); + Adw.ActionRow id_row { + styles ["property"] + title: _("Application ID"); activatable: true; Image { - icon-name: "right-large-symbolic"; + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow ref_row { + styles ["property"] + title: "Ref"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow arch_row { + styles ["property"] + title: _("Architecture"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow branch_row { + styles ["property"] + title: _("Branch"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow license_row { + styles ["property"] + title: _("License"); + activatable: true; + Image { + icon-name: "copy-symbolic"; } } } - Adw.ActionRow installed_size_row { - styles ["property"] - title: _("Installed Size"); - activatable: true; - Image { - icon-name: "copy-symbolic"; + Adw.PreferencesGroup remote_info { + margin-bottom: 12; + title: _("Installation Information"); + Adw.ActionRow sdk_row { + styles ["property"] + title: "SDK"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow origin_row { + styles ["property"] + title: _("Origin"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow collection_row { + styles ["property"] + title: _("Collection"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow installation_row { + styles ["property"] + title: _("Installation"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } } - Adw.ActionRow runtime_row { - styles ["property"] - title: _("Runtime"); - activatable: true; - Image eol_package_package_status_icon { - icon-name: "error-symbolic"; - tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); - margin-end: 6; - styles["error"] + Adw.PreferencesGroup commit_info { + title: _("Commit Information"); + Adw.ActionRow commit_row { + styles ["property"] + title: "Commit"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } - Image { - icon-name: "right-large-symbolic"; + Adw.ActionRow parent_row { + styles ["property"] + title: _("Parent"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } - } - Adw.ActionRow pin_row { - title: _("Disable Automactic Removal"); - subtitle: _("Pin this runtime to keep it installed"); - activatable: true; - Gtk.Switch pin_switch { - valign: center; - can-focus: false; - can-target: false; + Adw.ActionRow subject_row { + styles ["property"] + title: _("Subject"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } - } - } - Adw.PreferencesGroup package_info { - margin-bottom: 12; - title: _("Package Information"); - Adw.ActionRow id_row { - styles ["property"] - title: _("Application ID"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow ref_row { - styles ["property"] - title: "Ref"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow arch_row { - styles ["property"] - title: _("Architecture"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow branch_row { - styles ["property"] - title: _("Branch"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow license_row { - styles ["property"] - title: _("License"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - } - Adw.PreferencesGroup remote_info { - margin-bottom: 12; - title: _("Installation Information"); - Adw.ActionRow sdk_row { - styles ["property"] - title: "SDK"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow origin_row { - styles ["property"] - title: _("Origin"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow collection_row { - styles ["property"] - title: _("Collection"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow installation_row { - styles ["property"] - title: _("Installation"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - } - Adw.PreferencesGroup commit_info { - title: _("Commit Information"); - Adw.ActionRow commit_row { - styles ["property"] - title: "Commit"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow parent_row { - styles ["property"] - title: _("Parent"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow subject_row { - styles ["property"] - title: _("Subject"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow date_row { - styles ["property"] - title: _("Date"); - activatable: true; - Image { - icon-name: "copy-symbolic"; + Adw.ActionRow date_row { + styles ["property"] + title: _("Date"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } } } diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 9d1c400..859e243 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -8,6 +8,10 @@ import subprocess, os class PropertiesPage(Adw.NavigationPage): __gtype_name__ = 'PropertiesPage' gtc = Gtk.Template.Child + stack = gtc() + error_tbv = gtc() + loading_tbv = gtc() + nav_view = gtc() inner_nav_page = gtc() toast_overlay = gtc() @@ -203,7 +207,7 @@ class PropertiesPage(Adw.NavigationPage): def on_choice(_, response): if response != 'continue': return - self.packages_page.set_status(self.packages_page.uninstall_status) + self.packages_page.set_status(self.packages_page.uninstalling) self.package.uninstall(callback) def callback(*args): From 5bd0e54fcf8568557ff3742f84a5a68c45927bca Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 13 Jul 2024 01:01:46 -0400 Subject: [PATCH 051/332] add status page for no search results --- src/packages_page/packages_page.blp | 5 +++++ src/packages_page/packages_page.py | 27 ++++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index a87fdd9..87d0856 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -93,6 +93,11 @@ template $PackagesPage : Adw.BreakpointBin { description: _("Warehouse cannot see the list of installed packages or your system has no packages installed"); icon-name: "error-symbolic"; } + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; + } } [bottom] Revealer { diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index f586539..c486405 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -18,6 +18,7 @@ class PackagesPage(Adw.BreakpointBin): loading_packages = gtc() no_filter_results = gtc() no_packages = gtc() + no_results = gtc() sidebar_button = gtc() filter_button = gtc() refresh_button = gtc() @@ -61,6 +62,9 @@ class PackagesPage(Adw.BreakpointBin): 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) + self.status_stack.set_visible_child(to_set) def apply_filters(self): @@ -126,6 +130,7 @@ class PackagesPage(Adw.BreakpointBin): title = row.get_title().lower() subtitle = row.get_subtitle().lower() if search_text in title or search_text in subtitle: + self.is_result = True return True def set_selection_mode(self, is_enabled): @@ -154,6 +159,18 @@ class PackagesPage(Adw.BreakpointBin): 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 __init__(self, main_window, **kwargs): super().__init__(**kwargs) HostInfo.get_flatpaks(callback=self.generate_list) @@ -163,6 +180,8 @@ class PackagesPage(Adw.BreakpointBin): self.properties_page = PropertiesPage(main_window, self) self.filters_page = FiltersPage(main_window, self) self.filter_settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") + self.is_result = False + self.prev_status = None # Apply # self.set_status("loading_packages") @@ -171,18 +190,12 @@ class PackagesPage(Adw.BreakpointBin): self.content_stack.add_child(self.filters_page) self.__class__.instance = self - self.loading_packages - self.uninstalling - self.no_filter_results - self.no_packages - self.scrolled_window - # Connections main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) - self.search_entry.connect("search-changed", lambda *_: self.packages_list_box.invalidate_filter()) + 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_select_handler) self.refresh_button.connect("clicked", self.refresh_handler) From 7ca33e5d31fcec2701d6baa388ef93a1a142d57d Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 13 Jul 2024 02:43:33 -0400 Subject: [PATCH 052/332] add reset filters buttons --- src/filters_page/filters_page.blp | 11 ++++++++++ src/filters_page/filters_page.py | 31 ++++++++++++++++++++++++++++- src/packages_page/packages_page.blp | 7 +++++++ src/packages_page/packages_page.py | 2 ++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp index 6eb6119..f1be4f4 100644 --- a/src/filters_page/filters_page.blp +++ b/src/filters_page/filters_page.blp @@ -105,5 +105,16 @@ template $FiltersPage : Adw.NavigationPage { } } } + [bottom] + ActionBar action_bar { + [center] + Button reset_button { + sensitive: bind action_bar.revealed; + margin-top: 3; + margin-bottom: 3; + label: _("Reset Filters"); + styles ["pill"] + } + } } } \ No newline at end of file diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index 8d25b5e..5d156df 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -22,11 +22,34 @@ class FiltersPage(Adw.NavigationPage): all_remotes_switch = gtc() runtimes_group = gtc() all_runtimes_switch = gtc() + action_bar = gtc() + reset_button = gtc() remote_rows = [] runtime_rows = [] + def reset_filters(self): + self.settings.reset("show-apps") + self.settings.reset("show-runtimes") + self.settings.reset("remotes-list") + self.settings.reset("runtimes-list") + self.generate_filters() + self.packages_page.apply_filters() + + def is_defaulted(self): + default = True + if not self.app_check.get_active(): + default = False + if self.runtime_check.get_active(): + default = False + if self.all_remotes_switch.get_active(): + default = False + if self.all_runtimes_switch.get_active(): + default = False + self.action_bar.set_revealed(not default) + def update_gsettings(self): + self.is_defaulted() if not self.is_settings_settable: return self.settings.set_boolean("show-apps", self.show_apps) @@ -134,6 +157,10 @@ class FiltersPage(Adw.NavigationPage): def generate_filters(self): self.is_settings_settable = False + self.show_apps = self.settings.get_boolean("show-apps") + self.show_runtimes = self.settings.get_boolean("show-runtimes") + self.remotes_string = self.settings.get_string("remotes-list") + self.runtimes_string = self.settings.get_string("runtimes-list") self.app_check.set_active(self.show_apps) self.runtime_check.set_active(self.show_runtimes) @@ -150,6 +177,7 @@ class FiltersPage(Adw.NavigationPage): self.packages_page = packages_page self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") self.is_settings_settable = False + self.show_apps = self.settings.get_boolean("show-apps") self.show_runtimes = self.settings.get_boolean("show-runtimes") self.remotes_string = self.settings.get_string("remotes-list") @@ -165,4 +193,5 @@ class FiltersPage(Adw.NavigationPage): self.app_check.connect("toggled", self.app_check_handler) self.runtime_check.connect("toggled", self.runtime_check_handler) self.all_remotes_switch.connect("state-set", self.all_remotes_handler) - self.all_runtimes_switch.connect("state-set", self.all_runtimes_handler) \ No newline at end of file + self.all_runtimes_switch.connect("state-set", self.all_runtimes_handler) + self.reset_button.connect("clicked", lambda *_: self.reset_filters()) \ No newline at end of file diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 87d0856..8dfa48a 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -12,6 +12,7 @@ template $PackagesPage : Adw.BreakpointBin { packages_split.collapsed: true; packages_split.show-content: false; content_stack.transition-duration: 9999999; + reset_filters_button.visible: true; } } @@ -87,6 +88,12 @@ template $PackagesPage : Adw.BreakpointBin { title: _("No Packages Match Filters"); description: _("No installed package matches all of the currently applied filters"); icon-name: "funnel-symbolic"; + Button reset_filters_button { + label: _("Reset Filters"); + halign: center; + visible: false; + styles ["pill"] + } } Adw.StatusPage no_packages { title: _("No Packages Found"); diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index c486405..7ea6519 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -17,6 +17,7 @@ class PackagesPage(Adw.BreakpointBin): uninstalling = gtc() loading_packages = gtc() no_filter_results = gtc() + reset_filters_button = gtc() no_packages = gtc() no_results = gtc() sidebar_button = gtc() @@ -201,5 +202,6 @@ class PackagesPage(Adw.BreakpointBin): self.refresh_button.connect("clicked", self.refresh_handler) self.select_button.connect("clicked", self.select_button_handler) self.filter_button.connect("toggled", self.filter_button_handler) + self.reset_filters_button.connect("clicked", lambda *_: self.filters_page.reset_filters()) self.packages_split.connect("notify::show-content", self.filter_page_handler) self.packages_bpt.connect("apply", self.filter_page_handler) \ No newline at end of file From bd8b55d5708597c96e8d0c2d2bf99edde23309b4 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 13 Jul 2024 16:04:11 -0400 Subject: [PATCH 053/332] Fix subpages in the properties page from having indefinite loading status --- src/properties_page/properties_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 859e243..405eb84 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -141,8 +141,8 @@ class PropertiesPage(Adw.NavigationPage): self.mask_label.set_visible(package.is_masked) self.mask_switch.set_active(package.is_masked) - self.pin_switch.set_active(package.is_pinned) + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.nav_view)) def open_data_handler(self, *args): if error := self.package.open_data(): From fc222d124daf8699c312b750553f9976363cd0b5 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 13 Jul 2024 23:19:51 -0400 Subject: [PATCH 054/332] Select All now works, and selection is handled properly --- src/packages_page/packages_page.blp | 6 ++--- src/packages_page/packages_page.py | 36 ++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 8dfa48a..ced0f65 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -22,7 +22,7 @@ template $PackagesPage : Adw.BreakpointBin { max-sidebar-width: 999999999; sidebar: Adw.NavigationPage packages_navpage { - title: "Packages"; + title: _("Packages"); Adw.ToolbarView packages_tbv { [top] Adw.HeaderBar { @@ -115,7 +115,7 @@ template $PackagesPage : Adw.BreakpointBin { styles ["toolbar"] hexpand: true; homogeneous: true; - Button { + Button select_all_button { styles ["raised"] Adw.ButtonContent { icon-name: "selection-mode-symbolic"; @@ -132,7 +132,7 @@ template $PackagesPage : Adw.BreakpointBin { } popover: copy_pop; } - Button { + Button trash_button { styles ["raised"] Adw.ButtonContent { icon-name: "user-trash-symbolic"; diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 7ea6519..e6ae0b2 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -29,6 +29,8 @@ class PackagesPage(Adw.BreakpointBin): packages_list_box = gtc() select_button = gtc() packages_navpage = gtc() + select_all_button = gtc() + trash_button = gtc() content_stack = gtc() # Referred to in the main window @@ -86,14 +88,37 @@ 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 + GLib.idle_add(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)) + def row_select_handler(self, row): + if row.check_button.get_active(): + self.selected_rows.append(row) + else: + self.selected_rows.remove(row) + + if (total := len(self.selected_rows)) > 0: + self.packages_navpage.set_title(_("{} Selected").format(total)) + else: + self.packages_navpage.set_title(_("Packages")) + + print(self.selected_rows) + + 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 generate_list(self, *args): self.packages_list_box.remove_all() GLib.idle_add(lambda *_: self.filters_page.generate_filters()) @@ -107,6 +132,7 @@ class PackagesPage(Adw.BreakpointBin): row.pinned_status_icon.set_visible(package.is_pinned) row.eol_package_package_status_icon.set_visible(package.is_eol) row.check_button.set_visible(self.select_button.get_active()) + row.check_button.connect("toggled", lambda *_, row=row: self.row_select_handler(row)) try: if not package.is_runtime: row.eol_runtime_status_icon.set_visible(package.dependant_runtime.is_eol) @@ -120,7 +146,7 @@ class PackagesPage(Adw.BreakpointBin): self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top self.apply_filters() - def row_select_handler(self, list_box, row): + 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) @@ -142,6 +168,8 @@ class PackagesPage(Adw.BreakpointBin): GLib.idle_add(row.check_button.set_visible, is_enabled) def refresh_handler(self, *args): + self.packages_navpage.set_title(_("Packages")) + self.selected_rows.clear() self.select_button.set_active(False) self.set_status(self.loading_packages) HostInfo.get_flatpaks(callback=self.generate_list) @@ -183,6 +211,7 @@ class PackagesPage(Adw.BreakpointBin): self.filter_settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") self.is_result = False self.prev_status = None + self.selected_rows = [] # Apply # self.set_status("loading_packages") @@ -198,10 +227,11 @@ class PackagesPage(Adw.BreakpointBin): 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_select_handler) + self.packages_list_box.connect("row-activated", self.row_activate_handler) self.refresh_button.connect("clicked", self.refresh_handler) self.select_button.connect("clicked", self.select_button_handler) self.filter_button.connect("toggled", self.filter_button_handler) self.reset_filters_button.connect("clicked", lambda *_: self.filters_page.reset_filters()) self.packages_split.connect("notify::show-content", self.filter_page_handler) - self.packages_bpt.connect("apply", self.filter_page_handler) \ No newline at end of file + self.packages_bpt.connect("apply", self.filter_page_handler) + self.select_all_button.connect("clicked", self.select_all_handler) \ No newline at end of file From e0a37f9726d532ae915213688650a1b02287708e Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 13 Jul 2024 23:57:35 -0400 Subject: [PATCH 055/332] add selection mode copy actions --- src/packages_page/packages_page.blp | 3 --- src/packages_page/packages_page.py | 32 ++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index ced0f65..3a2118c 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -169,8 +169,5 @@ Popover copy_pop { Label copy_refs { label: _("Copy Refs"); } - Label copy_data { - label: _("Copy Data Paths"); - } } } \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index e6ae0b2..711ff92 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -32,6 +32,10 @@ class PackagesPage(Adw.BreakpointBin): select_all_button = gtc() trash_button = gtc() content_stack = gtc() + copy_menu = gtc() + copy_names = gtc() + copy_ids = gtc() + copy_refs = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -167,6 +171,30 @@ class PackagesPage(Adw.BreakpointBin): 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): + info = "" + feedback = "" + match row.get_child(): + case self.copy_names: + info = "name" + feedback = _("Names") + case self.copy_ids: + info = "id" + feedback = _("IDs") + case self.copy_refs: + info = "ref" + feedback = _("Refs") + + to_copy = [] + for row in self.selected_rows: + to_copy.append(row.package.info[info]) + to_copy += ['\n'] + try: + HostInfo.clipboard.set("".join(to_copy[:-1])) + 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 refresh_handler(self, *args): self.packages_navpage.set_title(_("Packages")) self.selected_rows.clear() @@ -234,4 +262,6 @@ class PackagesPage(Adw.BreakpointBin): self.reset_filters_button.connect("clicked", lambda *_: self.filters_page.reset_filters()) self.packages_split.connect("notify::show-content", self.filter_page_handler) self.packages_bpt.connect("apply", self.filter_page_handler) - self.select_all_button.connect("clicked", self.select_all_handler) \ No newline at end of file + self.select_all_button.connect("clicked", self.select_all_handler) + + self.copy_menu.connect("row-activated", self.selection_copy) \ No newline at end of file From 4f1bf3e31c7c547b1937e87825621709cd42b510 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 14 Jul 2024 00:55:08 -0400 Subject: [PATCH 056/332] Made selection uninstalls work --- src/host_info.py | 2 +- src/packages_page/packages_page.blp | 2 +- src/packages_page/packages_page.py | 45 +++++++++++++++++++++++--- src/properties_page/properties_page.py | 4 +-- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index c605585..e3226f3 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -110,7 +110,7 @@ class Flatpak: cmd.append(f"--installation={installation}") try: - subprocess.run(prefix + cmd, check=True, text=True) + subprocess.run(prefix + cmd, check=True, text=True, capture_output=True) # print(prefix + cmd) except subprocess.CalledProcessError as cpe: if installation == "user": diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 3a2118c..81ad43e 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -132,7 +132,7 @@ template $PackagesPage : Adw.BreakpointBin { } popover: copy_pop; } - Button trash_button { + Button uninstall_button { styles ["raised"] Adw.ButtonContent { icon-name: "user-trash-symbolic"; diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 711ff92..b4f9e48 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -5,6 +5,7 @@ from .error_toast import ErrorToast from .properties_page import PropertiesPage from .status_box import StatusBox from .filters_page import FiltersPage +import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") class PackagesPage(Adw.BreakpointBin): @@ -30,12 +31,12 @@ class PackagesPage(Adw.BreakpointBin): select_button = gtc() packages_navpage = gtc() select_all_button = gtc() - trash_button = gtc() content_stack = gtc() copy_menu = gtc() copy_names = gtc() copy_ids = gtc() copy_refs = gtc() + uninstall_button = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -56,6 +57,7 @@ class PackagesPage(Adw.BreakpointBin): self.properties_page.stack.set_visible_child(self.properties_page.loading_tbv) self.filter_button.set_sensitive(False) self.filters_page.set_sensitive(False) + self.select_button.set_active(False) if to_set is self.no_packages: self.properties_page.stack.set_visible_child(self.properties_page.error_tbv) @@ -115,8 +117,6 @@ class PackagesPage(Adw.BreakpointBin): else: self.packages_navpage.set_title(_("Packages")) - print(self.selected_rows) - def select_all_handler(self, *args): i = 0 while row := self.packages_list_box.get_row_at_index(i): @@ -195,6 +195,42 @@ class PackagesPage(Adw.BreakpointBin): except Exception as e: self.packages_toast_overlay.add_toast(ErrorToast(_("Could not copy {}").format(feedback), str(e)).toast) + def selection_uninstall(self, *args): + def on_response(alert_dialog, response): + if response != "continue": + return + + GLib.idle_add(lambda *_: self.set_status(self.uninstalling)) + error = [None] + def thread(*args): + try: + subprocess.run(cmd, check=True, capture_output=True) + except subprocess.CalledProcessError as cpe: + error[0] = cpe + except Exception as e: + error[0] = e + + def callback(*args): + if err := error[0]: + details = err.stderr if type(err) == subprocess.CalledProcessError else str(err) + self.packages_toast_overlay.add_toast(ErrorToast(_("Could not uninstall packages"), details).toast) + else: + self.refresh_handler() + GLib.idle_add(lambda *__: self.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled Packages")))) + + Gio.Task.new(None, None, callback).run_in_thread(thread) + + cmd = ['flatpak-spawn', '--host', 'flatpak', 'uninstall', '-y'] + for row in self.selected_rows: + cmd.append(row.package.info["ref"]) + + dialog = Adw.AlertDialog(heading=_("Uninstall Packages?"), body=_("It will not be possible to use these packages after removal")) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Uninstall")) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.connect("response", on_response) + dialog.present(self.main_window) + def refresh_handler(self, *args): self.packages_navpage.set_title(_("Packages")) self.selected_rows.clear() @@ -264,4 +300,5 @@ class PackagesPage(Adw.BreakpointBin): self.packages_bpt.connect("apply", self.filter_page_handler) self.select_all_button.connect("clicked", self.select_all_handler) - self.copy_menu.connect("row-activated", self.selection_copy) \ No newline at end of file + self.copy_menu.connect("row-activated", self.selection_copy) + self.uninstall_button.connect("clicked", self.selection_uninstall) \ No newline at end of file diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 405eb84..e58088c 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -221,8 +221,8 @@ class PropertiesPage(Adw.NavigationPage): # name = self.package.info["name"] dialog = Adw.AlertDialog( - heading=_("Uninstall {}?").format(guhhh := self.package.info["name"]), - body=_("It will not be possible to use {} after removal.").format(guhhh), + heading=_("Uninstall {}?").format(name := self.package.info["name"]), + body=_("It will not be possible to use {} after removal.").format(name), ) dialog.add_response('cancel', _("Cancel")) dialog.add_response('continue', _("Uninstall")) From edadf44d992d1b8f30857429d2f972c2af6bf5cf Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 14 Jul 2024 01:13:26 -0400 Subject: [PATCH 057/332] remove selection styles in filters page --- src/filters_page/filters_page.blp | 8 ++------ src/filters_page/filters_page.py | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/filters_page/filters_page.blp b/src/filters_page/filters_page.blp index f1be4f4..cc87cd8 100644 --- a/src/filters_page/filters_page.blp +++ b/src/filters_page/filters_page.blp @@ -22,17 +22,13 @@ template $FiltersPage : Adw.NavigationPage { Adw.ActionRow application_row { title: _("Applications"); subtitle: _("Packages that can be opened"); - CheckButton app_check { - styles["selection-mode"] - } + CheckButton app_check {} activatable-widget: app_check; } Adw.ActionRow runtime_row { title: _("Runtimes"); subtitle: _("Packages that applications depend on"); - CheckButton runtime_check { - styles["selection-mode"] - } + CheckButton runtime_check {} activatable-widget: runtime_check; } } diff --git a/src/filters_page/filters_page.py b/src/filters_page/filters_page.py index 5d156df..3983641 100644 --- a/src/filters_page/filters_page.py +++ b/src/filters_page/filters_page.py @@ -8,7 +8,6 @@ class FilterRow(Adw.ActionRow): self.item = item self.installation = installation self.check_button = Gtk.CheckButton() - self.check_button.add_css_class("selection-mode") self.add_suffix(self.check_button) self.set_activatable_widget(self.check_button) From 9f89e5e43be171e66eb7f32b832d7502ba5b0b83 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 14 Jul 2024 01:44:16 -0400 Subject: [PATCH 058/332] made selection actions insensitive when nothing is selected --- src/packages_page/packages_page.blp | 2 +- src/packages_page/packages_page.py | 4 ++++ src/widgets/app_row.blp | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 81ad43e..ff2fef0 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -111,7 +111,7 @@ template $PackagesPage : Adw.BreakpointBin { reveal-child: bind select_button.active; transition-type: slide_up; [center] - Box { + Box bottom_bar { styles ["toolbar"] hexpand: true; homogeneous: true; diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index b4f9e48..779ace2 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -32,6 +32,7 @@ class PackagesPage(Adw.BreakpointBin): packages_navpage = gtc() select_all_button = gtc() content_stack = gtc() + bottom_bar = gtc() copy_menu = gtc() copy_names = gtc() copy_ids = gtc() @@ -114,8 +115,10 @@ class PackagesPage(Adw.BreakpointBin): if (total := len(self.selected_rows)) > 0: self.packages_navpage.set_title(_("{} Selected").format(total)) + self.bottom_bar.set_sensitive(True) else: self.packages_navpage.set_title(_("Packages")) + self.bottom_bar.set_sensitive(False) def select_all_handler(self, *args): i = 0 @@ -126,6 +129,7 @@ class PackagesPage(Adw.BreakpointBin): def generate_list(self, *args): self.packages_list_box.remove_all() GLib.idle_add(lambda *_: self.filters_page.generate_filters()) + self.bottom_bar.set_sensitive(False) if len(HostInfo.flatpaks) == 0: self.set_status(self.no_packages) return diff --git a/src/widgets/app_row.blp b/src/widgets/app_row.blp index beb9a35..040fc1b 100644 --- a/src/widgets/app_row.blp +++ b/src/widgets/app_row.blp @@ -36,6 +36,7 @@ template $AppRow : Adw.ActionRow { } [suffix] CheckButton check_button { + margin-start: 6; styles["selection-mode"] visible: false; } From 5730a82d7e58c4cbf62a1a76ccd5a2e32568e51e Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 14 Jul 2024 01:46:23 -0400 Subject: [PATCH 059/332] Moved packages toasts to be only over the sidebar --- src/packages_page/packages_page.blp | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index ff2fef0..a7b7f55 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -16,13 +16,13 @@ template $PackagesPage : Adw.BreakpointBin { } } - Adw.ToastOverlay packages_toast_overlay { - Adw.NavigationSplitView packages_split { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage packages_navpage { - title: _("Packages"); + Adw.NavigationSplitView packages_split { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage packages_navpage { + title: _("Packages"); + Adw.ToastOverlay packages_toast_overlay { Adw.ToolbarView packages_tbv { [top] Adw.HeaderBar { @@ -144,16 +144,16 @@ template $PackagesPage : Adw.BreakpointBin { } } } - ; - content: - Adw.NavigationPage { - title: "Content Stack"; - Stack content_stack { - transition-type: slide_left_right; - } + } + ; + content: + Adw.NavigationPage { + title: "Content Stack"; + Stack content_stack { + transition-type: slide_left_right; } - ; - } + } + ; } } From ece3261d8649ae7da080427e71d2521e9e8c158b Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 14 Jul 2024 01:48:47 -0400 Subject: [PATCH 060/332] Made selection copy popover close when an option is clicked --- src/packages_page/packages_page.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 779ace2..589eb36 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -33,6 +33,7 @@ class PackagesPage(Adw.BreakpointBin): select_all_button = gtc() content_stack = gtc() bottom_bar = gtc() + copy_pop = gtc() copy_menu = gtc() copy_names = gtc() copy_ids = gtc() @@ -176,6 +177,7 @@ class PackagesPage(Adw.BreakpointBin): GLib.idle_add(row.check_button.set_visible, is_enabled) def selection_copy(self, box, row): + self.copy_pop.popdown() info = "" feedback = "" match row.get_child(): From 1cb6520a7773b327c1d10a1a66b4b282694f839f Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 14 Jul 2024 01:53:41 -0400 Subject: [PATCH 061/332] Made select all button always sensitive --- src/packages_page/packages_page.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 589eb36..375dc1b 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -32,7 +32,7 @@ class PackagesPage(Adw.BreakpointBin): packages_navpage = gtc() select_all_button = gtc() content_stack = gtc() - bottom_bar = gtc() + copy_button = gtc() copy_pop = gtc() copy_menu = gtc() copy_names = gtc() @@ -116,10 +116,12 @@ class PackagesPage(Adw.BreakpointBin): if (total := len(self.selected_rows)) > 0: self.packages_navpage.set_title(_("{} Selected").format(total)) - self.bottom_bar.set_sensitive(True) + self.copy_button.set_sensitive(True) + self.uninstall_button.set_sensitive(True) else: self.packages_navpage.set_title(_("Packages")) - self.bottom_bar.set_sensitive(False) + self.copy_button.set_sensitive(False) + self.uninstall_button.set_sensitive(False) def select_all_handler(self, *args): i = 0 @@ -130,7 +132,8 @@ class PackagesPage(Adw.BreakpointBin): def generate_list(self, *args): self.packages_list_box.remove_all() GLib.idle_add(lambda *_: self.filters_page.generate_filters()) - self.bottom_bar.set_sensitive(False) + self.copy_button.set_sensitive(False) + self.uninstall_button.set_sensitive(False) if len(HostInfo.flatpaks) == 0: self.set_status(self.no_packages) return From ddba312b69da167fbdca8180d86b1f23ae880456 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 14 Jul 2024 13:15:47 -0400 Subject: [PATCH 062/332] downgrade window shows currently installed version --- src/change_version_page/change_version_page.blp | 3 ++- src/change_version_page/change_version_page.py | 15 ++++++++++----- src/host_info.py | 2 ++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/change_version_page/change_version_page.blp b/src/change_version_page/change_version_page.blp index ec9b627..27f1782 100644 --- a/src/change_version_page/change_version_page.blp +++ b/src/change_version_page/change_version_page.blp @@ -48,12 +48,13 @@ Adw.Clamp versions_clamp { Adw.PreferencesGroup mask_group { Adw.SwitchRow mask_row { title: _("Disable Updates"); + active: true; } } Adw.PreferencesGroup versions_group { title: _("Select a Release"); - description: _("This will uninstalls the current release and install the chosen one instead. Note that downgrading can cause issues."); + description: _("This will uninstall the current release and install the chosen one instead. Note that downgrading can cause issues."); } } } \ No newline at end of file diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py index 76200d4..0b26688 100644 --- a/src/change_version_page/change_version_page.py +++ b/src/change_version_page/change_version_page.py @@ -62,11 +62,16 @@ class ChangeVersionPage(Adw.NavigationPage): def idle(*args): for index, commit in enumerate(commits): row = Adw.ActionRow(title=GLib.markup_escape_text(changes[index]), subtitle=f"{GLib.markup_escape_text(commit)}\n{GLib.markup_escape_text(dates[index])}") - check = Gtk.CheckButton() - check.connect("activate", lambda *_, comm=commit: self.set_commit(comm)) - check.set_group(self.root_group_check_button) - row.set_activatable_widget(check) - row.add_prefix(check) + if commit == self.package.cli_info["commit"]: + row.set_sensitive(False) + row.add_prefix(Gtk.Image(icon_name="check-plain-symbolic", margin_start=5, margin_end=5)) + row.set_tooltip_text(_("Currently Installed Version")) + else: + check = Gtk.CheckButton() + check.connect("activate", lambda *_, comm=commit: self.set_commit(comm)) + check.set_group(self.root_group_check_button) + row.set_activatable_widget(check) + row.add_prefix(check) self.versions_group.add(row) GLib.idle_add(idle) diff --git a/src/host_info.py b/src/host_info.py index e3226f3..1db56d1 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -159,6 +159,7 @@ class Flatpak: word[1] = word[1].replace("?", " ") cli_info[word[0]] = word[1] + self.cli_info = cli_info return cli_info def __init__(self, columns): @@ -177,6 +178,7 @@ class Flatpak: } self.data_path = f"{home}/.var/app/{columns[2]}" self.data_size = -1 + self.cli_info = None installation = columns[7] if len(i := installation.split(' ')) > 1: self.info["installation"] = i[1].replace("(", "").replace(")", "") From 3a6ce441f87bfbb3a8bfdea0ea4e544fc5b63027 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 15 Jul 2024 00:18:49 -0400 Subject: [PATCH 063/332] Add user data page --- data/icons/dot-symbolic.svg | 2 + data/icons/folder-templates-symbolic.svg | 4 + data/icons/vertical-arrows-long-symbolic.svg | 2 + src/main_window/window.py | 3 + src/meson.build | 4 + src/user_data_page/user_data_page.blp | 254 +++++++++++++++++++ src/user_data_page/user_data_page.py | 58 +++++ src/warehouse.gresource.xml | 5 + src/widgets/data_box.blp | 77 ++++++ src/widgets/data_box.py | 13 + 10 files changed, 422 insertions(+) create mode 100644 data/icons/dot-symbolic.svg create mode 100644 data/icons/folder-templates-symbolic.svg create mode 100644 data/icons/vertical-arrows-long-symbolic.svg create mode 100644 src/user_data_page/user_data_page.blp create mode 100644 src/user_data_page/user_data_page.py create mode 100644 src/widgets/data_box.blp create mode 100644 src/widgets/data_box.py diff --git a/data/icons/dot-symbolic.svg b/data/icons/dot-symbolic.svg new file mode 100644 index 0000000..c94c8e1 --- /dev/null +++ b/data/icons/dot-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/folder-templates-symbolic.svg b/data/icons/folder-templates-symbolic.svg new file mode 100644 index 0000000..4e5faac --- /dev/null +++ b/data/icons/folder-templates-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/data/icons/vertical-arrows-long-symbolic.svg b/data/icons/vertical-arrows-long-symbolic.svg new file mode 100644 index 0000000..ac7a6ee --- /dev/null +++ b/data/icons/vertical-arrows-long-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/main_window/window.py b/src/main_window/window.py index 023e863..8595323 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -24,6 +24,7 @@ import time from gi.repository import Adw, Gdk, Gio, GLib, Gtk from .packages_page import PackagesPage +from .user_data_page import UserDataPage from .const import Config from .error_toast import ErrorToast from .status_box import StatusBox @@ -74,6 +75,8 @@ class WarehouseWindow(Adw.ApplicationWindow): file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) self.pages = { self.packages_row: PackagesPage, + + self.user_data_row: UserDataPage, } # Apply diff --git a/src/meson.build b/src/meson.build index 9c2023e..3f25607 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,6 +11,8 @@ blueprints = custom_target('blueprints', 'packages_page/packages_page.blp', 'filters_page/filters_page.blp', 'properties_page/properties_page.blp', + 'user_data_page/user_data_page.blp', + 'widgets/data_box.blp', 'change_version_page/change_version_page.blp', ), output: '.', @@ -62,6 +64,8 @@ warehouse_sources = [ 'filters_page/filters_page.py', 'properties_page/properties_page.py', 'change_version_page/change_version_page.py', + 'user_data_page/user_data_page.py', + 'widgets/data_box.py', '../data/style.css', ] diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp new file mode 100644 index 0000000..1bbd1cd --- /dev/null +++ b/src/user_data_page/user_data_page.blp @@ -0,0 +1,254 @@ +using Gtk 4.0; +using Adw 1; + +template $UserDataPage : Adw.BreakpointBin { + // title: _("User Data"); + width-request: 1; + height-request: 1; + + Adw.Breakpoint { + condition ("max-width: 585") + + setters { + header_bar.show-title: false; + switcher_bar.reveal: true; + switcher_bar.visible: true; + } + } + + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar { + title-widget: + Adw.ViewSwitcher { + stack: stack; + policy: wide; + } + ; + [start] + ToggleButton search_button { + icon-name: "system-search-symbolic"; + tooltip-text: _("Search User Data"); + } + [end] + MenuButton sort_button { + popover: sort_pop; + icon-name: "vertical-arrows-long-symbolic"; + tooltip-text: _("Sort User Data"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select User Data"); + } + } + [top] + Adw.Clamp { + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + key-capture-widget: template; + + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search User Data"); + } + } + } + [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 { + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; + } + } + Button uninstall_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "user-trash-symbolic"; + label: _("Uninstall"); + can-shrink: true; + } + } + } + } + [bottom] + Adw.ViewSwitcherBar switcher_bar { + stack: stack; + visible: false; + } + Adw.ViewStack stack { + Adw.ViewStackPage { + name: "active"; + title: _("Active Data"); + icon-name: "file-manager-symbolic"; + child: + ScrolledWindow { + Box { + orientation: vertical; + Box { + orientation: vertical; + margin-start: 24; + margin-end: 24; + Label { + label: _("Active User Data"); + styles ["title-1"] + hexpand: true; + halign: start; + } + Label { + label: "32 Items - 39.7 GB"; + styles ["title-3"] + hexpand: true; + halign: start; + } + } + Separator { + margin-start: 12; + margin-end: 12; + margin-top: 9; + margin-bottom: 6; + } + FlowBox flow_box { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } + } + ; + } + Adw.ViewStackPage { + name: "leftover"; + title: _("Leftover Data"); + icon-name: "folder-templates-symbolic"; + child: + ScrolledWindow { + Box { + orientation: vertical; + Box { + orientation: vertical; + margin-start: 24; + margin-end: 24; + Label { + label: _("Leftover User Data"); + styles ["title-1"] + hexpand: true; + halign: start; + } + Label { + label: "25 Items - 18.6 GB"; + styles ["title-3"] + hexpand: true; + halign: start; + } + } + Separator { + margin-start: 12; + margin-end: 12; + margin-top: 9; + margin-bottom: 6; + } + FlowBox { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } + } + ; + } + } + } +} + +Popover sort_pop { + styles ["menu"] + Box { + orientation: vertical; + margin-start: 6; + margin-end: 6; + margin-top: 6; + margin-bottom: 6; + Box { + homogeneous: true; + spacing: 3; + ToggleButton asc { + styles ["flat"] + label: _("Ascending"); + can-focus: bind asc.active inverted; + can-target: bind asc.active inverted; + } + ToggleButton dsc { + styles ["flat"] + label: _("Descending"); + active: bind asc.active inverted bidirectional; + can-focus: bind dsc.active inverted; + can-target: bind dsc.active inverted; + } + } + Separator { + } + Box sort_list { + orientation: vertical; + ToggleButton sort_name { + styles ["flat"] + can-focus: bind sort_name.active inverted; + can-target: bind sort_name.active inverted; + Label { + styles ["body"] + halign: start; + label: _("Sort By Name"); + } + } + ToggleButton sort_id { + styles ["flat"] + can-focus: bind sort_id.active inverted; + can-target: bind sort_id.active inverted; + Label { + styles ["body"] + halign: start; + label: _("Sort By ID"); + } + } + ToggleButton sort_size { + active: true; + can-focus: bind sort_size.active inverted; + can-target: bind sort_size.active inverted; + styles ["flat"] + Label { + styles ["body"] + halign: start; + label: _("Sort By Size"); + } + } + } + } +} \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py new file mode 100644 index 0000000..f5ad5a8 --- /dev/null +++ b/src/user_data_page/user_data_page.py @@ -0,0 +1,58 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo +from .error_toast import ErrorToast +from .data_box import DataBox + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/user_data_page.ui") +class UserDataPage(Adw.BreakpointBin): + __gtype_name__ = 'UserDataPage' + gtc = Gtk.Template.Child + select_button = gtc() + flow_box = gtc() + sort_pop = gtc() + sort_list = gtc() + sort_name = gtc() + sort_id = gtc() + sort_size = 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 + + def set_selection_mode(self, is_enabled): + i = 0 + while row := self.flow_box.get_child_at_index(i): + i += 1 + row = row.get_child() + row.check_button.set_visible(is_enabled) + row.check_button.set_active(False) + + def sort_handler(self, button): + if button.get_active() == False: + return + self.sort_name.set_active(self.sort_name is button) + self.sort_id.set_active(self.sort_id is button) + self.sort_size.set_active(self.sort_size is button) + if button is self.sort_name: + self.sort_id.grab_focus() + else: + self.sort_name.grab_focus() + + def __init__(self, main_window, **kwargs): + super().__init__(**kwargs) + + # Apply + self.__class__.instance = self + + for i in range(10): + box = DataBox(main_window, "/home/heliguy/.var/app/io.github.io.github.flattool.Warehouse/") + self.flow_box.append(box) + self.flow_box.get_child_at_index(i).set_focusable(False) + + # self.select_button.connect("toggled", lambda *_: self.set_selection_mode(self.select_button.get_active())) + # self.flow_box.connect("child-activated", lambda _, item: (cb := (row := item.get_child()).check_button).set_active((not cb.get_active()) if row.get_activatable() else False)) + + self.sort_name.connect("toggled", self.sort_handler) + self.sort_id.connect("toggled", self.sort_handler) + self.sort_size.connect("toggled", self.sort_handler) \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 52f9312..5b40ffa 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -9,7 +9,9 @@ filters_page/filters_page.ui properties_page/properties_page.ui change_version_page/change_version_page.ui + user_data_page/user_data_page.ui widgets/status_box.ui + widgets/data_box.ui @@ -49,5 +51,8 @@ ../data/icons/error-small-symbolic.svg ../data/icons/copy-symbolic.svg ../data/icons/double-ended-arrows-vertical-symbolic.svg + ../data/icons/vertical-arrows-long-symbolic.svg + ../data/icons/dot-symbolic.svg + ../data/icons/folder-templates-symbolic.svg diff --git a/src/widgets/data_box.blp b/src/widgets/data_box.blp new file mode 100644 index 0000000..695d814 --- /dev/null +++ b/src/widgets/data_box.blp @@ -0,0 +1,77 @@ +using Gtk 4.0; +using Adw 1; + +template $DataBox : ListBox { + selection-mode: none; + styles ["boxed-list"] + Adw.ActionRow row { + activatable: bind check_button.visible; + width-request: 275; + [child] + Box root_box { + orientation: vertical; + Box title_box { + margin-top: 12; + margin-bottom: 12; + Image app_icon { + margin-start: 12; + margin-end: 12; + icon-name: "application-x-executable-symbolic"; + icon-size: large; + } + Box label_box { + orientation: vertical; + Label title_label { + label: "No Title Set"; + hexpand: true; + halign: start; + ellipsize: middle; + margin-end: 12; + styles ["title-4"] + } + Label subtitle_label { + label: "No subtitle set"; + hexpand: true; + halign: start; + ellipsize: middle; + margin-end: 12; + } + } + } + Box content_box { + spacing: 6; + margin-start: 12; + margin-end: 6; + margin-bottom: 6; + Label size_label { + label: "No size set"; + halign: start; + hexpand: true; + } + Button copy_button { + icon-name: "copy-symbolic"; + tooltip-text: _("Copy Path"); + visible: bind check_button.visible inverted; + styles ["flat", "circular"] + } + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data"); + visible: bind check_button.visible inverted; + styles ["flat", "circular"] + } + Button trash_button { + icon-name: "user-trash-symbolic"; + tooltip-text: _("Trash User Data"); + visible: bind check_button.visible inverted; + styles ["flat", "circular"] + } + CheckButton check_button { + visible: false; + sensitive: bind check_button.visible; + styles ["selection-mode"] + } + } + } + } +} \ No newline at end of file diff --git a/src/widgets/data_box.py b/src/widgets/data_box.py new file mode 100644 index 0000000..d67c5c1 --- /dev/null +++ b/src/widgets/data_box.py @@ -0,0 +1,13 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/widgets/data_box.ui") +class DataBox(Gtk.ListBox): + __gtype_name__ = 'DataBox' + gtc = Gtk.Template.Child + + title_label = gtc() + check_button = gtc() + + def __init__(self, main_window, path, **kwargs): + super().__init__(**kwargs) \ No newline at end of file From 7c72a51776084425c9baacb352b09ccf885ebb6d Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 15 Jul 2024 14:55:25 -0400 Subject: [PATCH 064/332] Continue work on data page --- src/user_data_page/user_data_page.blp | 334 +++++++++++++------------- src/user_data_page/user_data_page.py | 31 ++- 2 files changed, 201 insertions(+), 164 deletions(-) diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 1bbd1cd..75c2d55 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -2,188 +2,196 @@ using Gtk 4.0; using Adw 1; template $UserDataPage : Adw.BreakpointBin { - // title: _("User Data"); width-request: 1; height-request: 1; - Adw.Breakpoint { + Adw.Breakpoint bpt { condition ("max-width: 585") setters { - header_bar.show-title: false; + header_bar.title-widget: null; + // header_bar.show-title: false; switcher_bar.reveal: true; switcher_bar.visible: true; } } - Adw.ToolbarView { - [top] - Adw.HeaderBar header_bar { - title-widget: - Adw.ViewSwitcher { - stack: stack; - policy: wide; + Adw.NavigationPage { + title: _("User Data"); + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar { + title-widget: + Adw.ViewSwitcher { + stack: stack; + policy: wide; + } + ; + [start] + Button sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); + } + [start] + ToggleButton search_button { + icon-name: "system-search-symbolic"; + tooltip-text: _("Search User Data"); + } + [end] + MenuButton sort_button { + popover: sort_pop; + icon-name: "vertical-arrows-long-symbolic"; + tooltip-text: _("Sort User Data"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select User Data"); } - ; - [start] - ToggleButton search_button { - icon-name: "system-search-symbolic"; - tooltip-text: _("Search User Data"); } - [end] - MenuButton sort_button { - popover: sort_pop; - icon-name: "vertical-arrows-long-symbolic"; - tooltip-text: _("Sort User Data"); + [top] + Adw.Clamp { + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + key-capture-widget: template; + + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search User Data"); + } + } } - [end] - ToggleButton select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select User Data"); - } - } - [top] - Adw.Clamp { - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: template; - - SearchEntry search_entry { + [bottom] + Revealer { + reveal-child: bind select_button.active; + transition-type: slide_up; + [center] + Box bottom_bar { + styles ["toolbar"] hexpand: true; - placeholder-text: _("Search User Data"); - } - } - } - [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 { - styles ["raised"] - Adw.ButtonContent { - icon-name: "edit-copy-symbolic"; - label: _("Copy"); - can-shrink: true; - } - } - Button uninstall_button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "user-trash-symbolic"; - label: _("Uninstall"); - can-shrink: true; - } - } - } - } - [bottom] - Adw.ViewSwitcherBar switcher_bar { - stack: stack; - visible: false; - } - Adw.ViewStack stack { - Adw.ViewStackPage { - name: "active"; - title: _("Active Data"); - icon-name: "file-manager-symbolic"; - child: - ScrolledWindow { - Box { - orientation: vertical; - Box { - orientation: vertical; - margin-start: 24; - margin-end: 24; - Label { - label: _("Active User Data"); - styles ["title-1"] - hexpand: true; - halign: start; - } - Label { - label: "32 Items - 39.7 GB"; - styles ["title-3"] - hexpand: true; - halign: start; - } - } - Separator { - margin-start: 12; - margin-end: 12; - margin-top: 9; - margin-bottom: 6; - } - FlowBox flow_box { - styles ["boxed-list"] - homogeneous: true; - valign: start; - selection-mode: none; - max-children-per-line: 6; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - } + homogeneous: true; + Button select_all_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "selection-mode-symbolic"; + label: _("Select All"); + can-shrink: true; } } - ; - } - Adw.ViewStackPage { - name: "leftover"; - title: _("Leftover Data"); - icon-name: "folder-templates-symbolic"; - child: - ScrolledWindow { - Box { - orientation: vertical; - Box { - orientation: vertical; - margin-start: 24; - margin-end: 24; - Label { - label: _("Leftover User Data"); - styles ["title-1"] - hexpand: true; - halign: start; - } - Label { - label: "25 Items - 18.6 GB"; - styles ["title-3"] - hexpand: true; - halign: start; - } - } - Separator { - margin-start: 12; - margin-end: 12; - margin-top: 9; - margin-bottom: 6; - } - FlowBox { - styles ["boxed-list"] - homogeneous: true; - valign: start; - selection-mode: none; - max-children-per-line: 6; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - } + Button copy_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; } } - ; + Button uninstall_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "user-trash-symbolic"; + label: _("Uninstall"); + can-shrink: true; + } + } + } + } + [bottom] + Adw.ViewSwitcherBar switcher_bar { + stack: stack; + visible: false; + } + Adw.ViewStack stack { + Adw.ViewStackPage { + name: "active"; + title: _("Active Data"); + icon-name: "file-manager-symbolic"; + child: + ScrolledWindow scrolled_window { + Box { + orientation: vertical; + Box { + orientation: vertical; + margin-start: 24; + margin-end: 24; + Label { + label: _("Active User Data"); + styles ["title-1"] + hexpand: true; + halign: start; + } + Label { + label: "32 Items - 39.7 GB"; + styles ["title-3"] + hexpand: true; + halign: start; + } + } + Separator { + margin-start: 12; + margin-end: 12; + margin-top: 9; + margin-bottom: 6; + } + FlowBox flow_box { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } + } + ; + } + Adw.ViewStackPage { + name: "leftover"; + title: _("Leftover Data"); + icon-name: "folder-templates-symbolic"; + child: + ScrolledWindow { + Box { + orientation: vertical; + Box { + orientation: vertical; + margin-start: 24; + margin-end: 24; + Label { + label: _("Leftover User Data"); + styles ["title-1"] + hexpand: true; + halign: start; + } + Label { + label: "25 Items - 18.6 GB"; + styles ["title-3"] + hexpand: true; + halign: start; + } + } + Separator { + margin-start: 12; + margin-end: 12; + margin-top: 9; + margin-bottom: 6; + } + FlowBox { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } + } + ; + } } } } diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index f5ad5a8..4c2ac94 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -7,7 +7,12 @@ from .data_box import DataBox class UserDataPage(Adw.BreakpointBin): __gtype_name__ = 'UserDataPage' gtc = Gtk.Template.Child + bpt = gtc() + header_bar = gtc() + switcher_bar = gtc() + sidebar_button = gtc() select_button = gtc() + scrolled_window = gtc() flow_box = gtc() sort_pop = gtc() sort_list = gtc() @@ -39,8 +44,22 @@ class UserDataPage(Adw.BreakpointBin): else: self.sort_name.grab_focus() + def bpt_handler(self, _, is_applied): + if is_applied and self.adj.get_value() == 0: + self.header_bar.set_show_title(False) + else: + self.header_bar.set_show_title(True) + + def show_title_handler(self, *args): + if self.adj.get_value() != 0: + self.header_bar.set_show_title(True) + elif self.switcher_bar.get_reveal(): + self.header_bar.set_show_title(False) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) + + self.adj = self.scrolled_window.get_vadjustment() # Apply self.__class__.instance = self @@ -50,9 +69,19 @@ class UserDataPage(Adw.BreakpointBin): self.flow_box.append(box) self.flow_box.get_child_at_index(i).set_focusable(False) + # Connections + main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) + main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) + self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) + self.sidebar_button.set_visible(main_window.main_split.get_collapsed()) + self.adj.connect("value-changed", self.show_title_handler) + + # self.select_button.connect("toggled", lambda *_: self.set_selection_mode(self.select_button.get_active())) # self.flow_box.connect("child-activated", lambda _, item: (cb := (row := item.get_child()).check_button).set_active((not cb.get_active()) if row.get_activatable() else False)) self.sort_name.connect("toggled", self.sort_handler) self.sort_id.connect("toggled", self.sort_handler) - self.sort_size.connect("toggled", self.sort_handler) \ No newline at end of file + self.sort_size.connect("toggled", self.sort_handler) + self.bpt.connect("apply", self.bpt_handler, True) + self.bpt.connect("unapply", self.bpt_handler, False) \ No newline at end of file From 19f503410857644a9400b1308220093d2432ab4a Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 15 Jul 2024 17:06:40 -0400 Subject: [PATCH 065/332] Shift files around --- .../change_version_page.py | 3 +- src/{widgets => gtk}/error_toast.py | 0 src/main_window/window.py | 1 - src/meson.build | 16 ++-- src/{widgets => packages_page}/app_row.blp | 0 src/{widgets => packages_page}/app_row.py | 2 +- .../filters_page.blp | 0 .../filters_page.py | 2 +- src/packages_page/packages_page.py | 1 - src/user_data_page/active_data_page.blp | 6 ++ src/user_data_page/active_data_page.py | 0 src/{widgets => user_data_page}/data_box.blp | 0 src/{widgets => user_data_page}/data_box.py | 2 +- src/user_data_page/leftover_data_page.blp | 6 ++ src/user_data_page/leftover_data_page.py | 0 src/warehouse.gresource.xml | 7 +- src/widgets/status_box.blp | 77 ------------------- src/widgets/status_box.py | 26 ------- 18 files changed, 26 insertions(+), 123 deletions(-) rename src/{widgets => gtk}/error_toast.py (100%) rename src/{widgets => packages_page}/app_row.blp (100%) rename src/{widgets => packages_page}/app_row.py (92%) rename src/{filters_page => packages_page}/filters_page.blp (100%) rename src/{filters_page => packages_page}/filters_page.py (98%) create mode 100644 src/user_data_page/active_data_page.blp create mode 100644 src/user_data_page/active_data_page.py rename src/{widgets => user_data_page}/data_box.blp (100%) rename src/{widgets => user_data_page}/data_box.py (78%) create mode 100644 src/user_data_page/leftover_data_page.blp create mode 100644 src/user_data_page/leftover_data_page.py delete mode 100644 src/widgets/status_box.blp delete mode 100644 src/widgets/status_box.py diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py index 0b26688..40956d6 100644 --- a/src/change_version_page/change_version_page.py +++ b/src/change_version_page/change_version_page.py @@ -1,7 +1,6 @@ from gi.repository import Adw, Gtk,GLib, Gio from .error_toast import ErrorToast from .host_info import HostInfo -from .status_box import StatusBox import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/change_version_page/change_version_page.ui") @@ -104,7 +103,7 @@ class ChangeVersionPage(Adw.NavigationPage): self.set_title(_("{} Versions").format(pkg_name)) self.mask_row.set_subtitle(_("Ensure that {} will never be updated to a newer version").format(pkg_name)) - self.scrolled_window.set_child(StatusBox(_("Fetching Releases"), _("This could take a while"))) + self.scrolled_window.set_child(Adw.StatusPage(title=_("Fetching Releases"), description=_("This could take a while"))) Gio.Task.new(None, None, self.get_commits_callback).run_in_thread(self.get_commits) # for i in range(10): diff --git a/src/widgets/error_toast.py b/src/gtk/error_toast.py similarity index 100% rename from src/widgets/error_toast.py rename to src/gtk/error_toast.py diff --git a/src/main_window/window.py b/src/main_window/window.py index 8595323..32ba56d 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -27,7 +27,6 @@ from .packages_page import PackagesPage from .user_data_page import UserDataPage from .const import Config from .error_toast import ErrorToast -from .status_box import StatusBox @Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui") class WarehouseWindow(Adw.ApplicationWindow): diff --git a/src/meson.build b/src/meson.build index 3f25607..cedac79 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,15 +4,14 @@ gnome = import('gnome') blueprints = custom_target('blueprints', input: files( - 'widgets/app_row.blp', - 'widgets/status_box.blp', + 'packages_page/app_row.blp', 'gtk/help-overlay.blp', 'main_window/window.blp', 'packages_page/packages_page.blp', - 'filters_page/filters_page.blp', + 'packages_page/filters_page.blp', 'properties_page/properties_page.blp', 'user_data_page/user_data_page.blp', - 'widgets/data_box.blp', + 'user_data_page/data_box.blp', 'change_version_page/change_version_page.blp', ), output: '.', @@ -56,16 +55,15 @@ warehouse_sources = [ '__init__.py', 'main.py', 'host_info.py', - 'widgets/app_row.py', - 'widgets/error_toast.py', - 'widgets/status_box.py', + 'packages_page/app_row.py', + 'gtk/error_toast.py', 'main_window/window.py', 'packages_page/packages_page.py', - 'filters_page/filters_page.py', + 'packages_page/filters_page.py', 'properties_page/properties_page.py', 'change_version_page/change_version_page.py', 'user_data_page/user_data_page.py', - 'widgets/data_box.py', + 'user_data_page/data_box.py', '../data/style.css', ] diff --git a/src/widgets/app_row.blp b/src/packages_page/app_row.blp similarity index 100% rename from src/widgets/app_row.blp rename to src/packages_page/app_row.blp diff --git a/src/widgets/app_row.py b/src/packages_page/app_row.py similarity index 92% rename from src/widgets/app_row.py rename to src/packages_page/app_row.py index 0714d0f..f3aa2a7 100644 --- a/src/widgets/app_row.py +++ b/src/packages_page/app_row.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .host_info import HostInfo -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/widgets/app_row.ui") +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/app_row.ui") class AppRow(Adw.ActionRow): __gtype_name__ = 'AppRow' gtc = Gtk.Template.Child diff --git a/src/filters_page/filters_page.blp b/src/packages_page/filters_page.blp similarity index 100% rename from src/filters_page/filters_page.blp rename to src/packages_page/filters_page.blp diff --git a/src/filters_page/filters_page.py b/src/packages_page/filters_page.py similarity index 98% rename from src/filters_page/filters_page.py rename to src/packages_page/filters_page.py index 3983641..aa327f6 100644 --- a/src/filters_page/filters_page.py +++ b/src/packages_page/filters_page.py @@ -11,7 +11,7 @@ class FilterRow(Adw.ActionRow): self.add_suffix(self.check_button) self.set_activatable_widget(self.check_button) -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/filters_page/filters_page.ui") +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/filters_page.ui") class FiltersPage(Adw.NavigationPage): __gtype_name__ = 'FiltersPage' gtc = Gtk.Template.Child diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 375dc1b..57577db 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -3,7 +3,6 @@ from .host_info import HostInfo from .app_row import AppRow from .error_toast import ErrorToast from .properties_page import PropertiesPage -from .status_box import StatusBox from .filters_page import FiltersPage import subprocess diff --git a/src/user_data_page/active_data_page.blp b/src/user_data_page/active_data_page.blp new file mode 100644 index 0000000..26ab267 --- /dev/null +++ b/src/user_data_page/active_data_page.blp @@ -0,0 +1,6 @@ +using Gtk 4.0; +using Adw 1; + +template $ActiveDataPage : Adw.ViewStackPage { + +} \ No newline at end of file diff --git a/src/user_data_page/active_data_page.py b/src/user_data_page/active_data_page.py new file mode 100644 index 0000000..e69de29 diff --git a/src/widgets/data_box.blp b/src/user_data_page/data_box.blp similarity index 100% rename from src/widgets/data_box.blp rename to src/user_data_page/data_box.blp diff --git a/src/widgets/data_box.py b/src/user_data_page/data_box.py similarity index 78% rename from src/widgets/data_box.py rename to src/user_data_page/data_box.py index d67c5c1..6dac6f7 100644 --- a/src/widgets/data_box.py +++ b/src/user_data_page/data_box.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .host_info import HostInfo -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/widgets/data_box.ui") +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/data_box.ui") class DataBox(Gtk.ListBox): __gtype_name__ = 'DataBox' gtc = Gtk.Template.Child diff --git a/src/user_data_page/leftover_data_page.blp b/src/user_data_page/leftover_data_page.blp new file mode 100644 index 0000000..3423ab4 --- /dev/null +++ b/src/user_data_page/leftover_data_page.blp @@ -0,0 +1,6 @@ +using Gtk 4.0; +using Adw 1; + +template $LeftoverDataPage : Adw.ViewStackPage { + +} \ No newline at end of file diff --git a/src/user_data_page/leftover_data_page.py b/src/user_data_page/leftover_data_page.py new file mode 100644 index 0000000..e69de29 diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 5b40ffa..c993dcc 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -3,15 +3,14 @@ ../data/style.css gtk/help-overlay.ui - widgets/app_row.ui + packages_page/app_row.ui main_window/window.ui packages_page/packages_page.ui - filters_page/filters_page.ui + packages_page/filters_page.ui properties_page/properties_page.ui change_version_page/change_version_page.ui user_data_page/user_data_page.ui - widgets/status_box.ui - widgets/data_box.ui + user_data_page/data_box.ui diff --git a/src/widgets/status_box.blp b/src/widgets/status_box.blp deleted file mode 100644 index 0799907..0000000 --- a/src/widgets/status_box.blp +++ /dev/null @@ -1,77 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $StatusBox : Box { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - margin-start: 24; - margin-end: 24; - - Spinner spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label title { - halign: center; - label: "No Title Set"; - wrap: true; - styles ["title-1"] - } - - Label description { - halign: center; - label: "No Description Set"; - wrap: true; - styles["description", "body"] - } - - Adw.Clamp { - Box progress_box { - margin-top: 36; - spacing: 12; - halign: fill; - - Label mirror_label { - valign: center; - label: bind progress_label.label; - styles ["heading"] - opacity: 0.0; - wrap: true; - wrap-mode: char; - } - - ProgressBar progress_bar { - valign: center; - hexpand: true; - fraction: 0.5; - } - - Label progress_label { - valign: center; - label: "-1%"; - styles ["heading"] - wrap: true; - wrap-mode: char; - } - } - } - - Button cancel_button { - margin-top: 12; - halign: center; - styles ["pill"] - Adw.ButtonContent cancel_button_content { - icon-name: "cross-filled-symbolic"; - label: _("Cancel"); - } - } -} diff --git a/src/widgets/status_box.py b/src/widgets/status_box.py deleted file mode 100644 index 5b48599..0000000 --- a/src/widgets/status_box.py +++ /dev/null @@ -1,26 +0,0 @@ -from gi.repository import Adw, Gtk, GLib, Gio, Pango -from .host_info import HostInfo - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/widgets/status_box.ui") -class StatusBox(Gtk.Box): - __gtype_name__ = 'StatusBox' - gtc = Gtk.Template.Child - spinner = gtc() - title = gtc() - description = gtc() - progress_box = gtc() - progress_bar = gtc() - progress_label = gtc() - cancel_button = gtc() - cancel_button_content = gtc() - - def __init__(self, title, description, show_loading_bar=False, on_cancel=None, **kwargs): - super().__init__(**kwargs) - self.title.set_label(title) - self.description.set_label(description) - self.spinner.set_visible(not show_loading_bar) - self.progress_box.set_visible(show_loading_bar) - self.cancel_button.set_visible(on_cancel) - - if on_cancel: - self.cancel_button.connect("clicked", lambda *_: on_cancel()) \ No newline at end of file From 1c53c21447d0db6d3af6ed871980520de7d6e1eb Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 17 Jul 2024 15:08:03 -0400 Subject: [PATCH 066/332] make refresh button refresh all pages --- src/main_window/window.blp | 6 +++--- src/main_window/window.py | 22 ++++++++++++++++++++-- src/packages_page/packages_page.blp | 5 ----- src/packages_page/packages_page.py | 15 ++++++--------- src/user_data_page/user_data_page.py | 12 ++++++++---- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/main_window/window.blp b/src/main_window/window.blp index e95ad4e..0141a56 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -29,9 +29,9 @@ template $WarehouseWindow: Adw.ApplicationWindow { [top] Adw.HeaderBar header_bar { [start] - Button sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Hide Sidebar"); + Button refresh_button { + icon-name: "arrow-circular-top-right-symbolic"; + tooltip-text: _("Refresh List"); } [end] MenuButton main_menu { diff --git a/src/main_window/window.py b/src/main_window/window.py index 32ba56d..765d1bc 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -23,6 +23,7 @@ import re import time from gi.repository import Adw, Gdk, Gio, GLib, Gtk +from .host_info import HostInfo from .packages_page import PackagesPage from .user_data_page import UserDataPage from .const import Config @@ -34,7 +35,7 @@ class WarehouseWindow(Adw.ApplicationWindow): gtc = Gtk.Template.Child main_breakpoint = gtc() main_split = gtc() - sidebar_button = gtc() + refresh_button = gtc() navigation_row_listbox = gtc() packages_row = gtc() remotes_row = gtc() @@ -65,6 +66,20 @@ class WarehouseWindow(Adw.ApplicationWindow): else: self.main_split.set_content(page(main_window=self)) + def start_loading(self, *args): + for _, page in self.pages.items(): + if page.instance: + page.instance.start_loading() + + def end_loading(self, *args): + for _, page in self.pages.items(): + if page.instance: + page.instance.end_loading() + + def refresh_handler(self, *args): + self.start_loading() + HostInfo.get_flatpaks(callback=self.end_loading) + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -94,7 +109,10 @@ class WarehouseWindow(Adw.ApplicationWindow): event_controller.connect("key-pressed", self.key_handler) self.navigation_row_listbox.connect("row-activated", self.navigation_handler) # file_drop.connect("drop", self.drop_callback) - self.sidebar_button.connect("clicked", lambda *_: self.main_split.set_show_sidebar(False)) + self.refresh_button.connect("clicked", self.refresh_handler) self.navigation_row_listbox.get_row_at_index(0).activate() self.main_split.set_show_sidebar(True) + + self.start_loading() + HostInfo.get_flatpaks(callback=self.end_loading) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index a7b7f55..c2e9e93 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -36,11 +36,6 @@ template $PackagesPage : Adw.BreakpointBin { icon-name: "loupe-large-symbolic"; tooltip-text: _("Search Packages"); } - [start] - Button refresh_button { - icon-name: "arrow-circular-top-right-symbolic"; - tooltip-text: _("Refresh List"); - } [end] ToggleButton filter_button { icon-name: "funnel-symbolic"; diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 57577db..89f08d5 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -22,7 +22,6 @@ class PackagesPage(Adw.BreakpointBin): no_results = gtc() sidebar_button = gtc() filter_button = gtc() - refresh_button = gtc() search_bar = gtc() search_entry = gtc() packages_split = gtc() @@ -223,7 +222,7 @@ class PackagesPage(Adw.BreakpointBin): details = err.stderr if type(err) == subprocess.CalledProcessError else str(err) self.packages_toast_overlay.add_toast(ErrorToast(_("Could not uninstall packages"), details).toast) else: - self.refresh_handler() + self.main_window.refresh_handler() GLib.idle_add(lambda *__: self.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled Packages")))) Gio.Task.new(None, None, callback).run_in_thread(thread) @@ -239,12 +238,14 @@ class PackagesPage(Adw.BreakpointBin): dialog.connect("response", on_response) dialog.present(self.main_window) - def refresh_handler(self, *args): + def start_loading(self): self.packages_navpage.set_title(_("Packages")) self.selected_rows.clear() self.select_button.set_active(False) self.set_status(self.loading_packages) - HostInfo.get_flatpaks(callback=self.generate_list) + + def end_loading(self): + self.generate_list() def select_button_handler(self, button): self.set_selection_mode(button.get_active()) @@ -274,7 +275,6 @@ class PackagesPage(Adw.BreakpointBin): def __init__(self, main_window, **kwargs): super().__init__(**kwargs) - HostInfo.get_flatpaks(callback=self.generate_list) # Extra Object Creation self.main_window = main_window @@ -293,14 +293,11 @@ class PackagesPage(Adw.BreakpointBin): self.__class__.instance = self # Connections - main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) - main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) - self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) + self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) 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) - self.refresh_button.connect("clicked", self.refresh_handler) self.select_button.connect("clicked", self.select_button_handler) self.filter_button.connect("toggled", self.filter_button_handler) self.reset_filters_button.connect("clicked", lambda *_: self.filters_page.reset_filters()) diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 4c2ac94..67a7d94 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -56,6 +56,12 @@ class UserDataPage(Adw.BreakpointBin): elif self.switcher_bar.get_reveal(): self.header_bar.set_show_title(False) + def start_loading(self, *args): + pass + + def end_loading(self, *args): + pass + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -70,10 +76,8 @@ class UserDataPage(Adw.BreakpointBin): self.flow_box.get_child_at_index(i).set_focusable(False) # Connections - main_window.main_split.connect("notify::show-sidebar", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) - main_window.main_split.connect("notify::collapsed", lambda sidebar, *_: self.sidebar_button.set_visible(sidebar.get_collapsed() or not sidebar.get_show_sidebar())) - self.sidebar_button.connect("clicked", lambda *_: main_window.main_split.set_show_sidebar(True)) - self.sidebar_button.set_visible(main_window.main_split.get_collapsed()) + self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) + self.adj.connect("value-changed", self.show_title_handler) From 32b1c3ae6c7a94e26c7cae261aeddbf1f6d13e54 Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 17 Jul 2024 15:55:33 -0400 Subject: [PATCH 067/332] Seperate active and leftover data pages --- src/meson.build | 8 +- src/packages_page/packages_page.py | 2 +- src/user_data_page/active_data_page.blp | 39 +++++++++- src/user_data_page/active_data_page.py | 21 ++++++ src/user_data_page/leftover_data_page.blp | 39 +++++++++- src/user_data_page/leftover_data_page.py | 18 +++++ src/user_data_page/user_data_page.blp | 90 ----------------------- src/user_data_page/user_data_page.py | 69 ++++++++--------- src/warehouse.gresource.xml | 4 +- 9 files changed, 154 insertions(+), 136 deletions(-) diff --git a/src/meson.build b/src/meson.build index cedac79..e1a2b70 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,8 +10,10 @@ blueprints = custom_target('blueprints', 'packages_page/packages_page.blp', 'packages_page/filters_page.blp', 'properties_page/properties_page.blp', - 'user_data_page/user_data_page.blp', 'user_data_page/data_box.blp', + 'user_data_page/user_data_page.blp', + 'user_data_page/active_data_page.blp', + 'user_data_page/leftover_data_page.blp', 'change_version_page/change_version_page.blp', ), output: '.', @@ -62,8 +64,10 @@ warehouse_sources = [ 'packages_page/filters_page.py', 'properties_page/properties_page.py', 'change_version_page/change_version_page.py', - 'user_data_page/user_data_page.py', 'user_data_page/data_box.py', + 'user_data_page/user_data_page.py', + 'user_data_page/active_data_page.py', + 'user_data_page/leftover_data_page.py', '../data/style.css', ] diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 89f08d5..74319fe 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -245,7 +245,7 @@ class PackagesPage(Adw.BreakpointBin): self.set_status(self.loading_packages) def end_loading(self): - self.generate_list() + GLib.idle_add(lambda *_: self.generate_list()) def select_button_handler(self, button): self.set_selection_mode(button.get_active()) diff --git a/src/user_data_page/active_data_page.blp b/src/user_data_page/active_data_page.blp index 26ab267..2040566 100644 --- a/src/user_data_page/active_data_page.blp +++ b/src/user_data_page/active_data_page.blp @@ -1,6 +1,41 @@ using Gtk 4.0; using Adw 1; -template $ActiveDataPage : Adw.ViewStackPage { - +template $ActiveDataPage : ScrolledWindow { + Box { + orientation: vertical; + Box { + orientation: vertical; + margin-start: 24; + margin-end: 24; + Label { + label: _("Active User Data"); + styles ["title-1"] + hexpand: true; + halign: start; + } + Label { + label: "32 Items - 39.7 GB"; + styles ["title-3"] + hexpand: true; + halign: start; + } + } + Separator { + margin-start: 12; + margin-end: 12; + margin-top: 9; + margin-bottom: 6; + } + FlowBox flow_box { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } } \ No newline at end of file diff --git a/src/user_data_page/active_data_page.py b/src/user_data_page/active_data_page.py index e69de29..0efb0b9 100644 --- a/src/user_data_page/active_data_page.py +++ b/src/user_data_page/active_data_page.py @@ -0,0 +1,21 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo +from .error_toast import ErrorToast +from .data_box import DataBox + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/active_data_page.ui") +class ActiveDataPage(Gtk.ScrolledWindow): + __gtype_name__ = 'ActiveDataPage' + gtc = Gtk.Template.Child + + def __init__(self, main_window, data_page, **kwargs): + super().__init__(**kwargs) + + # self.select_button.connect("toggled", lambda *_: self.set_selection_mode(self.select_button.get_active())) + # self.flow_box.connect("child-activated", lambda _, item: (cb := (row := item.get_child()).check_button).set_active((not cb.get_active()) if row.get_activatable() else False)) + + # Extra Object Creation + + # Apply + + # Connections \ No newline at end of file diff --git a/src/user_data_page/leftover_data_page.blp b/src/user_data_page/leftover_data_page.blp index 3423ab4..2e6aa44 100644 --- a/src/user_data_page/leftover_data_page.blp +++ b/src/user_data_page/leftover_data_page.blp @@ -1,6 +1,41 @@ using Gtk 4.0; using Adw 1; -template $LeftoverDataPage : Adw.ViewStackPage { - +template $LeftoverDataPage : ScrolledWindow { + Box { + orientation: vertical; + Box { + orientation: vertical; + margin-start: 24; + margin-end: 24; + Label { + label: _("Leftover User Data"); + styles ["title-1"] + hexpand: true; + halign: start; + } + Label { + label: "25 Items - 18.6 GB"; + styles ["title-3"] + hexpand: true; + halign: start; + } + } + Separator { + margin-start: 12; + margin-end: 12; + margin-top: 9; + margin-bottom: 6; + } + FlowBox { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } } \ No newline at end of file diff --git a/src/user_data_page/leftover_data_page.py b/src/user_data_page/leftover_data_page.py index e69de29..84d0c0c 100644 --- a/src/user_data_page/leftover_data_page.py +++ b/src/user_data_page/leftover_data_page.py @@ -0,0 +1,18 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo +from .error_toast import ErrorToast +from .data_box import DataBox + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/leftover_data_page.ui") +class LeftoverDataPage(Gtk.ScrolledWindow): + __gtype_name__ = 'LeftoverDataPage' + gtc = Gtk.Template.Child + + def __init__(self, main_window, data_page, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + + # Apply + + # Connections \ No newline at end of file diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 75c2d55..6efc79f 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -102,96 +102,6 @@ template $UserDataPage : Adw.BreakpointBin { visible: false; } Adw.ViewStack stack { - Adw.ViewStackPage { - name: "active"; - title: _("Active Data"); - icon-name: "file-manager-symbolic"; - child: - ScrolledWindow scrolled_window { - Box { - orientation: vertical; - Box { - orientation: vertical; - margin-start: 24; - margin-end: 24; - Label { - label: _("Active User Data"); - styles ["title-1"] - hexpand: true; - halign: start; - } - Label { - label: "32 Items - 39.7 GB"; - styles ["title-3"] - hexpand: true; - halign: start; - } - } - Separator { - margin-start: 12; - margin-end: 12; - margin-top: 9; - margin-bottom: 6; - } - FlowBox flow_box { - styles ["boxed-list"] - homogeneous: true; - valign: start; - selection-mode: none; - max-children-per-line: 6; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - } - } - } - ; - } - Adw.ViewStackPage { - name: "leftover"; - title: _("Leftover Data"); - icon-name: "folder-templates-symbolic"; - child: - ScrolledWindow { - Box { - orientation: vertical; - Box { - orientation: vertical; - margin-start: 24; - margin-end: 24; - Label { - label: _("Leftover User Data"); - styles ["title-1"] - hexpand: true; - halign: start; - } - Label { - label: "25 Items - 18.6 GB"; - styles ["title-3"] - hexpand: true; - halign: start; - } - } - Separator { - margin-start: 12; - margin-end: 12; - margin-top: 9; - margin-bottom: 6; - } - FlowBox { - styles ["boxed-list"] - homogeneous: true; - valign: start; - selection-mode: none; - max-children-per-line: 6; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - } - } - } - ; - } } } } diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 67a7d94..4424db5 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango -from .host_info import HostInfo from .error_toast import ErrorToast -from .data_box import DataBox +from .active_data_page import ActiveDataPage +from .leftover_data_page import LeftoverDataPage @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/user_data_page.ui") class UserDataPage(Adw.BreakpointBin): @@ -12,8 +12,7 @@ class UserDataPage(Adw.BreakpointBin): switcher_bar = gtc() sidebar_button = gtc() select_button = gtc() - scrolled_window = gtc() - flow_box = gtc() + stack = gtc() sort_pop = gtc() sort_list = gtc() sort_name = gtc() @@ -25,14 +24,6 @@ class UserDataPage(Adw.BreakpointBin): # This must be set to the created object from within the class's __init__ method instance = None - def set_selection_mode(self, is_enabled): - i = 0 - while row := self.flow_box.get_child_at_index(i): - i += 1 - row = row.get_child() - row.check_button.set_visible(is_enabled) - row.check_button.set_active(False) - def sort_handler(self, button): if button.get_active() == False: return @@ -44,17 +35,17 @@ class UserDataPage(Adw.BreakpointBin): else: self.sort_name.grab_focus() - def bpt_handler(self, _, is_applied): - if is_applied and self.adj.get_value() == 0: - self.header_bar.set_show_title(False) - else: - self.header_bar.set_show_title(True) + # def bpt_handler(self, _, is_applied): + # if is_applied and self.adj.get_value() == 0: + # self.header_bar.set_show_title(False) + # else: + # self.header_bar.set_show_title(True) - def show_title_handler(self, *args): - if self.adj.get_value() != 0: - self.header_bar.set_show_title(True) - elif self.switcher_bar.get_reveal(): - self.header_bar.set_show_title(False) + # def show_title_handler(self, *args): + # if self.adj.get_value() != 0: + # self.header_bar.set_show_title(True) + # elif self.switcher_bar.get_reveal(): + # self.header_bar.set_show_title(False) def start_loading(self, *args): pass @@ -65,27 +56,29 @@ class UserDataPage(Adw.BreakpointBin): def __init__(self, main_window, **kwargs): super().__init__(**kwargs) - self.adj = self.scrolled_window.get_vadjustment() - - # Apply + # Extra Object Creation self.__class__.instance = self + # self.adj = self.scrolled_window.get_vadjustment() - for i in range(10): - box = DataBox(main_window, "/home/heliguy/.var/app/io.github.io.github.flattool.Warehouse/") - self.flow_box.append(box) - self.flow_box.get_child_at_index(i).set_focusable(False) + # Apply + self.stack.add_titled_with_icon( + child=ActiveDataPage(main_window, self), + name="active", + title=_("Active Data"), + icon_name="file-manager-symbolic", + ) + self.stack.add_titled_with_icon( + child=LeftoverDataPage(main_window, self), + name="leftover", + title=_("Leftover Data"), + icon_name="folder-templates-symbolic", + ) # Connections self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) - - self.adj.connect("value-changed", self.show_title_handler) - - - # self.select_button.connect("toggled", lambda *_: self.set_selection_mode(self.select_button.get_active())) - # self.flow_box.connect("child-activated", lambda _, item: (cb := (row := item.get_child()).check_button).set_active((not cb.get_active()) if row.get_activatable() else False)) - + # self.adj.connect("value-changed", self.show_title_handler) self.sort_name.connect("toggled", self.sort_handler) self.sort_id.connect("toggled", self.sort_handler) self.sort_size.connect("toggled", self.sort_handler) - self.bpt.connect("apply", self.bpt_handler, True) - self.bpt.connect("unapply", self.bpt_handler, False) \ No newline at end of file + # self.bpt.connect("apply", self.bpt_handler, True) + # self.bpt.connect("unapply", self.bpt_handler, False) \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index c993dcc..5403771 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -9,8 +9,10 @@ packages_page/filters_page.ui properties_page/properties_page.ui change_version_page/change_version_page.ui - user_data_page/user_data_page.ui user_data_page/data_box.ui + user_data_page/user_data_page.ui + user_data_page/active_data_page.ui + user_data_page/leftover_data_page.ui From f05fb969a1edb114c87e5a56aefa1380c6c6f137 Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 17 Jul 2024 16:40:47 -0400 Subject: [PATCH 068/332] use stack for switching main pages --- src/main_window/window.blp | 4 ++++ src/main_window/window.py | 22 ++++++++-------------- src/user_data_page/active_data_page.py | 16 +++++++++++++--- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/main_window/window.blp b/src/main_window/window.blp index 0141a56..e36c75e 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -130,6 +130,10 @@ template $WarehouseWindow: Adw.ApplicationWindow { } } ; + content: + Stack stack { + } + ; } ; } diff --git a/src/main_window/window.py b/src/main_window/window.py index 765d1bc..9825f1e 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -35,6 +35,7 @@ class WarehouseWindow(Adw.ApplicationWindow): gtc = Gtk.Template.Child main_breakpoint = gtc() main_split = gtc() + stack = gtc() refresh_button = gtc() navigation_row_listbox = gtc() packages_row = gtc() @@ -54,17 +55,7 @@ class WarehouseWindow(Adw.ApplicationWindow): row = row.get_child() page = self.pages[row] - if hide_sidebar and self.main_split.get_collapsed(): - self.main_split.set_show_sidebar(False) - - if type(self.main_split.get_content()) == page: - # Skip when the user clicks on the row that is already showing the page - return - - if page.instance: - self.main_split.set_content(page.instance) - else: - self.main_split.set_content(page(main_window=self)) + self.stack.set_visible_child(page) def start_loading(self, *args): for _, page in self.pages.items(): @@ -88,11 +79,14 @@ class WarehouseWindow(Adw.ApplicationWindow): event_controller = Gtk.EventControllerKey() file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) self.pages = { - self.packages_row: PackagesPage, + self.packages_row: PackagesPage(main_window=self), - self.user_data_row: UserDataPage, + self.user_data_row: UserDataPage(main_window=self), } + for _, page in self.pages.items(): + self.stack.add_child(page) + # Apply ErrorToast.main_window = self self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT) @@ -111,7 +105,7 @@ class WarehouseWindow(Adw.ApplicationWindow): # file_drop.connect("drop", self.drop_callback) self.refresh_button.connect("clicked", self.refresh_handler) - self.navigation_row_listbox.get_row_at_index(0).activate() + self.navigation_row_listbox.get_row_at_index(2).activate() self.main_split.set_show_sidebar(True) self.start_loading() diff --git a/src/user_data_page/active_data_page.py b/src/user_data_page/active_data_page.py index 0efb0b9..04ca401 100644 --- a/src/user_data_page/active_data_page.py +++ b/src/user_data_page/active_data_page.py @@ -2,20 +2,30 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .host_info import HostInfo from .error_toast import ErrorToast from .data_box import DataBox +import pathlib, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/active_data_page.ui") class ActiveDataPage(Gtk.ScrolledWindow): __gtype_name__ = 'ActiveDataPage' gtc = Gtk.Template.Child + flow_box = gtc() + + def generate_list(self, *args): + data = f"{HostInfo.home}/.var/app" + for folder in os.listdir(data): + print(folder) + def __init__(self, main_window, data_page, **kwargs): super().__init__(**kwargs) + self.generate_list() + # self.select_button.connect("toggled", lambda *_: self.set_selection_mode(self.select_button.get_active())) # self.flow_box.connect("child-activated", lambda _, item: (cb := (row := item.get_child()).check_button).set_active((not cb.get_active()) if row.get_activatable() else False)) - # Extra Object Creation + # Extra Object Creation - # Apply + # Apply - # Connections \ No newline at end of file + # Connections \ No newline at end of file From 78e76ed98123d8c800a96876a4b560ef830988f8 Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 18 Jul 2024 00:22:05 -0400 Subject: [PATCH 069/332] Continue work on data pages --- src/host_info.py | 3 + src/main_window/window.py | 8 ++- src/meson.build | 6 +- src/user_data_page/active_data_page.blp | 41 ----------- src/user_data_page/active_data_page.py | 31 -------- src/user_data_page/data_box.blp | 7 +- src/user_data_page/data_box.py | 23 +++++- src/user_data_page/data_subpage.blp | 60 ++++++++++++++++ src/user_data_page/data_subpage.py | 86 +++++++++++++++++++++++ src/user_data_page/leftover_data_page.blp | 41 ----------- src/user_data_page/leftover_data_page.py | 18 ----- src/user_data_page/user_data_page.py | 40 +++++++++-- src/warehouse.gresource.xml | 3 +- 13 files changed, 218 insertions(+), 149 deletions(-) delete mode 100644 src/user_data_page/active_data_page.blp delete mode 100644 src/user_data_page/active_data_page.py create mode 100644 src/user_data_page/data_subpage.blp create mode 100644 src/user_data_page/data_subpage.py delete mode 100644 src/user_data_page/leftover_data_page.blp delete mode 100644 src/user_data_page/leftover_data_page.py diff --git a/src/host_info.py b/src/host_info.py index 1db56d1..dbf1395 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -238,6 +238,7 @@ class HostInfo: icon_theme.add_search_path(f"{i}/exports/share/icons") flatpaks = [] + id_to_flatpak = {} ref_to_flatpak = {} remotes = {} installations = [] @@ -248,6 +249,7 @@ class HostInfo: def get_flatpaks(this, callback=None): # Callback is a function to run after the host flatpaks are found this.flatpaks.clear() + this.id_to_flatpak.clear() this.ref_to_flatpak.clear() this.remotes.clear() this.installations.clear() @@ -336,6 +338,7 @@ class HostInfo: for i in lines: package = Flatpak(i.split("\t")) this.flatpaks.append(package) + this.id_to_flatpak[package.info["id"]] = package this.ref_to_flatpak[package.info["ref"]] = package # Dependant Runtimes diff --git a/src/main_window/window.py b/src/main_window/window.py index 9825f1e..1197e6d 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -63,12 +63,14 @@ class WarehouseWindow(Adw.ApplicationWindow): page.instance.start_loading() def end_loading(self, *args): - for _, page in self.pages.items(): - if page.instance: - page.instance.end_loading() + for _, page in self.pages.items(): + if page.instance: + page.instance.end_loading() + self.refresh_button.set_sensitive(True) def refresh_handler(self, *args): self.start_loading() + self.refresh_button.set_sensitive(False) HostInfo.get_flatpaks(callback=self.end_loading) def __init__(self, **kwargs): diff --git a/src/meson.build b/src/meson.build index e1a2b70..139d30d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,8 +12,7 @@ blueprints = custom_target('blueprints', 'properties_page/properties_page.blp', 'user_data_page/data_box.blp', 'user_data_page/user_data_page.blp', - 'user_data_page/active_data_page.blp', - 'user_data_page/leftover_data_page.blp', + 'user_data_page/data_subpage.blp', 'change_version_page/change_version_page.blp', ), output: '.', @@ -66,8 +65,7 @@ warehouse_sources = [ 'change_version_page/change_version_page.py', 'user_data_page/data_box.py', 'user_data_page/user_data_page.py', - 'user_data_page/active_data_page.py', - 'user_data_page/leftover_data_page.py', + 'user_data_page/data_subpage.py', '../data/style.css', ] diff --git a/src/user_data_page/active_data_page.blp b/src/user_data_page/active_data_page.blp deleted file mode 100644 index 2040566..0000000 --- a/src/user_data_page/active_data_page.blp +++ /dev/null @@ -1,41 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $ActiveDataPage : ScrolledWindow { - Box { - orientation: vertical; - Box { - orientation: vertical; - margin-start: 24; - margin-end: 24; - Label { - label: _("Active User Data"); - styles ["title-1"] - hexpand: true; - halign: start; - } - Label { - label: "32 Items - 39.7 GB"; - styles ["title-3"] - hexpand: true; - halign: start; - } - } - Separator { - margin-start: 12; - margin-end: 12; - margin-top: 9; - margin-bottom: 6; - } - FlowBox flow_box { - styles ["boxed-list"] - homogeneous: true; - valign: start; - selection-mode: none; - max-children-per-line: 6; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - } - } -} \ No newline at end of file diff --git a/src/user_data_page/active_data_page.py b/src/user_data_page/active_data_page.py deleted file mode 100644 index 04ca401..0000000 --- a/src/user_data_page/active_data_page.py +++ /dev/null @@ -1,31 +0,0 @@ -from gi.repository import Adw, Gtk, GLib, Gio, Pango -from .host_info import HostInfo -from .error_toast import ErrorToast -from .data_box import DataBox -import pathlib, os - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/active_data_page.ui") -class ActiveDataPage(Gtk.ScrolledWindow): - __gtype_name__ = 'ActiveDataPage' - gtc = Gtk.Template.Child - - flow_box = gtc() - - def generate_list(self, *args): - data = f"{HostInfo.home}/.var/app" - for folder in os.listdir(data): - print(folder) - - def __init__(self, main_window, data_page, **kwargs): - super().__init__(**kwargs) - - self.generate_list() - - # self.select_button.connect("toggled", lambda *_: self.set_selection_mode(self.select_button.get_active())) - # self.flow_box.connect("child-activated", lambda _, item: (cb := (row := item.get_child()).check_button).set_active((not cb.get_active()) if row.get_activatable() else False)) - - # Extra Object Creation - - # Apply - - # Connections \ No newline at end of file diff --git a/src/user_data_page/data_box.blp b/src/user_data_page/data_box.blp index 695d814..05ac3bf 100644 --- a/src/user_data_page/data_box.blp +++ b/src/user_data_page/data_box.blp @@ -13,7 +13,7 @@ template $DataBox : ListBox { Box title_box { margin-top: 12; margin-bottom: 12; - Image app_icon { + Image image { margin-start: 12; margin-end: 12; icon-name: "application-x-executable-symbolic"; @@ -31,7 +31,7 @@ template $DataBox : ListBox { } Label subtitle_label { label: "No subtitle set"; - hexpand: true; + // hexpand: true; halign: start; ellipsize: middle; margin-end: 12; @@ -43,6 +43,9 @@ template $DataBox : ListBox { margin-start: 12; margin-end: 6; margin-bottom: 6; + Spinner spinner { + spinning: true; + } Label size_label { label: "No size set"; halign: start; diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 6dac6f7..c73cb89 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -1,13 +1,32 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .host_info import HostInfo +import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/data_box.ui") class DataBox(Gtk.ListBox): __gtype_name__ = 'DataBox' gtc = Gtk.Template.Child + image = gtc() title_label = gtc() + subtitle_label = gtc() + size_label = gtc() check_button = gtc() - def __init__(self, main_window, path, **kwargs): - super().__init__(**kwargs) \ No newline at end of file + def idle_stuff(self): + self.title_label.set_label(self.title) + self.subtitle_label.set_label(self.subtitle) + if self.icon_path: + self.image.add_css_class("icon-dropshadow") + self.image.set_from_file(self.icon_path) + + def __init__(self, title, subtitle, data_path, icon_path=None, callback=None, **kwargs): + super().__init__(**kwargs) + + self.title = title + self.subtitle = subtitle + self.icon_path = icon_path + self.data_path = data_path + self.callback = callback + + self.idle_stuff() diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp new file mode 100644 index 0000000..01f1e40 --- /dev/null +++ b/src/user_data_page/data_subpage.blp @@ -0,0 +1,60 @@ +using Gtk 4.0; +using Adw 1; + +template $DataSubpage : ScrolledWindow { + Box { + orientation: vertical; + Box { + orientation: vertical; + margin-start: 24; + margin-end: 24; + Label title { + label: _("No Title Set"); + styles ["title-1"] + hexpand: true; + halign: start; + } + Box { + Label subtitle { + label: "No Subtutle Set"; + styles ["title-3"] + } + Image { + icon-name: "dot-symbolic"; + margin-start: 6; + margin-end: 6; + margin-top: 3; + valign: center; + } + Spinner spinner { + spinning: true; + valign: center; + margin-top: 3; + margin-end: 6; + } + Label size_label { + label: _("Loading Size…"); + styles ["title-3"] + hexpand: true; + halign: start; + } + } + } + Separator { + margin-start: 12; + margin-end: 12; + margin-top: 9; + margin-bottom: 6; + } + FlowBox flow_box { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } +} \ No newline at end of file diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py new file mode 100644 index 0000000..be3c645 --- /dev/null +++ b/src/user_data_page/data_subpage.py @@ -0,0 +1,86 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo +from .error_toast import ErrorToast +from .data_box import DataBox +from .host_info import HostInfo +import subprocess + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/data_subpage.ui") +class DataSubpage(Gtk.ScrolledWindow): + __gtype_name__ = 'DataSubpage' + gtc = Gtk.Template.Child + + title = gtc() + subtitle = gtc() + spinner = gtc() + size_label = gtc() + flow_box = gtc() + + def human_readable_size(self, size): + units = ['KB', 'MB', 'GB', 'TB'] + # size *= 1024 + for unit in units: + if size < 1024: + return f"~ {round(size)} {unit}" + size /= 1024 + return f"~ {round(size)} PB" + + def get_size(self, path): + sed = "sed 's/K/ KB/; s/M/ MB/; s/G/ GB/; s/T/ TB/; s/P/ PB/;'" + self.total_size += int(subprocess.run(['du', '-s', path], capture_output=True, text=True).stdout.split("\t")[0]) + + def show_size(self, data): + for folder in data: + self.get_size(f"{HostInfo.home}/.var/app/{folder}") + + self.size_label.set_label(self.human_readable_size(self.total_size)) + self.spinner.set_visible(False) + + def generate_list(self, sort_mode, data=None, paks=None): + self.total_size = 0 + Gio.Task().run_in_thread(lambda *_: self.show_size(data)) + self.flow_box.remove_all() + total = len(data) + GLib.idle_add(lambda *_z: self.subtitle.set_label(_("{} Items").format(total))) + self.boxes.clear() + + def thread(sort_mode, data, paks): + if paks: + for package in paks: + folder = package.info["id"] + box = DataBox(package.info["name"], folder, f"{HostInfo.home}/.var/app/{folder}", package.icon_path) + self.boxes.append(box) + else: + for folder in data: + box = DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}") + self.boxes.append(box) + + def callback(sort_mode): + if sort_mode == "name": + self.boxes = sorted(self.boxes, key=lambda box: box.title) + elif sort_mode == "id": + self.boxes = sorted(self.boxes, key=lambda box: box.subtitle) + else: + pass + + for box in self.boxes: + self.flow_box.append(box) + + Gio.Task.new(None, None, lambda *_: callback(sort_mode)).run_in_thread(lambda *_: thread(sort_mode, data, paks)) + + def __init__(self, title, main_window, **kwargs): + super().__init__(**kwargs) + + GLib.idle_add(lambda *_: self.title.set_label(title)) + + # self.select_button.connect("toggled", lambda *_: self.set_selection_mode(self.select_button.get_active())) + # self.flow_box.connect("child-activated", lambda _, item: (cb := (row := item.get_child()).check_button).set_active((not cb.get_active()) if row.get_activatable() else False)) + + # Extra Object Creation + self.main_window = main_window + self.total_size = 0 + self.boxes = [] + + # Apply + + # Connections \ No newline at end of file diff --git a/src/user_data_page/leftover_data_page.blp b/src/user_data_page/leftover_data_page.blp deleted file mode 100644 index 2e6aa44..0000000 --- a/src/user_data_page/leftover_data_page.blp +++ /dev/null @@ -1,41 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $LeftoverDataPage : ScrolledWindow { - Box { - orientation: vertical; - Box { - orientation: vertical; - margin-start: 24; - margin-end: 24; - Label { - label: _("Leftover User Data"); - styles ["title-1"] - hexpand: true; - halign: start; - } - Label { - label: "25 Items - 18.6 GB"; - styles ["title-3"] - hexpand: true; - halign: start; - } - } - Separator { - margin-start: 12; - margin-end: 12; - margin-top: 9; - margin-bottom: 6; - } - FlowBox { - styles ["boxed-list"] - homogeneous: true; - valign: start; - selection-mode: none; - max-children-per-line: 6; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - } - } -} \ No newline at end of file diff --git a/src/user_data_page/leftover_data_page.py b/src/user_data_page/leftover_data_page.py deleted file mode 100644 index 84d0c0c..0000000 --- a/src/user_data_page/leftover_data_page.py +++ /dev/null @@ -1,18 +0,0 @@ -from gi.repository import Adw, Gtk, GLib, Gio, Pango -from .host_info import HostInfo -from .error_toast import ErrorToast -from .data_box import DataBox - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/leftover_data_page.ui") -class LeftoverDataPage(Gtk.ScrolledWindow): - __gtype_name__ = 'LeftoverDataPage' - gtc = Gtk.Template.Child - - def __init__(self, main_window, data_page, **kwargs): - super().__init__(**kwargs) - - # Extra Object Creation - - # Apply - - # Connections \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 4424db5..aa0fdd4 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -1,7 +1,9 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .error_toast import ErrorToast -from .active_data_page import ActiveDataPage -from .leftover_data_page import LeftoverDataPage +from .data_box import DataBox +from .data_subpage import DataSubpage +from .host_info import HostInfo +import os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/user_data_page.ui") class UserDataPage(Adw.BreakpointBin): @@ -47,11 +49,33 @@ class UserDataPage(Adw.BreakpointBin): # elif self.switcher_bar.get_reveal(): # self.header_bar.set_show_title(False) + def sort_data(self, *args): + self.data_flatpaks.clear() + self.active_data.clear() + self.leftover_data.clear() + # paks = dict(HostInfo.id_to_flatpak) + + for folder in os.listdir(f"{HostInfo.home}/.var/app"): + try: + self.data_flatpaks.append(HostInfo.id_to_flatpak[folder]) + self.active_data.append(folder) + except KeyError: + self.leftover_data.append(folder) + def start_loading(self, *args): + self.adp.size_label.set_label("Loading Size…") + self.adp.spinner.set_visible(True) + self.ldp.size_label.set_label("Loading Size…") + self.ldp.spinner.set_visible(True) pass def end_loading(self, *args): - pass + self.sort_mode = "id" + def callback(*args): + self.adp.generate_list(self.sort_mode, data=self.active_data, paks=self.data_flatpaks) + self.ldp.generate_list(self.sort_mode, data=self.leftover_data) + + Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -59,16 +83,22 @@ class UserDataPage(Adw.BreakpointBin): # Extra Object Creation self.__class__.instance = self # self.adj = self.scrolled_window.get_vadjustment() + self.adp = DataSubpage(_("Active Data"), main_window) + self.ldp = DataSubpage(_("Leftover Data"), main_window) + self.data_flatpaks = [] + self.active_data = [] + self.leftover_data = [] + self.total_items = 0 # Apply self.stack.add_titled_with_icon( - child=ActiveDataPage(main_window, self), + child=self.adp, name="active", title=_("Active Data"), icon_name="file-manager-symbolic", ) self.stack.add_titled_with_icon( - child=LeftoverDataPage(main_window, self), + child=self.ldp, name="leftover", title=_("Leftover Data"), icon_name="folder-templates-symbolic", diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 5403771..775fd45 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -11,8 +11,7 @@ change_version_page/change_version_page.ui user_data_page/data_box.ui user_data_page/user_data_page.ui - user_data_page/active_data_page.ui - user_data_page/leftover_data_page.ui + user_data_page/data_subpage.ui From 449414579698b89a1f47fb02f767e317fa7df274 Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 18 Jul 2024 01:15:26 -0400 Subject: [PATCH 070/332] Continue work on data page --- src/user_data_page/data_box.py | 1 + src/user_data_page/data_subpage.py | 26 ++++++++++++++++---------- src/user_data_page/user_data_page.py | 3 +-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index c73cb89..5d85e80 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -28,5 +28,6 @@ class DataBox(Gtk.ListBox): self.icon_path = icon_path self.data_path = data_path self.callback = callback + self.size = None self.idle_stuff() diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index be3c645..de970be 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -27,15 +27,23 @@ class DataSubpage(Gtk.ScrolledWindow): def get_size(self, path): sed = "sed 's/K/ KB/; s/M/ MB/; s/G/ GB/; s/T/ TB/; s/P/ PB/;'" - self.total_size += int(subprocess.run(['du', '-s', path], capture_output=True, text=True).stdout.split("\t")[0]) + return int(subprocess.run(['du', '-s', path], capture_output=True, text=True).stdout.split("\t")[0]) def show_size(self, data): for folder in data: - self.get_size(f"{HostInfo.home}/.var/app/{folder}") + self.total_size += self.get_size(f"{HostInfo.home}/.var/app/{folder}") self.size_label.set_label(self.human_readable_size(self.total_size)) self.spinner.set_visible(False) + def sort_boxes(self, sort_mode): + if sort_mode == "name": + self.boxes = sorted(self.boxes, key=lambda box: box.title) + elif sort_mode == "id": + self.boxes = sorted(self.boxes, key=lambda box: box.subtitle) + else: + self.boxes = sorted(self.boxes, key=lambda box: box.size) + def generate_list(self, sort_mode, data=None, paks=None): self.total_size = 0 Gio.Task().run_in_thread(lambda *_: self.show_size(data)) @@ -48,21 +56,19 @@ class DataSubpage(Gtk.ScrolledWindow): if paks: for package in paks: folder = package.info["id"] - box = DataBox(package.info["name"], folder, f"{HostInfo.home}/.var/app/{folder}", package.icon_path) + path = f"{HostInfo.home}/.var/app/{folder}" + box = DataBox(package.info["name"], folder, path, package.icon_path) + box.size = self.get_size(path) + box.size_label.set_label(self.human_readable_size(box.size)) self.boxes.append(box) else: for folder in data: box = DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}") self.boxes.append(box) - def callback(sort_mode): - if sort_mode == "name": - self.boxes = sorted(self.boxes, key=lambda box: box.title) - elif sort_mode == "id": - self.boxes = sorted(self.boxes, key=lambda box: box.subtitle) - else: - pass + self.sort_boxes(sort_mode) + def callback(sort_mode): for box in self.boxes: self.flow_box.append(box) diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index aa0fdd4..1038042 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -67,10 +67,9 @@ class UserDataPage(Adw.BreakpointBin): self.adp.spinner.set_visible(True) self.ldp.size_label.set_label("Loading Size…") self.ldp.spinner.set_visible(True) - pass def end_loading(self, *args): - self.sort_mode = "id" + self.sort_mode = "size" def callback(*args): self.adp.generate_list(self.sort_mode, data=self.active_data, paks=self.data_flatpaks) self.ldp.generate_list(self.sort_mode, data=self.leftover_data) From 2999155da3c1fa4a994d5998a370ef90762f3dbf Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 18 Jul 2024 16:57:46 -0400 Subject: [PATCH 071/332] Experiment with gtk flowbox sort --- src/user_data_page/user_data_page.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 1038042..1f0acaa 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -69,12 +69,24 @@ class UserDataPage(Adw.BreakpointBin): self.ldp.spinner.set_visible(True) def end_loading(self, *args): - self.sort_mode = "size" - def callback(*args): - self.adp.generate_list(self.sort_mode, data=self.active_data, paks=self.data_flatpaks) - self.ldp.generate_list(self.sort_mode, data=self.leftover_data) + def test(box1, box2): + return box1.get_child().get_label() > box2.get_child().get_label() - Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) + def test2(box1, box2): + return box1.get_child().get_label() < box2.get_child().get_label() + + self.adp.flow_box.insert(Gtk.Label(label="B"), 4) + self.adp.flow_box.insert(Gtk.Label(label="D"), 1) + self.adp.flow_box.insert(Gtk.Label(label="A"), 1) + self.adp.flow_box.set_sort_func(test) + self.adp.flow_box.insert(Gtk.Label(label="C"), 1) + self.adp.flow_box.set_sort_func(test2) + # self.sort_mode = "size" + # def callback(*args): + # self.adp.generate_list(self.sort_mode, data=self.active_data, paks=self.data_flatpaks) + # self.ldp.generate_list(self.sort_mode, data=self.leftover_data) + + # Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) From da3a9092308e474927a7f4650d27db714aa9ea5a Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 02:17:52 -0400 Subject: [PATCH 072/332] Sorting now functions --- src/user_data_page/data_box.py | 23 +++++++ src/user_data_page/data_subpage.blp | 3 + src/user_data_page/data_subpage.py | 90 ++++++++++++++++------------ src/user_data_page/user_data_page.py | 67 +++++++++++++-------- 4 files changed, 119 insertions(+), 64 deletions(-) diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 5d85e80..6ae259a 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -10,9 +10,31 @@ class DataBox(Gtk.ListBox): image = gtc() title_label = gtc() subtitle_label = gtc() + spinner = gtc() size_label = gtc() check_button = gtc() + def human_readable_size(self): + units = ['KB', 'MB', 'GB', 'TB'] + # size *= 1024 + for unit in units: + if self.size < 1024: + return f"~ {round(self.size)} {unit}" + self.size /= 1024 + return f"~ {round(self.size)} PB" + + def get_size(self, *args): + self.size = int(subprocess.run(['du', '-s', self.data_path], capture_output=True, text=True).stdout.split("\t")[0]) + + def show_size(self): + def callback(*args): + self.size_label.set_label(self.human_readable_size()) + self.spinner.set_visible(False) + if self.callback: + self.callback() + + Gio.Task.new(None, None, callback).run_in_thread(self.get_size) + def idle_stuff(self): self.title_label.set_label(self.title) self.subtitle_label.set_label(self.subtitle) @@ -31,3 +53,4 @@ class DataBox(Gtk.ListBox): self.size = None self.idle_stuff() + self.show_size() diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp index 01f1e40..d386c7e 100644 --- a/src/user_data_page/data_subpage.blp +++ b/src/user_data_page/data_subpage.blp @@ -13,11 +13,13 @@ template $DataSubpage : ScrolledWindow { styles ["title-1"] hexpand: true; halign: start; + wrap: true; } Box { Label subtitle { label: "No Subtutle Set"; styles ["title-3"] + wrap: true; } Image { icon-name: "dot-symbolic"; @@ -37,6 +39,7 @@ template $DataSubpage : ScrolledWindow { styles ["title-3"] hexpand: true; halign: start; + wrap: true; } } } diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index de970be..8d2d2b6 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -30,49 +30,57 @@ class DataSubpage(Gtk.ScrolledWindow): return int(subprocess.run(['du', '-s', path], capture_output=True, text=True).stdout.split("\t")[0]) def show_size(self, data): - for folder in data: - self.total_size += self.get_size(f"{HostInfo.home}/.var/app/{folder}") + def thread(*args): + for folder in data: + self.total_size += self.get_size(f"{HostInfo.home}/.var/app/{folder}") - self.size_label.set_label(self.human_readable_size(self.total_size)) - self.spinner.set_visible(False) + def callback(*args): + self.size_label.set_label(self.human_readable_size(self.total_size)) + self.spinner.set_visible(False) - def sort_boxes(self, sort_mode): - if sort_mode == "name": - self.boxes = sorted(self.boxes, key=lambda box: box.title) - elif sort_mode == "id": - self.boxes = sorted(self.boxes, key=lambda box: box.subtitle) - else: - self.boxes = sorted(self.boxes, key=lambda box: box.size) + Gio.Task.new(None, None, callback).run_in_thread(thread) - def generate_list(self, sort_mode, data=None, paks=None): - self.total_size = 0 - Gio.Task().run_in_thread(lambda *_: self.show_size(data)) - self.flow_box.remove_all() - total = len(data) - GLib.idle_add(lambda *_z: self.subtitle.set_label(_("{} Items").format(total))) + def sort_func(self, box1, box2): + i1 = None + i2 = None + if self.sort_mode == "name": + i1 = box1.get_child().title.lower() + i2 = box2.get_child().title.lower() + + if self.sort_mode == "id": + i1 = box1.get_child().subtitle.lower() + i2 = box2.get_child().subtitle.lower() + + if self.sort_mode == "size" and self.ready_to_sort_size: + i1 = box1.get_child().size + i2 = box2.get_child().size + + if i1 is None or i2 is None: + return 0 + + return i1 > i2 if self.sort_ascend else i1 < i2 + + def box_size_callback(self): + self.finished_boxes += 1 + if self.finished_boxes == self.total_items: + self.ready_to_sort_size = True + self.flow_box.invalidate_sort() + + def generate_list(self, flatpaks, data, sort_mode): self.boxes.clear() - - def thread(sort_mode, data, paks): - if paks: - for package in paks: - folder = package.info["id"] - path = f"{HostInfo.home}/.var/app/{folder}" - box = DataBox(package.info["name"], folder, path, package.icon_path) - box.size = self.get_size(path) - box.size_label.set_label(self.human_readable_size(box.size)) - self.boxes.append(box) - else: - for folder in data: - box = DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}") - self.boxes.append(box) - - self.sort_boxes(sort_mode) - - def callback(sort_mode): - for box in self.boxes: + self.ready_to_sort_size = False + self.finished_boxes = 0 + self.sort_mode = sort_mode + self.total_items = len(data) + self.subtitle.set_label(_("{} Items").format(self.total_items)) + if flatpaks: + for i, pak in enumerate(flatpaks): + box = DataBox(pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback) + self.boxes.append(box) self.flow_box.append(box) - - Gio.Task.new(None, None, lambda *_: callback(sort_mode)).run_in_thread(lambda *_: thread(sort_mode, data, paks)) + else: + for i, folder in enumerate(data): + self.flow_box.append(DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}")) def __init__(self, title, main_window, **kwargs): super().__init__(**kwargs) @@ -84,9 +92,15 @@ class DataSubpage(Gtk.ScrolledWindow): # Extra Object Creation self.main_window = main_window + self.sort_mode = "" + self.sort_ascend = False self.total_size = 0 + self.total_items = 0 self.boxes = [] + self.ready_to_sort_size = False + self.finished_boxes = 0 # Apply + self.flow_box.set_sort_func(self.sort_func) # Connections \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 1f0acaa..30ae3de 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -16,6 +16,8 @@ class UserDataPage(Adw.BreakpointBin): select_button = gtc() stack = gtc() sort_pop = gtc() + asc = gtc() + dsc = gtc() sort_list = gtc() sort_name = gtc() sort_id = gtc() @@ -27,15 +29,37 @@ class UserDataPage(Adw.BreakpointBin): instance = None def sort_handler(self, button): - if button.get_active() == False: + if not button.get_active(): return - self.sort_name.set_active(self.sort_name is button) - self.sort_id.set_active(self.sort_id is button) - self.sort_size.set_active(self.sort_size is button) - if button is self.sort_name: - self.sort_id.grab_focus() - else: - self.sort_name.grab_focus() + + match button: + case self.asc: + self.adp.sort_ascend = True + self.ldp.sort_ascend = True + case self.dsc: + self.adp.sort_ascend = False + self.ldp.sort_ascend = False + case self.sort_name: + self.sort_id.grab_focus() + self.sort_id.set_active(False) + self.sort_size.set_active(False) + self.adp.sort_mode = "name" + self.ldp.sort_mode = "name" + case self.sort_id: + self.sort_size.grab_focus() + self.sort_size.set_active(False) + self.sort_name.set_active(False) + self.adp.sort_mode = "id" + self.ldp.sort_mode = "id" + case self.sort_size: + self.sort_name.grab_focus() + self.sort_name.set_active(False) + self.sort_id.set_active(False) + self.adp.sort_mode = "size" + self.ldp.sort_mode = "size" + + self.adp.flow_box.invalidate_sort() + self.ldp.flow_box.invalidate_sort() # def bpt_handler(self, _, is_applied): # if is_applied and self.adj.get_value() == 0: @@ -65,28 +89,17 @@ class UserDataPage(Adw.BreakpointBin): def start_loading(self, *args): self.adp.size_label.set_label("Loading Size…") self.adp.spinner.set_visible(True) + self.adp.flow_box.remove_all() self.ldp.size_label.set_label("Loading Size…") self.ldp.spinner.set_visible(True) + self.ldp.flow_box.remove_all() def end_loading(self, *args): - def test(box1, box2): - return box1.get_child().get_label() > box2.get_child().get_label() - - def test2(box1, box2): - return box1.get_child().get_label() < box2.get_child().get_label() - - self.adp.flow_box.insert(Gtk.Label(label="B"), 4) - self.adp.flow_box.insert(Gtk.Label(label="D"), 1) - self.adp.flow_box.insert(Gtk.Label(label="A"), 1) - self.adp.flow_box.set_sort_func(test) - self.adp.flow_box.insert(Gtk.Label(label="C"), 1) - self.adp.flow_box.set_sort_func(test2) - # self.sort_mode = "size" - # def callback(*args): - # self.adp.generate_list(self.sort_mode, data=self.active_data, paks=self.data_flatpaks) - # self.ldp.generate_list(self.sort_mode, data=self.leftover_data) - - # Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) + def callback(*args): + self.adp.generate_list(self.data_flatpaks, self.active_data, "size") + self.ldp.generate_list([], self.leftover_data, "name") + + Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -118,6 +131,8 @@ class UserDataPage(Adw.BreakpointBin): # Connections self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) # self.adj.connect("value-changed", self.show_title_handler) + self.asc.connect("toggled", self.sort_handler) + self.dsc.connect("toggled", self.sort_handler) self.sort_name.connect("toggled", self.sort_handler) self.sort_id.connect("toggled", self.sort_handler) self.sort_size.connect("toggled", self.sort_handler) From ae3cd173edb818a086b991079ab178aa8c70b004 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 11:17:42 -0400 Subject: [PATCH 073/332] Make sorting UI better --- data/icons/font-x-generic-symbolic.svg | 4 +++ data/icons/harddisk-symbolic.svg | 2 ++ data/icons/tag-outline-symbolic.svg | 2 ++ data/icons/view-sort-ascending-symbolic.svg | 7 ++++ data/icons/view-sort-descending-symbolic.svg | 7 ++++ src/user_data_page/data_subpage.py | 3 +- src/user_data_page/user_data_page.blp | 37 +++++++++++--------- src/user_data_page/user_data_page.py | 17 ++++++--- src/warehouse.gresource.xml | 5 +++ 9 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 data/icons/font-x-generic-symbolic.svg create mode 100644 data/icons/harddisk-symbolic.svg create mode 100644 data/icons/tag-outline-symbolic.svg create mode 100644 data/icons/view-sort-ascending-symbolic.svg create mode 100644 data/icons/view-sort-descending-symbolic.svg diff --git a/data/icons/font-x-generic-symbolic.svg b/data/icons/font-x-generic-symbolic.svg new file mode 100644 index 0000000..4978a46 --- /dev/null +++ b/data/icons/font-x-generic-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/data/icons/harddisk-symbolic.svg b/data/icons/harddisk-symbolic.svg new file mode 100644 index 0000000..75214bd --- /dev/null +++ b/data/icons/harddisk-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/tag-outline-symbolic.svg b/data/icons/tag-outline-symbolic.svg new file mode 100644 index 0000000..2ed4768 --- /dev/null +++ b/data/icons/tag-outline-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/view-sort-ascending-symbolic.svg b/data/icons/view-sort-ascending-symbolic.svg new file mode 100644 index 0000000..bd303f0 --- /dev/null +++ b/data/icons/view-sort-ascending-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/icons/view-sort-descending-symbolic.svg b/data/icons/view-sort-descending-symbolic.svg new file mode 100644 index 0000000..c620c7d --- /dev/null +++ b/data/icons/view-sort-descending-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 8d2d2b6..2563ced 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -66,11 +66,10 @@ class DataSubpage(Gtk.ScrolledWindow): self.ready_to_sort_size = True self.flow_box.invalidate_sort() - def generate_list(self, flatpaks, data, sort_mode): + def generate_list(self, flatpaks, data): self.boxes.clear() self.ready_to_sort_size = False self.finished_boxes = 0 - self.sort_mode = sort_mode self.total_items = len(data) self.subtitle.set_label(_("{} Items").format(self.total_items)) if flatpaks: diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 6efc79f..749fd5d 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -119,52 +119,57 @@ Popover sort_pop { homogeneous: true; spacing: 3; ToggleButton asc { + active: true; styles ["flat"] - label: _("Ascending"); can-focus: bind asc.active inverted; can-target: bind asc.active inverted; + Adw.ButtonContent { + icon-name: "view-sort-ascending-symbolic"; + label: _("Ascending"); + } } ToggleButton dsc { styles ["flat"] - label: _("Descending"); active: bind asc.active inverted bidirectional; can-focus: bind dsc.active inverted; can-target: bind dsc.active inverted; + Adw.ButtonContent { + icon-name: "view-sort-descending-symbolic"; + label: _("Descending"); + } } } Separator { } Box sort_list { - orientation: vertical; + homogeneous: true; + spacing: 3; ToggleButton sort_name { + active: true; styles ["flat"] can-focus: bind sort_name.active inverted; can-target: bind sort_name.active inverted; - Label { - styles ["body"] - halign: start; - label: _("Sort By Name"); + Adw.ButtonContent { + icon-name: "font-x-generic-symbolic"; + label: _("Name"); } } ToggleButton sort_id { styles ["flat"] can-focus: bind sort_id.active inverted; can-target: bind sort_id.active inverted; - Label { - styles ["body"] - halign: start; - label: _("Sort By ID"); + Adw.ButtonContent { + icon-name: "tag-outline-symbolic"; + label: _("ID"); } } ToggleButton sort_size { - active: true; can-focus: bind sort_size.active inverted; can-target: bind sort_size.active inverted; styles ["flat"] - Label { - styles ["body"] - halign: start; - label: _("Sort By Size"); + Adw.ButtonContent { + icon-name: "harddisk-symbolic"; + label: _("Size"); } } } diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 30ae3de..66f8e8e 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -28,7 +28,7 @@ class UserDataPage(Adw.BreakpointBin): # This must be set to the created object from within the class's __init__ method instance = None - def sort_handler(self, button): + def sort_handler(self, button, should_sort=True): if not button.get_active(): return @@ -58,8 +58,9 @@ class UserDataPage(Adw.BreakpointBin): self.adp.sort_mode = "size" self.ldp.sort_mode = "size" - self.adp.flow_box.invalidate_sort() - self.ldp.flow_box.invalidate_sort() + if should_sort: + self.adp.flow_box.invalidate_sort() + self.ldp.flow_box.invalidate_sort() # def bpt_handler(self, _, is_applied): # if is_applied and self.adj.get_value() == 0: @@ -94,10 +95,16 @@ class UserDataPage(Adw.BreakpointBin): self.ldp.spinner.set_visible(True) self.ldp.flow_box.remove_all() + self.sort_handler(self.asc, False) + self.sort_handler(self.dsc, False) + self.sort_handler(self.sort_name, False) + self.sort_handler(self.sort_id, False) + self.sort_handler(self.sort_size, False) + def end_loading(self, *args): def callback(*args): - self.adp.generate_list(self.data_flatpaks, self.active_data, "size") - self.ldp.generate_list([], self.leftover_data, "name") + self.adp.generate_list(self.data_flatpaks, self.active_data) + self.ldp.generate_list([], self.leftover_data) Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 775fd45..fdccf7a 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -54,5 +54,10 @@ ../data/icons/vertical-arrows-long-symbolic.svg ../data/icons/dot-symbolic.svg ../data/icons/folder-templates-symbolic.svg + ../data/icons/view-sort-ascending-symbolic.svg + ../data/icons/view-sort-descending-symbolic.svg + ../data/icons/font-x-generic-symbolic.svg + ../data/icons/tag-outline-symbolic.svg + ../data/icons/harddisk-symbolic.svg From 92b856ad8f11506e05d5384ac72ce7d913a9dc7e Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 11:49:42 -0400 Subject: [PATCH 074/332] Set total size label upon done loading --- src/user_data_page/data_box.py | 11 +++++----- src/user_data_page/data_subpage.py | 34 +++++++++++------------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 6ae259a..007e4c4 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -15,13 +15,14 @@ class DataBox(Gtk.ListBox): check_button = gtc() def human_readable_size(self): + working_size = self.size units = ['KB', 'MB', 'GB', 'TB'] # size *= 1024 for unit in units: - if self.size < 1024: - return f"~ {round(self.size)} {unit}" - self.size /= 1024 - return f"~ {round(self.size)} PB" + if working_size < 1024: + return f"~ {round(working_size)} {unit}" + working_size /= 1024 + return f"~ {round(working_size)} PB" def get_size(self, *args): self.size = int(subprocess.run(['du', '-s', self.data_path], capture_output=True, text=True).stdout.split("\t")[0]) @@ -31,7 +32,7 @@ class DataBox(Gtk.ListBox): self.size_label.set_label(self.human_readable_size()) self.spinner.set_visible(False) if self.callback: - self.callback() + self.callback(self.size) Gio.Task.new(None, None, callback).run_in_thread(self.get_size) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 2563ced..409314a 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -16,29 +16,15 @@ class DataSubpage(Gtk.ScrolledWindow): size_label = gtc() flow_box = gtc() - def human_readable_size(self, size): + def human_readable_size(self): + working_size = self.total_size units = ['KB', 'MB', 'GB', 'TB'] # size *= 1024 for unit in units: - if size < 1024: - return f"~ {round(size)} {unit}" - size /= 1024 - return f"~ {round(size)} PB" - - def get_size(self, path): - sed = "sed 's/K/ KB/; s/M/ MB/; s/G/ GB/; s/T/ TB/; s/P/ PB/;'" - return int(subprocess.run(['du', '-s', path], capture_output=True, text=True).stdout.split("\t")[0]) - - def show_size(self, data): - def thread(*args): - for folder in data: - self.total_size += self.get_size(f"{HostInfo.home}/.var/app/{folder}") - - def callback(*args): - self.size_label.set_label(self.human_readable_size(self.total_size)) - self.spinner.set_visible(False) - - Gio.Task.new(None, None, callback).run_in_thread(thread) + if working_size < 1024: + return f"~ {round(working_size)} {unit}" + working_size /= 1024 + return f"~ {round(working_size)} PB" def sort_func(self, box1, box2): i1 = None @@ -60,9 +46,12 @@ class DataSubpage(Gtk.ScrolledWindow): return i1 > i2 if self.sort_ascend else i1 < i2 - def box_size_callback(self): + def box_size_callback(self, size): self.finished_boxes += 1 + self.total_size += size if self.finished_boxes == self.total_items: + self.size_label.set_label(self.human_readable_size()) + self.spinner.set_visible(False) self.ready_to_sort_size = True self.flow_box.invalidate_sort() @@ -70,6 +59,7 @@ class DataSubpage(Gtk.ScrolledWindow): self.boxes.clear() self.ready_to_sort_size = False self.finished_boxes = 0 + self.total_size = 0 self.total_items = len(data) self.subtitle.set_label(_("{} Items").format(self.total_items)) if flatpaks: @@ -79,7 +69,7 @@ class DataSubpage(Gtk.ScrolledWindow): self.flow_box.append(box) else: for i, folder in enumerate(data): - self.flow_box.append(DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}")) + self.flow_box.append(DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback)) def __init__(self, title, main_window, **kwargs): super().__init__(**kwargs) From f2246064c095079d21006f78fee16d4126b4ab49 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 12:13:12 -0400 Subject: [PATCH 075/332] Make tabbing through items predictable --- src/user_data_page/data_box.py | 1 + src/user_data_page/data_subpage.py | 10 ++++++++++ src/user_data_page/user_data_page.py | 2 ++ 3 files changed, 13 insertions(+) diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 007e4c4..9dddaa3 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -7,6 +7,7 @@ class DataBox(Gtk.ListBox): __gtype_name__ = 'DataBox' gtc = Gtk.Template.Child + row = gtc() image = gtc() title_label = gtc() subtitle_label = gtc() diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 409314a..f931e9d 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -70,6 +70,16 @@ class DataSubpage(Gtk.ScrolledWindow): else: for i, folder in enumerate(data): self.flow_box.append(DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback)) + child = self.flow_box.get_child_at_index(i) + child.set_focusable(False) + + idx = 0 + while box := self.flow_box.get_child_at_index(idx): + idx += 1 + box.set_focusable(False) + child = box.get_child() + child.set_focusable(False) + child.row.set_focusable(child.check_button.get_visible()) def __init__(self, title, main_window, **kwargs): super().__init__(**kwargs) diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 66f8e8e..b2bfc4f 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -13,6 +13,7 @@ class UserDataPage(Adw.BreakpointBin): header_bar = gtc() switcher_bar = gtc() sidebar_button = gtc() + search_button = gtc() select_button = gtc() stack = gtc() sort_pop = gtc() @@ -105,6 +106,7 @@ class UserDataPage(Adw.BreakpointBin): def callback(*args): self.adp.generate_list(self.data_flatpaks, self.active_data) self.ldp.generate_list([], self.leftover_data) + self.search_button.grab_focus() Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) From 6a7f770e131f5a1239591396db1c68e7c5f0e93c Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 12:26:13 -0400 Subject: [PATCH 076/332] add searching --- src/user_data_page/data_subpage.py | 15 +++++++++++++-- src/user_data_page/user_data_page.py | 5 +++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index f931e9d..eb89058 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -81,7 +81,15 @@ class DataSubpage(Gtk.ScrolledWindow): child.set_focusable(False) child.row.set_focusable(child.check_button.get_visible()) - def __init__(self, title, main_window, **kwargs): + def filter_func(self, box): + search_text = self.parent_page.search_entry.get_text().lower() + box = box.get_child() + return search_text in box.title.lower() or search_text in box.subtitle.lower() + + def on_invalidate(self, box): + self.flow_box.invalidate_filter() + + def __init__(self, title, parent_page, main_window, **kwargs): super().__init__(**kwargs) GLib.idle_add(lambda *_: self.title.set_label(title)) @@ -91,6 +99,7 @@ class DataSubpage(Gtk.ScrolledWindow): # Extra Object Creation self.main_window = main_window + self.parent_page = parent_page self.sort_mode = "" self.sort_ascend = False self.total_size = 0 @@ -101,5 +110,7 @@ class DataSubpage(Gtk.ScrolledWindow): # Apply self.flow_box.set_sort_func(self.sort_func) + self.flow_box.set_filter_func(self.filter_func) - # Connections \ No newline at end of file + # Connections + parent_page.search_entry.connect("search-changed", self.on_invalidate) \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index b2bfc4f..48fef62 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -15,6 +15,7 @@ class UserDataPage(Adw.BreakpointBin): sidebar_button = gtc() search_button = gtc() select_button = gtc() + search_entry = gtc() stack = gtc() sort_pop = gtc() asc = gtc() @@ -116,8 +117,8 @@ class UserDataPage(Adw.BreakpointBin): # Extra Object Creation self.__class__.instance = self # self.adj = self.scrolled_window.get_vadjustment() - self.adp = DataSubpage(_("Active Data"), main_window) - self.ldp = DataSubpage(_("Leftover Data"), main_window) + self.adp = DataSubpage(_("Active Data"), self, main_window) + self.ldp = DataSubpage(_("Leftover Data"), self, main_window) self.data_flatpaks = [] self.active_data = [] self.leftover_data = [] From 6ac69bb213d62c4764ccd579c8b438101dcedbb5 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 16:20:20 -0400 Subject: [PATCH 077/332] make the UI more pwetty :3 --- src/user_data_page/data_subpage.blp | 50 +++++----- src/user_data_page/data_subpage.py | 22 ++++- src/user_data_page/user_data_page.blp | 127 +++++++++++++++++++++----- src/user_data_page/user_data_page.py | 114 +++++++++++++---------- 4 files changed, 217 insertions(+), 96 deletions(-) diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp index d386c7e..5162185 100644 --- a/src/user_data_page/data_subpage.blp +++ b/src/user_data_page/data_subpage.blp @@ -1,21 +1,23 @@ using Gtk 4.0; using Adw 1; -template $DataSubpage : ScrolledWindow { - Box { +template $DataSubpage : Box { + Box outer_box { orientation: vertical; - Box { - orientation: vertical; + Box label_box { margin-start: 24; margin-end: 24; + halign: fill; + hexpand: true; Label title { label: _("No Title Set"); styles ["title-1"] hexpand: true; + justify: fill; halign: start; wrap: true; } - Box { + Box subtitle_size_box { Label subtitle { label: "No Subtutle Set"; styles ["title-3"] @@ -37,27 +39,33 @@ template $DataSubpage : ScrolledWindow { Label size_label { label: _("Loading Size…"); styles ["title-3"] - hexpand: true; halign: start; wrap: true; } } + margin-bottom: 9; } - Separator { - margin-start: 12; - margin-end: 12; - margin-top: 9; - margin-bottom: 6; - } - FlowBox flow_box { - styles ["boxed-list"] - homogeneous: true; - valign: start; - selection-mode: none; - max-children-per-line: 6; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; + ScrolledWindow scrolled_window { + Box { + orientation: vertical; + vexpand: true; + Separator { + margin-start: 12; + margin-end: 12; + // margin-top: 9; + margin-bottom: 6; + } + FlowBox flow_box { + styles ["boxed-list"] + homogeneous: true; + valign: start; + selection-mode: none; + max-children-per-line: 6; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + } + } } } } \ No newline at end of file diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index eb89058..dbe4ade 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -6,10 +6,15 @@ from .host_info import HostInfo import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/data_subpage.ui") -class DataSubpage(Gtk.ScrolledWindow): +class DataSubpage(Gtk.Box): __gtype_name__ = 'DataSubpage' gtc = Gtk.Template.Child + scrolled_window = gtc() + + outer_box = gtc() + label_box = gtc() + subtitle_size_box = gtc() title = gtc() subtitle = gtc() spinner = gtc() @@ -62,6 +67,7 @@ class DataSubpage(Gtk.ScrolledWindow): self.total_size = 0 self.total_items = len(data) self.subtitle.set_label(_("{} Items").format(self.total_items)) + self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width if flatpaks: for i, pak in enumerate(flatpaks): box = DataBox(pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback) @@ -89,6 +95,14 @@ class DataSubpage(Gtk.ScrolledWindow): def on_invalidate(self, box): self.flow_box.invalidate_filter() + def label_orientation_handler(self, adj): + current_page_width = adj.get_upper() - 24 + + if self.label_box.get_allocated_width() < self.min_horizontal_label_width: + GLib.idle_add(lambda *_: self.label_box.set_orientation(Gtk.Orientation.VERTICAL)) + else: + GLib.idle_add(lambda *_: self.label_box.set_orientation(Gtk.Orientation.HORIZONTAL)) + def __init__(self, title, parent_page, main_window, **kwargs): super().__init__(**kwargs) @@ -107,10 +121,14 @@ class DataSubpage(Gtk.ScrolledWindow): self.boxes = [] self.ready_to_sort_size = False self.finished_boxes = 0 + self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width # Apply self.flow_box.set_sort_func(self.sort_func) self.flow_box.set_filter_func(self.filter_func) # Connections - parent_page.search_entry.connect("search-changed", self.on_invalidate) \ No newline at end of file + parent_page.search_entry.connect("search-changed", self.on_invalidate) + + # self.title.get_preferred_size()[1].width + self.subtitle.get_preferred_size()[1].width + self.scrolled_window.get_hadjustment().connect("changed", self.label_orientation_handler) \ No newline at end of file diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 749fd5d..e5ad00f 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -28,7 +28,7 @@ template $UserDataPage : Adw.BreakpointBin { } ; [start] - Button sidebar_button { + ToggleButton sidebar_button { icon-name: "dock-left-symbolic"; tooltip-text: _("Show Sidebar"); } @@ -38,13 +38,26 @@ template $UserDataPage : Adw.BreakpointBin { tooltip-text: _("Search User Data"); } [end] - MenuButton sort_button { - popover: sort_pop; + MenuButton active_sort_button { + popover: active_sort_pop; icon-name: "vertical-arrows-long-symbolic"; tooltip-text: _("Sort User Data"); } [end] - ToggleButton select_button { + ToggleButton active_select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select User Data"); + } + [end] + MenuButton leftover_sort_button { + visible: false; + popover: leftover_sort_pop; + icon-name: "vertical-arrows-long-symbolic"; + tooltip-text: _("Sort User Data"); + } + [end] + ToggleButton leftover_select_button { + visible: false; icon-name: "selection-mode-symbolic"; tooltip-text: _("Select User Data"); } @@ -63,7 +76,6 @@ template $UserDataPage : Adw.BreakpointBin { } [bottom] Revealer { - reveal-child: bind select_button.active; transition-type: slide_up; [center] Box bottom_bar { @@ -107,7 +119,7 @@ template $UserDataPage : Adw.BreakpointBin { } } -Popover sort_pop { +Popover active_sort_pop { styles ["menu"] Box { orientation: vertical; @@ -118,21 +130,21 @@ Popover sort_pop { Box { homogeneous: true; spacing: 3; - ToggleButton asc { + ToggleButton active_asc { active: true; styles ["flat"] - can-focus: bind asc.active inverted; - can-target: bind asc.active inverted; + can-focus: bind active_asc.active inverted; + can-target: bind active_asc.active inverted; Adw.ButtonContent { icon-name: "view-sort-ascending-symbolic"; label: _("Ascending"); } } - ToggleButton dsc { + ToggleButton active_dsc { styles ["flat"] - active: bind asc.active inverted bidirectional; - can-focus: bind dsc.active inverted; - can-target: bind dsc.active inverted; + active: bind active_asc.active inverted bidirectional; + can-focus: bind active_dsc.active inverted; + can-target: bind active_dsc.active inverted; Adw.ButtonContent { icon-name: "view-sort-descending-symbolic"; label: _("Descending"); @@ -141,31 +153,100 @@ Popover sort_pop { } Separator { } - Box sort_list { + Box { homogeneous: true; spacing: 3; - ToggleButton sort_name { + ToggleButton active_sort_name { active: true; styles ["flat"] - can-focus: bind sort_name.active inverted; - can-target: bind sort_name.active inverted; + can-focus: bind active_sort_name.active inverted; + can-target: bind active_sort_name.active inverted; Adw.ButtonContent { icon-name: "font-x-generic-symbolic"; label: _("Name"); } } - ToggleButton sort_id { + ToggleButton active_sort_id { styles ["flat"] - can-focus: bind sort_id.active inverted; - can-target: bind sort_id.active inverted; + can-focus: bind active_sort_id.active inverted; + can-target: bind active_sort_id.active inverted; Adw.ButtonContent { icon-name: "tag-outline-symbolic"; label: _("ID"); } } - ToggleButton sort_size { - can-focus: bind sort_size.active inverted; - can-target: bind sort_size.active inverted; + ToggleButton active_sort_size { + can-focus: bind active_sort_size.active inverted; + can-target: bind active_sort_size.active inverted; + styles ["flat"] + Adw.ButtonContent { + icon-name: "harddisk-symbolic"; + label: _("Size"); + } + } + } + } +} + +Popover leftover_sort_pop { + styles ["menu"] + Box { + orientation: vertical; + margin-start: 6; + margin-end: 6; + margin-top: 6; + margin-bottom: 6; + Box { + homogeneous: true; + spacing: 3; + ToggleButton leftover_asc { + active: true; + styles ["flat"] + can-focus: bind leftover_asc.active inverted; + can-target: bind leftover_asc.active inverted; + Adw.ButtonContent { + icon-name: "view-sort-ascending-symbolic"; + label: _("Ascending"); + } + } + ToggleButton leftover_dsc { + styles ["flat"] + active: bind leftover_asc.active inverted bidirectional; + can-focus: bind leftover_dsc.active inverted; + can-target: bind leftover_dsc.active inverted; + Adw.ButtonContent { + icon-name: "view-sort-descending-symbolic"; + label: _("Descending"); + } + } + } + Separator { + } + Box { + homogeneous: true; + spacing: 3; + ToggleButton leftover_sort_name { + active: true; + styles ["flat"] + can-focus: bind leftover_sort_name.active inverted; + can-target: bind leftover_sort_name.active inverted; + Adw.ButtonContent { + icon-name: "font-x-generic-symbolic"; + label: _("Name"); + } + } + ToggleButton leftover_sort_id { + styles ["flat"] + can-focus: bind leftover_sort_id.active inverted; + can-target: bind leftover_sort_id.active inverted; + Adw.ButtonContent { + icon-name: "tag-outline-symbolic"; + label: _("ID"); + } + } + ToggleButton leftover_sort_size { + can-focus: bind leftover_sort_size.active inverted; + can-target: bind leftover_sort_size.active inverted; styles ["flat"] Adw.ButtonContent { icon-name: "harddisk-symbolic"; diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 48fef62..cef8a4a 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -14,55 +14,58 @@ class UserDataPage(Adw.BreakpointBin): switcher_bar = gtc() sidebar_button = gtc() search_button = gtc() - select_button = gtc() + active_select_button = gtc() + active_sort_button = gtc() + leftover_select_button = gtc() + leftover_sort_button = gtc() search_entry = gtc() stack = gtc() - sort_pop = gtc() - asc = gtc() - dsc = gtc() - sort_list = gtc() - sort_name = gtc() - sort_id = gtc() - sort_size = gtc() + # sort_pop = gtc() + # asc = gtc() + # dsc = gtc() + # sort_list = gtc() + # sort_name = gtc() + # sort_id = gtc() + # sort_size = 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 - def sort_handler(self, button, should_sort=True): - if not button.get_active(): - return + # def sort_handler(self, button, should_sort=True): + # if not button.get_active(): + # return - match button: - case self.asc: - self.adp.sort_ascend = True - self.ldp.sort_ascend = True - case self.dsc: - self.adp.sort_ascend = False - self.ldp.sort_ascend = False - case self.sort_name: - self.sort_id.grab_focus() - self.sort_id.set_active(False) - self.sort_size.set_active(False) - self.adp.sort_mode = "name" - self.ldp.sort_mode = "name" - case self.sort_id: - self.sort_size.grab_focus() - self.sort_size.set_active(False) - self.sort_name.set_active(False) - self.adp.sort_mode = "id" - self.ldp.sort_mode = "id" - case self.sort_size: - self.sort_name.grab_focus() - self.sort_name.set_active(False) - self.sort_id.set_active(False) - self.adp.sort_mode = "size" - self.ldp.sort_mode = "size" + # match button: + # case self.asc: + # self.adp.sort_ascend = True + # self.ldp.sort_ascend = True + # case self.dsc: + # self.adp.sort_ascend = False + # self.ldp.sort_ascend = False + # case self.sort_name: + # self.sort_id.grab_focus() + # self.sort_id.set_active(False) + # self.sort_size.set_active(False) + # self.adp.sort_mode = "name" + # self.ldp.sort_mode = "name" + # case self.sort_id: + # self.sort_size.grab_focus() + # self.sort_size.set_active(False) + # self.sort_name.set_active(False) + # self.adp.sort_mode = "id" + # self.ldp.sort_mode = "id" + # case self.sort_size: + # self.sort_name.grab_focus() + # self.sort_name.set_active(False) + # self.sort_id.set_active(False) + # self.adp.sort_mode = "size" + # self.ldp.sort_mode = "size" - if should_sort: - self.adp.flow_box.invalidate_sort() - self.ldp.flow_box.invalidate_sort() + # if should_sort: + # self.adp.flow_box.invalidate_sort() + # self.ldp.flow_box.invalidate_sort() # def bpt_handler(self, _, is_applied): # if is_applied and self.adj.get_value() == 0: @@ -97,11 +100,11 @@ class UserDataPage(Adw.BreakpointBin): self.ldp.spinner.set_visible(True) self.ldp.flow_box.remove_all() - self.sort_handler(self.asc, False) - self.sort_handler(self.dsc, False) - self.sort_handler(self.sort_name, False) - self.sort_handler(self.sort_id, False) - self.sort_handler(self.sort_size, False) + # self.sort_handler(self.asc, False) + # self.sort_handler(self.dsc, False) + # self.sort_handler(self.sort_name, False) + # self.sort_handler(self.sort_id, False) + # self.sort_handler(self.sort_size, False) def end_loading(self, *args): def callback(*args): @@ -111,6 +114,12 @@ class UserDataPage(Adw.BreakpointBin): Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) + def switch_view_handler(self, page): + self.active_select_button.set_visible(page is self.adp) + self.active_sort_button.set_visible(page is self.adp) + self.leftover_select_button.set_visible(page is self.ldp) + self.leftover_sort_button.set_visible(page is self.ldp) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -123,6 +132,7 @@ class UserDataPage(Adw.BreakpointBin): self.active_data = [] self.leftover_data = [] self.total_items = 0 + ms=main_window.main_split # Apply self.stack.add_titled_with_icon( @@ -137,14 +147,18 @@ class UserDataPage(Adw.BreakpointBin): title=_("Leftover Data"), icon_name="folder-templates-symbolic", ) + self.sidebar_button.set_active(ms.get_show_sidebar()) # Connections - self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) + # self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) + main_window.main_split.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) + self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + self.stack.connect("notify::visible-child", lambda *_: self.switch_view_handler(self.stack.get_visible_child())) # self.adj.connect("value-changed", self.show_title_handler) - self.asc.connect("toggled", self.sort_handler) - self.dsc.connect("toggled", self.sort_handler) - self.sort_name.connect("toggled", self.sort_handler) - self.sort_id.connect("toggled", self.sort_handler) - self.sort_size.connect("toggled", self.sort_handler) + # self.asc.connect("toggled", self.sort_handler) + # self.dsc.connect("toggled", self.sort_handler) + # self.sort_name.connect("toggled", self.sort_handler) + # self.sort_id.connect("toggled", self.sort_handler) + # self.sort_size.connect("toggled", self.sort_handler) # self.bpt.connect("apply", self.bpt_handler, True) # self.bpt.connect("unapply", self.bpt_handler, False) \ No newline at end of file From 7d5e3dd40dd08ca92a8076d22e5b0379159b7b3d Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 20:32:34 -0400 Subject: [PATCH 078/332] Finalize sorting --- src/user_data_page/data_subpage.py | 5 +- src/user_data_page/user_data_page.blp | 28 ++---- src/user_data_page/user_data_page.py | 125 ++++++++++++-------------- 3 files changed, 68 insertions(+), 90 deletions(-) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index dbe4ade..054b1ce 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -32,6 +32,8 @@ class DataSubpage(Gtk.Box): return f"~ {round(working_size)} PB" def sort_func(self, box1, box2): + import random + # print(random.randint(1, 100), self.sort_mode, self.sort_ascend) i1 = None i2 = None if self.sort_mode == "name": @@ -75,7 +77,8 @@ class DataSubpage(Gtk.Box): self.flow_box.append(box) else: for i, folder in enumerate(data): - self.flow_box.append(DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback)) + box = DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback) + self.flow_box.append(box) child = self.flow_box.get_child_at_index(i) child.set_focusable(False) diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index e5ad00f..2fef283 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -133,18 +133,14 @@ Popover active_sort_pop { ToggleButton active_asc { active: true; styles ["flat"] - can-focus: bind active_asc.active inverted; - can-target: bind active_asc.active inverted; Adw.ButtonContent { icon-name: "view-sort-ascending-symbolic"; label: _("Ascending"); } } ToggleButton active_dsc { + group: active_asc; styles ["flat"] - active: bind active_asc.active inverted bidirectional; - can-focus: bind active_dsc.active inverted; - can-target: bind active_dsc.active inverted; Adw.ButtonContent { icon-name: "view-sort-descending-symbolic"; label: _("Descending"); @@ -159,25 +155,21 @@ Popover active_sort_pop { ToggleButton active_sort_name { active: true; styles ["flat"] - can-focus: bind active_sort_name.active inverted; - can-target: bind active_sort_name.active inverted; Adw.ButtonContent { icon-name: "font-x-generic-symbolic"; label: _("Name"); } } ToggleButton active_sort_id { + group: active_sort_name; styles ["flat"] - can-focus: bind active_sort_id.active inverted; - can-target: bind active_sort_id.active inverted; Adw.ButtonContent { icon-name: "tag-outline-symbolic"; label: _("ID"); } } ToggleButton active_sort_size { - can-focus: bind active_sort_size.active inverted; - can-target: bind active_sort_size.active inverted; + group: active_sort_name; styles ["flat"] Adw.ButtonContent { icon-name: "harddisk-symbolic"; @@ -202,18 +194,14 @@ Popover leftover_sort_pop { ToggleButton leftover_asc { active: true; styles ["flat"] - can-focus: bind leftover_asc.active inverted; - can-target: bind leftover_asc.active inverted; Adw.ButtonContent { icon-name: "view-sort-ascending-symbolic"; label: _("Ascending"); } } ToggleButton leftover_dsc { + group: leftover_asc; styles ["flat"] - active: bind leftover_asc.active inverted bidirectional; - can-focus: bind leftover_dsc.active inverted; - can-target: bind leftover_dsc.active inverted; Adw.ButtonContent { icon-name: "view-sort-descending-symbolic"; label: _("Descending"); @@ -228,25 +216,21 @@ Popover leftover_sort_pop { ToggleButton leftover_sort_name { active: true; styles ["flat"] - can-focus: bind leftover_sort_name.active inverted; - can-target: bind leftover_sort_name.active inverted; Adw.ButtonContent { icon-name: "font-x-generic-symbolic"; label: _("Name"); } } ToggleButton leftover_sort_id { + group: leftover_sort_name; styles ["flat"] - can-focus: bind leftover_sort_id.active inverted; - can-target: bind leftover_sort_id.active inverted; Adw.ButtonContent { icon-name: "tag-outline-symbolic"; label: _("ID"); } } ToggleButton leftover_sort_size { - can-focus: bind leftover_sort_size.active inverted; - can-target: bind leftover_sort_size.active inverted; + group: leftover_sort_name; styles ["flat"] Adw.ButtonContent { icon-name: "harddisk-symbolic"; diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index cef8a4a..c246496 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -20,65 +20,25 @@ class UserDataPage(Adw.BreakpointBin): leftover_sort_button = gtc() search_entry = gtc() stack = gtc() - # sort_pop = gtc() - # asc = gtc() - # dsc = gtc() - # sort_list = gtc() - # sort_name = gtc() - # sort_id = gtc() - # sort_size = gtc() + + active_asc = gtc() + active_dsc = gtc() + active_sort_name = gtc() + active_sort_id = gtc() + active_sort_size = gtc() + + leftover_asc = gtc() + leftover_dsc = gtc() + leftover_sort_name = gtc() + leftover_sort_id = gtc() + leftover_sort_size = 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 - # def sort_handler(self, button, should_sort=True): - # if not button.get_active(): - # return - - # match button: - # case self.asc: - # self.adp.sort_ascend = True - # self.ldp.sort_ascend = True - # case self.dsc: - # self.adp.sort_ascend = False - # self.ldp.sort_ascend = False - # case self.sort_name: - # self.sort_id.grab_focus() - # self.sort_id.set_active(False) - # self.sort_size.set_active(False) - # self.adp.sort_mode = "name" - # self.ldp.sort_mode = "name" - # case self.sort_id: - # self.sort_size.grab_focus() - # self.sort_size.set_active(False) - # self.sort_name.set_active(False) - # self.adp.sort_mode = "id" - # self.ldp.sort_mode = "id" - # case self.sort_size: - # self.sort_name.grab_focus() - # self.sort_name.set_active(False) - # self.sort_id.set_active(False) - # self.adp.sort_mode = "size" - # self.ldp.sort_mode = "size" - - # if should_sort: - # self.adp.flow_box.invalidate_sort() - # self.ldp.flow_box.invalidate_sort() - - # def bpt_handler(self, _, is_applied): - # if is_applied and self.adj.get_value() == 0: - # self.header_bar.set_show_title(False) - # else: - # self.header_bar.set_show_title(True) - - # def show_title_handler(self, *args): - # if self.adj.get_value() != 0: - # self.header_bar.set_show_title(True) - # elif self.switcher_bar.get_reveal(): - # self.header_bar.set_show_title(False) - def sort_data(self, *args): self.data_flatpaks.clear() self.active_data.clear() @@ -100,12 +60,6 @@ class UserDataPage(Adw.BreakpointBin): self.ldp.spinner.set_visible(True) self.ldp.flow_box.remove_all() - # self.sort_handler(self.asc, False) - # self.sort_handler(self.dsc, False) - # self.sort_handler(self.sort_name, False) - # self.sort_handler(self.sort_id, False) - # self.sort_handler(self.sort_size, False) - def end_loading(self, *args): def callback(*args): self.adp.generate_list(self.data_flatpaks, self.active_data) @@ -115,6 +69,7 @@ class UserDataPage(Adw.BreakpointBin): Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) def switch_view_handler(self, page): + print('test') self.active_select_button.set_visible(page is self.adp) self.active_sort_button.set_visible(page is self.adp) self.leftover_select_button.set_visible(page is self.ldp) @@ -154,11 +109,47 @@ class UserDataPage(Adw.BreakpointBin): main_window.main_split.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.stack.connect("notify::visible-child", lambda *_: self.switch_view_handler(self.stack.get_visible_child())) - # self.adj.connect("value-changed", self.show_title_handler) - # self.asc.connect("toggled", self.sort_handler) - # self.dsc.connect("toggled", self.sort_handler) - # self.sort_name.connect("toggled", self.sort_handler) - # self.sort_id.connect("toggled", self.sort_handler) - # self.sort_size.connect("toggled", self.sort_handler) - # self.bpt.connect("apply", self.bpt_handler, True) - # self.bpt.connect("unapply", self.bpt_handler, False) \ No newline at end of file + + def sorter(button=None): + if button and not button.get_active(): + return + + if self.active_sort_name.get_active(): + self.adp.sort_mode = "name" + elif self.active_sort_id.get_active(): + self.adp.sort_mode = "id" + elif self.active_sort_size.get_active(): + self.adp.sort_mode = "size" + + if self.leftover_sort_name.get_active(): + self.ldp.sort_mode = "name" + elif self.leftover_sort_id.get_active(): + self.ldp.sort_mode = "id" + elif self.leftover_sort_size.get_active(): + self.ldp.sort_mode = "size" + + self.adp.sort_ascend = self.active_asc.get_active() + self.ldp.sort_ascend = self.leftover_asc.get_active() + + self.adp.flow_box.invalidate_sort() + self.ldp.flow_box.invalidate_sort() + + self.active_asc.connect("clicked", sorter) + self.active_dsc.connect("clicked", sorter) + self.active_sort_name.connect("clicked", sorter) + self.active_sort_id.connect("clicked", sorter) + self.active_sort_size.connect("clicked", sorter) + + self.leftover_asc.connect("clicked", sorter) + self.leftover_dsc.connect("clicked", sorter) + self.leftover_sort_name.connect("clicked", sorter) + self.leftover_sort_id.connect("clicked", sorter) + self.leftover_sort_size.connect("clicked", sorter) + + sorter() + + def thingie(*args): + while True: + print(self.leftover_sort_id.get_active()) + + # Gio.Task().run_in_thread(thingie) \ No newline at end of file From 5063dade2ff671ba47f389307d164e6cc6824f37 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 20:58:15 -0400 Subject: [PATCH 079/332] Change pckages page to use gtk's listbox sorting --- src/host_info.py | 3 --- src/packages_page/packages_page.py | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index dbf1395..e2f61ae 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -357,7 +357,4 @@ class HostInfo: if not runtime in this.dependant_runtime_refs: this.dependant_runtime_refs.append(runtime) - this.flatpaks = sorted(this.flatpaks, key=lambda flatpak: flatpak.info["name"].lower()) - this.dependant_runtime_refs = sorted(this.dependant_runtime_refs) - Gio.Task.new(None, None, callback).run_in_thread(thread) \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 74319fe..f147221 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -273,6 +273,9 @@ class PackagesPage(Adw.BreakpointBin): else: self.set_status(self.no_results) + def sort_func(self, row1, row2): + return row1.package.info["name"] > row2.package.info["name"] + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -288,6 +291,7 @@ class PackagesPage(Adw.BreakpointBin): # Apply # self.set_status("loading_packages") self.packages_list_box.set_filter_func(self.filter_func) + self.packages_list_box.set_sort_func(self.sort_func) self.content_stack.add_child(self.properties_page) self.content_stack.add_child(self.filters_page) self.__class__.instance = self From e092ed8c3e4c5e790b82e8dea2b01a0335579e42 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 21:41:36 -0400 Subject: [PATCH 080/332] Make the UI better --- src/main_window/window.py | 3 ++- src/user_data_page/data_subpage.blp | 24 ++++++++++++------------ src/user_data_page/user_data_page.blp | 2 +- src/user_data_page/user_data_page.py | 20 +++++++++++--------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/main_window/window.py b/src/main_window/window.py index 1197e6d..f392fb9 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -54,8 +54,9 @@ class WarehouseWindow(Adw.ApplicationWindow): def navigation_handler(self, _, row, hide_sidebar=True): row = row.get_child() page = self.pages[row] - self.stack.set_visible_child(page) + if self.main_split.get_collapsed(): + self.main_split.set_show_sidebar(False) def start_loading(self, *args): for _, page in self.pages.items(): diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp index 5162185..152ab2f 100644 --- a/src/user_data_page/data_subpage.blp +++ b/src/user_data_page/data_subpage.blp @@ -18,18 +18,6 @@ template $DataSubpage : Box { wrap: true; } Box subtitle_size_box { - Label subtitle { - label: "No Subtutle Set"; - styles ["title-3"] - wrap: true; - } - Image { - icon-name: "dot-symbolic"; - margin-start: 6; - margin-end: 6; - margin-top: 3; - valign: center; - } Spinner spinner { spinning: true; valign: center; @@ -42,6 +30,18 @@ template $DataSubpage : Box { halign: start; wrap: true; } + Image { + icon-name: "dot-symbolic"; + margin-start: 6; + margin-end: 6; + margin-top: 3; + valign: center; + } + Label subtitle { + label: "No Subtutle Set"; + styles ["title-3"] + wrap: true; + } } margin-bottom: 9; } diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 2fef283..b773935 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -75,7 +75,7 @@ template $UserDataPage : Adw.BreakpointBin { } } [bottom] - Revealer { + Revealer revealer { transition-type: slide_up; [center] Box bottom_bar { diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index c246496..e8e5653 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -20,6 +20,7 @@ class UserDataPage(Adw.BreakpointBin): leftover_sort_button = gtc() search_entry = gtc() stack = gtc() + revealer = gtc() active_asc = gtc() active_dsc = gtc() @@ -69,11 +70,16 @@ class UserDataPage(Adw.BreakpointBin): Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) def switch_view_handler(self, page): - print('test') self.active_select_button.set_visible(page is self.adp) self.active_sort_button.set_visible(page is self.adp) self.leftover_select_button.set_visible(page is self.ldp) self.leftover_sort_button.set_visible(page is self.ldp) + self.active_select_button.set_active(False) + self.leftover_select_button.set_active(False) + self.revealer_handler() + + def revealer_handler(self, *args): + self.revealer.set_reveal_child(self.active_select_button.get_active() or self.leftover_select_button.get_active()) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -109,6 +115,8 @@ class UserDataPage(Adw.BreakpointBin): main_window.main_split.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.stack.connect("notify::visible-child", lambda *_: self.switch_view_handler(self.stack.get_visible_child())) + self.active_select_button.connect("toggled", self.revealer_handler) + self.leftover_select_button.connect("toggled", self.revealer_handler) def sorter(button=None): if button and not button.get_active(): @@ -127,7 +135,7 @@ class UserDataPage(Adw.BreakpointBin): self.ldp.sort_mode = "id" elif self.leftover_sort_size.get_active(): self.ldp.sort_mode = "size" - + self.adp.sort_ascend = self.active_asc.get_active() self.ldp.sort_ascend = self.leftover_asc.get_active() @@ -146,10 +154,4 @@ class UserDataPage(Adw.BreakpointBin): self.leftover_sort_id.connect("clicked", sorter) self.leftover_sort_size.connect("clicked", sorter) - sorter() - - def thingie(*args): - while True: - print(self.leftover_sort_id.get_active()) - - # Gio.Task().run_in_thread(thingie) \ No newline at end of file + sorter() \ No newline at end of file From b377856e7a20540723528faf133b700387f80e4d Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 22:35:00 -0400 Subject: [PATCH 081/332] Make the data boxes do shit --- src/host_info.py | 2 +- src/user_data_page/data_box.py | 51 ++++++++++++++++++++++++++- src/user_data_page/data_subpage.py | 4 +-- src/user_data_page/user_data_page.blp | 4 ++- src/user_data_page/user_data_page.py | 1 + 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index e2f61ae..38588da 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -44,7 +44,7 @@ class Flatpak: def trash_data(self, callback=None): try: - subprocess.run(['gio', 'trash', f"{self.data_path}"], capture_output=True, text=True, check=True) + subprocess.run(['gio', 'trash', self.data_path], capture_output=True, text=True, check=True) except subprocess.CalledProcessError as cpe: raise cpe except Exception as e: diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 9dddaa3..5a8b46c 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -1,5 +1,6 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .host_info import HostInfo +from .error_toast import ErrorToast import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/data_box.ui") @@ -13,6 +14,10 @@ class DataBox(Gtk.ListBox): subtitle_label = gtc() spinner = gtc() size_label = gtc() + + copy_button = gtc() + open_button = gtc() + trash_button = gtc() check_button = gtc() def human_readable_size(self): @@ -44,15 +49,59 @@ class DataBox(Gtk.ListBox): self.image.add_css_class("icon-dropshadow") self.image.set_from_file(self.icon_path) - def __init__(self, title, subtitle, data_path, icon_path=None, callback=None, **kwargs): + def copy_handler(self, *args): + try: + HostInfo.clipboard.set(self.data_path) + self.toast_overlay.add_toast(Adw.Toast.new(_("Copied data path"))) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not copy data path"), str(e)).toast) + + def open_handler(self, *args): + try: + Gio.AppInfo.launch_default_for_uri(f"file://{self.data_path}", None) + self.toast_overlay.add_toast(Adw.Toast.new(_("Opened data path"))) + except GLib.GError as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(e)).toast) + + def trash_handler(self, *args): + self.failed_trash = False + def thread(*args): + try: + subprocess.run(['gio', 'trash', self.data_path], check=True, text=True, capture_output=True) + except subprocess.CalledProcessError as cpe: + self.failed_trash = cpe.stderr + except Exception as e: + self.failed_trash = e + + def callback(*args): + if self.failed_trash: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(self.failed_trash)).toast) + else: + self.toast_overlay.add_toast(Adw.Toast.new("Trashed data")) + if self.trash_callback: + self.trash_callback(self) + + Gio.Task.new(None, None, callback).run_in_thread(thread) + + def __init__(self, toast_overlay, title, subtitle, data_path, icon_path=None, callback=None, trash_callback=None, **kwargs): super().__init__(**kwargs) + # Extra Object Creation + self.toast_overlay = toast_overlay self.title = title self.subtitle = subtitle self.icon_path = icon_path self.data_path = data_path self.callback = callback + self.trash_callback = trash_callback self.size = None + self.failed_trash = None + # Connections + self.copy_button.connect("clicked", self.copy_handler) + self.open_button.connect("clicked", self.open_handler) + self.trash_button.connect("clicked", self.trash_handler) + + # Apply self.idle_stuff() self.show_size() diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 054b1ce..60b06dc 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -72,12 +72,12 @@ class DataSubpage(Gtk.Box): self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width if flatpaks: for i, pak in enumerate(flatpaks): - box = DataBox(pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback) + box = DataBox(self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, lambda trashed_box: self.flow_box.remove(trashed_box)) self.boxes.append(box) self.flow_box.append(box) else: for i, folder in enumerate(data): - box = DataBox(folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback) + box = DataBox(self.parent_page.toast_overlay, folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback, lambda trashed_box: self.flow_box.remove(trashed_box)) self.flow_box.append(box) child = self.flow_box.get_child_at_index(i) child.set_focusable(False) diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index b773935..6d934d4 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -113,7 +113,9 @@ template $UserDataPage : Adw.BreakpointBin { stack: stack; visible: false; } - Adw.ViewStack stack { + Adw.ToastOverlay toast_overlay { + Adw.ViewStack stack { + } } } } diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index e8e5653..9d4767f 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -19,6 +19,7 @@ class UserDataPage(Adw.BreakpointBin): leftover_select_button = gtc() leftover_sort_button = gtc() search_entry = gtc() + toast_overlay = gtc() stack = gtc() revealer = gtc() From 4bb6c54dec5bfbaa640f6d8075cc38be4b551881 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 19 Jul 2024 22:53:24 -0400 Subject: [PATCH 082/332] Data box asks for confirmation before trashing --- src/host_info.py | 1 + src/user_data_page/data_box.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/host_info.py b/src/host_info.py index 38588da..a4b0f59 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -225,6 +225,7 @@ class Remote: class HostInfo: home = home clipboard = Gdk.Display.get_default().get_clipboard() + main_window = None # Get all possible installation icon theme dirs output = subprocess.run( diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 5a8b46c..ce052c2 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -65,6 +65,7 @@ class DataBox(Gtk.ListBox): def trash_handler(self, *args): self.failed_trash = False + def thread(*args): try: subprocess.run(['gio', 'trash', self.data_path], check=True, text=True, capture_output=True) @@ -81,7 +82,18 @@ class DataBox(Gtk.ListBox): if self.trash_callback: self.trash_callback(self) - Gio.Task.new(None, None, callback).run_in_thread(thread) + def on_response(_, response): + if response != "continue": + return + + Gio.Task.new(None, None, callback).run_in_thread(thread) + + dialog = Adw.AlertDialog(heading=_("Trash {}?").format(self.title), body=_("{}'s data will be sent to the trash").format(self.title)) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Continue")) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.connect("response", on_response) + dialog.present(ErrorToast.main_window) def __init__(self, toast_overlay, title, subtitle, data_path, icon_path=None, callback=None, trash_callback=None, **kwargs): super().__init__(**kwargs) From d2ab5f223e244575e3588d76351e60f6784bb4ae Mon Sep 17 00:00:00 2001 From: heliguy Date: Sat, 20 Jul 2024 23:13:45 -0400 Subject: [PATCH 083/332] add batch copying --- src/user_data_page/data_subpage.blp | 29 ++++- src/user_data_page/data_subpage.py | 102 +++++++++++++++-- src/user_data_page/user_data_page.blp | 105 +++-------------- src/user_data_page/user_data_page.py | 156 +++++++++++++++----------- 4 files changed, 223 insertions(+), 169 deletions(-) diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp index 152ab2f..adc8edc 100644 --- a/src/user_data_page/data_subpage.blp +++ b/src/user_data_page/data_subpage.blp @@ -1,8 +1,17 @@ using Gtk 4.0; using Adw 1; -template $DataSubpage : Box { - Box outer_box { +template $DataSubpage : Stack { + Adw.StatusPage loading_data { + title: _("Loading Data"); + description: _("This should only take a moment"); + child: + Spinner { + spinning: true; + } + ; + } + Box content_box { orientation: vertical; Box label_box { margin-start: 24; @@ -46,14 +55,13 @@ template $DataSubpage : Box { margin-bottom: 9; } ScrolledWindow scrolled_window { + vexpand: true; Box { orientation: vertical; - vexpand: true; Separator { margin-start: 12; margin-end: 12; - // margin-top: 9; - margin-bottom: 6; + margin-bottom: 9; } FlowBox flow_box { styles ["boxed-list"] @@ -68,4 +76,13 @@ template $DataSubpage : Box { } } } -} \ No newline at end of file + Adw.StatusPage no_data { + // Contents will be set from the subpage object + } + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; + valign: center; + } +} diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 60b06dc..05d069c 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -6,13 +6,12 @@ from .host_info import HostInfo import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/data_subpage.ui") -class DataSubpage(Gtk.Box): +class DataSubpage(Gtk.Stack): __gtype_name__ = 'DataSubpage' gtc = Gtk.Template.Child scrolled_window = gtc() - outer_box = gtc() label_box = gtc() subtitle_size_box = gtc() title = gtc() @@ -21,6 +20,12 @@ class DataSubpage(Gtk.Box): size_label = gtc() flow_box = gtc() + # Statuses + loading_data = gtc() + content_box = gtc() + no_data = gtc() + no_results = gtc() + def human_readable_size(self): working_size = self.total_size units = ['KB', 'MB', 'GB', 'TB'] @@ -60,27 +65,74 @@ class DataSubpage(Gtk.Box): self.size_label.set_label(self.human_readable_size()) self.spinner.set_visible(False) self.ready_to_sort_size = True - self.flow_box.invalidate_sort() + if self.sort_mode == "size": + self.flow_box.invalidate_sort() + self.set_visible_child(self.content_box) + + def trash_handler(self, trashed_box): + self.flow_box.remove(trashed_box) + if not self.flow_box.get_child_at_index(0): + self.set_visible_child(self.no_data) + + def set_selection_mode(self, is_enabled): + self.selected_boxes.clear() + idx = 0 + while box := self.flow_box.get_child_at_index(idx): + idx += 1 + box = box.get_child() + if not is_enabled: + GLib.idle_add(lambda *_, box=box: box.check_button.set_active(False)) + GLib.idle_add(lambda *_, box=box: box.check_button.set_visible(is_enabled)) + + def box_select_handler(self, _, box): + box = box.get_child() + if not box.check_button.get_visible(): + return + cb = box.check_button + if cb.get_active(): + cb.set_active(False) + self.selected_boxes.remove(box) + else: + cb.set_active(True) + self.selected_boxes.append(box) + + total = len(self.selected_boxes) + self.parent_page.copy_button.set_sensitive(total) + self.parent_page.trash_button.set_sensitive(total) + + def select_all_handler(self, *args): + idx = 0 + while box := self.flow_box.get_child_at_index(idx): + idx += 1 + self.box_select_handler(None, box) def generate_list(self, flatpaks, data): self.boxes.clear() + self.selected_boxes.clear() self.ready_to_sort_size = False self.finished_boxes = 0 self.total_size = 0 self.total_items = len(data) - self.subtitle.set_label(_("{} Items").format(self.total_items)) + + if self.total_items == 1: + self.subtitle.set_label(_("1 Item")) + else: + self.subtitle.set_label(_("{} Items").format(self.total_items)) + self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width if flatpaks: for i, pak in enumerate(flatpaks): - box = DataBox(self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, lambda trashed_box: self.flow_box.remove(trashed_box)) + box = DataBox(self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, self.trash_handler) self.boxes.append(box) self.flow_box.append(box) else: for i, folder in enumerate(data): - box = DataBox(self.parent_page.toast_overlay, folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback, lambda trashed_box: self.flow_box.remove(trashed_box)) + box = DataBox(self.parent_page.toast_overlay, folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback, self.trash_handler) self.flow_box.append(box) child = self.flow_box.get_child_at_index(i) child.set_focusable(False) + + self.flow_box.connect("child-activated", self.box_select_handler) idx = 0 while box := self.flow_box.get_child_at_index(idx): @@ -90,13 +142,34 @@ class DataSubpage(Gtk.Box): child.set_focusable(False) child.row.set_focusable(child.check_button.get_visible()) + if idx == 0: + self.set_visible_child(self.no_data) + elif self.sort_mode != "size": + self.set_visible_child(self.content_box) + def filter_func(self, box): search_text = self.parent_page.search_entry.get_text().lower() box = box.get_child() - return search_text in box.title.lower() or search_text in box.subtitle.lower() + if search_text in box.title.lower() or search_text in box.subtitle.lower(): + self.is_result = True + return True def on_invalidate(self, box): + current_status = self.get_visible_child() + if not current_status is self.no_results: + self.prev_status = self.get_visible_child() + + self.is_result = False self.flow_box.invalidate_filter() + if self.is_result: + self.set_visible_child(self.prev_status) + else: + self.set_visible_child(self.no_results) + + if self.parent_page.search_entry.get_text().lower() != "" and self.total_items == 0: + self.set_visible_child(self.no_results) + elif self.total_items == 0: + self.set_visible_child(self.no_data) def label_orientation_handler(self, adj): current_page_width = adj.get_upper() - 24 @@ -106,7 +179,7 @@ class DataSubpage(Gtk.Box): else: GLib.idle_add(lambda *_: self.label_box.set_orientation(Gtk.Orientation.HORIZONTAL)) - def __init__(self, title, parent_page, main_window, **kwargs): + def __init__(self, title, parent_page, is_active, main_window, **kwargs): super().__init__(**kwargs) GLib.idle_add(lambda *_: self.title.set_label(title)) @@ -117,19 +190,32 @@ class DataSubpage(Gtk.Box): # Extra Object Creation self.main_window = main_window self.parent_page = parent_page + # self.is_active = is_active self.sort_mode = "" self.sort_ascend = False self.total_size = 0 self.total_items = 0 self.boxes = [] + self.selected_boxes = [] self.ready_to_sort_size = False self.finished_boxes = 0 self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width + self.is_result = False + self.prev_status = None # Apply self.flow_box.set_sort_func(self.sort_func) self.flow_box.set_filter_func(self.filter_func) + if is_active: + self.no_data.set_icon_name("error-symbolic") + self.no_data.set_title(_("No Active Data")) + self.no_data.set_description(_("Warehouse cannot see any active user data or your system has no active user data present")) + else: + self.no_data.set_icon_name("check-plain-symbolic") + self.no_data.set_title(_("No Leftover Data")) + self.no_data.set_description(_("There is no leftover user data")) + # Connections parent_page.search_entry.connect("search-changed", self.on_invalidate) diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 6d934d4..1f36697 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -38,26 +38,13 @@ template $UserDataPage : Adw.BreakpointBin { tooltip-text: _("Search User Data"); } [end] - MenuButton active_sort_button { - popover: active_sort_pop; + MenuButton sort_button { + popover: sort_pop; icon-name: "vertical-arrows-long-symbolic"; tooltip-text: _("Sort User Data"); } [end] - ToggleButton active_select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select User Data"); - } - [end] - MenuButton leftover_sort_button { - visible: false; - popover: leftover_sort_pop; - icon-name: "vertical-arrows-long-symbolic"; - tooltip-text: _("Sort User Data"); - } - [end] - ToggleButton leftover_select_button { - visible: false; + ToggleButton select_button { icon-name: "selection-mode-symbolic"; tooltip-text: _("Select User Data"); } @@ -76,6 +63,7 @@ template $UserDataPage : Adw.BreakpointBin { } [bottom] Revealer revealer { + reveal-child: bind select_button.active; transition-type: slide_up; [center] Box bottom_bar { @@ -91,6 +79,7 @@ template $UserDataPage : Adw.BreakpointBin { } } Button copy_button { + sensitive: false; styles ["raised"] Adw.ButtonContent { icon-name: "edit-copy-symbolic"; @@ -98,11 +87,12 @@ template $UserDataPage : Adw.BreakpointBin { can-shrink: true; } } - Button uninstall_button { + Button trash_button { + sensitive: false; styles ["raised"] Adw.ButtonContent { icon-name: "user-trash-symbolic"; - label: _("Uninstall"); + label: _("Move to Trash"); can-shrink: true; } } @@ -121,7 +111,7 @@ template $UserDataPage : Adw.BreakpointBin { } } -Popover active_sort_pop { +Popover sort_pop { styles ["menu"] Box { orientation: vertical; @@ -132,7 +122,7 @@ Popover active_sort_pop { Box { homogeneous: true; spacing: 3; - ToggleButton active_asc { + ToggleButton sort_ascend { active: true; styles ["flat"] Adw.ButtonContent { @@ -140,8 +130,8 @@ Popover active_sort_pop { label: _("Ascending"); } } - ToggleButton active_dsc { - group: active_asc; + ToggleButton sort_descend { + group: sort_ascend; styles ["flat"] Adw.ButtonContent { icon-name: "view-sort-descending-symbolic"; @@ -154,7 +144,7 @@ Popover active_sort_pop { Box { homogeneous: true; spacing: 3; - ToggleButton active_sort_name { + ToggleButton sort_name { active: true; styles ["flat"] Adw.ButtonContent { @@ -162,77 +152,16 @@ Popover active_sort_pop { label: _("Name"); } } - ToggleButton active_sort_id { - group: active_sort_name; + ToggleButton sort_id { + group: sort_name; styles ["flat"] Adw.ButtonContent { icon-name: "tag-outline-symbolic"; label: _("ID"); } } - ToggleButton active_sort_size { - group: active_sort_name; - styles ["flat"] - Adw.ButtonContent { - icon-name: "harddisk-symbolic"; - label: _("Size"); - } - } - } - } -} - -Popover leftover_sort_pop { - styles ["menu"] - Box { - orientation: vertical; - margin-start: 6; - margin-end: 6; - margin-top: 6; - margin-bottom: 6; - Box { - homogeneous: true; - spacing: 3; - ToggleButton leftover_asc { - active: true; - styles ["flat"] - Adw.ButtonContent { - icon-name: "view-sort-ascending-symbolic"; - label: _("Ascending"); - } - } - ToggleButton leftover_dsc { - group: leftover_asc; - styles ["flat"] - Adw.ButtonContent { - icon-name: "view-sort-descending-symbolic"; - label: _("Descending"); - } - } - } - Separator { - } - Box { - homogeneous: true; - spacing: 3; - ToggleButton leftover_sort_name { - active: true; - styles ["flat"] - Adw.ButtonContent { - icon-name: "font-x-generic-symbolic"; - label: _("Name"); - } - } - ToggleButton leftover_sort_id { - group: leftover_sort_name; - styles ["flat"] - Adw.ButtonContent { - icon-name: "tag-outline-symbolic"; - label: _("ID"); - } - } - ToggleButton leftover_sort_size { - group: leftover_sort_name; + ToggleButton sort_size { + group: sort_name; styles ["flat"] Adw.ButtonContent { icon-name: "harddisk-symbolic"; diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 9d4767f..885d6d5 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -14,27 +14,22 @@ class UserDataPage(Adw.BreakpointBin): switcher_bar = gtc() sidebar_button = gtc() search_button = gtc() - active_select_button = gtc() - active_sort_button = gtc() - leftover_select_button = gtc() - leftover_sort_button = gtc() + select_button = gtc() + sort_button = gtc() search_entry = gtc() toast_overlay = gtc() stack = gtc() revealer = gtc() - active_asc = gtc() - active_dsc = gtc() - active_sort_name = gtc() - active_sort_id = gtc() - active_sort_size = gtc() - - leftover_asc = gtc() - leftover_dsc = gtc() - leftover_sort_name = gtc() - leftover_sort_id = gtc() - leftover_sort_size = gtc() + sort_ascend = gtc() + sort_descend = gtc() + sort_name = gtc() + sort_id = gtc() + sort_size = gtc() + select_all_button = gtc() + copy_button = gtc() + trash_button = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -55,10 +50,12 @@ class UserDataPage(Adw.BreakpointBin): self.leftover_data.append(folder) def start_loading(self, *args): - self.adp.size_label.set_label("Loading Size…") + self.adp.set_visible_child(self.adp.loading_data) + self.adp.size_label.set_label("Loading Size") self.adp.spinner.set_visible(True) self.adp.flow_box.remove_all() - self.ldp.size_label.set_label("Loading Size…") + self.ldp.set_visible_child(self.ldp.loading_data) + self.ldp.size_label.set_label("Loading Size") self.ldp.spinner.set_visible(True) self.ldp.flow_box.remove_all() @@ -70,17 +67,68 @@ class UserDataPage(Adw.BreakpointBin): Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) - def switch_view_handler(self, page): - self.active_select_button.set_visible(page is self.adp) - self.active_sort_button.set_visible(page is self.adp) - self.leftover_select_button.set_visible(page is self.ldp) - self.leftover_sort_button.set_visible(page is self.ldp) - self.active_select_button.set_active(False) - self.leftover_select_button.set_active(False) - self.revealer_handler() + def sorter(self, button=None): + if button and not button.get_active(): + return - def revealer_handler(self, *args): - self.revealer.set_reveal_child(self.active_select_button.get_active() or self.leftover_select_button.get_active()) + if self.sort_name.get_active(): + self.adp.sort_mode = "name" + self.ldp.sort_mode = "name" + elif self.sort_id.get_active(): + self.adp.sort_mode = "id" + self.ldp.sort_mode = "id" + elif self.sort_size.get_active(): + self.adp.sort_mode = "size" + self.ldp.sort_mode = "size" + + + self.adp.sort_ascend = self.sort_ascend.get_active() + self.ldp.sort_ascend = self.sort_ascend.get_active() + + self.adp.flow_box.invalidate_sort() + self.ldp.flow_box.invalidate_sort() + + def view_change_handler(self, *args): + child = self.stack.get_visible_child() + if child.total_size == 0: + self.search_button.set_active(False) + self.search_button.set_sensitive(False) + self.select_button.set_active(False) + self.select_button.set_sensitive(False) + self.sort_button.set_active(False) + self.sort_button.set_sensitive(False) + else: + self.search_button.set_sensitive(True) + self.select_button.set_sensitive(True) + self.sort_button.set_sensitive(True) + + has_selected = len(child.selected_boxes) > 0 + self.copy_button.set_sensitive(has_selected) + self.trash_button.set_sensitive(has_selected) + + def select_toggle_handler(self, *args): + active = self.select_button.get_active() + self.adp.set_selection_mode(active) + self.ldp.set_selection_mode(active) + if not active: + self.copy_button.set_sensitive(False) + self.trash_button.set_sensitive(False) + + def select_all_handler(self, *args): + child = self.stack.get_visible_child() + child.select_all_handler() + + def copy_handler(self, *args): + child = self.stack.get_visible_child() + to_copy = "" + for box in child.selected_boxes: + to_copy += "\n" + box.data_path + + if len(to_copy) > 0: + HostInfo.clipboard.set(to_copy.replace("\n", "", 1)) + self.toast_overlay.add_toast(Adw.Toast(title=_("Copied paths"))) + else: + self.toast_overlay.add_toast(ErrorToast(_("Could not copy paths"), _("No boxes were selected")).toast) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -88,8 +136,8 @@ class UserDataPage(Adw.BreakpointBin): # Extra Object Creation self.__class__.instance = self # self.adj = self.scrolled_window.get_vadjustment() - self.adp = DataSubpage(_("Active Data"), self, main_window) - self.ldp = DataSubpage(_("Leftover Data"), self, main_window) + self.adp = DataSubpage(_("Active Data"), self, True, main_window) + self.ldp = DataSubpage(_("Leftover Data"), self, False, main_window) self.data_flatpaks = [] self.active_data = [] self.leftover_data = [] @@ -115,44 +163,18 @@ class UserDataPage(Adw.BreakpointBin): # self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) main_window.main_split.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) - self.stack.connect("notify::visible-child", lambda *_: self.switch_view_handler(self.stack.get_visible_child())) - self.active_select_button.connect("toggled", self.revealer_handler) - self.leftover_select_button.connect("toggled", self.revealer_handler) + self.stack.connect("notify::visible-child", self.view_change_handler) + + self.select_button.connect("toggled", self.select_toggle_handler) - def sorter(button=None): - if button and not button.get_active(): - return + self.select_all_button.connect("clicked", self.select_all_handler) + self.copy_button.connect("clicked", self.copy_handler) - if self.active_sort_name.get_active(): - self.adp.sort_mode = "name" - elif self.active_sort_id.get_active(): - self.adp.sort_mode = "id" - elif self.active_sort_size.get_active(): - self.adp.sort_mode = "size" - - if self.leftover_sort_name.get_active(): - self.ldp.sort_mode = "name" - elif self.leftover_sort_id.get_active(): - self.ldp.sort_mode = "id" - elif self.leftover_sort_size.get_active(): - self.ldp.sort_mode = "size" + self.sort_ascend.connect("clicked", self.sorter) + self.sort_descend.connect("clicked", self.sorter) + self.sort_name.connect("clicked", self.sorter) + self.sort_id.connect("clicked", self.sorter) + self.sort_size.connect("clicked", self.sorter) - self.adp.sort_ascend = self.active_asc.get_active() - self.ldp.sort_ascend = self.leftover_asc.get_active() - - self.adp.flow_box.invalidate_sort() - self.ldp.flow_box.invalidate_sort() - - self.active_asc.connect("clicked", sorter) - self.active_dsc.connect("clicked", sorter) - self.active_sort_name.connect("clicked", sorter) - self.active_sort_id.connect("clicked", sorter) - self.active_sort_size.connect("clicked", sorter) - - self.leftover_asc.connect("clicked", sorter) - self.leftover_dsc.connect("clicked", sorter) - self.leftover_sort_name.connect("clicked", sorter) - self.leftover_sort_id.connect("clicked", sorter) - self.leftover_sort_size.connect("clicked", sorter) - - sorter() \ No newline at end of file + # Apply again + self.sorter() \ No newline at end of file From 7e6a147784c9382203b0cdf531ce8c9e9523ddb2 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 00:17:30 -0400 Subject: [PATCH 084/332] add batch trashing --- src/user_data_page/data_subpage.py | 13 +++++--- src/user_data_page/user_data_page.py | 50 ++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 05d069c..ff48fcd 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -81,8 +81,11 @@ class DataSubpage(Gtk.Stack): idx += 1 box = box.get_child() if not is_enabled: + # continue GLib.idle_add(lambda *_, box=box: box.check_button.set_active(False)) GLib.idle_add(lambda *_, box=box: box.check_button.set_visible(is_enabled)) + # if not is_enabled: + # return def box_select_handler(self, _, box): box = box.get_child() @@ -90,10 +93,10 @@ class DataSubpage(Gtk.Stack): return cb = box.check_button if cb.get_active(): - cb.set_active(False) + GLib.idle_add(lambda *_: cb.set_active(False)) self.selected_boxes.remove(box) else: - cb.set_active(True) + GLib.idle_add(lambda *_: cb.set_active(True)) self.selected_boxes.append(box) total = len(self.selected_boxes) @@ -104,7 +107,8 @@ class DataSubpage(Gtk.Stack): idx = 0 while box := self.flow_box.get_child_at_index(idx): idx += 1 - self.box_select_handler(None, box) + if not box.get_child().check_button.get_active(): + self.box_select_handler(None, box) def generate_list(self, flatpaks, data): self.boxes.clear() @@ -131,8 +135,6 @@ class DataSubpage(Gtk.Stack): self.flow_box.append(box) child = self.flow_box.get_child_at_index(i) child.set_focusable(False) - - self.flow_box.connect("child-activated", self.box_select_handler) idx = 0 while box := self.flow_box.get_child_at_index(idx): @@ -218,6 +220,7 @@ class DataSubpage(Gtk.Stack): # Connections parent_page.search_entry.connect("search-changed", self.on_invalidate) + self.flow_box.connect("child-activated", self.box_select_handler) # self.title.get_preferred_size()[1].width + self.subtitle.get_preferred_size()[1].width self.scrolled_window.get_hadjustment().connect("changed", self.label_orientation_handler) \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 885d6d5..c36bde8 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -3,7 +3,9 @@ from .error_toast import ErrorToast from .data_box import DataBox from .data_subpage import DataSubpage from .host_info import HostInfo -import os +import os, subprocess + +import time @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/user_data_page.ui") class UserDataPage(Adw.BreakpointBin): @@ -50,6 +52,7 @@ class UserDataPage(Adw.BreakpointBin): self.leftover_data.append(folder) def start_loading(self, *args): + self.select_button.set_active(False) self.adp.set_visible_child(self.adp.loading_data) self.adp.size_label.set_label("Loading Size") self.adp.spinner.set_visible(True) @@ -80,8 +83,7 @@ class UserDataPage(Adw.BreakpointBin): elif self.sort_size.get_active(): self.adp.sort_mode = "size" self.ldp.sort_mode = "size" - - + self.adp.sort_ascend = self.sort_ascend.get_active() self.ldp.sort_ascend = self.sort_ascend.get_active() @@ -124,11 +126,46 @@ class UserDataPage(Adw.BreakpointBin): for box in child.selected_boxes: to_copy += "\n" + box.data_path - if len(to_copy) > 0: + if len(to_copy) == 0: + self.toast_overlay.add_toast(ErrorToast(_("Could not copy paths"), _("No boxes were selected")).toast) + else: HostInfo.clipboard.set(to_copy.replace("\n", "", 1)) self.toast_overlay.add_toast(Adw.Toast(title=_("Copied paths"))) - else: - self.toast_overlay.add_toast(ErrorToast(_("Could not copy paths"), _("No boxes were selected")).toast) + + def trash_handler(self, *args): + error = [None] + + def thread(path): + cmd = ['gio', 'trash'] + path + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + error[0] = cpe.stderr + except Exception as e: + error[0] = e + + def callback(*args): + self.start_loading() + self.end_loading() + if error[0]: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(error[0])).toast) + else: + self.toast_overlay.add_toast(Adw.Toast(title=_("Trashed data"))) + + child = self.stack.get_visible_child() + to_trash = [] + for box in child.selected_boxes: + to_trash.append(box.data_path) + + if len(to_trash) == 0: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), _("No boxes were selected")).toast) + return + + self.select_button.set_active(False) + child.set_visible_child(child.loading_data) + Gio.Task.new(None, None, callback).run_in_thread(lambda *_: thread(to_trash)) + + # self.toast_overlay.add_toast(Adw.Toast(title=_("Trashed data"))) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -169,6 +206,7 @@ class UserDataPage(Adw.BreakpointBin): self.select_all_button.connect("clicked", self.select_all_handler) self.copy_button.connect("clicked", self.copy_handler) + self.trash_button.connect("clicked", self.trash_handler) self.sort_ascend.connect("clicked", self.sorter) self.sort_descend.connect("clicked", self.sorter) From 10af1db799b9c768627ccbfe698670e6fcedf3bc Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 12:08:54 -0400 Subject: [PATCH 085/332] Initial work for remotes page --- src/main_window/window.py | 5 ++- src/meson.build | 2 + src/remotes_page/remotes_page.blp | 64 ++++++++++++++++++++++++++++ src/remotes_page/remotes_page.py | 26 +++++++++++ src/user_data_page/user_data_page.py | 2 +- src/warehouse.gresource.xml | 1 + 6 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 src/remotes_page/remotes_page.blp create mode 100644 src/remotes_page/remotes_page.py diff --git a/src/main_window/window.py b/src/main_window/window.py index f392fb9..32cabf1 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -25,6 +25,7 @@ import time from gi.repository import Adw, Gdk, Gio, GLib, Gtk from .host_info import HostInfo from .packages_page import PackagesPage +from .remotes_page import RemotesPage from .user_data_page import UserDataPage from .const import Config from .error_toast import ErrorToast @@ -83,7 +84,7 @@ class WarehouseWindow(Adw.ApplicationWindow): file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) self.pages = { self.packages_row: PackagesPage(main_window=self), - + self.remotes_row: RemotesPage(main_window=self), self.user_data_row: UserDataPage(main_window=self), } @@ -108,7 +109,7 @@ class WarehouseWindow(Adw.ApplicationWindow): # file_drop.connect("drop", self.drop_callback) self.refresh_button.connect("clicked", self.refresh_handler) - self.navigation_row_listbox.get_row_at_index(2).activate() + self.navigation_row_listbox.get_row_at_index(1).activate() self.main_split.set_show_sidebar(True) self.start_loading() diff --git a/src/meson.build b/src/meson.build index 139d30d..16f2ae2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -13,6 +13,7 @@ blueprints = custom_target('blueprints', 'user_data_page/data_box.blp', 'user_data_page/user_data_page.blp', 'user_data_page/data_subpage.blp', + 'remotes_page/remotes_page.blp', 'change_version_page/change_version_page.blp', ), output: '.', @@ -66,6 +67,7 @@ warehouse_sources = [ 'user_data_page/data_box.py', 'user_data_page/user_data_page.py', 'user_data_page/data_subpage.py', + 'remotes_page/remotes_page.py', '../data/style.css', ] diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp new file mode 100644 index 0000000..4ac76f9 --- /dev/null +++ b/src/remotes_page/remotes_page.blp @@ -0,0 +1,64 @@ +using Gtk 4.0; +using Adw 1; + +template $RemotesPage : Adw.NavigationPage { + title: _("Manage Remotes"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + [start] + ToggleButton sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); + } + } + Adw.ToastOverlay { + Stack stack { + Adw.StatusPage loading_remotes { + visible: false; + title: _("Loading Remotes"); + description: _("This should only take a moment"); + child: + Spinner { + spinning: true; + } + ; + } + Adw.StatusPage no_packages { + visible: false; + title: _("No Remotes Found"); + description: _("Warehouse cannot see the current remotes or your system has no remotes added"); + icon-name: "error-symbolic"; + } + Adw.PreferencesPage content_page { + Adw.PreferencesGroup current_remotes { + title: _("Current Remotes"); + description: _("Remotes available on your system"); + Adw.ActionRow { + title: "test"; + } + Adw.ActionRow { + title: "test"; + } + Adw.ActionRow { + title: "test"; + } + } + Adw.PreferencesGroup new_remotes { + title: _("Add Remotes"); + description: _("Add new remotes to get more software"); + Adw.ActionRow { + title: "test"; + } + Adw.ActionRow { + title: "test"; + } + Adw.ActionRow { + title: "test"; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py new file mode 100644 index 0000000..282bf51 --- /dev/null +++ b/src/remotes_page/remotes_page.py @@ -0,0 +1,26 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +import subprocess + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remotes_page.ui") +class RemotesPage(Adw.NavigationPage): + __gtype_name__ = 'RemotesPage' + gtc = Gtk.Template.Child + + sidebar_button = 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 + + def __init__(self, main_window, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + ms = main_window.main_split + + # Connections + ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) + self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index c36bde8..fd9a868 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -198,7 +198,7 @@ class UserDataPage(Adw.BreakpointBin): # Connections # self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) - main_window.main_split.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) + ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.stack.connect("notify::visible-child", self.view_change_handler) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index fdccf7a..d16d910 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -12,6 +12,7 @@ user_data_page/data_box.ui user_data_page/user_data_page.ui user_data_page/data_subpage.ui + remotes_page/remotes_page.ui From 9424f0e038602f83abdc5c60c1baa0e59541e25e Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 12:11:26 -0400 Subject: [PATCH 086/332] Update packages page's sidebar button to new toggle style --- src/packages_page/packages_page.blp | 2 +- src/packages_page/packages_page.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index c2e9e93..9f613f4 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -27,7 +27,7 @@ template $PackagesPage : Adw.BreakpointBin { [top] Adw.HeaderBar { [start] - Button sidebar_button { + ToggleButton sidebar_button { icon-name: "dock-left-symbolic"; tooltip-text: _("Show Sidebar"); } diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index f147221..ee4712c 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -287,6 +287,7 @@ class PackagesPage(Adw.BreakpointBin): self.is_result = False self.prev_status = None self.selected_rows = [] + ms = main_window.main_split # Apply # self.set_status("loading_packages") @@ -297,7 +298,8 @@ class PackagesPage(Adw.BreakpointBin): self.__class__.instance = self # Connections - self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) + ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) + self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.search_entry.connect("search-changed", self.on_invalidate) self.search_bar.set_key_capture_widget(main_window) From 528f1b22bc5f8f0c188545ec1fe38ac300802766 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 12:25:44 -0400 Subject: [PATCH 087/332] Change searchbar to open even when page is not focused --- src/user_data_page/user_data_page.blp | 1 - src/user_data_page/user_data_page.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 1f36697..5f68dcc 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -53,7 +53,6 @@ template $UserDataPage : Adw.BreakpointBin { Adw.Clamp { SearchBar search_bar { search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: template; SearchEntry search_entry { hexpand: true; diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index fd9a868..5e74d7d 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -18,6 +18,7 @@ class UserDataPage(Adw.BreakpointBin): search_button = gtc() select_button = gtc() sort_button = gtc() + search_bar = gtc() search_entry = gtc() toast_overlay = gtc() stack = gtc() @@ -215,4 +216,5 @@ class UserDataPage(Adw.BreakpointBin): self.sort_size.connect("clicked", self.sorter) # Apply again + self.search_bar.set_key_capture_widget(main_window) self.sorter() \ No newline at end of file From 859d1ff28c6da38f4cb6551cfd63e913589398b0 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 12:28:47 -0400 Subject: [PATCH 088/332] Add search UI --- src/remotes_page/remotes_page.blp | 23 ++++++++++++++++++++++- src/remotes_page/remotes_page.py | 10 ++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 4ac76f9..f989ed5 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -11,6 +11,22 @@ template $RemotesPage : Adw.NavigationPage { icon-name: "dock-left-symbolic"; tooltip-text: _("Show Sidebar"); } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } + } + [top] + Adw.Clamp { + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + key-capture-widget: template; + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search Remotes"); + } + } } Adw.ToastOverlay { Stack stack { @@ -24,7 +40,12 @@ template $RemotesPage : Adw.NavigationPage { } ; } - Adw.StatusPage no_packages { + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; + } + Adw.StatusPage no_remotes { visible: false; title: _("No Remotes Found"); description: _("Warehouse cannot see the current remotes or your system has no remotes added"); diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 282bf51..e359645 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -9,6 +9,15 @@ class RemotesPage(Adw.NavigationPage): gtc = Gtk.Template.Child sidebar_button = gtc() + search_bar = gtc() + stack = gtc() + current_remotes = gtc() + new_remotes = gtc() + + # Statuses + loading_remotes = gtc() + no_remotes = gtc() + content_page = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -20,6 +29,7 @@ class RemotesPage(Adw.NavigationPage): # Extra Object Creation ms = main_window.main_split + self.search_bar.set_key_capture_widget(main_window) # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) From 36a70fdf7117c991f64202da98cd59aa693cd38c Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 12:32:31 -0400 Subject: [PATCH 089/332] Setup loading functions --- src/remotes_page/remotes_page.blp | 2 -- src/remotes_page/remotes_page.py | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index f989ed5..613fc17 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -31,7 +31,6 @@ template $RemotesPage : Adw.NavigationPage { Adw.ToastOverlay { Stack stack { Adw.StatusPage loading_remotes { - visible: false; title: _("Loading Remotes"); description: _("This should only take a moment"); child: @@ -46,7 +45,6 @@ template $RemotesPage : Adw.NavigationPage { icon-name: "system-search-symbolic"; } Adw.StatusPage no_remotes { - visible: false; title: _("No Remotes Found"); description: _("Warehouse cannot see the current remotes or your system has no remotes added"); icon-name: "error-symbolic"; diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index e359645..a4075c7 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -23,6 +23,12 @@ class RemotesPage(Adw.NavigationPage): # 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 + + def start_loading(self): + self.stack.set_visible_child(self.loading_remotes) + + def end_loading(self): + self.stack.set_visible_child(self.content_page) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -30,6 +36,7 @@ class RemotesPage(Adw.NavigationPage): # Extra Object Creation ms = main_window.main_split self.search_bar.set_key_capture_widget(main_window) + self.__class__.instance = self # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) From 027c0c63c9c73b48ac4bbd2bfb5029620da7ef18 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 14:42:00 -0400 Subject: [PATCH 090/332] Add UI for remote actions --- src/main_window/window.py | 9 +++++- src/meson.build | 2 ++ src/remotes_page/remote_row.blp | 43 +++++++++++++++++++++++++++++ src/remotes_page/remote_row.py | 46 +++++++++++++++++++++++++++++++ src/remotes_page/remotes_page.blp | 15 ++-------- src/remotes_page/remotes_page.py | 17 ++++++++++-- src/warehouse.gresource.xml | 1 + 7 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 src/remotes_page/remote_row.blp create mode 100644 src/remotes_page/remote_row.py diff --git a/src/main_window/window.py b/src/main_window/window.py index 32cabf1..d42b5eb 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -75,6 +75,13 @@ class WarehouseWindow(Adw.ApplicationWindow): self.refresh_button.set_sensitive(False) HostInfo.get_flatpaks(callback=self.end_loading) + def activate_row(self, nav_row): + idx = 0 + while row := self.navigation_row_listbox.get_row_at_index(idx): + idx += 1 + if row.get_child() is nav_row: + row.activate() + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -109,7 +116,7 @@ class WarehouseWindow(Adw.ApplicationWindow): # file_drop.connect("drop", self.drop_callback) self.refresh_button.connect("clicked", self.refresh_handler) - self.navigation_row_listbox.get_row_at_index(1).activate() + self.activate_row(self.remotes_row) self.main_split.set_show_sidebar(True) self.start_loading() diff --git a/src/meson.build b/src/meson.build index 16f2ae2..ad607ca 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ blueprints = custom_target('blueprints', 'user_data_page/user_data_page.blp', 'user_data_page/data_subpage.blp', 'remotes_page/remotes_page.blp', + 'remotes_page/remote_row.blp', 'change_version_page/change_version_page.blp', ), output: '.', @@ -68,6 +69,7 @@ warehouse_sources = [ 'user_data_page/user_data_page.py', 'user_data_page/data_subpage.py', 'remotes_page/remotes_page.py', + 'remotes_page/remote_row.py', '../data/style.css', ] diff --git a/src/remotes_page/remote_row.blp b/src/remotes_page/remote_row.blp new file mode 100644 index 0000000..86f3004 --- /dev/null +++ b/src/remotes_page/remote_row.blp @@ -0,0 +1,43 @@ +using Gtk 4.0; +using Adw 1; + +template $RemoteRow : Adw.ActionRow { + [suffix] + Label suffix_label { + styles ["subtitle"] + margin-end: 6; + } + [suffix] + Button { + styles ["flat"] + valign: center; + icon-name: "funnel-symbolic"; + tooltip-text: _("Set a Filter for this Remote"); + } + [suffix] + MenuButton menu_button { + styles ["flat"] + valign: center; + popover: menu_pop; + icon-name: "view-more-symbolic"; + tooltip-text: _("More Actions"); + } +} + +Popover menu_pop { + styles ["menu"] + ListBox menu_listbox { + Label copy_title { + label: _("Copy Title"); + halign: start; + } + Label copy_name { + label: _("Copy Name"); + halign: start; + } + Label remove { + label: _("Remove"); + halign: start; + } + } +} \ No newline at end of file diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py new file mode 100644 index 0000000..22ca980 --- /dev/null +++ b/src/remotes_page/remote_row.py @@ -0,0 +1,46 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango +from .host_info import HostInfo + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remote_row.ui") +class RemoteRow(Adw.ActionRow): + __gtype_name__ = 'RemoteRow' + gtc = Gtk.Template.Child + + suffix_label = gtc() + menu_pop = gtc() + menu_listbox = gtc() + + copy_title = gtc() + copy_name = gtc() + remove = gtc() + + def on_menu_action(self, listbox, row): + row = row.get_child() + if row is self.copy_title: + HostInfo.clipboard.set(self.get_title()) + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied title"))) + elif row is self.copy_name: + HostInfo.clipboard.set(self.get_subtitle()) + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied name"))) + elif row is self.remove: + print("remove") + self.menu_pop.popdown() + + def idle_stuff(self): + self.set_title(self.remote.title) + self.set_subtitle(_("Installation: {}").format(self.installation)) + self.suffix_label.set_label(self.remote.name) + + def __init__(self, parent_page, installation, remote, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + self.parent_page = parent_page + self.remote = remote + self.installation = installation + + # Apply + GLib.idle_add(lambda *_: self.idle_stuff()) + + # Connections + self.menu_listbox.connect("row-activated", self.on_menu_action) \ No newline at end of file diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 613fc17..6985999 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -28,7 +28,7 @@ template $RemotesPage : Adw.NavigationPage { } } } - Adw.ToastOverlay { + Adw.ToastOverlay toast_overlay { Stack stack { Adw.StatusPage loading_remotes { title: _("Loading Remotes"); @@ -50,20 +50,11 @@ template $RemotesPage : Adw.NavigationPage { icon-name: "error-symbolic"; } Adw.PreferencesPage content_page { - Adw.PreferencesGroup current_remotes { + Adw.PreferencesGroup current_remotes_group { title: _("Current Remotes"); description: _("Remotes available on your system"); - Adw.ActionRow { - title: "test"; - } - Adw.ActionRow { - title: "test"; - } - Adw.ActionRow { - title: "test"; - } } - Adw.PreferencesGroup new_remotes { + Adw.PreferencesGroup new_remotes_group { title: _("Add Remotes"); description: _("Add new remotes to get more software"); Adw.ActionRow { diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index a4075c7..710ff5d 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -1,6 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast +from .remote_row import RemoteRow import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remotes_page.ui") @@ -10,9 +11,10 @@ class RemotesPage(Adw.NavigationPage): sidebar_button = gtc() search_bar = gtc() + toast_overlay = gtc() stack = gtc() - current_remotes = gtc() - new_remotes = gtc() + current_remotes_group = gtc() + new_remotes_group = gtc() # Statuses loading_remotes = gtc() @@ -26,17 +28,26 @@ class RemotesPage(Adw.NavigationPage): def start_loading(self): self.stack.set_visible_child(self.loading_remotes) + for row in self.current_remote_rows: + self.current_remotes_group.remove(row) + self.current_remote_rows.clear() def end_loading(self): self.stack.set_visible_child(self.content_page) + for install in HostInfo.installations: + for remote in HostInfo.remotes[install]: + row = RemoteRow(self, install, remote) + self.current_remotes_group.add(row) + self.current_remote_rows.append(row) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) # Extra Object Creation + self.__class__.instance = self ms = main_window.main_split self.search_bar.set_key_capture_widget(main_window) - self.__class__.instance = self + self.current_remote_rows = [] # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index d16d910..043a096 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -13,6 +13,7 @@ user_data_page/user_data_page.ui user_data_page/data_subpage.ui remotes_page/remotes_page.ui + remotes_page/remote_row.ui From 0299ef3f04a98e6ff2f3d615044405d97809fe32 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 14:51:39 -0400 Subject: [PATCH 091/332] More UI work --- src/remotes_page/remote_row.blp | 4 ++++ src/remotes_page/remotes_page.blp | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/remotes_page/remote_row.blp b/src/remotes_page/remote_row.blp index 86f3004..b9ee25c 100644 --- a/src/remotes_page/remote_row.blp +++ b/src/remotes_page/remote_row.blp @@ -6,6 +6,10 @@ template $RemoteRow : Adw.ActionRow { Label suffix_label { styles ["subtitle"] margin-end: 6; + wrap: true; + wrap-mode: word_char; + hexpand: true; + justify: right; } [suffix] Button { diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 6985999..54c102a 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -16,6 +16,16 @@ template $RemotesPage : Adw.NavigationPage { icon-name: "loupe-large-symbolic"; tooltip-text: _("Search Packages"); } + [end] + Button { + tooltip-text: _("Add Remote from File"); + sensitive: false; + Adw.ButtonContent { + can-shrink: true; + icon-name: "folder-open-symbolic"; + label: _("Open"); + } + } } [top] Adw.Clamp { From 8b57938339613b2d50cbc589a754c4f457a4e980 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 15:32:18 -0400 Subject: [PATCH 092/332] Add UI for new remotes --- src/remotes_page/remote_row.blp | 2 +- src/remotes_page/remote_row.py | 4 +- src/remotes_page/remotes_page.blp | 9 ---- src/remotes_page/remotes_page.py | 80 ++++++++++++++++++++++++++++++- 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/remotes_page/remote_row.blp b/src/remotes_page/remote_row.blp index b9ee25c..257701a 100644 --- a/src/remotes_page/remote_row.blp +++ b/src/remotes_page/remote_row.blp @@ -12,7 +12,7 @@ template $RemoteRow : Adw.ActionRow { justify: right; } [suffix] - Button { + Button filter_button { styles ["flat"] valign: center; icon-name: "funnel-symbolic"; diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 22ca980..6786c36 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -7,6 +7,7 @@ class RemoteRow(Adw.ActionRow): gtc = Gtk.Template.Child suffix_label = gtc() + filter_button = gtc() menu_pop = gtc() menu_listbox = gtc() @@ -43,4 +44,5 @@ class RemoteRow(Adw.ActionRow): GLib.idle_add(lambda *_: self.idle_stuff()) # Connections - self.menu_listbox.connect("row-activated", self.on_menu_action) \ No newline at end of file + self.menu_listbox.connect("row-activated", self.on_menu_action) + self.filter_button.connect("clicked", lambda *_: parent_page.filter_remote(self)) \ No newline at end of file diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 54c102a..a6549f8 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -67,15 +67,6 @@ template $RemotesPage : Adw.NavigationPage { Adw.PreferencesGroup new_remotes_group { title: _("Add Remotes"); description: _("Add new remotes to get more software"); - Adw.ActionRow { - title: "test"; - } - Adw.ActionRow { - title: "test"; - } - Adw.ActionRow { - title: "test"; - } } } } diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 710ff5d..b3b01e5 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -4,6 +4,21 @@ from .error_toast import ErrorToast from .remote_row import RemoteRow import subprocess +class NewRemoteRow(Adw.ActionRow): + __gtype_name__ = "NewRemoteRow" + + def idle_stuff(self, *args): + self.set_title(self.info["title"]) + self.set_subtitle(self.info["description"]) + self.add_suffix(Gtk.Image.new_from_icon_name("plus-large-symbolic")) + + def __init__(self, info, **kwargs): + super().__init__(**kwargs) + self.info = info + GLib.idle_add(self.idle_stuff) + self.set_activatable(True) + self.connect("activated", lambda *_: print(info["name"])) + @Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remotes_page.ui") class RemotesPage(Adw.NavigationPage): __gtype_name__ = 'RemotesPage' @@ -21,6 +36,52 @@ class RemotesPage(Adw.NavigationPage): no_remotes = gtc() content_page = gtc() + # Preselected Remotes + new_remotes = [ + { + "title": "AppCenter", + "name": "appcenter", + "link": "https://flatpak.elementary.io/repo.flatpakrepo", + "description": _("The open source, pay-what-you-want app store from elementary") + }, + { + "title": "Flathub", + "name": "flathub", + "link": "https://dl.flathub.org/repo/flathub.flatpakrepo", + "description": _("Central repository of Flatpak applications"), + }, + { + "title": "Flathub beta", + "name": "flathub-beta", + "link": "https://flathub.org/beta-repo/flathub-beta.flatpakrepo", + "description": _("Beta builds of Flatpak applications"), + }, + { + "title": "Fedora", + "name": "fedora", + "link": "oci+https://registry.fedoraproject.org", + "description": _("Flatpaks packaged by Fedora Linux"), + }, + { + "title": "GNOME Nightly", + "name": "gnome-nightly", + "link": "https://nightly.gnome.org/gnome-nightly.flatpakrepo", + "description": _("The latest beta GNOME Apps and Runtimes"), + }, + { + "title": "KDE Testing Applications", + "name": "kdeapps", + "link": "https://distribute.kde.org/kdeapps.flatpakrepo", + "description": _("Beta KDE Apps and Runtimes"), + }, + { + "title": "WebKit Developer SDK", + "name": "webkit-sdk", + "link": "https://software.igalia.com/flatpak-refs/webkit-sdk.flatpakrepo", + "description": _("Central repository of the WebKit Developer and Runtime SDK"), + } + ] + # 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 @@ -40,15 +101,32 @@ class RemotesPage(Adw.NavigationPage): self.current_remotes_group.add(row) self.current_remote_rows.append(row) + def filter_remote(self, row): + self.filter_setting.set_boolean("show-apps", True) + self.filter_setting.set_boolean("show-runtimes", True) + self.filter_setting.set_string("remotes-list", f"{row.remote.name}<>{row.installation};") + self.filter_setting.reset("runtimes-list") + packages_page = self.main_window.pages[self.main_window.packages_row] + packages_page.filters_page.generate_filters() + packages_page.apply_filters() + GLib.idle_add(lambda *_: self.main_window.activate_row(self.main_window.packages_row)) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.__class__.instance = self + self.main_window = main_window ms = main_window.main_split self.search_bar.set_key_capture_widget(main_window) self.current_remote_rows = [] + self.filter_setting = Gio.Settings.new("io.github.flattool.Warehouse.filter") # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) - self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) \ No newline at end of file + self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + + # Apply + for item in self.new_remotes: + row = NewRemoteRow(item) + self.new_remotes_group.add(row) \ No newline at end of file From 0689db51d9069b9b1519aeb9baa3fb85f162c2e8 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 15:39:30 -0400 Subject: [PATCH 093/332] Initial work for searching --- src/remotes_page/remotes_page.py | 42 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index b3b01e5..7cf1454 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -21,20 +21,6 @@ class NewRemoteRow(Adw.ActionRow): @Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remotes_page.ui") class RemotesPage(Adw.NavigationPage): - __gtype_name__ = 'RemotesPage' - gtc = Gtk.Template.Child - - sidebar_button = gtc() - search_bar = gtc() - toast_overlay = gtc() - stack = gtc() - current_remotes_group = gtc() - new_remotes_group = gtc() - - # Statuses - loading_remotes = gtc() - no_remotes = gtc() - content_page = gtc() # Preselected Remotes new_remotes = [ @@ -82,6 +68,22 @@ class RemotesPage(Adw.NavigationPage): } ] + __gtype_name__ = 'RemotesPage' + gtc = Gtk.Template.Child + + sidebar_button = gtc() + search_button = gtc() + search_bar = gtc() + toast_overlay = gtc() + stack = gtc() + current_remotes_group = gtc() + new_remotes_group = gtc() + + # Statuses + loading_remotes = gtc() + no_remotes = gtc() + content_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 @@ -91,16 +93,26 @@ class RemotesPage(Adw.NavigationPage): self.stack.set_visible_child(self.loading_remotes) for row in self.current_remote_rows: self.current_remotes_group.remove(row) + self.current_remote_rows.clear() def end_loading(self): - self.stack.set_visible_child(self.content_page) + if len(HostInfo.remotes) < 1 or len(list(HostInfo.remotes.items())[0][1]) < 1: + self.search_button.set_sensitive(False) + self.search_button.set_active(False) + self.stack.set_visible_child(self.no_remotes) + return + else: + self.search_button.set_sensitive(True) + for install in HostInfo.installations: for remote in HostInfo.remotes[install]: row = RemoteRow(self, install, remote) self.current_remotes_group.add(row) self.current_remote_rows.append(row) + self.stack.set_visible_child(self.content_page) + def filter_remote(self, row): self.filter_setting.set_boolean("show-apps", True) self.filter_setting.set_boolean("show-runtimes", True) From 66b5cec7b1b184364a1d28c3ccc15ac401429885 Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 17:03:14 -0400 Subject: [PATCH 094/332] More remotes work --- src/remotes_page/remote_row.py | 2 +- src/remotes_page/remotes_page.blp | 35 +++++++++++++------- src/remotes_page/remotes_page.py | 53 +++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 6786c36..8cb94f8 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -24,7 +24,7 @@ class RemoteRow(Adw.ActionRow): HostInfo.clipboard.set(self.get_subtitle()) self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied name"))) elif row is self.remove: - print("remove") + self.parent_page.remove_remote(self) self.menu_pop.popdown() def idle_stuff(self): diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index a6549f8..93b9372 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -16,16 +16,6 @@ template $RemotesPage : Adw.NavigationPage { icon-name: "loupe-large-symbolic"; tooltip-text: _("Search Packages"); } - [end] - Button { - tooltip-text: _("Add Remote from File"); - sensitive: false; - Adw.ButtonContent { - can-shrink: true; - icon-name: "folder-open-symbolic"; - label: _("Open"); - } - } } [top] Adw.Clamp { @@ -65,9 +55,32 @@ template $RemotesPage : Adw.NavigationPage { description: _("Remotes available on your system"); } Adw.PreferencesGroup new_remotes_group { - title: _("Add Remotes"); + title: _("Add Popular Remotes"); description: _("Add new remotes to get more software"); } + Adw.PreferencesGroup other_remotes { + title: _("Add Other Remotes"); + Adw.ActionRow file_remote_row { + activatable: true; + sensitive: false; + title: _("Add a Repo File"); + subtitle: _("Open a downloaded repo file to add"); + [suffix] + Image { + icon-name: "plus-large-symbolic"; + } + } + Adw.ActionRow custom_remote_row { + activatable: true; + sensitive: false; + title: _("Add a Custom Remote"); + subtitle: _("Manually enter new remote details"); + [suffix] + Image { + icon-name: "plus-large-symbolic"; + } + } + } } } } diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 7cf1454..317d945 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -17,7 +17,6 @@ class NewRemoteRow(Adw.ActionRow): self.info = info GLib.idle_add(self.idle_stuff) self.set_activatable(True) - self.connect("activated", lambda *_: print(info["name"])) @Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remotes_page.ui") class RemotesPage(Adw.NavigationPage): @@ -111,7 +110,7 @@ class RemotesPage(Adw.NavigationPage): self.current_remotes_group.add(row) self.current_remote_rows.append(row) - self.stack.set_visible_child(self.content_page) + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.content_page)) def filter_remote(self, row): self.filter_setting.set_boolean("show-apps", True) @@ -122,6 +121,55 @@ class RemotesPage(Adw.NavigationPage): packages_page.filters_page.generate_filters() packages_page.apply_filters() GLib.idle_add(lambda *_: self.main_window.activate_row(self.main_window.packages_row)) + + def remove_remote(self, row): + error = [None] + def thread(*args): + install = row.installation + cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-delete', row.remote.name, '-y'] + if install == "user" or install == "system": + cmd.append(f"--{install}") + else: + cmd.append(f"--installation={install}") + + print(cmd) + + def on_response(_, response): + if response != "continue": + return + + thread() + + dialog = Adw.AlertDialog(heading=_("Remove {}?").format(row.remote.title), body=_("Any installed apps from {} will stop receiving updates").format(row.remote.name)) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Remove")) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.connect("response", on_response) + dialog.present(self.main_window) + + def add_remote(self, name, url_or_path, formatted_installation, title=None): + error = [None] + cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-add', name, url_or_path, formatted_installation] + if title: + cmd.append(title) + + def thread(*args): + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + error[0] = cpe.stderr + except Exception as e: + error[0] = e + + def callback(*args): + if error[0]: + self.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0])).toast) + else: + self.toast_overlay.add_toast(Adw.Toast(title=_("Added {}").format(title if title else name))) + self.start_loading() + self.end_loading() + + Gio.Task.new(None, None, callback).run_in_thread(thread) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -141,4 +189,5 @@ class RemotesPage(Adw.NavigationPage): # Apply for item in self.new_remotes: row = NewRemoteRow(item) + # row.connect("activated", lambda *_, row=row: self.add_remote()) self.new_remotes_group.add(row) \ No newline at end of file From 5ada9c5ca5d5ac85d087e34722282226b82d63ef Mon Sep 17 00:00:00 2001 From: heliguy Date: Sun, 21 Jul 2024 23:35:32 -0400 Subject: [PATCH 095/332] Initial code for adding remotes --- src/host_info.py | 7 +- src/meson.build | 2 + src/packages_page/filters_page.py | 16 +++-- src/remotes_page/add_remote_dialog.blp | 42 +++++++++++ src/remotes_page/add_remote_dialog.py | 30 ++++++++ src/remotes_page/remote_row.blp | 8 +++ src/remotes_page/remote_row.py | 96 +++++++++++++++++++++++--- src/remotes_page/remotes_page.py | 45 ++++++------ src/warehouse.gresource.xml | 1 + 9 files changed, 206 insertions(+), 41 deletions(-) create mode 100644 src/remotes_page/add_remote_dialog.blp create mode 100644 src/remotes_page/add_remote_dialog.py diff --git a/src/host_info.py b/src/host_info.py index a4b0f59..f247753 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -216,9 +216,10 @@ class Flatpak: class Remote: - def __init__(self, name, title): + def __init__(self, name, title, disabled): self.name = name self.title = title + self.disabled = disabled if title == "" or title == "-": self.title = name @@ -263,7 +264,7 @@ class HostInfo: # Remotes def remote_info(installation): cmd = ['flatpak-spawn', '--host', - 'flatpak', 'remotes', '--columns=name,title'] + 'flatpak', 'remotes', '--columns=name,title,options', '--show-disabled'] if installation == "user" or installation == "system": cmd.append(f"--{installation}") else: @@ -277,7 +278,7 @@ class HostInfo: remote_list = [] for line in lines: line = line.split("\t") - remote_list.append(Remote(line[0], line[1])) + remote_list.append(Remote(name=line[0], title=line[1], disabled=(len(line) == 3) and "disabled" in line[2])) this.remotes[installation] = remote_list # Masks diff --git a/src/meson.build b/src/meson.build index ad607ca..dc6cf2e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,7 @@ blueprints = custom_target('blueprints', 'user_data_page/data_subpage.blp', 'remotes_page/remotes_page.blp', 'remotes_page/remote_row.blp', + 'remotes_page/add_remote_dialog.blp', 'change_version_page/change_version_page.blp', ), output: '.', @@ -70,6 +71,7 @@ warehouse_sources = [ 'user_data_page/data_subpage.py', 'remotes_page/remotes_page.py', 'remotes_page/remote_row.py', + 'remotes_page/add_remote_dialog.py', '../data/style.css', ] diff --git a/src/packages_page/filters_page.py b/src/packages_page/filters_page.py index aa327f6..a4e1612 100644 --- a/src/packages_page/filters_page.py +++ b/src/packages_page/filters_page.py @@ -111,16 +111,18 @@ class FiltersPage(Adw.NavigationPage): for row in self.remote_rows: self.remotes_group.remove(row) self.remote_rows.clear() - if len(HostInfo.remotes) < 2 and len(list(HostInfo.remotes.items())[0][1]) < 2: - self.remotes_group.set_visible(False) - if self.remotes_string != "all": - self.remotes_string = "all" - self.settings.set_string("remotes-list", self.remotes_string) - self.packages_page.apply_filters() - return + # if len(HostInfo.remotes) < 2 and len(list(HostInfo.remotes.items())[0][1]) < 2: + # self.remotes_group.set_visible(False) + # if self.remotes_string != "all": + # self.remotes_string = "all" + # self.settings.set_string("remotes-list", self.remotes_string) + # self.packages_page.apply_filters() + # return for i, installation in enumerate(HostInfo.installations): try: for remote in HostInfo.remotes[installation]: + # if remote.disabled: + # continue row = FilterRow(remote, installation) row.set_title(remote.title) row.set_subtitle(_("Installation: {}").format(installation)) diff --git a/src/remotes_page/add_remote_dialog.blp b/src/remotes_page/add_remote_dialog.blp new file mode 100644 index 0000000..ad5c85b --- /dev/null +++ b/src/remotes_page/add_remote_dialog.blp @@ -0,0 +1,42 @@ +using Gtk 4.0; +using Adw 1; + +template $AddRemoteDialog : Adw.Dialog { + title: _("Add a Remote"); + content-width: 500; + content-height: 450; + Adw.ToolbarView { + [top] + Adw.HeaderBar { + } + [bottom] + ActionBar action_bar { + [center] + Button apply_button { + margin-top: 3; + margin-bottom: 3; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + icon-name: "plus-large-symbolic"; + label: _("Add Remote"); + } + } + } + Adw.PreferencesPage { + Adw.PreferencesGroup { + Adw.EntryRow title_row { + title: _("Enter Title"); + } + Adw.EntryRow name_row { + title: _("Enter Name"); + } + Adw.EntryRow url_row { + title: _("Enter Repo URL"); + } + Adw.ComboRow installation_row { + title: _("Installation"); + } + } + } + } +} \ No newline at end of file diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py new file mode 100644 index 0000000..23ff2a7 --- /dev/null +++ b/src/remotes_page/add_remote_dialog.py @@ -0,0 +1,30 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +import subprocess + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/add_remote_dialog.ui") +class AddRemoteDialog(Adw.Dialog): + __gtype_name__ = "AddRemoteDialog" + gtc = Gtk.Template.Child + + apply_button = gtc() + title_row = gtc() + name_row = gtc() + url_row = gtc() + installation_row = gtc() + + def on_apply(self, *args): + print(self.installation_row.get_selected_item().get_string()) + + def __init__(self, main_window, remote_info=None, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + self.string_list = Gtk.StringList(strings=HostInfo.installations) + + # Apply + self.installation_row.set_model(self.string_list) + + # Connections + self.apply_button.connect("clicked", self.on_apply) \ No newline at end of file diff --git a/src/remotes_page/remote_row.blp b/src/remotes_page/remote_row.blp index 257701a..914b535 100644 --- a/src/remotes_page/remote_row.blp +++ b/src/remotes_page/remote_row.blp @@ -39,6 +39,14 @@ Popover menu_pop { label: _("Copy Name"); halign: start; } + Label enable_remote { + label: _("Enable"); + halign: start; + } + Label disable_remote { + label: _("Disable"); + halign: start; + } Label remove { label: _("Remove"); halign: start; diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 8cb94f8..03230f5 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -1,5 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .host_info import HostInfo +from .error_toast import ErrorToast +import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remote_row.ui") class RemoteRow(Adw.ActionRow): @@ -13,23 +15,95 @@ class RemoteRow(Adw.ActionRow): copy_title = gtc() copy_name = gtc() + enable_remote = gtc() + disable_remote = gtc() remove = gtc() + def enable_remote_handler(self, *args): + def idle_stuff(*args): + self.remove_css_class("warning") + self.set_subtitle(_("Installation: {}").format(self.installation)) + self.remote.disabled = False + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Enabled remote"))) + self.menu_listbox.get_row_at_index(2).set_visible(False) + self.menu_listbox.get_row_at_index(3).set_visible(True) + + if not self.remote.disabled: + self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), _("Remote is already enabled")).toast) + return + + cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-modify', '--enable', self.remote.name] + if self.installation == "user" or self.installation == "system": + cmd.append(f"--{self.installation}") + else: + cmd.append(f"--installation={self.installation}") + + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + GLib.idle_add(lambda *args, cpe=cpe: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), str(cpe.stderr)).toast)) + return + except Exception as e: + GLib.idle_add(lambda *args, e=e: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), str(e)).toast)) + return + + idle_stuff() + + def disable_remote_handler(self, *args): + def idle_stuff(*args): + self.add_css_class("warning") + self.set_subtitle(_("Disabled")) + self.remote.disabled = True + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Disabled remote"))) + self.menu_listbox.get_row_at_index(2).set_visible(True) + self.menu_listbox.get_row_at_index(3).set_visible(False) + + if self.remote.disabled: + self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), _("Remote is already disabled")).toast) + return + + cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-modify', '--disable', self.remote.name] + if self.installation == "user" or self.installation == "system": + cmd.append(f"--{self.installation}") + else: + cmd.append(f"--installation={self.installation}") + + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + GLib.idle_add(lambda *args, cpe=cpe: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(cpe.stderr)).toast)) + return + except Exception as e: + GLib.idle_add(lambda *args, e=e: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(e)).toast)) + return + + idle_stuff() + def on_menu_action(self, listbox, row): row = row.get_child() - if row is self.copy_title: - HostInfo.clipboard.set(self.get_title()) - self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied title"))) - elif row is self.copy_name: - HostInfo.clipboard.set(self.get_subtitle()) - self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied name"))) - elif row is self.remove: - self.parent_page.remove_remote(self) + match row: + case self.copy_title: + HostInfo.clipboard.set(self.get_title()) + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied title"))) + case self.copy_name: + HostInfo.clipboard.set(self.get_subtitle()) + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied name"))) + case self.enable_remote: + Gio.Task().run_in_thread(self.enable_remote_handler) + case self.disable_remote: + Gio.Task().run_in_thread(self.disable_remote_handler) + case self.remove: + self.parent_page.remove_remote(self) + self.menu_pop.popdown() def idle_stuff(self): self.set_title(self.remote.title) - self.set_subtitle(_("Installation: {}").format(self.installation)) + if self.remote.disabled: + self.set_subtitle(_("Disabled")) + self.add_css_class("warning") + else: + self.set_subtitle(_("Installation: {}").format(self.installation)) self.suffix_label.set_label(self.remote.name) def __init__(self, parent_page, installation, remote, **kwargs): @@ -43,6 +117,10 @@ class RemoteRow(Adw.ActionRow): # Apply GLib.idle_add(lambda *_: self.idle_stuff()) + ## Show / Hide the Enable / Disable actions depending on remote status + self.menu_listbox.get_row_at_index(2).set_visible(remote.disabled) + self.menu_listbox.get_row_at_index(3).set_visible(not remote.disabled) + # Connections self.menu_listbox.connect("row-activated", self.on_menu_action) self.filter_button.connect("clicked", lambda *_: parent_page.filter_remote(self)) \ No newline at end of file diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 317d945..5c1ca48 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -2,6 +2,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast from .remote_row import RemoteRow +from .add_remote_dialog import AddRemoteDialog import subprocess class NewRemoteRow(Adw.ActionRow): @@ -147,29 +148,29 @@ class RemotesPage(Adw.NavigationPage): dialog.connect("response", on_response) dialog.present(self.main_window) - def add_remote(self, name, url_or_path, formatted_installation, title=None): - error = [None] - cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-add', name, url_or_path, formatted_installation] - if title: - cmd.append(title) + # def add_remote(self, name, url_or_path, formatted_installation, title=None): + # error = [None] + # cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-add', name, url_or_path, formatted_installation] + # if title: + # cmd.append(title) - def thread(*args): - try: - subprocess.run(cmd, check=True, capture_output=True, text=True) - except subprocess.CalledProcessError as cpe: - error[0] = cpe.stderr - except Exception as e: - error[0] = e + # def thread(*args): + # try: + # subprocess.run(cmd, check=True, capture_output=True, text=True) + # except subprocess.CalledProcessError as cpe: + # error[0] = cpe.stderr + # except Exception as e: + # error[0] = e - def callback(*args): - if error[0]: - self.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0])).toast) - else: - self.toast_overlay.add_toast(Adw.Toast(title=_("Added {}").format(title if title else name))) - self.start_loading() - self.end_loading() + # def callback(*args): + # if error[0]: + # self.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0])).toast) + # else: + # self.toast_overlay.add_toast(Adw.Toast(title=_("Added {}").format(title if title else name))) + # self.start_loading() + # self.end_loading() - Gio.Task.new(None, None, callback).run_in_thread(thread) + # Gio.Task.new(None, None, callback).run_in_thread(thread) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -186,8 +187,8 @@ class RemotesPage(Adw.NavigationPage): ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) - # Apply + # Appply for item in self.new_remotes: row = NewRemoteRow(item) - # row.connect("activated", lambda *_, row=row: self.add_remote()) + row.connect("activated", lambda *_: AddRemoteDialog(item).present(main_window)) self.new_remotes_group.add(row) \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 043a096..fc20f99 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -14,6 +14,7 @@ user_data_page/data_subpage.ui remotes_page/remotes_page.ui remotes_page/remote_row.ui + remotes_page/add_remote_dialog.ui From 9b26ad6c40152f8f904dd7a23dcc1440e69d09a2 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 00:05:44 -0400 Subject: [PATCH 096/332] Can now add preselected remotes --- src/host_info.py | 2 +- src/remotes_page/add_remote_dialog.blp | 3 +- src/remotes_page/add_remote_dialog.py | 38 ++++++++++++++++++++++++-- src/remotes_page/remotes_page.py | 2 +- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index f247753..707eaf9 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -273,7 +273,7 @@ class HostInfo: cmd, text=True, capture_output=True, ).stdout - lines = output.strip().replace(" ", "").split("\n") + lines = output.strip().split("\n") if lines[0] != '': remote_list = [] for line in lines: diff --git a/src/remotes_page/add_remote_dialog.blp b/src/remotes_page/add_remote_dialog.blp index ad5c85b..f69e4e1 100644 --- a/src/remotes_page/add_remote_dialog.blp +++ b/src/remotes_page/add_remote_dialog.blp @@ -4,7 +4,7 @@ using Adw 1; template $AddRemoteDialog : Adw.Dialog { title: _("Add a Remote"); content-width: 500; - content-height: 450; + content-height: 375; Adw.ToolbarView { [top] Adw.HeaderBar { @@ -15,6 +15,7 @@ template $AddRemoteDialog : Adw.Dialog { Button apply_button { margin-top: 3; margin-bottom: 3; + sensitive: false; styles ["pill", "suggested-action"] Adw.ButtonContent { icon-name: "plus-large-symbolic"; diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index 23ff2a7..50ca3bd 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -15,9 +15,35 @@ class AddRemoteDialog(Adw.Dialog): installation_row = gtc() def on_apply(self, *args): - print(self.installation_row.get_selected_item().get_string()) + error = [None] + def thread(*args): + cmd = [ + 'flatpak-spawn', '--host', + 'flatpak', 'remote-add', '--if-not-exists', + f'--title={self.title_row.get_text()}', + self.name_row.get_text(), + self.url_row.get_text() + ] + installation = self.installation_row.get_selected_item().get_string() + if installation == "user" or installation == "system": + cmd.append(f"--{installation}") + else: + cmd.append(f"--installation={installation}") + + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + error[0] = cpe.stderr + except Exception as e: + error[0] = e + + def callback(*args): + if error[0]: + print(error[0]) - def __init__(self, main_window, remote_info=None, **kwargs): + Gio.Task.new(None, None, callback).run_in_thread(thread) + + def __init__(self, main_window, parent_page, remote_info=None, **kwargs): super().__init__(**kwargs) # Extra Object Creation @@ -25,6 +51,14 @@ class AddRemoteDialog(Adw.Dialog): # Apply self.installation_row.set_model(self.string_list) + if remote_info: + self.title_row.set_text(remote_info["title"]) + self.title_row.set_editable(False) + self.name_row.set_text(remote_info["name"]) + self.name_row.set_editable(False) + self.url_row.set_text(remote_info["link"]) + self.url_row.set_editable(False) + self.apply_button.set_sensitive(True) # Connections self.apply_button.connect("clicked", self.on_apply) \ No newline at end of file diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 5c1ca48..669aa01 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -190,5 +190,5 @@ class RemotesPage(Adw.NavigationPage): # Appply for item in self.new_remotes: row = NewRemoteRow(item) - row.connect("activated", lambda *_: AddRemoteDialog(item).present(main_window)) + row.connect("activated", lambda *_: AddRemoteDialog(main_window, self, item).present(main_window)) self.new_remotes_group.add(row) \ No newline at end of file From 097fcdda9f91aec011cb7a34989af4aeb8adc915 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 00:13:46 -0400 Subject: [PATCH 097/332] Removal of remotes properly works --- src/remotes_page/remotes_page.py | 42 ++++++++++++-------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 669aa01..c774fa3 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -127,19 +127,31 @@ class RemotesPage(Adw.NavigationPage): error = [None] def thread(*args): install = row.installation - cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-delete', row.remote.name, '-y'] + cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-delete', row.remote.name, '--force'] if install == "user" or install == "system": cmd.append(f"--{install}") else: cmd.append(f"--installation={install}") - print(cmd) + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + error[0] = cpe.stderr + except Exception as e: + error[0] = e + + def callback(*args): + if error[0]: + self.toast_overlay.add_toast(ErrorToast(_("Could not remove remote"), str(error[0])).toast) + else: + self.toast_overlay.add_toast(Adw.Toast(title=_("Removed remote"))) + self.main_window.refresh_handler() def on_response(_, response): if response != "continue": return - thread() + Gio.Task.new(None, None, callback).run_in_thread(thread) dialog = Adw.AlertDialog(heading=_("Remove {}?").format(row.remote.title), body=_("Any installed apps from {} will stop receiving updates").format(row.remote.name)) dialog.add_response("cancel", _("Cancel")) @@ -148,30 +160,6 @@ class RemotesPage(Adw.NavigationPage): dialog.connect("response", on_response) dialog.present(self.main_window) - # def add_remote(self, name, url_or_path, formatted_installation, title=None): - # error = [None] - # cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-add', name, url_or_path, formatted_installation] - # if title: - # cmd.append(title) - - # def thread(*args): - # try: - # subprocess.run(cmd, check=True, capture_output=True, text=True) - # except subprocess.CalledProcessError as cpe: - # error[0] = cpe.stderr - # except Exception as e: - # error[0] = e - - # def callback(*args): - # if error[0]: - # self.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0])).toast) - # else: - # self.toast_overlay.add_toast(Adw.Toast(title=_("Added {}").format(title if title else name))) - # self.start_loading() - # self.end_loading() - - # Gio.Task.new(None, None, callback).run_in_thread(thread) - def __init__(self, main_window, **kwargs): super().__init__(**kwargs) From 64d34c3043c1f8cf8b019811217ef0135364318d Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 00:21:02 -0400 Subject: [PATCH 098/332] Adding remotes works more properly --- src/remotes_page/add_remote_dialog.py | 9 ++++++++- src/remotes_page/remotes_page.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index 50ca3bd..fbba099 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -15,6 +15,8 @@ class AddRemoteDialog(Adw.Dialog): installation_row = gtc() def on_apply(self, *args): + self.close() + self.parent_page.stack.set_visible_child(self.parent_page.loading_remotes) error = [None] def thread(*args): cmd = [ @@ -39,7 +41,10 @@ class AddRemoteDialog(Adw.Dialog): def callback(*args): if error[0]: - print(error[0]) + self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0]))) + else: + self.main_window.refresh_handler() + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Added {}").format(self.name_row.get_text()))) Gio.Task.new(None, None, callback).run_in_thread(thread) @@ -48,6 +53,8 @@ class AddRemoteDialog(Adw.Dialog): # Extra Object Creation self.string_list = Gtk.StringList(strings=HostInfo.installations) + self.main_window = main_window + self.parent_page = parent_page # Apply self.installation_row.set_model(self.string_list) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index c774fa3..9d5182a 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -144,8 +144,8 @@ class RemotesPage(Adw.NavigationPage): if error[0]: self.toast_overlay.add_toast(ErrorToast(_("Could not remove remote"), str(error[0])).toast) else: - self.toast_overlay.add_toast(Adw.Toast(title=_("Removed remote"))) self.main_window.refresh_handler() + self.toast_overlay.add_toast(Adw.Toast(title=_("Removed remote"))) def on_response(_, response): if response != "continue": From 6cfac026a2bbc3eec371cd5049bc4af7736cb07f Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 00:23:25 -0400 Subject: [PATCH 099/332] Don't always prefill to the last remote info item --- src/remotes_page/remotes_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 9d5182a..26fff8f 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -178,5 +178,5 @@ class RemotesPage(Adw.NavigationPage): # Appply for item in self.new_remotes: row = NewRemoteRow(item) - row.connect("activated", lambda *_: AddRemoteDialog(main_window, self, item).present(main_window)) + row.connect("activated", lambda *_, remote_info=item: AddRemoteDialog(main_window, self, remote_info).present(main_window)) self.new_remotes_group.add(row) \ No newline at end of file From 15584632d2e7d7ede21eb84dc78db28847060491 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 14:20:48 -0400 Subject: [PATCH 100/332] Fix some UI issues --- src/remotes_page/add_remote_dialog.blp | 37 +++++++++++++++++--------- src/remotes_page/add_remote_dialog.py | 18 ++++++++++--- src/remotes_page/remotes_page.py | 2 +- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/remotes_page/add_remote_dialog.blp b/src/remotes_page/add_remote_dialog.blp index f69e4e1..f7f1a9a 100644 --- a/src/remotes_page/add_remote_dialog.blp +++ b/src/remotes_page/add_remote_dialog.blp @@ -23,19 +23,32 @@ template $AddRemoteDialog : Adw.Dialog { } } } - Adw.PreferencesPage { - Adw.PreferencesGroup { - Adw.EntryRow title_row { - title: _("Enter Title"); + Adw.ToastOverlay toast_overlay { + Stack stack { + Adw.PreferencesPage content_page { + Adw.PreferencesGroup { + Adw.EntryRow title_row { + title: _("Enter Title"); + } + Adw.EntryRow name_row { + title: _("Enter Name"); + } + Adw.EntryRow url_row { + title: _("Enter Repo URL"); + } + Adw.ComboRow installation_row { + title: _("Installation"); + } + } } - Adw.EntryRow name_row { - title: _("Enter Name"); - } - Adw.EntryRow url_row { - title: _("Enter Repo URL"); - } - Adw.ComboRow installation_row { - title: _("Installation"); + Adw.StatusPage loading_page { + title: _("Adding Remote"); + description: _("This should only take a moment"); + child: + Spinner { + spinning: true; + } + ; } } } diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index fbba099..32fb0cb 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -8,15 +8,21 @@ class AddRemoteDialog(Adw.Dialog): __gtype_name__ = "AddRemoteDialog" gtc = Gtk.Template.Child + action_bar = gtc() + toast_overlay = gtc() + stack = gtc() apply_button = gtc() + content_page = gtc() title_row = gtc() name_row = gtc() url_row = gtc() installation_row = gtc() + loading_page = gtc() def on_apply(self, *args): - self.close() - self.parent_page.stack.set_visible_child(self.parent_page.loading_remotes) + self.stack.set_visible_child(self.loading_page) + self.apply_button.set_sensitive(False) + self.action_bar.set_revealed(False) error = [None] def thread(*args): cmd = [ @@ -41,10 +47,14 @@ class AddRemoteDialog(Adw.Dialog): def callback(*args): if error[0]: - self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0]))) + self.stack.set_visible_child(self.content_page) + self.action_bar.set_revealed(True) + self.apply_button.set_sensitive(True) + self.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0])).toast) else: + self.close() self.main_window.refresh_handler() - self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Added {}").format(self.name_row.get_text()))) + self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Added {}").format(self.title_row.get_text()))) Gio.Task.new(None, None, callback).run_in_thread(thread) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 26fff8f..99eb8bc 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -145,7 +145,7 @@ class RemotesPage(Adw.NavigationPage): self.toast_overlay.add_toast(ErrorToast(_("Could not remove remote"), str(error[0])).toast) else: self.main_window.refresh_handler() - self.toast_overlay.add_toast(Adw.Toast(title=_("Removed remote"))) + self.toast_overlay.add_toast(Adw.Toast(title=_("Removed {}").format(row.remote.title))) def on_response(_, response): if response != "continue": From f8c8dde16ff120caa3d3039b75d0af7d7c464dd6 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 15:46:46 -0400 Subject: [PATCH 101/332] Add ability to add custom remotes --- src/remotes_page/add_remote_dialog.py | 33 +++++++++++++++++++++++++-- src/remotes_page/remotes_page.blp | 1 - src/remotes_page/remotes_page.py | 14 ++++++++---- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index 32fb0cb..950fdd3 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast -import subprocess +import subprocess, re @Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/add_remote_dialog.ui") class AddRemoteDialog(Adw.Dialog): @@ -58,6 +58,23 @@ class AddRemoteDialog(Adw.Dialog): Gio.Task.new(None, None, callback).run_in_thread(thread) + def check_entries(self, row): + is_passing = re.match(self.rexes[row], row.get_text()) + if is_passing: + row.remove_css_class("error") + else: + row.add_css_class("error") + + match row: + case self.title_row: + self.title_passes = bool(is_passing) + case self.name_row: + self.name_passes = bool(is_passing) + case self.url_row: + self.url_passes = bool(is_passing) + + self.apply_button.set_sensitive(self.title_passes and self.name_passes and self.url_passes) + def __init__(self, main_window, parent_page, remote_info=None, **kwargs): super().__init__(**kwargs) @@ -65,6 +82,15 @@ class AddRemoteDialog(Adw.Dialog): self.string_list = Gtk.StringList(strings=HostInfo.installations) self.main_window = main_window self.parent_page = parent_page + + self.rexes = { + self.title_row: "^(?=.*[A-Za-z0-9])[A-Za-z0-9._-]+( +[A-Za-z0-9._-]+)*$", #"^(?=.*[A-Za-z0-9])[A-Za-z0-9._-]+( [A-Za-z0-9._-]+)*$", + self.name_row: "^[a-zA-Z0-9\-._]+$", + self.url_row: "^[a-zA-Z0-9\-._~:/?#[\]@!$&\'()*+,;=]+$" + } + self.title_passes = False + self.name_passes = False + self.url_passes = False # Apply self.installation_row.set_model(self.string_list) @@ -78,4 +104,7 @@ class AddRemoteDialog(Adw.Dialog): self.apply_button.set_sensitive(True) # Connections - self.apply_button.connect("clicked", self.on_apply) \ No newline at end of file + self.apply_button.connect("clicked", self.on_apply) + self.title_row.connect("changed", self.check_entries) + self.name_row.connect("changed", self.check_entries) + self.url_row.connect("changed", self.check_entries) \ No newline at end of file diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 93b9372..4dc354f 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -72,7 +72,6 @@ template $RemotesPage : Adw.NavigationPage { } Adw.ActionRow custom_remote_row { activatable: true; - sensitive: false; title: _("Add a Custom Remote"); subtitle: _("Manually enter new remote details"); [suffix] diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 99eb8bc..9abf009 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -78,6 +78,8 @@ class RemotesPage(Adw.NavigationPage): stack = gtc() current_remotes_group = gtc() new_remotes_group = gtc() + file_remote_row = gtc() + custom_remote_row = gtc() # Statuses loading_remotes = gtc() @@ -106,10 +108,13 @@ class RemotesPage(Adw.NavigationPage): self.search_button.set_sensitive(True) for install in HostInfo.installations: - for remote in HostInfo.remotes[install]: - row = RemoteRow(self, install, remote) - self.current_remotes_group.add(row) - self.current_remote_rows.append(row) + try: + for remote in HostInfo.remotes[install]: + row = RemoteRow(self, install, remote) + self.current_remotes_group.add(row) + self.current_remote_rows.append(row) + except KeyError: + continue GLib.idle_add(lambda *_: self.stack.set_visible_child(self.content_page)) @@ -174,6 +179,7 @@ class RemotesPage(Adw.NavigationPage): # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + self.custom_remote_row.connect("activated", lambda *_: AddRemoteDialog(main_window, self).present(main_window)) # Appply for item in self.new_remotes: From dc17f9d7c275434145baec57a3bde95589b805b3 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 15:52:06 -0400 Subject: [PATCH 102/332] Add confirmation to remove remote --- src/remotes_page/remote_row.py | 50 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 03230f5..3368498 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -50,7 +50,7 @@ class RemoteRow(Adw.ActionRow): idle_stuff() def disable_remote_handler(self, *args): - def idle_stuff(*args): + def callback(*args): self.add_css_class("warning") self.set_subtitle(_("Disabled")) self.remote.disabled = True @@ -58,26 +58,38 @@ class RemoteRow(Adw.ActionRow): self.menu_listbox.get_row_at_index(2).set_visible(True) self.menu_listbox.get_row_at_index(3).set_visible(False) - if self.remote.disabled: - self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), _("Remote is already disabled")).toast) - return + def thread(): + if self.remote.disabled: + self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), _("Remote is already disabled")).toast) + return - cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-modify', '--disable', self.remote.name] - if self.installation == "user" or self.installation == "system": - cmd.append(f"--{self.installation}") - else: - cmd.append(f"--installation={self.installation}") + cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-modify', '--disable', self.remote.name] + if self.installation == "user" or self.installation == "system": + cmd.append(f"--{self.installation}") + else: + cmd.append(f"--installation={self.installation}") - try: - subprocess.run(cmd, check=True, capture_output=True, text=True) - except subprocess.CalledProcessError as cpe: - GLib.idle_add(lambda *args, cpe=cpe: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(cpe.stderr)).toast)) - return - except Exception as e: - GLib.idle_add(lambda *args, e=e: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(e)).toast)) - return + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + GLib.idle_add(lambda *args, cpe=cpe: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(cpe.stderr)).toast)) + return + except Exception as e: + GLib.idle_add(lambda *args, e=e: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(e)).toast)) + return - idle_stuff() + def on_response(_, response): + if response != "continue": + return + + Gio.Task.new(None, None, callback).run_in_thread(thread) + + dialog = Adw.AlertDialog(heading=_("Disable {}?").format(row.remote.title), body=_("Any installed apps from {} will stop receiving updates").format(row.remote.name)) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Remove")) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.connect("response", on_response) + dialog.present(self.main_window) def on_menu_action(self, listbox, row): row = row.get_child() @@ -91,7 +103,7 @@ class RemoteRow(Adw.ActionRow): case self.enable_remote: Gio.Task().run_in_thread(self.enable_remote_handler) case self.disable_remote: - Gio.Task().run_in_thread(self.disable_remote_handler) + self.disable_remote_handler case self.remove: self.parent_page.remove_remote(self) From d9ab9491c6e3d8f87fe79bc8dff85080dc4f1ecd Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 15:55:08 -0400 Subject: [PATCH 103/332] Show error when adding an already added remote --- src/remotes_page/add_remote_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index 950fdd3..0e42812 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -27,7 +27,7 @@ class AddRemoteDialog(Adw.Dialog): def thread(*args): cmd = [ 'flatpak-spawn', '--host', - 'flatpak', 'remote-add', '--if-not-exists', + 'flatpak', 'remote-add', f'--title={self.title_row.get_text()}', self.name_row.get_text(), self.url_row.get_text() From fc41e5218b6c6e1f62b772e25ce2f46326bac357 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 16:11:28 -0400 Subject: [PATCH 104/332] Enable searching current remotes --- src/remotes_page/remotes_page.blp | 2 ++ src/remotes_page/remotes_page.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 4dc354f..4019d7d 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -55,10 +55,12 @@ template $RemotesPage : Adw.NavigationPage { description: _("Remotes available on your system"); } Adw.PreferencesGroup new_remotes_group { + visible: bind search_button.active inverted; title: _("Add Popular Remotes"); description: _("Add new remotes to get more software"); } Adw.PreferencesGroup other_remotes { + visible: bind search_button.active inverted; title: _("Add Other Remotes"); Adw.ActionRow file_remote_row { activatable: true; diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 9abf009..979a090 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -74,6 +74,7 @@ class RemotesPage(Adw.NavigationPage): sidebar_button = gtc() search_button = gtc() search_bar = gtc() + search_entry = gtc() toast_overlay = gtc() stack = gtc() current_remotes_group = gtc() @@ -83,6 +84,7 @@ class RemotesPage(Adw.NavigationPage): # Statuses loading_remotes = gtc() + no_results = gtc() no_remotes = gtc() content_page = gtc() @@ -103,6 +105,8 @@ class RemotesPage(Adw.NavigationPage): self.search_button.set_sensitive(False) self.search_button.set_active(False) self.stack.set_visible_child(self.no_remotes) + self.search_button.set_sensitive(False) + self.search_entry.set_editable(False) return else: self.search_button.set_sensitive(True) @@ -117,6 +121,8 @@ class RemotesPage(Adw.NavigationPage): continue GLib.idle_add(lambda *_: self.stack.set_visible_child(self.content_page)) + self.search_button.set_sensitive(True) + self.search_entry.set_editable(True) def filter_remote(self, row): self.filter_setting.set_boolean("show-apps", True) @@ -165,6 +171,17 @@ class RemotesPage(Adw.NavigationPage): dialog.connect("response", on_response) dialog.present(self.main_window) + def on_search(self, entry): + text = entry.get_text().lower() + total = 0 + for row in self.current_remote_rows: + visible = text in row.get_title().lower() or text in row.get_subtitle().lower() + row.set_visible(visible) + if visible: + total += 1 + + self.stack.set_visible_child(self.content_page if total > 0 else self.no_results) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -180,6 +197,7 @@ class RemotesPage(Adw.NavigationPage): ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.custom_remote_row.connect("activated", lambda *_: AddRemoteDialog(main_window, self).present(main_window)) + self.search_entry.connect("search-changed", self.on_search) # Appply for item in self.new_remotes: From 7aca643ad2752dd78d1654c6442636702bbcf0b1 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 16:22:53 -0400 Subject: [PATCH 105/332] Do not allow searching when no data is present --- src/user_data_page/data_subpage.py | 2 ++ src/user_data_page/user_data_page.blp | 2 +- src/user_data_page/user_data_page.py | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index ff48fcd..b846631 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -120,7 +120,9 @@ class DataSubpage(Gtk.Stack): if self.total_items == 1: self.subtitle.set_label(_("1 Item")) + self.parent_page.search_entry.set_editable(True) else: + self.parent_page.search_entry.set_editable(True) self.subtitle.set_label(_("{} Items").format(self.total_items)) self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 5f68dcc..512e52c 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -53,8 +53,8 @@ template $UserDataPage : Adw.BreakpointBin { Adw.Clamp { SearchBar search_bar { search-mode-enabled: bind search_button.active bidirectional; - SearchEntry search_entry { + editable: false; hexpand: true; placeholder-text: _("Search User Data"); } diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 5e74d7d..323b18a 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -50,7 +50,8 @@ class UserDataPage(Adw.BreakpointBin): self.data_flatpaks.append(HostInfo.id_to_flatpak[folder]) self.active_data.append(folder) except KeyError: - self.leftover_data.append(folder) + # self.leftover_data.append(folder) + pass def start_loading(self, *args): self.select_button.set_active(False) @@ -100,10 +101,12 @@ class UserDataPage(Adw.BreakpointBin): self.select_button.set_sensitive(False) self.sort_button.set_active(False) self.sort_button.set_sensitive(False) + self.search_entry.set_editable(False) else: self.search_button.set_sensitive(True) self.select_button.set_sensitive(True) self.sort_button.set_sensitive(True) + self.search_entry.set_editable(True) has_selected = len(child.selected_boxes) > 0 self.copy_button.set_sensitive(has_selected) From 49f557d9eea8889ce5adc68da7c9ec7102875a2d Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 16:35:18 -0400 Subject: [PATCH 106/332] Fix some searching stuff --- src/main_window/window.py | 2 ++ src/remotes_page/remotes_page.blp | 2 +- src/user_data_page/user_data_page.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main_window/window.py b/src/main_window/window.py index d42b5eb..b46fd1a 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -81,6 +81,8 @@ class WarehouseWindow(Adw.ApplicationWindow): idx += 1 if row.get_child() is nav_row: row.activate() + nav_row.grab_focus() + return def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 4019d7d..28da401 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -5,7 +5,7 @@ template $RemotesPage : Adw.NavigationPage { title: _("Manage Remotes"); Adw.ToolbarView { [top] - Adw.HeaderBar { + Adw.HeaderBar header_bar { [start] ToggleButton sidebar_button { icon-name: "dock-left-symbolic"; diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 323b18a..4dbc850 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -68,7 +68,6 @@ class UserDataPage(Adw.BreakpointBin): def callback(*args): self.adp.generate_list(self.data_flatpaks, self.active_data) self.ldp.generate_list([], self.leftover_data) - self.search_button.grab_focus() Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) From 1f6ae74063e89a5cc4fc7c338a3adc0b9c424034 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 17:58:06 -0400 Subject: [PATCH 107/332] Add ability to add local remotes --- src/remotes_page/add_remote_dialog.py | 13 ++++++++---- src/remotes_page/remotes_page.blp | 1 - src/remotes_page/remotes_page.py | 30 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index 0e42812..700d2cc 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -96,12 +96,17 @@ class AddRemoteDialog(Adw.Dialog): self.installation_row.set_model(self.string_list) if remote_info: self.title_row.set_text(remote_info["title"]) - self.title_row.set_editable(False) self.name_row.set_text(remote_info["name"]) - self.name_row.set_editable(False) self.url_row.set_text(remote_info["link"]) - self.url_row.set_editable(False) - self.apply_button.set_sensitive(True) + if remote_info["description"] == "local file": + self.check_entries(self.title_row) + self.check_entries(self.name_row) + self.check_entries(self.url_row) + else: + self.title_row.set_editable(False) + self.name_row.set_editable(False) + self.url_row.set_editable(False) + self.apply_button.set_sensitive(True) # Connections self.apply_button.connect("clicked", self.on_apply) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 28da401..2a32df8 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -64,7 +64,6 @@ template $RemotesPage : Adw.NavigationPage { title: _("Add Other Remotes"); Adw.ActionRow file_remote_row { activatable: true; - sensitive: false; title: _("Add a Repo File"); subtitle: _("Open a downloaded repo file to add"); [suffix] diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 979a090..0f94b5e 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -182,6 +182,35 @@ class RemotesPage(Adw.NavigationPage): self.stack.set_visible_child(self.content_page if total > 0 else self.no_results) + def file_callback(self, chooser, result): + try: + file = chooser.open_finish(result) + path = file.get_path() + name = path.split("/")[-1].split(".")[0] + info = { + "title": name.title(), + "name": name, + "description": "local file", + "link": path, + } + AddRemoteDialog(self.main_window, self, info).present(self.main_window) + except GLib.GError as ge: + if "Dismissed by user" in str(ge): + return + self.toast_overlay.add_toast(ErrorToast(_("Could not open file"), str(ge)).toast) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not open file"), str(e)).toast) + + def add_file_handler(self): + file_filter = Gtk.FileFilter(name=_("Flatpak Repos")) + 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(self.main_window, None, self.file_callback) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -196,6 +225,7 @@ class RemotesPage(Adw.NavigationPage): # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + 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.search_entry.connect("search-changed", self.on_search) From 8fb9d8c52b4d8c47d5e68812dc8d8ff4adb3d5e3 Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 18:07:56 -0400 Subject: [PATCH 108/332] Fix inability to disable remote --- src/remotes_page/remote_row.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 3368498..75615fe 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -84,12 +84,12 @@ class RemoteRow(Adw.ActionRow): Gio.Task.new(None, None, callback).run_in_thread(thread) - dialog = Adw.AlertDialog(heading=_("Disable {}?").format(row.remote.title), body=_("Any installed apps from {} will stop receiving updates").format(row.remote.name)) + dialog = Adw.AlertDialog(heading=_("Disable {}?").format(self.remote.title), body=_("Any installed apps from {} will stop receiving updates").format(self.remote.name)) dialog.add_response("cancel", _("Cancel")) dialog.add_response("continue", _("Remove")) dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) dialog.connect("response", on_response) - dialog.present(self.main_window) + dialog.present(self.parent_page.main_window) def on_menu_action(self, listbox, row): row = row.get_child() @@ -103,7 +103,7 @@ class RemoteRow(Adw.ActionRow): case self.enable_remote: Gio.Task().run_in_thread(self.enable_remote_handler) case self.disable_remote: - self.disable_remote_handler + self.disable_remote_handler() case self.remove: self.parent_page.remove_remote(self) From 02274e6e5acbf78ea3fac188a8fcd57977b84cce Mon Sep 17 00:00:00 2001 From: heliguy Date: Mon, 22 Jul 2024 18:49:52 -0400 Subject: [PATCH 109/332] Add Show Disabled button --- src/remotes_page/remote_row.py | 24 +++++++++++++++--------- src/remotes_page/remotes_page.blp | 10 ++++++++++ src/remotes_page/remotes_page.py | 24 +++++++++++++++++++++--- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 75615fe..74de3fe 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -22,11 +22,15 @@ class RemoteRow(Adw.ActionRow): def enable_remote_handler(self, *args): def idle_stuff(*args): self.remove_css_class("warning") - self.set_subtitle(_("Installation: {}").format(self.installation)) + self.set_icon_name("") self.remote.disabled = False self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Enabled remote"))) self.menu_listbox.get_row_at_index(2).set_visible(False) self.menu_listbox.get_row_at_index(3).set_visible(True) + self.parent_page.total_disabled -= 1 + if self.parent_page.total_disabled == 0: + self.parent_page.show_disabled_button.set_active(False) + self.parent_page.show_disabled_button.set_visible(False) if not self.remote.disabled: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), _("Remote is already enabled")).toast) @@ -52,13 +56,16 @@ class RemoteRow(Adw.ActionRow): def disable_remote_handler(self, *args): def callback(*args): self.add_css_class("warning") - self.set_subtitle(_("Disabled")) + self.set_icon_name("error-symbolic") self.remote.disabled = True self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Disabled remote"))) self.menu_listbox.get_row_at_index(2).set_visible(True) self.menu_listbox.get_row_at_index(3).set_visible(False) + self.set_visible(self.parent_page.show_disabled_button.get_active()) + self.parent_page.show_disabled_button.set_visible(True) + self.parent_page.total_disabled += 1 - def thread(): + def thread(*args): if self.remote.disabled: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), _("Remote is already disabled")).toast) return @@ -86,7 +93,7 @@ class RemoteRow(Adw.ActionRow): dialog = Adw.AlertDialog(heading=_("Disable {}?").format(self.remote.title), body=_("Any installed apps from {} will stop receiving updates").format(self.remote.name)) dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Remove")) + dialog.add_response("continue", _("Disable")) dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) dialog.connect("response", on_response) dialog.present(self.parent_page.main_window) @@ -111,12 +118,11 @@ class RemoteRow(Adw.ActionRow): def idle_stuff(self): self.set_title(self.remote.title) - if self.remote.disabled: - self.set_subtitle(_("Disabled")) - self.add_css_class("warning") - else: - self.set_subtitle(_("Installation: {}").format(self.installation)) + self.set_subtitle(_("Installation: {}").format(self.installation)) self.suffix_label.set_label(self.remote.name) + if self.remote.disabled: + self.set_icon_name("error-symbolic") + self.add_css_class("warning") def __init__(self, parent_page, installation, remote, **kwargs): super().__init__(**kwargs) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 2a32df8..801bd41 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -53,6 +53,16 @@ template $RemotesPage : Adw.NavigationPage { Adw.PreferencesGroup current_remotes_group { title: _("Current Remotes"); description: _("Remotes available on your system"); + header-suffix: + ToggleButton show_disabled_button { + valign: center; + styles ["flat"] + Adw.ButtonContent show_disabled_button_content { + icon-name: "eye-not-looking-symbolic"; + label: _("Show Disabled"); + } + } + ; } Adw.PreferencesGroup new_remotes_group { visible: bind search_button.active inverted; diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 0f94b5e..bb3ef4e 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -78,6 +78,8 @@ class RemotesPage(Adw.NavigationPage): toast_overlay = gtc() stack = gtc() current_remotes_group = gtc() + show_disabled_button = gtc() + show_disabled_button_content = gtc() new_remotes_group = gtc() file_remote_row = gtc() custom_remote_row = gtc() @@ -95,6 +97,7 @@ class RemotesPage(Adw.NavigationPage): def start_loading(self): self.stack.set_visible_child(self.loading_remotes) + self.total_disabled = 0 for row in self.current_remote_rows: self.current_remotes_group.remove(row) @@ -111,12 +114,18 @@ class RemotesPage(Adw.NavigationPage): else: self.search_button.set_sensitive(True) + show_disabled = self.show_disabled_button.get_active() + self.show_disabled_button.set_visible(False) for install in HostInfo.installations: try: for remote in HostInfo.remotes[install]: row = RemoteRow(self, install, remote) self.current_remotes_group.add(row) self.current_remote_rows.append(row) + if (not show_disabled) and remote.disabled: + row.set_visible(False) + self.total_disabled += 1 + self.show_disabled_button.set_visible(True) except KeyError: continue @@ -174,11 +183,14 @@ class RemotesPage(Adw.NavigationPage): def on_search(self, entry): text = entry.get_text().lower() total = 0 + show_disabled = self.show_disabled_button.get_active() + for row in self.current_remote_rows: - visible = text in row.get_title().lower() or text in row.get_subtitle().lower() + title_match = text in row.get_title().lower() + subtitle_match = text in row.get_subtitle().lower() + visible = (title_match or subtitle_match) and (show_disabled or not row.remote.disabled) + total += visible row.set_visible(visible) - if visible: - total += 1 self.stack.set_visible_child(self.content_page if total > 0 else self.no_results) @@ -211,6 +223,10 @@ class RemotesPage(Adw.NavigationPage): file_chooser.set_default_filter(file_filter) file_chooser.open(self.main_window, None, self.file_callback) + def show_disabled_handler(self, button): + self.show_disabled_button_content.set_icon_name("eye-open-negative-filled-symbolic" if button.get_active() else "eye-not-looking-symbolic") + self.on_search(self.search_entry) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -221,6 +237,7 @@ class RemotesPage(Adw.NavigationPage): self.search_bar.set_key_capture_widget(main_window) self.current_remote_rows = [] self.filter_setting = Gio.Settings.new("io.github.flattool.Warehouse.filter") + self.total_disabled = 0 # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) @@ -228,6 +245,7 @@ class RemotesPage(Adw.NavigationPage): 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.search_entry.connect("search-changed", self.on_search) + self.show_disabled_button.connect("toggled", self.show_disabled_handler) # Appply for item in self.new_remotes: From 9f15e033f5ec2c2477e0c25a365fa7afb20cfb84 Mon Sep 17 00:00:00 2001 From: heliguy4599 <95997226+heliguy4599@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:43:59 -0400 Subject: [PATCH 110/332] Use new Flathub badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0bd6dcd..3ca92bc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Warehouse is now available on Flathub! Visit your software store and search for Warehouse, or click this badge. -Download on Flathub +Download on Flathub ## 🗣️ Translation - Translation is hosted with Weblate on Fyra Labs, [click here](https://weblate.fyralabs.com/projects/flattool/warehouse/) to contribute From 38771d52f2577755c0b56079cc5e4d15b8764f6f Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 25 Jul 2024 22:43:38 -0400 Subject: [PATCH 111/332] Fix issue in data page --- src/user_data_page/user_data_page.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 4dbc850..f8b0f55 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -50,8 +50,7 @@ class UserDataPage(Adw.BreakpointBin): self.data_flatpaks.append(HostInfo.id_to_flatpak[folder]) self.active_data.append(folder) except KeyError: - # self.leftover_data.append(folder) - pass + self.leftover_data.append(folder) def start_loading(self, *args): self.select_button.set_active(False) From ff835136dd53725cd6084e4cc2434d9f90a1d4d8 Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 25 Jul 2024 23:33:49 -0400 Subject: [PATCH 112/332] fix uninstall --- src/properties_page/properties_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index e58088c..afb89d2 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -216,7 +216,7 @@ class PropertiesPage(Adw.NavigationPage): self.toast_overlay.add_toast(ErrorToast(_("Could not uninstall"), str(fail)).toast) self.packages_page.stack.set_visible_child(self.packages_page.packages_split) else: - self.packages_page.refresh_handler() + self.main_window.refresh_handler() self.packages_page.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled {}").format(self.package.info["name"]))) # name = self.package.info["name"] From d25b22ce43a9b9f46e0c5a41b0cdd632af60045f Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 25 Jul 2024 23:34:46 -0400 Subject: [PATCH 113/332] Initial UI for snapshots page --- src/main_window/window.py | 3 +++ src/meson.build | 2 ++ src/snapshot_page/snapshot_page.blp | 33 +++++++++++++++++++++++++++++ src/snapshot_page/snapshot_page.py | 21 ++++++++++++++++++ src/warehouse.gresource.xml | 1 + 5 files changed, 60 insertions(+) create mode 100644 src/snapshot_page/snapshot_page.blp create mode 100644 src/snapshot_page/snapshot_page.py diff --git a/src/main_window/window.py b/src/main_window/window.py index b46fd1a..d3c7296 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -27,6 +27,7 @@ from .host_info import HostInfo from .packages_page import PackagesPage from .remotes_page import RemotesPage from .user_data_page import UserDataPage +from .snapshot_page import SnapshotPage from .const import Config from .error_toast import ErrorToast @@ -95,6 +96,8 @@ class WarehouseWindow(Adw.ApplicationWindow): self.packages_row: PackagesPage(main_window=self), self.remotes_row: RemotesPage(main_window=self), self.user_data_row: UserDataPage(main_window=self), + self.snapshots_row: SnapshotPage(main_window=self), + # self.install_row: None, } for _, page in self.pages.items(): diff --git a/src/meson.build b/src/meson.build index dc6cf2e..3a9efa1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -17,6 +17,7 @@ blueprints = custom_target('blueprints', 'remotes_page/remote_row.blp', 'remotes_page/add_remote_dialog.blp', 'change_version_page/change_version_page.blp', + 'snapshot_page/snapshot_page.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -72,6 +73,7 @@ warehouse_sources = [ 'remotes_page/remotes_page.py', 'remotes_page/remote_row.py', 'remotes_page/add_remote_dialog.py', + 'snapshot_page/snapshot_page.py', '../data/style.css', ] diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp new file mode 100644 index 0000000..782c224 --- /dev/null +++ b/src/snapshot_page/snapshot_page.blp @@ -0,0 +1,33 @@ +using Gtk 4.0; +using Adw 1; + +template $SnapshotPage : Adw.BreakpointBin { + width-request: 1; + height-request: 1; + + Adw.Breakpoint packages_bpt { + condition ("max-width: 600") + + setters { + split_view.collapsed: true; + split_view.show-content: false; + } + } + + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage nav_page { + title: _("Snapshots"); + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView sidebar_tbv { + [top] + Adw.HeaderBar { + } + } + } + } + ; + } +} \ No newline at end of file diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py new file mode 100644 index 0000000..04598dd --- /dev/null +++ b/src/snapshot_page/snapshot_page.py @@ -0,0 +1,21 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_page.ui") +class SnapshotPage(Adw.BreakpointBin): + __gtype_name__ = "SnapshotPage" + gtc = Gtk.Template.Child + + instance = None + + def start_loading(self): + pass + + def end_loading(self): + pass + + def __init__(self, main_window, **kwargs): + super().__init__(**kwargs) + + self.__class__.instance = self \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index fc20f99..942bf4c 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -15,6 +15,7 @@ remotes_page/remotes_page.ui remotes_page/remote_row.ui remotes_page/add_remote_dialog.ui + snapshot_page/snapshot_page.ui From a2d9f38f8424a2d7757fa4557e84ad747a19b61c Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 26 Jul 2024 10:42:14 -0400 Subject: [PATCH 114/332] work on snapshots page --- src/main_window/window.py | 2 +- src/snapshot_page/snapshot_page.blp | 113 ++++++++++++++++++++++++---- src/snapshot_page/snapshot_page.py | 20 ++++- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/src/main_window/window.py b/src/main_window/window.py index d3c7296..eb28a61 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -121,7 +121,7 @@ class WarehouseWindow(Adw.ApplicationWindow): # file_drop.connect("drop", self.drop_callback) self.refresh_button.connect("clicked", self.refresh_handler) - self.activate_row(self.remotes_row) + self.activate_row(self.snapshots_row) self.main_split.set_show_sidebar(True) self.start_loading() diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 782c224..3e6ea1d 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -5,7 +5,7 @@ template $SnapshotPage : Adw.BreakpointBin { width-request: 1; height-request: 1; - Adw.Breakpoint packages_bpt { + Adw.Breakpoint bp1 { condition ("max-width: 600") setters { @@ -14,20 +14,107 @@ template $SnapshotPage : Adw.BreakpointBin { } } - Adw.NavigationSplitView split_view { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage nav_page { - title: _("Snapshots"); - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView sidebar_tbv { - [top] - Adw.HeaderBar { + // Adw.Breakpoint bp2 { + // condition ("max-width: 700") + + // setters { + // header_bar.title-widget: null; + // switcher_bar.reveal: true; + // } + // } + + Adw.ToastOverlay toast_overlay { + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage sidebar_navpage { + title: _("Snapshots"); + Adw.BreakpointBin { + width-request: 1; + height-request: 1; + + Adw.Breakpoint bp2 { + condition ("max-width: 595") + + setters { + header_bar.title-widget: null; + switcher_bar.reveal: true; + } + } + + Adw.ToolbarView sidebar_tbv { + [top] + Adw.HeaderBar header_bar { + title-widget: + Adw.ViewSwitcher { + stack: view_stack; + policy: wide; + } + ; + [start] + ToggleButton sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); + } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select Packages"); + } + } + [bottom] + Adw.ViewSwitcherBar switcher_bar { + stack: view_stack; + } + Adw.ViewStack view_stack { + Adw.ViewStackPage { + title: _("Active Snapshots"); + child: + ScrolledWindow { + ListBox { + styles ["navigation-sidebar"] + Adw.ActionRow { + title: "sussy baka"; + } + } + } + ; + } + Adw.ViewStackPage { + title: _("Leftover Snapshots"); + child: + ScrolledWindow { + ListBox { + styles ["navigation-sidebar"] + Adw.ActionRow { + title: "skibidi toilet"; + } + } + } + ; + } + } } } } - } - ; + ; + content: + Adw.NavigationPage content_navpage { + title: _("Content 8===D~"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + + } + } + } + ; + } } } \ No newline at end of file diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 04598dd..98a3073 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -7,10 +7,15 @@ class SnapshotPage(Adw.BreakpointBin): __gtype_name__ = "SnapshotPage" gtc = Gtk.Template.Child + sidebar_button = 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 def start_loading(self): - pass + self.snapshot_rows.clear() def end_loading(self): pass @@ -18,4 +23,15 @@ class SnapshotPage(Adw.BreakpointBin): def __init__(self, main_window, **kwargs): super().__init__(**kwargs) - self.__class__.instance = self \ No newline at end of file + # Extra Object Creation + ms = main_window.main_split + self.__class__.instance = self + self.main_window = main_window + self.snapshot_rows = [] + + # Connections + ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) + self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + + # Apply + self.sidebar_button.set_active(ms.get_show_sidebar()) \ No newline at end of file From 8449b08a627354292e20d2bc6043b3f6d45beb22 Mon Sep 17 00:00:00 2001 From: heliguy Date: Fri, 26 Jul 2024 21:10:46 -0400 Subject: [PATCH 115/332] sync current work --- src/meson.build | 2 + src/snapshot_page/snapshot_page.blp | 116 ++++++++++------------ src/snapshot_page/snapshots_list_page.blp | 6 ++ src/snapshot_page/snapshots_list_page.py | 8 ++ src/warehouse.gresource.xml | 1 + 5 files changed, 71 insertions(+), 62 deletions(-) create mode 100644 src/snapshot_page/snapshots_list_page.blp create mode 100644 src/snapshot_page/snapshots_list_page.py diff --git a/src/meson.build b/src/meson.build index 3a9efa1..3d86269 100644 --- a/src/meson.build +++ b/src/meson.build @@ -18,6 +18,7 @@ blueprints = custom_target('blueprints', 'remotes_page/add_remote_dialog.blp', 'change_version_page/change_version_page.blp', 'snapshot_page/snapshot_page.blp', + 'snapshot_page/snapshots_list_page.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -74,6 +75,7 @@ warehouse_sources = [ 'remotes_page/remote_row.py', 'remotes_page/add_remote_dialog.py', 'snapshot_page/snapshot_page.py', + 'snapshot_page/snapshots_list_page.py', '../data/style.css', ] diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 3e6ea1d..d0b3ded 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -30,74 +30,67 @@ template $SnapshotPage : Adw.BreakpointBin { sidebar: Adw.NavigationPage sidebar_navpage { title: _("Snapshots"); - Adw.BreakpointBin { - width-request: 1; - height-request: 1; - Adw.Breakpoint bp2 { - condition ("max-width: 595") - - setters { - header_bar.title-widget: null; - switcher_bar.reveal: true; + Adw.ToolbarView sidebar_tbv { + [top] + Adw.HeaderBar header_bar { + [start] + ToggleButton sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); + } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } + [end] + Button new_button { + icon-name: "plus-large-symbolic"; + tooltip-text: _("New Snapshot"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select Packages"); } } - - Adw.ToolbarView sidebar_tbv { - [top] - Adw.HeaderBar header_bar { - title-widget: - Adw.ViewSwitcher { - stack: view_stack; - policy: wide; - } - ; - [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); + ScrolledWindow scrolled_window { + Box { + orientation: vertical; + margin-start: 12; + margin-end: 12; + Label { + label: _("Active Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; } - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Packages"); + Label { + label: _("Snapshots of installed apps"); + halign: start; + styles ["dim-label"] } - [end] - ToggleButton select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select Packages"); + ListBox active_listbox { + styles ["navigation-sidebar"] + valign: start; } - } - [bottom] - Adw.ViewSwitcherBar switcher_bar { - stack: view_stack; - } - Adw.ViewStack view_stack { - Adw.ViewStackPage { - title: _("Active Snapshots"); - child: - ScrolledWindow { - ListBox { - styles ["navigation-sidebar"] - Adw.ActionRow { - title: "sussy baka"; - } - } - } - ; + Label { + label: _("Leftover Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; } - Adw.ViewStackPage { - title: _("Leftover Snapshots"); - child: - ScrolledWindow { - ListBox { - styles ["navigation-sidebar"] - Adw.ActionRow { - title: "skibidi toilet"; - } - } - } - ; + Label { + label: _("Snapshots of apps that are no longer installed"); + halign: start; + styles ["dim-label"] + } + ListBox leftover_listbox { + styles ["navigation-sidebar"] + valign: start; } } } @@ -110,7 +103,6 @@ template $SnapshotPage : Adw.BreakpointBin { Adw.ToolbarView { [top] Adw.HeaderBar { - } } } diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp new file mode 100644 index 0000000..ac9097d --- /dev/null +++ b/src/snapshot_page/snapshots_list_page.blp @@ -0,0 +1,6 @@ +using Gtk 4.0; +using Adw 1; + +template $SnapshotsListPage : Adw.NavigationPage { + title: _("Snapshots"); +} \ No newline at end of file diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py new file mode 100644 index 0000000..8997cd5 --- /dev/null +++ b/src/snapshot_page/snapshots_list_page.py @@ -0,0 +1,8 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshots_list_page.ui") +class SnapshotsListPage(Adw.NavigationPage): + __gtype_name__ = "SnapshotsListPage" + gtc = Gtk.Template.Child \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 942bf4c..a4e12f1 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -16,6 +16,7 @@ remotes_page/remote_row.ui remotes_page/add_remote_dialog.ui snapshot_page/snapshot_page.ui + snapshot_page/snapshots_list_page.ui From 5ca4d71f95588ece3557673a9e20eb8c78e70854 Mon Sep 17 00:00:00 2001 From: heliguy Date: Tue, 30 Jul 2024 19:24:07 -0400 Subject: [PATCH 116/332] Further work on snapshots --- src/meson.build | 2 + src/snapshot_page/snapshot_box.blp | 69 +++++++++++++ src/snapshot_page/snapshot_box.py | 13 +++ src/snapshot_page/snapshot_page.blp | 93 +++++++++-------- src/snapshot_page/snapshot_page.py | 118 +++++++++++++++++++++- src/snapshot_page/snapshots_list_page.blp | 35 ++++++- src/snapshot_page/snapshots_list_page.py | 33 +++++- src/warehouse.gresource.xml | 1 + 8 files changed, 316 insertions(+), 48 deletions(-) create mode 100644 src/snapshot_page/snapshot_box.blp create mode 100644 src/snapshot_page/snapshot_box.py diff --git a/src/meson.build b/src/meson.build index 3d86269..8fbafc1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -19,6 +19,7 @@ blueprints = custom_target('blueprints', 'change_version_page/change_version_page.blp', 'snapshot_page/snapshot_page.blp', 'snapshot_page/snapshots_list_page.blp', + 'snapshot_page/snapshot_box.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -76,6 +77,7 @@ warehouse_sources = [ 'remotes_page/add_remote_dialog.py', 'snapshot_page/snapshot_page.py', 'snapshot_page/snapshots_list_page.py', + 'snapshot_page/snapshot_box.py', '../data/style.css', ] diff --git a/src/snapshot_page/snapshot_box.blp b/src/snapshot_page/snapshot_box.blp new file mode 100644 index 0000000..66c6461 --- /dev/null +++ b/src/snapshot_page/snapshot_box.blp @@ -0,0 +1,69 @@ +using Gtk 4.0; +using Adw 1; + +template $SnapshotBox : Gtk.Box { + orientation: vertical; + spacing: 6; + Box { + margin-start: 12; + margin-end: 12; + margin-top: 6; + spacing: 12; + Box { + orientation: vertical; + Label title { + label: "A Snapshot"; + wrap: true; + justify: left; + halign: start; + styles ["title-4"] + } + Label date { + label: "2024-04-03 4:30 PM"; + wrap: true; + justify: left; + halign: start; + } + } + Label version { + label: "Version: 45.3"; + wrap: true; + justify: right; + hexpand: true; + } + } + Box { + margin-start: 6; + margin-end: 6; + margin-bottom: 6; + spacing: 3; + homogeneous: true; + Button { + Adw.ButtonContent { + label: "Apply"; + icon-name: "check-plain-symbolic"; + can-shrink: true; + } + hexpand: true; + styles ["flat"] + } + Button { + Adw.ButtonContent { + label: "Rename"; + icon-name: "dot-symbolic"; + can-shrink: true; + } + hexpand: true; + styles ["flat"] + } + Button { + Adw.ButtonContent { + label: "Trash"; + icon-name: "user-trash-symbolic"; + can-shrink: true; + } + hexpand: true; + styles ["flat"] + } + } +} \ No newline at end of file diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py new file mode 100644 index 0000000..f7c208e --- /dev/null +++ b/src/snapshot_page/snapshot_box.py @@ -0,0 +1,13 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +import os + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_box.ui") +class SnapshotBox(Gtk.Box): + __gtype_name__ = "SnapshotBox" + + def __init__(self, folder, **kwargs): + super().__init__(**kwargs) + + print(folder) \ No newline at end of file diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index d0b3ded..09f4c44 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -58,55 +58,64 @@ template $SnapshotPage : Adw.BreakpointBin { ScrolledWindow scrolled_window { Box { orientation: vertical; - margin-start: 12; - margin-end: 12; - Label { - label: _("Active Snapshots"); - halign: start; - styles ["heading"] - margin-top: 3; - margin-bottom: 6; + + Box active_box { + orientation: vertical; + + Label { + label: _("Active Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; + margin-start: 12; + margin-end: 12; + wrap: true; + } + Label { + label: _("Snapshots of installed apps"); + halign: start; + styles ["dim-label"] + margin-start: 12; + margin-end: 12; + wrap: true; + } + ListBox active_listbox { + styles ["navigation-sidebar"] + valign: start; + } } - Label { - label: _("Snapshots of installed apps"); - halign: start; - styles ["dim-label"] - } - ListBox active_listbox { - styles ["navigation-sidebar"] - valign: start; - } - Label { - label: _("Leftover Snapshots"); - halign: start; - styles ["heading"] - margin-top: 3; - margin-bottom: 6; - } - Label { - label: _("Snapshots of apps that are no longer installed"); - halign: start; - styles ["dim-label"] - } - ListBox leftover_listbox { - styles ["navigation-sidebar"] - valign: start; + Box leftover_box { + orientation: vertical; + + Label { + label: _("Leftover Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; + margin-start: 12; + margin-end: 12; + wrap: true; + } + Label { + label: _("Snapshots of apps that are no longer installed"); + halign: start; + styles ["dim-label"] + margin-start: 12; + margin-end: 12; + wrap: true; + } + ListBox leftover_listbox { + styles ["navigation-sidebar"] + valign: start; + } } } } } } ; - content: - Adw.NavigationPage content_navpage { - title: _("Content 8===D~"); - Adw.ToolbarView { - [top] - Adw.HeaderBar { - } - } - } - ; } } } \ No newline at end of file diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 98a3073..4767458 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -1,6 +1,28 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast +from .app_row import AppRow +from .snapshots_list_page import SnapshotsListPage +import os, subprocess + +class LeftoverSnapshotRow(Adw.ActionRow): + __gtype_name__ = "LeftoverSnapshotRow" + + def idle_stuff(self): + self.set_title(self.folder.split('.')[-1]) + icon = Gtk.Image.new_from_icon_name("application-x-executable-symbolic") + icon.set_icon_size(Gtk.IconSize.LARGE) + self.add_prefix(icon) + self.add_suffix(self.check_button) + + def __init__(self, folder, **kwargs): + super().__init__(**kwargs) + + self.set_activatable(True) + self.folder = folder + self.check_button = Gtk.CheckButton(visible=False) + self.check_button.add_css_class("selection-mode") + GLib.idle_add(lambda *_: self.idle_stuff()) @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_page.ui") class SnapshotPage(Adw.BreakpointBin): @@ -8,17 +30,98 @@ class SnapshotPage(Adw.BreakpointBin): gtc = Gtk.Template.Child sidebar_button = gtc() + active_box = gtc() + active_listbox = gtc() + leftover_box = gtc() + leftover_listbox = gtc() + split_view = 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 + snapshots_path = f"{HostInfo.home}/.var/app/io.github.flattool.Warehouse/data/Snapshots/" + + def sort_snapshots(self, *args): + self.active_snapshot_paks.clear() + self.leftover_snapshots.clear() + bad_folders = [] + + for folder in os.listdir(self.snapshots_path): + if folder.count('.') < 2 or ' ' in folder: + bad_folders.append(folder) + continue + + has_tar = False + for file in os.listdir(f"{self.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'{self.snapshots_path}{folder}']) + except Exception: + pass + + def generate_active_list(self): + for pak in self.active_snapshot_paks: + row = AppRow(pak) + self.active_listbox.append(row) + + if len(self.active_snapshot_paks) > 0: + self.active_box.set_visible(True) + first_row = self.active_listbox.get_row_at_index(0) + 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) + + self.leftover_listbox.append(row) + + if len(self.leftover_snapshots) > 0: + self.leftover_box.set_visible(True) + if len(self.active_snapshot_paks) == 0: + first_row = self.leftover_box.get_row_at_index(0) + self.leftover_box.select_row(first_row) + else: + self.leftover_box.set_visible(False) + + def active_select_handler(self, listbox, row): + self.leftover_listbox.select_row(None) + self.list_page.set_snapshots(row.package.info["id"], row.get_title()) + + def leftover_select_handler(self, listbox, row): + self.active_listbox.select_row(None) + self.list_page.set_snapshots(row.folder, row.get_title()) def start_loading(self): - self.snapshot_rows.clear() + # self.list_page.start_loading() + 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): - pass + def callback(*args): + self.generate_active_list() + self.generate_leftover_list() + # self.list_page.end_loading() + + Gio.Task.new(None, None, callback).run_in_thread(self.sort_snapshots) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -27,11 +130,18 @@ class SnapshotPage(Adw.BreakpointBin): ms = main_window.main_split self.__class__.instance = self self.main_window = main_window - self.snapshot_rows = [] + self.active_snapshot_paks = [] + # self.active_rows = [] + self.leftover_snapshots = [] + # self.leftover_rows = [] + self.list_page = SnapshotsListPage(self) # Connections ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + self.active_listbox.connect("row-activated", self.active_select_handler) + self.leftover_listbox.connect("row-activated", self.leftover_select_handler) # Apply - self.sidebar_button.set_active(ms.get_show_sidebar()) \ No newline at end of file + self.sidebar_button.set_active(ms.get_show_sidebar()) + self.split_view.set_content(self.list_page) \ No newline at end of file diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index ac9097d..a3344e8 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -2,5 +2,38 @@ using Gtk 4.0; using Adw 1; template $SnapshotsListPage : Adw.NavigationPage { - title: _("Snapshots"); + title: _("Snapshots List"); + Adw.ToastOverlay { + Adw.ToolbarView { + [top] + Adw.HeaderBar { + } + ScrolledWindow { + Adw.Clamp { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + ListBox listbox { + valign: start; + selection-mode: none; + styles ["boxed-list"] + } + } + } + [bottom] + ActionBar { + [center] + Button new_button { + margin-top: 3; + margin-bottom: 3; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + icon-name: "plus-large-symbolic"; + label: _("New Snapshot"); + } + } + } + } + } } \ No newline at end of file diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 8997cd5..f09dfd9 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -1,8 +1,39 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast +from .snapshot_box import SnapshotBox +import os + @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshots_list_page.ui") class SnapshotsListPage(Adw.NavigationPage): __gtype_name__ = "SnapshotsListPage" - gtc = Gtk.Template.Child \ No newline at end of file + gtc = Gtk.Template.Child + + listbox = gtc() + + snapshots_path = f"{HostInfo.home}/.var/app/io.github.flattool.Warehouse/data/Snapshots/" + + def thread(self, *args): + for snapshot in os.listdir(f"{self.snapshots_path}{self.current_folder}"): + row = SnapshotBox(snapshot) + self.snapshots_rows.append(row) + + def callback(self, *args): + for i, row in enumerate(self.snapshots_rows): + self.listbox.append(row) + self.listbox.get_row_at_index(i).set_activatable(False) + + def set_snapshots(self, folder, title): + self.current_folder = folder + self.set_title(_("{} Snapshots").format(title)) + self.snapshots_rows.clear() + self.listbox.remove_all() + + Gio.Task.new(None, None, self.callback).run_in_thread(self.thread) + + def __init__(self, parent_page, **kwargs): + super().__init__(**kwargs) + + self.current_folder = None + self.snapshots_rows = [] \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index a4e12f1..49cbf8f 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -17,6 +17,7 @@ remotes_page/add_remote_dialog.ui snapshot_page/snapshot_page.ui snapshot_page/snapshots_list_page.ui + snapshot_page/snapshot_box.ui From 9cd78bb78d4b5fb32209f6e5608696de9b046c5c Mon Sep 17 00:00:00 2001 From: heliguy Date: Tue, 30 Jul 2024 22:28:19 -0400 Subject: [PATCH 117/332] Check that the path exists before preceding --- src/user_data_page/user_data_page.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index f8b0f55..9e9b663 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -45,6 +45,9 @@ class UserDataPage(Adw.BreakpointBin): self.leftover_data.clear() # paks = dict(HostInfo.id_to_flatpak) + if not os.path.exists(f"{HostInfo.home}/.var/app"): + return + for folder in os.listdir(f"{HostInfo.home}/.var/app"): try: self.data_flatpaks.append(HostInfo.id_to_flatpak[folder]) From 5c8a7cd81fbeb7a885fe7499cd031b532fcbf408 Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 1 Aug 2024 12:11:10 -0400 Subject: [PATCH 118/332] Sort packages page irregardless of casing --- src/packages_page/packages_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index ee4712c..ca5b17e 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -274,7 +274,7 @@ class PackagesPage(Adw.BreakpointBin): self.set_status(self.no_results) def sort_func(self, row1, row2): - return row1.package.info["name"] > row2.package.info["name"] + return row1.package.info["name"].lower() > row2.package.info["name"].lower () def __init__(self, main_window, **kwargs): super().__init__(**kwargs) From 4ac03696c680eae6d1bb19797e7cf9d338ba728c Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 1 Aug 2024 12:28:13 -0400 Subject: [PATCH 119/332] Catch errors more properly when loading packages --- src/gtk/error_toast.py | 4 +- src/host_info.py | 100 ++++++++++--------- src/main_window/window.blp | 200 +++++++++++++++++++------------------ src/main_window/window.py | 6 +- 4 files changed, 159 insertions(+), 151 deletions(-) diff --git a/src/gtk/error_toast.py b/src/gtk/error_toast.py index f22d33b..75e6251 100644 --- a/src/gtk/error_toast.py +++ b/src/gtk/error_toast.py @@ -1,5 +1,4 @@ from gi.repository import Adw, Gtk, Gdk, GLib -from .host_info import HostInfo class ErrorToast: main_window = None @@ -7,11 +6,12 @@ class ErrorToast: def on_response(dialog, response_id): if response_id == "copy": - HostInfo.clipboard.set(error_msg) + self.clipboard.set(error_msg) # Extra Object Creation self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) popup = Adw.AlertDialog.new(display_msg) + self.clipboard = Gdk.Display.get_default().get_clipboard() # Apply print(display_msg) diff --git a/src/host_info.py b/src/host_info.py index 707eaf9..db1b9df 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -1,7 +1,6 @@ -import subprocess, os, pathlib - from gi.repository import Gio, Gtk, GLib, Adw, Gdk -# from .app_row import AppRow +from .error_toast import ErrorToast +import subprocess, os, pathlib home = f"{pathlib.Path.home()}" icon_theme = Gtk.IconTheme.new() @@ -311,52 +310,57 @@ class HostInfo: if lines[0] != '': this.pins[installation] = lines - # Installations - # Get all config files for any extra installations - custom_install_config_path = "/run/host/etc/flatpak/installations.d" - if os.path.exists(custom_install_config_path): - for file in os.listdir(custom_install_config_path): - with open(f"{custom_install_config_path}/{file}", "r") as f: - for line in f: - if line.startswith("[Installation"): - # Get specifically the installation name itself - this.installations.append(line.replace("[Installation \"", "").replace("\"]", "").strip()) + try: + # Installations + # Get all config files for any extra installations + custom_install_config_path = "/run/host/etc/flatpak/installations.d" + if os.path.exists(custom_install_config_path): + for file in os.listdir(custom_install_config_path): + with open(f"{custom_install_config_path}/{file}", "r") as f: + for line in f: + if line.startswith("[Installation"): + # Get specifically the installation name itself + this.installations.append(line.replace("[Installation \"", "").replace("\"]", "").strip()) - this.installations.append("user") - this.installations.append("system") - for i in this.installations: - remote_info(i) - remote_info("user") - remote_info("system") + this.installations.append("user") + this.installations.append("system") + for i in this.installations: + remote_info(i) + remote_info("user") + remote_info("system") - # Packages - output = subprocess.run( - ['flatpak-spawn', '--host', - 'flatpak', 'list', '--columns=all'], - text=True, - capture_output=True, - ).stdout - lines = output.strip().split("\n") - for i in lines: - package = Flatpak(i.split("\t")) - this.flatpaks.append(package) - this.id_to_flatpak[package.info["id"]] = package - this.ref_to_flatpak[package.info["ref"]] = package - - # Dependant Runtimes - output = subprocess.run( - ['flatpak-spawn', '--host', - 'flatpak', 'list', '--columns=runtime'], - text=True, - capture_output=True, - ).stdout - lines = output.strip().split("\n") - for index, runtime in enumerate(lines): - package = this.flatpaks[index] - if package.is_runtime: - continue - package.dependant_runtime = this.ref_to_flatpak[runtime] - if not runtime in this.dependant_runtime_refs: - this.dependant_runtime_refs.append(runtime) + # Packages + output = subprocess.run( + ['flatpak-spawn', '--host', + 'flatpak', 'list', '--columns=all'], + text=True, check=True, + capture_output=True, + ).stdout + lines = output.strip().split("\n") + for i in lines: + package = Flatpak(i.split("\t")) + this.flatpaks.append(package) + this.id_to_flatpak[package.info["id"]] = package + this.ref_to_flatpak[package.info["ref"]] = package + + # Dependant Runtimes + output = subprocess.run( + ['flatpak-spawn', '--host', + 'flatpak', 'list', '--columns=runtime'], + text=True, check=True, + capture_output=True, + ).stdout + lines = output.strip().split("\n") + for index, runtime in enumerate(lines): + package = this.flatpaks[index] + if package.is_runtime: + continue + package.dependant_runtime = this.ref_to_flatpak[runtime] + if not runtime in this.dependant_runtime_refs: + this.dependant_runtime_refs.append(runtime) + except subprocess.CalledProcessError as cpe: + this.main_window.toast_overlay.add_toast(ErrorToast(_("Could not load pacakges"), cpe.stderr).toast) + except Exception as e: + this.main_window.toast_overlay.add_toast(ErrorToast(_("Could not load pacakges"), str(e)).toast) Gio.Task.new(None, None, callback).run_in_thread(thread) \ No newline at end of file diff --git a/src/main_window/window.blp b/src/main_window/window.blp index e36c75e..4d62690 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -16,124 +16,126 @@ template $WarehouseWindow: Adw.ApplicationWindow { } } content: - Adw.OverlaySplitView main_split { - collapsed: true; - show-sidebar: true; - sidebar-width-fraction: 0.2; - // max-sidebar-width: 280; - min-sidebar-width: 250; - sidebar: - Adw.NavigationPage { - title: "Warehouse"; - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - [start] - Button refresh_button { - icon-name: "arrow-circular-top-right-symbolic"; - tooltip-text: _("Refresh List"); + Adw.ToastOverlay toast_overlay { + Adw.OverlaySplitView main_split { + collapsed: true; + show-sidebar: true; + sidebar-width-fraction: 0.2; + // max-sidebar-width: 280; + min-sidebar-width: 250; + sidebar: + Adw.NavigationPage { + title: "Warehouse"; + Adw.ToolbarView main_toolbar_view { + [top] + Adw.HeaderBar header_bar { + [start] + Button refresh_button { + icon-name: "arrow-circular-top-right-symbolic"; + tooltip-text: _("Refresh List"); + } + [end] + MenuButton main_menu { + icon-name: "open-menu-symbolic"; + tooltip-text: _("Main Menu"); + menu-model: primary_menu; + } } - [end] - MenuButton main_menu { - icon-name: "open-menu-symbolic"; - tooltip-text: _("Main Menu"); - menu-model: primary_menu; - } - } - content: - ScrolledWindow { - ListBox navigation_row_listbox { - styles ["navigation-sidebar"] + content: + ScrolledWindow { + ListBox navigation_row_listbox { + styles ["navigation-sidebar"] - Box packages_row { - margin-top: 12; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - spacing: 12; + Box packages_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; - Image icon { - icon-name: "flatpak-symbolic"; + Image icon { + icon-name: "flatpak-symbolic"; + } + + Label { + label: _("Packages"); + } } - - Label { - label: _("Packages"); - } - } - - Box remotes_row { - margin-top: 12; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - spacing: 12; - - Image { - icon-name: "server-pick-symbolic"; - } - - Label { - label: _("Remotes"); - } - } - Box user_data_row { - margin-top: 12; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - spacing: 12; + Box remotes_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; - Image { - icon-name: "file-manager-symbolic"; - } + Image { + icon-name: "server-pick-symbolic"; + } - Label { - label: _("User Data"); + Label { + label: _("Remotes"); + } } - } - - Box snapshots_row { - margin-top: 12; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - spacing: 12; + + Box user_data_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; - Image { - icon-name: "snapshots-alt-symbolic"; - } + Image { + icon-name: "file-manager-symbolic"; + } - Label { - label: _("Snapshots"); + Label { + label: _("User Data"); + } } - } - Box install_row { - margin-top: 12; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - spacing: 12; + Box snapshots_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; - Image { - icon-name: "arrow-pointing-at-line-down-symbolic"; + Image { + icon-name: "snapshots-alt-symbolic"; + } + + Label { + label: _("Snapshots"); + } } + + Box install_row { + margin-top: 12; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; + spacing: 12; - Label { - label: _("Install Packages"); + Image { + icon-name: "arrow-pointing-at-line-down-symbolic"; + } + + Label { + label: _("Install Packages"); + } } } } - } - ; + ; + } } - } - ; - content: - Stack stack { - } - ; + ; + content: + Stack stack { + } + ; + } } ; } diff --git a/src/main_window/window.py b/src/main_window/window.py index eb28a61..74d8c72 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -36,6 +36,7 @@ class WarehouseWindow(Adw.ApplicationWindow): __gtype_name__ = "WarehouseWindow" gtc = Gtk.Template.Child main_breakpoint = gtc() + toast_overlay = gtc() main_split = gtc() stack = gtc() refresh_button = gtc() @@ -89,6 +90,8 @@ class WarehouseWindow(Adw.ApplicationWindow): super().__init__(**kwargs) # Extra Object Creation + HostInfo.main_window = self + ErrorToast.main_window = self self.settings = Gio.Settings.new("io.github.flattool.Warehouse") event_controller = Gtk.EventControllerKey() file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) @@ -104,7 +107,6 @@ class WarehouseWindow(Adw.ApplicationWindow): self.stack.add_child(page) # Apply - ErrorToast.main_window = self self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT) self.settings.bind("window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT) self.settings.bind("is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT) @@ -121,7 +123,7 @@ class WarehouseWindow(Adw.ApplicationWindow): # file_drop.connect("drop", self.drop_callback) self.refresh_button.connect("clicked", self.refresh_handler) - self.activate_row(self.snapshots_row) + self.activate_row(self.packages_row) self.main_split.set_show_sidebar(True) self.start_loading() From dca70a3924bbd15b531b43d216e793b9cc76ef2e Mon Sep 17 00:00:00 2001 From: heliguy Date: Thu, 1 Aug 2024 16:00:59 -0400 Subject: [PATCH 120/332] Select frist visible row after packages load --- src/packages_page/packages_page.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index ca5b17e..2993805 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -95,7 +95,7 @@ class PackagesPage(Adw.BreakpointBin): 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 - GLib.idle_add(row.set_visible, visible) + row.set_visible(visible) if visible: total_visible += 1 else: @@ -150,11 +150,18 @@ 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) - first_row = self.packages_list_box.get_row_at_index(0) - self.packages_list_box.select_row(first_row) - self.properties_page.set_properties(first_row.package) - self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top self.apply_filters() + first_visible_row = None + i = 0 + while row := self.packages_list_box.get_row_at_index(i): + i += 1 + if row.get_visible(): + first_visible_row = row + break + + self.packages_list_box.select_row(first_visible_row) + self.properties_page.set_properties(first_visible_row.package) + 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) From 6bbbcef53049904419c445532836574a70efd103 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 13 Aug 2024 15:05:59 -0400 Subject: [PATCH 121/332] Sync current work --- src/packages_page/filters_page.blp | 4 +- src/snapshot_page/snapshot_box.blp | 22 ++-- src/snapshot_page/snapshot_box.py | 58 +++++++++- src/snapshot_page/snapshot_page.blp | 123 +++++++++++++--------- src/snapshot_page/snapshot_page.py | 44 +++++++- src/snapshot_page/snapshots_list_page.blp | 2 +- src/snapshot_page/snapshots_list_page.py | 8 +- 7 files changed, 188 insertions(+), 73 deletions(-) diff --git a/src/packages_page/filters_page.blp b/src/packages_page/filters_page.blp index cc87cd8..c453265 100644 --- a/src/packages_page/filters_page.blp +++ b/src/packages_page/filters_page.blp @@ -35,7 +35,7 @@ template $FiltersPage : Adw.NavigationPage { Adw.PreferencesGroup remotes_group { title: _("Filter by Remotes"); - description: _("Showing packages from selected remotes"); + description: _("Show packages from selected remotes"); header-suffix: Switch all_remotes_switch { valign: center; @@ -45,6 +45,7 @@ template $FiltersPage : Adw.NavigationPage { visible: bind all_remotes_switch.active inverted; [child] Box { + spacing: 3; orientation: vertical; Label { margin-top: 7; @@ -78,6 +79,7 @@ template $FiltersPage : Adw.NavigationPage { visible: bind all_runtimes_switch.active inverted; [child] Box { + spacing: 3; orientation: vertical; Label { margin-top: 7; diff --git a/src/snapshot_page/snapshot_box.blp b/src/snapshot_page/snapshot_box.blp index 66c6461..962d736 100644 --- a/src/snapshot_page/snapshot_box.blp +++ b/src/snapshot_page/snapshot_box.blp @@ -12,21 +12,21 @@ template $SnapshotBox : Gtk.Box { Box { orientation: vertical; Label title { - label: "A Snapshot"; + label: _("No Name Set"); wrap: true; justify: left; halign: start; styles ["title-4"] } Label date { - label: "2024-04-03 4:30 PM"; + label: _("No date found"); wrap: true; justify: left; halign: start; } } Label version { - label: "Version: 45.3"; + label: _("No version found"); wrap: true; justify: right; hexpand: true; @@ -38,27 +38,27 @@ template $SnapshotBox : Gtk.Box { margin-bottom: 6; spacing: 3; homogeneous: true; - Button { + Button apply_button { Adw.ButtonContent { - label: "Apply"; + label: _("Apply"); icon-name: "check-plain-symbolic"; can-shrink: true; } hexpand: true; styles ["flat"] } - Button { + Button rename_button { Adw.ButtonContent { - label: "Rename"; + label: _("Rename"); icon-name: "dot-symbolic"; can-shrink: true; } hexpand: true; styles ["flat"] } - Button { + Button trash_button { Adw.ButtonContent { - label: "Trash"; + label: _("Trash"); icon-name: "user-trash-symbolic"; can-shrink: true; } @@ -66,4 +66,6 @@ template $SnapshotBox : Gtk.Box { styles ["flat"] } } -} \ No newline at end of file +} + +Adw.AlertDialog name_dialog {} \ No newline at end of file diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index f7c208e..f8651f6 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -1,13 +1,65 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast -import os +import os, json @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_box.ui") class SnapshotBox(Gtk.Box): __gtype_name__ = "SnapshotBox" + gtc = Gtk.Template.Child - def __init__(self, folder, **kwargs): + title = gtc() + date = gtc() + version = gtc() + apply_button = gtc() + rename_button = gtc() + trash_button = gtc() + + def create_json(self): + try: + data = { + 'snapshot_version': 1, + 'name': '', + } + with open(self.json_path, 'w') as file: + json.dump(data, file, indent=4) + return None + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast) + + def update_json(self): + try: + with open(self.json_path, 'r+') as file: + data = json.load(file) + data['name'] = "updated" + 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 json_handler(self, *args): + if not os.path.exists(self.json_path): + self.create_json() + + self.update_json() + + def __init__(self, folder, snapshots_path, toast_overlay, **kwargs): super().__init__(**kwargs) - print(folder) \ No newline at end of file + self.toast_overlay = toast_overlay + + split_folder = folder.split('_') + if len(split_folder) < 2: + return + + date_data = GLib.DateTime.new_from_unix_local(int(split_folder[0])).format("%x %X") + self.date.set_label(date_data) + self.version.set_label(split_folder[1].replace(".tar.zst", "")) + + self.json_path = f"{snapshots_path}{folder.replace('tar.zst', 'json')}" + + self.rename_button.connect("clicked", self.json_handler) + print(self.json_path) + \ No newline at end of file diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 09f4c44..9d0ca73 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -55,60 +55,81 @@ template $SnapshotPage : Adw.BreakpointBin { tooltip-text: _("Select Packages"); } } - ScrolledWindow scrolled_window { - Box { - orientation: vertical; - - Box active_box { + Stack stack { + Adw.StatusPage loading_snapshots { + title: _("Loading Snapshot"); + description: _("This should only take a moment"); + child: + Spinner { + spinning: true; + } + ; + } + Adw.StatusPage no_snapshots { + title: _("No Snapshots Found"); + description: _("Warehouse cannot see the list of snapshots or you don't have any snapshots"); + icon-name: "error-symbolic"; + } + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; + } + ScrolledWindow scrolled_window { + Box { orientation: vertical; - Label { - label: _("Active Snapshots"); - halign: start; - styles ["heading"] - margin-top: 3; - margin-bottom: 6; - margin-start: 12; - margin-end: 12; - wrap: true; + Box active_box { + orientation: vertical; + + Label { + label: _("Active Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; + margin-start: 12; + margin-end: 12; + wrap: true; + } + Label { + label: _("Snapshots of installed apps"); + halign: start; + styles ["dim-label"] + margin-start: 12; + margin-end: 12; + wrap: true; + } + ListBox active_listbox { + styles ["navigation-sidebar"] + valign: start; + } } - Label { - label: _("Snapshots of installed apps"); - halign: start; - styles ["dim-label"] - margin-start: 12; - margin-end: 12; - wrap: true; - } - ListBox active_listbox { - styles ["navigation-sidebar"] - valign: start; - } - } - Box leftover_box { - orientation: vertical; - - Label { - label: _("Leftover Snapshots"); - halign: start; - styles ["heading"] - margin-top: 3; - margin-bottom: 6; - margin-start: 12; - margin-end: 12; - wrap: true; - } - Label { - label: _("Snapshots of apps that are no longer installed"); - halign: start; - styles ["dim-label"] - margin-start: 12; - margin-end: 12; - wrap: true; - } - ListBox leftover_listbox { - styles ["navigation-sidebar"] - valign: start; + Box leftover_box { + orientation: vertical; + + Label { + label: _("Leftover Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; + margin-start: 12; + margin-end: 12; + wrap: true; + } + Label { + label: _("Snapshots of apps that are no longer installed"); + halign: start; + styles ["dim-label"] + margin-start: 12; + margin-end: 12; + wrap: true; + } + ListBox leftover_listbox { + styles ["navigation-sidebar"] + valign: start; + } } } } diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 4767458..7ad23cc 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -30,11 +30,17 @@ class SnapshotPage(Adw.BreakpointBin): gtc = Gtk.Template.Child sidebar_button = gtc() + toast_overlay = gtc() active_box = gtc() active_listbox = gtc() leftover_box = gtc() leftover_listbox = gtc() split_view = gtc() + stack = gtc() + loading_snapshots = gtc() + no_snapshots = gtc() + no_results = gtc() + scrolled_window = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -46,6 +52,13 @@ class SnapshotPage(Adw.BreakpointBin): self.active_snapshot_paks.clear() self.leftover_snapshots.clear() bad_folders = [] + + if not os.path.exists(self.snapshots_path): + try: + os.makedirs(self.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(self.snapshots_path): if folder.count('.') < 2 or ' ' in folder: @@ -83,20 +96,21 @@ class SnapshotPage(Adw.BreakpointBin): self.active_box.set_visible(True) first_row = self.active_listbox.get_row_at_index(0) self.active_listbox.select_row(first_row) + self.stack.set_visible_child(self.scrolled_window) else: self.active_box.set_visible(False) def generate_leftover_list(self): for folder in self.leftover_snapshots: row = LeftoverSnapshotRow(folder) - self.leftover_listbox.append(row) if len(self.leftover_snapshots) > 0: self.leftover_box.set_visible(True) if len(self.active_snapshot_paks) == 0: - first_row = self.leftover_box.get_row_at_index(0) - self.leftover_box.select_row(first_row) + self.stack.set_visible_child(self.scrolled_window) + first_row = self.leftover_listbox.get_row_at_index(0) + self.leftover_listbox.select_row(first_row) else: self.leftover_box.set_visible(False) @@ -114,12 +128,32 @@ class SnapshotPage(Adw.BreakpointBin): self.active_listbox.remove_all() self.leftover_box.set_visible(True) self.leftover_listbox.remove_all() + self.stack.set_visible_child(self.loading_snapshots) def end_loading(self): def callback(*args): self.generate_active_list() self.generate_leftover_list() - # self.list_page.end_loading() + if (not self.active_box.get_visible()) and (not self.leftover_box.get_visible()): + self.stack.set_visible_child(self.no_snapshots) + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # Gio.Task.new(None, None, callback).run_in_thread(self.sort_snapshots) @@ -144,4 +178,4 @@ class SnapshotPage(Adw.BreakpointBin): # Apply self.sidebar_button.set_active(ms.get_show_sidebar()) - self.split_view.set_content(self.list_page) \ No newline at end of file + self.split_view.set_content(self.list_page)## \ No newline at end of file diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index a3344e8..768f5fb 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -3,7 +3,7 @@ using Adw 1; template $SnapshotsListPage : Adw.NavigationPage { title: _("Snapshots List"); - Adw.ToastOverlay { + Adw.ToastOverlay toast_overlay { Adw.ToolbarView { [top] Adw.HeaderBar { diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index f09dfd9..c559ece 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -11,12 +11,13 @@ class SnapshotsListPage(Adw.NavigationPage): gtc = Gtk.Template.Child listbox = gtc() + toast_overlay = gtc() snapshots_path = f"{HostInfo.home}/.var/app/io.github.flattool.Warehouse/data/Snapshots/" def thread(self, *args): - for snapshot in os.listdir(f"{self.snapshots_path}{self.current_folder}"): - row = SnapshotBox(snapshot) + for snapshot in os.listdir(folder := f"{self.snapshots_path}{self.current_folder}/"): + row = SnapshotBox(snapshot, folder, self.toast_overlay) self.snapshots_rows.append(row) def callback(self, *args): @@ -25,6 +26,9 @@ class SnapshotsListPage(Adw.NavigationPage): self.listbox.get_row_at_index(i).set_activatable(False) def set_snapshots(self, folder, title): + if self.current_folder == folder: + return + self.current_folder = folder self.set_title(_("{} Snapshots").format(title)) self.snapshots_rows.clear() From ab6ed8bad99d8d0df777ada1cd48f656f460682f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 18 Aug 2024 16:26:43 -0400 Subject: [PATCH 122/332] Sync current work --- data/icons/arrow-turn-down-right-symbolic.svg | 2 + data/icons/minus-large-symbolic.svg | 2 + src/install_page/install_page.blp | 163 ++++++++++++++++++ src/install_page/install_page.py | 94 ++++++++++ src/main_window/window.py | 5 +- src/meson.build | 2 + src/warehouse.gresource.xml | 3 + 7 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 data/icons/arrow-turn-down-right-symbolic.svg create mode 100644 data/icons/minus-large-symbolic.svg create mode 100644 src/install_page/install_page.blp create mode 100644 src/install_page/install_page.py diff --git a/data/icons/arrow-turn-down-right-symbolic.svg b/data/icons/arrow-turn-down-right-symbolic.svg new file mode 100644 index 0000000..09bd65f --- /dev/null +++ b/data/icons/arrow-turn-down-right-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/minus-large-symbolic.svg b/data/icons/minus-large-symbolic.svg new file mode 100644 index 0000000..09943ae --- /dev/null +++ b/data/icons/minus-large-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp new file mode 100644 index 0000000..2165e85 --- /dev/null +++ b/src/install_page/install_page.blp @@ -0,0 +1,163 @@ +using Gtk 4.0; +using Adw 1; + +template $InstallPage : Adw.BreakpointBin { + width-request: 1; + height-request: 1; + + Adw.Breakpoint bp1 { + condition ("max-width: 600") + + setters { + split_view.collapsed: true; + split_view.show-content: false; + } + } + + Adw.ToastOverlay toast_overlay { + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage sidebar_navpage { + title: _("Search for Packages"); + + Gtk.Stack sb_stack { + Adw.ToolbarView no_remotes { + visible: false; + [top] + Adw.HeaderBar { + show-title: false; + } + Adw.StatusPage { + icon-name: "error-symbolic"; + title: _("No Remotes Found"); + description: _("Warehouse cannot see the current remotes or your system has no remotes added"); + Button open_remotes_page_button { + label: _("Add Remotes"); + halign: center; + styles ["pill"] + } + } + } + + Adw.NavigationView sb_page_view { + Adw.NavigationPage { + title: _("Install Packages"); + + Adw.ToolbarView { + [top] + Adw.HeaderBar { + } + Adw.PreferencesPage { + Adw.PreferencesGroup remotes_group { + title: "Choose a Remote"; + description: "Choose a remote to search for packages in"; + } + } + } + } + + Adw.NavigationPage results { + title: _("Search REMOTE"); + + Adw.ToolbarView sidebar_tbv { + [top] + Adw.HeaderBar header_bar { + show-back-button: false; + [start] + ToggleButton sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); + } + [start] + Button back_button { + icon-name: "left-large-symbolic"; + tooltip-text: _("Back"); + } + } + [top] + Adw.Clamp { + maximum-size: 577; + Box { + margin-top: 3; + margin-bottom: 3; + margin-start: 6; + margin-end: 6; + styles ["linked"] + Gtk.SearchEntry search_entry { + search-delay: 700; + halign: fill; + hexpand: true; + placeholder-text: _("Search for Packages"); + } + Button search_apply_button { + icon-name: "right-large-symbolic"; + } + } + } + ScrolledWindow { + Adw.Clamp { + ListBox results_list { + styles["boxed-list"] + valign: start; + selection-mode: none; + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + } + } + } + } + } + } + } + } + ; + content: + Adw.NavigationPage results_page { + title: _("Pending Packages"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + [start] + Button clear_button { + label: _("Remove All"); + } + } + [bottom] + Box { + homogeneous: true; + styles ["toolbar"] + Button remove_all_button { + styles ["raised"] + Adw.ButtonContent { + can-shrink: true; + icon-name: "minus-large-symbolic"; + label: _("Remove All"); + } + } + Button open_button { + styles ["raised"] + Adw.ButtonContent { + can-shrink: true; + icon-name: "folder-open-symbolic"; + label: _("Add File"); + } + } + Button install_button { + styles ["raised", "suggested-action"] + Adw.ButtonContent { + can-shrink: true; + icon-name: "arrow-pointing-at-line-down-symbolic"; + label: _("Install"); + } + } + } + } + } + ; + } + } +} \ No newline at end of file diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py new file mode 100644 index 0000000..cb18d3c --- /dev/null +++ b/src/install_page/install_page.py @@ -0,0 +1,94 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +from .app_row import AppRow +from .snapshots_list_page import SnapshotsListPage +import os, subprocess + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/install_page.ui") +class InstallPage(Adw.BreakpointBin): + __gtype_name__ = "InstallPage" + gtc = Gtk.Template.Child + + remotes_group = gtc() + sb_page_view = gtc() + results = gtc() + back_button = gtc() + search_entry = gtc() + search_apply_button = gtc() + results_list = 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 + + current_installation = "" + current_remote = None + + def start_loading(self): + pass + + def end_loading(self): + for installation in HostInfo.installations: + for remote in HostInfo.remotes[installation]: + if remote.disabled: + continue + row = Adw.ActionRow(title=remote.title, subtitle=_("Installation: {}").format(installation), activatable=True) + row.add_suffix(Gtk.Image(icon_name="right-large-symbolic")) + row.connect("activated", self.remote_selected, installation, remote) + self.remotes_group.add(row) + + def remote_selected(self, row, installation, remote): + self.current_installation = installation + self.current_remote = remote + self.results.set_title(_("Search {}").format(remote.title)) + self.sb_page_view.push(self.results) + self.search_entry.set_text("firefox") + self.search_entry.grab_focus() + + def on_search(self, _): + text = self.search_entry.get_text().strip().lower().replace(" ", "") + if not text: + return + + results = [] + def thread(*args): + installation = "" + self.results_list.remove_all() + if self.current_installation == "user" or self.current_installation == "system": + installation = f"--{self.current_installation}" + else: + installation = f"--installation={self.current_installation}" + + try: + output = subprocess.run(['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', installation, text], text=True, check=True, capture_output=True).stdout.split("\n") + for line in output: + info = line.split("\t") + if len(info) != 6: + continue + + name = GLib.markup_escape_text(info[0]) + description = GLib.markup_escape_text(info[1]) + app_id = GLib.markup_escape_text(info[2]) + version = GLib.markup_escape_text(info[3]) + branch = GLib.markup_escape_text(info[4]) + remotes = GLib.markup_escape_text(info[5]) + row = Adw.ActionRow(title=name, subtitle=app_id) + + self.results_list.append(row) + + except Exception as e: + print(e) + except subprocess.CalledProcessError as cpe: + print(cpe) + + thread() + + def __init__(self, main_window, **kwargs): + super().__init__(**kwargs) + self.instance = self + self.back_button.connect("clicked", lambda *_: self.sb_page_view.pop()) + # self.search_entry.connect("search-changed", self.on_search) + self.search_entry.connect("activate", self.on_search) + self.search_apply_button.connect("clicked", self.on_search) \ No newline at end of file diff --git a/src/main_window/window.py b/src/main_window/window.py index 74d8c72..40ede06 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -28,6 +28,7 @@ from .packages_page import PackagesPage from .remotes_page import RemotesPage from .user_data_page import UserDataPage from .snapshot_page import SnapshotPage +from .install_page import InstallPage from .const import Config from .error_toast import ErrorToast @@ -100,7 +101,7 @@ class WarehouseWindow(Adw.ApplicationWindow): self.remotes_row: RemotesPage(main_window=self), self.user_data_row: UserDataPage(main_window=self), self.snapshots_row: SnapshotPage(main_window=self), - # self.install_row: None, + self.install_row: InstallPage(main_window=self), } for _, page in self.pages.items(): @@ -123,7 +124,7 @@ class WarehouseWindow(Adw.ApplicationWindow): # file_drop.connect("drop", self.drop_callback) self.refresh_button.connect("clicked", self.refresh_handler) - self.activate_row(self.packages_row) + self.activate_row(self.install_row) self.main_split.set_show_sidebar(True) self.start_loading() diff --git a/src/meson.build b/src/meson.build index 8fbafc1..a5cfac8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,6 +20,7 @@ blueprints = custom_target('blueprints', 'snapshot_page/snapshot_page.blp', 'snapshot_page/snapshots_list_page.blp', 'snapshot_page/snapshot_box.blp', + 'install_page/install_page.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -78,6 +79,7 @@ warehouse_sources = [ 'snapshot_page/snapshot_page.py', 'snapshot_page/snapshots_list_page.py', 'snapshot_page/snapshot_box.py', + 'install_page/install_page.py', '../data/style.css', ] diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 49cbf8f..9ad410f 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -18,6 +18,7 @@ snapshot_page/snapshot_page.ui snapshot_page/snapshots_list_page.ui snapshot_page/snapshot_box.ui + install_page/install_page.ui @@ -65,5 +66,7 @@ ../data/icons/font-x-generic-symbolic.svg ../data/icons/tag-outline-symbolic.svg ../data/icons/harddisk-symbolic.svg + ../data/icons/arrow-turn-down-right-symbolic.svg + ../data/icons/minus-large-symbolic.svg From fa31365ecf11877f04552bfde8912e16ac233757 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 19 Aug 2024 16:34:30 -0400 Subject: [PATCH 123/332] Continue work on install page --- src/install_page/install_page.blp | 3 + src/install_page/install_page.py | 118 +++++++++++++++++++++++++++-- src/install_page/result_row.blp | 39 ++++++++++ src/install_page/result_row.py | 36 +++++++++ src/meson.build | 2 + src/snapshot_page/snapshot_box.blp | 4 +- src/warehouse.gresource.xml | 1 + 7 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 src/install_page/result_row.blp create mode 100644 src/install_page/result_row.py diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp index 2165e85..a877a06 100644 --- a/src/install_page/install_page.blp +++ b/src/install_page/install_page.blp @@ -125,6 +125,9 @@ template $InstallPage : Adw.BreakpointBin { Button clear_button { label: _("Remove All"); } + } + Adw.PreferencesPage added_pref_page { + } [bottom] Box { diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index cb18d3c..305d323 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -3,8 +3,42 @@ from .host_info import HostInfo from .error_toast import ErrorToast from .app_row import AppRow from .snapshots_list_page import SnapshotsListPage +from .result_row import ResultRow import os, subprocess +class AddedPackage: + def __init__(self, name, app_id, branch, version, remote, installation): + self.name = name + self.app_id = app_id + self.branch = branch + self.version = version + self.remote = remote + self.installation = installation + +class AddedGroup(Adw.PreferencesGroup): + __gtype_name__ = "AddedGroup" + + package_rows = [] + + def add_row(self, row): + self.package_rows.append(row) + self.add(row) + self.set_visible(True) + + def rem_row(self, row): + if row in self.package_rows: + self.package_rows.remove(row) + self.remove(row) + + def __init__(self, remote, installation, **kwargs): + super().__init__(**kwargs) + + self.remote = remote + self.installation = installation + + self.set_title(f"{remote.title}") + self.set_description(_("Installation: {}").format(installation)) + @Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/install_page.ui") class InstallPage(Adw.BreakpointBin): __gtype_name__ = "InstallPage" @@ -17,6 +51,7 @@ class InstallPage(Adw.BreakpointBin): search_entry = gtc() search_apply_button = gtc() results_list = gtc() + added_pref_page = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -27,7 +62,11 @@ class InstallPage(Adw.BreakpointBin): current_remote = None def start_loading(self): - pass + for row in self.remote_rows: + self.remotes_group.remove(row) + self.remote_rows.clear() + self.added_packages.clear() + self.added_package_groups.clear() def end_loading(self): for installation in HostInfo.installations: @@ -38,6 +77,7 @@ class InstallPage(Adw.BreakpointBin): row.add_suffix(Gtk.Image(icon_name="right-large-symbolic")) row.connect("activated", self.remote_selected, installation, remote) self.remotes_group.add(row) + self.remote_rows.append(row) def remote_selected(self, row, installation, remote): self.current_installation = installation @@ -63,7 +103,7 @@ class InstallPage(Adw.BreakpointBin): try: output = subprocess.run(['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', installation, text], text=True, check=True, capture_output=True).stdout.split("\n") - for line in output: + for i, line in enumerate(output): info = line.split("\t") if len(info) != 6: continue @@ -73,11 +113,26 @@ class InstallPage(Adw.BreakpointBin): app_id = GLib.markup_escape_text(info[2]) version = GLib.markup_escape_text(info[3]) branch = GLib.markup_escape_text(info[4]) - remotes = GLib.markup_escape_text(info[5]) - row = Adw.ActionRow(title=name, subtitle=app_id) + remotes = info[5] - self.results_list.append(row) - + if not self.current_remote.name in remotes.split(','): + continue + + is_added = False + try: + for package in self.added_packages: + if package.name == name and package.app_id == app_id and package.version == version and package.branch == branch: + is_added = True + break + + except KeyError: + print("passing key error") + + if not is_added: + row = ResultRow(name, app_id, branch, version) + row.connect("activated", self.add_package, i, name, app_id, branch, version) + self.results_list.append(row) + except Exception as e: print(e) except subprocess.CalledProcessError as cpe: @@ -85,9 +140,60 @@ class InstallPage(Adw.BreakpointBin): thread() + def list_focus_grabber(self, row): + i = 0 + prev_visible_row = None + while rover := self.results_list.get_row_at_index(i): + i += 1 + if rover is row: + break + + if rover.get_visible(): + prev_visible_row = rover + + while rover := self.results_list.get_row_at_index(i): + i += 1 + if rover.get_visible(): + rover.grab_focus() + return + + if prev_visible_row: + prev_visible_row.grab_focus() + + def add_package(self, row, index, name, app_id, branch, version): + key = f"{self.current_remote}<>{self.current_installation}" + package = AddedPackage(name, app_id, branch, version, self.current_remote, self.current_installation) + added_row = ResultRow(name, app_id, branch, version, row) + self.added_packages.append(package) + group = None + try: + group = self.added_package_groups[key] + group.add_row(added_row) + except KeyError: + group = AddedGroup(self.current_remote, self.current_installation) + self.added_package_groups[key] = group + self.added_pref_page.add(group) + group.add_row(added_row) + except Exception as e: + print(e) + return + added_row.connect("activated", self.remove_package, group) + row.set_visible(False) + self.list_focus_grabber(row) + + def remove_package(self, row, group): + row.original_row.set_visible(True) + group.rem_row(row) + if len(group.package_rows) == 0: + group.set_visible(False) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) self.instance = self + self.main_window = main_window + self.remote_rows = [] + self.added_packages = [] # remote<>installation + self.added_package_groups = {} # remote<>installation self.back_button.connect("clicked", lambda *_: self.sb_page_view.pop()) # self.search_entry.connect("search-changed", self.on_search) self.search_entry.connect("activate", self.on_search) diff --git a/src/install_page/result_row.blp b/src/install_page/result_row.blp new file mode 100644 index 0000000..4312df1 --- /dev/null +++ b/src/install_page/result_row.blp @@ -0,0 +1,39 @@ +using Gtk 4.0; +using Adw 1; + +template $ResultRow : Adw.ActionRow { + activatable: true; + title: "No title set"; + subtitle: "No subtitle set"; + + Box { + orientation: vertical; + valign: center; + spacing: 4; + margin-end: 4; + Label version_label { + styles ["subtitle"] + label: ""; + justify: right; + halign: end; + hexpand: true; + wrap: true; + } + Label branch_label { + styles ["subtitle"] + label: ""; + justify: right; + halign: end; + hexpand: true; + wrap: true; + } + } + [suffix] + Image add_image { + icon-name: "plus-large-symbolic"; + } + [suffix] + Image sub_image { + icon-name: "minus-large-symbolic"; + } +} \ No newline at end of file diff --git a/src/install_page/result_row.py b/src/install_page/result_row.py new file mode 100644 index 0000000..75d615a --- /dev/null +++ b/src/install_page/result_row.py @@ -0,0 +1,36 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +import os, subprocess + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/result_row.ui") +class ResultRow(Adw.ActionRow): + __gtype_name__ = "ResultRow" + gtc = Gtk.Template.Child + + version_label = gtc() + branch_label = gtc() + add_image = gtc() + sub_image = gtc() + + def idle_stuff(self): + self.set_title(GLib.markup_escape_text(self.name)) + self.set_subtitle(self.app_id) + self.version_label.set_label(GLib.markup_escape_text(self.version)) + self.branch_label.set_label(GLib.markup_escape_text(self.branch)) + self.version_label.set_visible(len(self.version_label.get_label()) != 0) + self.branch_label.set_visible(len(self.branch_label.get_label()) != 0) + self.sub_image.set_visible(bool(self.original_row)) + self.add_image.set_visible(not bool(self.original_row)) + self.set_tooltip_text(_("Remove Package from Queue") if bool(self.original_row) else _("Add Package to Queue")) + + def __init__(self, name, app_id, branch, version, original_row=None, **kwargs): + super().__init__(**kwargs) + + self.name = name + self.app_id = app_id + self.branch = branch + self.version = version + self.original_row = original_row + + GLib.idle_add(self.idle_stuff) \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index a5cfac8..7247cb5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,6 +21,7 @@ blueprints = custom_target('blueprints', 'snapshot_page/snapshots_list_page.blp', 'snapshot_page/snapshot_box.blp', 'install_page/install_page.blp', + 'install_page/result_row.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -80,6 +81,7 @@ warehouse_sources = [ 'snapshot_page/snapshots_list_page.py', 'snapshot_page/snapshot_box.py', 'install_page/install_page.py', + 'install_page/result_row.py', '../data/style.css', ] diff --git a/src/snapshot_page/snapshot_box.blp b/src/snapshot_page/snapshot_box.blp index 962d736..36cd0cb 100644 --- a/src/snapshot_page/snapshot_box.blp +++ b/src/snapshot_page/snapshot_box.blp @@ -66,6 +66,4 @@ template $SnapshotBox : Gtk.Box { styles ["flat"] } } -} - -Adw.AlertDialog name_dialog {} \ No newline at end of file +} \ No newline at end of file diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 9ad410f..bd2d391 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -19,6 +19,7 @@ snapshot_page/snapshots_list_page.ui snapshot_page/snapshot_box.ui install_page/install_page.ui + install_page/result_row.ui From bcd560a791aca4ff9897870b302c73ca0e3d1773 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Wed, 21 Aug 2024 01:15:14 -0400 Subject: [PATCH 124/332] Add new icons --- data/icons/list-remove-all-symbolic.svg | 4 ++++ data/icons/view-list-bullet-symbolic.svg | 4 ++++ src/warehouse.gresource.xml | 2 ++ 3 files changed, 10 insertions(+) create mode 100644 data/icons/list-remove-all-symbolic.svg create mode 100644 data/icons/view-list-bullet-symbolic.svg diff --git a/data/icons/list-remove-all-symbolic.svg b/data/icons/list-remove-all-symbolic.svg new file mode 100644 index 0000000..ae240aa --- /dev/null +++ b/data/icons/list-remove-all-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/data/icons/view-list-bullet-symbolic.svg b/data/icons/view-list-bullet-symbolic.svg new file mode 100644 index 0000000..30198f0 --- /dev/null +++ b/data/icons/view-list-bullet-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index bd2d391..22144bd 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -69,5 +69,7 @@ ../data/icons/harddisk-symbolic.svg ../data/icons/arrow-turn-down-right-symbolic.svg ../data/icons/minus-large-symbolic.svg + ../data/icons/view-list-bullet-symbolic.svg + ../data/icons/list-remove-all-symbolic.svg From d7a77b1a4d20c7ec93547ea59cfb54a04aaf67dd Mon Sep 17 00:00:00 2001 From: Heliguy Date: Wed, 21 Aug 2024 01:15:26 -0400 Subject: [PATCH 125/332] Fix syntax formatting --- src/packages_page/packages_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 2993805..104f38a 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -281,7 +281,7 @@ class PackagesPage(Adw.BreakpointBin): self.set_status(self.no_results) def sort_func(self, row1, row2): - return row1.package.info["name"].lower() > row2.package.info["name"].lower () + return row1.package.info["name"].lower() > row2.package.info["name"].lower() def __init__(self, main_window, **kwargs): super().__init__(**kwargs) From bfc2ecd13e229f8bb4e331b5a3616a3584584252 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Wed, 21 Aug 2024 01:15:36 -0400 Subject: [PATCH 126/332] Further work on install page --- src/install_page/install_page.blp | 102 +++++++++++------- src/install_page/install_page.py | 173 ++++++++++++++++++++---------- src/install_page/result_row.blp | 14 ++- src/install_page/result_row.py | 37 ++++--- 4 files changed, 210 insertions(+), 116 deletions(-) diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp index a877a06..04a86e8 100644 --- a/src/install_page/install_page.blp +++ b/src/install_page/install_page.blp @@ -11,6 +11,8 @@ template $InstallPage : Adw.BreakpointBin { setters { split_view.collapsed: true; split_view.show-content: false; + results_action_bar.visible: true; + pending_action_bar.visible: true; } } @@ -48,11 +50,29 @@ template $InstallPage : Adw.BreakpointBin { Adw.ToolbarView { [top] Adw.HeaderBar { + [start] + ToggleButton sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); + } } Adw.PreferencesPage { Adw.PreferencesGroup remotes_group { - title: "Choose a Remote"; - description: "Choose a remote to search for packages in"; + title: _("Search in a Remote"); + description: _("Choose a remote to search for new packages"); + } + Adw.PreferencesGroup local_group { + title: _("Add a File"); + description: _("Install a package from a file on your system"); + Adw.ActionRow open_row { + title: _("Open"); + subtitle: _("Add the file you want to install"); + activatable: true; + [suffix] + Image { + icon-name: "folder-open-symbolic"; + } + } } } } @@ -64,17 +84,6 @@ template $InstallPage : Adw.BreakpointBin { Adw.ToolbarView sidebar_tbv { [top] Adw.HeaderBar header_bar { - show-back-button: false; - [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } - [start] - Button back_button { - icon-name: "left-large-symbolic"; - tooltip-text: _("Back"); - } } [top] Adw.Clamp { @@ -109,6 +118,22 @@ template $InstallPage : Adw.BreakpointBin { } } } + [bottom] + ActionBar results_action_bar { + visible: false; + revealed: false; + [center] + Button review_button { + sensitive: bind results_action_bar.revealed; + margin-top: 3; + margin-bottom: 3; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + icon-name: "view-list-bullet-symbolic"; + label: _("Review and Install"); + } + } + } } } } @@ -116,41 +141,36 @@ template $InstallPage : Adw.BreakpointBin { } ; content: - Adw.NavigationPage results_page { + Adw.NavigationPage pending_page { title: _("Pending Packages"); Adw.ToolbarView { [top] Adw.HeaderBar { - [start] - Button clear_button { - label: _("Remove All"); + } + Stack pending_stack { + Adw.StatusPage no_added_packages_status { + icon-name: "flatpak-symbolic"; + title: _("Install New Packages"); + description: _("Search for a package or add one from a file"); + } + Adw.PreferencesPage added_pref_page { } } - Adw.PreferencesPage added_pref_page { - - } + // Button open_button { + // visible: false; + // Adw.ButtonContent { + // can-shrink: true; + // icon-name: "folder-open-symbolic"; + // label: _("Add File"); + // } + // } [bottom] - Box { - homogeneous: true; - styles ["toolbar"] - Button remove_all_button { - styles ["raised"] - Adw.ButtonContent { - can-shrink: true; - icon-name: "minus-large-symbolic"; - label: _("Remove All"); - } - } - Button open_button { - styles ["raised"] - Adw.ButtonContent { - can-shrink: true; - icon-name: "folder-open-symbolic"; - label: _("Add File"); - } - } + ActionBar pending_action_bar { + revealed: false; + [center] Button install_button { - styles ["raised", "suggested-action"] + sensitive: bind pending_action_bar.revealed; + styles ["pill", "suggested-action"] Adw.ButtonContent { can-shrink: true; icon-name: "arrow-pointing-at-line-down-symbolic"; @@ -163,4 +183,4 @@ template $InstallPage : Adw.BreakpointBin { ; } } -} \ No newline at end of file +} diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index 305d323..baaa9e1 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -7,6 +7,23 @@ from .result_row import ResultRow import os, subprocess class AddedPackage: + def __eq__(self, other): + return ( + self.name == other.name and \ + self.app_id == other.app_id and \ + self.branch == other.branch and \ + self.version == other.version and \ + self.remote == other.remote and \ + self.installation == other.installation + ) + + def is_similar(self, other): + return ( + self.app_id == other.app_id and \ + self.branch == other.branch and \ + self.version == other.version + ) + def __init__(self, name, app_id, branch, version, remote, installation): self.name = name self.app_id = app_id @@ -18,8 +35,6 @@ class AddedPackage: class AddedGroup(Adw.PreferencesGroup): __gtype_name__ = "AddedGroup" - package_rows = [] - def add_row(self, row): self.package_rows.append(row) self.add(row) @@ -35,6 +50,7 @@ class AddedGroup(Adw.PreferencesGroup): self.remote = remote self.installation = installation + self.package_rows = [] self.set_title(f"{remote.title}") self.set_description(_("Installation: {}").format(installation)) @@ -47,11 +63,17 @@ class InstallPage(Adw.BreakpointBin): remotes_group = gtc() sb_page_view = gtc() results = gtc() - back_button = gtc() search_entry = gtc() search_apply_button = gtc() results_list = gtc() added_pref_page = gtc() + results_action_bar = gtc() + pending_action_bar = gtc() + review_button = gtc() + split_view = gtc() + sidebar_button = gtc() + pending_stack = gtc() + no_added_packages_status = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -62,12 +84,23 @@ class InstallPage(Adw.BreakpointBin): current_remote = None def start_loading(self): + self.pending_stack.set_visible_child(self.no_added_packages_status) + self.added_packages.clear() + for row in self.remote_rows: self.remotes_group.remove(row) self.remote_rows.clear() - self.added_packages.clear() + + for _, group in self.added_package_groups.items(): + self.added_pref_page.remove(group) self.added_package_groups.clear() + self.results_action_bar.set_revealed(False) + self.pending_action_bar.set_revealed(False) + self.search_entry.set_text("") + self.results_list.remove_all() + self.sb_page_view.pop() + def end_loading(self): for installation in HostInfo.installations: for remote in HostInfo.remotes[installation]: @@ -84,8 +117,9 @@ class InstallPage(Adw.BreakpointBin): self.current_remote = remote self.results.set_title(_("Search {}").format(remote.title)) self.sb_page_view.push(self.results) - self.search_entry.set_text("firefox") self.search_entry.grab_focus() + self.search_entry.set_text("") + self.results_list.remove_all() def on_search(self, _): text = self.search_entry.get_text().strip().lower().replace(" ", "") @@ -103,7 +137,7 @@ class InstallPage(Adw.BreakpointBin): try: output = subprocess.run(['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', installation, text], text=True, check=True, capture_output=True).stdout.split("\n") - for i, line in enumerate(output): + for line in output: info = line.split("\t") if len(info) != 6: continue @@ -118,83 +152,104 @@ class InstallPage(Adw.BreakpointBin): if not self.current_remote.name in remotes.split(','): continue - is_added = False - try: - for package in self.added_packages: - if package.name == name and package.app_id == app_id and package.version == version and package.branch == branch: - is_added = True - break + current = AddedPackage(name, app_id, branch, version, self.current_remote, self.current_installation) + row = ResultRow(current) + row.connect("activated", self.add_package) + self.results_list.append(row) + + for other in self.added_packages: + if current.is_similar(other): + row.set_is_added(True) + break - except KeyError: - print("passing key error") - - if not is_added: - row = ResultRow(name, app_id, branch, version) - row.connect("activated", self.add_package, i, name, app_id, branch, version) - self.results_list.append(row) + except subprocess.CalledProcessError as cpe: + print(cpe.stderr) except Exception as e: print(e) - except subprocess.CalledProcessError as cpe: - print(cpe) - + thread() + def add_package(self, row): + row.set_is_added(True) + self.added_packages.append(row.package) + self.list_focus_grabber(row) + added_row = ResultRow(row.package, True) + self.results_action_bar.set_revealed(True) + self.pending_action_bar.set_revealed(True) + try: + key = f"{row.package.remote}<>{row.package.installation}" + group = self.added_package_groups[key] + group.add_row(added_row) + except KeyError: + group = AddedGroup(row.package.remote, row.package.installation) + group.add_row(added_row) + self.added_package_groups[key] = group + self.added_pref_page.add(group) + + added_row.connect("activated", self.rem_package, group) + self.pending_stack.set_visible_child(self.added_pref_page) + + def rem_package(self, row, group): + if not row.package in self.added_packages: + return + + self.added_packages.remove(row.package) + if len(self.added_packages) == 0: + self.results_action_bar.set_revealed(False) + self.pending_action_bar.set_revealed(False) + + i = 0 + while rover := self.results_list.get_row_at_index(i): + i += 1 + if not rover.is_added: + continue + + if row.package.is_similar(rover.package): + rover.set_is_added(False) + break + + group.rem_row(row) + if len(group.package_rows) == 0: + self.added_pref_page.remove(group) + self.added_package_groups.pop(f'{row.package.remote}<>{row.package.installation}', None) + + if len(self.added_package_groups) == 0: + self.pending_stack.set_visible_child(self.no_added_packages_status) + def list_focus_grabber(self, row): i = 0 - prev_visible_row = None + prev_unadded_row = None while rover := self.results_list.get_row_at_index(i): i += 1 if rover is row: break - - if rover.get_visible(): - prev_visible_row = rover + + if not rover.is_added: + prev_unadded_row = rover while rover := self.results_list.get_row_at_index(i): i += 1 - if rover.get_visible(): + if not rover.is_added: rover.grab_focus() return - if prev_visible_row: - prev_visible_row.grab_focus() - - def add_package(self, row, index, name, app_id, branch, version): - key = f"{self.current_remote}<>{self.current_installation}" - package = AddedPackage(name, app_id, branch, version, self.current_remote, self.current_installation) - added_row = ResultRow(name, app_id, branch, version, row) - self.added_packages.append(package) - group = None - try: - group = self.added_package_groups[key] - group.add_row(added_row) - except KeyError: - group = AddedGroup(self.current_remote, self.current_installation) - self.added_package_groups[key] = group - self.added_pref_page.add(group) - group.add_row(added_row) - except Exception as e: - print(e) - return - added_row.connect("activated", self.remove_package, group) - row.set_visible(False) - self.list_focus_grabber(row) - - def remove_package(self, row, group): - row.original_row.set_visible(True) - group.rem_row(row) - if len(group.package_rows) == 0: - group.set_visible(False) + if prev_unadded_row: + prev_unadded_row.grab_focus() def __init__(self, main_window, **kwargs): super().__init__(**kwargs) self.instance = self self.main_window = main_window self.remote_rows = [] - self.added_packages = [] # remote<>installation + self.added_packages = [] self.added_package_groups = {} # remote<>installation - self.back_button.connect("clicked", lambda *_: self.sb_page_view.pop()) + # self.back_button.connect("clicked", lambda *_: self.sb_page_view.pop()) # self.search_entry.connect("search-changed", self.on_search) self.search_entry.connect("activate", self.on_search) - self.search_apply_button.connect("clicked", self.on_search) \ No newline at end of file + self.search_apply_button.connect("clicked", self.on_search) + self.review_button.connect("clicked", lambda *_: self.split_view.set_show_content(True)) + + ms = main_window.main_split + ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) + self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) diff --git a/src/install_page/result_row.blp b/src/install_page/result_row.blp index 4312df1..73c969f 100644 --- a/src/install_page/result_row.blp +++ b/src/install_page/result_row.blp @@ -5,6 +5,7 @@ template $ResultRow : Adw.ActionRow { activatable: true; title: "No title set"; subtitle: "No subtitle set"; + tooltip-text: _("Add Package to Queue"); Box { orientation: vertical; @@ -36,4 +37,15 @@ template $ResultRow : Adw.ActionRow { Image sub_image { icon-name: "minus-large-symbolic"; } -} \ No newline at end of file + [suffix] + Image selected_image { + icon-name: "check-plain-symbolic"; + visible: false; + } + [suffix] + Image already_installed_image { + visible: false; + icon-name: "selection-mode-symbolic"; + styles ["success"] + } +} diff --git a/src/install_page/result_row.py b/src/install_page/result_row.py index 75d615a..6373ea2 100644 --- a/src/install_page/result_row.py +++ b/src/install_page/result_row.py @@ -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 import os, subprocess @@ -12,25 +12,32 @@ class ResultRow(Adw.ActionRow): branch_label = gtc() add_image = gtc() sub_image = gtc() + selected_image = gtc() def idle_stuff(self): - self.set_title(GLib.markup_escape_text(self.name)) - self.set_subtitle(self.app_id) - self.version_label.set_label(GLib.markup_escape_text(self.version)) - self.branch_label.set_label(GLib.markup_escape_text(self.branch)) + self.set_title(GLib.markup_escape_text(self.package.name)) + self.set_subtitle(self.package.app_id) + self.version_label.set_label(GLib.markup_escape_text(self.package.version)) + self.branch_label.set_label(GLib.markup_escape_text(self.package.branch)) self.version_label.set_visible(len(self.version_label.get_label()) != 0) self.branch_label.set_visible(len(self.branch_label.get_label()) != 0) - self.sub_image.set_visible(bool(self.original_row)) - self.add_image.set_visible(not bool(self.original_row)) - self.set_tooltip_text(_("Remove Package from Queue") if bool(self.original_row) else _("Add Package to Queue")) + if self.is_added: + self.set_tooltip_text(_("Remove Package from Queue")) - def __init__(self, name, app_id, branch, version, original_row=None, **kwargs): + def set_is_added(self, is_added): + self.is_added = is_added + self.set_sensitive(not is_added) + self.add_image.set_visible(not is_added) + self.selected_image.set_visible(is_added) + self.set_tooltip_text(_("This package is queued") if is_added else _("Add Package to Queue")) + + def __init__(self, package, is_added=False, **kwargs): super().__init__(**kwargs) - self.name = name - self.app_id = app_id - self.branch = branch - self.version = version - self.original_row = original_row + self.is_added = is_added + self.package = package - GLib.idle_add(self.idle_stuff) \ No newline at end of file + self.sub_image.set_visible(is_added) + self.add_image.set_visible(not is_added) + + GLib.idle_add(self.idle_stuff) From 0f874d28b8e4023c3a907c676ad88fea3714b670 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Thu, 22 Aug 2024 20:30:36 -0400 Subject: [PATCH 127/332] Sync current work --- src/host_info.py | 6 +++--- src/install_page/install_page.blp | 14 +++++++++++--- src/install_page/install_page.py | 27 ++++++++++++++++++++++++++- src/remotes_page/remotes_page.py | 2 +- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index db1b9df..b967968 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -273,12 +273,12 @@ class HostInfo: capture_output=True, ).stdout lines = output.strip().split("\n") + remote_list = [] if lines[0] != '': - remote_list = [] for line in lines: line = line.split("\t") remote_list.append(Remote(name=line[0], title=line[1], disabled=(len(line) == 3) and "disabled" in line[2])) - this.remotes[installation] = remote_list + this.remotes[installation] = remote_list # Masks cmd = ['flatpak-spawn', '--host', @@ -363,4 +363,4 @@ class HostInfo: except Exception as e: this.main_window.toast_overlay.add_toast(ErrorToast(_("Could not load pacakges"), str(e)).toast) - Gio.Task.new(None, None, callback).run_in_thread(thread) \ No newline at end of file + Gio.Task.new(None, None, callback).run_in_thread(thread) diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp index 04a86e8..8ce6ee7 100644 --- a/src/install_page/install_page.blp +++ b/src/install_page/install_page.blp @@ -5,7 +5,7 @@ template $InstallPage : Adw.BreakpointBin { width-request: 1; height-request: 1; - Adw.Breakpoint bp1 { + Adw.Breakpoint break_point { condition ("max-width: 600") setters { @@ -13,6 +13,7 @@ template $InstallPage : Adw.BreakpointBin { split_view.show-content: false; results_action_bar.visible: true; pending_action_bar.visible: true; + pending_page.child: null; } } @@ -75,6 +76,12 @@ template $InstallPage : Adw.BreakpointBin { } } } + [bottom] + ActionBar { + Button test_button { + label: "test"; + } + } } } @@ -143,9 +150,10 @@ template $InstallPage : Adw.BreakpointBin { content: Adw.NavigationPage pending_page { title: _("Pending Packages"); - Adw.ToolbarView { + [child] + Adw.ToolbarView pending_toolbar_view { [top] - Adw.HeaderBar { + Adw.HeaderBar pending_headerbar { } Stack pending_stack { Adw.StatusPage no_added_packages_status { diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index baaa9e1..493f963 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -74,6 +74,11 @@ class InstallPage(Adw.BreakpointBin): sidebar_button = gtc() pending_stack = gtc() no_added_packages_status = gtc() + break_point = gtc() + pending_toolbar_view = gtc() + pending_headerbar = gtc() + + test_button = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -215,6 +220,7 @@ class InstallPage(Adw.BreakpointBin): self.added_package_groups.pop(f'{row.package.remote}<>{row.package.installation}', None) if len(self.added_package_groups) == 0: + self.list_popup.close() self.pending_stack.set_visible_child(self.no_added_packages_status) def list_focus_grabber(self, row): @@ -237,6 +243,15 @@ class InstallPage(Adw.BreakpointBin): if prev_unadded_row: prev_unadded_row.grab_focus() + def break_point_handler(self, bp, is_applied): + self.list_popup.close() + self.pending_headerbar.set_show_start_title_buttons(not is_applied) + self.pending_headerbar.set_show_end_title_buttons(not is_applied) + if is_applied: + self.popup_holder.set_child(self.pending_toolbar_view) + else: + self.popup_holder.set_child(None) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) self.instance = self @@ -248,7 +263,17 @@ class InstallPage(Adw.BreakpointBin): # self.search_entry.connect("search-changed", self.on_search) self.search_entry.connect("activate", self.on_search) self.search_apply_button.connect("clicked", self.on_search) - self.review_button.connect("clicked", lambda *_: self.split_view.set_show_content(True)) + + + # self.review_button.connect("clicked", lambda *_: self.split_view.set_show_content(True)) + self.review_button.connect("clicked", lambda *_: self.list_popup.present(main_window)) + + self.popup_holder = Adw.NavigationPage(title=_("Pending Packages")) + self.list_popup = Adw.Dialog(width_request=400, child=self.popup_holder, follows_content_size=True, presentation_mode=Adw.DialogPresentationMode.BOTTOM_SHEET) + + self.break_point.connect("apply", self.break_point_handler, True) + self.break_point.connect("unapply", self.break_point_handler, False) + # self.test_button.connect("clicked", lambda *_: self.list_popup.present(main_window)) ms = main_window.main_split ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index bb3ef4e..266689a 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -251,4 +251,4 @@ class RemotesPage(Adw.NavigationPage): for item in self.new_remotes: row = NewRemoteRow(item) row.connect("activated", lambda *_, remote_info=item: AddRemoteDialog(main_window, self, remote_info).present(main_window)) - self.new_remotes_group.add(row) \ No newline at end of file + self.new_remotes_group.add(row) From a865a107833e7f634b757094ed4d42d4d3c70373 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 23 Aug 2024 01:10:03 -0400 Subject: [PATCH 128/332] Sync current work --- src/install_page/install_page.blp | 176 +-------------------- src/install_page/install_page.py | 244 ++---------------------------- src/install_page/pending_page.blp | 35 +++++ src/install_page/pending_page.py | 11 ++ src/install_page/results_page.blp | 77 ++++++++++ src/install_page/results_page.py | 117 ++++++++++++++ src/install_page/select_page.blp | 62 ++++++++ src/install_page/select_page.py | 51 +++++++ src/meson.build | 6 + src/warehouse.gresource.xml | 3 + 10 files changed, 382 insertions(+), 400 deletions(-) create mode 100644 src/install_page/pending_page.blp create mode 100644 src/install_page/pending_page.py create mode 100644 src/install_page/results_page.blp create mode 100644 src/install_page/results_page.py create mode 100644 src/install_page/select_page.blp create mode 100644 src/install_page/select_page.py diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp index 8ce6ee7..72c9a42 100644 --- a/src/install_page/install_page.blp +++ b/src/install_page/install_page.blp @@ -11,9 +11,9 @@ template $InstallPage : Adw.BreakpointBin { setters { split_view.collapsed: true; split_view.show-content: false; - results_action_bar.visible: true; - pending_action_bar.visible: true; - pending_page.child: null; + // results_action_bar.visible: true; + // pending_action_bar.visible: true; + // pending_page.child: null; } } @@ -21,174 +21,8 @@ template $InstallPage : Adw.BreakpointBin { Adw.NavigationSplitView split_view { sidebar-width-fraction: 0.5; max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage sidebar_navpage { - title: _("Search for Packages"); - - Gtk.Stack sb_stack { - Adw.ToolbarView no_remotes { - visible: false; - [top] - Adw.HeaderBar { - show-title: false; - } - Adw.StatusPage { - icon-name: "error-symbolic"; - title: _("No Remotes Found"); - description: _("Warehouse cannot see the current remotes or your system has no remotes added"); - Button open_remotes_page_button { - label: _("Add Remotes"); - halign: center; - styles ["pill"] - } - } - } - - Adw.NavigationView sb_page_view { - Adw.NavigationPage { - title: _("Install Packages"); - - Adw.ToolbarView { - [top] - Adw.HeaderBar { - [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } - } - Adw.PreferencesPage { - Adw.PreferencesGroup remotes_group { - title: _("Search in a Remote"); - description: _("Choose a remote to search for new packages"); - } - Adw.PreferencesGroup local_group { - title: _("Add a File"); - description: _("Install a package from a file on your system"); - Adw.ActionRow open_row { - title: _("Open"); - subtitle: _("Add the file you want to install"); - activatable: true; - [suffix] - Image { - icon-name: "folder-open-symbolic"; - } - } - } - } - [bottom] - ActionBar { - Button test_button { - label: "test"; - } - } - } - } - - Adw.NavigationPage results { - title: _("Search REMOTE"); - - Adw.ToolbarView sidebar_tbv { - [top] - Adw.HeaderBar header_bar { - } - [top] - Adw.Clamp { - maximum-size: 577; - Box { - margin-top: 3; - margin-bottom: 3; - margin-start: 6; - margin-end: 6; - styles ["linked"] - Gtk.SearchEntry search_entry { - search-delay: 700; - halign: fill; - hexpand: true; - placeholder-text: _("Search for Packages"); - } - Button search_apply_button { - icon-name: "right-large-symbolic"; - } - } - } - ScrolledWindow { - Adw.Clamp { - ListBox results_list { - styles["boxed-list"] - valign: start; - selection-mode: none; - margin-start: 12; - margin-end: 12; - margin-top: 12; - margin-bottom: 12; - } - } - } - [bottom] - ActionBar results_action_bar { - visible: false; - revealed: false; - [center] - Button review_button { - sensitive: bind results_action_bar.revealed; - margin-top: 3; - margin-bottom: 3; - styles ["pill", "suggested-action"] - Adw.ButtonContent { - icon-name: "view-list-bullet-symbolic"; - label: _("Review and Install"); - } - } - } - } - } - } - } - } - ; - content: - Adw.NavigationPage pending_page { - title: _("Pending Packages"); - [child] - Adw.ToolbarView pending_toolbar_view { - [top] - Adw.HeaderBar pending_headerbar { - } - Stack pending_stack { - Adw.StatusPage no_added_packages_status { - icon-name: "flatpak-symbolic"; - title: _("Install New Packages"); - description: _("Search for a package or add one from a file"); - } - Adw.PreferencesPage added_pref_page { - } - } - // Button open_button { - // visible: false; - // Adw.ButtonContent { - // can-shrink: true; - // icon-name: "folder-open-symbolic"; - // label: _("Add File"); - // } - // } - [bottom] - ActionBar pending_action_bar { - revealed: false; - [center] - Button install_button { - sensitive: bind pending_action_bar.revealed; - styles ["pill", "suggested-action"] - Adw.ButtonContent { - can-shrink: true; - icon-name: "arrow-pointing-at-line-down-symbolic"; - label: _("Install"); - } - } - } - } - } - ; + sidebar: $SelectPage select_page {}; + content: $PendingPage pending_page {}; } } } diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index 493f963..8a4152b 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -1,36 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo -from .error_toast import ErrorToast -from .app_row import AppRow -from .snapshots_list_page import SnapshotsListPage -from .result_row import ResultRow -import os, subprocess - -class AddedPackage: - def __eq__(self, other): - return ( - self.name == other.name and \ - self.app_id == other.app_id and \ - self.branch == other.branch and \ - self.version == other.version and \ - self.remote == other.remote and \ - self.installation == other.installation - ) - - def is_similar(self, other): - return ( - self.app_id == other.app_id and \ - self.branch == other.branch and \ - self.version == other.version - ) - - def __init__(self, name, app_id, branch, version, remote, installation): - self.name = name - self.app_id = app_id - self.branch = branch - self.version = version - self.remote = remote - self.installation = installation +from .select_page import SelectPage +from .pending_page import PendingPage class AddedGroup(Adw.PreferencesGroup): __gtype_name__ = "AddedGroup" @@ -60,25 +31,9 @@ class InstallPage(Adw.BreakpointBin): __gtype_name__ = "InstallPage" gtc = Gtk.Template.Child - remotes_group = gtc() - sb_page_view = gtc() - results = gtc() - search_entry = gtc() - search_apply_button = gtc() - results_list = gtc() - added_pref_page = gtc() - results_action_bar = gtc() - pending_action_bar = gtc() - review_button = gtc() - split_view = gtc() - sidebar_button = gtc() - pending_stack = gtc() - no_added_packages_status = gtc() break_point = gtc() - pending_toolbar_view = gtc() - pending_headerbar = gtc() - - test_button = gtc() + select_page = gtc() + split_view = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -89,192 +44,23 @@ class InstallPage(Adw.BreakpointBin): current_remote = None def start_loading(self): - self.pending_stack.set_visible_child(self.no_added_packages_status) - self.added_packages.clear() - - for row in self.remote_rows: - self.remotes_group.remove(row) - self.remote_rows.clear() - - for _, group in self.added_package_groups.items(): - self.added_pref_page.remove(group) - self.added_package_groups.clear() - - self.results_action_bar.set_revealed(False) - self.pending_action_bar.set_revealed(False) - self.search_entry.set_text("") - self.results_list.remove_all() - self.sb_page_view.pop() - + self.select_page.start_loading() + def end_loading(self): - for installation in HostInfo.installations: - for remote in HostInfo.remotes[installation]: - if remote.disabled: - continue - row = Adw.ActionRow(title=remote.title, subtitle=_("Installation: {}").format(installation), activatable=True) - row.add_suffix(Gtk.Image(icon_name="right-large-symbolic")) - row.connect("activated", self.remote_selected, installation, remote) - self.remotes_group.add(row) - self.remote_rows.append(row) + self.select_page.end_loading() - def remote_selected(self, row, installation, remote): - self.current_installation = installation - self.current_remote = remote - self.results.set_title(_("Search {}").format(remote.title)) - self.sb_page_view.push(self.results) - self.search_entry.grab_focus() - self.search_entry.set_text("") - self.results_list.remove_all() - - def on_search(self, _): - text = self.search_entry.get_text().strip().lower().replace(" ", "") - if not text: - return - - results = [] - def thread(*args): - installation = "" - self.results_list.remove_all() - if self.current_installation == "user" or self.current_installation == "system": - installation = f"--{self.current_installation}" - else: - installation = f"--installation={self.current_installation}" - - try: - output = subprocess.run(['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', installation, text], text=True, check=True, capture_output=True).stdout.split("\n") - for line in output: - info = line.split("\t") - if len(info) != 6: - continue - - name = GLib.markup_escape_text(info[0]) - description = GLib.markup_escape_text(info[1]) - app_id = GLib.markup_escape_text(info[2]) - version = GLib.markup_escape_text(info[3]) - branch = GLib.markup_escape_text(info[4]) - remotes = info[5] - - if not self.current_remote.name in remotes.split(','): - continue - - current = AddedPackage(name, app_id, branch, version, self.current_remote, self.current_installation) - row = ResultRow(current) - row.connect("activated", self.add_package) - self.results_list.append(row) - - for other in self.added_packages: - if current.is_similar(other): - row.set_is_added(True) - break - - - except subprocess.CalledProcessError as cpe: - print(cpe.stderr) - except Exception as e: - print(e) - - thread() - - def add_package(self, row): - row.set_is_added(True) - self.added_packages.append(row.package) - self.list_focus_grabber(row) - added_row = ResultRow(row.package, True) - self.results_action_bar.set_revealed(True) - self.pending_action_bar.set_revealed(True) - try: - key = f"{row.package.remote}<>{row.package.installation}" - group = self.added_package_groups[key] - group.add_row(added_row) - except KeyError: - group = AddedGroup(row.package.remote, row.package.installation) - group.add_row(added_row) - self.added_package_groups[key] = group - self.added_pref_page.add(group) - - added_row.connect("activated", self.rem_package, group) - self.pending_stack.set_visible_child(self.added_pref_page) - - def rem_package(self, row, group): - if not row.package in self.added_packages: - return - - self.added_packages.remove(row.package) - if len(self.added_packages) == 0: - self.results_action_bar.set_revealed(False) - self.pending_action_bar.set_revealed(False) - - i = 0 - while rover := self.results_list.get_row_at_index(i): - i += 1 - if not rover.is_added: - continue - - if row.package.is_similar(rover.package): - rover.set_is_added(False) - break - - group.rem_row(row) - if len(group.package_rows) == 0: - self.added_pref_page.remove(group) - self.added_package_groups.pop(f'{row.package.remote}<>{row.package.installation}', None) - - if len(self.added_package_groups) == 0: - self.list_popup.close() - self.pending_stack.set_visible_child(self.no_added_packages_status) - - def list_focus_grabber(self, row): - i = 0 - prev_unadded_row = None - while rover := self.results_list.get_row_at_index(i): - i += 1 - if rover is row: - break - - if not rover.is_added: - prev_unadded_row = rover - - while rover := self.results_list.get_row_at_index(i): - i += 1 - if not rover.is_added: - rover.grab_focus() - return - - if prev_unadded_row: - prev_unadded_row.grab_focus() - - def break_point_handler(self, bp, is_applied): - self.list_popup.close() - self.pending_headerbar.set_show_start_title_buttons(not is_applied) - self.pending_headerbar.set_show_end_title_buttons(not is_applied) - if is_applied: - self.popup_holder.set_child(self.pending_toolbar_view) - else: - self.popup_holder.set_child(None) + def breakpoint_handler(self, bp, is_applied): + self.select_page.results_page.action_bar.set_revealed(is_applied) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) self.instance = self - self.main_window = main_window - self.remote_rows = [] - self.added_packages = [] - self.added_package_groups = {} # remote<>installation - # self.back_button.connect("clicked", lambda *_: self.sb_page_view.pop()) - # self.search_entry.connect("search-changed", self.on_search) - self.search_entry.connect("activate", self.on_search) - self.search_apply_button.connect("clicked", self.on_search) + # Extra Object Creation - # self.review_button.connect("clicked", lambda *_: self.split_view.set_show_content(True)) - self.review_button.connect("clicked", lambda *_: self.list_popup.present(main_window)) - - self.popup_holder = Adw.NavigationPage(title=_("Pending Packages")) - self.list_popup = Adw.Dialog(width_request=400, child=self.popup_holder, follows_content_size=True, presentation_mode=Adw.DialogPresentationMode.BOTTOM_SHEET) + # Connections + self.break_point.connect("apply", self.breakpoint_handler, True) + self.break_point.connect("unapply", self.breakpoint_handler, False) + self.select_page.results_page.review_button.connect("clicked", lambda *_: self.split_view.set_show_content(True)) - self.break_point.connect("apply", self.break_point_handler, True) - self.break_point.connect("unapply", self.break_point_handler, False) - # self.test_button.connect("clicked", lambda *_: self.list_popup.present(main_window)) - - ms = main_window.main_split - ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) - self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + # Apply diff --git a/src/install_page/pending_page.blp b/src/install_page/pending_page.blp new file mode 100644 index 0000000..cfb4a46 --- /dev/null +++ b/src/install_page/pending_page.blp @@ -0,0 +1,35 @@ +using Gtk 4.0; +using Adw 1; + +template $PendingPage : Adw.NavigationPage { + title: _("Pending Packages"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + } + Adw.PreferencesPage preferences_page { + Adw.PreferencesGroup { + title: "Flathub"; + description: "Installation: system"; + Adw.ActionRow { + title: "Firefox"; + subtitle: "org.mozilla.Firefox"; + } + } + } + [bottom] + ActionBar pending_action_bar { + revealed: true; + [center] + Button install_button { + sensitive: bind pending_action_bar.revealed; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + can-shrink: true; + icon-name: "arrow-pointing-at-line-down-symbolic"; + label: _("Install"); + } + } + } + } +} diff --git a/src/install_page/pending_page.py b/src/install_page/pending_page.py new file mode 100644 index 0000000..c4ae4c5 --- /dev/null +++ b/src/install_page/pending_page.py @@ -0,0 +1,11 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/pending_page.ui") +class PendingPage(Adw.NavigationPage): + __gtype_name__ = "PendingPage" + gtc = Gtk.Template.Child + + def __init__(self, **kwargs): + super().__init__(**kwargs) diff --git a/src/install_page/results_page.blp b/src/install_page/results_page.blp new file mode 100644 index 0000000..1fb3dfa --- /dev/null +++ b/src/install_page/results_page.blp @@ -0,0 +1,77 @@ +using Gtk 4.0; +using Adw 1; + +template $ResultsPage : Adw.NavigationPage { + title: _("Search a Remote"); + Adw.ToolbarView { + [top] + Adw.HeaderBar {} + [top] + Adw.Clamp { + maximum-size: 577; + Box { + margin-top: 3; + margin-bottom: 3; + margin-start: 6; + margin-end: 6; + styles ["linked"] + Gtk.SearchEntry search_entry { + search-delay: 700; + halign: fill; + hexpand: true; + placeholder-text: _("Search for Packages"); + } + Button search_apply_button { + icon-name: "right-large-symbolic"; + } + } + } + Stack stack { + Adw.StatusPage new_search { + icon-name: "flatpak-symbolic"; + title: _("Search for Flatpaks"); + description: _("Search for Flatpaks you want to install"); + } + Adw.StatusPage loading { + title: _("Searching"); + description: _("This should only take a moment"); + child: + Spinner { + spinning: true; + } + ; + } + ScrolledWindow results_view { + Adw.Clamp { + ListBox results_list { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + styles ["boxed-list"] + selection-mode: none; + valign: start; + } + } + } + Adw.StatusPage no_results { + icon-name: "loupe-large-symbolic"; + title: _("No Results Found"); + description: _("Try a different search term"); + } + } + [bottom] + ActionBar action_bar { + [center] + Button review_button { + margin-top: 3; + margin-bottom: 3; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + icon-name: "view-list-bullet-symbolic"; + label: _("Review and Install"); + } + } + } + } +} diff --git a/src/install_page/results_page.py b/src/install_page/results_page.py new file mode 100644 index 0000000..eb44004 --- /dev/null +++ b/src/install_page/results_page.py @@ -0,0 +1,117 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +from .result_row import ResultRow +import subprocess + +class AddedPackage: + def __eq__(self, other): + return ( + self.name == other.name and \ + self.app_id == other.app_id and \ + self.branch == other.branch and \ + self.version == other.version and \ + self.remote == other.remote and \ + self.installation == other.installation + ) + + def is_similar(self, other): + return ( + self.app_id == other.app_id and \ + self.branch == other.branch and \ + self.version == other.version + ) + + def __init__(self, name, app_id, branch, version, remote, installation): + self.name = name + self.app_id = app_id + self.branch = branch + self.version = version + self.remote = remote + self.installation = installation + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/results_page.ui") +class ResultsPage(Adw.NavigationPage): + __gtype_name__ = "ResultsPage" + gtc = Gtk.Template.Child + + action_bar = gtc() + review_button = gtc() + search_entry = gtc() + search_apply_button = gtc() + results_list = gtc() + stack = gtc() + new_search = gtc() + loading = gtc() + results_view= gtc() + no_results = gtc() + + def show_remote(self, row, remote, installation, nav_view=None): + self.remote = remote + self.installation = installation + self.set_title(_("Search {}").format(remote.title)) + self.search_entry.grab_focus() + if nav_view: + nav_view.push(self) + + def on_search(self, *args): + self.packages.clear() + self.stack.set_visible_child(self.loading) + self.results_list.remove_all() + search_text = self.search_entry.get_text() + if search_text == "": + self.stack.set_visible_child(self.new_search) + return + + def thread(*args): + installation = "" + if self.installation == "user" or self.installation == "system": + installation = self.installation + else: + installation = f"--installation={self.installation}" + + try: + output = subprocess.run( + ['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', self.remote.name, installation, self.search_entry.get_text()], + check=True, text=True, capture_output=True + ).stdout.split('\n') + for line in output: + line = line.strip() + + info = line.split('\t') + if len(info) != 6: + continue + + package = AddedPackage(info[0], info[2], info[4], info[3], self.remote, self.installation) + row = ResultRow(package) + self.packages.append(package) + GLib.idle_add(lambda *_, _row=row: self.results_list.append(_row)) + + except subprocess.CalledProcessError as cpe: + print(cpe) + + def callback(*args): + if len(self.packages) > 0: + self.stack.set_visible_child(self.results_view) + else: + self.stack.set_visible_child(self.no_results) + + Gio.Task.new(None, None, callback).run_in_thread(thread) + + def on_back(self, *args): + self.results_list.remove_all() + self.stack.set_visible_child(self.new_search) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + self.remote = None + self.installation = None + self.packages = [] + + # Connections + self.search_entry.connect("activate", self.on_search) + self.search_apply_button.connect("clicked", self.on_search) + + # Apply diff --git a/src/install_page/select_page.blp b/src/install_page/select_page.blp new file mode 100644 index 0000000..9b6796d --- /dev/null +++ b/src/install_page/select_page.blp @@ -0,0 +1,62 @@ +using Gtk 4.0; +using Adw 1; + +template $SelectPage : Adw.NavigationPage { + title: _("Install Packages"); + + Adw.ToastOverlay toast_overlay { + Adw.NavigationView nav_view { + Adw.NavigationPage select_nav_page { + title: _("Install Packages"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + [start] + ToggleButton sidebar_button { + icon-name: "dock-left-symbolic"; + tooltip-text: _("Show Sidebar"); + } + [start] + Button test { + label: "test"; + } + } + Adw.PreferencesPage { + Adw.PreferencesGroup remotes_group { + title: _("Search in a Remote"); + description: _("Choose a remote to search for new packages"); + } + Adw.PreferencesGroup no_remotes { + title: _("Online Searches Disabled"); + description: _("Your system has no remotes added to search from"); + visible: bind remotes_group.visible inverted; + Adw.ActionRow add_remote_row { + title: _("Add a Remote"); + subtitle: _("Add a remote to your system to enable online searching"); + activatable: true; + [suffix] + Image { + icon-name: "right-large-symbolic"; + } + } + } + Adw.PreferencesGroup local_group { + title: _("Add a File"); + description: _("Install a package from a file on your system"); + Adw.ActionRow open_row { + title: _("Open"); + subtitle: _("Add the file you want to install"); + activatable: true; + [suffix] + Image { + icon-name: "folder-open-symbolic"; + } + } + } + } + } + } + $ResultsPage results_page {} + } + } +} diff --git a/src/install_page/select_page.py b/src/install_page/select_page.py new file mode 100644 index 0000000..c9061c8 --- /dev/null +++ b/src/install_page/select_page.py @@ -0,0 +1,51 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +from .results_page import ResultsPage + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/select_page.ui") +class SelectPage(Adw.NavigationPage): + __gtype_name__ = "SelectPage" + gtc = Gtk.Template.Child + + nav_view = gtc() + sidebar_button = gtc() + results_page = gtc() + remotes_group = gtc() + add_remote_row = gtc() + + test = gtc() + + def start_loading(self): + pass + + def end_loading(self): + total_remotes = 0 + for installation, remotes in HostInfo.remotes.items(): + for remote in remotes: + if remote.disabled: + continue + + row = Adw.ActionRow(title=remote.title, subtitle=_("Installation: {}").format(installation), activatable=True) + row.add_suffix(Gtk.Image(icon_name="right-large-symbolic")) + row.connect("activated", self.results_page.show_remote, remote, installation, self.nav_view) + self.remotes_group.add(row) + total_remotes += 1 + + self.remotes_group.set_visible(total_remotes != 0) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + ms = HostInfo.main_window.main_split + + # Connections + ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) + self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) + self.add_remote_row.connect("activated", lambda *_: HostInfo.main_window.activate_row(HostInfo.main_window.remotes_row)) + self.nav_view.connect("popped", self.results_page.on_back) + + self.test.connect("clicked", lambda *_: self.nav_view.push(self.results_page)) + + # Apply diff --git a/src/meson.build b/src/meson.build index 7247cb5..0cdbc67 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,9 @@ blueprints = custom_target('blueprints', 'snapshot_page/snapshot_box.blp', 'install_page/install_page.blp', 'install_page/result_row.blp', + 'install_page/select_page.blp', + 'install_page/results_page.blp', + 'install_page/pending_page.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -82,6 +85,9 @@ warehouse_sources = [ 'snapshot_page/snapshot_box.py', 'install_page/install_page.py', 'install_page/result_row.py', + 'install_page/select_page.py', + 'install_page/results_page.py', + 'install_page/pending_page.py', '../data/style.css', ] diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 22144bd..842029b 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -20,6 +20,9 @@ snapshot_page/snapshot_box.ui install_page/install_page.ui install_page/result_row.ui + install_page/select_page.ui + install_page/results_page.ui + install_page/pending_page.ui From 087b0452e56e93494f2714fdf7fa8ecd2ab512f9 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 24 Aug 2024 01:06:21 -0400 Subject: [PATCH 129/332] Sync current work --- src/install_page/install_page.py | 27 +++------------- src/install_page/pending_page.blp | 21 ++++++++----- src/install_page/pending_page.py | 50 +++++++++++++++++++++++++++++ src/install_page/result_row.blp | 12 +++---- src/install_page/result_row.py | 52 ++++++++++++++++++++++++------- src/install_page/results_page.blp | 7 ++++- src/install_page/results_page.py | 31 +++++++++++++----- src/install_page/select_page.py | 11 ++++--- 8 files changed, 150 insertions(+), 61 deletions(-) diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index 8a4152b..18f12c8 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -3,29 +3,6 @@ from .host_info import HostInfo from .select_page import SelectPage from .pending_page import PendingPage -class AddedGroup(Adw.PreferencesGroup): - __gtype_name__ = "AddedGroup" - - def add_row(self, row): - self.package_rows.append(row) - self.add(row) - self.set_visible(True) - - def rem_row(self, row): - if row in self.package_rows: - self.package_rows.remove(row) - self.remove(row) - - def __init__(self, remote, installation, **kwargs): - super().__init__(**kwargs) - - self.remote = remote - self.installation = installation - self.package_rows = [] - - self.set_title(f"{remote.title}") - self.set_description(_("Installation: {}").format(installation)) - @Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/install_page.ui") class InstallPage(Adw.BreakpointBin): __gtype_name__ = "InstallPage" @@ -57,6 +34,8 @@ class InstallPage(Adw.BreakpointBin): self.instance = self # Extra Object Creation + # ======== self.select_page = SelectPage() + # ======== self.pending_page = PendingPage() # Connections self.break_point.connect("apply", self.breakpoint_handler, True) @@ -64,3 +43,5 @@ class InstallPage(Adw.BreakpointBin): self.select_page.results_page.review_button.connect("clicked", lambda *_: self.split_view.set_show_content(True)) # Apply + # ======== self.split_view.set_sidebar(self.select_page) + # ======== self.split_view.set_content(self.pending_page) diff --git a/src/install_page/pending_page.blp b/src/install_page/pending_page.blp index cfb4a46..1bce7d2 100644 --- a/src/install_page/pending_page.blp +++ b/src/install_page/pending_page.blp @@ -7,13 +7,20 @@ template $PendingPage : Adw.NavigationPage { [top] Adw.HeaderBar { } - Adw.PreferencesPage preferences_page { - Adw.PreferencesGroup { - title: "Flathub"; - description: "Installation: system"; - Adw.ActionRow { - title: "Firefox"; - subtitle: "org.mozilla.Firefox"; + Stack stack { + Adw.StatusPage none_pending { + icon-name: "flatpak-symbolic"; + title: _("Add Packages"); + description: _("Packages queued to install will show up here"); + } + Adw.PreferencesPage preferences_page { + Adw.PreferencesGroup { + title: "Flathub"; + description: "Installation: system"; + Adw.ActionRow { + title: "Firefox"; + subtitle: "org.mozilla.Firefox"; + } } } } diff --git a/src/install_page/pending_page.py b/src/install_page/pending_page.py index c4ae4c5..c49b871 100644 --- a/src/install_page/pending_page.py +++ b/src/install_page/pending_page.py @@ -1,11 +1,61 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast +from .result_row import ResultRow + +class AddedGroup(Adw.PreferencesGroup): + __gtype_name__ = "AddedGroup" + + def add_row(self, row): + self.rows.append(row) + self.add(row) + + def rem_row(self, row): + if row in self.rows: + self.rows.remove(row) + self.remove(row) + + def __init__(self, remote, installation, **kwargs): + super().__init__(**kwargs) + + self.remote = remote + self.installation = installation + self.rows = [] + + self.set_title(f"{remote.title}") + self.set_description(_("Installation: {}").format(installation)) @Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/pending_page.ui") class PendingPage(Adw.NavigationPage): __gtype_name__ = "PendingPage" gtc = Gtk.Template.Child + stack = gtc() + none_pending = gtc() + preferences_page = gtc() + + def add_package(self, row): + key = f"{row.package.remote}<>{row.package.installation}" + row = ResultRow(row.package, ResultRow.PackageState.ADDED) + row.connect("activated", self.remove_package, group) + try: + group = self.groups[key] + group.add_row(row) + except KeyError: + group = AddedGroup(row.package.remote, row.package.installation) + group.add_row(row) + self.groups[key] = group + self.preferences_page.append(group) + + def remove_package(self, row, group): + pass + def __init__(self, **kwargs): super().__init__(**kwargs) + + # Extra Object Creation + self.groups = {} # remote<>installation: adw.preference_group + + # Connections + + # Apply diff --git a/src/install_page/result_row.blp b/src/install_page/result_row.blp index 73c969f..d7928a2 100644 --- a/src/install_page/result_row.blp +++ b/src/install_page/result_row.blp @@ -34,17 +34,15 @@ template $ResultRow : Adw.ActionRow { icon-name: "plus-large-symbolic"; } [suffix] + Image selected_image { + icon-name: "check-plain-symbolic"; + } + [suffix] Image sub_image { icon-name: "minus-large-symbolic"; } [suffix] - Image selected_image { - icon-name: "check-plain-symbolic"; - visible: false; - } - [suffix] - Image already_installed_image { - visible: false; + Image installed_image { icon-name: "selection-mode-symbolic"; styles ["success"] } diff --git a/src/install_page/result_row.py b/src/install_page/result_row.py index 6373ea2..88911f3 100644 --- a/src/install_page/result_row.py +++ b/src/install_page/result_row.py @@ -1,6 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio, Gdk from .host_info import HostInfo from .error_toast import ErrorToast +from enum import Enum import os, subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/result_row.ui") @@ -13,6 +14,13 @@ class ResultRow(Adw.ActionRow): add_image = gtc() sub_image = gtc() selected_image = gtc() + installed_image = gtc() + + class PackageState(Enum): + NEW = 0 + SELECTED = 1 + ADDED = 2 + INSTALLED = 3 def idle_stuff(self): self.set_title(GLib.markup_escape_text(self.package.name)) @@ -21,23 +29,43 @@ class ResultRow(Adw.ActionRow): self.branch_label.set_label(GLib.markup_escape_text(self.package.branch)) self.version_label.set_visible(len(self.version_label.get_label()) != 0) self.branch_label.set_visible(len(self.branch_label.get_label()) != 0) - if self.is_added: - self.set_tooltip_text(_("Remove Package from Queue")) - def set_is_added(self, is_added): - self.is_added = is_added - self.set_sensitive(not is_added) - self.add_image.set_visible(not is_added) - self.selected_image.set_visible(is_added) - self.set_tooltip_text(_("This package is queued") if is_added else _("Add Package to Queue")) + def update_state_handler(self, state): + if state == self.state: + return - def __init__(self, package, is_added=False, **kwargs): + self.state = state + self.add_image.set_visible(False) + self.sub_image.set_visible(False) + self.selected_image.set_visible(False) + self.installed_image.set_visible(False) + match state: + case self.PackageState.NEW: + self.set_sensitive(True) + self.set_tooltip_text(_("Add Package to Queue")) + self.add_image.set_visible(True) + case self.PackageState.SELECTED: + self.set_sensitive(False) + self.set_tooltip_text(_("Package has been Added to Queue")) + self.selected_image.set_visible(True) + case self.PackageState.ADDED: + self.set_sensitive(True) + self.set_tooltip_text(_("Remove Package from Queue")) + self.sub_image.set_visible(True) + case self.PackageState.INSTALLED: + self.set_sensitive(False) + self.set_tooltip_text(_("This Package is Already Installed")) + self.installed_image.set_visible(True) + + def __init__(self, package, package_state, parent_row=None, **kwargs): super().__init__(**kwargs) - self.is_added = is_added + # Extra Object Creation + self.state = None self.package = package - self.sub_image.set_visible(is_added) - self.add_image.set_visible(not is_added) + # Connections + # Apply GLib.idle_add(self.idle_stuff) + self.update_state_handler(package_state) diff --git a/src/install_page/results_page.blp b/src/install_page/results_page.blp index 1fb3dfa..be58ac7 100644 --- a/src/install_page/results_page.blp +++ b/src/install_page/results_page.blp @@ -28,10 +28,15 @@ template $ResultsPage : Adw.NavigationPage { } Stack stack { Adw.StatusPage new_search { - icon-name: "flatpak-symbolic"; + icon-name: "loupe-large-symbolic"; title: _("Search for Flatpaks"); description: _("Search for Flatpaks you want to install"); } + Adw.StatusPage too_many { + icon-name: "error-symbolic"; + title: _("Too Many Results"); + description: _("Try being more specific with your search"); + } Adw.StatusPage loading { title: _("Searching"); description: _("This should only take a moment"); diff --git a/src/install_page/results_page.py b/src/install_page/results_page.py index eb44004..39e55b5 100644 --- a/src/install_page/results_page.py +++ b/src/install_page/results_page.py @@ -42,6 +42,7 @@ class ResultsPage(Adw.NavigationPage): results_list = gtc() stack = gtc() new_search = gtc() + too_many = gtc() loading = gtc() results_view= gtc() no_results = gtc() @@ -50,10 +51,14 @@ class ResultsPage(Adw.NavigationPage): self.remote = remote self.installation = installation self.set_title(_("Search {}").format(remote.title)) + self.search_entry.set_text("") self.search_entry.grab_focus() if nav_view: nav_view.push(self) + def add_package(self, row): + print(row) + def on_search(self, *args): self.packages.clear() self.stack.set_visible_child(self.loading) @@ -66,15 +71,19 @@ class ResultsPage(Adw.NavigationPage): def thread(*args): installation = "" if self.installation == "user" or self.installation == "system": - installation = self.installation + installation = f"--{self.installation}" else: installation = f"--installation={self.installation}" try: output = subprocess.run( - ['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', self.remote.name, installation, self.search_entry.get_text()], + ['flatpak-spawn', '--host', 'flatpak', 'search', '--columns=all', installation, self.search_entry.get_text()], check=True, text=True, capture_output=True ).stdout.split('\n') + if len(output) > 100: + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.too_many)) + return + for line in output: line = line.strip() @@ -82,19 +91,27 @@ class ResultsPage(Adw.NavigationPage): if len(info) != 6: continue + remotes = info[5].split(',') + if not self.remote.name in remotes: + continue + package = AddedPackage(info[0], info[2], info[4], info[3], self.remote, self.installation) - row = ResultRow(package) + row = ResultRow(package, ResultRow.PackageState.NEW) + row.connect("activated", self.add_package) self.packages.append(package) GLib.idle_add(lambda *_, _row=row: self.results_list.append(_row)) + if len(self.packages) > 0: + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.results_view)) + else: + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.no_results)) + except subprocess.CalledProcessError as cpe: print(cpe) def callback(*args): - if len(self.packages) > 0: - self.stack.set_visible_child(self.results_view) - else: - self.stack.set_visible_child(self.no_results) + pass + Gio.Task.new(None, None, callback).run_in_thread(thread) diff --git a/src/install_page/select_page.py b/src/install_page/select_page.py index c9061c8..d1587ad 100644 --- a/src/install_page/select_page.py +++ b/src/install_page/select_page.py @@ -17,10 +17,12 @@ class SelectPage(Adw.NavigationPage): test = gtc() def start_loading(self): - pass + self.nav_view.pop() + for row in self.remote_rows: + self.remotes_group.remove(row) + self.remote_rows.clear() def end_loading(self): - total_remotes = 0 for installation, remotes in HostInfo.remotes.items(): for remote in remotes: if remote.disabled: @@ -30,9 +32,9 @@ class SelectPage(Adw.NavigationPage): row.add_suffix(Gtk.Image(icon_name="right-large-symbolic")) row.connect("activated", self.results_page.show_remote, remote, installation, self.nav_view) self.remotes_group.add(row) - total_remotes += 1 + self.remote_rows.append(row) - self.remotes_group.set_visible(total_remotes != 0) + self.remotes_group.set_visible(len(self.remote_rows) != 0) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -45,6 +47,7 @@ class SelectPage(Adw.NavigationPage): self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.add_remote_row.connect("activated", lambda *_: HostInfo.main_window.activate_row(HostInfo.main_window.remotes_row)) self.nav_view.connect("popped", self.results_page.on_back) + self.remote_rows = [] self.test.connect("clicked", lambda *_: self.nav_view.push(self.results_page)) From 76fdbde9e7c30e3a13309c558db5539bb157ab75 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 24 Aug 2024 14:45:37 -0400 Subject: [PATCH 130/332] More work on install page --- src/install_page/install_page.py | 4 +++- src/install_page/pending_page.blp | 8 ------- src/install_page/pending_page.py | 40 ++++++++++++++++++++++++------- src/install_page/result_row.py | 8 ++++--- src/install_page/results_page.py | 18 ++++++++++---- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index 18f12c8..6073271 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -9,8 +9,9 @@ class InstallPage(Adw.BreakpointBin): gtc = Gtk.Template.Child break_point = gtc() - select_page = gtc() split_view = gtc() + select_page = gtc() + pending_page = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -45,3 +46,4 @@ class InstallPage(Adw.BreakpointBin): # Apply # ======== self.split_view.set_sidebar(self.select_page) # ======== self.split_view.set_content(self.pending_page) + self.select_page.results_page.pending_page = self.pending_page diff --git a/src/install_page/pending_page.blp b/src/install_page/pending_page.blp index 1bce7d2..ef98bb4 100644 --- a/src/install_page/pending_page.blp +++ b/src/install_page/pending_page.blp @@ -14,14 +14,6 @@ template $PendingPage : Adw.NavigationPage { description: _("Packages queued to install will show up here"); } Adw.PreferencesPage preferences_page { - Adw.PreferencesGroup { - title: "Flathub"; - description: "Installation: system"; - Adw.ActionRow { - title: "Firefox"; - subtitle: "org.mozilla.Firefox"; - } - } } } [bottom] diff --git a/src/install_page/pending_page.py b/src/install_page/pending_page.py index c49b871..8d93ecb 100644 --- a/src/install_page/pending_page.py +++ b/src/install_page/pending_page.py @@ -34,27 +34,49 @@ class PendingPage(Adw.NavigationPage): none_pending = gtc() preferences_page = gtc() - def add_package(self, row): + def add_package_row(self, row): + self.added_packages.append(row.package) + row.set_state(ResultRow.PackageState.SELECTED) key = f"{row.package.remote}<>{row.package.installation}" - row = ResultRow(row.package, ResultRow.PackageState.ADDED) - row.connect("activated", self.remove_package, group) + added_row = ResultRow(row.package, ResultRow.PackageState.ADDED, row.origin_list_box) + group = None try: group = self.groups[key] - group.add_row(row) + group.add_row(added_row) except KeyError: - group = AddedGroup(row.package.remote, row.package.installation) - group.add_row(row) + group = AddedGroup(added_row.package.remote, added_row.package.installation) + group.add_row(added_row) self.groups[key] = group - self.preferences_page.append(group) + self.preferences_page.add(group) - def remove_package(self, row, group): - pass + added_row.connect("activated", self.remove_package_row, group) + self.stack.set_visible_child(self.preferences_page) + + def remove_package_row(self, row, group): + # row.origin_row.set_state(ResultRow.PackageState.NEW) + for item in row.origin_list_box: + if item.state == ResultRow.PackageState.SELECTED and item.package.is_similar(row.package): + item.set_state(ResultRow.PackageState.NEW) + break + + group.rem_row(row) + if row.package in self.added_packages: + self.added_packages.remove(row.package) + + if len(group.rows) == 0: + key = f"{row.package.remote}<>{row.package.installation}" + self.groups.pop(key, None) + self.preferences_page.remove(group) + + if len(self.added_packages) == 0: + self.stack.set_visible_child(self.none_pending) def __init__(self, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.groups = {} # remote<>installation: adw.preference_group + self.added_packages = [] # Connections diff --git a/src/install_page/result_row.py b/src/install_page/result_row.py index 88911f3..78a0cca 100644 --- a/src/install_page/result_row.py +++ b/src/install_page/result_row.py @@ -30,7 +30,7 @@ class ResultRow(Adw.ActionRow): self.version_label.set_visible(len(self.version_label.get_label()) != 0) self.branch_label.set_visible(len(self.branch_label.get_label()) != 0) - def update_state_handler(self, state): + def set_state(self, state): if state == self.state: return @@ -57,15 +57,17 @@ class ResultRow(Adw.ActionRow): self.set_tooltip_text(_("This Package is Already Installed")) self.installed_image.set_visible(True) - def __init__(self, package, package_state, parent_row=None, **kwargs): + def __init__(self, package, package_state, origin_list_box, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.state = None self.package = package + self.origin_list_box = origin_list_box # Connections # Apply GLib.idle_add(self.idle_stuff) - self.update_state_handler(package_state) + self.set_state(package_state) + diff --git a/src/install_page/results_page.py b/src/install_page/results_page.py index 39e55b5..f2ba938 100644 --- a/src/install_page/results_page.py +++ b/src/install_page/results_page.py @@ -56,8 +56,8 @@ class ResultsPage(Adw.NavigationPage): if nav_view: nav_view.push(self) - def add_package(self, row): - print(row) + def add_package_row(self, row): + self.pending_page.add_package_row(row) def on_search(self, *args): self.packages.clear() @@ -96,8 +96,17 @@ class ResultsPage(Adw.NavigationPage): continue package = AddedPackage(info[0], info[2], info[4], info[3], self.remote, self.installation) - row = ResultRow(package, ResultRow.PackageState.NEW) - row.connect("activated", self.add_package) + row = ResultRow(package, ResultRow.PackageState.NEW, self.results_list) + for item in self.pending_page.added_packages: + if package.is_similar(item): + row.set_state(ResultRow.PackageState.SELECTED) + + if package.app_id in HostInfo.id_to_flatpak: + installed_package = HostInfo.id_to_flatpak[package.app_id] + if installed_package.info["id"] == package.app_id and installed_package.info["branch"] == package.branch: + row.set_state(ResultRow.PackageState.INSTALLED) + + row.connect("activated", self.add_package_row) self.packages.append(package) GLib.idle_add(lambda *_, _row=row: self.results_list.append(_row)) @@ -126,6 +135,7 @@ class ResultsPage(Adw.NavigationPage): self.remote = None self.installation = None self.packages = [] + self.pending_page = None # Connections self.search_entry.connect("activate", self.on_search) From 1862d9e0a6207d88124828ed9a20706c3bddb88c Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 25 Aug 2024 16:05:15 -0400 Subject: [PATCH 131/332] Handle when there are no enabled remotes --- src/remotes_page/remote_row.py | 5 +++ src/remotes_page/remotes_page.blp | 23 +++++++++++ src/remotes_page/remotes_page.py | 63 ++++++++++++++++++++----------- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 74de3fe..0b41fff 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -28,6 +28,8 @@ class RemoteRow(Adw.ActionRow): self.menu_listbox.get_row_at_index(2).set_visible(False) self.menu_listbox.get_row_at_index(3).set_visible(True) self.parent_page.total_disabled -= 1 + HostInfo.main_window.pages[HostInfo.main_window.install_row].start_loading() + HostInfo.main_window.pages[HostInfo.main_window.install_row].end_loading() if self.parent_page.total_disabled == 0: self.parent_page.show_disabled_button.set_active(False) self.parent_page.show_disabled_button.set_visible(False) @@ -64,6 +66,9 @@ class RemoteRow(Adw.ActionRow): self.set_visible(self.parent_page.show_disabled_button.get_active()) self.parent_page.show_disabled_button.set_visible(True) self.parent_page.total_disabled += 1 + self.parent_page.none_visible_handler() + HostInfo.main_window.pages[HostInfo.main_window.install_row].start_loading() + HostInfo.main_window.pages[HostInfo.main_window.install_row].end_loading() def thread(*args): if self.remote.disabled: diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 801bd41..754f58c 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -63,6 +63,29 @@ template $RemotesPage : Adw.NavigationPage { } } ; + Adw.ActionRow none_visible { + [child] + Box { + spacing: 3; + orientation: vertical; + Label { + margin-top: 7; + label: _("No Enabled Remotes"); + wrap: true; + halign: center; + styles ["heading"] + } + Label { + label: _("You only have disabled remotes on this system"); + margin-start: 16; + margin-end: 16; + margin-bottom: 8; + justify: center; + halign: center; + wrap: true; + } + } + } } Adw.PreferencesGroup new_remotes_group { visible: bind search_button.active inverted; diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 266689a..400c133 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -83,6 +83,7 @@ class RemotesPage(Adw.NavigationPage): new_remotes_group = gtc() file_remote_row = gtc() custom_remote_row = gtc() + none_visible = gtc() # Statuses loading_remotes = gtc() @@ -103,31 +104,35 @@ class RemotesPage(Adw.NavigationPage): self.current_remote_rows.clear() - def end_loading(self): - if len(HostInfo.remotes) < 1 or len(list(HostInfo.remotes.items())[0][1]) < 1: - self.search_button.set_sensitive(False) - self.search_button.set_active(False) - self.stack.set_visible_child(self.no_remotes) - self.search_button.set_sensitive(False) - self.search_entry.set_editable(False) - return - else: - self.search_button.set_sensitive(True) + def none_visible_handler(self): + any_visible = False + for row in self.current_remote_rows: + if row.get_visible(): + any_visible = True + break + + self.none_visible.set_visible(not any_visible) + def end_loading(self): show_disabled = self.show_disabled_button.get_active() self.show_disabled_button.set_visible(False) - for install in HostInfo.installations: - try: - for remote in HostInfo.remotes[install]: - row = RemoteRow(self, install, remote) - self.current_remotes_group.add(row) - self.current_remote_rows.append(row) - if (not show_disabled) and remote.disabled: + total_visible = 0 + for installation, remotes in HostInfo.remotes.items(): + for remote in remotes: + row = RemoteRow(self, installation, remote) + self.current_remote_rows.append(row) + self.current_remotes_group.add(row) + if row.remote.disabled: + self.total_disabled += 1 + self.show_disabled_button.set_visible(True) + if show_disabled: + total_visible += 1 + else: row.set_visible(False) - self.total_disabled += 1 - self.show_disabled_button.set_visible(True) - except KeyError: - continue + else: + total_visible += 1 + + self.none_visible.set_visible(total_visible == 0) GLib.idle_add(lambda *_: self.stack.set_visible_child(self.content_page)) self.search_button.set_sensitive(True) @@ -224,8 +229,20 @@ class RemotesPage(Adw.NavigationPage): file_chooser.open(self.main_window, None, self.file_callback) def show_disabled_handler(self, button): - self.show_disabled_button_content.set_icon_name("eye-open-negative-filled-symbolic" if button.get_active() else "eye-not-looking-symbolic") - self.on_search(self.search_entry) + show_disabled = button.get_active() + self.show_disabled_button_content.set_icon_name("eye-open-negative-filled-symbolic" if show_disabled else "eye-not-looking-symbolic") + total_visible = 0 + for row in self.current_remote_rows: + if row.remote.disabled: + if show_disabled: # show disabled + row.set_visible(True) + total_visible += 1 + else: + row.set_visible(False) + else: + total_visible += 1 + + self.none_visible.set_visible(total_visible == 0) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) From 39f27c094dd5be9438ccc718fc6ea5f74f79d28c Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 25 Aug 2024 16:28:23 -0400 Subject: [PATCH 132/332] Properly handle having no remotes --- src/remotes_page/remote_row.py | 50 ++++++++++++++------------- src/remotes_page/remotes_page.blp | 56 +++++++++++++++++++++++++------ src/remotes_page/remotes_page.py | 6 ++++ 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 0b41fff..b655a48 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -20,7 +20,30 @@ class RemoteRow(Adw.ActionRow): remove = gtc() def enable_remote_handler(self, *args): - def idle_stuff(*args): + if not self.remote.disabled: + self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), _("Remote is already enabled")).toast) + return + + has_error = [] + def thread(*args): + cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-modify', '--enable', self.remote.name] + if self.installation == "user" or self.installation == "system": + cmd.append(f"--{self.installation}") + else: + cmd.append(f"--installation={self.installation}") + + try: + subprocess.run(cmd, check=True, capture_output=True, text=True) + except subprocess.CalledProcessError as cpe: + has_error.append(str(cpe.stderr)) + except Exception as e: + has_error.append(str(e)) + + def callback(*args): + if len(has_error) > 0: + GLib.idle_add(lambda *args, cpe=cpe: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), has_error[0]).toast)) + return + self.remove_css_class("warning") self.set_icon_name("") self.remote.disabled = False @@ -33,27 +56,8 @@ class RemoteRow(Adw.ActionRow): if self.parent_page.total_disabled == 0: self.parent_page.show_disabled_button.set_active(False) self.parent_page.show_disabled_button.set_visible(False) - - if not self.remote.disabled: - self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), _("Remote is already enabled")).toast) - return - - cmd = ['flatpak-spawn', '--host', 'flatpak', 'remote-modify', '--enable', self.remote.name] - if self.installation == "user" or self.installation == "system": - cmd.append(f"--{self.installation}") - else: - cmd.append(f"--installation={self.installation}") - - try: - subprocess.run(cmd, check=True, capture_output=True, text=True) - except subprocess.CalledProcessError as cpe: - GLib.idle_add(lambda *args, cpe=cpe: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), str(cpe.stderr)).toast)) - return - except Exception as e: - GLib.idle_add(lambda *args, e=e: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not enable remote"), str(e)).toast)) - return - - idle_stuff() + + Gio.Task.new(None, None, callback).run_in_thread(thread) def disable_remote_handler(self, *args): def callback(*args): @@ -113,7 +117,7 @@ class RemoteRow(Adw.ActionRow): HostInfo.clipboard.set(self.get_subtitle()) self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Copied name"))) case self.enable_remote: - Gio.Task().run_in_thread(self.enable_remote_handler) + self.enable_remote_handler() case self.disable_remote: self.disable_remote_handler() case self.remove: diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 754f58c..240c666 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -44,11 +44,6 @@ template $RemotesPage : Adw.NavigationPage { description: _("Try a different search"); icon-name: "system-search-symbolic"; } - Adw.StatusPage no_remotes { - title: _("No Remotes Found"); - description: _("Warehouse cannot see the current remotes or your system has no remotes added"); - icon-name: "error-symbolic"; - } Adw.PreferencesPage content_page { Adw.PreferencesGroup current_remotes_group { title: _("Current Remotes"); @@ -64,16 +59,25 @@ template $RemotesPage : Adw.NavigationPage { } ; Adw.ActionRow none_visible { + styles ["warning"] [child] Box { spacing: 3; orientation: vertical; - Label { - margin-top: 7; - label: _("No Enabled Remotes"); - wrap: true; + Box { halign: center; - styles ["heading"] + Image { + valign: center; + margin-top: 7; + margin-end: 6; + icon-name: "eye-not-looking-symbolic"; + } + Label { + margin-top: 7; + label: _("No Enabled Remotes"); + wrap: true; + styles ["heading"] + } } Label { label: _("You only have disabled remotes on this system"); @@ -86,6 +90,38 @@ template $RemotesPage : Adw.NavigationPage { } } } + Adw.ActionRow no_remotes { + styles ["error"] + [child] + Box { + spacing: 3; + orientation: vertical; + Box { + halign: center; + Image { + valign: center; + margin-top: 7; + margin-end: 6; + icon-name: "error-symbolic"; + } + Label { + margin-top: 7; + label: _("No Remotes Found"); + wrap: true; + styles ["heading"] + } + } + Label { + label: _("Warehouse cannot see the current remotes or your system has no remotes added"); + margin-start: 16; + margin-end: 16; + margin-bottom: 8; + justify: center; + halign: center; + wrap: true; + } + } + } } Adw.PreferencesGroup new_remotes_group { visible: bind search_button.active inverted; diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 400c133..02e7395 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -134,6 +134,12 @@ class RemotesPage(Adw.NavigationPage): self.none_visible.set_visible(total_visible == 0) + if len(self.current_remote_rows) == 0: + self.no_remotes.set_visible(True) + self.none_visible.set_visible(False) + else: + self.no_remotes.set_visible(False) + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.content_page)) self.search_button.set_sensitive(True) self.search_entry.set_editable(True) From 9af38d5f3c0132471df75c1fb4f15f94afec5fa6 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 25 Aug 2024 17:04:29 -0400 Subject: [PATCH 133/332] Handle no remotes in the filters page --- src/packages_page/filters_page.py | 38 +++++++++++++------------------ src/remotes_page/remote_row.py | 17 ++++++++++---- src/remotes_page/remotes_page.py | 4 ++++ 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/packages_page/filters_page.py b/src/packages_page/filters_page.py index a4e1612..00f14a2 100644 --- a/src/packages_page/filters_page.py +++ b/src/packages_page/filters_page.py @@ -110,29 +110,23 @@ class FiltersPage(Adw.NavigationPage): def generate_remote_filters(self): for row in self.remote_rows: self.remotes_group.remove(row) + self.remote_rows.clear() - # if len(HostInfo.remotes) < 2 and len(list(HostInfo.remotes.items())[0][1]) < 2: - # self.remotes_group.set_visible(False) - # if self.remotes_string != "all": - # self.remotes_string = "all" - # self.settings.set_string("remotes-list", self.remotes_string) - # self.packages_page.apply_filters() - # return - for i, installation in enumerate(HostInfo.installations): - try: - for remote in HostInfo.remotes[installation]: - # if remote.disabled: - # continue - row = FilterRow(remote, installation) - row.set_title(remote.title) - row.set_subtitle(_("Installation: {}").format(installation)) - row.check_button.set_active(f"{remote.name}<>{installation}" in self.remotes_string) - row.check_button.connect("toggled", lambda *_, row=row: self.remote_row_check_handler(row)) - row.set_visible(self.all_remotes_switch.get_active()) - self.remote_rows.append(row) - self.remotes_group.add(row) - except KeyError: - pass + for installation, remotes in HostInfo.remotes.items(): + for remote in remotes: + if remote.disabled: + continue + + row = FilterRow(remote, installation) + row.set_title(remote.title) + row.set_subtitle(_("Installation: {}").format(installation)) + row.check_button.set_active(f"{remote.name}<>{installation}" in self.remotes_string) + row.check_button.connect("toggled", lambda *_, row=row: self.remote_row_check_handler(row)) + row.set_visible(self.all_remotes_switch.get_active()) + self.remote_rows.append(row) + self.remotes_group.add(row) + + self.remotes_group.set_visible(len(self.remote_rows) > 1) self.all_remotes_switch.set_active("all" != self.remotes_string) def generate_runtime_filters(self): diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index b655a48..62005db 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -51,8 +51,11 @@ class RemoteRow(Adw.ActionRow): self.menu_listbox.get_row_at_index(2).set_visible(False) self.menu_listbox.get_row_at_index(3).set_visible(True) self.parent_page.total_disabled -= 1 - HostInfo.main_window.pages[HostInfo.main_window.install_row].start_loading() - HostInfo.main_window.pages[HostInfo.main_window.install_row].end_loading() + install_page = HostInfo.main_window.pages[HostInfo.main_window.install_row] + install_page.start_loading() + install_page.end_loading() + filters_page = HostInfo.main_window.pages[HostInfo.main_window.packages_row].filters_page + filters_page.generate_filters() if self.parent_page.total_disabled == 0: self.parent_page.show_disabled_button.set_active(False) self.parent_page.show_disabled_button.set_visible(False) @@ -71,8 +74,14 @@ class RemoteRow(Adw.ActionRow): self.parent_page.show_disabled_button.set_visible(True) self.parent_page.total_disabled += 1 self.parent_page.none_visible_handler() - HostInfo.main_window.pages[HostInfo.main_window.install_row].start_loading() - HostInfo.main_window.pages[HostInfo.main_window.install_row].end_loading() + install_page = HostInfo.main_window.pages[HostInfo.main_window.install_row] + install_page.start_loading() + install_page.end_loading() + filters_page = HostInfo.main_window.pages[HostInfo.main_window.packages_row].filters_page + filters_page.settings.reset("remotes-list") + filters_page.all_remotes_switch.set_active(False) + filters_page.generate_filters() + filters_page.packages_page.apply_filters() def thread(*args): if self.remote.disabled: diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 02e7395..0b472c9 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -175,6 +175,10 @@ class RemotesPage(Adw.NavigationPage): if error[0]: self.toast_overlay.add_toast(ErrorToast(_("Could not remove remote"), str(error[0])).toast) else: + filters_page = HostInfo.main_window.pages[HostInfo.main_window.packages_row].filters_page + filters_page.settings.reset("remotes-list") + filters_page.all_remotes_switch.set_active(False) + # filters_page.packages_page.apply_filters() self.main_window.refresh_handler() self.toast_overlay.add_toast(Adw.Toast(title=_("Removed {}").format(row.remote.title))) From 394ec9859478aaadfe098be573dbf72003d232b5 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 25 Aug 2024 17:05:57 -0400 Subject: [PATCH 134/332] Handle having < 2 runtimes --- src/packages_page/filters_page.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/packages_page/filters_page.py b/src/packages_page/filters_page.py index 00f14a2..1b63971 100644 --- a/src/packages_page/filters_page.py +++ b/src/packages_page/filters_page.py @@ -139,7 +139,9 @@ class FiltersPage(Adw.NavigationPage): self.runtimes_string = "all" self.settings.set_string("runtimes-list", self.runtimes_string) self.packages_page.apply_filters() + return + for j, ref in enumerate(HostInfo.dependant_runtime_refs): row = FilterRow(ref) row.set_title(ref) @@ -148,6 +150,8 @@ class FiltersPage(Adw.NavigationPage): row.set_visible(self.all_runtimes_switch.get_active()) self.runtime_rows.append(row) self.runtimes_group.add(row) + + self.runtimes_group.set_visible(len(self.runtime_rows) > 1) self.all_runtimes_switch.set_active("all" != self.runtimes_string) def generate_filters(self): From 7ce710708f60b357f7d1ceac5a6688d803e925aa Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 25 Aug 2024 17:09:44 -0400 Subject: [PATCH 135/332] Fix error when there is no visible row to select --- src/packages_page/packages_page.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 104f38a..dcf1fb9 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -159,8 +159,10 @@ class PackagesPage(Adw.BreakpointBin): first_visible_row = row break - self.packages_list_box.select_row(first_visible_row) - self.properties_page.set_properties(first_visible_row.package) + 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) + 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): From bb1a374db37bf7e036adb888b8342443bb380970 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 25 Aug 2024 17:39:40 -0400 Subject: [PATCH 136/332] Long press and rclicks now select rows --- src/packages_page/app_row.py | 18 ++++++++++++------ src/packages_page/packages_page.blp | 3 +++ src/packages_page/packages_page.py | 12 +++++++++--- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/packages_page/app_row.py b/src/packages_page/app_row.py index f3aa2a7..1e3ea1b 100644 --- a/src/packages_page/app_row.py +++ b/src/packages_page/app_row.py @@ -16,20 +16,26 @@ class AppRow(Adw.ActionRow): if self.package.icon_path: self.image.add_css_class("icon-dropshadow") self.image.set_from_file(self.package.icon_path) - - if self.callback: - self.callback() - def __init__(self, package, callback=None, **kwargs): + def gest(self, *args): + self.on_long_press(self) + + def __init__(self, package, on_long_press=None, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.package = package - self.callback = callback + self.on_long_press = on_long_press + self.rclick_gesture = Gtk.GestureClick() + self.long_press_gesture = Gtk.GestureLongPress() # Apply GLib.idle_add(lambda *_: self.set_title(package.info["name"])) GLib.idle_add(lambda *_: self.set_subtitle(package.info["id"])) GLib.idle_add(lambda *_: self.idle_stuff()) + self.rclick_gesture.set_button(3) + self.add_controller(self.rclick_gesture) - # Connections \ No newline at end of file + # Connections + self.rclick_gesture.connect("released", self.gest) + self.long_press_gesture.connect("pressed", self.gest) \ No newline at end of file diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 9f613f4..031ce2f 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -157,12 +157,15 @@ Popover copy_pop { ListBox copy_menu { Label copy_names { label: _("Copy Names"); + halign: start; } Label copy_ids { label: _("Copy IDs"); + halign: start; } Label copy_refs { label: _("Copy Refs"); + halign: start; } } } \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index dcf1fb9..fd5c1a8 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -127,6 +127,10 @@ class PackagesPage(Adw.BreakpointBin): 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.packages_list_box.remove_all() GLib.idle_add(lambda *_: self.filters_page.generate_filters()) @@ -135,8 +139,9 @@ class PackagesPage(Adw.BreakpointBin): if len(HostInfo.flatpaks) == 0: self.set_status(self.no_packages) return + for package in HostInfo.flatpaks: - row = AppRow(package) + row = AppRow(package, self.row_rclick_handler) package.app_row = row row.masked_status_icon.set_visible(package.is_masked) row.pinned_status_icon.set_visible(package.is_pinned) @@ -148,6 +153,7 @@ class PackagesPage(Adw.BreakpointBin): row.eol_runtime_status_icon.set_visible(package.dependant_runtime.is_eol) except Exception as e: 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() @@ -162,7 +168,7 @@ class PackagesPage(Adw.BreakpointBin): 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) - + 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): @@ -313,7 +319,7 @@ class PackagesPage(Adw.BreakpointBin): 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) - self.select_button.connect("clicked", self.select_button_handler) + self.select_button.connect("toggled", self.select_button_handler) self.filter_button.connect("toggled", self.filter_button_handler) self.reset_filters_button.connect("clicked", lambda *_: self.filters_page.reset_filters()) self.packages_split.connect("notify::show-content", self.filter_page_handler) From 485a845b5eea33f0e1f0cf85af0fcff3cbb7a593 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 25 Aug 2024 23:19:46 -0400 Subject: [PATCH 137/332] Sync current work --- data/io.github.flattool.Warehouse.gschema.xml | 14 +++++++ src/install_page/install_page.py | 1 + src/main_window/window.py | 38 +++++++++++++------ src/packages_page/app_row.py | 10 ++--- src/packages_page/packages_page.py | 1 + src/remotes_page/remotes_page.py | 1 + src/snapshot_page/snapshot_page.py | 2 + src/user_data_page/user_data_page.py | 1 + 8 files changed, 51 insertions(+), 17 deletions(-) diff --git a/data/io.github.flattool.Warehouse.gschema.xml b/data/io.github.flattool.Warehouse.gschema.xml index 788abef..72aeb0e 100644 --- a/data/io.github.flattool.Warehouse.gschema.xml +++ b/data/io.github.flattool.Warehouse.gschema.xml @@ -13,6 +13,12 @@ false + + true + + + "packages" + @@ -28,4 +34,12 @@ "all" + + + false + + + "size" + + diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index 6073271..d023f51 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -17,6 +17,7 @@ class InstallPage(Adw.BreakpointBin): # 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 = "install" current_installation = "" current_remote = None diff --git a/src/main_window/window.py b/src/main_window/window.py index 40ede06..a8bcc09 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -55,13 +55,6 @@ class WarehouseWindow(Adw.ApplicationWindow): # if keyval == Gdk.KEY_Escape: # self.batch_mode_button.set_active(False) - def navigation_handler(self, _, row, hide_sidebar=True): - row = row.get_child() - page = self.pages[row] - self.stack.set_visible_child(page) - if self.main_split.get_collapsed(): - self.main_split.set_show_sidebar(False) - def start_loading(self, *args): for _, page in self.pages.items(): if page.instance: @@ -78,6 +71,14 @@ class WarehouseWindow(Adw.ApplicationWindow): self.refresh_button.set_sensitive(False) HostInfo.get_flatpaks(callback=self.end_loading) + def navigation_handler(self, _, row): + row = row.get_child() + page = self.pages[row] + self.stack.set_visible_child(page) + self.settings.set_string("page-shown", page.page_name) + if self.main_split.get_collapsed(): + self.main_split.set_show_sidebar(False) + def activate_row(self, nav_row): idx = 0 while row := self.navigation_row_listbox.get_row_at_index(idx): @@ -85,7 +86,12 @@ class WarehouseWindow(Adw.ApplicationWindow): if row.get_child() is nav_row: row.activate() nav_row.grab_focus() - return + break + + def save_sidebar_state(self, *args): + state = self.main_split.get_show_sidebar() + self.settings.set_boolean("sidebar-shown", state) + print(self.settings.get_boolean("sidebar-shown")) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -104,8 +110,14 @@ class WarehouseWindow(Adw.ApplicationWindow): self.install_row: InstallPage(main_window=self), } - for _, page in self.pages.items(): + self.navigation_row_listbox.connect("row-activated", self.navigation_handler) + + page_to_show = self.settings.get_string("page-shown") + print(page_to_show) + for row, page in self.pages.items(): self.stack.add_child(page) + if page_to_show == page.page_name: + self.activate_row(row) # Apply self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT) @@ -120,12 +132,14 @@ class WarehouseWindow(Adw.ApplicationWindow): # Connections event_controller.connect("key-pressed", self.key_handler) - self.navigation_row_listbox.connect("row-activated", self.navigation_handler) # file_drop.connect("drop", self.drop_callback) self.refresh_button.connect("clicked", self.refresh_handler) - self.activate_row(self.install_row) - self.main_split.set_show_sidebar(True) + # self.activate_row(self.user_data_row) + # self.main_split.set_show_sidebar(self.settings.get_boolean("sidebar-shown")) + # GLib.idle_add(lambda *_: self.main_split.set_show_sidebar(False)) + # print(self.settings.get_boolean("sidebar-shown")) + # self.main_split.connect("notify::show-sidebar", self.save_sidebar_state) self.start_loading() HostInfo.get_flatpaks(callback=self.end_loading) diff --git a/src/packages_page/app_row.py b/src/packages_page/app_row.py index 1e3ea1b..3ca505f 100644 --- a/src/packages_page/app_row.py +++ b/src/packages_page/app_row.py @@ -17,7 +17,7 @@ class AppRow(Adw.ActionRow): self.image.add_css_class("icon-dropshadow") self.image.set_from_file(self.package.icon_path) - def gest(self, *args): + def gesture_handler(self, *args): self.on_long_press(self) def __init__(self, package, on_long_press=None, **kwargs): @@ -26,16 +26,16 @@ class AppRow(Adw.ActionRow): # Extra Object Creation self.package = package self.on_long_press = on_long_press - self.rclick_gesture = Gtk.GestureClick() + self.rclick_gesture = Gtk.GestureClick(button=3) self.long_press_gesture = Gtk.GestureLongPress() # Apply GLib.idle_add(lambda *_: self.set_title(package.info["name"])) GLib.idle_add(lambda *_: self.set_subtitle(package.info["id"])) GLib.idle_add(lambda *_: self.idle_stuff()) - self.rclick_gesture.set_button(3) self.add_controller(self.rclick_gesture) + self.add_controller(self.long_press_gesture) # Connections - self.rclick_gesture.connect("released", self.gest) - self.long_press_gesture.connect("pressed", self.gest) \ No newline at end of file + self.rclick_gesture.connect("released", self.gesture_handler) + self.long_press_gesture.connect("pressed", self.gesture_handler) \ No newline at end of file diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index fd5c1a8..b937502 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -42,6 +42,7 @@ class PackagesPage(Adw.BreakpointBin): # 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): diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 0b472c9..52aa50f 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -95,6 +95,7 @@ class RemotesPage(Adw.NavigationPage): # 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 = "remotes" def start_loading(self): self.stack.set_visible_child(self.loading_remotes) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 7ad23cc..b1ddfbe 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -46,6 +46,8 @@ class SnapshotPage(Adw.BreakpointBin): # 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" + snapshots_path = f"{HostInfo.home}/.var/app/io.github.flattool.Warehouse/data/Snapshots/" def sort_snapshots(self, *args): diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 9e9b663..96e3422 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -38,6 +38,7 @@ class UserDataPage(Adw.BreakpointBin): # 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 = "user-data" def sort_data(self, *args): self.data_flatpaks.clear() From 2d68866aa5450ed44e6528f0cd2113f77efad1d0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 10:37:05 -0400 Subject: [PATCH 138/332] reuse code for sidebar button --- src/gtk/sidebar_button.py | 19 +++++++++++++++++++ src/install_page/select_page.blp | 5 +---- src/install_page/select_page.py | 5 +---- src/main_window/window.py | 1 + src/meson.build | 1 + src/packages_page/packages_page.blp | 5 +---- src/packages_page/packages_page.py | 5 +---- src/remotes_page/remotes_page.blp | 5 +---- src/remotes_page/remotes_page.py | 4 ---- src/snapshot_page/snapshot_page.blp | 5 +---- src/snapshot_page/snapshot_page.py | 6 +----- src/user_data_page/user_data_page.blp | 5 +---- src/user_data_page/user_data_page.py | 6 +----- 13 files changed, 30 insertions(+), 42 deletions(-) create mode 100644 src/gtk/sidebar_button.py diff --git a/src/gtk/sidebar_button.py b/src/gtk/sidebar_button.py new file mode 100644 index 0000000..f808c5a --- /dev/null +++ b/src/gtk/sidebar_button.py @@ -0,0 +1,19 @@ +from gi.repository import Adw, Gtk, Gdk, GLib +from .host_info import HostInfo + +class SidebarButton(Gtk.ToggleButton): + __gtype_name__ = "SidebarButton" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creation + main_split = HostInfo.main_window.main_split + + # Connections + main_split.connect("notify::show-sidebar", lambda *_: self.set_active(main_split.get_show_sidebar())) + self.connect("toggled", lambda *_: main_split.set_show_sidebar(self.get_active())) + + # Apply + self.set_icon_name("dock-left-symbolic") + self.set_tooltip_text(_("Show Sidebar")) \ No newline at end of file diff --git a/src/install_page/select_page.blp b/src/install_page/select_page.blp index 9b6796d..08f0557 100644 --- a/src/install_page/select_page.blp +++ b/src/install_page/select_page.blp @@ -12,10 +12,7 @@ template $SelectPage : Adw.NavigationPage { [top] Adw.HeaderBar { [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } + $SidebarButton {} [start] Button test { label: "test"; diff --git a/src/install_page/select_page.py b/src/install_page/select_page.py index d1587ad..6d89f73 100644 --- a/src/install_page/select_page.py +++ b/src/install_page/select_page.py @@ -2,6 +2,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast from .results_page import ResultsPage +from .sidebar_button import SidebarButton @Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/select_page.ui") class SelectPage(Adw.NavigationPage): @@ -9,7 +10,6 @@ class SelectPage(Adw.NavigationPage): gtc = Gtk.Template.Child nav_view = gtc() - sidebar_button = gtc() results_page = gtc() remotes_group = gtc() add_remote_row = gtc() @@ -40,11 +40,8 @@ class SelectPage(Adw.NavigationPage): super().__init__(**kwargs) # Extra Object Creation - ms = HostInfo.main_window.main_split # Connections - ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) - self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.add_remote_row.connect("activated", lambda *_: HostInfo.main_window.activate_row(HostInfo.main_window.remotes_row)) self.nav_view.connect("popped", self.results_page.on_back) self.remote_rows = [] diff --git a/src/main_window/window.py b/src/main_window/window.py index a8bcc09..ca7557d 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -143,3 +143,4 @@ class WarehouseWindow(Adw.ApplicationWindow): self.start_loading() HostInfo.get_flatpaks(callback=self.end_loading) + # GLib.idle_add(lambda *_: self.main_split.set_show_sidebar(False)) diff --git a/src/meson.build b/src/meson.build index 0cdbc67..063d9bc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -69,6 +69,7 @@ warehouse_sources = [ 'host_info.py', 'packages_page/app_row.py', 'gtk/error_toast.py', + 'gtk/sidebar_button.py', 'main_window/window.py', 'packages_page/packages_page.py', 'packages_page/filters_page.py', diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 031ce2f..bcb55cb 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -27,10 +27,7 @@ template $PackagesPage : Adw.BreakpointBin { [top] Adw.HeaderBar { [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } + $SidebarButton {} [start] ToggleButton search_button { icon-name: "loupe-large-symbolic"; diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index b937502..d2f89bd 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -4,6 +4,7 @@ from .app_row import AppRow from .error_toast import ErrorToast from .properties_page import PropertiesPage from .filters_page import FiltersPage +from .sidebar_button import SidebarButton import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") @@ -20,7 +21,6 @@ class PackagesPage(Adw.BreakpointBin): reset_filters_button = gtc() no_packages = gtc() no_results = gtc() - sidebar_button = gtc() filter_button = gtc() search_bar = gtc() search_entry = gtc() @@ -303,7 +303,6 @@ class PackagesPage(Adw.BreakpointBin): self.is_result = False self.prev_status = None self.selected_rows = [] - ms = main_window.main_split # Apply # self.set_status("loading_packages") @@ -314,8 +313,6 @@ class PackagesPage(Adw.BreakpointBin): self.__class__.instance = self # Connections - ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) - self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.search_entry.connect("search-changed", self.on_invalidate) self.search_bar.set_key_capture_widget(main_window) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 240c666..3c3c065 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -7,10 +7,7 @@ template $RemotesPage : Adw.NavigationPage { [top] Adw.HeaderBar header_bar { [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } + $SidebarButton {} [start] ToggleButton search_button { icon-name: "loupe-large-symbolic"; diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 52aa50f..0f0e8d7 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -71,7 +71,6 @@ class RemotesPage(Adw.NavigationPage): __gtype_name__ = 'RemotesPage' gtc = Gtk.Template.Child - sidebar_button = gtc() search_button = gtc() search_bar = gtc() search_entry = gtc() @@ -261,15 +260,12 @@ class RemotesPage(Adw.NavigationPage): # Extra Object Creation self.__class__.instance = self self.main_window = main_window - ms = main_window.main_split self.search_bar.set_key_capture_widget(main_window) self.current_remote_rows = [] self.filter_setting = Gio.Settings.new("io.github.flattool.Warehouse.filter") self.total_disabled = 0 # Connections - ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) - self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) 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.search_entry.connect("search-changed", self.on_search) diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 9d0ca73..6104e04 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -35,10 +35,7 @@ template $SnapshotPage : Adw.BreakpointBin { [top] Adw.HeaderBar header_bar { [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } + $SidebarButton {} [start] ToggleButton search_button { icon-name: "loupe-large-symbolic"; diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index b1ddfbe..a447b91 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -3,6 +3,7 @@ from .host_info import HostInfo from .error_toast import ErrorToast from .app_row import AppRow from .snapshots_list_page import SnapshotsListPage +from .sidebar_button import SidebarButton import os, subprocess class LeftoverSnapshotRow(Adw.ActionRow): @@ -29,7 +30,6 @@ class SnapshotPage(Adw.BreakpointBin): __gtype_name__ = "SnapshotPage" gtc = Gtk.Template.Child - sidebar_button = gtc() toast_overlay = gtc() active_box = gtc() active_listbox = gtc() @@ -163,7 +163,6 @@ class SnapshotPage(Adw.BreakpointBin): super().__init__(**kwargs) # Extra Object Creation - ms = main_window.main_split self.__class__.instance = self self.main_window = main_window self.active_snapshot_paks = [] @@ -173,11 +172,8 @@ class SnapshotPage(Adw.BreakpointBin): self.list_page = SnapshotsListPage(self) # Connections - ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) - self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.active_listbox.connect("row-activated", self.active_select_handler) self.leftover_listbox.connect("row-activated", self.leftover_select_handler) # Apply - self.sidebar_button.set_active(ms.get_show_sidebar()) self.split_view.set_content(self.list_page)## \ No newline at end of file diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 512e52c..871a48a 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -28,10 +28,7 @@ template $UserDataPage : Adw.BreakpointBin { } ; [start] - ToggleButton sidebar_button { - icon-name: "dock-left-symbolic"; - tooltip-text: _("Show Sidebar"); - } + $SidebarButton {} [start] ToggleButton search_button { icon-name: "system-search-symbolic"; diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 96e3422..80c94f4 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -3,6 +3,7 @@ from .error_toast import ErrorToast from .data_box import DataBox from .data_subpage import DataSubpage from .host_info import HostInfo +from .sidebar_button import SidebarButton import os, subprocess import time @@ -14,7 +15,6 @@ class UserDataPage(Adw.BreakpointBin): bpt = gtc() header_bar = gtc() switcher_bar = gtc() - sidebar_button = gtc() search_button = gtc() select_button = gtc() sort_button = gtc() @@ -200,12 +200,8 @@ class UserDataPage(Adw.BreakpointBin): title=_("Leftover Data"), icon_name="folder-templates-symbolic", ) - self.sidebar_button.set_active(ms.get_show_sidebar()) # Connections - # self.sidebar_button.connect("clicked", lambda *_, ms=main_window.main_split: ms.set_show_sidebar(not ms.get_show_sidebar() if not ms.get_collapsed() else True)) - ms.connect("notify::show-sidebar", lambda *_: self.sidebar_button.set_active(ms.get_show_sidebar())) - self.sidebar_button.connect("toggled", lambda *_: ms.set_show_sidebar(self.sidebar_button.get_active())) self.stack.connect("notify::visible-child", self.view_change_handler) self.select_button.connect("toggled", self.select_toggle_handler) From f49229ca17ddc0223a69cc17179687298beae6b6 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 11:25:07 -0400 Subject: [PATCH 139/332] save and load sort mode --- src/user_data_page/data_subpage.py | 6 +++ src/user_data_page/user_data_page.blp | 2 - src/user_data_page/user_data_page.py | 55 +++++++++++++++------------ 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index b846631..8b8e6e6 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -183,6 +183,11 @@ class DataSubpage(Gtk.Stack): else: GLib.idle_add(lambda *_: self.label_box.set_orientation(Gtk.Orientation.HORIZONTAL)) + def update_sort_mode(self): + self.sort_ascend = self.settings.get_boolean("sort-ascend") + self.sort_mode = self.settings.get_string("sort-mode") + self.flow_box.invalidate_sort() + def __init__(self, title, parent_page, is_active, main_window, **kwargs): super().__init__(**kwargs) @@ -206,6 +211,7 @@ class DataSubpage(Gtk.Stack): self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width self.is_result = False self.prev_status = None + self.settings = Gio.Settings.new("io.github.flattool.Warehouse.data_page") # Apply self.flow_box.set_sort_func(self.sort_func) diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 871a48a..89c06b9 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -119,7 +119,6 @@ Popover sort_pop { homogeneous: true; spacing: 3; ToggleButton sort_ascend { - active: true; styles ["flat"] Adw.ButtonContent { icon-name: "view-sort-ascending-symbolic"; @@ -141,7 +140,6 @@ Popover sort_pop { homogeneous: true; spacing: 3; ToggleButton sort_name { - active: true; styles ["flat"] Adw.ButtonContent { icon-name: "font-x-generic-symbolic"; diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 80c94f4..26c119e 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -74,25 +74,22 @@ class UserDataPage(Adw.BreakpointBin): Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) - def sorter(self, button=None): - if button and not button.get_active(): - return + def sort_button_handler(self, button): + if button in {self.sort_ascend, self.sort_descend}: + self.settings.set_boolean("sort-ascend", self.sort_ascend.get_active()) + else: + self.settings.set_string("sort-mode", self.buttons_to_sort_modes[button]) - if self.sort_name.get_active(): - self.adp.sort_mode = "name" - self.ldp.sort_mode = "name" - elif self.sort_id.get_active(): - self.adp.sort_mode = "id" - self.ldp.sort_mode = "id" - elif self.sort_size.get_active(): - self.adp.sort_mode = "size" - self.ldp.sort_mode = "size" - - self.adp.sort_ascend = self.sort_ascend.get_active() - self.ldp.sort_ascend = self.sort_ascend.get_active() + self.adp.update_sort_mode() + self.ldp.update_sort_mode() - self.adp.flow_box.invalidate_sort() - self.ldp.flow_box.invalidate_sort() + def load_sort_settings(self): + mode = self.settings.get_string("sort-mode") + ascend = self.settings.get_boolean("sort-ascend") + self.sort_modes_to_buttons[mode].set_active(True) + (self.sort_ascend if ascend else self.sort_descend).set_active(True) + self.adp.update_sort_mode() + self.ldp.update_sort_mode() def view_change_handler(self, *args): child = self.stack.get_visible_child() @@ -178,14 +175,22 @@ class UserDataPage(Adw.BreakpointBin): # Extra Object Creation self.__class__.instance = self - # self.adj = self.scrolled_window.get_vadjustment() self.adp = DataSubpage(_("Active Data"), self, True, main_window) self.ldp = DataSubpage(_("Leftover Data"), self, False, main_window) self.data_flatpaks = [] self.active_data = [] self.leftover_data = [] self.total_items = 0 - ms=main_window.main_split + 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 = {} + for key, button in self.sort_modes_to_buttons.items(): + self.buttons_to_sort_modes[button] = key # Apply self.stack.add_titled_with_icon( @@ -210,12 +215,12 @@ class UserDataPage(Adw.BreakpointBin): self.copy_button.connect("clicked", self.copy_handler) self.trash_button.connect("clicked", self.trash_handler) - self.sort_ascend.connect("clicked", self.sorter) - self.sort_descend.connect("clicked", self.sorter) - self.sort_name.connect("clicked", self.sorter) - self.sort_id.connect("clicked", self.sorter) - self.sort_size.connect("clicked", self.sorter) + self.sort_ascend.connect("clicked", self.sort_button_handler) + self.sort_descend.connect("clicked", self.sort_button_handler) + self.sort_name.connect("clicked", self.sort_button_handler) + self.sort_id.connect("clicked", self.sort_button_handler) + self.sort_size.connect("clicked", self.sort_button_handler) # Apply again self.search_bar.set_key_capture_widget(main_window) - self.sorter() \ No newline at end of file + self.load_sort_settings() From 330391b804925d6be8866b720082b9a4d4844bd0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 12:15:15 -0400 Subject: [PATCH 140/332] select first visible row if current row becomes invisible --- src/packages_page/packages_page.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index d2f89bd..40068e0 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -106,6 +106,22 @@ class PackagesPage(Adw.BreakpointBin): 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 + while row := self.packages_list_box.get_row_at_index(i): + i += 1 + if row.get_visible(): + 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(): @@ -158,17 +174,7 @@ class PackagesPage(Adw.BreakpointBin): self.packages_list_box.append(row) self.apply_filters() - first_visible_row = None - i = 0 - while row := self.packages_list_box.get_row_at_index(i): - i += 1 - if row.get_visible(): - first_visible_row = 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) + self.select_first_visible_row() self.scrolled_window.set_vadjustment(Gtk.Adjustment.new(0,0,0,0,0,0)) # Scroll list to top @@ -177,6 +183,7 @@ class PackagesPage(Adw.BreakpointBin): 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() @@ -303,6 +310,7 @@ class PackagesPage(Adw.BreakpointBin): self.is_result = False self.prev_status = None self.selected_rows = [] + self.current_row_for_properties = None # Apply # self.set_status("loading_packages") From f507f048b5ca77bae40ae2380a13696f3eb7b78e Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 12:15:28 -0400 Subject: [PATCH 141/332] save and load last opened page --- src/main_window/window.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main_window/window.py b/src/main_window/window.py index ca7557d..bc5497e 100644 --- a/src/main_window/window.py +++ b/src/main_window/window.py @@ -93,6 +93,19 @@ class WarehouseWindow(Adw.ApplicationWindow): self.settings.set_boolean("sidebar-shown", state) print(self.settings.get_boolean("sidebar-shown")) + def show_saved_page(self): + page_to_show = self.settings.get_string("page-shown") + page_found = False + for row, page in self.pages.items(): + self.stack.add_child(page) + + if page.page_name == page_to_show: + page_found = True + self.activate_row(row) + + if not page_found: + self.navigation_row_listbox.get_row_at_index(0).activate() + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -111,13 +124,7 @@ class WarehouseWindow(Adw.ApplicationWindow): } self.navigation_row_listbox.connect("row-activated", self.navigation_handler) - - page_to_show = self.settings.get_string("page-shown") - print(page_to_show) - for row, page in self.pages.items(): - self.stack.add_child(page) - if page_to_show == page.page_name: - self.activate_row(row) + self.show_saved_page() # Apply self.settings.bind("window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT) From 98b624c6fcc8f2c9325f1fc27792638f699e058d Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 22:16:11 -0400 Subject: [PATCH 142/332] Optionally trash data on uninstall --- src/gtk/sidebar_button.py | 1 + src/meson.build | 4 +++- src/packages_page/packages_page.py | 31 ++++++++++++------------- src/packages_page/uninstall_dialog.blp | 32 ++++++++++++++++++++++++++ src/packages_page/uninstall_dialog.py | 32 ++++++++++++++++++++++++++ src/properties_page/properties_page.py | 23 +++++++++--------- src/warehouse.gresource.xml | 1 + 7 files changed, 96 insertions(+), 28 deletions(-) create mode 100644 src/packages_page/uninstall_dialog.blp create mode 100644 src/packages_page/uninstall_dialog.py diff --git a/src/gtk/sidebar_button.py b/src/gtk/sidebar_button.py index f808c5a..094bf8a 100644 --- a/src/gtk/sidebar_button.py +++ b/src/gtk/sidebar_button.py @@ -12,6 +12,7 @@ class SidebarButton(Gtk.ToggleButton): # Connections main_split.connect("notify::show-sidebar", lambda *_: self.set_active(main_split.get_show_sidebar())) + # main_split.connect("notify::collapsed", lambda *_: self.set_visible(main_split.get_collapsed())) self.connect("toggled", lambda *_: main_split.set_show_sidebar(self.get_active())) # Apply diff --git a/src/meson.build b/src/meson.build index 063d9bc..3233df2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,6 +9,7 @@ blueprints = custom_target('blueprints', 'main_window/window.blp', 'packages_page/packages_page.blp', 'packages_page/filters_page.blp', + 'packages_page/uninstall_dialog.blp', 'properties_page/properties_page.blp', 'user_data_page/data_box.blp', 'user_data_page/user_data_page.blp', @@ -67,10 +68,11 @@ warehouse_sources = [ '__init__.py', 'main.py', 'host_info.py', - 'packages_page/app_row.py', 'gtk/error_toast.py', 'gtk/sidebar_button.py', 'main_window/window.py', + 'packages_page/app_row.py', + 'packages_page/uninstall_dialog.py', 'packages_page/packages_page.py', 'packages_page/filters_page.py', 'properties_page/properties_page.py', diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 40068e0..ea33650 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -5,7 +5,8 @@ from .error_toast import ErrorToast from .properties_page import PropertiesPage from .filters_page import FiltersPage from .sidebar_button import SidebarButton -import subprocess +from .uninstall_dialog import UninstallDialog +import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") class PackagesPage(Adw.BreakpointBin): @@ -150,6 +151,7 @@ class PackagesPage(Adw.BreakpointBin): def generate_list(self, *args): self.packages_list_box.remove_all() + self.selected_rows.clear() GLib.idle_add(lambda *_: self.filters_page.generate_filters()) self.copy_button.set_sensitive(False) self.uninstall_button.set_sensitive(False) @@ -226,15 +228,21 @@ class PackagesPage(Adw.BreakpointBin): self.packages_toast_overlay.add_toast(ErrorToast(_("Could not copy {}").format(feedback), str(e)).toast) def selection_uninstall(self, *args): - def on_response(alert_dialog, response): - if response != "continue": - return - + def on_response(should_trash): GLib.idle_add(lambda *_: self.set_status(self.uninstalling)) error = [None] def thread(*args): + cmd = ['flatpak-spawn', '--host', 'flatpak', 'uninstall', '-y'] + to_trash = [] + for row in self.selected_rows: + 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: + subprocess.run(['gio', 'trash'] + to_trash, check=True, capture_output=True) except subprocess.CalledProcessError as cpe: error[0] = cpe except Exception as e: @@ -249,21 +257,12 @@ class PackagesPage(Adw.BreakpointBin): GLib.idle_add(lambda *__: self.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled Packages")))) Gio.Task.new(None, None, callback).run_in_thread(thread) - - cmd = ['flatpak-spawn', '--host', 'flatpak', 'uninstall', '-y'] - for row in self.selected_rows: - cmd.append(row.package.info["ref"]) - - dialog = Adw.AlertDialog(heading=_("Uninstall Packages?"), body=_("It will not be possible to use these packages after removal")) - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Uninstall")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_response) + + dialog = UninstallDialog(on_response) dialog.present(self.main_window) def start_loading(self): self.packages_navpage.set_title(_("Packages")) - self.selected_rows.clear() self.select_button.set_active(False) self.set_status(self.loading_packages) diff --git a/src/packages_page/uninstall_dialog.blp b/src/packages_page/uninstall_dialog.blp new file mode 100644 index 0000000..068e9a3 --- /dev/null +++ b/src/packages_page/uninstall_dialog.blp @@ -0,0 +1,32 @@ +using Gtk 4.0; +using Adw 1; + +template $UninstallDialog : Adw.AlertDialog { + // responses [ + // cancel: _("Cancel"), + // continue: _("Uninstall") destructive, + // ] + + extra-child: + Adw.PreferencesGroup group { + Adw.ActionRow { + title: _("Keep"); + subtitle: _("Allows restoring app settings and content"); + activatable-widget: keep; + [prefix] + CheckButton keep { + active: true; + } + } + Adw.ActionRow { + title: _("Trash"); + subtitle: _("Send data to the trash"); + activatable-widget: trash; + [prefix] + CheckButton trash { + group: keep; + } + } + } + ; +} \ No newline at end of file diff --git a/src/packages_page/uninstall_dialog.py b/src/packages_page/uninstall_dialog.py new file mode 100644 index 0000000..fb1f080 --- /dev/null +++ b/src/packages_page/uninstall_dialog.py @@ -0,0 +1,32 @@ +from gi.repository import Adw, Gtk, GLib, Gio, Pango + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/uninstall_dialog.ui") +class UninstallDialog(Adw.AlertDialog): + __gtype_name__ = "UninstallDialog" + gtc = Gtk.Template.Child + + group = gtc() + trash = gtc() + + def on_response(self, dialog, response): + if response != "continue": + return + + self.continue_callback(self.trash.get_active()) + + def __init__(self, continue_callback, 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"))) \ No newline at end of file diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index afb89d2..35e4fbc 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -2,6 +2,7 @@ from gi.repository import Adw, Gtk,GLib#, Gio, Pango from .error_toast import ErrorToast from .host_info import HostInfo from .change_version_page import ChangeVersionPage +from .uninstall_dialog import UninstallDialog import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") @@ -204,11 +205,18 @@ class PropertiesPage(Adw.NavigationPage): self.package.set_pin(state, callback) def uninstall_handler(self, *args): - def on_choice(_, response): - if response != 'continue': - return + def on_choice(should_trash): self.packages_page.set_status(self.packages_page.uninstalling) self.package.uninstall(callback) + if should_trash: + try: + self.package.trash_data() + self.set_properties(self.package, refresh=True) + self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) + except subprocess.CalledProcessError as cpe: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), cpe.stderr).toast) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e)).toast) def callback(*args): if fail := self.package.failed_uninstall: @@ -220,14 +228,7 @@ class PropertiesPage(Adw.NavigationPage): self.packages_page.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled {}").format(self.package.info["name"]))) # name = self.package.info["name"] - dialog = Adw.AlertDialog( - heading=_("Uninstall {}?").format(name := self.package.info["name"]), - body=_("It will not be possible to use {} after removal.").format(name), - ) - dialog.add_response('cancel', _("Cancel")) - dialog.add_response('continue', _("Uninstall")) - dialog.connect("response", on_choice) - dialog.set_response_appearance('continue', Adw.ResponseAppearance.DESTRUCTIVE) + dialog = UninstallDialog(on_choice) dialog.present(self.main_window) def runtime_row_handler(self, *args): diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 842029b..c51b058 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -7,6 +7,7 @@ main_window/window.ui packages_page/packages_page.ui packages_page/filters_page.ui + packages_page/uninstall_dialog.ui properties_page/properties_page.ui change_version_page/change_version_page.ui user_data_page/data_box.ui From 3c4ff6b72d0cb5af2aa900e7eafb37fb0bd4c330 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 23:49:13 -0400 Subject: [PATCH 143/332] Fix padding --- src/install_page/pending_page.blp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/install_page/pending_page.blp b/src/install_page/pending_page.blp index ef98bb4..f0cf81f 100644 --- a/src/install_page/pending_page.blp +++ b/src/install_page/pending_page.blp @@ -21,6 +21,8 @@ template $PendingPage : Adw.NavigationPage { revealed: true; [center] Button install_button { + margin-top: 3; + margin-bottom: 3; sensitive: bind pending_action_bar.revealed; styles ["pill", "suggested-action"] Adw.ButtonContent { From 269a87cb48b3bd88cf232024d275e6f3800d407f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 23:54:00 -0400 Subject: [PATCH 144/332] Hide sidebar button when window is wide enough --- src/gtk/sidebar_button.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gtk/sidebar_button.py b/src/gtk/sidebar_button.py index 094bf8a..044a356 100644 --- a/src/gtk/sidebar_button.py +++ b/src/gtk/sidebar_button.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, Gdk, GLib from .host_info import HostInfo -class SidebarButton(Gtk.ToggleButton): +class SidebarButton(Gtk.Button): __gtype_name__ = "SidebarButton" def __init__(self, **kwargs): @@ -11,9 +11,8 @@ class SidebarButton(Gtk.ToggleButton): main_split = HostInfo.main_window.main_split # Connections - main_split.connect("notify::show-sidebar", lambda *_: self.set_active(main_split.get_show_sidebar())) - # main_split.connect("notify::collapsed", lambda *_: self.set_visible(main_split.get_collapsed())) - self.connect("toggled", lambda *_: main_split.set_show_sidebar(self.get_active())) + main_split.connect("notify::collapsed", lambda *_: self.set_visible(main_split.get_collapsed())) + self.connect("clicked", lambda *_: main_split.set_show_sidebar(True)) # Apply self.set_icon_name("dock-left-symbolic") From 69039bc2f59aa680d3e8cf0d55c9aa9d50809233 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 26 Aug 2024 23:58:56 -0400 Subject: [PATCH 145/332] Do not show trash option for uninstall if no data is present (single package only) --- src/packages_page/packages_page.py | 2 +- src/packages_page/uninstall_dialog.blp | 5 ----- src/packages_page/uninstall_dialog.py | 7 ++++--- src/properties_page/properties_page.py | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index ea33650..f042218 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -258,7 +258,7 @@ class PackagesPage(Adw.BreakpointBin): Gio.Task.new(None, None, callback).run_in_thread(thread) - dialog = UninstallDialog(on_response) + dialog = UninstallDialog(on_response, True) dialog.present(self.main_window) def start_loading(self): diff --git a/src/packages_page/uninstall_dialog.blp b/src/packages_page/uninstall_dialog.blp index 068e9a3..5ccc0ce 100644 --- a/src/packages_page/uninstall_dialog.blp +++ b/src/packages_page/uninstall_dialog.blp @@ -2,11 +2,6 @@ using Gtk 4.0; using Adw 1; template $UninstallDialog : Adw.AlertDialog { - // responses [ - // cancel: _("Cancel"), - // continue: _("Uninstall") destructive, - // ] - extra-child: Adw.PreferencesGroup group { Adw.ActionRow { diff --git a/src/packages_page/uninstall_dialog.py b/src/packages_page/uninstall_dialog.py index fb1f080..7b4b2ac 100644 --- a/src/packages_page/uninstall_dialog.py +++ b/src/packages_page/uninstall_dialog.py @@ -14,11 +14,11 @@ class UninstallDialog(Adw.AlertDialog): self.continue_callback(self.trash.get_active()) - def __init__(self, continue_callback, package_name=None, **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_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?"))) @@ -29,4 +29,5 @@ class UninstallDialog(Adw.AlertDialog): 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"))) \ No newline at end of file + self.group.set_title(GLib.markup_escape_text(_("App Settings & Content"))) + self.group.set_visible(show_trash_option) \ No newline at end of file diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 35e4fbc..e716863 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -228,7 +228,7 @@ class PropertiesPage(Adw.NavigationPage): self.packages_page.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled {}").format(self.package.info["name"]))) # name = self.package.info["name"] - dialog = UninstallDialog(on_choice) + dialog = UninstallDialog(on_choice, os.path.exists(self.package.data_path), self.package.info["name"]) dialog.present(self.main_window) def runtime_row_handler(self, *args): From f2d2737d3310e033a896e465a7924c05459af80d Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 27 Aug 2024 00:48:24 -0400 Subject: [PATCH 146/332] More work on install page --- src/install_page/pending_page.py | 15 +++++++++++++++ src/install_page/select_page.blp | 4 ---- src/install_page/select_page.py | 4 ---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/install_page/pending_page.py b/src/install_page/pending_page.py index 8d93ecb..9bd74bf 100644 --- a/src/install_page/pending_page.py +++ b/src/install_page/pending_page.py @@ -15,6 +15,10 @@ class AddedGroup(Adw.PreferencesGroup): self.rows.remove(row) self.remove(row) + def remove_all(self, button): + while len(self.rows) > 0 and (row := self.rows[0]): + row.activate() + def __init__(self, remote, installation, **kwargs): super().__init__(**kwargs) @@ -25,6 +29,17 @@ class AddedGroup(Adw.PreferencesGroup): self.set_title(f"{remote.title}") self.set_description(_("Installation: {}").format(installation)) + remove_all = Gtk.Button( + child=Adw.ButtonContent( + icon_name="list-remove-all-symbolic", + label=_("Remove All"), + ), + valign = Gtk.Align.CENTER, + ) + remove_all.add_css_class("flat") + remove_all.connect("clicked", self.remove_all) + self.set_header_suffix(remove_all) + @Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/pending_page.ui") class PendingPage(Adw.NavigationPage): __gtype_name__ = "PendingPage" diff --git a/src/install_page/select_page.blp b/src/install_page/select_page.blp index 08f0557..d1f5d50 100644 --- a/src/install_page/select_page.blp +++ b/src/install_page/select_page.blp @@ -13,10 +13,6 @@ template $SelectPage : Adw.NavigationPage { Adw.HeaderBar { [start] $SidebarButton {} - [start] - Button test { - label: "test"; - } } Adw.PreferencesPage { Adw.PreferencesGroup remotes_group { diff --git a/src/install_page/select_page.py b/src/install_page/select_page.py index 6d89f73..d60d38a 100644 --- a/src/install_page/select_page.py +++ b/src/install_page/select_page.py @@ -14,8 +14,6 @@ class SelectPage(Adw.NavigationPage): remotes_group = gtc() add_remote_row = gtc() - test = gtc() - def start_loading(self): self.nav_view.pop() for row in self.remote_rows: @@ -46,6 +44,4 @@ class SelectPage(Adw.NavigationPage): self.nav_view.connect("popped", self.results_page.on_back) self.remote_rows = [] - self.test.connect("clicked", lambda *_: self.nav_view.push(self.results_page)) - # Apply From 47c416f42140573dc349a12f03eb91e055c5e72c Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 27 Aug 2024 00:53:43 -0400 Subject: [PATCH 147/332] Fix error for when current version cannot be determined --- src/change_version_page/change_version_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py index 40956d6..7b99899 100644 --- a/src/change_version_page/change_version_page.py +++ b/src/change_version_page/change_version_page.py @@ -61,7 +61,7 @@ class ChangeVersionPage(Adw.NavigationPage): def idle(*args): for index, commit in enumerate(commits): row = Adw.ActionRow(title=GLib.markup_escape_text(changes[index]), subtitle=f"{GLib.markup_escape_text(commit)}\n{GLib.markup_escape_text(dates[index])}") - if commit == self.package.cli_info["commit"]: + if commit == self.package.cli_info.get("commit", None): row.set_sensitive(False) row.add_prefix(Gtk.Image(icon_name="check-plain-symbolic", margin_start=5, margin_end=5)) row.set_tooltip_text(_("Currently Installed Version")) From bbbcc6633643124255c779086fcfbd17b8f87ec9 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 27 Aug 2024 15:13:25 -0400 Subject: [PATCH 148/332] Add rclick and long press actions to data boxes --- src/user_data_page/data_box.py | 2 +- src/user_data_page/data_subpage.py | 50 ++++++++++++++++++---------- src/user_data_page/user_data_page.py | 4 +-- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index ce052c2..667e744 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -88,7 +88,7 @@ class DataBox(Gtk.ListBox): Gio.Task.new(None, None, callback).run_in_thread(thread) - dialog = Adw.AlertDialog(heading=_("Trash {}?").format(self.title), body=_("{}'s data will be sent to the trash").format(self.title)) + dialog = Adw.AlertDialog(heading=_("Trash {}'s Data?").format(self.title), body=_("{}'s data will be sent to the trash").format(self.title)) dialog.add_response("cancel", _("Cancel")) dialog.add_response("continue", _("Continue")) dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 8b8e6e6..b3ce5ba 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -75,44 +75,51 @@ class DataSubpage(Gtk.Stack): self.set_visible_child(self.no_data) def set_selection_mode(self, is_enabled): - self.selected_boxes.clear() idx = 0 while box := self.flow_box.get_child_at_index(idx): idx += 1 box = box.get_child() if not is_enabled: - # continue GLib.idle_add(lambda *_, box=box: box.check_button.set_active(False)) - GLib.idle_add(lambda *_, box=box: box.check_button.set_visible(is_enabled)) - # if not is_enabled: - # return - def box_select_handler(self, _, box): - box = box.get_child() - if not box.check_button.get_visible(): - return + GLib.idle_add(lambda *_, box=box: box.check_button.set_visible(is_enabled)) + + self.selected_boxes.clear() + + def box_select_handler(self, box): cb = box.check_button if cb.get_active(): - GLib.idle_add(lambda *_: cb.set_active(False)) - self.selected_boxes.remove(box) - else: - GLib.idle_add(lambda *_: cb.set_active(True)) self.selected_boxes.append(box) + else: + try: + self.selected_boxes.remove(box) + except ValueError: + pass total = len(self.selected_boxes) self.parent_page.copy_button.set_sensitive(total) self.parent_page.trash_button.set_sensitive(total) + def box_interact_handler(self, flow_box, box): + box = box.get_child() + cb = box.check_button + if cb.get_visible(): + cb.set_active(not cb.get_active()) + def select_all_handler(self, *args): idx = 0 while box := self.flow_box.get_child_at_index(idx): idx += 1 if not box.get_child().check_button.get_active(): - self.box_select_handler(None, box) + self.box_select_handler(box.get_child()) + + def box_rclick_handler(self, box): + self.parent_page.select_button.set_active(True) + box.check_button.set_active(not box.check_button.get_active()) def generate_list(self, flatpaks, data): + self.flow_box.remove_all() self.boxes.clear() - self.selected_boxes.clear() self.ready_to_sort_size = False self.finished_boxes = 0 self.total_size = 0 @@ -129,14 +136,15 @@ class DataSubpage(Gtk.Stack): if flatpaks: for i, pak in enumerate(flatpaks): box = DataBox(self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, self.trash_handler) + box.check_button.connect("toggled", lambda *_, box=box: self.box_select_handler(box)) self.boxes.append(box) self.flow_box.append(box) + else: for i, folder in enumerate(data): box = DataBox(self.parent_page.toast_overlay, folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback, self.trash_handler) + box.check_button.connect("toggled", lambda *_, box=box: self.box_select_handler(box)) self.flow_box.append(box) - child = self.flow_box.get_child_at_index(i) - child.set_focusable(False) idx = 0 while box := self.flow_box.get_child_at_index(idx): @@ -145,6 +153,12 @@ class DataSubpage(Gtk.Stack): child = box.get_child() child.set_focusable(False) child.row.set_focusable(child.check_button.get_visible()) + rclick = Gtk.GestureClick(button=3) + rclick.connect("released", lambda *_, child=child: self.box_rclick_handler(child)) + box.add_controller(rclick) + long_press = Gtk.GestureLongPress() + long_press.connect("pressed", lambda *_, child=child: self.box_rclick_handler(child)) + box.add_controller(long_press) if idx == 0: self.set_visible_child(self.no_data) @@ -228,7 +242,7 @@ class DataSubpage(Gtk.Stack): # Connections parent_page.search_entry.connect("search-changed", self.on_invalidate) - self.flow_box.connect("child-activated", self.box_select_handler) + self.flow_box.connect("child-activated", self.box_interact_handler) # self.title.get_preferred_size()[1].width + self.subtitle.get_preferred_size()[1].width self.scrolled_window.get_hadjustment().connect("changed", self.label_orientation_handler) \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 26c119e..dc9bae9 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -61,11 +61,11 @@ class UserDataPage(Adw.BreakpointBin): self.adp.set_visible_child(self.adp.loading_data) self.adp.size_label.set_label("Loading Size") self.adp.spinner.set_visible(True) - self.adp.flow_box.remove_all() + # self.adp.flow_box.remove_all() self.ldp.set_visible_child(self.ldp.loading_data) self.ldp.size_label.set_label("Loading Size") self.ldp.spinner.set_visible(True) - self.ldp.flow_box.remove_all() + # self.ldp.flow_box.remove_all() def end_loading(self, *args): def callback(*args): From 0f111decc9daa0befe74549bbf05f0e18fa33eb0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 27 Aug 2024 15:17:20 -0400 Subject: [PATCH 149/332] Ask for confirmation when batch trashing --- src/user_data_page/user_data_page.py | 35 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index dc9bae9..359ce77 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -155,18 +155,29 @@ class UserDataPage(Adw.BreakpointBin): else: self.toast_overlay.add_toast(Adw.Toast(title=_("Trashed data"))) - child = self.stack.get_visible_child() - to_trash = [] - for box in child.selected_boxes: - to_trash.append(box.data_path) - - if len(to_trash) == 0: - self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), _("No boxes were selected")).toast) - return - - self.select_button.set_active(False) - child.set_visible_child(child.loading_data) - Gio.Task.new(None, None, callback).run_in_thread(lambda *_: thread(to_trash)) + def on_response(dialog, response): + if response != "continue": + return + + child = self.stack.get_visible_child() + to_trash = [] + for box in child.selected_boxes: + to_trash.append(box.data_path) + + if len(to_trash) == 0: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), _("No boxes were selected")).toast) + return + + self.select_button.set_active(False) + child.set_visible_child(child.loading_data) + Gio.Task.new(None, None, callback).run_in_thread(lambda *_: thread(to_trash)) + + dialog = Adw.AlertDialog(heading=_("Trash Data?"), body=_("Data will be sent to the trash")) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Continue")) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.connect("response", on_response) + dialog.present(ErrorToast.main_window) # self.toast_overlay.add_toast(Adw.Toast(title=_("Trashed data"))) From 619b8225f7fc48982c6ec0bea01a0bfa0744c069 Mon Sep 17 00:00:00 2001 From: heliguy4599 Date: Wed, 28 Aug 2024 11:26:48 -0400 Subject: [PATCH 150/332] Add right click and long press actions to data boxes --- src/user_data_page/data_subpage.blp | 8 +------ src/user_data_page/data_subpage.py | 34 ++++++++-------------------- src/user_data_page/user_data_page.py | 9 ++++---- 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp index adc8edc..b3be303 100644 --- a/src/user_data_page/data_subpage.blp +++ b/src/user_data_page/data_subpage.blp @@ -39,14 +39,8 @@ template $DataSubpage : Stack { halign: start; wrap: true; } - Image { - icon-name: "dot-symbolic"; - margin-start: 6; - margin-end: 6; - margin-top: 3; - valign: center; - } Label subtitle { + visible: false; label: "No Subtutle Set"; styles ["title-3"] wrap: true; diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index b3ce5ba..9aee758 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -75,6 +75,10 @@ class DataSubpage(Gtk.Stack): self.set_visible_child(self.no_data) def set_selection_mode(self, is_enabled): + if not is_enabled: + self.size_label.set_visible(True) + self.subtitle.set_visible(False) + idx = 0 while box := self.flow_box.get_child_at_index(idx): idx += 1 @@ -97,6 +101,9 @@ class DataSubpage(Gtk.Stack): pass total = len(self.selected_boxes) + self.subtitle.set_visible(not total == 0) + self.size_label.set_visible(total == 0) + self.subtitle.set_label(_("{} Selected").format(total)) self.parent_page.copy_button.set_sensitive(total) self.parent_page.trash_button.set_sensitive(total) @@ -110,8 +117,7 @@ class DataSubpage(Gtk.Stack): idx = 0 while box := self.flow_box.get_child_at_index(idx): idx += 1 - if not box.get_child().check_button.get_active(): - self.box_select_handler(box.get_child()) + box.get_child().check_button.set_active(True) def box_rclick_handler(self, box): self.parent_page.select_button.set_active(True) @@ -124,15 +130,7 @@ class DataSubpage(Gtk.Stack): self.finished_boxes = 0 self.total_size = 0 self.total_items = len(data) - - if self.total_items == 1: - self.subtitle.set_label(_("1 Item")) - self.parent_page.search_entry.set_editable(True) - else: - self.parent_page.search_entry.set_editable(True) - self.subtitle.set_label(_("{} Items").format(self.total_items)) - - self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width + self.parent_page.search_entry.set_editable(True) if flatpaks: for i, pak in enumerate(flatpaks): box = DataBox(self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, self.trash_handler) @@ -189,14 +187,6 @@ class DataSubpage(Gtk.Stack): elif self.total_items == 0: self.set_visible_child(self.no_data) - def label_orientation_handler(self, adj): - current_page_width = adj.get_upper() - 24 - - if self.label_box.get_allocated_width() < self.min_horizontal_label_width: - GLib.idle_add(lambda *_: self.label_box.set_orientation(Gtk.Orientation.VERTICAL)) - else: - GLib.idle_add(lambda *_: self.label_box.set_orientation(Gtk.Orientation.HORIZONTAL)) - def update_sort_mode(self): self.sort_ascend = self.settings.get_boolean("sort-ascend") self.sort_mode = self.settings.get_string("sort-mode") @@ -222,7 +212,6 @@ class DataSubpage(Gtk.Stack): self.selected_boxes = [] self.ready_to_sort_size = False self.finished_boxes = 0 - self.min_horizontal_label_width = self.label_box.get_preferred_size()[1].width self.is_result = False self.prev_status = None self.settings = Gio.Settings.new("io.github.flattool.Warehouse.data_page") @@ -242,7 +231,4 @@ class DataSubpage(Gtk.Stack): # Connections parent_page.search_entry.connect("search-changed", self.on_invalidate) - self.flow_box.connect("child-activated", self.box_interact_handler) - - # self.title.get_preferred_size()[1].width + self.subtitle.get_preferred_size()[1].width - self.scrolled_window.get_hadjustment().connect("changed", self.label_orientation_handler) \ No newline at end of file + self.flow_box.connect("child-activated", self.box_interact_handler) \ No newline at end of file diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index 359ce77..c07c4bb 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -179,7 +179,9 @@ class UserDataPage(Adw.BreakpointBin): dialog.connect("response", on_response) dialog.present(ErrorToast.main_window) - # self.toast_overlay.add_toast(Adw.Toast(title=_("Trashed data"))) + def breakpoint_handler(self, bpt, is_applied): + self.adp.label_box.set_orientation(Gtk.Orientation.VERTICAL if is_applied else Gtk.Orientation.HORIZONTAL) + self.ldp.label_box.set_orientation(Gtk.Orientation.VERTICAL if is_applied else Gtk.Orientation.HORIZONTAL) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -219,18 +221,17 @@ class UserDataPage(Adw.BreakpointBin): # Connections self.stack.connect("notify::visible-child", self.view_change_handler) - self.select_button.connect("toggled", self.select_toggle_handler) - self.select_all_button.connect("clicked", self.select_all_handler) self.copy_button.connect("clicked", self.copy_handler) self.trash_button.connect("clicked", self.trash_handler) - self.sort_ascend.connect("clicked", self.sort_button_handler) self.sort_descend.connect("clicked", self.sort_button_handler) self.sort_name.connect("clicked", self.sort_button_handler) self.sort_id.connect("clicked", self.sort_button_handler) self.sort_size.connect("clicked", self.sort_button_handler) + self.bpt.connect("apply", self.breakpoint_handler, True) + self.bpt.connect("unapply", self.breakpoint_handler, False) # Apply again self.search_bar.set_key_capture_widget(main_window) From 1594207fdbf1922fc20b353bffb9f6f1334a55fb Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 1 Sep 2024 00:04:36 -0400 Subject: [PATCH 151/332] Begin changing to proper loading pages --- src/gtk/loading_status.blp | 36 +++++++++++++++++++++++++++ src/gtk/loading_status.py | 21 ++++++++++++++++ src/meson.build | 2 ++ src/packages_page/packages_page.blp | 38 ++++++++++++++--------------- src/packages_page/packages_page.py | 11 ++++++--- src/warehouse.gresource.xml | 1 + 6 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 src/gtk/loading_status.blp create mode 100644 src/gtk/loading_status.py diff --git a/src/gtk/loading_status.blp b/src/gtk/loading_status.blp new file mode 100644 index 0000000..4afc463 --- /dev/null +++ b/src/gtk/loading_status.blp @@ -0,0 +1,36 @@ +using Gtk 4.0; +using Adw 1; + +template $LoadingStatus : ScrolledWindow { + Box { + orientation: vertical; + valign: center; + halign: fill; + spacing: 12; + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + Spinner { + height-request: 30; + spinning: true; + } + Label title_label { + label: "No Title Set"; + wrap: true; + justify: center; + styles ["title-1"] + } + Label description_label { + label: "No Description Set"; + wrap: true; + justify: center; + styles ["description", "body"] + } + Button button { + label: "No Label Set"; + styles ["pill"] + halign: center; + } + } +} diff --git a/src/gtk/loading_status.py b/src/gtk/loading_status.py new file mode 100644 index 0000000..96ec0c1 --- /dev/null +++ b/src/gtk/loading_status.py @@ -0,0 +1,21 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/gtk/loading_status.ui") +class LoadingStatus(Gtk.ScrolledWindow): + __gtype_name__ = 'LoadingStatus' + gtc = Gtk.Template.Child + + title_label = gtc() + description_label = gtc() + button = gtc() + + def __init__(self, title, description, on_cancel=None, **kwargs): + super().__init__(**kwargs) + + self.title_label.set_label(GLib.markup_escape_text(title)) + self.description_label.set_label(GLib.markup_escape_text(description)) + if on_cancel is None: + self.button.set_visible(False) + else: + self.button.connect("clicked", on_cancel) diff --git a/src/meson.build b/src/meson.build index 3233df2..54915a6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,6 +6,7 @@ blueprints = custom_target('blueprints', input: files( 'packages_page/app_row.blp', 'gtk/help-overlay.blp', + 'gtk/loading_status.blp', 'main_window/window.blp', 'packages_page/packages_page.blp', 'packages_page/filters_page.blp', @@ -70,6 +71,7 @@ warehouse_sources = [ 'host_info.py', 'gtk/error_toast.py', 'gtk/sidebar_button.py', + 'gtk/loading_status.py', 'main_window/window.py', 'packages_page/app_row.py', 'packages_page/uninstall_dialog.py', diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index bcb55cb..3d8e264 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -53,29 +53,29 @@ template $PackagesPage : Adw.BreakpointBin { } } Stack status_stack { - Adw.StatusPage loading_packages { - title: _("Loading Packages"); - description: _("This should only take a moment"); - child: - Spinner { - spinning: true; - } - ; - } + // Adw.StatusPage loading_packages { + // title: _("Loading Packages"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } ScrolledWindow scrolled_window { ListBox packages_list_box { styles ["navigation-sidebar"] } } - Adw.StatusPage uninstalling { - title: _("Uninstalling Packages"); - description: _("This should only take a moment"); - child: - Spinner { - spinning: true; - } - ; - } + // Adw.StatusPage uninstalling { + // title: _("Uninstalling Packages"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } Adw.StatusPage no_filter_results { title: _("No Packages Match Filters"); description: _("No installed package matches all of the currently applied filters"); @@ -165,4 +165,4 @@ Popover copy_pop { halign: start; } } -} \ No newline at end of file +} diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index f042218..e5cf72c 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -6,6 +6,7 @@ from .properties_page import PropertiesPage from .filters_page import FiltersPage from .sidebar_button import SidebarButton from .uninstall_dialog import UninstallDialog +from .loading_status import LoadingStatus import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/packages_page.ui") @@ -16,8 +17,8 @@ class PackagesPage(Adw.BreakpointBin): packages_toast_overlay = gtc() status_stack = gtc() scrolled_window = gtc() - uninstalling = gtc() - loading_packages = gtc() + # uninstalling = gtc() + # loading_packages = gtc() no_filter_results = gtc() reset_filters_button = gtc() no_packages = gtc() @@ -303,6 +304,8 @@ class PackagesPage(Adw.BreakpointBin): # Extra Object Creation self.main_window = main_window + self.loading_packages = LoadingStatus(_("Loading Packages"), _("This should only take a moment")) + self.uninstalling = LoadingStatus(_("Uninstalling Packages"), _("This should only take a moment")) self.properties_page = PropertiesPage(main_window, self) self.filters_page = FiltersPage(main_window, self) self.filter_settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") @@ -313,6 +316,8 @@ class PackagesPage(Adw.BreakpointBin): # Apply # self.set_status("loading_packages") + self.status_stack.add_child(self.loading_packages) + self.status_stack.add_child(self.uninstalling) self.packages_list_box.set_filter_func(self.filter_func) self.packages_list_box.set_sort_func(self.sort_func) self.content_stack.add_child(self.properties_page) @@ -332,4 +337,4 @@ class PackagesPage(Adw.BreakpointBin): self.select_all_button.connect("clicked", self.select_all_handler) self.copy_menu.connect("row-activated", self.selection_copy) - self.uninstall_button.connect("clicked", self.selection_uninstall) \ No newline at end of file + self.uninstall_button.connect("clicked", self.selection_uninstall) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index c51b058..d102e75 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -3,6 +3,7 @@ ../data/style.css gtk/help-overlay.ui + gtk/loading_status.ui packages_page/app_row.ui main_window/window.ui packages_page/packages_page.ui From aeba673cded58379e734e285a09f0d8505b692f0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 1 Sep 2024 00:13:15 -0400 Subject: [PATCH 152/332] Reset the properties page view to the current page on refresh --- src/packages_page/packages_page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index e5cf72c..6d823ac 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -151,6 +151,7 @@ class PackagesPage(Adw.BreakpointBin): 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() self.selected_rows.clear() GLib.idle_add(lambda *_: self.filters_page.generate_filters()) From 2b92514c9f0f5260d920011e338fdbf279f89cff Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 1 Sep 2024 22:56:06 -0400 Subject: [PATCH 153/332] Make the loading pages look good --- .../change_version_page.py | 5 ++-- src/properties_page/properties_page.blp | 8 +++--- src/properties_page/properties_page.py | 5 ++-- src/remotes_page/add_remote_dialog.blp | 20 +++++++------- src/remotes_page/add_remote_dialog.py | 6 +++-- src/remotes_page/remotes_page.blp | 20 +++++++------- src/remotes_page/remotes_page.py | 26 ++++++++++++------- src/snapshot_page/snapshot_page.blp | 20 +++++++------- src/snapshot_page/snapshot_page.py | 6 +++-- src/user_data_page/data_subpage.blp | 18 ++++++------- src/user_data_page/data_subpage.py | 6 +++-- 11 files changed, 77 insertions(+), 63 deletions(-) diff --git a/src/change_version_page/change_version_page.py b/src/change_version_page/change_version_page.py index 7b99899..a9ba690 100644 --- a/src/change_version_page/change_version_page.py +++ b/src/change_version_page/change_version_page.py @@ -1,6 +1,7 @@ from gi.repository import Adw, Gtk,GLib, Gio from .error_toast import ErrorToast from .host_info import HostInfo +from .loading_status import LoadingStatus import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/change_version_page/change_version_page.ui") @@ -103,7 +104,7 @@ class ChangeVersionPage(Adw.NavigationPage): self.set_title(_("{} Versions").format(pkg_name)) self.mask_row.set_subtitle(_("Ensure that {} will never be updated to a newer version").format(pkg_name)) - self.scrolled_window.set_child(Adw.StatusPage(title=_("Fetching Releases"), description=_("This could take a while"))) + self.scrolled_window.set_child(LoadingStatus(_("Fetching Releases"), _("This could take a while"))) Gio.Task.new(None, None, self.get_commits_callback).run_in_thread(self.get_commits) # for i in range(10): @@ -117,4 +118,4 @@ class ChangeVersionPage(Adw.NavigationPage): # Connections self.root_group_check_button.connect("toggled", lambda *_: self.action_bar.set_revealed(True)) - self.apply_button.connect("clicked", self.loader_test) \ No newline at end of file + self.apply_button.connect("clicked", self.loader_test) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 3e20f44..a7cc638 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -9,9 +9,9 @@ template $PropertiesPage : Adw.NavigationPage { Adw.HeaderBar { show-title: false; } - Adw.StatusPage { - title: _("Loading Properties"); - } + // Adw.StatusPage { + // title: _("Loading Properties"); + // } } Adw.ToolbarView error_tbv { [top] @@ -348,4 +348,4 @@ template $PropertiesPage : Adw.NavigationPage { } } } -} \ No newline at end of file +} diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index e716863..aa60c7a 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -3,6 +3,7 @@ from .error_toast import ErrorToast from .host_info import HostInfo from .change_version_page import ChangeVersionPage from .uninstall_dialog import UninstallDialog +from .loading_status import LoadingStatus import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") @@ -279,7 +280,7 @@ class PropertiesPage(Adw.NavigationPage): "subject": self.subject_row, "date": self.date_row, } - + self.loading_tbv.set_content(LoadingStatus(_("Loading Properties"), _("This should only take a moment"))) self.packages_page = packages_page self.__class__.main_window = main_window @@ -297,4 +298,4 @@ class PropertiesPage(Adw.NavigationPage): row = self.info_rows[key] if type(row) != Adw.ActionRow: continue - row.connect("activated", self.copy_handler) \ No newline at end of file + row.connect("activated", self.copy_handler) diff --git a/src/remotes_page/add_remote_dialog.blp b/src/remotes_page/add_remote_dialog.blp index f7f1a9a..7e7942b 100644 --- a/src/remotes_page/add_remote_dialog.blp +++ b/src/remotes_page/add_remote_dialog.blp @@ -41,16 +41,16 @@ template $AddRemoteDialog : Adw.Dialog { } } } - Adw.StatusPage loading_page { - title: _("Adding Remote"); - description: _("This should only take a moment"); - child: - Spinner { - spinning: true; - } - ; - } + // Adw.StatusPage loading_page { + // title: _("Adding Remote"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } } } } -} \ No newline at end of file +} diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index 700d2cc..643fd44 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -1,6 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast +from .loading_status import LoadingStatus import subprocess, re @Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/add_remote_dialog.ui") @@ -17,7 +18,6 @@ class AddRemoteDialog(Adw.Dialog): name_row = gtc() url_row = gtc() installation_row = gtc() - loading_page = gtc() def on_apply(self, *args): self.stack.set_visible_child(self.loading_page) @@ -82,6 +82,7 @@ class AddRemoteDialog(Adw.Dialog): self.string_list = Gtk.StringList(strings=HostInfo.installations) self.main_window = main_window self.parent_page = parent_page + self.loading_page = LoadingStatus(_("Adding Remote"), _("This should only take a moment")) self.rexes = { self.title_row: "^(?=.*[A-Za-z0-9])[A-Za-z0-9._-]+( +[A-Za-z0-9._-]+)*$", #"^(?=.*[A-Za-z0-9])[A-Za-z0-9._-]+( [A-Za-z0-9._-]+)*$", @@ -93,6 +94,7 @@ class AddRemoteDialog(Adw.Dialog): self.url_passes = False # Apply + self.stack.add_child(self.loading_page) self.installation_row.set_model(self.string_list) if remote_info: self.title_row.set_text(remote_info["title"]) @@ -112,4 +114,4 @@ class AddRemoteDialog(Adw.Dialog): self.apply_button.connect("clicked", self.on_apply) self.title_row.connect("changed", self.check_entries) self.name_row.connect("changed", self.check_entries) - self.url_row.connect("changed", self.check_entries) \ No newline at end of file + self.url_row.connect("changed", self.check_entries) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index 3c3c065..dbe50ff 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -27,15 +27,15 @@ template $RemotesPage : Adw.NavigationPage { } Adw.ToastOverlay toast_overlay { Stack stack { - Adw.StatusPage loading_remotes { - title: _("Loading Remotes"); - description: _("This should only take a moment"); - child: - Spinner { - spinning: true; - } - ; - } + // Adw.StatusPage loading_remotes { + // title: _("Loading Remotes"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } Adw.StatusPage no_results { title: _("No Results Found"); description: _("Try a different search"); @@ -151,4 +151,4 @@ template $RemotesPage : Adw.NavigationPage { } } } -} \ No newline at end of file +} diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 0f0e8d7..fc37abc 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -3,6 +3,7 @@ from .host_info import HostInfo from .error_toast import ErrorToast from .remote_row import RemoteRow from .add_remote_dialog import AddRemoteDialog +from .loading_status import LoadingStatus import subprocess class NewRemoteRow(Adw.ActionRow): @@ -85,7 +86,6 @@ class RemotesPage(Adw.NavigationPage): none_visible = gtc() # Statuses - loading_remotes = gtc() no_results = gtc() no_remotes = gtc() content_page = gtc() @@ -97,6 +97,10 @@ class RemotesPage(Adw.NavigationPage): page_name = "remotes" def start_loading(self): + self.search_button.set_active(False) + self.search_button.set_sensitive(False) + self.search_entry.set_text("") + self.search_entry.set_editable(False) self.stack.set_visible_child(self.loading_remotes) self.total_disabled = 0 for row in self.current_remote_rows: @@ -104,15 +108,6 @@ class RemotesPage(Adw.NavigationPage): self.current_remote_rows.clear() - def none_visible_handler(self): - any_visible = False - for row in self.current_remote_rows: - if row.get_visible(): - any_visible = True - break - - self.none_visible.set_visible(not any_visible) - def end_loading(self): show_disabled = self.show_disabled_button.get_active() self.show_disabled_button.set_visible(False) @@ -144,6 +139,15 @@ class RemotesPage(Adw.NavigationPage): self.search_button.set_sensitive(True) self.search_entry.set_editable(True) + def none_visible_handler(self): + any_visible = False + for row in self.current_remote_rows: + if row.get_visible(): + any_visible = True + break + + self.none_visible.set_visible(not any_visible) + def filter_remote(self, row): self.filter_setting.set_boolean("show-apps", True) self.filter_setting.set_boolean("show-runtimes", True) @@ -260,6 +264,7 @@ class RemotesPage(Adw.NavigationPage): # Extra Object Creation self.__class__.instance = self self.main_window = main_window + self.loading_remotes = LoadingStatus(_("Loading Remotes"), _("This should only take a moment")) self.search_bar.set_key_capture_widget(main_window) self.current_remote_rows = [] self.filter_setting = Gio.Settings.new("io.github.flattool.Warehouse.filter") @@ -272,6 +277,7 @@ class RemotesPage(Adw.NavigationPage): self.show_disabled_button.connect("toggled", self.show_disabled_handler) # Appply + self.stack.add_child(self.loading_remotes) for item in self.new_remotes: row = NewRemoteRow(item) row.connect("activated", lambda *_, remote_info=item: AddRemoteDialog(main_window, self, remote_info).present(main_window)) diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 6104e04..297230d 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -53,15 +53,15 @@ template $SnapshotPage : Adw.BreakpointBin { } } Stack stack { - Adw.StatusPage loading_snapshots { - title: _("Loading Snapshot"); - description: _("This should only take a moment"); - child: - Spinner { - spinning: true; - } - ; - } + // Adw.StatusPage loading_snapshots { + // title: _("Loading Snapshot"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } Adw.StatusPage no_snapshots { title: _("No Snapshots Found"); description: _("Warehouse cannot see the list of snapshots or you don't have any snapshots"); @@ -136,4 +136,4 @@ template $SnapshotPage : Adw.BreakpointBin { ; } } -} \ No newline at end of file +} diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index a447b91..e64f9ac 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -4,6 +4,7 @@ from .error_toast import ErrorToast from .app_row import AppRow from .snapshots_list_page import SnapshotsListPage from .sidebar_button import SidebarButton +from .loading_status import LoadingStatus import os, subprocess class LeftoverSnapshotRow(Adw.ActionRow): @@ -37,7 +38,6 @@ class SnapshotPage(Adw.BreakpointBin): leftover_listbox = gtc() split_view = gtc() stack = gtc() - loading_snapshots = gtc() no_snapshots = gtc() no_results = gtc() scrolled_window = gtc() @@ -165,6 +165,7 @@ class SnapshotPage(Adw.BreakpointBin): # Extra Object Creation self.__class__.instance = self self.main_window = main_window + self.loading_snapshots = LoadingStatus(_("Loading Snapshots"), _("This should only take a moment")) self.active_snapshot_paks = [] # self.active_rows = [] self.leftover_snapshots = [] @@ -176,4 +177,5 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_listbox.connect("row-activated", self.leftover_select_handler) # Apply - self.split_view.set_content(self.list_page)## \ No newline at end of file + self.stack.add_child(self.loading_snapshots) + self.split_view.set_content(self.list_page)## diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp index b3be303..da892aa 100644 --- a/src/user_data_page/data_subpage.blp +++ b/src/user_data_page/data_subpage.blp @@ -2,15 +2,15 @@ using Gtk 4.0; using Adw 1; template $DataSubpage : Stack { - Adw.StatusPage loading_data { - title: _("Loading Data"); - description: _("This should only take a moment"); - child: - Spinner { - spinning: true; - } - ; - } + // Adw.StatusPage loading_data { + // title: _("Loading Data"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } Box content_box { orientation: vertical; Box label_box { diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 9aee758..f048463 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -3,6 +3,7 @@ from .host_info import HostInfo from .error_toast import ErrorToast from .data_box import DataBox from .host_info import HostInfo +from .loading_status import LoadingStatus import subprocess @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/data_subpage.ui") @@ -21,7 +22,6 @@ class DataSubpage(Gtk.Stack): flow_box = gtc() # Statuses - loading_data = gtc() content_box = gtc() no_data = gtc() no_results = gtc() @@ -203,6 +203,7 @@ class DataSubpage(Gtk.Stack): # Extra Object Creation self.main_window = main_window self.parent_page = parent_page + self.loading_data = LoadingStatus(_("Loading User Data"), _("This should only take a moment")) # self.is_active = is_active self.sort_mode = "" self.sort_ascend = False @@ -217,6 +218,7 @@ class DataSubpage(Gtk.Stack): self.settings = Gio.Settings.new("io.github.flattool.Warehouse.data_page") # Apply + self.add_child(self.loading_data) self.flow_box.set_sort_func(self.sort_func) self.flow_box.set_filter_func(self.filter_func) @@ -231,4 +233,4 @@ class DataSubpage(Gtk.Stack): # Connections parent_page.search_entry.connect("search-changed", self.on_invalidate) - self.flow_box.connect("child-activated", self.box_interact_handler) \ No newline at end of file + self.flow_box.connect("child-activated", self.box_interact_handler) From f2077d3cfec223747f379073b918fa57b2411602 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 1 Sep 2024 23:29:22 -0400 Subject: [PATCH 154/332] Make the add remote dialog HIG compliant --- src/remotes_page/add_remote_dialog.blp | 27 +++++++++++++------------- src/remotes_page/add_remote_dialog.py | 6 +++--- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/remotes_page/add_remote_dialog.blp b/src/remotes_page/add_remote_dialog.blp index 7e7942b..72f1d9f 100644 --- a/src/remotes_page/add_remote_dialog.blp +++ b/src/remotes_page/add_remote_dialog.blp @@ -3,24 +3,23 @@ using Adw 1; template $AddRemoteDialog : Adw.Dialog { title: _("Add a Remote"); - content-width: 500; - content-height: 375; + // content-width: 500; + // content-height: 375; + width-request: 400; + follows-content-size: true; Adw.ToolbarView { [top] Adw.HeaderBar { - } - [bottom] - ActionBar action_bar { - [center] + show-start-title-buttons: false; + show-end-title-buttons: false; + [start] + Button cancel_button { + label: _("Cancel"); + } + [end] Button apply_button { - margin-top: 3; - margin-bottom: 3; - sensitive: false; - styles ["pill", "suggested-action"] - Adw.ButtonContent { - icon-name: "plus-large-symbolic"; - label: _("Add Remote"); - } + styles ["suggested-action"] + label: _("Add"); } } Adw.ToastOverlay toast_overlay { diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index 643fd44..cc74a2b 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -9,9 +9,9 @@ class AddRemoteDialog(Adw.Dialog): __gtype_name__ = "AddRemoteDialog" gtc = Gtk.Template.Child - action_bar = gtc() toast_overlay = gtc() stack = gtc() + cancel_button = gtc() apply_button = gtc() content_page = gtc() title_row = gtc() @@ -22,7 +22,6 @@ class AddRemoteDialog(Adw.Dialog): def on_apply(self, *args): self.stack.set_visible_child(self.loading_page) self.apply_button.set_sensitive(False) - self.action_bar.set_revealed(False) error = [None] def thread(*args): cmd = [ @@ -48,7 +47,6 @@ class AddRemoteDialog(Adw.Dialog): def callback(*args): if error[0]: self.stack.set_visible_child(self.content_page) - self.action_bar.set_revealed(True) self.apply_button.set_sensitive(True) self.toast_overlay.add_toast(ErrorToast(_("Could not add remote"), str(error[0])).toast) else: @@ -104,6 +102,7 @@ class AddRemoteDialog(Adw.Dialog): self.check_entries(self.title_row) self.check_entries(self.name_row) self.check_entries(self.url_row) + self.url_row.set_editable(False) else: self.title_row.set_editable(False) self.name_row.set_editable(False) @@ -111,6 +110,7 @@ class AddRemoteDialog(Adw.Dialog): self.apply_button.set_sensitive(True) # Connections + self.cancel_button.connect("clicked", lambda *_: self.close()) self.apply_button.connect("clicked", self.on_apply) self.title_row.connect("changed", self.check_entries) self.name_row.connect("changed", self.check_entries) From 45d56d5d42af59f562c44866dc5bc1bd22377d54 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 1 Sep 2024 23:33:27 -0400 Subject: [PATCH 155/332] Show a toast when filtering by remote from Remotes Page --- src/remotes_page/remotes_page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index fc37abc..3cad15f 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -157,6 +157,7 @@ class RemotesPage(Adw.NavigationPage): packages_page.filters_page.generate_filters() packages_page.apply_filters() GLib.idle_add(lambda *_: self.main_window.activate_row(self.main_window.packages_row)) + GLib.idle_add(lambda *args: packages_page.packages_toast_overlay.add_toast(Adw.Toast(title=_("Showing all packages from {}").format(row.remote.title)))) def remove_remote(self, row): error = [None] From ed9133b6e29bc0f28dde4c141d0df09957a6710b Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 1 Sep 2024 23:37:42 -0400 Subject: [PATCH 156/332] Fix Remote Page's search showing No Results when a search is made when the system only has disabled remotes --- src/remotes_page/remotes_page.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 3cad15f..47fb8cd 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -212,6 +212,10 @@ class RemotesPage(Adw.NavigationPage): total += visible row.set_visible(visible) + if text == "": + self.stack.set_visible_child(self.content_page) + return + self.stack.set_visible_child(self.content_page if total > 0 else self.no_results) def file_callback(self, chooser, result): From 8c97454b2ed7fc30c4e41adb8ce164cf1c8f0fff Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 2 Sep 2024 20:36:20 -0400 Subject: [PATCH 157/332] Properly handle search sensitivity on reload --- src/packages_page/packages_page.py | 8 ++++++++ src/remotes_page/remotes_page.py | 1 - src/user_data_page/user_data_page.py | 7 +++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 6d823ac..7ee7cbb 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -24,6 +24,7 @@ class PackagesPage(Adw.BreakpointBin): no_packages = gtc() no_results = gtc() filter_button = gtc() + search_button = gtc() search_bar = gtc() search_entry = gtc() packages_split = gtc() @@ -53,6 +54,9 @@ 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) @@ -62,6 +66,10 @@ class PackagesPage(Adw.BreakpointBin): self.filters_page.set_sensitive(False) self.select_button.set_active(False) + self.search_button.set_active(False) + self.search_button.set_sensitive(False) + self.search_entry.set_editable(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) diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 47fb8cd..9701511 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -99,7 +99,6 @@ class RemotesPage(Adw.NavigationPage): def start_loading(self): self.search_button.set_active(False) self.search_button.set_sensitive(False) - self.search_entry.set_text("") self.search_entry.set_editable(False) self.stack.set_visible_child(self.loading_remotes) self.total_disabled = 0 diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index c07c4bb..fe9d8e7 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -57,20 +57,23 @@ class UserDataPage(Adw.BreakpointBin): self.leftover_data.append(folder) def start_loading(self, *args): + self.header_bar.set_sensitive(False) + self.search_button.set_active(False) + self.search_entry.set_editable(False) self.select_button.set_active(False) self.adp.set_visible_child(self.adp.loading_data) self.adp.size_label.set_label("Loading Size") self.adp.spinner.set_visible(True) - # self.adp.flow_box.remove_all() self.ldp.set_visible_child(self.ldp.loading_data) self.ldp.size_label.set_label("Loading Size") self.ldp.spinner.set_visible(True) - # self.ldp.flow_box.remove_all() def end_loading(self, *args): def callback(*args): self.adp.generate_list(self.data_flatpaks, self.active_data) self.ldp.generate_list([], self.leftover_data) + self.header_bar.set_sensitive(True) + self.search_entry.set_editable(True) Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) From 779e9a6a4f4121d5f701e0d43db289365fb4c33f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 2 Sep 2024 20:44:48 -0400 Subject: [PATCH 158/332] Add ability to open user data folder --- src/user_data_page/data_box.py | 4 ++-- src/user_data_page/user_data_page.blp | 7 ++++++- src/user_data_page/user_data_page.py | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 667e744..0f31806 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -59,9 +59,9 @@ class DataBox(Gtk.ListBox): def open_handler(self, *args): try: Gio.AppInfo.launch_default_for_uri(f"file://{self.data_path}", None) - self.toast_overlay.add_toast(Adw.Toast.new(_("Opened data path"))) + self.toast_overlay.add_toast(Adw.Toast.new(_("Opened data folder"))) except GLib.GError as e: - self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(e)).toast) + self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) def trash_handler(self, *args): self.failed_trash = False diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 89c06b9..8f5c11a 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -34,6 +34,11 @@ template $UserDataPage : Adw.BreakpointBin { icon-name: "system-search-symbolic"; tooltip-text: _("Search User Data"); } + [start] + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data Folder"); + } [end] MenuButton sort_button { popover: sort_pop; @@ -164,4 +169,4 @@ Popover sort_pop { } } } -} \ No newline at end of file +} diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index fe9d8e7..ef87121 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -16,6 +16,7 @@ class UserDataPage(Adw.BreakpointBin): header_bar = gtc() switcher_bar = gtc() search_button = gtc() + open_button = gtc() select_button = gtc() sort_button = gtc() search_bar = gtc() @@ -39,6 +40,7 @@ class UserDataPage(Adw.BreakpointBin): # This must be set to the created object from within the class's __init__ method instance = None page_name = "user-data" + data_path = f"{HostInfo.home}/.var/app" def sort_data(self, *args): self.data_flatpaks.clear() @@ -46,10 +48,10 @@ class UserDataPage(Adw.BreakpointBin): self.leftover_data.clear() # paks = dict(HostInfo.id_to_flatpak) - if not os.path.exists(f"{HostInfo.home}/.var/app"): + if not os.path.exists(self.data_path): return - for folder in os.listdir(f"{HostInfo.home}/.var/app"): + for folder in os.listdir(self.data_path): try: self.data_flatpaks.append(HostInfo.id_to_flatpak[folder]) self.active_data.append(folder) @@ -186,6 +188,13 @@ class UserDataPage(Adw.BreakpointBin): self.adp.label_box.set_orientation(Gtk.Orientation.VERTICAL if is_applied else Gtk.Orientation.HORIZONTAL) self.ldp.label_box.set_orientation(Gtk.Orientation.VERTICAL if is_applied else Gtk.Orientation.HORIZONTAL) + def open_data_folder(self, button): + try: + Gio.AppInfo.launch_default_for_uri(f"file://{self.data_path}", None) + self.toast_overlay.add_toast(Adw.Toast.new(_("Opened data folder"))) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -223,6 +232,7 @@ class UserDataPage(Adw.BreakpointBin): ) # Connections + 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) self.select_all_button.connect("clicked", self.select_all_handler) From 759eceabf66cb212ea26845d0454b27705c269b4 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 2 Sep 2024 20:47:44 -0400 Subject: [PATCH 159/332] Fix Packages Page search trying to show invisible rows --- src/packages_page/packages_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 7ee7cbb..1ff6d74 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -201,7 +201,7 @@ class PackagesPage(Adw.BreakpointBin): search_text = self.search_entry.get_text().lower() title = row.get_title().lower() subtitle = row.get_subtitle().lower() - if search_text in title or search_text in subtitle: + if row.get_visible() and (search_text in title or search_text in subtitle): self.is_result = True return True From 482a55bc90aa56f88fada03e5dc6f1160f2ae8f0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 2 Sep 2024 20:56:23 -0400 Subject: [PATCH 160/332] fix User Data Page's Trash button enabling select mode --- src/user_data_page/data_box.py | 7 +++++-- src/user_data_page/data_subpage.py | 10 ++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/user_data_page/data_box.py b/src/user_data_page/data_box.py index 0f31806..1ab1df3 100644 --- a/src/user_data_page/data_box.py +++ b/src/user_data_page/data_box.py @@ -83,21 +83,24 @@ class DataBox(Gtk.ListBox): self.trash_callback(self) def on_response(_, response): + self.parent_page.should_rclick = True if response != "continue": return Gio.Task.new(None, None, callback).run_in_thread(thread) + self.parent_page.should_rclick = False dialog = Adw.AlertDialog(heading=_("Trash {}'s Data?").format(self.title), body=_("{}'s data will be sent to the trash").format(self.title)) dialog.add_response("cancel", _("Cancel")) dialog.add_response("continue", _("Continue")) dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) dialog.connect("response", on_response) - dialog.present(ErrorToast.main_window) + dialog.present(HostInfo.main_window) - def __init__(self, toast_overlay, title, subtitle, data_path, icon_path=None, callback=None, trash_callback=None, **kwargs): + def __init__(self, parent_page, toast_overlay, title, subtitle, data_path, icon_path=None, callback=None, trash_callback=None, **kwargs): super().__init__(**kwargs) + self.parent_page = parent_page # Extra Object Creation self.toast_overlay = toast_overlay self.title = title diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index f048463..c7dffb6 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -120,8 +120,9 @@ class DataSubpage(Gtk.Stack): box.get_child().check_button.set_active(True) def box_rclick_handler(self, box): - self.parent_page.select_button.set_active(True) - box.check_button.set_active(not box.check_button.get_active()) + if self.should_rclick: + self.parent_page.select_button.set_active(True) + box.check_button.set_active(not box.check_button.get_active()) def generate_list(self, flatpaks, data): self.flow_box.remove_all() @@ -133,14 +134,14 @@ class DataSubpage(Gtk.Stack): self.parent_page.search_entry.set_editable(True) if flatpaks: for i, pak in enumerate(flatpaks): - box = DataBox(self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, self.trash_handler) + box = DataBox(self, self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, self.trash_handler) box.check_button.connect("toggled", lambda *_, box=box: self.box_select_handler(box)) self.boxes.append(box) self.flow_box.append(box) else: for i, folder in enumerate(data): - box = DataBox(self.parent_page.toast_overlay, folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback, self.trash_handler) + box = DataBox(self, self.parent_page.toast_overlay, folder.split('.')[-1], folder, f"{HostInfo.home}/.var/app/{folder}", None, self.box_size_callback, self.trash_handler) box.check_button.connect("toggled", lambda *_, box=box: self.box_select_handler(box)) self.flow_box.append(box) @@ -212,6 +213,7 @@ class DataSubpage(Gtk.Stack): self.boxes = [] self.selected_boxes = [] self.ready_to_sort_size = False + self.should_rclick = False self.finished_boxes = 0 self.is_result = False self.prev_status = None From 1b400f43b197c085b21b15647376924141dfd355 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 2 Sep 2024 21:33:11 -0400 Subject: [PATCH 161/332] Fix rclick not being active on first page load --- src/user_data_page/data_subpage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index c7dffb6..9718667 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -132,6 +132,7 @@ class DataSubpage(Gtk.Stack): self.total_size = 0 self.total_items = len(data) self.parent_page.search_entry.set_editable(True) + self.should_rclick = True if flatpaks: for i, pak in enumerate(flatpaks): box = DataBox(self, self.parent_page.toast_overlay, pak.info["name"], pak.info["id"], pak.data_path, pak.icon_path, self.box_size_callback, self.trash_handler) @@ -213,7 +214,7 @@ class DataSubpage(Gtk.Stack): self.boxes = [] self.selected_boxes = [] self.ready_to_sort_size = False - self.should_rclick = False + self.should_rclick = True self.finished_boxes = 0 self.is_result = False self.prev_status = None From db7b1044f4e9cc9e56da50b5c29e603af57315ee Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 2 Sep 2024 22:02:50 -0400 Subject: [PATCH 162/332] Fix the padding --- src/main_window/window.blp | 4 +-- src/remotes_page/add_remote_dialog.blp | 39 +++++++++++++++++--------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/main_window/window.blp b/src/main_window/window.blp index 4d62690..277d4d7 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -6,7 +6,7 @@ template $WarehouseWindow: Adw.ApplicationWindow { // default-width: 240; default-width: 865; width-request: 400; - height-request: 360; + height-request: 260; Adw.Breakpoint main_breakpoint { condition ("min-width: 865") @@ -182,4 +182,4 @@ menu primary_menu { action: "app.about"; } } -} \ No newline at end of file +} diff --git a/src/remotes_page/add_remote_dialog.blp b/src/remotes_page/add_remote_dialog.blp index 72f1d9f..5bfcd64 100644 --- a/src/remotes_page/add_remote_dialog.blp +++ b/src/remotes_page/add_remote_dialog.blp @@ -5,7 +5,7 @@ template $AddRemoteDialog : Adw.Dialog { title: _("Add a Remote"); // content-width: 500; // content-height: 375; - width-request: 400; + // width-request: 400; follows-content-size: true; Adw.ToolbarView { [top] @@ -24,19 +24,30 @@ template $AddRemoteDialog : Adw.Dialog { } Adw.ToastOverlay toast_overlay { Stack stack { - Adw.PreferencesPage content_page { - Adw.PreferencesGroup { - Adw.EntryRow title_row { - title: _("Enter Title"); - } - Adw.EntryRow name_row { - title: _("Enter Name"); - } - Adw.EntryRow url_row { - title: _("Enter Repo URL"); - } - Adw.ComboRow installation_row { - title: _("Installation"); + ScrolledWindow content_page { + propagate-natural-height: true; + propagate-natural-width: true; + Adw.Clamp { + ListBox { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + selection-mode: none; + valign: start; + styles ["boxed-list"] + Adw.EntryRow title_row { + title: _("Enter Title"); + } + Adw.EntryRow name_row { + title: _("Enter Name"); + } + Adw.EntryRow url_row { + title: _("Enter Repo URL"); + } + Adw.ComboRow installation_row { + title: _("Installation"); + } } } } From b6a6c9ca36e715630538f9b4d6595a1e45c11e1f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 2 Sep 2024 23:21:21 -0400 Subject: [PATCH 163/332] Add some functionality to the more menu --- src/properties_page/properties_page.blp | 39 +++++--------------- src/properties_page/properties_page.py | 47 +++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index a7cc638..5f63125 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -9,9 +9,6 @@ template $PropertiesPage : Adw.NavigationPage { Adw.HeaderBar { show-title: false; } - // Adw.StatusPage { - // title: _("Loading Properties"); - // } } Adw.ToolbarView error_tbv { [top] @@ -33,35 +30,9 @@ template $PropertiesPage : Adw.NavigationPage { Adw.HeaderBar header_bar { show-title: false; [end] - MenuButton more_menu { + MenuButton more_menu_button { icon-name: "view-more-symbolic"; - popover: - Popover { - styles ["menu"] - ListBox more_list { - ListBoxRow details { - Label { - label: _("Show Details in Store"); - } - } - ListBoxRow reinstall { - Label { - label: _("Reinstall"); - } - } - ListBoxRow copy_launch { - Label { - label: _("Copy Launch Command"); - } - } - ListBoxRow view_snapshots { - Label { - label: _("View Snapshots"); - } - } - } - } - ; + popover: more_menu; } } ScrolledWindow scrolled_window { @@ -349,3 +320,9 @@ template $PropertiesPage : Adw.NavigationPage { } } } + +Popover more_menu { + styles ["menu"] + ListBox more_list { + } +} diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index aa60c7a..3b881ef 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -1,4 +1,4 @@ -from gi.repository import Adw, Gtk,GLib#, Gio, Pango +from gi.repository import Adw, Gtk,GLib, Gio from .error_toast import ErrorToast from .host_info import HostInfo from .change_version_page import ChangeVersionPage @@ -14,6 +14,9 @@ class PropertiesPage(Adw.NavigationPage): error_tbv = gtc() loading_tbv = gtc() + more_menu = gtc() + more_list = gtc() + nav_view = gtc() inner_nav_page = gtc() toast_overlay = gtc() @@ -65,7 +68,6 @@ class PropertiesPage(Adw.NavigationPage): return self.package = package - pkg_name = package.info["name"] if pkg_name != "": self.inner_nav_page.set_title(_("{} Properties").format(package.info["name"])) @@ -145,7 +147,14 @@ class PropertiesPage(Adw.NavigationPage): self.mask_switch.set_active(package.is_masked) self.pin_switch.set_active(package.is_pinned) GLib.idle_add(lambda *_: self.stack.set_visible_child(self.nav_view)) + self.more_list.remove_all() + if self.open_app_button.get_visible(): + self.more_list.append(self.view_snapshots) + self.more_list.append(self.copy_launch_command) + self.more_list.append(self.show_details) + self.more_list.append(self.reinstall) + def open_data_handler(self, *args): if error := self.package.open_data(): self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error)).toast) @@ -255,6 +264,28 @@ class PropertiesPage(Adw.NavigationPage): page = ChangeVersionPage(self.main_window, self.package) self.nav_view.push(page) + def more_menu_handler(self, listbox, row): + self.more_menu.popdown() + match row.get_child(): + case self.view_snapshots: + print("not implemented") + + case self.copy_launch_command: + try: + HostInfo.clipboard.set(f"flatpak run {self.package.info['ref']}") + self.toast_overlay.add_toast(Adw.Toast.new(_("Copied launch command"))) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not copy launch command"), str(e)).toast) + + case self.show_details: + try: + Gio.AppInfo.launch_default_for_uri(f"appstream://{self.package.info['id']}", None) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not show details"), str(e)).toast) + + case self.reinstall: + print("not implemented") + def __init__(self, main_window, packages_page, **kwargs): super().__init__(**kwargs) @@ -283,8 +314,19 @@ class PropertiesPage(Adw.NavigationPage): self.loading_tbv.set_content(LoadingStatus(_("Loading Properties"), _("This should only take a moment"))) self.packages_page = packages_page self.__class__.main_window = main_window + self.view_snapshots = Gtk.Label(halign=Gtk.Align.START, label=_("View Snapshots")) + self.copy_launch_command = Gtk.Label(halign=Gtk.Align.START, label=_("Copy Launch Command")) + self.show_details = Gtk.Label(halign=Gtk.Align.START, label=_("Show Details")) + self.reinstall = Gtk.Label(halign=Gtk.Align.START, label=_("Reinstall")) + + # Apply + self.more_list.append(self.view_snapshots) + self.more_list.append(self.copy_launch_command) + self.more_list.append(self.show_details) + self.more_list.append(self.reinstall) # Connections + self.more_list.connect("row-activated", self.more_menu_handler) self.open_data_button.connect("clicked", self.open_data_handler) self.scrolled_window.get_vadjustment().connect("value-changed", lambda adjustment: self.header_bar.set_show_title(not adjustment.get_value() == 0)) self.trash_data_button.connect("clicked", self.trash_data_handler) @@ -298,4 +340,5 @@ class PropertiesPage(Adw.NavigationPage): row = self.info_rows[key] if type(row) != Adw.ActionRow: continue + row.connect("activated", self.copy_handler) From bae9c0d955403492d314454e8463db7eb2a8677f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 3 Sep 2024 16:42:37 -0400 Subject: [PATCH 164/332] Further work on snapshots --- src/{packages_page => gtk}/app_row.blp | 0 src/{packages_page => gtk}/app_row.py | 7 ++-- src/meson.build | 4 +- src/snapshot_page/snapshot_box.py | 1 - src/snapshot_page/snapshot_page.blp | 47 +++++++++++++----------- src/snapshot_page/snapshot_page.py | 33 ++++++----------- src/snapshot_page/snapshots_list_page.py | 5 ++- src/warehouse.gresource.xml | 2 +- 8 files changed, 48 insertions(+), 51 deletions(-) rename src/{packages_page => gtk}/app_row.blp (100%) rename src/{packages_page => gtk}/app_row.py (89%) diff --git a/src/packages_page/app_row.blp b/src/gtk/app_row.blp similarity index 100% rename from src/packages_page/app_row.blp rename to src/gtk/app_row.blp diff --git a/src/packages_page/app_row.py b/src/gtk/app_row.py similarity index 89% rename from src/packages_page/app_row.py rename to src/gtk/app_row.py index 3ca505f..e62ee06 100644 --- a/src/packages_page/app_row.py +++ b/src/gtk/app_row.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio, Pango from .host_info import HostInfo -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/packages_page/app_row.ui") +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/gtk/app_row.ui") class AppRow(Adw.ActionRow): __gtype_name__ = 'AppRow' gtc = Gtk.Template.Child @@ -18,7 +18,8 @@ class AppRow(Adw.ActionRow): self.image.set_from_file(self.package.icon_path) def gesture_handler(self, *args): - self.on_long_press(self) + if self.on_long_press: + self.on_long_press(self) def __init__(self, package, on_long_press=None, **kwargs): super().__init__(**kwargs) @@ -38,4 +39,4 @@ class AppRow(Adw.ActionRow): # Connections self.rclick_gesture.connect("released", self.gesture_handler) - self.long_press_gesture.connect("pressed", self.gesture_handler) \ No newline at end of file + self.long_press_gesture.connect("pressed", self.gesture_handler) diff --git a/src/meson.build b/src/meson.build index 54915a6..4b1ef9e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,7 +4,7 @@ gnome = import('gnome') blueprints = custom_target('blueprints', input: files( - 'packages_page/app_row.blp', + 'gtk/app_row.blp', 'gtk/help-overlay.blp', 'gtk/loading_status.blp', 'main_window/window.blp', @@ -73,7 +73,7 @@ warehouse_sources = [ 'gtk/sidebar_button.py', 'gtk/loading_status.py', 'main_window/window.py', - 'packages_page/app_row.py', + 'gtk/app_row.py', 'packages_page/uninstall_dialog.py', 'packages_page/packages_page.py', 'packages_page/filters_page.py', diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index f8651f6..83b9771 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -61,5 +61,4 @@ class SnapshotBox(Gtk.Box): self.json_path = f"{snapshots_path}{folder.replace('tar.zst', 'json')}" self.rename_button.connect("clicked", self.json_handler) - print(self.json_path) \ No newline at end of file diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 297230d..4476936 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -14,23 +14,13 @@ template $SnapshotPage : Adw.BreakpointBin { } } - // Adw.Breakpoint bp2 { - // condition ("max-width: 700") - - // setters { - // header_bar.title-widget: null; - // switcher_bar.reveal: true; - // } - // } - - Adw.ToastOverlay toast_overlay { - Adw.NavigationSplitView split_view { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage sidebar_navpage { - title: _("Snapshots"); - + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage sidebar_navpage { + title: _("Snapshots"); + Adw.ToastOverlay toast_overlay { Adw.ToolbarView sidebar_tbv { [top] Adw.HeaderBar header_bar { @@ -41,6 +31,11 @@ template $SnapshotPage : Adw.BreakpointBin { icon-name: "loupe-large-symbolic"; tooltip-text: _("Search Packages"); } + [start] + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open Snapshots Folder"); + } [end] Button new_button { icon-name: "plus-large-symbolic"; @@ -63,9 +58,17 @@ template $SnapshotPage : Adw.BreakpointBin { // ; // } Adw.StatusPage no_snapshots { - title: _("No Snapshots Found"); - description: _("Warehouse cannot see the list of snapshots or you don't have any snapshots"); - icon-name: "error-symbolic"; + title: _("No Snapshots"); + description: _("Create a Snapshot to save the state of any Flatpak application"); + icon-name: "snapshots-alt-symbolic"; + Button status_new_button { + styles ["suggested-action", "pill"] + halign: center; + Adw.ButtonContent { + icon-name: "plus-large-symbolic"; + label: _("New Snapshot"); + } + } } Adw.StatusPage no_results { title: _("No Results Found"); @@ -133,7 +136,7 @@ template $SnapshotPage : Adw.BreakpointBin { } } } - ; - } + } + ; } } diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index e64f9ac..2a4a4be 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -41,6 +41,7 @@ class SnapshotPage(Adw.BreakpointBin): no_snapshots = gtc() no_results = gtc() scrolled_window = gtc() + open_button = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -98,7 +99,6 @@ class SnapshotPage(Adw.BreakpointBin): self.active_box.set_visible(True) first_row = self.active_listbox.get_row_at_index(0) self.active_listbox.select_row(first_row) - self.stack.set_visible_child(self.scrolled_window) else: self.active_box.set_visible(False) @@ -125,7 +125,6 @@ class SnapshotPage(Adw.BreakpointBin): self.list_page.set_snapshots(row.folder, row.get_title()) def start_loading(self): - # self.list_page.start_loading() self.active_box.set_visible(True) self.active_listbox.remove_all() self.leftover_box.set_visible(True) @@ -137,28 +136,19 @@ class SnapshotPage(Adw.BreakpointBin): self.generate_active_list() self.generate_leftover_list() if (not self.active_box.get_visible()) and (not self.leftover_box.get_visible()): - self.stack.set_visible_child(self.no_snapshots) - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.no_snapshots)) + else: + GLib.idle_add(lambda *_: self.stack.set_visible_child(self.scrolled_window)) 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://{self.snapshots_path}", None) + self.toast_overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -175,6 +165,7 @@ class SnapshotPage(Adw.BreakpointBin): # Connections 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) # Apply self.stack.add_child(self.loading_snapshots) diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index c559ece..9e2b8de 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -17,6 +17,9 @@ class SnapshotsListPage(Adw.NavigationPage): def thread(self, *args): for snapshot in os.listdir(folder := f"{self.snapshots_path}{self.current_folder}/"): + if snapshot.endswith(".json"): + continue + row = SnapshotBox(snapshot, folder, self.toast_overlay) self.snapshots_rows.append(row) @@ -40,4 +43,4 @@ class SnapshotsListPage(Adw.NavigationPage): super().__init__(**kwargs) self.current_folder = None - self.snapshots_rows = [] \ No newline at end of file + self.snapshots_rows = [] diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index d102e75..1e78244 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -4,7 +4,7 @@ ../data/style.css gtk/help-overlay.ui gtk/loading_status.ui - packages_page/app_row.ui + gtk/app_row.ui main_window/window.ui packages_page/packages_page.ui packages_page/filters_page.ui From fc521c6791bffc13b1d34f5a479ce74a8a328a8e Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 3 Sep 2024 18:15:37 -0400 Subject: [PATCH 165/332] Add ability to rename snapshots --- src/snapshot_page/snapshot_box.blp | 31 +++++++++++++++++++++++-- src/snapshot_page/snapshot_box.py | 36 +++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/snapshot_page/snapshot_box.blp b/src/snapshot_page/snapshot_box.blp index 36cd0cb..28b3fe9 100644 --- a/src/snapshot_page/snapshot_box.blp +++ b/src/snapshot_page/snapshot_box.blp @@ -47,7 +47,7 @@ template $SnapshotBox : Gtk.Box { hexpand: true; styles ["flat"] } - Button rename_button { + MenuButton rename_button { Adw.ButtonContent { label: _("Rename"); icon-name: "dot-symbolic"; @@ -55,6 +55,7 @@ template $SnapshotBox : Gtk.Box { } hexpand: true; styles ["flat"] + popover: rename_menu; } Button trash_button { Adw.ButtonContent { @@ -66,4 +67,30 @@ template $SnapshotBox : Gtk.Box { styles ["flat"] } } -} \ No newline at end of file +} + +Popover rename_menu { + Box { + orientation: vertical; + spacing: 11; + margin-start: 12; + margin-end: 12; + margin-top: 5; + margin-bottom: 12; + Label { + label: _("Rename Snapshot?"); + styles ["title-2"] + } + Box { + spacing: 6; + Entry rename_entry { + text: bind title.label; + } + Button apply_rename { + icon-name: "check-plain-symbolic"; + tooltip-text: _("Confirm Rename"); + styles ["circular", "suggested-action"] + } + } + } +} diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 83b9771..d022409 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -13,6 +13,9 @@ class SnapshotBox(Gtk.Box): version = gtc() apply_button = gtc() rename_button = gtc() + rename_menu = gtc() + rename_entry = gtc() + apply_rename = gtc() trash_button = gtc() def create_json(self): @@ -24,14 +27,15 @@ class SnapshotBox(Gtk.Box): with open(self.json_path, 'w') as file: json.dump(data, file, indent=4) return None + except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast) - def update_json(self): + def update_json(self, key, value): try: with open(self.json_path, 'r+') as file: data = json.load(file) - data['name'] = "updated" + data[key] = value file.seek(0) json.dump(data, file, indent=4) file.truncate() @@ -39,12 +43,25 @@ class SnapshotBox(Gtk.Box): except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast) - def json_handler(self, *args): + 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) + name = data['name'] + if name != "": + self.title.set_label(GLib.markup_escape_text(name)) + + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast) - self.update_json() - + def on_rename(self, widget): + self.update_json('name', self.rename_entry.get_text().strip()) + self.load_from_json() + self.rename_menu.popdown() + def __init__(self, folder, snapshots_path, toast_overlay, **kwargs): super().__init__(**kwargs) @@ -56,9 +73,8 @@ class SnapshotBox(Gtk.Box): date_data = GLib.DateTime.new_from_unix_local(int(split_folder[0])).format("%x %X") self.date.set_label(date_data) - self.version.set_label(split_folder[1].replace(".tar.zst", "")) - + self.version.set_label(_("Version: {}").format(split_folder[1].replace(".tar.zst", ""))) self.json_path = f"{snapshots_path}{folder.replace('tar.zst', 'json')}" - - self.rename_button.connect("clicked", self.json_handler) - \ No newline at end of file + self.load_from_json() + self.apply_rename.connect("clicked", self.on_rename) + self.rename_entry.connect("activate", self.on_rename) From b202b31765a8c8305c3fc153960ce5d9a9e23539 Mon Sep 17 00:00:00 2001 From: heliguy4599 Date: Wed, 4 Sep 2024 15:25:45 -0400 Subject: [PATCH 166/332] Fix packages page not showing proper status upon failed uninstalls --- src/host_info.py | 18 +++--------------- src/packages_page/packages_page.py | 6 +++--- src/properties_page/properties_page.py | 2 +- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index b967968..c421801 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -100,8 +100,7 @@ class Flatpak: self.failed_uninstall = None def thread(*args): - prefix = ['flatpak-spawn', '--host'] - cmd = ['flatpak', 'uninstall', '-y', self.info["ref"]] + cmd = ['flatpak-spawn', '--host', 'flatpak', 'uninstall', '-y', self.info["ref"]] installation = self.info["installation"] if installation == "system" or installation == "user": cmd.append(f"--{installation}") @@ -109,20 +108,9 @@ class Flatpak: cmd.append(f"--installation={installation}") try: - subprocess.run(prefix + cmd, check=True, text=True, capture_output=True) - # print(prefix + cmd) + subprocess.run(cmd, check=True, text=True, capture_output=True) except subprocess.CalledProcessError as cpe: - if installation == "user": - self.failed_uninstall = cpe - return - - try: - subprocess.run(prefix + ['pkexec'] + cmd, check=True, text=True) - except subprocess.CalledProcessError as cpe2: - self.failed_uninstall = cpe2 - except Exception as e2: - self.failed_uninstall = e2 - + self.failed_uninstall = cpe except Exception as e: self.failed_uninstall = e diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index 1ff6d74..e50c39b 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -259,12 +259,12 @@ class PackagesPage(Adw.BreakpointBin): error[0] = e def callback(*args): + self.main_window.refresh_handler() if err := error[0]: details = err.stderr if type(err) == subprocess.CalledProcessError else str(err) - self.packages_toast_overlay.add_toast(ErrorToast(_("Could not uninstall packages"), details).toast) + GLib.idle_add(lambda *args: self.packages_toast_overlay.add_toast(ErrorToast(_("Could not uninstall packages"), details).toast)) else: - self.main_window.refresh_handler() - GLib.idle_add(lambda *__: self.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled Packages")))) + 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) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 3b881ef..4957f8a 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -232,7 +232,7 @@ class PropertiesPage(Adw.NavigationPage): if fail := self.package.failed_uninstall: fail = fail.stderr if type(fail) == subprocess.CalledProcessError else fail self.toast_overlay.add_toast(ErrorToast(_("Could not uninstall"), str(fail)).toast) - self.packages_page.stack.set_visible_child(self.packages_page.packages_split) + self.packages_page.set_status(self.packages_page.scrolled_window) else: self.main_window.refresh_handler() self.packages_page.packages_toast_overlay.add_toast(Adw.Toast(title=_("Uninstalled {}").format(self.package.info["name"]))) From d156e3452f4d94b8b214df194505a185f9860566 Mon Sep 17 00:00:00 2001 From: heliguy4599 Date: Wed, 4 Sep 2024 15:39:15 -0400 Subject: [PATCH 167/332] Add button to open the current app's snapshots folder --- src/snapshot_page/snapshot_page.py | 2 +- src/snapshot_page/snapshots_list_page.blp | 5 +++++ src/snapshot_page/snapshots_list_page.py | 21 +++++++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 2a4a4be..31ec95e 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -169,4 +169,4 @@ class SnapshotPage(Adw.BreakpointBin): # Apply self.stack.add_child(self.loading_snapshots) - self.split_view.set_content(self.list_page)## + self.split_view.set_content(self.list_page) diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index 768f5fb..330625c 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -7,6 +7,11 @@ template $SnapshotsListPage : Adw.NavigationPage { Adw.ToolbarView { [top] Adw.HeaderBar { + [start] + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open Snapshots Folder for this App"); + } } ScrolledWindow { Adw.Clamp { diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 9e2b8de..2d30dee 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -12,8 +12,7 @@ class SnapshotsListPage(Adw.NavigationPage): listbox = gtc() toast_overlay = gtc() - - snapshots_path = f"{HostInfo.home}/.var/app/io.github.flattool.Warehouse/data/Snapshots/" + open_button = gtc() def thread(self, *args): for snapshot in os.listdir(folder := f"{self.snapshots_path}{self.current_folder}/"): @@ -39,8 +38,26 @@ class SnapshotsListPage(Adw.NavigationPage): Gio.Task.new(None, None, self.callback).run_in_thread(self.thread) + def open_snapshots_folder(self, button): + path = f"{self.snapshots_path}{self.current_folder}/" + try: + if not os.path.exists(path): + raise Exception(f"error: File '{path}' does not exist") + + Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) + self.toast_overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) + def __init__(self, parent_page, **kwargs): super().__init__(**kwargs) + # Extra Object Creation + self.snapshots_path = parent_page.snapshots_path self.current_folder = None self.snapshots_rows = [] + + # Connections + self.open_button.connect("clicked", self.open_snapshots_folder) + + # Apply \ No newline at end of file From 55642f8ab93b5d7a0c81d9530571afeac431d12b Mon Sep 17 00:00:00 2001 From: heliguy4599 Date: Wed, 4 Sep 2024 18:07:15 -0400 Subject: [PATCH 168/332] More work on snapshots --- src/snapshot_page/snapshot_box.py | 41 +++++++++++++- src/snapshot_page/snapshots_list_page.blp | 69 +++++++++++++---------- src/snapshot_page/snapshots_list_page.py | 15 ++++- 3 files changed, 91 insertions(+), 34 deletions(-) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index d022409..7d2ca93 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast -import os, json +import os, subprocess, json @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_box.ui") class SnapshotBox(Gtk.Box): @@ -61,8 +61,41 @@ class SnapshotBox(Gtk.Box): self.update_json('name', self.rename_entry.get_text().strip()) self.load_from_json() self.rename_menu.popdown() + + def on_trash(self, button): + error = [None] + path = f"{self.snapshots_path}{self.folder}" + def thread(*args): + try: + subprocess.run(['gio', 'trash', path], capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as cpe: + 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.parent_page.start_loading() + self.parent_page.parent_page.end_loading() + self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed snapshot"))) + + def on_response(_, response): + if response != "continue": + return + + Gio.Task.new(None, None, callback).run_in_thread(thread) + + dialog = Adw.AlertDialog(heading=_("Trash Snapshot?"), body=_("This snapshot will be moved to the trash")) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Trash")) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.connect("response", on_response) + dialog.present(HostInfo.main_window) - def __init__(self, folder, snapshots_path, toast_overlay, **kwargs): + def __init__(self, parent_page, folder, snapshots_path, toast_overlay, **kwargs): super().__init__(**kwargs) self.toast_overlay = toast_overlay @@ -71,6 +104,9 @@ class SnapshotBox(Gtk.Box): if len(split_folder) < 2: return + self.parent_page = parent_page + self.folder = folder + self.snapshots_path = snapshots_path date_data = GLib.DateTime.new_from_unix_local(int(split_folder[0])).format("%x %X") self.date.set_label(date_data) self.version.set_label(_("Version: {}").format(split_folder[1].replace(".tar.zst", ""))) @@ -78,3 +114,4 @@ class SnapshotBox(Gtk.Box): self.load_from_json() self.apply_rename.connect("clicked", self.on_rename) self.rename_entry.connect("activate", self.on_rename) + self.trash_button.connect("clicked", self.on_trash) diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index 330625c..a0d7442 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -4,39 +4,50 @@ using Adw 1; template $SnapshotsListPage : Adw.NavigationPage { title: _("Snapshots List"); Adw.ToastOverlay toast_overlay { - Adw.ToolbarView { - [top] - Adw.HeaderBar { - [start] - Button open_button { - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open Snapshots Folder for this App"); + Stack stack { + Adw.ToolbarView toolbar_view { + [top] + Adw.HeaderBar { + [start] + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open Snapshots Folder for this App"); + } } - } - ScrolledWindow { - Adw.Clamp { - margin-start: 12; - margin-end: 12; - margin-top: 12; - margin-bottom: 12; - ListBox listbox { - valign: start; - selection-mode: none; - styles ["boxed-list"] + ScrolledWindow { + Adw.Clamp { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + ListBox listbox { + valign: start; + selection-mode: none; + styles ["boxed-list"] + Adw.PreferencesGroup { + Adw.ActionRow {title: "test";} + } + } + } + } + [bottom] + ActionBar { + [center] + Button new_button { + margin-top: 3; + margin-bottom: 3; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + icon-name: "plus-large-symbolic"; + label: _("New Snapshot"); + } } } } - [bottom] - ActionBar { - [center] - Button new_button { - margin-top: 3; - margin-bottom: 3; - styles ["pill", "suggested-action"] - Adw.ButtonContent { - icon-name: "plus-large-symbolic"; - label: _("New Snapshot"); - } + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + show-title: false; } } } diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 2d30dee..3bff687 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -2,6 +2,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast from .snapshot_box import SnapshotBox +from .loading_status import LoadingStatus import os @@ -10,6 +11,9 @@ class SnapshotsListPage(Adw.NavigationPage): __gtype_name__ = "SnapshotsListPage" gtc = Gtk.Template.Child + stack = gtc() + toolbar_view = gtc() + loading_view = gtc() listbox = gtc() toast_overlay = gtc() open_button = gtc() @@ -19,18 +23,21 @@ class SnapshotsListPage(Adw.NavigationPage): if snapshot.endswith(".json"): continue - row = SnapshotBox(snapshot, folder, self.toast_overlay) + row = SnapshotBox(self, snapshot, folder, self.toast_overlay) self.snapshots_rows.append(row) def callback(self, *args): for i, row in enumerate(self.snapshots_rows): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) + + self.stack.set_visible_child(self.toolbar_view) def set_snapshots(self, folder, title): if self.current_folder == folder: return - + + self.stack.set_visible_child(self.loading_view) self.current_folder = folder self.set_title(_("{} Snapshots").format(title)) self.snapshots_rows.clear() @@ -53,6 +60,7 @@ class SnapshotsListPage(Adw.NavigationPage): super().__init__(**kwargs) # Extra Object Creation + self.parent_page = parent_page self.snapshots_path = parent_page.snapshots_path self.current_folder = None self.snapshots_rows = [] @@ -60,4 +68,5 @@ class SnapshotsListPage(Adw.NavigationPage): # Connections self.open_button.connect("clicked", self.open_snapshots_folder) - # Apply \ No newline at end of file + # Apply + self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment"))) \ No newline at end of file From 363dcc3dcfe5b0fd7154433a0b94082bb55677b3 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Wed, 4 Sep 2024 22:54:08 -0400 Subject: [PATCH 169/332] Fix padding and opacity --- src/gtk/loading_status.blp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gtk/loading_status.blp b/src/gtk/loading_status.blp index 4afc463..4303e86 100644 --- a/src/gtk/loading_status.blp +++ b/src/gtk/loading_status.blp @@ -14,6 +14,8 @@ template $LoadingStatus : ScrolledWindow { Spinner { height-request: 30; spinning: true; + margin-bottom: 12; + opacity: 0.5; } Label title_label { label: "No Title Set"; From 14ce17838a5d0eb76c0e28a113c18482850c33de Mon Sep 17 00:00:00 2001 From: Heliguy Date: Wed, 4 Sep 2024 22:54:43 -0400 Subject: [PATCH 170/332] Implement loading pages and mobile view --- src/snapshot_page/snapshot_page.blp | 252 ++++++++++++---------- src/snapshot_page/snapshot_page.py | 54 +++-- src/snapshot_page/snapshots_list_page.blp | 72 +++---- src/snapshot_page/snapshots_list_page.py | 8 +- 4 files changed, 209 insertions(+), 177 deletions(-) diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 4476936..9a562fd 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -14,129 +14,157 @@ template $SnapshotPage : Adw.BreakpointBin { } } - Adw.NavigationSplitView split_view { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage sidebar_navpage { - title: _("Snapshots"); - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView sidebar_tbv { - [top] - Adw.HeaderBar header_bar { - [start] - $SidebarButton {} - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Packages"); - } - [start] - Button open_button { - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open Snapshots Folder"); - } - [end] - Button new_button { - icon-name: "plus-large-symbolic"; - tooltip-text: _("New Snapshot"); - } - [end] - ToggleButton select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select Packages"); - } - } - Stack stack { - // Adw.StatusPage loading_snapshots { - // title: _("Loading Snapshot"); - // description: _("This should only take a moment"); - // child: - // Spinner { - // spinning: true; - // } - // ; - // } - Adw.StatusPage no_snapshots { - title: _("No Snapshots"); - description: _("Create a Snapshot to save the state of any Flatpak application"); - icon-name: "snapshots-alt-symbolic"; - Button status_new_button { - styles ["suggested-action", "pill"] - halign: center; - Adw.ButtonContent { + Adw.NavigationPage { + title: _("Snapshots"); + Stack status_stack { + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage sidebar_navpage { + title: _("Snapshots"); + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView sidebar_tbv { + [top] + Adw.HeaderBar header_bar { + [start] + $SidebarButton {} + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } + [start] + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open Snapshots Folder"); + } + [end] + Button new_button { icon-name: "plus-large-symbolic"; - label: _("New Snapshot"); + tooltip-text: _("New Snapshot"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select Packages"); } } - } - Adw.StatusPage no_results { - title: _("No Results Found"); - description: _("Try a different search"); - icon-name: "system-search-symbolic"; - } - ScrolledWindow scrolled_window { - Box { - orientation: vertical; - - Box active_box { - orientation: vertical; - - Label { - label: _("Active Snapshots"); - halign: start; - styles ["heading"] - margin-top: 3; - margin-bottom: 6; - margin-start: 12; - margin-end: 12; - wrap: true; - } - Label { - label: _("Snapshots of installed apps"); - halign: start; - styles ["dim-label"] - margin-start: 12; - margin-end: 12; - wrap: true; - } - ListBox active_listbox { - styles ["navigation-sidebar"] - valign: start; - } + Stack stack { + // Adw.StatusPage loading_snapshots { + // title: _("Loading Snapshot"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; } - Box leftover_box { - orientation: vertical; - - Label { - label: _("Leftover Snapshots"); - halign: start; - styles ["heading"] - margin-top: 3; - margin-bottom: 6; - margin-start: 12; - margin-end: 12; - wrap: true; - } - Label { - label: _("Snapshots of apps that are no longer installed"); - halign: start; - styles ["dim-label"] - margin-start: 12; - margin-end: 12; - wrap: true; - } - ListBox leftover_listbox { - styles ["navigation-sidebar"] - valign: start; + ScrolledWindow scrolled_window { + Box { + orientation: vertical; + + Box active_box { + orientation: vertical; + + Label { + label: _("Active Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; + margin-start: 12; + margin-end: 12; + wrap: true; + } + Label { + label: _("Snapshots of installed apps"); + halign: start; + styles ["dim-label"] + margin-start: 12; + margin-end: 12; + margin-bottom: 3; + wrap: true; + } + ListBox active_listbox { + styles ["navigation-sidebar"] + valign: start; + } + } + Box leftover_box { + orientation: vertical; + + Label { + label: _("Leftover Snapshots"); + halign: start; + styles ["heading"] + margin-top: 3; + margin-bottom: 6; + margin-start: 12; + margin-end: 12; + wrap: true; + } + Label { + label: _("Snapshots of apps that are no longer installed"); + halign: start; + styles ["dim-label"] + margin-start: 12; + margin-end: 12; + margin-bottom: 3; + wrap: true; + } + ListBox leftover_listbox { + styles ["navigation-sidebar"] + valign: start; + } + } } } } } } } + ; + } + Adw.ToolbarView no_snapshots { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + [start] + Button status_open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open Snapshots Folder"); + } + } + Adw.ToastOverlay no_snapshots_toast { + Adw.StatusPage { + title: _("No Snapshots"); + description: _("Create a Snapshot to save the state of any Flatpak application"); + icon-name: "snapshots-alt-symbolic"; + Button status_new_button { + styles ["suggested-action", "pill"] + halign: center; + Adw.ButtonContent { + icon-name: "plus-large-symbolic"; + label: _("New Snapshot"); + } + } + } } } - ; + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + } + } + } } } diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 31ec95e..4f8d93e 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -11,7 +11,7 @@ class LeftoverSnapshotRow(Adw.ActionRow): __gtype_name__ = "LeftoverSnapshotRow" def idle_stuff(self): - self.set_title(self.folder.split('.')[-1]) + self.set_title(self.name) icon = Gtk.Image.new_from_icon_name("application-x-executable-symbolic") icon.set_icon_size(Gtk.IconSize.LARGE) self.add_prefix(icon) @@ -20,8 +20,9 @@ class LeftoverSnapshotRow(Adw.ActionRow): def __init__(self, folder, **kwargs): super().__init__(**kwargs) - self.set_activatable(True) self.folder = folder + self.set_activatable(True) + self.name = self.folder.split('.')[-1] self.check_button = Gtk.CheckButton(visible=False) self.check_button.add_css_class("selection-mode") GLib.idle_add(lambda *_: self.idle_stuff()) @@ -32,6 +33,7 @@ class SnapshotPage(Adw.BreakpointBin): gtc = Gtk.Template.Child toast_overlay = gtc() + no_snapshots_toast = gtc() active_box = gtc() active_listbox = gtc() leftover_box = gtc() @@ -42,6 +44,10 @@ class SnapshotPage(Adw.BreakpointBin): no_results = gtc() scrolled_window = gtc() open_button = gtc() + status_open_button = gtc() + new_button = gtc() + status_stack = gtc() + loading_view = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -97,8 +103,8 @@ class SnapshotPage(Adw.BreakpointBin): if len(self.active_snapshot_paks) > 0: self.active_box.set_visible(True) - first_row = self.active_listbox.get_row_at_index(0) - self.active_listbox.select_row(first_row) + # first_row = self.active_listbox.get_row_at_index(0) + # self.active_listbox.select_row(first_row) else: self.active_box.set_visible(False) @@ -111,43 +117,55 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_box.set_visible(True) if len(self.active_snapshot_paks) == 0: self.stack.set_visible_child(self.scrolled_window) - first_row = self.leftover_listbox.get_row_at_index(0) - self.leftover_listbox.select_row(first_row) + # first_row = self.leftover_listbox.get_row_at_index(0) + # self.leftover_listbox.select_row(first_row) else: self.leftover_box.set_visible(False) - def active_select_handler(self, listbox, row): + def active_select_handler(self, listbox, row, should_show_content=True): self.leftover_listbox.select_row(None) - self.list_page.set_snapshots(row.package.info["id"], row.get_title()) + self.list_page.set_snapshots(row.package.info["id"], row.package.info["name"]) + self.split_view.set_show_content(should_show_content) - def leftover_select_handler(self, listbox, row): + def leftover_select_handler(self, listbox, row, should_show_content=True): self.active_listbox.select_row(None) - self.list_page.set_snapshots(row.folder, row.get_title()) + self.list_page.set_snapshots(row.folder, row.name) + self.split_view.set_show_content(should_show_content) def start_loading(self): self.active_box.set_visible(True) self.active_listbox.remove_all() self.leftover_box.set_visible(True) self.leftover_listbox.remove_all() - self.stack.set_visible_child(self.loading_snapshots) + self.status_stack.set_visible_child(self.loading_view) + + def select_first_row(self): + if row := self.active_listbox.get_row_at_index(0): + self.active_listbox.select_row(row) + self.active_select_handler(None, row, False) + elif row := self.leftover_listbox.get_row_at_index(0): + self.leftover_listbox.select_row(row) + self.leftover_select_handler(None, row, False) def end_loading(self): def callback(*args): self.generate_active_list() self.generate_leftover_list() if (not self.active_box.get_visible()) and (not self.leftover_box.get_visible()): - GLib.idle_add(lambda *_: self.stack.set_visible_child(self.no_snapshots)) + GLib.idle_add(lambda *_: self.status_stack.set_visible_child(self.no_snapshots)) else: + self.select_first_row() 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)) Gio.Task.new(None, None, callback).run_in_thread(self.sort_snapshots) - def open_snapshots_folder(self, button): + def open_snapshots_folder(self, button, overlay): try: Gio.AppInfo.launch_default_for_uri(f"file://{self.snapshots_path}", None) - self.toast_overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) + overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) except Exception as e: - self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) + overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -155,7 +173,6 @@ class SnapshotPage(Adw.BreakpointBin): # Extra Object Creation self.__class__.instance = self self.main_window = main_window - self.loading_snapshots = LoadingStatus(_("Loading Snapshots"), _("This should only take a moment")) self.active_snapshot_paks = [] # self.active_rows = [] self.leftover_snapshots = [] @@ -165,8 +182,9 @@ class SnapshotPage(Adw.BreakpointBin): # Connections 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) + self.open_button.connect("clicked", self.open_snapshots_folder, self.toast_overlay) + self.status_open_button.connect("clicked", self.open_snapshots_folder, self.no_snapshots_toast) # Apply - self.stack.add_child(self.loading_snapshots) + self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment"))) self.split_view.set_content(self.list_page) diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index a0d7442..384094d 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -4,52 +4,44 @@ using Adw 1; template $SnapshotsListPage : Adw.NavigationPage { title: _("Snapshots List"); Adw.ToastOverlay toast_overlay { - Stack stack { - Adw.ToolbarView toolbar_view { - [top] - Adw.HeaderBar { - [start] - Button open_button { - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open Snapshots Folder for this App"); - } + Adw.ToolbarView toolbar_view { + [top] + Adw.HeaderBar { + [start] + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open Snapshots Folder for this App"); } - ScrolledWindow { - Adw.Clamp { - margin-start: 12; - margin-end: 12; - margin-top: 12; - margin-bottom: 12; - ListBox listbox { - valign: start; - selection-mode: none; - styles ["boxed-list"] - Adw.PreferencesGroup { - Adw.ActionRow {title: "test";} - } - } - } - } - [bottom] - ActionBar { - [center] - Button new_button { - margin-top: 3; - margin-bottom: 3; - styles ["pill", "suggested-action"] - Adw.ButtonContent { - icon-name: "plus-large-symbolic"; - label: _("New Snapshot"); + } + ScrolledWindow { + Adw.Clamp { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + ListBox listbox { + valign: start; + selection-mode: none; + styles ["boxed-list"] + Adw.PreferencesGroup { + Adw.ActionRow {title: "test";} } } } } - Adw.ToolbarView loading_view { - [top] - Adw.HeaderBar { - show-title: false; + [bottom] + ActionBar { + [center] + Button new_button { + margin-top: 3; + margin-bottom: 3; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + icon-name: "plus-large-symbolic"; + label: _("New Snapshot"); + } } } } } -} \ No newline at end of file +} diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 3bff687..d9d14b9 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -11,9 +11,7 @@ class SnapshotsListPage(Adw.NavigationPage): __gtype_name__ = "SnapshotsListPage" gtc = Gtk.Template.Child - stack = gtc() toolbar_view = gtc() - loading_view = gtc() listbox = gtc() toast_overlay = gtc() open_button = gtc() @@ -30,14 +28,11 @@ class SnapshotsListPage(Adw.NavigationPage): for i, row in enumerate(self.snapshots_rows): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) - - self.stack.set_visible_child(self.toolbar_view) def set_snapshots(self, folder, title): if self.current_folder == folder: return - - self.stack.set_visible_child(self.loading_view) + self.current_folder = folder self.set_title(_("{} Snapshots").format(title)) self.snapshots_rows.clear() @@ -69,4 +64,3 @@ class SnapshotsListPage(Adw.NavigationPage): self.open_button.connect("clicked", self.open_snapshots_folder) # Apply - self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment"))) \ No newline at end of file From 764d9e22f66759338d60e1e896a33c29194e13d6 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Wed, 4 Sep 2024 23:29:01 -0400 Subject: [PATCH 171/332] Make Remote Page's loading view full-page --- src/remotes_page/remotes_page.blp | 254 +++++++++++++++--------------- src/remotes_page/remotes_page.py | 15 +- 2 files changed, 133 insertions(+), 136 deletions(-) diff --git a/src/remotes_page/remotes_page.blp b/src/remotes_page/remotes_page.blp index dbe50ff..c423124 100644 --- a/src/remotes_page/remotes_page.blp +++ b/src/remotes_page/remotes_page.blp @@ -3,149 +3,149 @@ using Adw 1; template $RemotesPage : Adw.NavigationPage { title: _("Manage Remotes"); - Adw.ToolbarView { - [top] - Adw.HeaderBar header_bar { - [start] - $SidebarButton {} - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Packages"); - } - } - [top] - Adw.Clamp { - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: template; - SearchEntry search_entry { - hexpand: true; - placeholder-text: _("Search Remotes"); + Adw.ToastOverlay toast_overlay { + Stack status_stack { + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} } } - } - Adw.ToastOverlay toast_overlay { - Stack stack { - // Adw.StatusPage loading_remotes { - // title: _("Loading Remotes"); - // description: _("This should only take a moment"); - // child: - // Spinner { - // spinning: true; - // } - // ; - // } - Adw.StatusPage no_results { - title: _("No Results Found"); - description: _("Try a different search"); - icon-name: "system-search-symbolic"; + Adw.ToolbarView main_view { + [top] + Adw.HeaderBar header_bar { + [start] + $SidebarButton {} + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } } - Adw.PreferencesPage content_page { - Adw.PreferencesGroup current_remotes_group { - title: _("Current Remotes"); - description: _("Remotes available on your system"); - header-suffix: - ToggleButton show_disabled_button { - valign: center; - styles ["flat"] - Adw.ButtonContent show_disabled_button_content { - icon-name: "eye-not-looking-symbolic"; - label: _("Show Disabled"); - } - } - ; - Adw.ActionRow none_visible { - styles ["warning"] - [child] - Box { - spacing: 3; - orientation: vertical; - Box { - halign: center; - Image { - valign: center; - margin-top: 7; - margin-end: 6; + [top] + Adw.Clamp { + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + key-capture-widget: template; + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search Remotes"); + } + } + } + Stack stack { + Adw.PreferencesPage content_page { + Adw.PreferencesGroup current_remotes_group { + title: _("Current Remotes"); + description: _("Remotes available on your system"); + header-suffix: + ToggleButton show_disabled_button { + valign: center; + styles ["flat"] + Adw.ButtonContent show_disabled_button_content { icon-name: "eye-not-looking-symbolic"; - } - Label { - margin-top: 7; - label: _("No Enabled Remotes"); - wrap: true; - styles ["heading"] + label: _("Show Disabled"); } } - Label { - label: _("You only have disabled remotes on this system"); - margin-start: 16; - margin-end: 16; - margin-bottom: 8; - justify: center; - halign: center; - wrap: true; - } - } - } - Adw.ActionRow no_remotes { - styles ["error"] - [child] - Box { - spacing: 3; - orientation: vertical; + ; + Adw.ActionRow none_visible { + styles ["warning"] + [child] Box { - halign: center; - Image { - valign: center; - margin-top: 7; - margin-end: 6; - icon-name: "error-symbolic"; + spacing: 3; + orientation: vertical; + Box { + halign: center; + Image { + valign: center; + margin-top: 7; + margin-end: 6; + icon-name: "eye-not-looking-symbolic"; + } + Label { + margin-top: 7; + label: _("No Enabled Remotes"); + wrap: true; + styles ["heading"] + } } Label { - margin-top: 7; - label: _("No Remotes Found"); + label: _("You only have disabled remotes on this system"); + margin-start: 16; + margin-end: 16; + margin-bottom: 8; + justify: center; + halign: center; wrap: true; - styles ["heading"] } } - Label { - label: _("Warehouse cannot see the current remotes or your system has no remotes added"); - margin-start: 16; - margin-end: 16; - margin-bottom: 8; - justify: center; - halign: center; - wrap: true; + } + Adw.ActionRow no_remotes { + styles ["error"] + [child] + Box { + spacing: 3; + orientation: vertical; + Box { + halign: center; + Image { + valign: center; + margin-top: 7; + margin-end: 6; + icon-name: "error-symbolic"; + } + Label { + margin-top: 7; + label: _("No Remotes Found"); + wrap: true; + styles ["heading"] + } + } + Label { + label: _("Warehouse cannot see the current remotes or your system has no remotes added"); + margin-start: 16; + margin-end: 16; + margin-bottom: 8; + justify: center; + halign: center; + wrap: true; + } + } + } + } + Adw.PreferencesGroup new_remotes_group { + visible: bind search_button.active inverted; + title: _("Add Popular Remotes"); + description: _("Add new remotes to get more software"); + } + Adw.PreferencesGroup other_remotes { + visible: bind search_button.active inverted; + title: _("Add Other Remotes"); + Adw.ActionRow file_remote_row { + activatable: true; + title: _("Add a Repo File"); + subtitle: _("Open a downloaded repo file to add"); + [suffix] + Image { + icon-name: "plus-large-symbolic"; + } + } + Adw.ActionRow custom_remote_row { + activatable: true; + title: _("Add a Custom Remote"); + subtitle: _("Manually enter new remote details"); + [suffix] + Image { + icon-name: "plus-large-symbolic"; } } } } - Adw.PreferencesGroup new_remotes_group { - visible: bind search_button.active inverted; - title: _("Add Popular Remotes"); - description: _("Add new remotes to get more software"); - } - Adw.PreferencesGroup other_remotes { - visible: bind search_button.active inverted; - title: _("Add Other Remotes"); - Adw.ActionRow file_remote_row { - activatable: true; - title: _("Add a Repo File"); - subtitle: _("Open a downloaded repo file to add"); - [suffix] - Image { - icon-name: "plus-large-symbolic"; - } - } - Adw.ActionRow custom_remote_row { - activatable: true; - title: _("Add a Custom Remote"); - subtitle: _("Manually enter new remote details"); - [suffix] - Image { - icon-name: "plus-large-symbolic"; - } - } + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; } } } diff --git a/src/remotes_page/remotes_page.py b/src/remotes_page/remotes_page.py index 9701511..2f752fc 100644 --- a/src/remotes_page/remotes_page.py +++ b/src/remotes_page/remotes_page.py @@ -84,8 +84,10 @@ class RemotesPage(Adw.NavigationPage): file_remote_row = gtc() custom_remote_row = gtc() none_visible = gtc() + status_stack = gtc() + loading_view = gtc() + main_view = gtc() - # Statuses no_results = gtc() no_remotes = gtc() content_page = gtc() @@ -98,9 +100,7 @@ class RemotesPage(Adw.NavigationPage): def start_loading(self): self.search_button.set_active(False) - self.search_button.set_sensitive(False) - self.search_entry.set_editable(False) - self.stack.set_visible_child(self.loading_remotes) + self.status_stack.set_visible_child(self.loading_view) self.total_disabled = 0 for row in self.current_remote_rows: self.current_remotes_group.remove(row) @@ -134,9 +134,7 @@ class RemotesPage(Adw.NavigationPage): else: self.no_remotes.set_visible(False) - GLib.idle_add(lambda *_: self.stack.set_visible_child(self.content_page)) - self.search_button.set_sensitive(True) - self.search_entry.set_editable(True) + GLib.idle_add(lambda *_: self.status_stack.set_visible_child(self.main_view)) def none_visible_handler(self): any_visible = False @@ -268,7 +266,6 @@ class RemotesPage(Adw.NavigationPage): # Extra Object Creation self.__class__.instance = self self.main_window = main_window - self.loading_remotes = LoadingStatus(_("Loading Remotes"), _("This should only take a moment")) self.search_bar.set_key_capture_widget(main_window) self.current_remote_rows = [] self.filter_setting = Gio.Settings.new("io.github.flattool.Warehouse.filter") @@ -281,7 +278,7 @@ class RemotesPage(Adw.NavigationPage): self.show_disabled_button.connect("toggled", self.show_disabled_handler) # Appply - self.stack.add_child(self.loading_remotes) + self.loading_view.set_content(LoadingStatus(_("Loading Remotes"), _("This should only take a moment"))) for item in self.new_remotes: row = NewRemoteRow(item) row.connect("activated", lambda *_, remote_info=item: AddRemoteDialog(main_window, self, remote_info).present(main_window)) From ea4c7a15c75842648c988129ee63802678586ce2 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 6 Sep 2024 14:54:06 -0400 Subject: [PATCH 172/332] Make loading status full page --- src/user_data_page/data_subpage.blp | 9 -- src/user_data_page/data_subpage.py | 4 +- src/user_data_page/user_data_page.blp | 169 ++++++++++++++------------ src/user_data_page/user_data_page.py | 19 ++- 4 files changed, 100 insertions(+), 101 deletions(-) diff --git a/src/user_data_page/data_subpage.blp b/src/user_data_page/data_subpage.blp index da892aa..965c49a 100644 --- a/src/user_data_page/data_subpage.blp +++ b/src/user_data_page/data_subpage.blp @@ -2,15 +2,6 @@ using Gtk 4.0; using Adw 1; template $DataSubpage : Stack { - // Adw.StatusPage loading_data { - // title: _("Loading Data"); - // description: _("This should only take a moment"); - // child: - // Spinner { - // spinning: true; - // } - // ; - // } Box content_box { orientation: vertical; Box label_box { diff --git a/src/user_data_page/data_subpage.py b/src/user_data_page/data_subpage.py index 9718667..2e3a317 100644 --- a/src/user_data_page/data_subpage.py +++ b/src/user_data_page/data_subpage.py @@ -68,6 +68,7 @@ class DataSubpage(Gtk.Stack): if self.sort_mode == "size": self.flow_box.invalidate_sort() self.set_visible_child(self.content_box) + GLib.idle_add(lambda *_: self.parent_page.status_stack.set_visible_child(self.parent_page.main_view)) def trash_handler(self, trashed_box): self.flow_box.remove(trashed_box) @@ -164,6 +165,7 @@ class DataSubpage(Gtk.Stack): self.set_visible_child(self.no_data) elif self.sort_mode != "size": self.set_visible_child(self.content_box) + self.parent_page.status_stack.set_visible_child(self.parent_page.main_view) def filter_func(self, box): search_text = self.parent_page.search_entry.get_text().lower() @@ -205,7 +207,6 @@ class DataSubpage(Gtk.Stack): # Extra Object Creation self.main_window = main_window self.parent_page = parent_page - self.loading_data = LoadingStatus(_("Loading User Data"), _("This should only take a moment")) # self.is_active = is_active self.sort_mode = "" self.sort_ascend = False @@ -221,7 +222,6 @@ class DataSubpage(Gtk.Stack): self.settings = Gio.Settings.new("io.github.flattool.Warehouse.data_page") # Apply - self.add_child(self.loading_data) self.flow_box.set_sort_func(self.sort_func) self.flow_box.set_filter_func(self.filter_func) diff --git a/src/user_data_page/user_data_page.blp b/src/user_data_page/user_data_page.blp index 8f5c11a..f0aefc0 100644 --- a/src/user_data_page/user_data_page.blp +++ b/src/user_data_page/user_data_page.blp @@ -18,94 +18,103 @@ template $UserDataPage : Adw.BreakpointBin { Adw.NavigationPage { title: _("User Data"); - Adw.ToolbarView { - [top] - Adw.HeaderBar header_bar { - title-widget: - Adw.ViewSwitcher { - stack: stack; - policy: wide; - } - ; - [start] - $SidebarButton {} - [start] - ToggleButton search_button { - icon-name: "system-search-symbolic"; - tooltip-text: _("Search User Data"); - } - [start] - Button open_button { - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open User Data Folder"); - } - [end] - MenuButton sort_button { - popover: sort_pop; - icon-name: "vertical-arrows-long-symbolic"; - tooltip-text: _("Sort User Data"); - } - [end] - ToggleButton select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select User Data"); + Stack status_stack { + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} } } - [top] - Adw.Clamp { - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - SearchEntry search_entry { - editable: false; + Adw.ToolbarView main_view { + [top] + Adw.HeaderBar header_bar { + title-widget: + Adw.ViewSwitcher { + stack: stack; + policy: wide; + } + ; + [start] + $SidebarButton {} + [start] + ToggleButton search_button { + icon-name: "system-search-symbolic"; + tooltip-text: _("Search User Data"); + } + [start] + Button open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data Folder"); + } + [end] + MenuButton sort_button { + popover: sort_pop; + icon-name: "vertical-arrows-long-symbolic"; + tooltip-text: _("Sort User Data"); + } + [end] + ToggleButton select_button { + icon-name: "selection-mode-symbolic"; + tooltip-text: _("Select User Data"); + } + } + [top] + Adw.Clamp { + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + SearchEntry search_entry { + editable: false; + hexpand: true; + placeholder-text: _("Search User Data"); + } + } + } + [bottom] + Revealer revealer { + reveal-child: bind select_button.active; + transition-type: slide_up; + [center] + Box bottom_bar { + styles ["toolbar"] hexpand: true; - placeholder-text: _("Search User Data"); - } - } - } - [bottom] - Revealer 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; + 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; + Button copy_button { + sensitive: false; + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; + } } - } - Button trash_button { - sensitive: false; - styles ["raised"] - Adw.ButtonContent { - icon-name: "user-trash-symbolic"; - label: _("Move to Trash"); - can-shrink: true; + Button trash_button { + sensitive: false; + styles ["raised"] + Adw.ButtonContent { + icon-name: "user-trash-symbolic"; + label: _("Move to Trash"); + can-shrink: true; + } } } } - } - [bottom] - Adw.ViewSwitcherBar switcher_bar { - stack: stack; - visible: false; - } - Adw.ToastOverlay toast_overlay { - Adw.ViewStack stack { + [bottom] + Adw.ViewSwitcherBar switcher_bar { + stack: stack; + visible: false; + } + Adw.ToastOverlay toast_overlay { + Adw.ViewStack stack { + } } } } diff --git a/src/user_data_page/user_data_page.py b/src/user_data_page/user_data_page.py index ef87121..1f87207 100644 --- a/src/user_data_page/user_data_page.py +++ b/src/user_data_page/user_data_page.py @@ -4,15 +4,18 @@ from .data_box import DataBox from .data_subpage import DataSubpage from .host_info import HostInfo from .sidebar_button import SidebarButton +from .loading_status import LoadingStatus import os, subprocess -import time - @Gtk.Template(resource_path="/io/github/flattool/Warehouse/user_data_page/user_data_page.ui") class UserDataPage(Adw.BreakpointBin): __gtype_name__ = 'UserDataPage' gtc = Gtk.Template.Child + bpt = gtc() + status_stack = gtc() + loading_view = gtc() + main_view = gtc() header_bar = gtc() switcher_bar = gtc() search_button = gtc() @@ -59,23 +62,18 @@ class UserDataPage(Adw.BreakpointBin): self.leftover_data.append(folder) def start_loading(self, *args): - self.header_bar.set_sensitive(False) + self.status_stack.set_visible_child(self.loading_view) self.search_button.set_active(False) - self.search_entry.set_editable(False) self.select_button.set_active(False) - self.adp.set_visible_child(self.adp.loading_data) - self.adp.size_label.set_label("Loading Size") + self.adp.size_label.set_label(_("Loading Size")) self.adp.spinner.set_visible(True) - self.ldp.set_visible_child(self.ldp.loading_data) - self.ldp.size_label.set_label("Loading Size") + self.ldp.size_label.set_label(_("Loading Size")) self.ldp.spinner.set_visible(True) def end_loading(self, *args): def callback(*args): self.adp.generate_list(self.data_flatpaks, self.active_data) self.ldp.generate_list([], self.leftover_data) - self.header_bar.set_sensitive(True) - self.search_entry.set_editable(True) Gio.Task.new(None, None, callback).run_in_thread(self.sort_data) @@ -247,5 +245,6 @@ class UserDataPage(Adw.BreakpointBin): self.bpt.connect("unapply", self.breakpoint_handler, False) # Apply again + self.loading_view.set_content(LoadingStatus(_("Loading User Data"), _("This should only take a moment"))) self.search_bar.set_key_capture_widget(main_window) self.load_sort_settings() From 3d674213bd4b91fc374785fadbe851fab0cf4b6e Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 7 Sep 2024 00:31:02 -0400 Subject: [PATCH 173/332] Make loading page fullscreen --- src/install_page/install_page.blp | 24 ++++++++++++++++++------ src/install_page/install_page.py | 7 +++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp index 72c9a42..127a6e1 100644 --- a/src/install_page/install_page.blp +++ b/src/install_page/install_page.blp @@ -17,12 +17,24 @@ template $InstallPage : Adw.BreakpointBin { } } - Adw.ToastOverlay toast_overlay { - Adw.NavigationSplitView split_view { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: $SelectPage select_page {}; - content: $PendingPage pending_page {}; + Adw.NavigationPage { + title: _("Install Packages"); + Adw.ToastOverlay toast_overlay { + Stack status_stack { + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + } + } + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: $SelectPage select_page {}; + content: $PendingPage pending_page {}; + } + } } } } diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index d023f51..40bc5be 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -2,6 +2,8 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .select_page import SelectPage from .pending_page import PendingPage +from .sidebar_button import SidebarButton +from .loading_status import LoadingStatus @Gtk.Template(resource_path="/io/github/flattool/Warehouse/install_page/install_page.ui") class InstallPage(Adw.BreakpointBin): @@ -12,6 +14,8 @@ class InstallPage(Adw.BreakpointBin): split_view = gtc() select_page = gtc() pending_page = gtc() + status_stack = gtc() + loading_view = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -23,10 +27,12 @@ class InstallPage(Adw.BreakpointBin): current_remote = None def start_loading(self): + self.status_stack.set_visible_child(self.loading_view) self.select_page.start_loading() def end_loading(self): self.select_page.end_loading() + self.status_stack.set_visible_child(self.split_view) def breakpoint_handler(self, bp, is_applied): self.select_page.results_page.action_bar.set_revealed(is_applied) @@ -48,3 +54,4 @@ class InstallPage(Adw.BreakpointBin): # ======== self.split_view.set_sidebar(self.select_page) # ======== self.split_view.set_content(self.pending_page) self.select_page.results_page.pending_page = self.pending_page + self.loading_view.set_content(LoadingStatus(_("Loading Installation Options"), _("This should only take a moment"))) From 8224fed342e5322b65bf87f999bb35145bcc8fc1 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 7 Sep 2024 00:34:46 -0400 Subject: [PATCH 174/332] Make the none pending page cover the toolbar views --- src/install_page/pending_page.blp | 45 +++++++++++++++++-------------- src/install_page/pending_page.py | 3 ++- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/install_page/pending_page.blp b/src/install_page/pending_page.blp index f0cf81f..2c0aae5 100644 --- a/src/install_page/pending_page.blp +++ b/src/install_page/pending_page.blp @@ -3,32 +3,37 @@ using Adw 1; template $PendingPage : Adw.NavigationPage { title: _("Pending Packages"); - Adw.ToolbarView { - [top] - Adw.HeaderBar { - } - Stack stack { - Adw.StatusPage none_pending { + Stack stack { + Adw.ToolbarView none_pending { + [top] + Adw.HeaderBar { + } + Adw.StatusPage { icon-name: "flatpak-symbolic"; title: _("Add Packages"); description: _("Packages queued to install will show up here"); } + } + Adw.ToolbarView main_view { + [top] + Adw.HeaderBar { + } Adw.PreferencesPage preferences_page { } - } - [bottom] - ActionBar pending_action_bar { - revealed: true; - [center] - Button install_button { - margin-top: 3; - margin-bottom: 3; - sensitive: bind pending_action_bar.revealed; - styles ["pill", "suggested-action"] - Adw.ButtonContent { - can-shrink: true; - icon-name: "arrow-pointing-at-line-down-symbolic"; - label: _("Install"); + [bottom] + ActionBar pending_action_bar { + revealed: true; + [center] + Button install_button { + margin-top: 3; + margin-bottom: 3; + sensitive: bind pending_action_bar.revealed; + styles ["pill", "suggested-action"] + Adw.ButtonContent { + can-shrink: true; + icon-name: "arrow-pointing-at-line-down-symbolic"; + label: _("Install"); + } } } } diff --git a/src/install_page/pending_page.py b/src/install_page/pending_page.py index 9bd74bf..b9e55c7 100644 --- a/src/install_page/pending_page.py +++ b/src/install_page/pending_page.py @@ -46,6 +46,7 @@ class PendingPage(Adw.NavigationPage): gtc = Gtk.Template.Child stack = gtc() + main_view = gtc() none_pending = gtc() preferences_page = gtc() @@ -65,7 +66,7 @@ class PendingPage(Adw.NavigationPage): self.preferences_page.add(group) added_row.connect("activated", self.remove_package_row, group) - self.stack.set_visible_child(self.preferences_page) + self.stack.set_visible_child(self.main_view) def remove_package_row(self, row, group): # row.origin_row.set_state(ResultRow.PackageState.NEW) From 11a7d9918d05c7cb03ac0fc2de3c08afe34c116b Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 7 Sep 2024 00:36:58 -0400 Subject: [PATCH 175/332] Use the LoadingStatus object --- src/install_page/results_page.blp | 18 +++++++++--------- src/install_page/results_page.py | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/install_page/results_page.blp b/src/install_page/results_page.blp index be58ac7..cebc00f 100644 --- a/src/install_page/results_page.blp +++ b/src/install_page/results_page.blp @@ -37,15 +37,15 @@ template $ResultsPage : Adw.NavigationPage { title: _("Too Many Results"); description: _("Try being more specific with your search"); } - Adw.StatusPage loading { - title: _("Searching"); - description: _("This should only take a moment"); - child: - Spinner { - spinning: true; - } - ; - } + // Adw.StatusPage loading { + // title: _("Searching"); + // description: _("This should only take a moment"); + // child: + // Spinner { + // spinning: true; + // } + // ; + // } ScrolledWindow results_view { Adw.Clamp { ListBox results_list { diff --git a/src/install_page/results_page.py b/src/install_page/results_page.py index f2ba938..7a7cde3 100644 --- a/src/install_page/results_page.py +++ b/src/install_page/results_page.py @@ -2,6 +2,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast from .result_row import ResultRow +from .loading_status import LoadingStatus import subprocess class AddedPackage: @@ -43,7 +44,6 @@ class ResultsPage(Adw.NavigationPage): stack = gtc() new_search = gtc() too_many = gtc() - loading = gtc() results_view= gtc() no_results = gtc() @@ -136,9 +136,11 @@ class ResultsPage(Adw.NavigationPage): self.installation = None self.packages = [] self.pending_page = None + self.loading = LoadingStatus(_("Searching"), _("This should only take a moment")) # Connections self.search_entry.connect("activate", self.on_search) self.search_apply_button.connect("clicked", self.on_search) # Apply + self.stack.add_child(self.loading) From 742bbc84447a7a54651ad563484272d98a2d6e37 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 7 Sep 2024 00:39:39 -0400 Subject: [PATCH 176/332] Add tooltip text for disabled remote rows --- src/remotes_page/remote_row.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 62005db..345742f 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -46,6 +46,7 @@ class RemoteRow(Adw.ActionRow): self.remove_css_class("warning") self.set_icon_name("") + self.set_tooltip_text("") self.remote.disabled = False self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Enabled remote"))) self.menu_listbox.get_row_at_index(2).set_visible(False) @@ -66,6 +67,7 @@ class RemoteRow(Adw.ActionRow): def callback(*args): self.add_css_class("warning") self.set_icon_name("error-symbolic") + self.set_tooltip_text(_("Remote is Disabled")) self.remote.disabled = True self.parent_page.toast_overlay.add_toast(Adw.Toast(title=_("Disabled remote"))) self.menu_listbox.get_row_at_index(2).set_visible(True) @@ -141,6 +143,7 @@ class RemoteRow(Adw.ActionRow): if self.remote.disabled: self.set_icon_name("error-symbolic") self.add_css_class("warning") + self.set_tooltip_text(_("Remote is Disabled")) def __init__(self, parent_page, installation, remote, **kwargs): super().__init__(**kwargs) @@ -159,4 +162,4 @@ class RemoteRow(Adw.ActionRow): # Connections self.menu_listbox.connect("row-activated", self.on_menu_action) - self.filter_button.connect("clicked", lambda *_: parent_page.filter_remote(self)) \ No newline at end of file + self.filter_button.connect("clicked", lambda *_: parent_page.filter_remote(self)) From f60ad5210f3ef6924d6066ba733ba0acab1e0a95 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 7 Sep 2024 15:36:11 -0400 Subject: [PATCH 177/332] Fix snapshots page not updating upon snapshot trashing --- src/snapshot_page/snapshot_page.py | 12 ++++++------ src/snapshot_page/snapshots_list_page.py | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 4f8d93e..4a2cac5 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -122,14 +122,14 @@ class SnapshotPage(Adw.BreakpointBin): else: self.leftover_box.set_visible(False) - def active_select_handler(self, listbox, row, should_show_content=True): + 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.info["id"], row.package.info["name"]) + self.list_page.set_snapshots(row.package.info["id"], row.package.info["name"], refresh) self.split_view.set_show_content(should_show_content) - def leftover_select_handler(self, listbox, row, should_show_content=True): + 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, row.name) + self.list_page.set_snapshots(row.folder, row.name, refresh) self.split_view.set_show_content(should_show_content) def start_loading(self): @@ -142,10 +142,10 @@ class SnapshotPage(Adw.BreakpointBin): def select_first_row(self): if row := self.active_listbox.get_row_at_index(0): self.active_listbox.select_row(row) - self.active_select_handler(None, row, False) + self.active_select_handler(None, row, False, True) elif row := self.leftover_listbox.get_row_at_index(0): self.leftover_listbox.select_row(row) - self.leftover_select_handler(None, row, False) + self.leftover_select_handler(None, row, False, True) def end_loading(self): def callback(*args): diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index d9d14b9..98a9d4d 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -5,7 +5,6 @@ from .snapshot_box import SnapshotBox from .loading_status import LoadingStatus import os - @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshots_list_page.ui") class SnapshotsListPage(Adw.NavigationPage): __gtype_name__ = "SnapshotsListPage" @@ -29,8 +28,8 @@ class SnapshotsListPage(Adw.NavigationPage): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) - def set_snapshots(self, folder, title): - if self.current_folder == folder: + def set_snapshots(self, folder, title, refresh=False): + if self.current_folder == folder and not refresh: return self.current_folder = folder From ad7ad2acc13c4a2cb8fa9533dbd5d384803963bd Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 7 Sep 2024 16:58:44 -0400 Subject: [PATCH 178/332] More work on snapshots page --- src/meson.build | 2 + src/snapshot_page/new_snapshot_dialog.blp | 72 +++++++++++++++++++++++ src/snapshot_page/new_snapshot_dialog.py | 64 ++++++++++++++++++++ src/snapshot_page/snapshot_page.py | 2 + src/warehouse.gresource.xml | 1 + 5 files changed, 141 insertions(+) create mode 100644 src/snapshot_page/new_snapshot_dialog.blp create mode 100644 src/snapshot_page/new_snapshot_dialog.py diff --git a/src/meson.build b/src/meson.build index 4b1ef9e..816b91b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ blueprints = custom_target('blueprints', 'snapshot_page/snapshot_page.blp', 'snapshot_page/snapshots_list_page.blp', 'snapshot_page/snapshot_box.blp', + 'snapshot_page/new_snapshot_dialog.blp', 'install_page/install_page.blp', 'install_page/result_row.blp', 'install_page/select_page.blp', @@ -88,6 +89,7 @@ warehouse_sources = [ 'snapshot_page/snapshot_page.py', 'snapshot_page/snapshots_list_page.py', 'snapshot_page/snapshot_box.py', + 'snapshot_page/new_snapshot_dialog.py', 'install_page/install_page.py', 'install_page/result_row.py', 'install_page/select_page.py', diff --git a/src/snapshot_page/new_snapshot_dialog.blp b/src/snapshot_page/new_snapshot_dialog.blp new file mode 100644 index 0000000..eed8004 --- /dev/null +++ b/src/snapshot_page/new_snapshot_dialog.blp @@ -0,0 +1,72 @@ +using Gtk 4.0; +using Adw 1; + +template $NewSnapshotDialog : Adw.Dialog { + follows-content-size: true; + width-request: 400; + Adw.NavigationView nav_view { + Adw.NavigationPage app_list_page { + title: _("Choose Applications"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + show-start-title-buttons: false; + show-end-title-buttons: false; + [start] + Button list_cancel_button { + label: _("Cancel"); + } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Apps"); + } + [end] + Button next_button { + // sensitive: false; + label: _("Next"); + styles ["suggested-action"] + } + } + [top] + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + key-capture-widget: template; + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search Apps"); + } + } + ScrolledWindow { + propagate-natural-height: true; + propagate-natural-width: true; + ListBox listbox { + valign: start; + margin-start: 12; + margin-bottom: 12; + margin-top: 12; + margin-end: 12; + selection-mode: none; + styles ["boxed-list"] + } + } + } + } + Adw.NavigationPage details_page { + title: _("New Snapshot"); + Adw.ToolbarView { + [top] + Adw.HeaderBar { + show-start-title-buttons: false; + show-end-title-buttons: false; + [end] + Button create_button { + // sensitive: false; + label: _("Snapshot"); + styles ["suggested-action"] + } + } + } + } + } +} diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py new file mode 100644 index 0000000..a978554 --- /dev/null +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -0,0 +1,64 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +from .loading_status import LoadingStatus +from .app_row import AppRow +import subprocess, os + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/new_snapshot_dialog.ui") +class NewSnapshotDialog(Adw.Dialog): + __gtype_name__ = "NewSnapshotDialog" + gtc = Gtk.Template.Child + + nav_view = gtc() + + app_list_page = gtc() + list_cancel_button = gtc() + next_button = gtc() + listbox = gtc() + + details_page = gtc() + create_button = gtc() + + def row_gesture_handler(self, row): + row.check_button.set_active(not row.check_button.get_active()) + + def row_select_handler(self, row): + if row.check_button.get_active(): + self.selected_rows.append(row) + else: + self.selected_rows.remove(row) + + if (total := len(self.selected_rows)) > 0: + self.app_list_page.set_title(_("{} Selected").format(total)) + else: + self.app_list_page.set_title(_("Choose Applications")) + + def generate_list(self, *args): + for package in HostInfo.flatpaks: + if package.is_runtime or not os.path.exists(package.data_path): + continue + row = AppRow(package, self.row_gesture_handler) + row.check_button.set_visible(True) + row.check_button.connect("toggled", lambda *_, row=row: self.row_select_handler(row)) + row.set_activatable(True) + row.set_activatable_widget(row.check_button) + GLib.idle_add(lambda *_, row=row: self.listbox.append(row)) + + def sort_func(self, row1, row2): + return row1.get_title().lower() > row2.get_title().lower() + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Extra Object Creations + self.rows = [] + self.selected_rows = [] + + # Connections + self.list_cancel_button.connect("clicked", lambda *_: self.close()) + self.next_button.connect("clicked", lambda *_: self.nav_view.push(self.details_page)) + + # Apply + Gio.Task.new(None, None, None).run_in_thread(self.generate_list) + self.listbox.set_sort_func(self.sort_func) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 4a2cac5..3a99cf3 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -5,6 +5,7 @@ from .app_row import AppRow from .snapshots_list_page import SnapshotsListPage from .sidebar_button import SidebarButton from .loading_status import LoadingStatus +from .new_snapshot_dialog import NewSnapshotDialog import os, subprocess class LeftoverSnapshotRow(Adw.ActionRow): @@ -184,6 +185,7 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_listbox.connect("row-activated", self.leftover_select_handler) self.open_button.connect("clicked", self.open_snapshots_folder, self.toast_overlay) self.status_open_button.connect("clicked", self.open_snapshots_folder, self.no_snapshots_toast) + self.new_button.connect("clicked", lambda *_: NewSnapshotDialog().present(HostInfo.main_window)) # Apply self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment"))) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index 1e78244..ba13402 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -20,6 +20,7 @@ snapshot_page/snapshot_page.ui snapshot_page/snapshots_list_page.ui snapshot_page/snapshot_box.ui + snapshot_page/new_snapshot_dialog.ui install_page/install_page.ui install_page/result_row.ui install_page/select_page.ui From 5a317f60f9bc835660021627f5236ea5df959c10 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 8 Sep 2024 13:54:54 -0400 Subject: [PATCH 179/332] Make the new snapshots dialog better --- src/snapshot_page/new_snapshot_dialog.blp | 106 +++++++++++++--------- src/snapshot_page/new_snapshot_dialog.py | 78 ++++++++++++---- src/snapshot_page/snapshot_box.py | 2 + src/snapshot_page/snapshot_page.py | 21 +++-- src/snapshot_page/snapshots_list_page.py | 13 ++- 5 files changed, 146 insertions(+), 74 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.blp b/src/snapshot_page/new_snapshot_dialog.blp index eed8004..1965e62 100644 --- a/src/snapshot_page/new_snapshot_dialog.blp +++ b/src/snapshot_page/new_snapshot_dialog.blp @@ -4,31 +4,31 @@ using Adw 1; template $NewSnapshotDialog : Adw.Dialog { follows-content-size: true; width-request: 400; - Adw.NavigationView nav_view { - Adw.NavigationPage app_list_page { - title: _("Choose Applications"); - Adw.ToolbarView { - [top] - Adw.HeaderBar { - show-start-title-buttons: false; - show-end-title-buttons: false; - [start] - Button list_cancel_button { - label: _("Cancel"); - } - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Apps"); - } - [end] - Button next_button { - // sensitive: false; - label: _("Next"); - styles ["suggested-action"] - } + Adw.NavigationPage nav_page { + title: "No Title Set"; + Adw.ToolbarView { + [top] + Adw.HeaderBar { + show-start-title-buttons: false; + show-end-title-buttons: false; + [start] + Button list_cancel_button { + label: _("Cancel"); } - [top] + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Apps"); + } + [end] + Button create_button { + sensitive: false; + label: _("Create"); + styles ["suggested-action"] + } + } + [top] + Adw.Clamp { SearchBar search_bar { search-mode-enabled: bind search_button.active bidirectional; key-capture-widget: template; @@ -37,35 +37,51 @@ template $NewSnapshotDialog : Adw.Dialog { placeholder-text: _("Search Apps"); } } + } + Adw.Clamp { ScrolledWindow { propagate-natural-height: true; propagate-natural-width: true; - ListBox listbox { - valign: start; - margin-start: 12; - margin-bottom: 12; - margin-top: 12; - margin-end: 12; - selection-mode: none; - styles ["boxed-list"] + Box { + orientation: vertical; + Adw.EntryRow name_entry { + title: "No Title Set"; + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + styles ["card"] + } + ListBox listbox { + valign: start; + margin-start: 12; + margin-end: 12; + // margin-top: 12; + margin-bottom: 12; + selection-mode: none; + styles ["boxed-list"] + } } } } - } - Adw.NavigationPage details_page { - title: _("New Snapshot"); - Adw.ToolbarView { - [top] - Adw.HeaderBar { - show-start-title-buttons: false; - show-end-title-buttons: false; - [end] - Button create_button { - // sensitive: false; - label: _("Snapshot"); - styles ["suggested-action"] + [bottom] + ActionBar { + revealed: bind search_button.visible; + [start] + Button select_all_button { + styles ["raised"] + Adw.ButtonContent { + label: _("Select All"); + icon-name: "selection-mode-symbolic"; } } + [end] + Label total_selected_label { + label: ""; + ellipsize: middle; + margin-end: 6; + visible: false; + } } } } diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index a978554..6cabc14 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -10,15 +10,15 @@ class NewSnapshotDialog(Adw.Dialog): __gtype_name__ = "NewSnapshotDialog" gtc = Gtk.Template.Child - nav_view = gtc() - - app_list_page = gtc() + nav_page = gtc() list_cancel_button = gtc() - next_button = gtc() - listbox = gtc() - - details_page = gtc() + search_button = gtc() create_button = gtc() + search_entry = gtc() + name_entry = gtc() + listbox = gtc() + select_all_button = gtc() + total_selected_label = gtc() def row_gesture_handler(self, row): row.check_button.set_active(not row.check_button.get_active()) @@ -28,11 +28,11 @@ class NewSnapshotDialog(Adw.Dialog): self.selected_rows.append(row) else: self.selected_rows.remove(row) - - if (total := len(self.selected_rows)) > 0: - self.app_list_page.set_title(_("{} Selected").format(total)) - else: - self.app_list_page.set_title(_("Choose Applications")) + + self.valid_checker() + total = len(self.selected_rows) + self.total_selected_label.set_label(_("{} Selected").format(total)) + self.total_selected_label.set_visible(total > 0) def generate_list(self, *args): for package in HostInfo.flatpaks: @@ -43,12 +43,43 @@ class NewSnapshotDialog(Adw.Dialog): row.check_button.connect("toggled", lambda *_, row=row: self.row_select_handler(row)) row.set_activatable(True) row.set_activatable_widget(row.check_button) - GLib.idle_add(lambda *_, row=row: self.listbox.append(row)) + self.listbox.append(row) def sort_func(self, row1, row2): - return row1.get_title().lower() > row2.get_title().lower() + return row1.package.info["name"].lower() > row2.package.info["name"].lower() + + def filter_func(self, row): + title = row.get_title().lower() + subtitle = row.get_subtitle().lower() + search = self.search_entry.get_text().lower() + return search in title or search in subtitle + + def on_close(self, *args): + self.search_button.set_active(False) + if len(self.selected_rows) > 1: + while len(self.selected_rows) > 0: + self.selected_rows[0].check_button.set_active(False) - def __init__(self, **kwargs): + def valid_checker(self): + valid = len(self.selected_rows) > 0 and len(self.name_entry.get_text().strip()) > 0 + self.create_button.set_sensitive(valid) + + def on_invalidate(self, search_entry): + self.listbox.invalidate_filter() + + def on_select_all(self, button): + i = 0 + while row := self.listbox.get_row_at_index(i): + i += 1 + row.check_button.set_active(True) + + def set_single(self, package): + row = AppRow(package) + row.set_activatable(False) + self.selected_rows.append(row) + self.listbox.append(row) + + def __init__(self, parent_page, package=None, **kwargs): super().__init__(**kwargs) # Extra Object Creations @@ -56,9 +87,22 @@ class NewSnapshotDialog(Adw.Dialog): self.selected_rows = [] # Connections + self.connect("closed", self.on_close) + self.search_entry.connect("search-changed", self.on_invalidate) self.list_cancel_button.connect("clicked", lambda *_: self.close()) - self.next_button.connect("clicked", lambda *_: self.nav_view.push(self.details_page)) + self.name_entry.connect("changed", lambda *_: self.valid_checker()) + self.select_all_button.connect("clicked", self.on_select_all) # Apply - Gio.Task.new(None, None, None).run_in_thread(self.generate_list) self.listbox.set_sort_func(self.sort_func) + self.listbox.set_filter_func(self.filter_func) + if not package is None: + self.search_entry.set_editable(False) + self.search_button.set_visible(False) + self.nav_page.set_title(_("New Snapshot")) + self.name_entry.set_title(_("Name this Snapshot")) + self.set_single(package) + else: + self.nav_page.set_title(_("New Snapshots")) + self.name_entry.set_title(_("Name these Snapshot")) + self.generate_list() diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 7d2ca93..f344278 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -53,6 +53,8 @@ class SnapshotBox(Gtk.Box): name = data['name'] if name != "": self.title.set_label(GLib.markup_escape_text(name)) + else: + self.title.set_label(_("No Name Set")) except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not write data"), str(e)).toast) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 3a99cf3..03bb40d 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -125,21 +125,14 @@ class SnapshotPage(Adw.BreakpointBin): 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.info["id"], row.package.info["name"], refresh) + 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, row.name, refresh) + self.list_page.set_snapshots(row.package, refresh) self.split_view.set_show_content(should_show_content) - def start_loading(self): - self.active_box.set_visible(True) - self.active_listbox.remove_all() - self.leftover_box.set_visible(True) - self.leftover_listbox.remove_all() - self.status_stack.set_visible_child(self.loading_view) - def select_first_row(self): if row := self.active_listbox.get_row_at_index(0): self.active_listbox.select_row(row) @@ -148,8 +141,16 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_listbox.select_row(row) self.leftover_select_handler(None, row, False, True) + def start_loading(self): + self.active_box.set_visible(True) + self.active_listbox.remove_all() + self.leftover_box.set_visible(True) + self.leftover_listbox.remove_all() + self.status_stack.set_visible_child(self.loading_view) + def end_loading(self): def callback(*args): + self.new_snapshot_dialog = NewSnapshotDialog(self) self.generate_active_list() self.generate_leftover_list() if (not self.active_box.get_visible()) and (not self.leftover_box.get_visible()): @@ -185,7 +186,7 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_listbox.connect("row-activated", self.leftover_select_handler) self.open_button.connect("clicked", self.open_snapshots_folder, self.toast_overlay) self.status_open_button.connect("clicked", self.open_snapshots_folder, self.no_snapshots_toast) - self.new_button.connect("clicked", lambda *_: NewSnapshotDialog().present(HostInfo.main_window)) + self.new_button.connect("clicked", lambda *_: self.new_snapshot_dialog.present(HostInfo.main_window)) # Apply self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment"))) diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 98a9d4d..3174439 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -3,6 +3,7 @@ from .host_info import HostInfo from .error_toast import ErrorToast from .snapshot_box import SnapshotBox from .loading_status import LoadingStatus +from .new_snapshot_dialog import NewSnapshotDialog import os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshots_list_page.ui") @@ -14,6 +15,7 @@ class SnapshotsListPage(Adw.NavigationPage): listbox = gtc() toast_overlay = gtc() open_button = gtc() + new_button = gtc() def thread(self, *args): for snapshot in os.listdir(folder := f"{self.snapshots_path}{self.current_folder}/"): @@ -28,12 +30,14 @@ class SnapshotsListPage(Adw.NavigationPage): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) - def set_snapshots(self, folder, title, refresh=False): + def set_snapshots(self, package, refresh=False): + folder = package.info["id"] if self.current_folder == folder and not refresh: return + self.current_package = package self.current_folder = folder - self.set_title(_("{} Snapshots").format(title)) + self.set_title(_("{} Snapshots").format(package.info["name"])) self.snapshots_rows.clear() self.listbox.remove_all() @@ -49,6 +53,9 @@ class SnapshotsListPage(Adw.NavigationPage): self.toast_overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) + + def on_new(self, button): + NewSnapshotDialog(self.parent_page, self.current_package).present(HostInfo.main_window) def __init__(self, parent_page, **kwargs): super().__init__(**kwargs) @@ -57,9 +64,11 @@ class SnapshotsListPage(Adw.NavigationPage): self.parent_page = parent_page self.snapshots_path = parent_page.snapshots_path self.current_folder = None + self.current_package = None self.snapshots_rows = [] # Connections self.open_button.connect("clicked", self.open_snapshots_folder) + self.new_button.connect("clicked", self.on_new) # Apply From 61c7bc028a480ebb093830e3860738194ef554eb Mon Sep 17 00:00:00 2001 From: Heliguy Date: Thu, 19 Sep 2024 01:47:43 -0400 Subject: [PATCH 180/332] Update to GNOME 47 runtime --- io.github.flattool.Warehouse.json | 2 +- src/gtk/loading_status.blp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/io.github.flattool.Warehouse.json b/io.github.flattool.Warehouse.json index fbd819e..88e477b 100644 --- a/io.github.flattool.Warehouse.json +++ b/io.github.flattool.Warehouse.json @@ -1,7 +1,7 @@ { "id" : "io.github.flattool.Warehouse", "runtime" : "org.gnome.Platform", - "runtime-version" : "46", + "runtime-version" : "47", "sdk" : "org.gnome.Sdk", "command" : "warehouse", "finish-args" : [ diff --git a/src/gtk/loading_status.blp b/src/gtk/loading_status.blp index 4303e86..7b78c18 100644 --- a/src/gtk/loading_status.blp +++ b/src/gtk/loading_status.blp @@ -11,9 +11,8 @@ template $LoadingStatus : ScrolledWindow { margin-end: 12; margin-top: 12; margin-bottom: 12; - Spinner { + Adw.Spinner { height-request: 30; - spinning: true; margin-bottom: 12; opacity: 0.5; } From 47cf9cc7d76f16328571a1e5c2326de6b2917a9f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 21 Sep 2024 20:41:10 -0400 Subject: [PATCH 181/332] Fix package description newlines messing up the app --- src/host_info.py | 47 +++++++++++++++++--------- src/properties_page/properties_page.py | 7 ++-- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/host_info.py b/src/host_info.py index c421801..49772d6 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -129,14 +129,29 @@ class Flatpak: cmd += f"--installation={installation} " cmd += self.info["ref"] - try: - output = subprocess.run(['flatpak-spawn', '--host', 'sh', '-c', cmd], text=True, capture_output=True).stdout + output = subprocess.run( + ['flatpak-spawn', '--host', 'sh', '-c', cmd], + text=True, capture_output=True + ).stdout except Exception as e: raise e lines = output.strip().split("\n") + cli_info["description"] = "" + first = lines.pop(0) + if " - " in first: + cli_info["description"] = first.split(" - ")[1] + + # Handle descriptions that contain newlines + while (line := lines.pop(0)) and not ":" in line: + if len(line) > 0: + cli_info["description"] += f" {line}" + for i, word in enumerate(lines): + if not ":" in word: + continue + word = word.strip().split(": ", 1) if len(word) < 2: continue @@ -150,23 +165,23 @@ class Flatpak: return cli_info def __init__(self, columns): - self.is_runtime = "runtime" in columns[12] self.info = { "name": columns[0], - "description": columns[1], - "id": columns[2], - "version": columns[3], - "branch": columns[4], - "arch": columns[5], - "origin": columns[6], - "ref": columns[8], - "installed_size": columns[11], - "options": columns[12], + "id": columns[1], + "version": columns[2], + "branch": columns[3], + "arch": columns[4], + "origin": columns[5], + "installation": columns[6], + "ref": columns[7], + "installed_size": columns[8], + "options": columns[9], } - self.data_path = f"{home}/.var/app/{columns[2]}" + self.is_runtime = "runtime" in self.info["options"] + self.data_path = f"{home}/.var/app/{self.info["id"]}" self.data_size = -1 self.cli_info = None - installation = columns[7] + installation = self.info["installation"] if len(i := installation.split(' ')) > 1: self.info["installation"] = i[1].replace("(", "").replace(")", "") else: @@ -319,8 +334,8 @@ class HostInfo: # Packages output = subprocess.run( - ['flatpak-spawn', '--host', - 'flatpak', 'list', '--columns=all'], + ['flatpak-spawn', '--host', 'flatpak', 'list', + '--columns=name,application,version,branch,arch,origin,installation,ref,size,options'], text=True, check=True, capture_output=True, ).stdout diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index 4957f8a..a1e72df 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -77,10 +77,6 @@ class PropertiesPage(Adw.NavigationPage): self.name.set_visible(False) self.inner_nav_page.set_title(_("Properties")) - pkg_description = package.info["description"] - self.description.set_visible(pkg_description != "") - self.description.set_label(pkg_description) - if package.icon_path: GLib.idle_add(lambda *_: self.app_icon.set_from_file(package.icon_path)) else: @@ -123,6 +119,9 @@ class PropertiesPage(Adw.NavigationPage): cli_info = None try: cli_info = package.get_cli_info() + pkg_description = package.cli_info["description"] + self.description.set_visible(pkg_description != "") + self.description.set_label(pkg_description) except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e)).toast) return From c320b52595655eee6fd65f197a270cddf84532aa Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 21 Sep 2024 22:13:15 -0400 Subject: [PATCH 182/332] Improve snapshots dialog --- src/snapshot_page/new_snapshot_dialog.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 6cabc14..ee97f64 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -56,9 +56,8 @@ class NewSnapshotDialog(Adw.Dialog): def on_close(self, *args): self.search_button.set_active(False) - if len(self.selected_rows) > 1: - while len(self.selected_rows) > 0: - self.selected_rows[0].check_button.set_active(False) + for row in self.selected_rows.copy(): + row.check_button.set_active(False) def valid_checker(self): valid = len(self.selected_rows) > 0 and len(self.name_entry.get_text().strip()) > 0 @@ -78,6 +77,10 @@ class NewSnapshotDialog(Adw.Dialog): row.set_activatable(False) self.selected_rows.append(row) self.listbox.append(row) + + def present(self, *args, **kwargs): + super().present(*args, **kwargs) + self.name_entry.grab_focus() def __init__(self, parent_page, package=None, **kwargs): super().__init__(**kwargs) @@ -104,5 +107,5 @@ class NewSnapshotDialog(Adw.Dialog): self.set_single(package) else: self.nav_page.set_title(_("New Snapshots")) - self.name_entry.set_title(_("Name these Snapshot")) + self.name_entry.set_title(_("Name these Snapshots")) self.generate_list() From fca66ff0bf43c746a3b809c16fdace19bc393881 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 21 Sep 2024 22:13:34 -0400 Subject: [PATCH 183/332] Improve the new snapshot dialog --- src/properties_page/properties_page.blp | 564 ++++++++++++------------ 1 file changed, 290 insertions(+), 274 deletions(-) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 5f63125..c44ab60 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -3,310 +3,326 @@ using Adw 1; template $PropertiesPage : Adw.NavigationPage { title: "Outer Page"; - Stack stack { - Adw.ToolbarView loading_tbv { - [top] - Adw.HeaderBar { - show-title: false; + Adw.BreakpointBin { + width-request: 1; + height-request: 1; + Adw.Breakpoint { + condition ("max-width: 450") + + setters { + header_box.orientation: vertical; } } - Adw.ToolbarView error_tbv { - [top] - Adw.HeaderBar { - show-title: false; + Stack stack { + Adw.ToolbarView loading_tbv { + [top] + Adw.HeaderBar { + show-title: false; + } } - Adw.StatusPage { - title: _("Properties Page Unavailable"); - description: _("Cannot show the properties page at this time"); - icon-name: "error-symbolic"; + Adw.ToolbarView error_tbv { + [top] + Adw.HeaderBar { + show-title: false; + } + Adw.StatusPage { + title: _("Properties Page Unavailable"); + description: _("Cannot show the properties page at this time"); + icon-name: "error-symbolic"; + } } - } - Adw.NavigationView nav_view { - Adw.NavigationPage inner_nav_page { - title: "Inner Page"; - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView { - [top] - Adw.HeaderBar header_bar { - show-title: false; - [end] - MenuButton more_menu_button { - icon-name: "view-more-symbolic"; - popover: more_menu; + Adw.NavigationView nav_view { + Adw.NavigationPage inner_nav_page { + title: "Inner Page"; + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar { + show-title: false; + [end] + MenuButton more_menu_button { + icon-name: "view-more-symbolic"; + popover: more_menu; + } } - } - ScrolledWindow scrolled_window { - Adw.Clamp { - Box { - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - orientation: vertical; - halign: fill; - - Image app_icon { - pixel-size: 100; - margin-top: 6; - margin-bottom: 18; - icon-name: "application-x-executable-symbolic"; - styles["icon-dropshadow"] - } - - Label name { - styles ["title-1"] - wrap: true; - wrap-mode: word_char; - justify: center; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - } - - Label description { - styles ["title-4"] - wrap: true; - wrap-mode: word_char; - justify: center; - margin-start: 6; - margin-end: 6; - } - + ScrolledWindow scrolled_window { + Adw.Clamp { Box { - spacing: 6; - homogeneous: true; - margin-top: 18; + margin-start: 12; + margin-end: 12; margin-bottom: 12; - halign: center; - Button open_app_button { - styles ["suggested-action", "pill"] - can-shrink: true; - label: _("Open"); - } - Button uninstall_button { - styles ["pill"] - can-shrink: true; - label: _("Uninstall"); - } - } - - Box eol_box { - margin-bottom: 12; - styles ["card"] - Label status_label { - margin-top: 6; - margin-bottom: 7; - margin-start: 6; - margin-end: 6; - label: _("This package is End Of Life, and will not recieve any security updates"); - styles ["heading", "error"] - halign: center; - hexpand: true; - wrap: true; - justify: center; - } - } - - Box information { orientation: vertical; - Adw.PreferencesGroup actions { - margin-bottom: 12; - Adw.ActionRow data_row { - title: _("User Data"); - styles["property"] + halign: fill; - [suffix] - Button open_data_button { - styles["flat"] - valign: center; - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open User Data"); + Box header_box { + Image app_icon { + pixel-size: 100; + margin-top: 6; + margin-bottom: 18; + icon-name: "application-x-executable-symbolic"; + styles["icon-dropshadow"] + } + + Box { + orientation: vertical; + Label name { + styles ["title-1"] + wrap: true; + wrap-mode: word_char; + justify: center; + margin-bottom: 12; + margin-start: 6; + margin-end: 6; } - [suffix] - Button trash_data_button { - styles["flat"] - valign: center; - icon-name: "user-trash-symbolic"; - tooltip-text: _("Trash User Data"); - } - - [suffix] - Spinner data_spinner { - spinning: true; + Label description { + styles ["title-4"] + wrap: true; + wrap-mode: word_char; + justify: center; + margin-start: 6; + margin-end: 6; } } - Adw.ExpanderRow version_row { - title: _("Version"); - styles ["property"] - [suffix] - Label mask_label { - label: _("Updates Disabled"); - styles["warning"] + } + + Box { + spacing: 6; + homogeneous: true; + margin-top: 18; + margin-bottom: 12; + halign: center; + Button open_app_button { + styles ["suggested-action", "pill"] + can-shrink: true; + label: _("Open"); + } + Button uninstall_button { + styles ["pill"] + can-shrink: true; + label: _("Uninstall"); + } + } + + Box eol_box { + margin-bottom: 12; + styles ["card"] + Label status_label { + margin-top: 6; + margin-bottom: 7; + margin-start: 6; + margin-end: 6; + label: _("This package is End Of Life, and will not recieve any security updates"); + styles ["heading", "error"] + halign: center; + hexpand: true; + wrap: true; + justify: center; + } + } + + Box information { + orientation: vertical; + Adw.PreferencesGroup actions { + margin-bottom: 12; + Adw.ActionRow data_row { + title: _("User Data"); + styles["property"] + + [suffix] + Button open_data_button { + styles["flat"] + valign: center; + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data"); + } + + [suffix] + Button trash_data_button { + styles["flat"] + valign: center; + icon-name: "user-trash-symbolic"; + tooltip-text: _("Trash User Data"); + } + + [suffix] + Spinner data_spinner { + spinning: true; + } } - Adw.ActionRow mask_row { - title: _("Disable Updates"); - subtitle: _("Mask this package so it's never updated"); + Adw.ExpanderRow version_row { + title: _("Version"); + styles ["property"] + [suffix] + Label mask_label { + label: _("Updates Disabled"); + styles["warning"] + } + Adw.ActionRow mask_row { + title: _("Disable Updates"); + subtitle: _("Mask this package so it's never updated"); + activatable: true; + Gtk.Switch mask_switch { + valign: center; + can-focus: false; + can-target: false; + } + } + Adw.ActionRow change_version_row { + title: _("Change Version"); + subtitle: _("Upgrade or downgrade this package"); + activatable: true; + Image { + icon-name: "right-large-symbolic"; + } + } + } + Adw.ActionRow installed_size_row { + styles ["property"] + title: _("Installed Size"); activatable: true; - Gtk.Switch mask_switch { + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow runtime_row { + styles ["property"] + title: _("Runtime"); + activatable: true; + Image eol_package_package_status_icon { + icon-name: "error-symbolic"; + tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + margin-end: 6; + styles["error"] + } + Image { + icon-name: "right-large-symbolic"; + } + } + Adw.ActionRow pin_row { + title: _("Disable Automactic Removal"); + subtitle: _("Pin this runtime to keep it installed"); + activatable: true; + Gtk.Switch pin_switch { valign: center; can-focus: false; can-target: false; } } - Adw.ActionRow change_version_row { - title: _("Change Version"); - subtitle: _("Upgrade or downgrade this package"); + } + Adw.PreferencesGroup package_info { + margin-bottom: 12; + title: _("Package Information"); + Adw.ActionRow id_row { + styles ["property"] + title: _("Application ID"); activatable: true; Image { - icon-name: "right-large-symbolic"; + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow ref_row { + styles ["property"] + title: "Ref"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow arch_row { + styles ["property"] + title: _("Architecture"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow branch_row { + styles ["property"] + title: _("Branch"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow license_row { + styles ["property"] + title: _("License"); + activatable: true; + Image { + icon-name: "copy-symbolic"; } } } - Adw.ActionRow installed_size_row { - styles ["property"] - title: _("Installed Size"); - activatable: true; - Image { - icon-name: "copy-symbolic"; + Adw.PreferencesGroup remote_info { + margin-bottom: 12; + title: _("Installation Information"); + Adw.ActionRow sdk_row { + styles ["property"] + title: "SDK"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow origin_row { + styles ["property"] + title: _("Origin"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow collection_row { + styles ["property"] + title: _("Collection"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow installation_row { + styles ["property"] + title: _("Installation"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } } - Adw.ActionRow runtime_row { - styles ["property"] - title: _("Runtime"); - activatable: true; - Image eol_package_package_status_icon { - icon-name: "error-symbolic"; - tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); - margin-end: 6; - styles["error"] + Adw.PreferencesGroup commit_info { + title: _("Commit Information"); + Adw.ActionRow commit_row { + styles ["property"] + title: "Commit"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } - Image { - icon-name: "right-large-symbolic"; + Adw.ActionRow parent_row { + styles ["property"] + title: _("Parent"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } - } - Adw.ActionRow pin_row { - title: _("Disable Automactic Removal"); - subtitle: _("Pin this runtime to keep it installed"); - activatable: true; - Gtk.Switch pin_switch { - valign: center; - can-focus: false; - can-target: false; + Adw.ActionRow subject_row { + styles ["property"] + title: _("Subject"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } - } - } - Adw.PreferencesGroup package_info { - margin-bottom: 12; - title: _("Package Information"); - Adw.ActionRow id_row { - styles ["property"] - title: _("Application ID"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow ref_row { - styles ["property"] - title: "Ref"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow arch_row { - styles ["property"] - title: _("Architecture"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow branch_row { - styles ["property"] - title: _("Branch"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow license_row { - styles ["property"] - title: _("License"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - } - Adw.PreferencesGroup remote_info { - margin-bottom: 12; - title: _("Installation Information"); - Adw.ActionRow sdk_row { - styles ["property"] - title: "SDK"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow origin_row { - styles ["property"] - title: _("Origin"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow collection_row { - styles ["property"] - title: _("Collection"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow installation_row { - styles ["property"] - title: _("Installation"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - } - Adw.PreferencesGroup commit_info { - title: _("Commit Information"); - Adw.ActionRow commit_row { - styles ["property"] - title: "Commit"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow parent_row { - styles ["property"] - title: _("Parent"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow subject_row { - styles ["property"] - title: _("Subject"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow date_row { - styles ["property"] - title: _("Date"); - activatable: true; - Image { - icon-name: "copy-symbolic"; + Adw.ActionRow date_row { + styles ["property"] + title: _("Date"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } } } } From 7c099987a7495ea93100b049828f0ba419cbda8b Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 21 Sep 2024 23:02:11 -0400 Subject: [PATCH 184/332] Set wide header label in wide windows --- src/properties_page/properties_page.blp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index c44ab60..8d89064 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -7,10 +7,21 @@ template $PropertiesPage : Adw.NavigationPage { width-request: 1; height-request: 1; Adw.Breakpoint { - condition ("max-width: 450") + condition ("min-width: 450") setters { - header_box.orientation: vertical; + header_box.orientation: horizontal; + header_box.hexpand: true; + header_box.halign: center; + label_box.margin-start: 16; + name.justify: left; + name.natural-wrap-mode: none; + name.hexpand: true; + name.halign: start; + description.justify: left; + description.natural-wrap-mode: none; + description.hexpand: true; + description.halign: start; } } Stack stack { @@ -55,6 +66,7 @@ template $PropertiesPage : Adw.NavigationPage { halign: fill; Box header_box { + orientation: vertical; Image app_icon { pixel-size: 100; margin-top: 6; @@ -63,8 +75,9 @@ template $PropertiesPage : Adw.NavigationPage { styles["icon-dropshadow"] } - Box { + Box label_box { orientation: vertical; + valign: center; Label name { styles ["title-1"] wrap: true; From 9d992df2fa48b3a3f0dd47da48d6f12485862397 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 21 Sep 2024 23:59:36 -0400 Subject: [PATCH 185/332] Undo non working wide changes --- src/properties_page/properties_page.blp | 573 +++++++++++------------- 1 file changed, 272 insertions(+), 301 deletions(-) diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index 8d89064..f800904 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -3,339 +3,310 @@ using Adw 1; template $PropertiesPage : Adw.NavigationPage { title: "Outer Page"; - Adw.BreakpointBin { - width-request: 1; - height-request: 1; - Adw.Breakpoint { - condition ("min-width: 450") - - setters { - header_box.orientation: horizontal; - header_box.hexpand: true; - header_box.halign: center; - label_box.margin-start: 16; - name.justify: left; - name.natural-wrap-mode: none; - name.hexpand: true; - name.halign: start; - description.justify: left; - description.natural-wrap-mode: none; - description.hexpand: true; - description.halign: start; + Stack stack { + Adw.ToolbarView loading_tbv { + [top] + Adw.HeaderBar { + show-title: false; } } - Stack stack { - Adw.ToolbarView loading_tbv { - [top] - Adw.HeaderBar { - show-title: false; - } + Adw.ToolbarView error_tbv { + [top] + Adw.HeaderBar { + show-title: false; } - Adw.ToolbarView error_tbv { - [top] - Adw.HeaderBar { - show-title: false; - } - Adw.StatusPage { - title: _("Properties Page Unavailable"); - description: _("Cannot show the properties page at this time"); - icon-name: "error-symbolic"; - } + Adw.StatusPage { + title: _("Properties Page Unavailable"); + description: _("Cannot show the properties page at this time"); + icon-name: "error-symbolic"; } - Adw.NavigationView nav_view { - Adw.NavigationPage inner_nav_page { - title: "Inner Page"; - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView { - [top] - Adw.HeaderBar header_bar { - show-title: false; - [end] - MenuButton more_menu_button { - icon-name: "view-more-symbolic"; - popover: more_menu; - } + } + Adw.NavigationView nav_view { + Adw.NavigationPage inner_nav_page { + title: "Inner Page"; + Adw.ToastOverlay toast_overlay { + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar { + show-title: false; + [end] + MenuButton more_menu_button { + icon-name: "view-more-symbolic"; + popover: more_menu; } - ScrolledWindow scrolled_window { - Adw.Clamp { - Box { - margin-start: 12; - margin-end: 12; + } + ScrolledWindow scrolled_window { + Adw.Clamp { + Box { + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + orientation: vertical; + halign: fill; + + Image app_icon { + pixel-size: 100; + margin-top: 6; + margin-bottom: 18; + icon-name: "application-x-executable-symbolic"; + styles["icon-dropshadow"] + } + + Label name { + styles ["title-1"] + wrap: true; + wrap-mode: word_char; + justify: center; margin-bottom: 12; - orientation: vertical; - halign: fill; + margin-start: 6; + margin-end: 6; + } - Box header_box { - orientation: vertical; - Image app_icon { - pixel-size: 100; - margin-top: 6; - margin-bottom: 18; - icon-name: "application-x-executable-symbolic"; - styles["icon-dropshadow"] - } + Label description { + styles ["title-4"] + wrap: true; + wrap-mode: word_char; + justify: center; + margin-start: 6; + margin-end: 6; + } - Box label_box { - orientation: vertical; - valign: center; - Label name { - styles ["title-1"] - wrap: true; - wrap-mode: word_char; - justify: center; - margin-bottom: 12; - margin-start: 6; - margin-end: 6; - } - - Label description { - styles ["title-4"] - wrap: true; - wrap-mode: word_char; - justify: center; - margin-start: 6; - margin-end: 6; - } - } + Box { + spacing: 6; + homogeneous: true; + margin-top: 18; + margin-bottom: 12; + halign: center; + Button open_app_button { + styles ["suggested-action", "pill"] + can-shrink: true; + label: _("Open"); } + Button uninstall_button { + styles ["pill"] + can-shrink: true; + label: _("Uninstall"); + } + } - Box { - spacing: 6; - homogeneous: true; - margin-top: 18; - margin-bottom: 12; + Box eol_box { + margin-bottom: 12; + styles ["card"] + Label status_label { + margin-top: 6; + margin-bottom: 7; + margin-start: 6; + margin-end: 6; + label: _("This package is End Of Life, and will not recieve any security updates"); + styles ["heading", "error"] halign: center; - Button open_app_button { - styles ["suggested-action", "pill"] - can-shrink: true; - label: _("Open"); - } - Button uninstall_button { - styles ["pill"] - can-shrink: true; - label: _("Uninstall"); - } + hexpand: true; + wrap: true; + justify: center; } + } - Box eol_box { + Box information { + orientation: vertical; + Adw.PreferencesGroup actions { margin-bottom: 12; - styles ["card"] - Label status_label { - margin-top: 6; - margin-bottom: 7; - margin-start: 6; - margin-end: 6; - label: _("This package is End Of Life, and will not recieve any security updates"); - styles ["heading", "error"] - halign: center; - hexpand: true; - wrap: true; - justify: center; + Adw.ActionRow data_row { + title: _("User Data"); + styles["property"] + + [suffix] + Button open_data_button { + styles["flat"] + valign: center; + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open User Data"); + } + + [suffix] + Button trash_data_button { + styles["flat"] + valign: center; + icon-name: "user-trash-symbolic"; + tooltip-text: _("Trash User Data"); + } + + [suffix] + Spinner data_spinner { + spinning: true; + } } - } - - Box information { - orientation: vertical; - Adw.PreferencesGroup actions { - margin-bottom: 12; - Adw.ActionRow data_row { - title: _("User Data"); - styles["property"] - - [suffix] - Button open_data_button { - styles["flat"] - valign: center; - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open User Data"); - } - - [suffix] - Button trash_data_button { - styles["flat"] - valign: center; - icon-name: "user-trash-symbolic"; - tooltip-text: _("Trash User Data"); - } - - [suffix] - Spinner data_spinner { - spinning: true; - } + Adw.ExpanderRow version_row { + title: _("Version"); + styles ["property"] + [suffix] + Label mask_label { + label: _("Updates Disabled"); + styles["warning"] } - Adw.ExpanderRow version_row { - title: _("Version"); - styles ["property"] - [suffix] - Label mask_label { - label: _("Updates Disabled"); - styles["warning"] - } - Adw.ActionRow mask_row { - title: _("Disable Updates"); - subtitle: _("Mask this package so it's never updated"); - activatable: true; - Gtk.Switch mask_switch { - valign: center; - can-focus: false; - can-target: false; - } - } - Adw.ActionRow change_version_row { - title: _("Change Version"); - subtitle: _("Upgrade or downgrade this package"); - activatable: true; - Image { - icon-name: "right-large-symbolic"; - } - } - } - Adw.ActionRow installed_size_row { - styles ["property"] - title: _("Installed Size"); + Adw.ActionRow mask_row { + title: _("Disable Updates"); + subtitle: _("Mask this package so it's never updated"); activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow runtime_row { - styles ["property"] - title: _("Runtime"); - activatable: true; - Image eol_package_package_status_icon { - icon-name: "error-symbolic"; - tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); - margin-end: 6; - styles["error"] - } - Image { - icon-name: "right-large-symbolic"; - } - } - Adw.ActionRow pin_row { - title: _("Disable Automactic Removal"); - subtitle: _("Pin this runtime to keep it installed"); - activatable: true; - Gtk.Switch pin_switch { + Gtk.Switch mask_switch { valign: center; can-focus: false; can-target: false; } } - } - Adw.PreferencesGroup package_info { - margin-bottom: 12; - title: _("Package Information"); - Adw.ActionRow id_row { - styles ["property"] - title: _("Application ID"); + Adw.ActionRow change_version_row { + title: _("Change Version"); + subtitle: _("Upgrade or downgrade this package"); activatable: true; Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow ref_row { - styles ["property"] - title: "Ref"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow arch_row { - styles ["property"] - title: _("Architecture"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow branch_row { - styles ["property"] - title: _("Branch"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow license_row { - styles ["property"] - title: _("License"); - activatable: true; - Image { - icon-name: "copy-symbolic"; + icon-name: "right-large-symbolic"; } } } - Adw.PreferencesGroup remote_info { - margin-bottom: 12; - title: _("Installation Information"); - Adw.ActionRow sdk_row { - styles ["property"] - title: "SDK"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow origin_row { - styles ["property"] - title: _("Origin"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow collection_row { - styles ["property"] - title: _("Collection"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } - } - Adw.ActionRow installation_row { - styles ["property"] - title: _("Installation"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } + Adw.ActionRow installed_size_row { + styles ["property"] + title: _("Installed Size"); + activatable: true; + Image { + icon-name: "copy-symbolic"; } } - Adw.PreferencesGroup commit_info { - title: _("Commit Information"); - Adw.ActionRow commit_row { - styles ["property"] - title: "Commit"; - activatable: true; - Image { - icon-name: "copy-symbolic"; - } + Adw.ActionRow runtime_row { + styles ["property"] + title: _("Runtime"); + activatable: true; + Image eol_package_package_status_icon { + icon-name: "error-symbolic"; + tooltip-text: _("This package is End Of Life, and will not recieve any security updates"); + margin-end: 6; + styles["error"] } - Adw.ActionRow parent_row { - styles ["property"] - title: _("Parent"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } + Image { + icon-name: "right-large-symbolic"; } - Adw.ActionRow subject_row { - styles ["property"] - title: _("Subject"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } + } + Adw.ActionRow pin_row { + title: _("Disable Automactic Removal"); + subtitle: _("Pin this runtime to keep it installed"); + activatable: true; + Gtk.Switch pin_switch { + valign: center; + can-focus: false; + can-target: false; } - Adw.ActionRow date_row { - styles ["property"] - title: _("Date"); - activatable: true; - Image { - icon-name: "copy-symbolic"; - } + } + } + Adw.PreferencesGroup package_info { + margin-bottom: 12; + title: _("Package Information"); + Adw.ActionRow id_row { + styles ["property"] + title: _("Application ID"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow ref_row { + styles ["property"] + title: "Ref"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow arch_row { + styles ["property"] + title: _("Architecture"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow branch_row { + styles ["property"] + title: _("Branch"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow license_row { + styles ["property"] + title: _("License"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + } + Adw.PreferencesGroup remote_info { + margin-bottom: 12; + title: _("Installation Information"); + Adw.ActionRow sdk_row { + styles ["property"] + title: "SDK"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow origin_row { + styles ["property"] + title: _("Origin"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow collection_row { + styles ["property"] + title: _("Collection"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow installation_row { + styles ["property"] + title: _("Installation"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + } + Adw.PreferencesGroup commit_info { + title: _("Commit Information"); + Adw.ActionRow commit_row { + styles ["property"] + title: "Commit"; + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow parent_row { + styles ["property"] + title: _("Parent"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow subject_row { + styles ["property"] + title: _("Subject"); + activatable: true; + Image { + icon-name: "copy-symbolic"; + } + } + Adw.ActionRow date_row { + styles ["property"] + title: _("Date"); + activatable: true; + Image { + icon-name: "copy-symbolic"; } } } From 549778a99bf1a5ba5d1dc163d321838cd4f417b3 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 22 Sep 2024 01:45:38 -0400 Subject: [PATCH 186/332] Use bottom sheet and multi layout view --- src/install_page/install_page.blp | 90 ++++++++++++++++++++++++++----- src/install_page/install_page.py | 3 +- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp index 127a6e1..59cdb68 100644 --- a/src/install_page/install_page.blp +++ b/src/install_page/install_page.blp @@ -9,30 +9,92 @@ template $InstallPage : Adw.BreakpointBin { condition ("max-width: 600") setters { - split_view.collapsed: true; - split_view.show-content: false; + // split_view.collapsed: true; + // split_view.show-content: false; // results_action_bar.visible: true; // pending_action_bar.visible: true; // pending_page.child: null; + multi_view.layout: skinny; } } Adw.NavigationPage { title: _("Install Packages"); Adw.ToastOverlay toast_overlay { - Stack status_stack { - Adw.ToolbarView loading_view { - [top] - Adw.HeaderBar { - [start] - $SidebarButton {} + Stack { + // Adw.MultiLayoutView { + // Adw.Layout { + // name: "test1"; + // Adw.LayoutSlot { + // id: "secondary"; + // } + // } + // [primary] + // Adw.StatusPage { + // title: "one"; + // } + // [secondary] + // Adw.StatusPage { + // title: "two"; + // } + // } + Stack status_stack { + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + } + } + Adw.MultiLayoutView multi_view { + Adw.Layout wide { + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage { + title: _("Select Source"); + Adw.LayoutSlot { + id: "select_page"; + } + } + ; + content: + Adw.NavigationPage { + title: _("Pending Packages"); + Adw.LayoutSlot { + id: "pending_page"; + } + } + ; + } + } + Adw.Layout skinny { + Adw.BottomSheet bottom_sheet { + [content] + Box { + margin-bottom: bind bottom_sheet.bottom-bar-height; + Adw.LayoutSlot { + id: "select_page"; + } + } + [sheet] + Adw.LayoutSlot { + id: "pending_page"; + } + [bottom-bar] + Label { + label: _("Pending Packages"); + margin-top: 12; + margin-bottom: 12; + } + } + } + [select_page] + $SelectPage select_page {} + [pending_page] + $PendingPage pending_page {} } - } - Adw.NavigationSplitView split_view { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: $SelectPage select_page {}; - content: $PendingPage pending_page {}; } } } diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index 40bc5be..a114426 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -12,6 +12,7 @@ class InstallPage(Adw.BreakpointBin): break_point = gtc() split_view = gtc() + multi_view = gtc() select_page = gtc() pending_page = gtc() status_stack = gtc() @@ -32,7 +33,7 @@ class InstallPage(Adw.BreakpointBin): def end_loading(self): self.select_page.end_loading() - self.status_stack.set_visible_child(self.split_view) + self.status_stack.set_visible_child(self.multi_view) def breakpoint_handler(self, bp, is_applied): self.select_page.results_page.action_bar.set_revealed(is_applied) From 9b6405a73c27d7b6e28e426d691349a0ac66244a Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 22 Sep 2024 14:09:55 -0400 Subject: [PATCH 187/332] Clean up code and remove unneeded button --- src/install_page/install_page.blp | 115 ++++++++++++------------------ src/install_page/install_page.py | 6 -- src/install_page/results_page.blp | 13 ---- src/install_page/results_page.py | 2 - 4 files changed, 46 insertions(+), 90 deletions(-) diff --git a/src/install_page/install_page.blp b/src/install_page/install_page.blp index 59cdb68..54fe668 100644 --- a/src/install_page/install_page.blp +++ b/src/install_page/install_page.blp @@ -9,11 +9,6 @@ template $InstallPage : Adw.BreakpointBin { condition ("max-width: 600") setters { - // split_view.collapsed: true; - // split_view.show-content: false; - // results_action_bar.visible: true; - // pending_action_bar.visible: true; - // pending_page.child: null; multi_view.layout: skinny; } } @@ -21,80 +16,62 @@ template $InstallPage : Adw.BreakpointBin { Adw.NavigationPage { title: _("Install Packages"); Adw.ToastOverlay toast_overlay { - Stack { - // Adw.MultiLayoutView { - // Adw.Layout { - // name: "test1"; - // Adw.LayoutSlot { - // id: "secondary"; - // } - // } - // [primary] - // Adw.StatusPage { - // title: "one"; - // } - // [secondary] - // Adw.StatusPage { - // title: "two"; - // } - // } - Stack status_stack { - Adw.ToolbarView loading_view { - [top] - Adw.HeaderBar { - [start] - $SidebarButton {} - } + Stack status_stack { + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} } - Adw.MultiLayoutView multi_view { - Adw.Layout wide { - Adw.NavigationSplitView split_view { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage { - title: _("Select Source"); - Adw.LayoutSlot { - id: "select_page"; - } - } - ; - content: - Adw.NavigationPage { - title: _("Pending Packages"); - Adw.LayoutSlot { - id: "pending_page"; - } - } - ; - } - } - Adw.Layout skinny { - Adw.BottomSheet bottom_sheet { - [content] - Box { - margin-bottom: bind bottom_sheet.bottom-bar-height; + } + Adw.MultiLayoutView multi_view { + Adw.Layout wide { + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage { + title: _("Select Source"); Adw.LayoutSlot { id: "select_page"; } } - [sheet] - Adw.LayoutSlot { - id: "pending_page"; + ; + content: + Adw.NavigationPage { + title: _("Pending Packages"); + Adw.LayoutSlot { + id: "pending_page"; + } } - [bottom-bar] - Label { - label: _("Pending Packages"); - margin-top: 12; - margin-bottom: 12; + ; + } + } + Adw.Layout skinny { + Adw.BottomSheet bottom_sheet { + [content] + Box { + margin-bottom: bind bottom_sheet.bottom-bar-height; + Adw.LayoutSlot { + id: "select_page"; } } + [sheet] + Adw.LayoutSlot { + id: "pending_page"; + } + [bottom-bar] + Label { + label: _("Pending Packages"); + margin-top: 12; + margin-bottom: 12; + } } - [select_page] - $SelectPage select_page {} - [pending_page] - $PendingPage pending_page {} } + [select_page] + $SelectPage select_page {} + [pending_page] + $PendingPage pending_page {} } } } diff --git a/src/install_page/install_page.py b/src/install_page/install_page.py index a114426..5c03638 100644 --- a/src/install_page/install_page.py +++ b/src/install_page/install_page.py @@ -35,9 +35,6 @@ class InstallPage(Adw.BreakpointBin): self.select_page.end_loading() self.status_stack.set_visible_child(self.multi_view) - def breakpoint_handler(self, bp, is_applied): - self.select_page.results_page.action_bar.set_revealed(is_applied) - def __init__(self, main_window, **kwargs): super().__init__(**kwargs) self.instance = self @@ -47,9 +44,6 @@ class InstallPage(Adw.BreakpointBin): # ======== self.pending_page = PendingPage() # Connections - self.break_point.connect("apply", self.breakpoint_handler, True) - self.break_point.connect("unapply", self.breakpoint_handler, False) - self.select_page.results_page.review_button.connect("clicked", lambda *_: self.split_view.set_show_content(True)) # Apply # ======== self.split_view.set_sidebar(self.select_page) diff --git a/src/install_page/results_page.blp b/src/install_page/results_page.blp index cebc00f..1bd7298 100644 --- a/src/install_page/results_page.blp +++ b/src/install_page/results_page.blp @@ -65,18 +65,5 @@ template $ResultsPage : Adw.NavigationPage { description: _("Try a different search term"); } } - [bottom] - ActionBar action_bar { - [center] - Button review_button { - margin-top: 3; - margin-bottom: 3; - styles ["pill", "suggested-action"] - Adw.ButtonContent { - icon-name: "view-list-bullet-symbolic"; - label: _("Review and Install"); - } - } - } } } diff --git a/src/install_page/results_page.py b/src/install_page/results_page.py index 7a7cde3..3fbd3c9 100644 --- a/src/install_page/results_page.py +++ b/src/install_page/results_page.py @@ -36,8 +36,6 @@ class ResultsPage(Adw.NavigationPage): __gtype_name__ = "ResultsPage" gtc = Gtk.Template.Child - action_bar = gtc() - review_button = gtc() search_entry = gtc() search_apply_button = gtc() results_list = gtc() From f05a0170d29d08e9df0d80e09584f9854e52eab4 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 22 Sep 2024 22:20:23 -0400 Subject: [PATCH 188/332] Add batch snapshot creation --- src/host_info.py | 1 + src/meson.build | 1 + src/snapshot_page/new_snapshot_dialog.py | 34 ++++++++++++- src/snapshot_page/snapshot_page.py | 14 +++--- src/snapshot_page/snapshots_list_page.py | 2 +- src/snapshot_page/tar_worker.py | 63 ++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 src/snapshot_page/tar_worker.py diff --git a/src/host_info.py b/src/host_info.py index 49772d6..ab4d915 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -229,6 +229,7 @@ class HostInfo: home = home clipboard = Gdk.Display.get_default().get_clipboard() main_window = None + snapshots_path = f"{home}/.var/app/io.github.flattool.Warehouse/data/Snapshots/" # Get all possible installation icon theme dirs output = subprocess.run( diff --git a/src/meson.build b/src/meson.build index 816b91b..42b6f37 100644 --- a/src/meson.build +++ b/src/meson.build @@ -86,6 +86,7 @@ warehouse_sources = [ 'remotes_page/remotes_page.py', 'remotes_page/remote_row.py', 'remotes_page/add_remote_dialog.py', + 'snapshot_page/tar_worker.py', 'snapshot_page/snapshot_page.py', 'snapshot_page/snapshots_list_page.py', 'snapshot_page/snapshot_box.py', diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index ee97f64..70e42a8 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -3,7 +3,8 @@ from .host_info import HostInfo from .error_toast import ErrorToast from .loading_status import LoadingStatus from .app_row import AppRow -import subprocess, os +from .tar_worker import TarWorker +import subprocess, os, time @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/new_snapshot_dialog.ui") class NewSnapshotDialog(Adw.Dialog): @@ -29,10 +30,10 @@ class NewSnapshotDialog(Adw.Dialog): else: self.selected_rows.remove(row) - self.valid_checker() total = len(self.selected_rows) self.total_selected_label.set_label(_("{} Selected").format(total)) self.total_selected_label.set_visible(total > 0) + self.valid_checker() def generate_list(self, *args): for package in HostInfo.flatpaks: @@ -63,6 +64,33 @@ class NewSnapshotDialog(Adw.Dialog): valid = len(self.selected_rows) > 0 and len(self.name_entry.get_text().strip()) > 0 self.create_button.set_sensitive(valid) + def get_total_fraction(self): + total = 0 + stopped_workers_amount = 0 + for worker in self.workers: + total += worker.fraction + if worker.stop: + stopped_workers_amount += 1 + + if stopped_workers_amount == len(self.workers): + return False + + print(f"{total / len(self.workers):.2f}") + return True + + def on_create(self, button): + self.workers.clear() + for row in self.selected_rows: + package = row.package + worker = TarWorker( + existing_path=package.data_path, + new_path=f"{HostInfo.snapshots_path}{package.info['id']}", + file_name=f"{int(time.time())}_{package.info["version"]}", + name=self.name_entry.get_text(), + ) + self.workers.append(worker) + worker.compress() + def on_invalidate(self, search_entry): self.listbox.invalidate_filter() @@ -88,9 +116,11 @@ class NewSnapshotDialog(Adw.Dialog): # Extra Object Creations self.rows = [] self.selected_rows = [] + self.workers = [] # Connections self.connect("closed", self.on_close) + self.create_button.connect("clicked", self.on_create) self.search_entry.connect("search-changed", self.on_invalidate) self.list_cancel_button.connect("clicked", lambda *_: self.close()) self.name_entry.connect("changed", lambda *_: self.valid_checker()) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 03bb40d..007efc0 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -55,28 +55,26 @@ class SnapshotPage(Adw.BreakpointBin): # This must be set to the created object from within the class's __init__ method instance = None page_name = "snapshots" - - snapshots_path = f"{HostInfo.home}/.var/app/io.github.flattool.Warehouse/data/Snapshots/" def sort_snapshots(self, *args): self.active_snapshot_paks.clear() self.leftover_snapshots.clear() bad_folders = [] - if not os.path.exists(self.snapshots_path): + if not os.path.exists(HostInfo.snapshots_path): try: - os.makedirs(self.snapshots_path) + 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(self.snapshots_path): + 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"{self.snapshots_path}{folder}"): + for file in os.listdir(f"{HostInfo.snapshots_path}{folder}"): if file.endswith(".tar.zst"): has_tar = True break @@ -93,7 +91,7 @@ class SnapshotPage(Adw.BreakpointBin): for folder in bad_folders: try: - subprocess.run(['gio', 'trash', f'{self.snapshots_path}{folder}']) + subprocess.run(['gio', 'trash', f'{HostInfo.snapshots_path}{folder}']) except Exception: pass @@ -164,7 +162,7 @@ class SnapshotPage(Adw.BreakpointBin): def open_snapshots_folder(self, button, overlay): try: - Gio.AppInfo.launch_default_for_uri(f"file://{self.snapshots_path}", None) + Gio.AppInfo.launch_default_for_uri(f"file://{HostInfo.snapshots_path}", None) overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) except Exception as e: overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 3174439..2c56961 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -62,7 +62,7 @@ class SnapshotsListPage(Adw.NavigationPage): # Extra Object Creation self.parent_page = parent_page - self.snapshots_path = parent_page.snapshots_path + self.snapshots_path = HostInfo.snapshots_path self.current_folder = None self.current_package = None self.snapshots_rows = [] diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py new file mode 100644 index 0000000..ab4ef06 --- /dev/null +++ b/src/snapshot_page/tar_worker.py @@ -0,0 +1,63 @@ +from gi.repository import Adw, Gtk, GLib, Gio +from .host_info import HostInfo +from .error_toast import ErrorToast +import os, tarfile, subprocess, json + +class TarWorker: + def __init__(self, existing_path, new_path, file_name, name=""): + self.existing_path = existing_path + self.new_path = new_path + self.file_name = file_name + self.name = name + self.should_check = False + self.stop = False + self.fraction = 0.0 + self.total = 0 + + def compress_thread(self, *args): + try: + if not os.path.exists(self.new_path): + os.makedirs(self.new_path) + + self.total = int(subprocess.run(['du', '-s', self.existing_path], check=True, text=True, capture_output=True).stdout.split('\t')[0]) + self.total /= 1.5 # estimate for space savings + subprocess.run(['tar', 'cafv', f'{self.new_path}/{self.file_name}.tar.zst', '-C', self.existing_path, '.'], check=True, capture_output=True) + + with open(f"{self.new_path}/{self.file_name}.json", 'w') as file: + data = { + 'snapshot_version': 1, + 'name': self.name, + } + json.dump(data, file, indent=4) + + self.stop = True # tell the check timeout to stop, because we know the file is done being made + + except subprocess.CalledProcessError as cpe: + self.stop = True + print(cpe.stderr) + + except Exception as e: + self.stop = True + print(f"Error during compression: {e}") + + def check_size(self): + try: + output = subprocess.run(['du', '-s', f"{self.new_path}/{self.file_name}.tar.zst"], check=True, text=True, capture_output=True).stdout.split('\t')[0] + working_total = int(output) + self.fraction = working_total / self.total + if self.stop: + print("fraction: 1.00") + return False # stop the timeout + else: + print(f"fraction: {self.fraction:.2f}") + return True # continue the timeout + + except subprocess.CalledProcessError as cpe: + return not self.stop # continue the timeout or stop the timeout + + def compress(self): + self.compress = True + self.stop = False + Gio.Task.new(None, None, None).run_in_thread(self.compress_thread) + GLib.timeout_add(10, self.check_size) + From e28e2e7f32e1ad819645ba90caf319fbb2236a31 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sun, 22 Sep 2024 22:45:48 -0400 Subject: [PATCH 189/332] Get fraction from batch snapshots properly --- src/snapshot_page/new_snapshot_dialog.py | 2 ++ src/snapshot_page/snapshot_page.py | 2 ++ src/snapshot_page/tar_worker.py | 9 ++------- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 70e42a8..1ac507e 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -73,6 +73,7 @@ class NewSnapshotDialog(Adw.Dialog): stopped_workers_amount += 1 if stopped_workers_amount == len(self.workers): + print("1.00") return False print(f"{total / len(self.workers):.2f}") @@ -90,6 +91,7 @@ class NewSnapshotDialog(Adw.Dialog): ) self.workers.append(worker) worker.compress() + GLib.timeout_add(10, self.get_total_fraction) def on_invalidate(self, search_entry): self.listbox.invalidate_filter() diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 007efc0..70ec911 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -46,6 +46,7 @@ class SnapshotPage(Adw.BreakpointBin): scrolled_window = gtc() open_button = gtc() status_open_button = gtc() + status_new_button = gtc() new_button = gtc() status_stack = gtc() loading_view = gtc() @@ -184,6 +185,7 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_listbox.connect("row-activated", self.leftover_select_handler) self.open_button.connect("clicked", self.open_snapshots_folder, self.toast_overlay) self.status_open_button.connect("clicked", self.open_snapshots_folder, self.no_snapshots_toast) + self.status_new_button.connect("clicked", lambda *_: self.new_snapshot_dialog.present(HostInfo.main_window)) self.new_button.connect("clicked", lambda *_: self.new_snapshot_dialog.present(HostInfo.main_window)) # Apply diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index ab4ef06..b551c73 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -20,7 +20,7 @@ class TarWorker: os.makedirs(self.new_path) self.total = int(subprocess.run(['du', '-s', self.existing_path], check=True, text=True, capture_output=True).stdout.split('\t')[0]) - self.total /= 1.5 # estimate for space savings + self.total /= 2.5 # estimate for space savings subprocess.run(['tar', 'cafv', f'{self.new_path}/{self.file_name}.tar.zst', '-C', self.existing_path, '.'], check=True, capture_output=True) with open(f"{self.new_path}/{self.file_name}.json", 'w') as file: @@ -45,12 +45,7 @@ class TarWorker: output = subprocess.run(['du', '-s', f"{self.new_path}/{self.file_name}.tar.zst"], check=True, text=True, capture_output=True).stdout.split('\t')[0] working_total = int(output) self.fraction = working_total / self.total - if self.stop: - print("fraction: 1.00") - return False # stop the timeout - else: - print(f"fraction: {self.fraction:.2f}") - return True # continue the timeout + return not self.stop except subprocess.CalledProcessError as cpe: return not self.stop # continue the timeout or stop the timeout From 284798f604bd44dce0005dab3c29f17d09392a99 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 00:21:45 -0400 Subject: [PATCH 190/332] Add loading status and make it like work and whatnot --- src/gtk/loading_status.blp | 20 +++++++++++++++++ src/gtk/loading_status.py | 14 ++++++++++-- src/snapshot_page/new_snapshot_dialog.py | 27 +++++++++++++++++++---- src/snapshot_page/snapshot_page.blp | 7 ++++++ src/snapshot_page/snapshot_page.py | 28 ++++++++++++++++++------ 5 files changed, 83 insertions(+), 13 deletions(-) diff --git a/src/gtk/loading_status.blp b/src/gtk/loading_status.blp index 7b78c18..a74d058 100644 --- a/src/gtk/loading_status.blp +++ b/src/gtk/loading_status.blp @@ -28,6 +28,26 @@ template $LoadingStatus : ScrolledWindow { justify: center; styles ["description", "body"] } + Adw.Clamp progress_clamp { + margin-start: 24; + margin-end: 24; + margin-top: 12; + margin-bottom: 12; + maximum-size: 400; + Box { + halign: fill; + hexpand: true; + spacing: 12; + ProgressBar progress_bar { + halign: fill; + hexpand: true; + valign: center; + } + Label progress_label { + valign: center; + } + } + } Button button { label: "No Label Set"; styles ["pill"] diff --git a/src/gtk/loading_status.py b/src/gtk/loading_status.py index 96ec0c1..0cfe7ad 100644 --- a/src/gtk/loading_status.py +++ b/src/gtk/loading_status.py @@ -8,14 +8,24 @@ class LoadingStatus(Gtk.ScrolledWindow): title_label = gtc() description_label = gtc() + progress_clamp = gtc() + progress_bar = gtc() + progress_label = gtc() button = gtc() + + def set_progress_label(self, *args): + text = self.progress_bar.get_fraction() * 100 + self.progress_label.set_label(f"{text:.0f}%") - def __init__(self, title, description, on_cancel=None, **kwargs): + def __init__(self, title, description, show_progress=False, on_cancel=None, **kwargs): super().__init__(**kwargs) self.title_label.set_label(GLib.markup_escape_text(title)) self.description_label.set_label(GLib.markup_escape_text(description)) + self.progress_clamp.set_visible(show_progress) if on_cancel is None: self.button.set_visible(False) else: - self.button.connect("clicked", on_cancel) + self.button.connect("clicked", lambda *_: on_cancel) + + # self.progress_bar.connect("notify::fraction", self.set_progress_label) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 1ac507e..698b868 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -37,8 +37,12 @@ class NewSnapshotDialog(Adw.Dialog): def generate_list(self, *args): for package in HostInfo.flatpaks: + if "io.github.flattool.Warehouse" in package.info["id"]: + continue + if package.is_runtime or not os.path.exists(package.data_path): continue + row = AppRow(package, self.row_gesture_handler) row.check_button.set_visible(True) row.check_button.connect("toggled", lambda *_, row=row: self.row_select_handler(row)) @@ -58,7 +62,7 @@ class NewSnapshotDialog(Adw.Dialog): def on_close(self, *args): self.search_button.set_active(False) for row in self.selected_rows.copy(): - row.check_button.set_active(False) + GLib.idle_add(lambda *_, row=row: row.check_button.set_active(False)) def valid_checker(self): valid = len(self.selected_rows) > 0 and len(self.name_entry.get_text().strip()) > 0 @@ -71,17 +75,28 @@ class NewSnapshotDialog(Adw.Dialog): total += worker.fraction if worker.stop: stopped_workers_amount += 1 - + if stopped_workers_amount == len(self.workers): + self.loading_status.progress_bar.set_fraction(1) + self.loading_status.progress_label.set_label(f"{len(self.workers)} / {len(self.workers)}") print("1.00") + if self.on_done: + self.on_done() + return False + + self.loading_status.progress_label.set_label(f"{stopped_workers_amount} / {len(self.workers)}") - print(f"{total / len(self.workers):.2f}") + print(total / len(self.workers)) + self.loading_status.progress_bar.set_fraction(total / len(self.workers)) return True def on_create(self, button): self.workers.clear() for row in self.selected_rows: + if "io.github.flattool.Warehouse" in row.package.info["id"]: + continue + package = row.package worker = TarWorker( existing_path=package.data_path, @@ -91,7 +106,9 @@ class NewSnapshotDialog(Adw.Dialog): ) self.workers.append(worker) worker.compress() + GLib.timeout_add(10, self.get_total_fraction) + self.close() def on_invalidate(self, search_entry): self.listbox.invalidate_filter() @@ -112,10 +129,12 @@ class NewSnapshotDialog(Adw.Dialog): super().present(*args, **kwargs) self.name_entry.grab_focus() - def __init__(self, parent_page, package=None, **kwargs): + def __init__(self, parent_page, loading_status, on_done=None, package=None, **kwargs): super().__init__(**kwargs) # Extra Object Creations + self.loading_status = loading_status + self.on_done = on_done self.rows = [] self.selected_rows = [] self.workers = [] diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 9a562fd..64e2778 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -165,6 +165,13 @@ template $SnapshotPage : Adw.BreakpointBin { $SidebarButton {} } } + Adw.ToolbarView snapshotting_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + } + } } } } diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 70ec911..77f923e 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -50,6 +50,7 @@ class SnapshotPage(Adw.BreakpointBin): new_button = gtc() status_stack = gtc() loading_view = gtc() + snapshotting_view = gtc() # Referred to in the main window # It is used to determine if a new page should be made or not @@ -141,15 +142,16 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_select_handler(None, row, False, True) 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() - self.status_stack.set_visible_child(self.loading_view) def end_loading(self): def callback(*args): - self.new_snapshot_dialog = NewSnapshotDialog(self) + self.new_snapshot_dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh) + self.new_snapshot_dialog.create_button.connect("clicked", lambda *_: self.status_stack.set_visible_child(self.snapshotting_view)) self.generate_active_list() self.generate_leftover_list() if (not self.active_box.get_visible()) and (not self.leftover_box.get_visible()): @@ -158,7 +160,7 @@ class SnapshotPage(Adw.BreakpointBin): self.select_first_row() 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)) - + Gio.Task.new(None, None, callback).run_in_thread(self.sort_snapshots) def open_snapshots_folder(self, button, overlay): @@ -167,10 +169,20 @@ class SnapshotPage(Adw.BreakpointBin): overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) except Exception as e: overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) - + + def on_cancel(self): + pass + + def on_new(self, *args): + self.new_snapshot_dialog.present(HostInfo.main_window) + + def refresh(self): + self.start_loading() + self.end_loading() + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) - + # Extra Object Creation self.__class__.instance = self self.main_window = main_window @@ -179,15 +191,17 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_snapshots = [] # self.leftover_rows = [] self.list_page = SnapshotsListPage(self) + self.snapshotting_status = LoadingStatus(_("Creating Snapshots"), _("This might take a while"), True, self.on_cancel) # Connections 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, self.toast_overlay) self.status_open_button.connect("clicked", self.open_snapshots_folder, self.no_snapshots_toast) - self.status_new_button.connect("clicked", lambda *_: self.new_snapshot_dialog.present(HostInfo.main_window)) - self.new_button.connect("clicked", lambda *_: self.new_snapshot_dialog.present(HostInfo.main_window)) + self.status_new_button.connect("clicked", self.on_new) + self.new_button.connect("clicked", self.on_new) # Apply 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) From 9add61101cbe3c980ce11238b0c9c7a742ee8ad0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 00:52:04 -0400 Subject: [PATCH 191/332] apply loading screen to single snapshot creation --- src/snapshot_page/new_snapshot_dialog.py | 2 -- src/snapshot_page/snapshot_page.blp | 4 +-- src/snapshot_page/snapshots_list_page.py | 32 ++++++++++++++---------- src/snapshot_page/tar_worker.py | 2 +- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 698b868..fb60424 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -79,7 +79,6 @@ class NewSnapshotDialog(Adw.Dialog): if stopped_workers_amount == len(self.workers): self.loading_status.progress_bar.set_fraction(1) self.loading_status.progress_label.set_label(f"{len(self.workers)} / {len(self.workers)}") - print("1.00") if self.on_done: self.on_done() @@ -87,7 +86,6 @@ class NewSnapshotDialog(Adw.Dialog): self.loading_status.progress_label.set_label(f"{stopped_workers_amount} / {len(self.workers)}") - print(total / len(self.workers)) self.loading_status.progress_bar.set_fraction(total / len(self.workers)) return True diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 64e2778..2a5c42c 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -68,10 +68,10 @@ template $SnapshotPage : Adw.BreakpointBin { ScrolledWindow scrolled_window { Box { orientation: vertical; - + Box active_box { orientation: vertical; - + Label { label: _("Active Snapshots"); halign: start; diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 2c56961..dc4e42c 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -10,13 +10,13 @@ import os class SnapshotsListPage(Adw.NavigationPage): __gtype_name__ = "SnapshotsListPage" gtc = Gtk.Template.Child - + toolbar_view = gtc() listbox = gtc() toast_overlay = gtc() open_button = gtc() new_button = gtc() - + def thread(self, *args): for snapshot in os.listdir(folder := f"{self.snapshots_path}{self.current_folder}/"): if snapshot.endswith(".json"): @@ -24,51 +24,57 @@ class SnapshotsListPage(Adw.NavigationPage): row = SnapshotBox(self, snapshot, folder, self.toast_overlay) self.snapshots_rows.append(row) - + def callback(self, *args): for i, row in enumerate(self.snapshots_rows): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) - + def set_snapshots(self, package, refresh=False): folder = package.info["id"] if self.current_folder == folder and not refresh: return - + self.current_package = package self.current_folder = folder self.set_title(_("{} Snapshots").format(package.info["name"])) self.snapshots_rows.clear() self.listbox.remove_all() - + Gio.Task.new(None, None, self.callback).run_in_thread(self.thread) - + def open_snapshots_folder(self, button): path = f"{self.snapshots_path}{self.current_folder}/" try: if not os.path.exists(path): raise Exception(f"error: File '{path}' does not exist") - + Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) self.toast_overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) except Exception as e: self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) + def on_done(self): + self.parent_page.status_stack.set_visible_child(self.parent_page.split_view) + self.set_snapshots(self.current_package, refresh=True) + def on_new(self, button): - NewSnapshotDialog(self.parent_page, self.current_package).present(HostInfo.main_window) - + dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, self.current_package) + dialog.create_button.connect("clicked", lambda *_: self.parent_page.status_stack.set_visible_child(self.parent_page.snapshotting_view)) + dialog.present(HostInfo.main_window) + def __init__(self, parent_page, **kwargs): super().__init__(**kwargs) - + # Extra Object Creation self.parent_page = parent_page self.snapshots_path = HostInfo.snapshots_path self.current_folder = None self.current_package = None self.snapshots_rows = [] - + # Connections self.open_button.connect("clicked", self.open_snapshots_folder) self.new_button.connect("clicked", self.on_new) - + # Apply diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index b551c73..365c29d 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -20,7 +20,7 @@ class TarWorker: os.makedirs(self.new_path) self.total = int(subprocess.run(['du', '-s', self.existing_path], check=True, text=True, capture_output=True).stdout.split('\t')[0]) - self.total /= 2.5 # estimate for space savings + self.total /= 2.2 # estimate for space savings subprocess.run(['tar', 'cafv', f'{self.new_path}/{self.file_name}.tar.zst', '-C', self.existing_path, '.'], check=True, capture_output=True) with open(f"{self.new_path}/{self.file_name}.json", 'w') as file: From b0443840f8bce0c76d3694c79b486f472826d67b Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 12:16:05 -0400 Subject: [PATCH 192/332] Add ability to cancel snapshot creation --- src/gtk/loading_status.py | 8 +----- src/snapshot_page/new_snapshot_dialog.py | 2 +- src/snapshot_page/snapshot_page.py | 4 ++- src/snapshot_page/tar_worker.py | 36 ++++++++++++++++++------ 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/gtk/loading_status.py b/src/gtk/loading_status.py index 0cfe7ad..3a78e30 100644 --- a/src/gtk/loading_status.py +++ b/src/gtk/loading_status.py @@ -12,10 +12,6 @@ class LoadingStatus(Gtk.ScrolledWindow): progress_bar = gtc() progress_label = gtc() button = gtc() - - def set_progress_label(self, *args): - text = self.progress_bar.get_fraction() * 100 - self.progress_label.set_label(f"{text:.0f}%") def __init__(self, title, description, show_progress=False, on_cancel=None, **kwargs): super().__init__(**kwargs) @@ -26,6 +22,4 @@ class LoadingStatus(Gtk.ScrolledWindow): if on_cancel is None: self.button.set_visible(False) else: - self.button.connect("clicked", lambda *_: on_cancel) - - # self.progress_bar.connect("notify::fraction", self.set_progress_label) + self.button.connect("clicked", lambda *_: on_cancel()) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index fb60424..efc0a9f 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -105,7 +105,7 @@ class NewSnapshotDialog(Adw.Dialog): self.workers.append(worker) worker.compress() - GLib.timeout_add(10, self.get_total_fraction) + GLib.timeout_add(200, self.get_total_fraction) self.close() def on_invalidate(self, search_entry): diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 77f923e..3d2c854 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -171,7 +171,8 @@ class SnapshotPage(Adw.BreakpointBin): overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) def on_cancel(self): - pass + 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) @@ -204,4 +205,5 @@ class SnapshotPage(Adw.BreakpointBin): # Apply self.loading_view.set_content(LoadingStatus(_("Loading Snapshots"), _("This should only take a moment"))) self.snapshotting_view.set_content(self.snapshotting_status) + self.snapshotting_status.button.set_label(_("Cancel")) self.split_view.set_content(self.list_page) diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index 365c29d..bbac690 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -13,6 +13,7 @@ class TarWorker: self.stop = False self.fraction = 0.0 self.total = 0 + self.process = None def compress_thread(self, *args): try: @@ -21,7 +22,13 @@ class TarWorker: self.total = int(subprocess.run(['du', '-s', self.existing_path], check=True, text=True, capture_output=True).stdout.split('\t')[0]) self.total /= 2.2 # estimate for space savings - subprocess.run(['tar', 'cafv', f'{self.new_path}/{self.file_name}.tar.zst', '-C', self.existing_path, '.'], check=True, capture_output=True) + self.process = subprocess.Popen(['tar', 'cafv', f'{self.new_path}/{self.file_name}.tar.zst', '-C', self.existing_path, '.'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + stdout, stderr = self.process.communicate() + if self.process.returncode != 0: + raise subprocess.CalledProcessError(self.process.returncode, self.process.args, output=stdout, stderr=stderr) with open(f"{self.new_path}/{self.file_name}.json", 'w') as file: data = { @@ -33,12 +40,26 @@ class TarWorker: self.stop = True # tell the check timeout to stop, because we know the file is done being made except subprocess.CalledProcessError as cpe: - self.stop = True - print(cpe.stderr) + print("Called Error") + self.do_cancel(cpe.stderr.decode()) # stderr is in bytes, so decode it except Exception as e: - self.stop = True - print(f"Error during compression: {e}") + print("Exception") + self.do_cancel(str(e)) + + def do_cancel(self, error_str=None): + self.process.terminate() + self.process.wait() + if error_str == "manual_cancel": + try: + subprocess.run(['gio', 'trash', f'{self.new_path}/{self.file_name}.tar.zst'],capture_output=True) + subprocess.run(['gio', 'trash', f'{self.new_path}/{self.file_name}.json'],capture_output=True) + + except Exception: + pass + + self.stop = True + print("Error in compression:", error_str) def check_size(self): try: @@ -46,13 +67,12 @@ class TarWorker: working_total = int(output) self.fraction = working_total / self.total return not self.stop - + except subprocess.CalledProcessError as cpe: return not self.stop # continue the timeout or stop the timeout def compress(self): - self.compress = True self.stop = False Gio.Task.new(None, None, None).run_in_thread(self.compress_thread) - GLib.timeout_add(10, self.check_size) + GLib.timeout_add(200, self.check_size) From 484b2ca2e787ff4bb23a54146952d7df2bd44ffa Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 13:30:02 -0400 Subject: [PATCH 193/332] Fix bug when selecting leftover snapshots --- src/snapshot_page/snapshot_page.py | 2 +- src/snapshot_page/snapshots_list_page.blp | 1 + src/snapshot_page/snapshots_list_page.py | 26 +++++++++++++++-------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 3d2c854..e225627 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -130,7 +130,7 @@ class SnapshotPage(Adw.BreakpointBin): 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.package, refresh) + self.list_page.set_snapshots(row.folder, refresh) self.split_view.set_show_content(should_show_content) def select_first_row(self): diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index 384094d..2e27e50 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -31,6 +31,7 @@ template $SnapshotsListPage : Adw.NavigationPage { } [bottom] ActionBar { + revealed: bind new_button.visible; [center] Button new_button { margin-top: 3; diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index dc4e42c..889e39c 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -30,14 +30,22 @@ class SnapshotsListPage(Adw.NavigationPage): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) - def set_snapshots(self, package, refresh=False): - folder = package.info["id"] - if self.current_folder == folder and not refresh: + def set_snapshots(self, package_or_folder, refresh=False): + if package_or_folder == self.package_or_folder and not refresh: return - - self.current_package = package + + folder = None + self.package_or_folder = package_or_folder + if type(package_or_folder) is str: + self.set_title(package_or_folder) + folder = package_or_folder + self.new_button.set_visible(False) + else: + folder = package_or_folder.info["id"] + self.set_title(_("{} Snapshots").format(package_or_folder.info["name"])) + self.new_button.set_visible(os.path.exists(package_or_folder.data_path)) + self.current_folder = folder - self.set_title(_("{} Snapshots").format(package.info["name"])) self.snapshots_rows.clear() self.listbox.remove_all() @@ -56,10 +64,10 @@ class SnapshotsListPage(Adw.NavigationPage): def on_done(self): self.parent_page.status_stack.set_visible_child(self.parent_page.split_view) - self.set_snapshots(self.current_package, refresh=True) + self.set_snapshots(self.package_or_folder, refresh=True) def on_new(self, button): - dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, self.current_package) + dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, self.package_or_folder) dialog.create_button.connect("clicked", lambda *_: self.parent_page.status_stack.set_visible_child(self.parent_page.snapshotting_view)) dialog.present(HostInfo.main_window) @@ -70,7 +78,7 @@ class SnapshotsListPage(Adw.NavigationPage): self.parent_page = parent_page self.snapshots_path = HostInfo.snapshots_path self.current_folder = None - self.current_package = None + self.package_or_folder = None self.snapshots_rows = [] # Connections From d7edab156400ff03019b91f3afd5a8a87bdc44c0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 13:33:13 -0400 Subject: [PATCH 194/332] Set rename button's icon to pencil --- data/icons/edit-symbolic.svg | 2 ++ src/snapshot_page/snapshot_box.blp | 2 +- src/warehouse.gresource.xml | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 data/icons/edit-symbolic.svg diff --git a/data/icons/edit-symbolic.svg b/data/icons/edit-symbolic.svg new file mode 100644 index 0000000..51090b9 --- /dev/null +++ b/data/icons/edit-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/snapshot_page/snapshot_box.blp b/src/snapshot_page/snapshot_box.blp index 28b3fe9..73a7207 100644 --- a/src/snapshot_page/snapshot_box.blp +++ b/src/snapshot_page/snapshot_box.blp @@ -50,7 +50,7 @@ template $SnapshotBox : Gtk.Box { MenuButton rename_button { Adw.ButtonContent { label: _("Rename"); - icon-name: "dot-symbolic"; + icon-name: "edit-symbolic"; can-shrink: true; } hexpand: true; diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index ba13402..3ac514e 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -77,5 +77,6 @@ ../data/icons/minus-large-symbolic.svg ../data/icons/view-list-bullet-symbolic.svg ../data/icons/list-remove-all-symbolic.svg + ../data/icons/edit-symbolic.svg From bc8ce3855e7d50966660f822077ede164a889362 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 14:09:57 -0400 Subject: [PATCH 195/332] Implement valid name checks --- src/snapshot_page/new_snapshot_dialog.py | 3 ++- src/snapshot_page/snapshot_box.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index efc0a9f..345862a 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -65,7 +65,8 @@ class NewSnapshotDialog(Adw.Dialog): GLib.idle_add(lambda *_, row=row: row.check_button.set_active(False)) def valid_checker(self): - valid = len(self.selected_rows) > 0 and len(self.name_entry.get_text().strip()) > 0 + text = self.name_entry.get_text().strip() + valid = len(self.selected_rows) > 0 and len(text) > 0 and not("/" in text or "\0" in text) self.create_button.set_sensitive(valid) def get_total_fraction(self): diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index f344278..0f4077e 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -1,7 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast -import os, subprocess, json +import os, subprocess, json, re @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_box.ui") class SnapshotBox(Gtk.Box): @@ -60,10 +60,19 @@ class SnapshotBox(Gtk.Box): 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 + self.apply_rename.set_sensitive(valid) + return valid + def on_trash(self, button): error = [None] path = f"{self.snapshots_path}{self.folder}" @@ -116,4 +125,5 @@ class SnapshotBox(Gtk.Box): self.load_from_json() self.apply_rename.connect("clicked", self.on_rename) self.rename_entry.connect("activate", self.on_rename) + self.rename_entry.connect("changed", self.valid_checker) self.trash_button.connect("clicked", self.on_trash) From 25cae455c2fcaac2ff180efd0951a74f1a4864d6 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 14:18:57 -0400 Subject: [PATCH 196/332] Apply stylings to valid names, and connect dialog name entry enter key press to create button --- src/snapshot_page/new_snapshot_dialog.py | 11 +++++++++++ src/snapshot_page/snapshot_box.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 345862a..660b21d 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -68,6 +68,12 @@ class NewSnapshotDialog(Adw.Dialog): text = self.name_entry.get_text().strip() valid = len(self.selected_rows) > 0 and len(text) > 0 and not("/" in text or "\0" in text) self.create_button.set_sensitive(valid) + if valid: + self.name_entry.remove_css_class("error") + else: + self.name_entry.add_css_class("error") + + return valid def get_total_fraction(self): total = 0 @@ -128,6 +134,10 @@ class NewSnapshotDialog(Adw.Dialog): super().present(*args, **kwargs) self.name_entry.grab_focus() + def enter_handler(self, *args): + if self.create_button.get_sensitive(): + self.create_button.activate() + def __init__(self, parent_page, loading_status, on_done=None, package=None, **kwargs): super().__init__(**kwargs) @@ -144,6 +154,7 @@ class NewSnapshotDialog(Adw.Dialog): self.search_entry.connect("search-changed", self.on_invalidate) self.list_cancel_button.connect("clicked", lambda *_: self.close()) self.name_entry.connect("changed", lambda *_: self.valid_checker()) + self.name_entry.connect("entry-activated", self.enter_handler) self.select_all_button.connect("clicked", self.on_select_all) # Apply diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 0f4077e..05788cf 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -71,6 +71,11 @@ class SnapshotBox(Gtk.Box): text = self.rename_entry.get_text().strip() valid = not ("/" in text or "\0" in text) and len(text) > 0 self.apply_rename.set_sensitive(valid) + if valid: + self.rename_entry.remove_css_class("error") + else: + self.rename_entry.add_css_class("error") + return valid def on_trash(self, button): From 64ef6c3aca516799e09caeb623d3a4b476270d79 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 14:28:24 -0400 Subject: [PATCH 197/332] Fix Add Remote Dialog allowing empty custom remotes to be added --- src/remotes_page/add_remote_dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/remotes_page/add_remote_dialog.py b/src/remotes_page/add_remote_dialog.py index cc74a2b..85efd48 100644 --- a/src/remotes_page/add_remote_dialog.py +++ b/src/remotes_page/add_remote_dialog.py @@ -108,6 +108,8 @@ class AddRemoteDialog(Adw.Dialog): self.name_row.set_editable(False) self.url_row.set_editable(False) self.apply_button.set_sensitive(True) + else: + self.apply_button.set_sensitive(False) # Connections self.cancel_button.connect("clicked", lambda *_: self.close()) From 025c3f0c0c22e1f2b5aa7eff9f340b723343ed54 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 14:33:29 -0400 Subject: [PATCH 198/332] Fix page thinking a remote was remove even when it couldn't be --- src/remotes_page/remote_row.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/remotes_page/remote_row.py b/src/remotes_page/remote_row.py index 345742f..a3f0795 100644 --- a/src/remotes_page/remote_row.py +++ b/src/remotes_page/remote_row.py @@ -64,7 +64,12 @@ class RemoteRow(Adw.ActionRow): Gio.Task.new(None, None, callback).run_in_thread(thread) def disable_remote_handler(self, *args): + error = [None] + def callback(*args): + if error[0]: + return + self.add_css_class("warning") self.set_icon_name("error-symbolic") self.set_tooltip_text(_("Remote is Disabled")) @@ -100,9 +105,11 @@ class RemoteRow(Adw.ActionRow): subprocess.run(cmd, check=True, capture_output=True, text=True) except subprocess.CalledProcessError as cpe: GLib.idle_add(lambda *args, cpe=cpe: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(cpe.stderr)).toast)) + error[0] = cpe return except Exception as e: GLib.idle_add(lambda *args, e=e: self.parent_page.toast_overlay.add_toast(ErrorToast(_("Could not disable remote"), str(e)).toast)) + error[0] = e return def on_response(_, response): From 9ec552dd3894eb06f6b9be18d2b7714b0a646a72 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 14:46:03 -0400 Subject: [PATCH 199/332] Sort snapshot list by date and time created --- src/snapshot_page/snapshot_box.py | 3 ++- src/snapshot_page/snapshots_list_page.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 05788cf..88a14ad 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -123,7 +123,8 @@ class SnapshotBox(Gtk.Box): self.parent_page = parent_page self.folder = folder self.snapshots_path = snapshots_path - date_data = GLib.DateTime.new_from_unix_local(int(split_folder[0])).format("%x %X") + self.epoch = int(split_folder[0]) + date_data = GLib.DateTime.new_from_unix_local(self.epoch).format("%x %X") self.date.set_label(date_data) self.version.set_label(_("Version: {}").format(split_folder[1].replace(".tar.zst", ""))) self.json_path = f"{snapshots_path}{folder.replace('tar.zst', 'json')}" diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 889e39c..988985b 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -70,6 +70,11 @@ class SnapshotsListPage(Adw.NavigationPage): dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, self.package_or_folder) dialog.create_button.connect("clicked", lambda *_: self.parent_page.status_stack.set_visible_child(self.parent_page.snapshotting_view)) dialog.present(HostInfo.main_window) + + def sort_func(self, row1, row2): + row1 = row1.get_child() + row2 = row2.get_child() + return row1.epoch > row2.epoch def __init__(self, parent_page, **kwargs): super().__init__(**kwargs) @@ -86,3 +91,4 @@ class SnapshotsListPage(Adw.NavigationPage): self.new_button.connect("clicked", self.on_new) # Apply + self.listbox.set_sort_func(self.sort_func) From 9fa2bb6e9ee03103eda1aca33525ce1207e6004e Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 15:22:33 -0400 Subject: [PATCH 200/332] open the same package page again instead of resetting to the first row, if there are still snapshots left to delete for the current app --- src/snapshot_page/snapshot_box.py | 3 +-- src/snapshot_page/snapshots_list_page.blp | 2 +- src/snapshot_page/snapshots_list_page.py | 11 +++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 88a14ad..6586387 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -94,8 +94,7 @@ class SnapshotBox(Gtk.Box): self.toast_overlay.add_toast(ErrorToast(_("Could not trash snapshot"), error[0]).toast) return - self.parent_page.parent_page.start_loading() - self.parent_page.parent_page.end_loading() + self.parent_page.on_trash() self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed snapshot"))) def on_response(_, response): diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index 2e27e50..3d8187c 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -31,7 +31,7 @@ template $SnapshotsListPage : Adw.NavigationPage { } [bottom] ActionBar { - revealed: bind new_button.visible; + revealed: bind new_button.sensitive; [center] Button new_button { margin-top: 3; diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 988985b..8b314ea 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -26,6 +26,10 @@ class SnapshotsListPage(Adw.NavigationPage): self.snapshots_rows.append(row) def callback(self, *args): + if len(self.snapshots_rows) == 0: + self.parent_page.refresh() + return + for i, row in enumerate(self.snapshots_rows): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) @@ -39,11 +43,11 @@ class SnapshotsListPage(Adw.NavigationPage): if type(package_or_folder) is str: self.set_title(package_or_folder) folder = package_or_folder - self.new_button.set_visible(False) + self.new_button.set_sensitive(False) else: folder = package_or_folder.info["id"] self.set_title(_("{} Snapshots").format(package_or_folder.info["name"])) - self.new_button.set_visible(os.path.exists(package_or_folder.data_path)) + self.new_button.set_sensitive(os.path.exists(package_or_folder.data_path)) self.current_folder = folder self.snapshots_rows.clear() @@ -75,6 +79,9 @@ class SnapshotsListPage(Adw.NavigationPage): row1 = row1.get_child() row2 = row2.get_child() return row1.epoch > row2.epoch + + def on_trash(self): + self.set_snapshots(self.package_or_folder, refresh=True) def __init__(self, parent_page, **kwargs): super().__init__(**kwargs) From 332cdf32b6f1b9ea74842c3301332ac8749b0c18 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Mon, 23 Sep 2024 15:58:25 -0400 Subject: [PATCH 201/332] Simply showing snapshotting status --- src/snapshot_page/new_snapshot_dialog.py | 4 +++- src/snapshot_page/snapshot_page.py | 1 - src/snapshot_page/snapshots_list_page.py | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 660b21d..e2fbdda 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -97,6 +97,7 @@ class NewSnapshotDialog(Adw.Dialog): return True def on_create(self, button): + self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) self.workers.clear() for row in self.selected_rows: if "io.github.flattool.Warehouse" in row.package.info["id"]: @@ -138,10 +139,11 @@ class NewSnapshotDialog(Adw.Dialog): if self.create_button.get_sensitive(): self.create_button.activate() - def __init__(self, parent_page, loading_status, on_done=None, package=None, **kwargs): + def __init__(self, snapshot_page, loading_status, on_done=None, package=None, **kwargs): super().__init__(**kwargs) # Extra Object Creations + self.snapshot_page = snapshot_page self.loading_status = loading_status self.on_done = on_done self.rows = [] diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index e225627..9511a3b 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -151,7 +151,6 @@ class SnapshotPage(Adw.BreakpointBin): def end_loading(self): def callback(*args): self.new_snapshot_dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh) - self.new_snapshot_dialog.create_button.connect("clicked", lambda *_: self.status_stack.set_visible_child(self.snapshotting_view)) self.generate_active_list() self.generate_leftover_list() if (not self.active_box.get_visible()) and (not self.leftover_box.get_visible()): diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 8b314ea..1015230 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -72,7 +72,6 @@ class SnapshotsListPage(Adw.NavigationPage): def on_new(self, button): dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, self.package_or_folder) - dialog.create_button.connect("clicked", lambda *_: self.parent_page.status_stack.set_visible_child(self.parent_page.snapshotting_view)) dialog.present(HostInfo.main_window) def sort_func(self, row1, row2): From 1df4f26aea23c85f7b3b067373b9f239ac9adf70 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Tue, 24 Sep 2024 23:08:12 -0400 Subject: [PATCH 202/332] Disable, instead of hide, new snapshot button when there is no user data --- src/snapshot_page/snapshots_list_page.blp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/snapshot_page/snapshots_list_page.blp b/src/snapshot_page/snapshots_list_page.blp index 3d8187c..384094d 100644 --- a/src/snapshot_page/snapshots_list_page.blp +++ b/src/snapshot_page/snapshots_list_page.blp @@ -31,7 +31,6 @@ template $SnapshotsListPage : Adw.NavigationPage { } [bottom] ActionBar { - revealed: bind new_button.sensitive; [center] Button new_button { margin-top: 3; From 8e68adfc9b5cdc96404c691a35bb88bcf636f69b Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 21:17:03 -0400 Subject: [PATCH 203/332] Apply better wrapping --- src/snapshot_page/snapshot_box.blp | 1 + src/snapshot_page/snapshot_page.blp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/snapshot_page/snapshot_box.blp b/src/snapshot_page/snapshot_box.blp index 73a7207..edcfe22 100644 --- a/src/snapshot_page/snapshot_box.blp +++ b/src/snapshot_page/snapshot_box.blp @@ -14,6 +14,7 @@ template $SnapshotBox : Gtk.Box { Label title { label: _("No Name Set"); wrap: true; + wrap-mode: word_char; justify: left; halign: start; styles ["title-4"] diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 2a5c42c..4361619 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -81,6 +81,7 @@ template $SnapshotPage : Adw.BreakpointBin { margin-start: 12; margin-end: 12; wrap: true; + wrap-mode: word_char; } Label { label: _("Snapshots of installed apps"); @@ -90,6 +91,7 @@ template $SnapshotPage : Adw.BreakpointBin { margin-end: 12; margin-bottom: 3; wrap: true; + wrap-mode: word_char; } ListBox active_listbox { styles ["navigation-sidebar"] @@ -108,6 +110,7 @@ template $SnapshotPage : Adw.BreakpointBin { margin-start: 12; margin-end: 12; wrap: true; + wrap-mode: word_char; } Label { label: _("Snapshots of apps that are no longer installed"); @@ -117,6 +120,7 @@ template $SnapshotPage : Adw.BreakpointBin { margin-end: 12; margin-bottom: 3; wrap: true; + wrap-mode: word_char; } ListBox leftover_listbox { styles ["navigation-sidebar"] From 7de63a5cc223b219629324473f5b3bcd0c6de869 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 21:17:34 -0400 Subject: [PATCH 204/332] Increase min width to avoid clipping content --- src/main_window/window.blp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main_window/window.blp b/src/main_window/window.blp index 277d4d7..2840f17 100644 --- a/src/main_window/window.blp +++ b/src/main_window/window.blp @@ -5,7 +5,7 @@ template $WarehouseWindow: Adw.ApplicationWindow { title: "Warehouse"; // default-width: 240; default-width: 865; - width-request: 400; + width-request: 403; height-request: 260; Adw.Breakpoint main_breakpoint { condition ("min-width: 865") From 67293e7e4e8abf7abcda296580ac52fdd3de89a9 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 21:18:58 -0400 Subject: [PATCH 205/332] Set label for action instead of making two different loading pages --- src/snapshot_page/new_snapshot_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index e2fbdda..d84c64f 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -97,6 +97,7 @@ class NewSnapshotDialog(Adw.Dialog): return True def on_create(self, button): + self.loading_status.title_label.set_label(_("Creating Snapshot")) self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) self.workers.clear() for row in self.selected_rows: From 5d6b408259e32c5deb429503a299b15cd3040676 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 21:19:28 -0400 Subject: [PATCH 206/332] Set no title for the snapshotting status, as that will now be handled later --- src/snapshot_page/snapshot_box.py | 20 +++++++++++- src/snapshot_page/snapshot_page.py | 2 +- src/snapshot_page/tar_worker.py | 49 ++++++++++++++++++++++-------- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 6586387..84136d7 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -1,6 +1,7 @@ from gi.repository import Adw, Gtk, GLib, Gio from .host_info import HostInfo from .error_toast import ErrorToast +from .tar_worker import TarWorker import os, subprocess, json, re @Gtk.Template(resource_path="/io/github/flattool/Warehouse/snapshot_page/snapshot_box.ui") @@ -109,11 +110,27 @@ class SnapshotBox(Gtk.Box): dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) dialog.connect("response", on_response) dialog.present(HostInfo.main_window) - + + def get_fraction(self): + loading_status = self.parent_page.parent_page.loading_status + loading_status.title_label.set_label(_("Applying Snapshot")) + loading_status.progress_bar.set_fraction(total / len(self.workers)) + + def on_apply(self, button): + self.parent_page.parent_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) + self.worker.extract() + + def __init__(self, parent_page, folder, snapshots_path, toast_overlay, **kwargs): super().__init__(**kwargs) self.toast_overlay = toast_overlay + self.app_id = snapshots_path.split('/')[-2].strip() + self.worker = TarWorker( + existing_path=f"{snapshots_path}{folder}", + new_path=f"{HostInfo.home}/.var/app/{self.app_id}/", + file_name=self.app_id, + ) split_folder = folder.split('_') if len(split_folder) < 2: @@ -128,6 +145,7 @@ class SnapshotBox(Gtk.Box): self.version.set_label(_("Version: {}").format(split_folder[1].replace(".tar.zst", ""))) self.json_path = f"{snapshots_path}{folder.replace('tar.zst', 'json')}" self.load_from_json() + self.apply_button.connect("clicked", self.on_apply) self.apply_rename.connect("clicked", self.on_rename) self.rename_entry.connect("activate", self.on_rename) self.rename_entry.connect("changed", self.valid_checker) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 9511a3b..c752490 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -191,7 +191,7 @@ class SnapshotPage(Adw.BreakpointBin): self.leftover_snapshots = [] # self.leftover_rows = [] self.list_page = SnapshotsListPage(self) - self.snapshotting_status = LoadingStatus(_("Creating Snapshots"), _("This might take a while"), True, self.on_cancel) + self.snapshotting_status = LoadingStatus("Initial Title", _("This might take a while"), True, self.on_cancel) # Connections self.active_listbox.connect("row-activated", self.active_select_handler) diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index bbac690..9f62f20 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -40,30 +40,51 @@ class TarWorker: self.stop = True # tell the check timeout to stop, because we know the file is done being made except subprocess.CalledProcessError as cpe: - print("Called Error") - self.do_cancel(cpe.stderr.decode()) # stderr is in bytes, so decode it + print("Called Error in Compress Thread") + self.do_cancel(cpe.stderr.decode(), [f'{self.new_path}/{self.file_name}.tar.zst', f'{self.new_path}/{self.file_name}.json']) # stderr is in bytes, so decode it except Exception as e: - print("Exception") - self.do_cancel(str(e)) + print("Exception in Compress Thread") + self.do_cancel(str(e), [f'{self.new_path}/{self.file_name}.tar.zst', f'{self.new_path}/{self.file_name}.json']) + + def extract_thread(self, *args): + try: + if not os.path.exists(self.new_path): + os.makedirs(self.new_path) # create the new user data path if none exists + else: + subprocess.run('gio', 'trash', self.new_path) # trash the current user data, because new data will go in its place + + self.total = int(subprocess.run(['du', '-s', self.existing_path], check=True, text=True, capture_output=True).stdout.split('\t')[0]) + self.total *= 2.2 # estimate from space savings + self.process = subprocess.Popen(['tar', '--zstd', '-xvf', self.existing_path, '-C', self.new_path]) + stdout, stderr = self.process.communicate() + if self.process.returncode != 0: + raise subprocess.CalledProcessError(self.process.returncode, self.process.args, output=stdout, stderr=stderr) + + except subprocess.CalledProcessError as cpe: + print("Called Error in Extract Thread") + self.do_cancel(cpe.stderr.decode(), [self.new_path]) + + except Exception as e: + print("Exception in Extract Thread") + self.do_cancel(str(e), [self.new_path]) - def do_cancel(self, error_str=None): + def do_cancel(self, error_str, files_to_trash=None): self.process.terminate() self.process.wait() - if error_str == "manual_cancel": + if not files_to_trash is None: try: - subprocess.run(['gio', 'trash', f'{self.new_path}/{self.file_name}.tar.zst'],capture_output=True) - subprocess.run(['gio', 'trash', f'{self.new_path}/{self.file_name}.json'],capture_output=True) + subprocess.run(['gio', 'trash'] + files_to_trash, capture_output=True) except Exception: pass self.stop = True - print("Error in compression:", error_str) + print("Error in cancelling:", error_str) - def check_size(self): + def check_size(self, check_path): try: - output = subprocess.run(['du', '-s', f"{self.new_path}/{self.file_name}.tar.zst"], check=True, text=True, capture_output=True).stdout.split('\t')[0] + output = subprocess.run(['du', '-s', check_path], check=True, text=True, capture_output=True).stdout.split('\t')[0] working_total = int(output) self.fraction = working_total / self.total return not self.stop @@ -74,5 +95,9 @@ class TarWorker: def compress(self): self.stop = False Gio.Task.new(None, None, None).run_in_thread(self.compress_thread) - GLib.timeout_add(200, self.check_size) + GLib.timeout_add(200, self.check_size, f"{self.new_path}/{self.file_name}.tar.zst") + def extract(self): + self.stop = False + Gio.Task.new(None, None, None).run_in_thread(self.existing_path) + GLib.timeout_add(200, self.check_size, self.new_path) From a59ec729511cc0b10e5ec143d8fc584ff451e579 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 21:52:02 -0400 Subject: [PATCH 207/332] Allow applying snapshots --- src/snapshot_page/snapshot_box.py | 14 ++++++++++---- src/snapshot_page/tar_worker.py | 19 ++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 84136d7..a56a6bd 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -112,18 +112,24 @@ class SnapshotBox(Gtk.Box): dialog.present(HostInfo.main_window) def get_fraction(self): - loading_status = self.parent_page.parent_page.loading_status + loading_status = self.snapshot_page.snapshotting_status loading_status.title_label.set_label(_("Applying Snapshot")) - loading_status.progress_bar.set_fraction(total / len(self.workers)) + loading_status.progress_bar.set_fraction(self.worker.fraction) + if self.worker.stop: + self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.split_view) + return False # Stop the timeout + else: + return True # Continue the timeout def on_apply(self, button): - self.parent_page.parent_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) + self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) self.worker.extract() - + GLib.timeout_add(200, self.get_fraction) 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() self.worker = TarWorker( diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index 9f62f20..81c56a9 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -49,18 +49,23 @@ class TarWorker: def extract_thread(self, *args): try: - if not os.path.exists(self.new_path): - os.makedirs(self.new_path) # create the new user data path if none exists - else: - subprocess.run('gio', 'trash', self.new_path) # trash the current user data, because new data will go in its place + if os.path.exists(self.new_path): + subprocess.run(['gio', 'trash', self.new_path], capture_output=True, check=True) # trash the current user data, because new data will go in its place + + os.makedirs(self.new_path) # create the new user data path self.total = int(subprocess.run(['du', '-s', self.existing_path], check=True, text=True, capture_output=True).stdout.split('\t')[0]) self.total *= 2.2 # estimate from space savings - self.process = subprocess.Popen(['tar', '--zstd', '-xvf', self.existing_path, '-C', self.new_path]) + self.process = subprocess.Popen(['tar', '--zstd', '-xvf', self.existing_path, '-C', self.new_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) stdout, stderr = self.process.communicate() if self.process.returncode != 0: raise subprocess.CalledProcessError(self.process.returncode, self.process.args, output=stdout, stderr=stderr) + self.stop = True # tell the check timeout to stop, because we know the file is done being made + except subprocess.CalledProcessError as cpe: print("Called Error in Extract Thread") self.do_cancel(cpe.stderr.decode(), [self.new_path]) @@ -74,7 +79,7 @@ class TarWorker: self.process.wait() if not files_to_trash is None: try: - subprocess.run(['gio', 'trash'] + files_to_trash, capture_output=True) + subprocess.run(['gio', 'trash'] + files_to_trash, capture_output=True, check=True) except Exception: pass @@ -99,5 +104,5 @@ class TarWorker: def extract(self): self.stop = False - Gio.Task.new(None, None, None).run_in_thread(self.existing_path) + Gio.Task.new(None, None, None).run_in_thread(self.extract_thread) GLib.timeout_add(200, self.check_size, self.new_path) From 1be2a7e7a72a154cd14965e7a23ce8e6f1571d33 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 22:24:36 -0400 Subject: [PATCH 208/332] Spruce up the UI for applying snapshots, and ask for confirmation --- src/snapshot_page/new_snapshot_dialog.py | 2 ++ src/snapshot_page/snapshot_box.py | 25 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index d84c64f..96ca13f 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -98,6 +98,7 @@ class NewSnapshotDialog(Adw.Dialog): def on_create(self, button): self.loading_status.title_label.set_label(_("Creating Snapshot")) + self.loading_status.progress_bar.set_fraction(0.0) self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) self.workers.clear() for row in self.selected_rows: @@ -114,6 +115,7 @@ class NewSnapshotDialog(Adw.Dialog): self.workers.append(worker) worker.compress() + self.loading_status.progress_label.set_visible(len(self.workers) > 1) GLib.timeout_add(200, self.get_total_fraction) self.close() diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index a56a6bd..ac5bef8 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -113,18 +113,35 @@ class SnapshotBox(Gtk.Box): def get_fraction(self): loading_status = self.snapshot_page.snapshotting_status - loading_status.title_label.set_label(_("Applying Snapshot")) loading_status.progress_bar.set_fraction(self.worker.fraction) if self.worker.stop: self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.split_view) + self.parent_page.set_snapshots(self.parent_page.package_or_folder, True) return False # Stop the timeout else: return True # Continue the timeout def on_apply(self, button): - self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) - self.worker.extract() - GLib.timeout_add(200, self.get_fraction) + def on_response(dialog, response): + if response != "continue": + return + + self.snapshot_page.snapshotting_status.title_label.set_label(_("Applying Snapshot")) + self.snapshot_page.snapshotting_status.progress_label.set_visible(False) + self.snapshot_page.snapshotting_status.progress_bar.set_fraction(0.0) + self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) + 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?"), + body=_("Any current user data for this app will be trashed") if has_data else "", + ) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Apply")) + dialog.connect("response", on_response) + dialog.present(HostInfo.main_window) def __init__(self, parent_page, folder, snapshots_path, toast_overlay, **kwargs): super().__init__(**kwargs) From d57f126ed073737767c15daf580cc3b54edaa51b Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 22:40:26 -0400 Subject: [PATCH 209/332] snapshots list and properties page will update eachother when data is trashed or restored --- src/host_info.py | 2 ++ src/properties_page/properties_page.py | 2 +- src/snapshot_page/snapshot_box.py | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/host_info.py b/src/host_info.py index ab4d915..72c66d8 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -44,6 +44,8 @@ class Flatpak: def trash_data(self, callback=None): try: subprocess.run(['gio', 'trash', self.data_path], capture_output=True, text=True, check=True) + snapshot_list_page = HostInfo.main_window.pages[HostInfo.main_window.snapshots_row].list_page + snapshot_list_page.set_snapshots(snapshot_list_page.package_or_folder, True) except subprocess.CalledProcessError as cpe: raise cpe except Exception as e: diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index a1e72df..f608f62 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -159,7 +159,7 @@ class PropertiesPage(Adw.NavigationPage): self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error)).toast) def trash_data_handler(self, *args): - def on_choice(_, response): + def on_choice(dialog, response): if response != 'continue': return try: diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index ac5bef8..1b980b8 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -117,6 +117,8 @@ class SnapshotBox(Gtk.Box): if self.worker.stop: self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.split_view) self.parent_page.set_snapshots(self.parent_page.package_or_folder, True) + properties_page = HostInfo.main_window.pages[HostInfo.main_window.packages_row].properties_page + properties_page.set_properties(properties_page.package, True) return False # Stop the timeout else: return True # Continue the timeout From 06a572daeb4645bac222a17aa3751b478e569c1e Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 22:59:28 -0400 Subject: [PATCH 210/332] Refresh user data page upon snapshot apply --- src/snapshot_page/snapshot_box.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 1b980b8..8a6eca2 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -119,6 +119,9 @@ class SnapshotBox(Gtk.Box): self.parent_page.set_snapshots(self.parent_page.package_or_folder, True) properties_page = HostInfo.main_window.pages[HostInfo.main_window.packages_row].properties_page properties_page.set_properties(properties_page.package, True) + data_page = HostInfo.main_window.pages[HostInfo.main_window.user_data_row] + data_page.start_loading() + data_page.end_loading() return False # Stop the timeout else: return True # Continue the timeout From db7641da42ae6242ee105069ea1e037d4ee0555f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Fri, 27 Sep 2024 23:45:34 -0400 Subject: [PATCH 211/332] Make View Snapshots in Properties Page show app snapshots, or prompt to make a new snapshot --- src/properties_page/properties_page.py | 9 +- src/snapshot_page/new_snapshot_dialog.blp | 142 +++++++++++----------- src/snapshot_page/new_snapshot_dialog.py | 1 + src/snapshot_page/snapshot_page.blp | 88 +++++++------- src/snapshot_page/snapshot_page.py | 26 +++- 5 files changed, 141 insertions(+), 125 deletions(-) diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index f608f62..2a2a347 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -267,7 +267,10 @@ class PropertiesPage(Adw.NavigationPage): self.more_menu.popdown() match row.get_child(): case self.view_snapshots: - print("not implemented") + snapshots_row = HostInfo.main_window.snapshots_row + snapshots_page = HostInfo.main_window.pages[snapshots_row] + HostInfo.main_window.activate_row(snapshots_row) + snapshots_page.show_snapshot(self.package) case self.copy_launch_command: try: @@ -319,10 +322,6 @@ class PropertiesPage(Adw.NavigationPage): self.reinstall = Gtk.Label(halign=Gtk.Align.START, label=_("Reinstall")) # Apply - self.more_list.append(self.view_snapshots) - self.more_list.append(self.copy_launch_command) - self.more_list.append(self.show_details) - self.more_list.append(self.reinstall) # Connections self.more_list.connect("row-activated", self.more_menu_handler) diff --git a/src/snapshot_page/new_snapshot_dialog.blp b/src/snapshot_page/new_snapshot_dialog.blp index 1965e62..9b3c310 100644 --- a/src/snapshot_page/new_snapshot_dialog.blp +++ b/src/snapshot_page/new_snapshot_dialog.blp @@ -4,83 +4,85 @@ using Adw 1; template $NewSnapshotDialog : Adw.Dialog { follows-content-size: true; width-request: 400; - Adw.NavigationPage nav_page { - title: "No Title Set"; - Adw.ToolbarView { - [top] - Adw.HeaderBar { - show-start-title-buttons: false; - show-end-title-buttons: false; - [start] - Button list_cancel_button { - label: _("Cancel"); - } - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Apps"); - } - [end] - Button create_button { - sensitive: false; - label: _("Create"); - styles ["suggested-action"] - } - } - [top] - Adw.Clamp { - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: template; - SearchEntry search_entry { - hexpand: true; - placeholder-text: _("Search Apps"); + Adw.ToastOverlay toast_overlay { + Adw.NavigationPage nav_page { + title: "No Title Set"; + Adw.ToolbarView { + [top] + Adw.HeaderBar { + show-start-title-buttons: false; + show-end-title-buttons: false; + [start] + Button list_cancel_button { + label: _("Cancel"); + } + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Apps"); + } + [end] + Button create_button { + sensitive: false; + label: _("Create"); + styles ["suggested-action"] } } - } - Adw.Clamp { - ScrolledWindow { - propagate-natural-height: true; - propagate-natural-width: true; - Box { - orientation: vertical; - Adw.EntryRow name_entry { - title: "No Title Set"; - margin-start: 12; - margin-end: 12; - margin-top: 12; - margin-bottom: 12; - styles ["card"] - } - ListBox listbox { - valign: start; - margin-start: 12; - margin-end: 12; - // margin-top: 12; - margin-bottom: 12; - selection-mode: none; - styles ["boxed-list"] + [top] + Adw.Clamp { + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + key-capture-widget: template; + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search Apps"); } } } - } - [bottom] - ActionBar { - revealed: bind search_button.visible; - [start] - Button select_all_button { - styles ["raised"] - Adw.ButtonContent { - label: _("Select All"); - icon-name: "selection-mode-symbolic"; + Adw.Clamp { + ScrolledWindow { + propagate-natural-height: true; + propagate-natural-width: true; + Box { + orientation: vertical; + Adw.EntryRow name_entry { + title: "No Title Set"; + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + styles ["card"] + } + ListBox listbox { + valign: start; + margin-start: 12; + margin-end: 12; + // margin-top: 12; + margin-bottom: 12; + selection-mode: none; + styles ["boxed-list"] + } + } } } - [end] - Label total_selected_label { - label: ""; - ellipsize: middle; - margin-end: 6; - visible: false; + [bottom] + ActionBar { + revealed: bind search_button.visible; + [start] + Button select_all_button { + styles ["raised"] + Adw.ButtonContent { + label: _("Select All"); + icon-name: "selection-mode-symbolic"; + } + } + [end] + Label total_selected_label { + label: ""; + ellipsize: middle; + margin-end: 6; + visible: false; + } } } } diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 96ca13f..f39487a 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -11,6 +11,7 @@ class NewSnapshotDialog(Adw.Dialog): __gtype_name__ = "NewSnapshotDialog" gtc = Gtk.Template.Child + toast_overlay = gtc() nav_page = gtc() list_cancel_button = gtc() search_button = gtc() diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 4361619..7910808 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -16,14 +16,14 @@ template $SnapshotPage : Adw.BreakpointBin { Adw.NavigationPage { title: _("Snapshots"); - Stack status_stack { - Adw.NavigationSplitView split_view { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage sidebar_navpage { - title: _("Snapshots"); - Adw.ToastOverlay toast_overlay { + Adw.ToastOverlay toast_overlay { + Stack status_stack { + Adw.NavigationSplitView split_view { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage sidebar_navpage { + title: _("Snapshots"); Adw.ToolbarView sidebar_tbv { [top] Adw.HeaderBar header_bar { @@ -132,48 +132,48 @@ template $SnapshotPage : Adw.BreakpointBin { } } } - } - ; - } - Adw.ToolbarView no_snapshots { - [top] - Adw.HeaderBar { - [start] - $SidebarButton {} - [start] - Button status_open_button { - icon-name: "folder-open-symbolic"; - tooltip-text: _("Open Snapshots Folder"); - } + ; } - Adw.ToastOverlay no_snapshots_toast { - Adw.StatusPage { - title: _("No Snapshots"); - description: _("Create a Snapshot to save the state of any Flatpak application"); - icon-name: "snapshots-alt-symbolic"; - Button status_new_button { - styles ["suggested-action", "pill"] - halign: center; - Adw.ButtonContent { - icon-name: "plus-large-symbolic"; - label: _("New Snapshot"); + Adw.ToolbarView no_snapshots { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + [start] + Button status_open_button { + icon-name: "folder-open-symbolic"; + tooltip-text: _("Open Snapshots Folder"); + } + } + Adw.ToastOverlay no_snapshots_toast { + Adw.StatusPage { + title: _("No Snapshots"); + description: _("Create a Snapshot to save the state of any Flatpak application"); + icon-name: "snapshots-alt-symbolic"; + Button status_new_button { + styles ["suggested-action", "pill"] + halign: center; + Adw.ButtonContent { + icon-name: "plus-large-symbolic"; + label: _("New Snapshot"); + } } } } } - } - Adw.ToolbarView loading_view { - [top] - Adw.HeaderBar { - [start] - $SidebarButton {} + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + } } - } - Adw.ToolbarView snapshotting_view { - [top] - Adw.HeaderBar { - [start] - $SidebarButton {} + Adw.ToolbarView snapshotting_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + } } } } diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index c752490..7465e1e 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -34,7 +34,6 @@ class SnapshotPage(Adw.BreakpointBin): gtc = Gtk.Template.Child toast_overlay = gtc() - no_snapshots_toast = gtc() active_box = gtc() active_listbox = gtc() leftover_box = gtc() @@ -140,6 +139,21 @@ class SnapshotPage(Adw.BreakpointBin): elif row := self.leftover_listbox.get_row_at_index(0): self.leftover_listbox.select_row(row) self.leftover_select_handler(None, row, False, True) + + def show_snapshot(self, package): + i = 0 + while row := self.active_listbox.get_row_at_index(i): + i += 1 + if row.package is package: + self.active_listbox.select_row(row) + self.active_select_handler(None, row, True) + self.toast_overlay.add_toast(Adw.Toast(title=_("Showing snapshots for {}").format(package.info['name']))) + break + else: + dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh, package) + 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) @@ -162,12 +176,12 @@ class SnapshotPage(Adw.BreakpointBin): Gio.Task.new(None, None, callback).run_in_thread(self.sort_snapshots) - def open_snapshots_folder(self, button, overlay): + def open_snapshots_folder(self, button): try: Gio.AppInfo.launch_default_for_uri(f"file://{HostInfo.snapshots_path}", None) - overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) + self.toast_overlay.add_toast(Adw.Toast.new(_("Opened snapshots folder"))) except Exception as e: - overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) + self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) def on_cancel(self): for worker in self.new_snapshot_dialog.workers: @@ -196,8 +210,8 @@ class SnapshotPage(Adw.BreakpointBin): # Connections 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, self.toast_overlay) - self.status_open_button.connect("clicked", self.open_snapshots_folder, self.no_snapshots_toast) + self.open_button.connect("clicked", self.open_snapshots_folder) + self.status_open_button.connect("clicked", self.open_snapshots_folder) self.status_new_button.connect("clicked", self.on_new) self.new_button.connect("clicked", self.on_new) From df7f8689af43ec68c392aa3142b303e1e7a3dcbc Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 14:29:27 -0400 Subject: [PATCH 212/332] Add ability to search --- src/snapshot_page/snapshot_page.blp | 17 ++++++++-------- src/snapshot_page/snapshot_page.py | 31 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/snapshot_page/snapshot_page.blp b/src/snapshot_page/snapshot_page.blp index 7910808..92b2ee1 100644 --- a/src/snapshot_page/snapshot_page.blp +++ b/src/snapshot_page/snapshot_page.blp @@ -50,16 +50,15 @@ template $SnapshotPage : Adw.BreakpointBin { tooltip-text: _("Select Packages"); } } + [top] + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search Snapshots"); + } + } Stack stack { - // Adw.StatusPage loading_snapshots { - // title: _("Loading Snapshot"); - // description: _("This should only take a moment"); - // child: - // Spinner { - // spinning: true; - // } - // ; - // } Adw.StatusPage no_results { title: _("No Results Found"); description: _("Try a different search"); diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 7465e1e..dae2047 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -34,6 +34,8 @@ class SnapshotPage(Adw.BreakpointBin): gtc = Gtk.Template.Child toast_overlay = gtc() + search_entry = gtc() + search_bar = gtc() active_box = gtc() active_listbox = gtc() leftover_box = gtc() @@ -194,6 +196,33 @@ class SnapshotPage(Adw.BreakpointBin): self.start_loading() self.end_loading() + def on_search(self, search_entry): + text = search_entry.get_text().lower() + i = 0 + total_active_visible = 0 + while row := self.active_listbox.get_row_at_index(i): + i += 1 + row.set_visible(False) + if text in row.get_title().lower(): + row.set_visible(True) + total_active_visible += 1 + self.active_box.set_visible(total_active_visible > 0) + + i = 0 + total_leftover_visible = 0 + while row := self.leftover_listbox.get_row_at_index(i): + i += 1 + row.set_visible(False) + if text in row.get_title().lower(): + row.set_visible(True) + total_leftover_visible += 1 + self.leftover_box.set_visible(total_leftover_visible > 0) + + if total_active_visible > 0 or total_leftover_visible > 0: + self.stack.set_visible_child(self.scrolled_window) + else: + self.stack.set_visible_child(self.no_results) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -214,8 +243,10 @@ class SnapshotPage(Adw.BreakpointBin): self.status_open_button.connect("clicked", self.open_snapshots_folder) 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) # 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.snapshotting_status.button.set_label(_("Cancel")) From 6d62f7ffc047ec3a8a6568312bd7ecb5b622e5fd Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 14:45:36 -0400 Subject: [PATCH 213/332] Sort snapshot folder entries by name --- src/snapshot_page/snapshot_page.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index dae2047..f86d7e8 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -222,6 +222,12 @@ class SnapshotPage(Adw.BreakpointBin): self.stack.set_visible_child(self.scrolled_window) else: self.stack.set_visible_child(self.no_results) + + def sort_func(self, row1, row2): + if type(row1) is AppRow: + return row1.package.info['name'].lower() > row2.package.info['name'].lower() + else: + return row1.name.lower() > row2.name.lower() def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -235,7 +241,7 @@ class SnapshotPage(Adw.BreakpointBin): # self.leftover_rows = [] self.list_page = SnapshotsListPage(self) self.snapshotting_status = LoadingStatus("Initial Title", _("This might take a while"), True, self.on_cancel) - + # Connections self.active_listbox.connect("row-activated", self.active_select_handler) self.leftover_listbox.connect("row-activated", self.leftover_select_handler) @@ -244,10 +250,12 @@ 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) - + # 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.snapshotting_status.button.set_label(_("Cancel")) 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) From aa5a787d576730410668eb7381302903068014d0 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 15:38:53 -0400 Subject: [PATCH 214/332] Show status page when no results are found in the dialog --- src/snapshot_page/new_snapshot_dialog.blp | 9 ++++++-- src/snapshot_page/new_snapshot_dialog.py | 28 +++++++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.blp b/src/snapshot_page/new_snapshot_dialog.blp index 9b3c310..763284e 100644 --- a/src/snapshot_page/new_snapshot_dialog.blp +++ b/src/snapshot_page/new_snapshot_dialog.blp @@ -39,8 +39,8 @@ template $NewSnapshotDialog : Adw.Dialog { } } } - Adw.Clamp { - ScrolledWindow { + Stack stack { + ScrolledWindow scrolled_window { propagate-natural-height: true; propagate-natural-width: true; Box { @@ -64,6 +64,11 @@ template $NewSnapshotDialog : Adw.Dialog { } } } + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; + } } [bottom] ActionBar { diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index f39487a..d0a32a3 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -21,6 +21,9 @@ class NewSnapshotDialog(Adw.Dialog): listbox = gtc() select_all_button = gtc() total_selected_label = gtc() + scrolled_window = gtc() + no_results = gtc() + stack = gtc() def row_gesture_handler(self, row): row.check_button.set_active(not row.check_button.get_active()) @@ -58,7 +61,11 @@ class NewSnapshotDialog(Adw.Dialog): title = row.get_title().lower() subtitle = row.get_subtitle().lower() search = self.search_entry.get_text().lower() - return search in title or search in subtitle + if search in title or search in subtitle: + self.is_result = True + return True + else: + return False def on_close(self, *args): self.search_button.set_active(False) @@ -67,14 +74,15 @@ class NewSnapshotDialog(Adw.Dialog): def valid_checker(self): text = self.name_entry.get_text().strip() - valid = len(self.selected_rows) > 0 and len(text) > 0 and not("/" in text or "\0" in text) - self.create_button.set_sensitive(valid) - if valid: + something_selected = len(self.selected_rows) > 0 + text_good = len(text) > 0 and not("/" in text or "\0" in text) + self.create_button.set_sensitive(something_selected and text_good) + if text_good: self.name_entry.remove_css_class("error") else: self.name_entry.add_css_class("error") - return valid + return something_selected and text_good def get_total_fraction(self): total = 0 @@ -121,7 +129,12 @@ class NewSnapshotDialog(Adw.Dialog): self.close() def on_invalidate(self, search_entry): + self.is_result = False self.listbox.invalidate_filter() + if self.is_result: + self.stack.set_visible_child(self.scrolled_window) + else: + self.stack.set_visible_child(self.no_results) def on_select_all(self, button): i = 0 @@ -134,10 +147,6 @@ class NewSnapshotDialog(Adw.Dialog): row.set_activatable(False) self.selected_rows.append(row) self.listbox.append(row) - - def present(self, *args, **kwargs): - super().present(*args, **kwargs) - self.name_entry.grab_focus() def enter_handler(self, *args): if self.create_button.get_sensitive(): @@ -150,6 +159,7 @@ class NewSnapshotDialog(Adw.Dialog): self.snapshot_page = snapshot_page self.loading_status = loading_status self.on_done = on_done + self.is_result = False self.rows = [] self.selected_rows = [] self.workers = [] From 01c2f4616feeda1a084537dad706c4dde9288bd6 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 16:00:34 -0400 Subject: [PATCH 215/332] Use error toast for snapshotting --- src/snapshot_page/new_snapshot_dialog.py | 1 + src/snapshot_page/snapshot_box.py | 1 + src/snapshot_page/tar_worker.py | 12 +++++++----- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index d0a32a3..4d768ed 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -120,6 +120,7 @@ class NewSnapshotDialog(Adw.Dialog): new_path=f"{HostInfo.snapshots_path}{package.info['id']}", file_name=f"{int(time.time())}_{package.info["version"]}", name=self.name_entry.get_text(), + toast_overlay=self.snapshot_page.toast_overlay, ) self.workers.append(worker) worker.compress() diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 8a6eca2..16160ae 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -158,6 +158,7 @@ class SnapshotBox(Gtk.Box): existing_path=f"{snapshots_path}{folder}", new_path=f"{HostInfo.home}/.var/app/{self.app_id}/", file_name=self.app_id, + toast_overlay=self.toast_overlay, ) split_folder = folder.split('_') diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index 81c56a9..95696b4 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -4,7 +4,7 @@ from .error_toast import ErrorToast import os, tarfile, subprocess, json class TarWorker: - def __init__(self, existing_path, new_path, file_name, name=""): + def __init__(self, existing_path, new_path, file_name, name="", toast_overlay=None): self.existing_path = existing_path self.new_path = new_path self.file_name = file_name @@ -14,6 +14,7 @@ class TarWorker: self.fraction = 0.0 self.total = 0 self.process = None + self.toast_overlay = toast_overlay def compress_thread(self, *args): try: @@ -80,13 +81,14 @@ class TarWorker: if not files_to_trash is None: try: subprocess.run(['gio', 'trash'] + files_to_trash, capture_output=True, check=True) - + except Exception: pass - + self.stop = True - print("Error in cancelling:", error_str) - + if self.toast_overlay: + self.toast_overlay.add_toast(ErrorToast(_("Error in snapshot handling"), error_str).toast) + def check_size(self, check_path): try: output = subprocess.run(['du', '-s', check_path], check=True, text=True, capture_output=True).stdout.split('\t')[0] From f475390b287dbdb2a456361faee476d13b2f0c9d Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 17:02:34 -0400 Subject: [PATCH 216/332] 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) From 093e40f621117e84ad0cbc25e428b77283a07d15 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 17:14:28 -0400 Subject: [PATCH 217/332] Add selection gestures to snapshot rows --- src/snapshot_page/snapshot_page.py | 31 ++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index eacb057..582df01 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -17,16 +17,31 @@ class LeftoverSnapshotRow(Adw.ActionRow): icon.set_icon_size(Gtk.IconSize.LARGE) self.add_prefix(icon) self.add_suffix(self.check_button) + + def gesture_handler(self, *args): + self.on_long_press(self) - def __init__(self, folder, **kwargs): + def __init__(self, folder, on_long_press, **kwargs): super().__init__(**kwargs) + # Extra Object Creation self.folder = folder - self.set_activatable(True) - self.name = self.folder.split('.')[-1] self.check_button = Gtk.CheckButton(visible=False) + self.on_long_press = on_long_press + self.rclick_gesture = Gtk.GestureClick(button=3) + self.long_press_gesture = Gtk.GestureLongPress() + + # Apply + self.add_controller(self.rclick_gesture) + self.add_controller(self.long_press_gesture) self.check_button.add_css_class("selection-mode") + self.name = self.folder.split('.')[-1] + self.set_activatable(True) GLib.idle_add(lambda *_: self.idle_stuff()) + + # 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): @@ -106,10 +121,14 @@ 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) + row = AppRow(pak, self.long_press_handler) row.check_button.connect("toggled", lambda *_, _row=row: self.row_select_handler(_row)) self.active_listbox.append(row) @@ -122,9 +141,9 @@ class SnapshotPage(Adw.BreakpointBin): def generate_leftover_list(self): for folder in self.leftover_snapshots: - row = LeftoverSnapshotRow(folder) + row = LeftoverSnapshotRow(folder, self.long_press_handler) row.check_button.connect("toggled", lambda *_, _row=row: self.row_select_handler(_row)) - self.leftover_listbox.append(row) + self.leftover_listbox.append(row) if len(self.leftover_snapshots) > 0: self.leftover_box.set_visible(True) From 93df6e9bc10076c935e96b3d7c7e1e465cd8e2c6 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 17:46:44 -0400 Subject: [PATCH 218/332] Add batch trash ability --- src/snapshot_page/snapshot_box.py | 2 +- src/snapshot_page/snapshot_page.py | 41 ++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 16160ae..11f87a9 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -104,7 +104,7 @@ class SnapshotBox(Gtk.Box): Gio.Task.new(None, None, callback).run_in_thread(thread) - dialog = Adw.AlertDialog(heading=_("Trash Snapshot?"), body=_("This snapshot will be moved to the trash")) + 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")) dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 582df01..c271d92 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -72,6 +72,7 @@ class SnapshotPage(Adw.BreakpointBin): select_all_button = gtc() copy_button = gtc() more_button = gtc() + more_popover = gtc() more_menu = gtc() new_snapshots = gtc() apply_snapshots = gtc() @@ -188,11 +189,14 @@ class SnapshotPage(Adw.BreakpointBin): self.toast_overlay.add_toast(toast) def start_loading(self): + self.select_button.set_active(False) 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() + self.selected_active_rows.clear() + self.selected_leftover_rows.clear() def end_loading(self): def callback(*args): @@ -206,8 +210,6 @@ 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): @@ -325,6 +327,40 @@ class SnapshotPage(Adw.BreakpointBin): HostInfo.clipboard.set(to_copy) self.toast_overlay.add_toast(Adw.Toast(title=_("Copied Snapshot Paths"))) + def select_trash_handler(self): + def on_response(dialog, response): + to_trash = [] + if response != "continue": + return + + for row in self.selected_active_rows: + to_trash.append(f"{HostInfo.snapshots_path}{row.package.info['id']}") + + for row in self.selected_leftover_rows: + to_trash.append(f"{HostInfo.snapshots_path}{row.folder}") + + try: + subprocess.run(['gio', 'trash'] + to_trash, check=True, text=True, capture_output=True) + self.start_loading() + self.end_loading() + 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) + + dialog = Adw.AlertDialog(heading=_("Trash Snapshots?"), body=_("These snapshots will be sent to the trash")) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Trash")) + dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.connect("response", on_response) + dialog.present(HostInfo.main_window) + + def more_menu_handler(self, listbox, row): + self.more_popover.popdown() + row = row.get_child() + match row: + case self.trash_snapshots: + self.select_trash_handler() + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) @@ -351,6 +387,7 @@ class SnapshotPage(Adw.BreakpointBin): 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) + self.more_menu.connect("row-activated", self.more_menu_handler) # Apply self.search_bar.set_key_capture_widget(HostInfo.main_window) From c0b74de74c0ae40f908fab19f04f4450931b9b83 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 19:57:48 -0400 Subject: [PATCH 219/332] Allow batch creation of new snapshots for already snapshotted apps --- src/snapshot_page/new_snapshot_dialog.py | 23 +++++++++++++---------- src/snapshot_page/snapshot_page.py | 15 +++++++++++++-- src/snapshot_page/snapshots_list_page.py | 2 +- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 4d768ed..dfa1190 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -143,17 +143,18 @@ class NewSnapshotDialog(Adw.Dialog): i += 1 row.check_button.set_active(True) - def set_single(self, package): - row = AppRow(package) - row.set_activatable(False) - self.selected_rows.append(row) - self.listbox.append(row) + def set_packages(self, packages): + for package in packages: + row = AppRow(package) + 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 __init__(self, snapshot_page, loading_status, on_done=None, package=None, **kwargs): + def __init__(self, snapshot_page, loading_status, on_done=None, packages=None, **kwargs): super().__init__(**kwargs) # Extra Object Creations @@ -177,13 +178,15 @@ class NewSnapshotDialog(Adw.Dialog): # Apply self.listbox.set_sort_func(self.sort_func) self.listbox.set_filter_func(self.filter_func) - if not package is None: + self.name_entry.set_title(_("Name these Snapshots")) + if not packages is None: self.search_entry.set_editable(False) self.search_button.set_visible(False) self.nav_page.set_title(_("New Snapshot")) - self.name_entry.set_title(_("Name this Snapshot")) - self.set_single(package) + if len(packages) == 1: + self.name_entry.set_title(_("Name this Snapshot")) + self.set_packages(packages) + self.no_results.set_visible(False) else: self.nav_page.set_title(_("New Snapshots")) - self.name_entry.set_title(_("Name these Snapshots")) self.generate_list() diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index c271d92..99c5835 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -6,6 +6,7 @@ from .snapshots_list_page import SnapshotsListPage from .sidebar_button import SidebarButton from .loading_status import LoadingStatus from .new_snapshot_dialog import NewSnapshotDialog +from .tar_worker import TarWorker import os, subprocess class LeftoverSnapshotRow(Adw.ActionRow): @@ -183,7 +184,7 @@ class SnapshotPage(Adw.BreakpointBin): self.toast_overlay.add_toast(Adw.Toast(title=_("Showing snapshots for {}").format(package.info['name']))) break else: - dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh, package) + dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh, [package]) 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) @@ -347,17 +348,27 @@ class SnapshotPage(Adw.BreakpointBin): except subprocess.CalledProcessError as cpe: self.toast_overlay.add_toast(ErrorToast(_("Could not trash snapshots"), cpe.stderr).toast) - dialog = Adw.AlertDialog(heading=_("Trash Snapshots?"), body=_("These snapshots will be sent to the trash")) + 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")) dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) dialog.connect("response", on_response) dialog.present(HostInfo.main_window) + def select_new_handler(self): + packages = [] + for row in self.selected_active_rows: + if os.path.exists(row.package.data_path): + packages.append(row.package) + + NewSnapshotDialog(self, self.snapshotting_status, self.refresh, packages).present(HostInfo.main_window) + def more_menu_handler(self, listbox, row): self.more_popover.popdown() row = row.get_child() match row: + case self.new_snapshots: + self.select_new_handler() case self.trash_snapshots: self.select_trash_handler() diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 1015230..22424d1 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -71,7 +71,7 @@ class SnapshotsListPage(Adw.NavigationPage): self.set_snapshots(self.package_or_folder, refresh=True) def on_new(self, button): - dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, self.package_or_folder) + dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, [self.package_or_folder]) dialog.present(HostInfo.main_window) def sort_func(self, row1, row2): From 20bcc9fd2415bfa8518f37369be0afb4be704426 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 22:21:05 -0400 Subject: [PATCH 220/332] Allow for batch snapshot applying --- src/snapshot_page/new_snapshot_dialog.py | 3 +- src/snapshot_page/snapshot_box.py | 1 - src/snapshot_page/snapshot_page.py | 116 +++++++++++++++++++++-- src/snapshot_page/tar_worker.py | 4 +- 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index dfa1190..082d206 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -100,8 +100,7 @@ class NewSnapshotDialog(Adw.Dialog): return False - self.loading_status.progress_label.set_label(f"{stopped_workers_amount} / {len(self.workers)}") - + self.loading_status.progress_label.set_label(f"{stopped_workers_amount + 1} / {len(self.workers)}") self.loading_status.progress_bar.set_fraction(total / len(self.workers)) return True diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index 11f87a9..bb1db34 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -157,7 +157,6 @@ class SnapshotBox(Gtk.Box): self.worker = TarWorker( existing_path=f"{snapshots_path}{folder}", new_path=f"{HostInfo.home}/.var/app/{self.app_id}/", - file_name=self.app_id, toast_overlay=self.toast_overlay, ) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 99c5835..a6af720 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -190,6 +190,7 @@ class SnapshotPage(Adw.BreakpointBin): self.toast_overlay.add_toast(toast) def start_loading(self): + self.workers.clear() self.select_button.set_active(False) self.status_stack.set_visible_child(self.loading_view) self.active_box.set_visible(True) @@ -307,8 +308,14 @@ class SnapshotPage(Adw.BreakpointBin): 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.new_snapshots.set_visible(total_active > 0) self.copy_button.set_sensitive(total > 0) self.more_button.set_sensitive(total > 0) + i = 0 + while row := self.more_menu.get_row_at_index(i): + i += 1 + if row.get_child() is self.new_snapshots: + row.set_visible(total_active > 0) def select_copy_handler(self, *args): to_copy = "" @@ -328,6 +335,102 @@ class SnapshotPage(Adw.BreakpointBin): HostInfo.clipboard.set(to_copy) self.toast_overlay.add_toast(Adw.Toast(title=_("Copied Snapshot Paths"))) + def select_new_handler(self): + packages = [] + for row in self.selected_active_rows: + if os.path.exists(row.package.data_path): + packages.append(row.package) + + if len(packages) == 0: + self.toast_overlay.add_toast(Adw.Toast(title=_("No apps in your selection can be snapshotted"))) + return + + NewSnapshotDialog(self, self.snapshotting_status, self.refresh, packages).present(HostInfo.main_window) + + def get_snapshots_from_entry(self, app_ids): + id_to_tar = {} + for app_id in app_ids: + path = f"{HostInfo.snapshots_path}{app_id}" + if not os.path.exists: + continue + + tarlist = [] + for file in os.listdir(path): + if file.endswith(".tar.zst"): + tarlist.append(file) + + 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): + total = 0 + stopped_workers_amount = 0 + for worker in self.workers: + total += worker.fraction + if worker.stop: + stopped_workers_amount += 1 + + if stopped_workers_amount == len(self.workers): + self.snapshotting_status.progress_bar.set_fraction(1) + self.snapshotting_status.progress_label.set_label(f"{len(self.workers)} / {len(self.workers)}") + HostInfo.main_window.refresh_handler() + return False + + self.snapshotting_status.progress_label.set_label(f"{stopped_workers_amount + 1} / {len(self.workers)}") + self.snapshotting_status.progress_bar.set_fraction(total / len(self.workers)) + return True + + def on_apply_response(self, dialog, response): + if response != "continue": + return + + app_ids = [] + for row in self.selected_active_rows: + app_ids.append(row.package.info['id']) + + for row in self.selected_leftover_rows: + app_ids.append(row.folder) + + id_to_tar = self.get_snapshots_from_entry(app_ids) + for app_id in id_to_tar: + biggest = 0 + biggest_tar = "" + for tar in id_to_tar[app_id]: + epoch = int(tar.split('_')[0]) + if epoch > biggest: + biggest = epoch + 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}", + new_path=f"{HostInfo.home}/.var/app/{app_id}/", + toast_overlay=self.toast_overlay, + ) + 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) + self.snapshotting_status.progress_label.set_visible(len(self.workers) > 1) + self.status_stack.set_visible_child(self.snapshotting_view) + GLib.timeout_add(200, self.get_total_fraction) + else: + self.toast_overlay.add_toast(ErrorToast(_("No snapshots to extract"), _("No snapshots were found to extract"))) + + def select_apply_handler(self): + dialog = Adw.AlertDialog(heading=_("Apply These Snapshots?"), body=_("This will trash the current apps' user data, and apply their newest snapshot")) + dialog.add_response("cancel", _("Cancel")) + dialog.add_response("continue", _("Continue")) + dialog.connect("response", self.on_apply_response) + dialog.present(HostInfo.main_window) + def select_trash_handler(self): def on_response(dialog, response): to_trash = [] @@ -336,7 +439,7 @@ class SnapshotPage(Adw.BreakpointBin): for row in self.selected_active_rows: to_trash.append(f"{HostInfo.snapshots_path}{row.package.info['id']}") - + for row in self.selected_leftover_rows: to_trash.append(f"{HostInfo.snapshots_path}{row.folder}") @@ -355,20 +458,14 @@ class SnapshotPage(Adw.BreakpointBin): dialog.connect("response", on_response) dialog.present(HostInfo.main_window) - def select_new_handler(self): - packages = [] - for row in self.selected_active_rows: - if os.path.exists(row.package.data_path): - packages.append(row.package) - - NewSnapshotDialog(self, self.snapshotting_status, self.refresh, packages).present(HostInfo.main_window) - def more_menu_handler(self, listbox, row): self.more_popover.popdown() row = row.get_child() match row: case self.new_snapshots: self.select_new_handler() + case self.apply_snapshots: + self.select_apply_handler() case self.trash_snapshots: self.select_trash_handler() @@ -381,6 +478,7 @@ class SnapshotPage(Adw.BreakpointBin): self.active_snapshot_paks = [] self.selected_active_rows = [] self.selected_leftover_rows = [] + self.workers = [] # self.active_rows = [] self.leftover_snapshots = [] # self.leftover_rows = [] diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index 95696b4..0208594 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -4,7 +4,7 @@ from .error_toast import ErrorToast import os, tarfile, subprocess, json class TarWorker: - def __init__(self, existing_path, new_path, file_name, name="", toast_overlay=None): + def __init__(self, existing_path, new_path, file_name="", name="", toast_overlay=None): self.existing_path = existing_path self.new_path = new_path self.file_name = file_name @@ -92,7 +92,7 @@ class TarWorker: def check_size(self, check_path): try: output = subprocess.run(['du', '-s', check_path], check=True, text=True, capture_output=True).stdout.split('\t')[0] - working_total = int(output) + working_total = float(output) self.fraction = working_total / self.total return not self.stop From 66be56f785fa896a2e68881d9828f50fecd5420d Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 22:42:33 -0400 Subject: [PATCH 221/332] Focus name entry if search is disabled --- src/snapshot_page/new_snapshot_dialog.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 082d206..7b10ac1 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -142,8 +142,8 @@ class NewSnapshotDialog(Adw.Dialog): i += 1 row.check_button.set_active(True) - def set_packages(self, packages): - for package in packages: + def set_packages(self): + for package in self.packages: row = AppRow(package) row.set_activatable(False) self.selected_rows.append(row) @@ -152,6 +152,11 @@ class NewSnapshotDialog(Adw.Dialog): def enter_handler(self, *args): if self.create_button.get_sensitive(): self.create_button.activate() + + def present(self, *args, **kwargs): + super().present(*args, **kwargs) + 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) @@ -164,6 +169,7 @@ class NewSnapshotDialog(Adw.Dialog): self.rows = [] self.selected_rows = [] self.workers = [] + self.packages = packages # Connections self.connect("closed", self.on_close) @@ -182,10 +188,10 @@ class NewSnapshotDialog(Adw.Dialog): self.search_entry.set_editable(False) self.search_button.set_visible(False) self.nav_page.set_title(_("New Snapshot")) + self.set_packages() + self.no_results.set_visible(False) if len(packages) == 1: self.name_entry.set_title(_("Name this Snapshot")) - self.set_packages(packages) - self.no_results.set_visible(False) else: self.nav_page.set_title(_("New Snapshots")) self.generate_list() From 9916c0a216ddb1d5aed2ad11643ad719d97d5c4a Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 22:57:58 -0400 Subject: [PATCH 222/332] Disable and clear search upon refresh --- src/snapshot_page/snapshot_page.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index a6af720..fa54024 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -51,9 +51,10 @@ class SnapshotPage(Adw.BreakpointBin): toast_overlay = gtc() sidebar_navpage = gtc() + search_button = gtc() + select_button = gtc() search_entry = gtc() search_bar = gtc() - select_button = gtc() active_box = gtc() active_listbox = gtc() leftover_box = gtc() @@ -190,6 +191,7 @@ class SnapshotPage(Adw.BreakpointBin): self.toast_overlay.add_toast(toast) def start_loading(self): + self.search_button.set_active(False) self.workers.clear() self.select_button.set_active(False) self.status_stack.set_visible_child(self.loading_view) From fa4983def48ab1a5fced69dc7713b21776b0130f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 22:58:15 -0400 Subject: [PATCH 223/332] Use full page loading status --- src/packages_page/packages_page.blp | 232 ++++++++++++++-------------- src/packages_page/packages_page.py | 17 +- 2 files changed, 123 insertions(+), 126 deletions(-) diff --git a/src/packages_page/packages_page.blp b/src/packages_page/packages_page.blp index 3d8e264..ffd3887 100644 --- a/src/packages_page/packages_page.blp +++ b/src/packages_page/packages_page.blp @@ -16,136 +16,130 @@ template $PackagesPage : Adw.BreakpointBin { } } - Adw.NavigationSplitView packages_split { - sidebar-width-fraction: 0.5; - max-sidebar-width: 999999999; - sidebar: - Adw.NavigationPage packages_navpage { - title: _("Packages"); - Adw.ToastOverlay packages_toast_overlay { - Adw.ToolbarView packages_tbv { - [top] - Adw.HeaderBar { - [start] - $SidebarButton {} - [start] - ToggleButton search_button { - icon-name: "loupe-large-symbolic"; - tooltip-text: _("Search Packages"); - } - [end] - ToggleButton filter_button { - icon-name: "funnel-symbolic"; - tooltip-text: _("Filter Packages"); - } - [end] - ToggleButton select_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Select Packages"); - } - } - [top] - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - SearchEntry search_entry { - hexpand: true; - placeholder-text: _("Search Packages"); - } - } - Stack status_stack { - // Adw.StatusPage loading_packages { - // title: _("Loading Packages"); - // description: _("This should only take a moment"); - // child: - // Spinner { - // spinning: true; - // } - // ; - // } - ScrolledWindow scrolled_window { - ListBox packages_list_box { - styles ["navigation-sidebar"] - } - } - // Adw.StatusPage uninstalling { - // title: _("Uninstalling Packages"); - // description: _("This should only take a moment"); - // child: - // Spinner { - // spinning: true; - // } - // ; - // } - Adw.StatusPage no_filter_results { - title: _("No Packages Match Filters"); - description: _("No installed package matches all of the currently applied filters"); - icon-name: "funnel-symbolic"; - Button reset_filters_button { - label: _("Reset Filters"); - halign: center; - visible: false; - styles ["pill"] - } - } - Adw.StatusPage no_packages { - title: _("No Packages Found"); - description: _("Warehouse cannot see the list of installed packages or your system has no packages installed"); - icon-name: "error-symbolic"; - } - Adw.StatusPage no_results { - title: _("No Results Found"); - description: _("Try a different search"); - icon-name: "system-search-symbolic"; - } - } - [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 { + Adw.NavigationPage { + title: _("Packages"); + Stack stack { + Adw.ToolbarView loading_view { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + } + } + Adw.NavigationSplitView packages_split { + sidebar-width-fraction: 0.5; + max-sidebar-width: 999999999; + sidebar: + Adw.NavigationPage packages_navpage { + title: _("Packages"); + Adw.ToastOverlay packages_toast_overlay { + Adw.ToolbarView packages_tbv { + [top] + Adw.HeaderBar { + [start] + $SidebarButton {} + [start] + ToggleButton search_button { + icon-name: "loupe-large-symbolic"; + tooltip-text: _("Search Packages"); + } + [end] + ToggleButton filter_button { + icon-name: "funnel-symbolic"; + tooltip-text: _("Filter Packages"); + } + [end] + ToggleButton select_button { icon-name: "selection-mode-symbolic"; - label: _("Select All"); - can-shrink: true; + tooltip-text: _("Select Packages"); } } - MenuButton copy_button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "edit-copy-symbolic"; - label: _("Copy"); - can-shrink: true; + [top] + SearchBar search_bar { + search-mode-enabled: bind search_button.active bidirectional; + SearchEntry search_entry { + hexpand: true; + placeholder-text: _("Search Packages"); } - popover: copy_pop; } - Button uninstall_button { - styles ["raised"] - Adw.ButtonContent { - icon-name: "user-trash-symbolic"; - label: _("Uninstall"); - can-shrink: true; + Stack status_stack { + ScrolledWindow scrolled_window { + ListBox packages_list_box { + styles ["navigation-sidebar"] + } + } + Adw.StatusPage no_filter_results { + title: _("No Packages Match Filters"); + description: _("No installed package matches all of the currently applied filters"); + icon-name: "funnel-symbolic"; + Button reset_filters_button { + label: _("Reset Filters"); + halign: center; + visible: false; + styles ["pill"] + } + } + Adw.StatusPage no_packages { + title: _("No Packages Found"); + description: _("Warehouse cannot see the list of installed packages or your system has no packages installed"); + icon-name: "error-symbolic"; + } + Adw.StatusPage no_results { + title: _("No Results Found"); + description: _("Try a different search"); + icon-name: "system-search-symbolic"; + } + } + [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; + } + } + MenuButton copy_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "edit-copy-symbolic"; + label: _("Copy"); + can-shrink: true; + } + popover: copy_pop; + } + Button uninstall_button { + styles ["raised"] + Adw.ButtonContent { + icon-name: "user-trash-symbolic"; + label: _("Uninstall"); + can-shrink: true; + } + } } } } } } - } + ; + content: + Adw.NavigationPage { + title: "Content Stack"; + Stack content_stack { + transition-type: slide_left_right; + } + } + ; } - ; - content: - Adw.NavigationPage { - title: "Content Stack"; - Stack content_stack { - transition-type: slide_left_right; - } - } - ; + } } } diff --git a/src/packages_page/packages_page.py b/src/packages_page/packages_page.py index e50c39b..addbeda 100644 --- a/src/packages_page/packages_page.py +++ b/src/packages_page/packages_page.py @@ -15,10 +15,10 @@ class PackagesPage(Adw.BreakpointBin): gtc = Gtk.Template.Child packages_bpt = gtc() packages_toast_overlay = gtc() + stack = gtc() status_stack = gtc() scrolled_window = gtc() - # uninstalling = gtc() - # loading_packages = gtc() + loading_view = gtc() no_filter_results = gtc() reset_filters_button = gtc() no_packages = gtc() @@ -60,7 +60,7 @@ class PackagesPage(Adw.BreakpointBin): else: self.select_button.set_sensitive(False) - if to_set is self.loading_packages or to_set is self.uninstalling: + if to_set is self.uninstalling: self.properties_page.stack.set_visible_child(self.properties_page.loading_tbv) self.filter_button.set_sensitive(False) self.filters_page.set_sensitive(False) @@ -84,8 +84,12 @@ class PackagesPage(Adw.BreakpointBin): if to_set is self.no_results: self.filters_page.set_sensitive(False) - - self.status_stack.set_visible_child(to_set) + + if to_set is self.loading_packages: + self.stack.set_visible_child(self.loading_view) + else: + self.stack.set_visible_child(self.packages_split) + self.status_stack.set_visible_child(to_set) def apply_filters(self): i = 0 @@ -324,8 +328,7 @@ class PackagesPage(Adw.BreakpointBin): self.current_row_for_properties = None # Apply - # self.set_status("loading_packages") - self.status_stack.add_child(self.loading_packages) + self.loading_view.set_content(self.loading_packages) self.status_stack.add_child(self.uninstalling) self.packages_list_box.set_filter_func(self.filter_func) self.packages_list_box.set_sort_func(self.sort_func) From a6870ce80bb92189b4aa5b353222b320477d9932 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 23:10:54 -0400 Subject: [PATCH 224/332] Fix snapshot creation cancelling --- src/snapshot_page/new_snapshot_dialog.py | 1 + src/snapshot_page/snapshot_page.py | 9 +++++++-- src/snapshot_page/tar_worker.py | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/snapshot_page/new_snapshot_dialog.py b/src/snapshot_page/new_snapshot_dialog.py index 7b10ac1..e4a8a16 100644 --- a/src/snapshot_page/new_snapshot_dialog.py +++ b/src/snapshot_page/new_snapshot_dialog.py @@ -95,6 +95,7 @@ class NewSnapshotDialog(Adw.Dialog): if stopped_workers_amount == len(self.workers): self.loading_status.progress_bar.set_fraction(1) self.loading_status.progress_label.set_label(f"{len(self.workers)} / {len(self.workers)}") + self.workers.clear() if self.on_done: self.on_done() diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index fa54024..883d9fe 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -204,7 +204,6 @@ class SnapshotPage(Adw.BreakpointBin): def end_loading(self): def callback(*args): - self.new_snapshot_dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh) self.generate_active_list() self.generate_leftover_list() if (not self.active_box.get_visible()) and (not self.leftover_box.get_visible()): @@ -224,10 +223,14 @@ class SnapshotPage(Adw.BreakpointBin): self.toast_overlay.add_toast(ErrorToast(_("Could not open folder"), str(e)).toast) def on_cancel(self): + for worker in self.workers: + worker.do_cancel("manual_cancel") + for worker in self.new_snapshot_dialog.workers: worker.do_cancel("manual_cancel") def on_new(self, *args): + self.new_snapshot_dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh) self.new_snapshot_dialog.present(HostInfo.main_window) def refresh(self): @@ -347,7 +350,8 @@ class SnapshotPage(Adw.BreakpointBin): self.toast_overlay.add_toast(Adw.Toast(title=_("No apps in your selection can be snapshotted"))) return - NewSnapshotDialog(self, self.snapshotting_status, self.refresh, packages).present(HostInfo.main_window) + self.new_snapshot_dialog = NewSnapshotDialog(self, self.snapshotting_status, self.refresh, packages) + self.new_snapshot_dialog.present(HostInfo.main_window) def get_snapshots_from_entry(self, app_ids): id_to_tar = {} @@ -379,6 +383,7 @@ class SnapshotPage(Adw.BreakpointBin): self.snapshotting_status.progress_bar.set_fraction(1) self.snapshotting_status.progress_label.set_label(f"{len(self.workers)} / {len(self.workers)}") HostInfo.main_window.refresh_handler() + self.workers.clear() return False self.snapshotting_status.progress_label.set_label(f"{stopped_workers_amount + 1} / {len(self.workers)}") diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index 0208594..f3459ed 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -15,6 +15,7 @@ class TarWorker: self.total = 0 self.process = None self.toast_overlay = toast_overlay + self.has_cancelled = False def compress_thread(self, *args): try: @@ -76,6 +77,10 @@ class TarWorker: self.do_cancel(str(e), [self.new_path]) def do_cancel(self, error_str, files_to_trash=None): + if self.has_cancelled: + return + + self.has_cancelled = True self.process.terminate() self.process.wait() if not files_to_trash is None: From 3b745ff3c1503b77493ba6381743e5e6edd0040f Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 23:37:04 -0400 Subject: [PATCH 225/332] Fix cancelling yet again --- src/snapshot_page/snapshot_page.py | 6 ++-- src/snapshot_page/tar_worker.py | 53 +++++++++++++++--------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/snapshot_page/snapshot_page.py b/src/snapshot_page/snapshot_page.py index 883d9fe..6f8d1a9 100644 --- a/src/snapshot_page/snapshot_page.py +++ b/src/snapshot_page/snapshot_page.py @@ -226,6 +226,9 @@ class SnapshotPage(Adw.BreakpointBin): for worker in self.workers: worker.do_cancel("manual_cancel") + if self.new_snapshot_dialog is None: + return + for worker in self.new_snapshot_dialog.workers: worker.do_cancel("manual_cancel") @@ -486,11 +489,10 @@ class SnapshotPage(Adw.BreakpointBin): self.selected_active_rows = [] self.selected_leftover_rows = [] self.workers = [] - # self.active_rows = [] self.leftover_snapshots = [] - # self.leftover_rows = [] self.list_page = SnapshotsListPage(self) self.snapshotting_status = LoadingStatus("Initial Title", _("This might take a while"), True, self.on_cancel) + self.new_snapshot_dialog = None # Connections self.active_listbox.connect("row-activated", self.active_select_handler) diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index f3459ed..8dedf25 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -4,19 +4,6 @@ from .error_toast import ErrorToast import os, tarfile, subprocess, json class TarWorker: - def __init__(self, existing_path, new_path, file_name="", name="", toast_overlay=None): - self.existing_path = existing_path - self.new_path = new_path - self.file_name = file_name - self.name = name - self.should_check = False - self.stop = False - self.fraction = 0.0 - self.total = 0 - self.process = None - self.toast_overlay = toast_overlay - self.has_cancelled = False - def compress_thread(self, *args): try: if not os.path.exists(self.new_path): @@ -43,11 +30,11 @@ class TarWorker: except subprocess.CalledProcessError as cpe: print("Called Error in Compress Thread") - self.do_cancel(cpe.stderr.decode(), [f'{self.new_path}/{self.file_name}.tar.zst', f'{self.new_path}/{self.file_name}.json']) # stderr is in bytes, so decode it + self.do_cancel(cpe.stderr.decode()) # stderr is in bytes, so decode it except Exception as e: print("Exception in Compress Thread") - self.do_cancel(str(e), [f'{self.new_path}/{self.file_name}.tar.zst', f'{self.new_path}/{self.file_name}.json']) + self.do_cancel(str(e)) def extract_thread(self, *args): try: @@ -69,31 +56,29 @@ class TarWorker: self.stop = True # tell the check timeout to stop, because we know the file is done being made except subprocess.CalledProcessError as cpe: - print("Called Error in Extract Thread") - self.do_cancel(cpe.stderr.decode(), [self.new_path]) + self.do_cancel(cpe.stderr.decode()) except Exception as e: - print("Exception in Extract Thread") - self.do_cancel(str(e), [self.new_path]) - - def do_cancel(self, error_str, files_to_trash=None): - if self.has_cancelled: + self.do_cancel(str(e)) + + def do_cancel(self, error_str): + if self.has_cancelled or self.stop: return self.has_cancelled = True self.process.terminate() self.process.wait() - if not files_to_trash is None: + if len(self.files_to_trash_on_cancel) > 0: try: - subprocess.run(['gio', 'trash'] + files_to_trash, capture_output=True, check=True) + subprocess.run(['gio', 'trash'] + self.files_to_trash_on_cancel, capture_output=True, check=True) except Exception: pass self.stop = True - if self.toast_overlay: + if self.toast_overlay and error_str != "manual_cancel": self.toast_overlay.add_toast(ErrorToast(_("Error in snapshot handling"), error_str).toast) - + def check_size(self, check_path): try: output = subprocess.run(['du', '-s', check_path], check=True, text=True, capture_output=True).stdout.split('\t')[0] @@ -106,10 +91,26 @@ class TarWorker: def compress(self): self.stop = False + self.files_to_trash_on_cancel = [f'{self.new_path}/{self.file_name}.tar.zst', f'{self.new_path}/{self.file_name}.json'] Gio.Task.new(None, None, None).run_in_thread(self.compress_thread) GLib.timeout_add(200, self.check_size, f"{self.new_path}/{self.file_name}.tar.zst") def extract(self): self.stop = False + self.files_to_trash_on_cancel = [self.new_path] Gio.Task.new(None, None, None).run_in_thread(self.extract_thread) GLib.timeout_add(200, self.check_size, self.new_path) + + def __init__(self, existing_path, new_path, file_name="", name="", toast_overlay=None): + self.existing_path = existing_path + self.new_path = new_path + self.file_name = file_name + self.name = name + self.should_check = False + self.stop = False + self.fraction = 0.0 + self.total = 0 + self.process = None + self.toast_overlay = toast_overlay + self.has_cancelled = False + self.files_to_trash_on_cancel = [] From 8b6746d2e8d580f4f935a501d7c6d1540d30b56e Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 23:40:05 -0400 Subject: [PATCH 226/332] allow cancelling from snapshot box apply --- src/snapshot_page/snapshot_box.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/snapshot_page/snapshot_box.py b/src/snapshot_page/snapshot_box.py index bb1db34..9970049 100644 --- a/src/snapshot_page/snapshot_box.py +++ b/src/snapshot_page/snapshot_box.py @@ -122,6 +122,9 @@ class SnapshotBox(Gtk.Box): data_page = HostInfo.main_window.pages[HostInfo.main_window.user_data_row] data_page.start_loading() data_page.end_loading() + if self.worker in self.snapshot_page.workers: + self.snapshot_page.workers.remove(self.worker) + return False # Stop the timeout else: return True # Continue the timeout @@ -135,6 +138,7 @@ class SnapshotBox(Gtk.Box): self.snapshot_page.snapshotting_status.progress_label.set_visible(False) self.snapshot_page.snapshotting_status.progress_bar.set_fraction(0.0) self.snapshot_page.status_stack.set_visible_child(self.snapshot_page.snapshotting_view) + self.snapshot_page.workers.append(self.worker) self.worker.extract() GLib.timeout_add(200, self.get_fraction) From 3393ec783ee404b7f26949697c3fa93e28e7371a Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 23:43:17 -0400 Subject: [PATCH 227/332] Allow cancelling on snapshot list page's worker --- src/snapshot_page/snapshots_list_page.py | 14 +++++++------- src/snapshot_page/tar_worker.py | 2 -- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/snapshot_page/snapshots_list_page.py b/src/snapshot_page/snapshots_list_page.py index 22424d1..d009840 100644 --- a/src/snapshot_page/snapshots_list_page.py +++ b/src/snapshot_page/snapshots_list_page.py @@ -29,7 +29,7 @@ class SnapshotsListPage(Adw.NavigationPage): if len(self.snapshots_rows) == 0: self.parent_page.refresh() return - + for i, row in enumerate(self.snapshots_rows): self.listbox.append(row) self.listbox.get_row_at_index(i).set_activatable(False) @@ -37,7 +37,7 @@ class SnapshotsListPage(Adw.NavigationPage): def set_snapshots(self, package_or_folder, refresh=False): if package_or_folder == self.package_or_folder and not refresh: return - + folder = None self.package_or_folder = package_or_folder if type(package_or_folder) is str: @@ -48,7 +48,7 @@ class SnapshotsListPage(Adw.NavigationPage): folder = package_or_folder.info["id"] self.set_title(_("{} Snapshots").format(package_or_folder.info["name"])) self.new_button.set_sensitive(os.path.exists(package_or_folder.data_path)) - + self.current_folder = folder self.snapshots_rows.clear() self.listbox.remove_all() @@ -71,14 +71,14 @@ class SnapshotsListPage(Adw.NavigationPage): self.set_snapshots(self.package_or_folder, refresh=True) def on_new(self, button): - dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, [self.package_or_folder]) - dialog.present(HostInfo.main_window) - + self.parent_page.new_snapshot_dialog = NewSnapshotDialog(self.parent_page, self.parent_page.snapshotting_status, self.on_done, [self.package_or_folder]) + self.parent_page.new_snapshot_dialog.present(HostInfo.main_window) + def sort_func(self, row1, row2): row1 = row1.get_child() row2 = row2.get_child() return row1.epoch > row2.epoch - + def on_trash(self): self.set_snapshots(self.package_or_folder, refresh=True) diff --git a/src/snapshot_page/tar_worker.py b/src/snapshot_page/tar_worker.py index 8dedf25..aa7db61 100644 --- a/src/snapshot_page/tar_worker.py +++ b/src/snapshot_page/tar_worker.py @@ -29,11 +29,9 @@ class TarWorker: self.stop = True # tell the check timeout to stop, because we know the file is done being made except subprocess.CalledProcessError as cpe: - print("Called Error in Compress Thread") self.do_cancel(cpe.stderr.decode()) # stderr is in bytes, so decode it except Exception as e: - print("Exception in Compress Thread") self.do_cancel(str(e)) def extract_thread(self, *args): From a6844ef438d03bd003804b224ed6d8096ce14907 Mon Sep 17 00:00:00 2001 From: Heliguy Date: Sat, 28 Sep 2024 23:47:44 -0400 Subject: [PATCH 228/332] Fix the to in the metainfo, so Warehouse is shown to work on all devices --- data/io.github.flattool.Warehouse.metainfo.xml.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/io.github.flattool.Warehouse.metainfo.xml.in b/data/io.github.flattool.Warehouse.metainfo.xml.in index e95926d..33eb2c4 100644 --- a/data/io.github.flattool.Warehouse.metainfo.xml.in +++ b/data/io.github.flattool.Warehouse.metainfo.xml.in @@ -8,8 +8,8 @@ GPL-3.0-only Manage all things Flatpak -

Warehouse is an app that manages installed Flatpaks, their user data, and Flatpak remotes.

-

Features:

+

Warehouse is an app that manages installed Flatpaks, their user data, and Flatpak remotes.

+

Features: