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;
+}