diff options
Diffstat (limited to '')
-rw-r--r-- | src/gs-upgrade-banner.c | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/src/gs-upgrade-banner.c b/src/gs-upgrade-banner.c new file mode 100644 index 0000000..b38a353 --- /dev/null +++ b/src/gs-upgrade-banner.c @@ -0,0 +1,411 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2016 Kalev Lember <klember@redhat.com> + * Copyright (C) 2016 Richard Hughes <richard@hughsie.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include <glib/gi18n.h> +#include <stdlib.h> + +#include "gs-upgrade-banner.h" +#include "gs-common.h" + +typedef struct +{ + GsApp *app; + + GtkWidget *box_upgrades; + GtkWidget *button_upgrades_download; + GtkWidget *button_upgrades_install; + GtkWidget *button_upgrades_help; + GtkWidget *button_upgrades_cancel; + GtkWidget *label_upgrades_summary; + GtkWidget *label_upgrades_title; + GtkWidget *label_upgrades_warning; + GtkWidget *progressbar; + guint progress_pulse_id; + GtkCssProvider *banner_provider; /* (owned) (nullable) */ +} GsUpgradeBannerPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GsUpgradeBanner, gs_upgrade_banner, GTK_TYPE_BIN) + +enum { + SIGNAL_DOWNLOAD_CLICKED, + SIGNAL_INSTALL_CLICKED, + SIGNAL_HELP_CLICKED, + SIGNAL_CANCEL_CLICKED, + SIGNAL_LAST +}; + +static guint signals [SIGNAL_LAST] = { 0 }; + +static gboolean +_pulse_cb (gpointer user_data) +{ + GsUpgradeBanner *self = GS_UPGRADE_BANNER (user_data); + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + + gtk_progress_bar_pulse (GTK_PROGRESS_BAR (priv->progressbar)); + + return G_SOURCE_CONTINUE; +} + +static void +stop_progress_pulsing (GsUpgradeBanner *self) +{ + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + + if (priv->progress_pulse_id != 0) { + g_source_remove (priv->progress_pulse_id); + priv->progress_pulse_id = 0; + } +} + +static void +gs_upgrade_banner_refresh (GsUpgradeBanner *self) +{ + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + const gchar *uri; + g_autofree gchar *name_bold = NULL; + g_autofree gchar *version_bold = NULL; + g_autofree gchar *str = NULL; + guint percentage; + + if (priv->app == NULL) + return; + + /* embolden */ + name_bold = g_strdup_printf ("<b>%s</b>", gs_app_get_name (priv->app)); + version_bold = g_strdup_printf ("<b>%s</b>", gs_app_get_version (priv->app)); + + /* Distributions that need to reboot to deploy the upgrade show the "Install" button */ + if (gs_app_has_quirk (priv->app, GS_APP_QUIRK_NEEDS_REBOOT)) { + gtk_button_set_label (GTK_BUTTON (priv->button_upgrades_install), + _("_Install")); + gtk_label_set_text (GTK_LABEL (priv->label_upgrades_warning), + _("It is recommended that you back up your " + "data and files before upgrading.")); + } else { + gtk_button_set_label (GTK_BUTTON (priv->button_upgrades_install), + _("_Restart Now")); + gtk_label_set_text (GTK_LABEL (priv->label_upgrades_warning), + _("Updates will be applied when the " + "computer is restarted.")); + } + + /* Refresh the title. Normally a distro upgrade state goes from + * + * AVAILABLE (available to download) to + * INSTALLING (downloading packages for later installation) to + * UPDATABLE (packages are downloaded and upgrade is ready to go) + */ + switch (gs_app_get_state (priv->app)) { + case AS_APP_STATE_AVAILABLE: + /* TRANSLATORS: This is the text displayed when a distro + * upgrade is available. First %s is the distro name and the + * 2nd %s is the version, e.g. "Fedora 23 Now Available" */ + str = g_strdup_printf (_("%s %s Now Available"), + name_bold, version_bold); + gtk_label_set_markup (GTK_LABEL (priv->label_upgrades_title), str); + gtk_widget_set_visible (priv->label_upgrades_warning, FALSE); + gtk_widget_set_visible (priv->button_upgrades_cancel, FALSE); + break; + case AS_APP_STATE_QUEUED_FOR_INSTALL: + /* TRANSLATORS: This is the text displayed while waiting to + * download a distro upgrade. First %s is the distro name and + * the 2nd %s is the version, e.g. "Waiting to Download Fedora 23" */ + str = g_strdup_printf (_("Waiting to Download %s %s"), + name_bold, version_bold); + gtk_label_set_markup (GTK_LABEL (priv->label_upgrades_title), str); + gtk_widget_set_visible (priv->label_upgrades_warning, FALSE); + gtk_widget_set_visible (priv->button_upgrades_cancel, TRUE); + break; + case AS_APP_STATE_INSTALLING: + /* TRANSLATORS: This is the text displayed while downloading a + * distro upgrade. First %s is the distro name and the 2nd %s + * is the version, e.g. "Downloading Fedora 23" */ + str = g_strdup_printf (_("Downloading %s %s"), + name_bold, version_bold); + gtk_label_set_markup (GTK_LABEL (priv->label_upgrades_title), str); + gtk_widget_set_visible (priv->label_upgrades_warning, FALSE); + gtk_widget_set_visible (priv->button_upgrades_cancel, TRUE); + break; + case AS_APP_STATE_UPDATABLE: + /* TRANSLATORS: This is the text displayed when a distro + * upgrade has been downloaded and is ready to be installed. + * First %s is the distro name and the 2nd %s is the version, + * e.g. "Fedora 23 Ready to be Installed" */ + str = g_strdup_printf (_("%s %s Ready to be Installed"), + name_bold, version_bold); + gtk_label_set_markup (GTK_LABEL (priv->label_upgrades_title), str); + gtk_widget_set_visible (priv->label_upgrades_warning, TRUE); + gtk_widget_set_visible (priv->button_upgrades_cancel, FALSE); + break; + default: + g_critical ("Unexpected app state ‘%s’ of app ‘%s’", + as_app_state_to_string (gs_app_get_state (priv->app)), + gs_app_get_unique_id (priv->app)); + break; + } + + /* Hide the upgrade box until the app state is known. */ + gtk_widget_set_visible (priv->box_upgrades, + (gs_app_get_state (priv->app) != AS_APP_STATE_UNKNOWN)); + + /* Refresh the summary if we got anything better than the default blurb */ + if (gs_app_get_summary (priv->app) != NULL) + gtk_label_set_text (GTK_LABEL (priv->label_upgrades_summary), + gs_app_get_summary (priv->app)); + + /* Show the right buttons for the current state */ + switch (gs_app_get_state (priv->app)) { + case AS_APP_STATE_AVAILABLE: + gtk_widget_show (priv->button_upgrades_download); + gtk_widget_hide (priv->button_upgrades_install); + break; + case AS_APP_STATE_QUEUED_FOR_INSTALL: + case AS_APP_STATE_INSTALLING: + gtk_widget_hide (priv->button_upgrades_download); + gtk_widget_hide (priv->button_upgrades_install); + break; + case AS_APP_STATE_UPDATABLE: + gtk_widget_hide (priv->button_upgrades_download); + gtk_widget_show (priv->button_upgrades_install); + break; + default: + g_critical ("Unexpected app state ‘%s’ of app ‘%s’", + as_app_state_to_string (gs_app_get_state (priv->app)), + gs_app_get_unique_id (priv->app)); + break; + } + + /* only show help when we have a URL */ + uri = gs_app_get_url (priv->app, AS_URL_KIND_HOMEPAGE); + gtk_widget_set_visible (priv->button_upgrades_help, uri != NULL); + + /* do a fill bar for the current progress */ + switch (gs_app_get_state (priv->app)) { + case AS_APP_STATE_INSTALLING: + percentage = gs_app_get_progress (priv->app); + if (percentage == GS_APP_PROGRESS_UNKNOWN) { + if (priv->progress_pulse_id == 0) + priv->progress_pulse_id = g_timeout_add (50, _pulse_cb, self); + + gtk_widget_set_visible (priv->progressbar, TRUE); + break; + } else if (percentage <= 100) { + stop_progress_pulsing (self); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->progressbar), + (gdouble) percentage / 100.f); + gtk_widget_set_visible (priv->progressbar, TRUE); + break; + } + break; + default: + gtk_widget_hide (priv->progressbar); + stop_progress_pulsing (self); + break; + } +} + +static gboolean +app_refresh_idle (gpointer user_data) +{ + GsUpgradeBanner *self = GS_UPGRADE_BANNER (user_data); + + gs_upgrade_banner_refresh (self); + + g_object_unref (self); + return G_SOURCE_REMOVE; +} + +static void +app_state_changed (GsApp *app, GParamSpec *pspec, GsUpgradeBanner *self) +{ + g_idle_add (app_refresh_idle, g_object_ref (self)); +} + +static void +app_progress_changed (GsApp *app, GParamSpec *pspec, GsUpgradeBanner *self) +{ + g_idle_add (app_refresh_idle, g_object_ref (self)); +} + +static void +download_button_cb (GtkWidget *widget, GsUpgradeBanner *self) +{ + g_signal_emit (self, signals[SIGNAL_DOWNLOAD_CLICKED], 0); +} + +static void +install_button_cb (GtkWidget *widget, GsUpgradeBanner *self) +{ + g_signal_emit (self, signals[SIGNAL_INSTALL_CLICKED], 0); +} + +static void +learn_more_button_cb (GtkWidget *widget, GsUpgradeBanner *self) +{ + g_signal_emit (self, signals[SIGNAL_HELP_CLICKED], 0); +} + +static void +cancel_button_cb (GtkWidget *widget, GsUpgradeBanner *self) +{ + g_signal_emit (self, signals[SIGNAL_CANCEL_CLICKED], 0); +} + +void +gs_upgrade_banner_set_app (GsUpgradeBanner *self, GsApp *app) +{ + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + const gchar *css; + + g_return_if_fail (GS_IS_UPGRADE_BANNER (self)); + g_return_if_fail (GS_IS_APP (app) || app == NULL); + + if (priv->app) { + g_signal_handlers_disconnect_by_func (priv->app, app_state_changed, self); + g_signal_handlers_disconnect_by_func (priv->app, app_progress_changed, self); + } + + g_set_object (&priv->app, app); + if (!app) + return; + + g_signal_connect (priv->app, "notify::state", + G_CALLBACK (app_state_changed), self); + g_signal_connect (priv->app, "notify::progress", + G_CALLBACK (app_progress_changed), self); + + /* perhaps set custom css */ + css = gs_app_get_metadata_item (app, "GnomeSoftware::UpgradeBanner-css"); + gs_utils_widget_set_css (priv->box_upgrades, &priv->banner_provider, "upgrade-banner-custom", css); + + gs_upgrade_banner_refresh (self); +} + +GsApp * +gs_upgrade_banner_get_app (GsUpgradeBanner *self) +{ + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + + g_return_val_if_fail (GS_IS_UPGRADE_BANNER (self), NULL); + + return priv->app; +} + +static void +gs_upgrade_banner_dispose (GObject *object) +{ + GsUpgradeBanner *self = GS_UPGRADE_BANNER (object); + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + + g_clear_object (&priv->banner_provider); + + G_OBJECT_CLASS (gs_upgrade_banner_parent_class)->dispose (object); +} + +static void +gs_upgrade_banner_destroy (GtkWidget *widget) +{ + GsUpgradeBanner *self = GS_UPGRADE_BANNER (widget); + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + + stop_progress_pulsing (self); + + if (priv->app) { + g_signal_handlers_disconnect_by_func (priv->app, app_state_changed, self); + g_signal_handlers_disconnect_by_func (priv->app, app_progress_changed, self); + } + + g_clear_object (&priv->app); + + GTK_WIDGET_CLASS (gs_upgrade_banner_parent_class)->destroy (widget); +} + +static void +gs_upgrade_banner_init (GsUpgradeBanner *self) +{ + GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self); + + gtk_widget_init_template (GTK_WIDGET (self)); + + g_signal_connect (priv->button_upgrades_download, "clicked", + G_CALLBACK (download_button_cb), + self); + g_signal_connect (priv->button_upgrades_install, "clicked", + G_CALLBACK (install_button_cb), + self); + g_signal_connect (priv->button_upgrades_help, "clicked", + G_CALLBACK (learn_more_button_cb), + self); + g_signal_connect (priv->button_upgrades_cancel, "clicked", + G_CALLBACK (cancel_button_cb), + self); +} + +static void +gs_upgrade_banner_class_init (GsUpgradeBannerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gs_upgrade_banner_dispose; + widget_class->destroy = gs_upgrade_banner_destroy; + + signals [SIGNAL_DOWNLOAD_CLICKED] = + g_signal_new ("download-clicked", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsUpgradeBannerClass, download_clicked), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals [SIGNAL_INSTALL_CLICKED] = + g_signal_new ("install-clicked", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsUpgradeBannerClass, install_clicked), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals [SIGNAL_CANCEL_CLICKED] = + g_signal_new ("cancel-clicked", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsUpgradeBannerClass, cancel_clicked), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals [SIGNAL_HELP_CLICKED] = + g_signal_new ("help-clicked", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsUpgradeBannerClass, help_clicked), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-upgrade-banner.ui"); + + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, box_upgrades); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, button_upgrades_download); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, button_upgrades_install); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, button_upgrades_cancel); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, button_upgrades_help); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, label_upgrades_summary); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, label_upgrades_title); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, progressbar); + gtk_widget_class_bind_template_child_private (widget_class, GsUpgradeBanner, label_upgrades_warning); +} + +GtkWidget * +gs_upgrade_banner_new (void) +{ + GsUpgradeBanner *self; + self = g_object_new (GS_TYPE_UPGRADE_BANNER, + "vexpand", FALSE, + NULL); + return GTK_WIDGET (self); +} |