diff --git a/src/bazaar.gresource.xml b/src/bazaar.gresource.xml index d6ff95c9..e9da2b0b 100644 --- a/src/bazaar.gresource.xml +++ b/src/bazaar.gresource.xml @@ -208,6 +208,7 @@ icons/io.github.kolunmi.Bazaar.google.svg bz-global-progress.wdgt + bz-install-controls.wdgt diff --git a/src/bz-addons-dialog.blp b/src/bz-addons-dialog.blp index 4542beae..dabeba60 100644 --- a/src/bz-addons-dialog.blp +++ b/src/bz-addons-dialog.blp @@ -262,56 +262,13 @@ template $BzAddonsDialog: Adw.Dialog { styles ["dimmed"] } - Stack { - transition-type: crossfade; + $BzInstallControls { halign: center; margin-top: 6; margin-bottom: 8; - visible-child-name: bind $get_install_stack_page(template.selected-group as <$BzEntryGroup>.installable, template.selected-group as <$BzEntryGroup>.removable) as ; - - StackPage { - name: "install"; - child: Button { - margin-top: 6; - margin-bottom: 6; - margin-start: 6; - margin-end: 6; - styles ["pill", "suggested-action"] - label: _("Install"); - sensitive: bind $invert_boolean($is_zero(template.selected-group as <$BzEntryGroup>.installable-and-available) as ) as ; - clicked => $install_cb(); - }; - } - - StackPage { - name: "open"; - child: Box { - spacing: 8; - margin-top: 6; - margin-bottom: 6; - margin-start: 6; - margin-end: 6; - - Button { - styles ["pill"] - label: _("Open"); - sensitive: bind $invert_boolean($is_zero(template.selected-group as <$BzEntryGroup>.removable-and-available) as ) as ; - clicked => $run_cb(); - } - - Button { - styles ["pill", "destructive-action"] - label: _("Remove"); - sensitive: bind $invert_boolean($is_zero(template.selected-group as <$BzEntryGroup>.removable-and-available) as ) as ; - clicked => $remove_cb(); - } - }; - } - - StackPage { - name: "empty"; - child: Adw.Bin {}; - } + wide: false; + entry-group: bind template.selected-group; + state: bind template.state; } Label { diff --git a/src/bz-addons-dialog.c b/src/bz-addons-dialog.c index f2c1958f..f7cae773 100644 --- a/src/bz-addons-dialog.c +++ b/src/bz-addons-dialog.c @@ -52,6 +52,7 @@ struct _BzAddonsDialog DexFuture *selected_ui_future; BzResult *parent_ui_entry; DexFuture *parent_ui_future; + BzStateInfo *state; AdwAnimation *width_animation; AdwAnimation *height_animation; @@ -74,6 +75,7 @@ enum PROP_SELECTED_GROUP, PROP_SELECTED_UI_ENTRY, PROP_PARENT_UI_ENTRY, + PROP_STATE, LAST_PROP }; @@ -87,10 +89,6 @@ static void license_cb (BzAddonsDialog *self, GtkButton *button); static void dl_stats_cb (BzAddonsDialog *self, GtkButton *button); static void animate_to_size (BzAddonsDialog *self); static void on_visible_page_tag_changed (AdwNavigationView *nav_view, GParamSpec *pspec, BzAddonsDialog *self); -static char *get_install_stack_page (gpointer object, int installable, int removable); -static void install_cb (GtkButton *button, BzAddonsDialog *self); -static void remove_cb (GtkButton *button, BzAddonsDialog *self); -static void run_cb (GtkButton *button, BzAddonsDialog *self); static DexFuture *on_parent_ui_entry_resolved (DexFuture *future, GWeakRef *wr); static DexFuture *on_selected_ui_entry_resolved (DexFuture *future, GWeakRef *wr); static void set_selected_group (BzAddonsDialog *self, BzEntryGroup *group); @@ -108,6 +106,7 @@ bz_addons_dialog_dispose (GObject *object) g_clear_object (&self->selected_group); g_clear_object (&self->selected_ui_entry); g_clear_object (&self->parent_ui_entry); + g_clear_object (&self->state); g_clear_object (&self->width_animation); g_clear_object (&self->height_animation); @@ -136,6 +135,9 @@ bz_addons_dialog_get_property (GObject *object, case PROP_PARENT_UI_ENTRY: g_value_set_object (value, self->parent_ui_entry); break; + case PROP_STATE: + g_value_set_object (value, bz_state_info_get_default ()); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -204,6 +206,13 @@ bz_addons_dialog_class_init (BzAddonsDialogClass *klass) BZ_TYPE_RESULT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + props[PROP_STATE] = + g_param_spec_object ( + "state", + NULL, NULL, + BZ_TYPE_STATE_INFO, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, LAST_PROP, props); g_type_ensure (BZ_TYPE_ADDON_TILE); @@ -233,10 +242,6 @@ bz_addons_dialog_class_init (BzAddonsDialogClass *klass) gtk_widget_class_bind_template_callback (widget_class, format_parent_title); gtk_widget_class_bind_template_callback (widget_class, get_description_max_height); gtk_widget_class_bind_template_callback (widget_class, get_description_toggle_text); - gtk_widget_class_bind_template_callback (widget_class, get_install_stack_page); - gtk_widget_class_bind_template_callback (widget_class, install_cb); - gtk_widget_class_bind_template_callback (widget_class, remove_cb); - gtk_widget_class_bind_template_callback (widget_class, run_cb); gtk_widget_class_bind_template_callback (widget_class, license_cb); gtk_widget_class_bind_template_callback (widget_class, size_cb); @@ -283,9 +288,9 @@ bz_addons_dialog_new (BzEntryGroup *group) NULL); if (groups == NULL || g_list_model_get_n_items (groups) == 0) - adw_navigation_view_replace (self->navigation_view, - (AdwNavigationPage *[]) { adw_navigation_view_find_page (self->navigation_view, "empty") }, - 1); + adw_navigation_view_replace (self->navigation_view, + (AdwNavigationPage *[]) { adw_navigation_view_find_page (self->navigation_view, "empty") }, + 1); else if (g_list_model_get_n_items (groups) == 1) { g_autoptr (BzEntryGroup) single = g_list_model_get_item (groups, 0); @@ -483,50 +488,6 @@ on_visible_page_tag_changed (AdwNavigationView *nav_view, g_idle_add_once ((GSourceOnceFunc) animate_to_size, self); } -static char * -get_install_stack_page (gpointer object, - int installable, - int removable) -{ - if (removable > 0) - return g_strdup ("open"); - else if (installable > 0) - return g_strdup ("install"); - else - return g_strdup ("empty"); -} - -static void -install_cb (GtkButton *button, - BzAddonsDialog *self) -{ - if (self->selected_group == NULL) - return; - gtk_widget_activate_action (GTK_WIDGET (self), "window.install-group", "(sb)", - bz_entry_group_get_id (self->selected_group), TRUE); -} - -static void -remove_cb (GtkButton *button, - BzAddonsDialog *self) -{ - if (self->selected_group == NULL) - return; - gtk_widget_activate_action (GTK_WIDGET (self), "window.remove-group", "(sb)", - bz_entry_group_get_id (self->selected_group), TRUE); -} - -static void -run_cb (GtkButton *button, - BzAddonsDialog *self) -{ - BzEntry *entry = NULL; - entry = bz_result_get_object (self->parent_ui_entry); - - gtk_widget_activate_action (GTK_WIDGET (self), "window.launch-group", "s", - bz_entry_get_id (entry)); -} - static DexFuture * on_parent_ui_entry_resolved (DexFuture *future, GWeakRef *wr) diff --git a/src/bz-install-controls.blp b/src/bz-install-controls.blp index d2cc0db2..75e59181 100644 --- a/src/bz-install-controls.blp +++ b/src/bz-install-controls.blp @@ -2,38 +2,39 @@ using Gtk 4.0; using Adw 1; template $BzInstallControls: Box { - homogeneous: bind $invert_boolean(template.wide) as ; - spacing: bind $choose(template.wide, 10, 8) as ; hexpand: bind $invert_boolean(template.wide) as ; - valign: center; Stack { transition-type: crossfade; - visible-child-name: bind $get_visible_page(template.entry-group as <$BzEntryGroup>.installable, template.entry-group as <$BzEntryGroup>.removable, template.state as <$BzStateInfo>.available-updates) as ; + visible-child-name: bind try { + $get_visible_page(template.entry-group as <$BzEntryGroup>.installable, template.entry-group as <$BzEntryGroup>.removable, template.state as <$BzStateInfo>.available-updates, template.tracker as <$BzTransactionEntryTracker>.active as ) as , + $get_visible_page(template.entry-group as <$BzEntryGroup>.installable, template.entry-group as <$BzEntryGroup>.removable, template.state as <$BzStateInfo>.available-updates, false ) as + }; hexpand: bind $invert_boolean(template.wide) as ; - hhomogeneous: false; + valign: center; StackPage install { name: "install"; - child: Button install_button { - styles [ - "suggested-action", - "pill", - ] - + child: $BgeWdgtRenderer animated_button { margin-top: 3; margin-bottom: 3; margin-start: 3; margin-end: 3; + resource: "/io/github/kolunmi/Bazaar/bz-install-controls.wdgt"; + reference: bind template.tracker as <$BzTransactionEntryTracker>; + + state: bind try { $get_install_btn_state( + template.tracker as <$BzTransactionEntryTracker>.status as <$BzTransactionEntryStatus>, + template.tracker as <$BzTransactionEntryTracker>.active as , + template.tracker as <$BzTransactionEntryTracker>.pending as , + template.tracker as <$BzTransactionEntryTracker>.progress as + ) as , "inactive"}; + has-tooltip: true; hexpand: bind $invert_boolean(template.wide) as ; halign: bind $choose(template.wide, 2, 0) as ; - sensitive: bind $invert_boolean($is_zero(template.entry-group as <$BzEntryGroup>.installable-and-available) as ) as ; - label: _("_Install"); - use-underline: true; - clicked => $install_cb(template); }; } @@ -44,6 +45,7 @@ template $BzInstallControls: Box { spacing: bind $choose(template.wide, 10, 8) as ; homogeneous: bind $invert_boolean(template.wide) as ; halign: bind $choose(template.wide, 2, 0) as ; + valign: center; margin-top: 3; margin-bottom: 3; @@ -102,6 +104,7 @@ template $BzInstallControls: Box { spacing: bind $choose(template.wide, 10, 8) as ; homogeneous: bind $invert_boolean(template.wide) as ; halign: bind $choose(template.wide, 2, 0) as ; + valign: center; margin-top: 3; margin-bottom: 3; @@ -155,4 +158,4 @@ template $BzInstallControls: Box { child: Adw.Bin {}; } } -} \ No newline at end of file +} diff --git a/src/bz-install-controls.c b/src/bz-install-controls.c index 80a368e3..1eec4855 100644 --- a/src/bz-install-controls.c +++ b/src/bz-install-controls.c @@ -22,6 +22,8 @@ #include +#include + #include "bz-install-controls.h" #include "bz-state-info.h" #include "bz-template-callbacks.h" @@ -31,12 +33,14 @@ struct _BzInstallControls { GtkBox parent_instance; - BzEntryGroup *group; - BzStateInfo *state; - gboolean wide; + BzEntryGroup *group; + BzStateInfo *state; + gboolean wide; + BzTransactionEntryTracker *tracker; /* Template widgets */ GtkWidget *open_button; + GtkWidget *animated_button; GtkWidget *install_button; }; @@ -48,6 +52,7 @@ enum PROP_WIDE, PROP_ENTRY_GROUP, PROP_STATE, + PROP_TRACKER, LAST_PROP }; static GParamSpec *props[LAST_PROP] = { 0 }; @@ -59,6 +64,64 @@ enum }; static guint signals[LAST_SIGNAL]; +static void +update_tracker (BzInstallControls *self) +{ + BzTransactionManager *manager = NULL; + g_autoptr (GListModel) all = NULL; + const char *group_id = NULL; + g_autoptr (BzTransactionEntryTracker) found = NULL; + + if (self->state != NULL) + manager = bz_state_info_get_transaction_manager (self->state); + if (manager != NULL) + g_object_get (manager, "all-trackers", &all, NULL); + if (self->group != NULL) + group_id = bz_entry_group_get_id (self->group); + + if (all != NULL && group_id != NULL) + { + for (guint i = 0; i < g_list_model_get_n_items (all); i++) + { + g_autoptr (BzTransactionEntryTracker) tracker = NULL; + BzEntry *entry = NULL; + + tracker = g_list_model_get_item (all, i); + entry = bz_transaction_entry_tracker_get_entry (tracker); + + if (g_strcmp0 (entry != NULL ? bz_entry_get_id (entry) : NULL, group_id) == 0) + { + found = g_steal_pointer (&tracker); + break; + } + } + } + + if (g_set_object (&self->tracker, found)) + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRACKER]); +} + +static void +on_all_trackers_changed (GListModel *model, + guint position, + guint removed, + guint added, + BzInstallControls *self) +{ + update_tracker (self); +} + +static void +cancel_cb (BzInstallControls *self, + GtkButton *button) +{ + if (self->group == NULL) + return; + + gtk_widget_activate_action (GTK_WIDGET (self), "window.cancel-group", "s", + bz_entry_group_get_id (self->group)); +} + static void install_cb (BzInstallControls *self, GtkButton *button) @@ -146,14 +209,19 @@ static char * get_visible_page (gpointer object, int installable, int removable, - GListModel *available_updates) + GListModel *available_updates, + gboolean active) { BzInstallControls *self = BZ_INSTALL_CONTROLS (object); g_autoptr (GListStore) store = NULL; + if (active) + return g_strdup ("install"); + if (removable > 0) { - store = find_matching_updates (self, available_updates); + if (g_signal_has_handler_pending (self, signals[SIGNAL_UPDATE], 0, FALSE)) + store = find_matching_updates (self, available_updates); return g_strdup (store != NULL ? "update" : "open"); } else if (installable > 0) @@ -162,6 +230,23 @@ get_visible_page (gpointer object, return g_strdup ("empty"); } +static char * +get_install_btn_state (gpointer object, + BzTransactionEntryStatus status, + gboolean active, + gboolean pending, + double progress) +{ + if ((active || pending) && status == BZ_TRANSACTION_ENTRY_STATUS_CANCELLED) + return g_strdup ("cancelling"); + else if (pending || status == BZ_TRANSACTION_ENTRY_STATUS_QUEUED) + return g_strdup ("pending"); + else if (!active || status == BZ_TRANSACTION_ENTRY_STATUS_DONE) + return g_strdup ("inactive"); + else + return g_strdup ("fraction"); +} + static gboolean is_blocked (gpointer object, GListModel *parental_blocked, @@ -211,6 +296,7 @@ bz_install_controls_dispose (GObject *object) g_clear_object (&self->group); g_clear_object (&self->state); + g_clear_object (&self->tracker); G_OBJECT_CLASS (bz_install_controls_parent_class)->dispose (object); } @@ -234,6 +320,9 @@ bz_install_controls_get_property (GObject *object, case PROP_STATE: g_value_set_object (value, self->state); break; + case PROP_TRACKER: + g_value_set_object (value, self->tracker); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -294,6 +383,13 @@ bz_install_controls_class_init (BzInstallControlsClass *klass) BZ_TYPE_STATE_INFO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + props[PROP_TRACKER] = + g_param_spec_object ( + "tracker", + NULL, NULL, + BZ_TYPE_TRANSACTION_ENTRY_TRACKER, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, LAST_PROP, props); signals[SIGNAL_UPDATE] = @@ -318,22 +414,37 @@ bz_install_controls_class_init (BzInstallControlsClass *klass) bz_widget_class_bind_all_util_callbacks (widget_class); gtk_widget_class_bind_template_child (widget_class, BzInstallControls, open_button); - gtk_widget_class_bind_template_child (widget_class, BzInstallControls, install_button); + gtk_widget_class_bind_template_child (widget_class, BzInstallControls, animated_button); gtk_widget_class_bind_template_callback (widget_class, install_cb); gtk_widget_class_bind_template_callback (widget_class, remove_cb); gtk_widget_class_bind_template_callback (widget_class, run_cb); gtk_widget_class_bind_template_callback (widget_class, update_cb); gtk_widget_class_bind_template_callback (widget_class, get_visible_page); + gtk_widget_class_bind_template_callback (widget_class, get_install_btn_state); gtk_widget_class_bind_template_callback (widget_class, is_blocked); } static void bz_install_controls_init (BzInstallControls *self) { + g_autoptr (GtkWidget) btn_cancel = NULL; + self->wide = TRUE; gtk_widget_init_template (GTK_WIDGET (self)); + + self->install_button = bge_wdgt_renderer_lookup_object ( + BGE_WDGT_RENDERER (self->animated_button), "btn-install"); + g_signal_connect_swapped ( + self->install_button, "clicked", + G_CALLBACK (install_cb), self); + + btn_cancel = bge_wdgt_renderer_lookup_object ( + BGE_WDGT_RENDERER (self->animated_button), "btn-cancel"); + g_signal_connect_swapped ( + btn_cancel, "clicked", + G_CALLBACK (cancel_cb), self); } GtkWidget * @@ -390,6 +501,8 @@ bz_install_controls_set_entry_group (BzInstallControls *self, G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) idle_grab_focus, bz_track_weak (self), bz_weak_release); + + update_tracker (self); } BzStateInfo * @@ -405,9 +518,33 @@ bz_install_controls_set_state (BzInstallControls *self, { g_return_if_fail (BZ_IS_INSTALL_CONTROLS (self)); - g_clear_object (&self->state); + if (self->state != NULL) + { + BzTransactionManager *old_mgr = NULL; + g_autoptr (GListModel) all = NULL; + + old_mgr = bz_state_info_get_transaction_manager (self->state); + if (old_mgr != NULL) + g_object_get (old_mgr, "all-trackers", &all, NULL); + if (all != NULL) + g_signal_handlers_disconnect_by_func (all, on_all_trackers_changed, self); + } + + g_set_object (&self->state, state); + if (state != NULL) - self->state = g_object_ref (state); + { + BzTransactionManager *mgr = NULL; + g_autoptr (GListModel) all = NULL; + + mgr = bz_state_info_get_transaction_manager (state); + if (mgr != NULL) + g_object_get (mgr, "all-trackers", &all, NULL); + if (all != NULL) + g_signal_connect (all, "items-changed", + G_CALLBACK (on_all_trackers_changed), self); + } g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]); + update_tracker (self); } diff --git a/src/bz-install-controls.wdgt b/src/bz-install-controls.wdgt new file mode 100644 index 00000000..9ccbed69 --- /dev/null +++ b/src/bz-install-controls.wdgt @@ -0,0 +1,123 @@ +defwidget "Install Button" { + reference state : "BzTransactionEntryTracker"; + var progress : "gdouble"; + + var btn-install : "GtkButton"; + var btn-cancel : "GtkButton"; + var btn-cancelling : "GtkButton"; + var bg : "GtkFixed"; + + var stack : "GtkStack"; + + var x : "gdouble"; + var w : "gdouble"; + var b : "gdouble"; + var opacity : "gdouble"; + + var final-x : "gdouble"; + var final-y : "gdouble"; + var final-w : "gdouble"; + var final-h : "gdouble"; + + init { + set progress = #transition(state:progress, 1.1, 0.1, 10.0); + set stack = #child/GtkStack(""() + transition-type = crossfade; + transition-duration = 200; + %set btn-install = #child/GtkButton(""("pill" "suggested-action") + label = "Install"; + margin-top = 4; + margin-bottom = 4; + margin-start = 4; + margin-end = 4; + ); + %set btn-cancel = #child/GtkButton(""("pill" "install-button") + label = "Cancel"; + margin-top = 4; + margin-bottom = 4; + margin-start = 4; + margin-end = 4; + ); + %set btn-cancelling = #child/GtkButton(""("pill") + sensitive = false; + label = "Cancelling"; + margin-top = 4; + margin-bottom = 4; + margin-start = 4; + margin-end = 4; + ); + ); + allocate stack %width%, %height%, #(); + set bg = #child/("" ("install-button" "install-button-indicator") + opacity = opacity; + can-target = false; + ); + + set final-w = #eval(#(w)-2.0*#(b)); + set final-h = #eval(#(%height%)-2.0*#(b)); + set final-x = #eval(#(x)+#(b)); + set final-y = b; + allocate bg final-w, final-h, #(translate(#GraphenePoint(final-x, final-y));); + + snapshot { + do-child bg; + do-child stack; + } + } + + state-default "inactive" { + set stack:visible-child = btn-install; + + set x = 0.0; + set w = %width%; + set b = 0.0; + set opacity = 0.0; + + transition-spring x 1.0, 0.1, 10.0; + transition-spring w 1.0, 0.1, 10.0; + transition-spring b 1.0, 0.1, 10.0; + transition-spring opacity 1.0, 0.1, 50.0; + } + + state "pending" { + set stack:visible-child = btn-cancel; + + set x = #eval((0.5+0.5*sin(3.0*#(%tick%))) * (#(%width%)-#(w))); + set w = #eval(#(%width%)*0.7); + set b = #eval(#(%height%)*0.175); + set opacity = 1.0; + + transition-spring x 1.0, 0.1, 50.0; + transition-spring w 1.0, 0.1, 50.0; + transition-spring b 1.0, 0.1, 50.0; + transition-spring opacity 1.0, 0.1, 50.0; + } + + state "fraction" { + set stack:visible-child = btn-cancel; + + set x = 0.0; + set w = #eval(#(%height%)+(#(%width%)-#(%height%))*#(progress)); + set b = #eval(#(%height%)*0.1); + set opacity = 1.0; + + transition-spring x 1.1, 0.1, 100.0; + transition-spring w 1.1, 0.1, 100.0; + transition-spring b 1.1, 0.1, 100.0; + transition-spring opacity 1.1, 0.1, 10.0; + } + + state "cancelling" { + set stack:visible-child = btn-cancelling; + + set x = #eval((#(%width%)-#(w))/2.0); + set w = #eval(#(%width%)*0.8); + set b = #eval(#(%height%)*0.175); + set opacity = 0.0; + + transition-spring x 0.95, 0.25, 50.0; + transition-spring w 0.95, 0.25, 50.0; + transition-spring b 0.95, 0.25, 50.0; + transition-spring opacity 1.0, 1.0, 50.0; + } +} diff --git a/src/bz-transaction-entry-tracker.txt b/src/bz-transaction-entry-tracker.txt index 66c4e29e..578c6b6d 100644 --- a/src/bz-transaction-entry-tracker.txt +++ b/src/bz-transaction-entry-tracker.txt @@ -5,7 +5,7 @@ parent-name=object author=AUTOGEN enum=bz transaction_entry_kind install update removal -enum=bz transaction_entry_status queued ongoing done +enum=bz transaction_entry_status queued ongoing done cancelled include= include="bz-entry.h" @@ -15,3 +15,6 @@ property=current_ops GListModel G_TYPE_LIST_MODEL object property=finished_ops GListModel G_TYPE_LIST_MODEL object property=kind BzTransactionEntryKind BZ_TYPE_TRANSACTION_ENTRY_KIND enum property=status BzTransactionEntryStatus BZ_TYPE_TRANSACTION_ENTRY_STATUS enum +property=progress double G_TYPE_DOUBLE double +property=pending gboolean G_TYPE_BOOLEAN boolean +property=active gboolean G_TYPE_BOOLEAN boolean diff --git a/src/bz-transaction-manager.c b/src/bz-transaction-manager.c index 1e5b24a8..8d90dd53 100644 --- a/src/bz-transaction-manager.c +++ b/src/bz-transaction-manager.c @@ -95,6 +95,7 @@ enum PROP_CURRENT_PROGRESS, PROP_INSTALL_TRACKERS, PROP_REMOVAL_TRACKERS, + PROP_ALL_TRACKERS, LAST_PROP }; @@ -178,6 +179,9 @@ bz_transaction_manager_get_property (GObject *object, case PROP_REMOVAL_TRACKERS: g_value_set_object (value, self->removal_trackers); break; + case PROP_ALL_TRACKERS: + g_value_set_object (value, self->all_trackers); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -335,6 +339,13 @@ bz_transaction_manager_class_init (BzTransactionManagerClass *klass) G_TYPE_LIST_MODEL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + props[PROP_ALL_TRACKERS] = + g_param_spec_object ( + "all-trackers", + NULL, NULL, + G_TYPE_LIST_MODEL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, LAST_PROP, props); signals[SIGNAL_SUCCESS] = @@ -493,6 +504,13 @@ bz_transaction_manager_get_has_transactions (BzTransactionManager *self) return g_list_model_get_n_items (G_LIST_MODEL (self->transactions)) > 0; } +GListModel * +bz_transaction_manager_get_all_trackers (BzTransactionManager *self) +{ + g_return_val_if_fail (BZ_IS_TRANSACTION_MANAGER (self), NULL); + return G_LIST_MODEL (self->all_trackers); +} + DexFuture * bz_transaction_manager_add (BzTransactionManager *self, BzTransaction *transaction) diff --git a/src/bz-transaction-manager.h b/src/bz-transaction-manager.h index 7027d6cf..638209ff 100644 --- a/src/bz-transaction-manager.h +++ b/src/bz-transaction-manager.h @@ -70,6 +70,9 @@ bz_transaction_manager_get_pending (BzTransactionManager *self); gboolean bz_transaction_manager_get_has_transactions (BzTransactionManager *self); +GListModel * +bz_transaction_manager_get_all_trackers (BzTransactionManager *self); + G_GNUC_WARN_UNUSED_RESULT DexFuture * bz_transaction_manager_add (BzTransactionManager *self, diff --git a/src/bz-transaction-tile.blp b/src/bz-transaction-tile.blp index 992b7204..85f7314a 100644 --- a/src/bz-transaction-tile.blp +++ b/src/bz-transaction-tile.blp @@ -207,6 +207,30 @@ template $BzTransactionTile: $BzListTile { } } + Box { + visible: bind $is_cancelled(template.tracker as <$BzTransactionEntryTracker>.status as <$BzTransactionEntryStatus>) as ; + halign: start; + spacing: 4; + margin-top: 8; + + styles [ + "grey", + "colored", + "small-pill", + "download-size-pill", + ] + + Label { + styles [ + "caption-heading", + ] + + valign: center; + xalign: 0.0; + label: _("Cancelled"); + } + } + Box { visible: bind $is_transaction_tracker_errored(template.tracker as <$BzTransactionEntryTracker>.finished-ops) as ; halign: start; diff --git a/src/bz-transaction-tile.c b/src/bz-transaction-tile.c index c5a15ec4..d6497565 100644 --- a/src/bz-transaction-tile.c +++ b/src/bz-transaction-tile.c @@ -211,6 +211,13 @@ is_completed (gpointer object, return status == BZ_TRANSACTION_ENTRY_STATUS_DONE; } +static gboolean +is_cancelled (gpointer object, + BzTransactionEntryStatus status) +{ + return status == BZ_TRANSACTION_ENTRY_STATUS_CANCELLED; +} + static gboolean is_both (gpointer object, gboolean first, @@ -309,28 +316,19 @@ static void cancel_cb (BzTransactionTile *self, GtkButton *button) { - gboolean result = FALSE; BzTransactionEntryTracker *tracker = NULL; BzEntry *entry = NULL; - BzStateInfo *state = NULL; - BzBackend *backend = NULL; tracker = bz_transaction_tile_get_tracker (self); if (tracker == NULL) return; entry = bz_transaction_entry_tracker_get_entry (tracker); - if (entry == NULL || !BZ_IS_FLATPAK_ENTRY (entry)) - return; - - state = bz_state_info_get_default (); - backend = bz_state_info_get_backend (state); - if (backend == NULL) + if (entry == NULL) return; - result = bz_backend_cancel_task_for_entry (backend, entry); - if (result) - gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); + gtk_widget_activate_action (GTK_WIDGET (self), "window.cancel-group", "s", + bz_entry_get_id (entry)); } static void @@ -405,6 +403,7 @@ bz_transaction_tile_class_init (BzTransactionTileClass *klass) gtk_widget_class_bind_template_callback (widget_class, is_queued); gtk_widget_class_bind_template_callback (widget_class, is_ongoing); gtk_widget_class_bind_template_callback (widget_class, is_completed); + gtk_widget_class_bind_template_callback (widget_class, is_cancelled); gtk_widget_class_bind_template_callback (widget_class, is_both); gtk_widget_class_bind_template_callback (widget_class, run_cb); gtk_widget_class_bind_template_callback (widget_class, cancel_cb); diff --git a/src/bz-transaction.c b/src/bz-transaction.c index e498a32f..b4173d36 100644 --- a/src/bz-transaction.c +++ b/src/bz-transaction.c @@ -89,9 +89,11 @@ find_and_maybe_transfer (GListStore *from, gpointer *out); static void -tracker_update (BzTransactionPrivate *priv, - BzBackendTransactionOpPayload *payload, - gboolean transfer); +tracker_update (BzTransactionPrivate *priv, + BzBackendTransactionOpPayload *payload, + BzBackendTransactionOpProgressPayload *progress_payload, + gboolean transfer, + gboolean set_done); static void bz_transaction_dispose (GObject *object) @@ -388,6 +390,9 @@ bz_transaction_new_full (BzEntry **installs, bz_transaction_entry_tracker_set_finished_ops (tracker, G_LIST_MODEL (finished_ops)); \ bz_transaction_entry_tracker_set_kind (tracker, transaction_type); \ bz_transaction_entry_tracker_set_status (tracker, BZ_TRANSACTION_ENTRY_STATUS_QUEUED); \ + bz_transaction_entry_tracker_set_pending (tracker, TRUE); \ + if ((transaction_type) == BZ_TRANSACTION_ENTRY_KIND_REMOVAL) \ + bz_transaction_entry_tracker_set_active (tracker, TRUE); \ \ g_list_store_append (priv->trackers, tracker); \ } \ @@ -627,7 +632,7 @@ bz_transaction_update_task (BzTransaction *self, if (result) bz_transaction_task_set_last_progress (task, payload); - tracker_update (priv, op, FALSE); + tracker_update (priv, op, payload, FALSE, FALSE); } void @@ -648,7 +653,7 @@ bz_transaction_finish_task (BzTransaction *self, (GEqualFuncFull) find_payload_eq_func, NULL); - tracker_update (priv, payload, TRUE); + tracker_update (priv, payload, NULL, TRUE, FALSE); } void @@ -675,7 +680,7 @@ bz_transaction_error_out_task (BzTransaction *self, if (result) bz_transaction_task_set_error (task, message); - tracker_update (priv, payload, TRUE); + tracker_update (priv, payload, NULL, TRUE, TRUE); } static void @@ -689,6 +694,10 @@ finish (BzTransactionPrivate *priv) g_autoptr (BzTransactionEntryTracker) tracker = NULL; tracker = g_list_model_get_item (G_LIST_MODEL (priv->trackers), i); + bz_transaction_entry_tracker_set_active (tracker, FALSE); + bz_transaction_entry_tracker_set_pending (tracker, FALSE); + if (bz_transaction_entry_tracker_get_status (tracker) == BZ_TRANSACTION_ENTRY_STATUS_CANCELLED) + continue; bz_transaction_entry_tracker_set_status (tracker, BZ_TRANSACTION_ENTRY_STATUS_DONE); } } @@ -742,30 +751,40 @@ find_and_maybe_transfer (GListStore *from, } static void -tracker_update (BzTransactionPrivate *priv, - BzBackendTransactionOpPayload *payload, - gboolean transfer) +tracker_update (BzTransactionPrivate *priv, + BzBackendTransactionOpPayload *payload, + BzBackendTransactionOpProgressPayload *progress_payload, + gboolean transfer, + gboolean set_done) { BzEntry *entry = NULL; g_autoptr (BzTransactionEntryTracker) tracker = NULL; gboolean result = FALSE; - entry = bz_backend_transaction_op_payload_get_entry (payload); - + entry = bz_backend_transaction_op_payload_get_entry (payload); result = find_and_maybe_transfer ( priv->trackers, NULL, entry, (GEqualFuncFull) find_entry_eq_func, (gpointer *) &tracker); + if (result) { + BzTransactionEntryStatus existing_status = 0; + + existing_status = bz_transaction_entry_tracker_get_status (tracker); if (transfer) { GListModel *from = NULL; GListModel *to = NULL; - bz_transaction_entry_tracker_set_status (tracker, BZ_TRANSACTION_ENTRY_STATUS_DONE); + if (set_done) + { + bz_transaction_entry_tracker_set_active (tracker, FALSE); + bz_transaction_entry_tracker_set_status (tracker, BZ_TRANSACTION_ENTRY_STATUS_DONE); + bz_transaction_entry_tracker_set_pending (tracker, FALSE); + } from = bz_transaction_entry_tracker_get_current_ops (tracker); to = bz_transaction_entry_tracker_get_finished_ops (tracker); @@ -780,6 +799,24 @@ tracker_update (BzTransactionPrivate *priv, g_object_notify (G_OBJECT (tracker), "finished-ops"); } else - bz_transaction_entry_tracker_set_status (tracker, BZ_TRANSACTION_ENTRY_STATUS_ONGOING); + { + if (progress_payload != NULL) + { + double progress = 0.0; + double scaled = 0.0; + + progress = bz_backend_transaction_op_progress_payload_get_total_progress (progress_payload); + scaled = progress >= 1.0 ? 1.0 : progress * 0.9; + + g_object_set (tracker, + "progress", scaled, + "pending", bz_backend_transaction_op_progress_payload_get_is_estimating (progress_payload), + NULL); + } + bz_transaction_entry_tracker_set_active (tracker, TRUE); + + if (existing_status != BZ_TRANSACTION_ENTRY_STATUS_CANCELLED) + bz_transaction_entry_tracker_set_status (tracker, BZ_TRANSACTION_ENTRY_STATUS_ONGOING); + } } } diff --git a/src/bz-window.c b/src/bz-window.c index bc2ace68..97d644af 100644 --- a/src/bz-window.c +++ b/src/bz-window.c @@ -422,6 +422,51 @@ action_remove_group (GtkWidget *widget, try_transact (self, NULL, group, TRUE, auto_confirm, NULL); } +static void +action_cancel_group (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + BzWindow *self = BZ_WINDOW (widget); + const char *id = NULL; + BzTransactionManager *manager = NULL; + BzBackend *backend = NULL; + GListModel *trackers = NULL; + guint n_items = 0; + + id = g_variant_get_string (parameter, NULL); + manager = bz_state_info_get_transaction_manager (self->state); + if (manager == NULL) + return; + + backend = bz_state_info_get_backend (self->state); + if (backend == NULL) + return; + + trackers = bz_transaction_manager_get_all_trackers (manager); + n_items = g_list_model_get_n_items (trackers); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr (BzTransactionEntryTracker) tracker = NULL; + BzEntry *entry = NULL; + const char *entry_id = NULL; + + tracker = g_list_model_get_item (trackers, i); + entry = bz_transaction_entry_tracker_get_entry (tracker); + if (entry == NULL) + continue; + + entry_id = bz_entry_get_id (entry); + if (g_strcmp0 (entry_id, id) == 0) + { + if (bz_backend_cancel_task_for_entry (backend, entry)) + g_object_set (tracker, "status", BZ_TRANSACTION_ENTRY_STATUS_CANCELLED, NULL); + break; + } + } +} + static void action_show_group (GtkWidget *widget, const char *action_name, @@ -527,10 +572,10 @@ action_open_library (GtkWidget *widget, static DexFuture * launch_group_fiber (BzEntryGroup *group) { - g_autoptr (GError) local_error = NULL; - g_autoptr (GListStore) store = NULL; - GtkWidget *window = NULL; - BzStateInfo *state = NULL; + g_autoptr (GError) local_error = NULL; + g_autoptr (GListStore) store = NULL; + GtkWidget *window = NULL; + BzStateInfo *state = NULL; state = bz_state_info_get_default (); window = GTK_WIDGET (gtk_application_get_active_window ( @@ -547,21 +592,40 @@ launch_group_fiber (BzEntryGroup *group) for (guint i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++) { - g_autoptr (BzEntry) entry = NULL; + g_autoptr (BzEntry) entry = NULL; + const char *ref = NULL; + gboolean result = FALSE; entry = g_list_model_get_item (G_LIST_MODEL (store), i); - if (BZ_IS_FLATPAK_ENTRY (entry) && bz_entry_is_installed (entry)) - { - gboolean result = bz_flatpak_entry_launch ( - BZ_FLATPAK_ENTRY (entry), - BZ_FLATPAK_INSTANCE (bz_state_info_get_backend (state)), - &local_error); - if (!result && window != NULL) - bz_show_error_for_widget (window, _ ("Failed to launch application"), local_error->message); + if (!BZ_IS_FLATPAK_ENTRY (entry) || !bz_entry_is_installed (entry)) + continue; - return result ? dex_future_new_true () : dex_future_new_for_error (g_steal_pointer (&local_error)); + ref = bz_flatpak_entry_get_addon_extension_of_ref (BZ_FLATPAK_ENTRY (entry)); + if (ref != NULL) + { + g_auto (GStrv) parts = NULL; + BzApplicationMapFactory *factory = NULL; + g_autoptr (BzEntryGroup) parent = NULL; + + parts = g_strsplit (ref, "/", -1); + if (parts[0] != NULL && parts[1] != NULL) + { + factory = bz_state_info_get_application_factory (state); + parent = bz_application_map_factory_convert_one ( + factory, gtk_string_object_new (parts[1])); + if (parent != NULL) + return launch_group_fiber (parent); + } } + + result = bz_flatpak_entry_launch ( + BZ_FLATPAK_ENTRY (entry), + BZ_FLATPAK_INSTANCE (bz_state_info_get_backend (state)), + &local_error); + if (!result && window != NULL) + bz_show_error_for_widget (window, _ ("Failed to launch application"), local_error->message); + return result ? dex_future_new_true () : dex_future_new_for_error (g_steal_pointer (&local_error)); } return dex_future_new_false (); @@ -655,6 +719,7 @@ bz_window_class_init (BzWindowClass *klass) gtk_widget_class_install_action (widget_class, "window.install-group", "(sb)", action_install_group); gtk_widget_class_install_action (widget_class, "window.remove-group", "(sb)", action_remove_group); + gtk_widget_class_install_action (widget_class, "window.cancel-group", "s", action_cancel_group); gtk_widget_class_install_action (widget_class, "window.show-group", "s", action_show_group); gtk_widget_class_install_action (widget_class, "window.addons-group", "s", action_addons_group); gtk_widget_class_install_action (widget_class, "window.bulk-install", NULL, action_bulk_install); diff --git a/src/style.css b/src/style.css index d04510c8..962a3473 100644 --- a/src/style.css +++ b/src/style.css @@ -895,3 +895,21 @@ curated-list-view, curated-list-view list { background: var(--bg-color); } + + +.install-button { + background-color: alpha(var(--accent-bg-color), 0.333); + color: var(--accent-color); +} + +.install-button:hover { + background-color: alpha(var(--accent-bg-color), 0.5); +} + +.install-button:active { + background-color: alpha(var(--accent-bg-color), 0.666); +} + +.install-button-indicator { + border-radius: 9999px; +}