summaryrefslogtreecommitdiffstats
path: root/src/gs-update-dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gs-update-dialog.c')
-rw-r--r--src/gs-update-dialog.c391
1 files changed, 391 insertions, 0 deletions
diff --git a/src/gs-update-dialog.c b/src/gs-update-dialog.c
new file mode 100644
index 0000000..fc14f2b
--- /dev/null
+++ b/src/gs-update-dialog.c
@@ -0,0 +1,391 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2013-2016 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gs-update-dialog.h"
+#include "gs-app-details-page.h"
+#include "gs-app-row.h"
+#include "gs-os-update-page.h"
+#include "gs-update-list.h"
+#include "gs-common.h"
+
+struct _GsUpdateDialog
+{
+ AdwWindow parent_instance;
+
+ GCancellable *cancellable;
+ GsPluginLoader *plugin_loader;
+ GsApp *app;
+ GtkWidget *leaflet;
+ GtkWidget *list_box_installed_updates;
+ GtkWidget *spinner;
+ GtkWidget *stack;
+ AdwWindowTitle *window_title;
+ gboolean showing_installed_updates;
+};
+
+G_DEFINE_TYPE (GsUpdateDialog, gs_update_dialog, ADW_TYPE_WINDOW)
+
+typedef enum {
+ PROP_PLUGIN_LOADER = 1,
+ PROP_APP,
+} GsUpdateDialogProperty;
+
+static GParamSpec *obj_props[PROP_APP + 1] = { NULL, };
+
+static void gs_update_dialog_show_installed_updates (GsUpdateDialog *dialog);
+static void gs_update_dialog_show_update_details (GsUpdateDialog *dialog, GsApp *app);
+
+static void
+leaflet_child_transition_cb (AdwLeaflet *leaflet, GParamSpec *pspec, GsUpdateDialog *dialog)
+{
+ GtkWidget *child;
+
+ if (adw_leaflet_get_child_transition_running (leaflet))
+ return;
+
+ while ((child = adw_leaflet_get_adjacent_child (leaflet, ADW_NAVIGATION_DIRECTION_FORWARD)))
+ adw_leaflet_remove (leaflet, child);
+
+ child = adw_leaflet_get_visible_child (leaflet);
+ if (child != NULL && g_object_class_find_property (G_OBJECT_CLASS (GTK_WIDGET_GET_CLASS (child)), "title") != NULL) {
+ g_autofree gchar *title = NULL;
+ g_object_get (G_OBJECT (child), "title", &title, NULL);
+ gtk_window_set_title (GTK_WINDOW (dialog), title);
+ } else if (dialog->showing_installed_updates) {
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Installed Updates"));
+ } else {
+ gtk_window_set_title (GTK_WINDOW (dialog), "");
+ }
+}
+
+static void
+installed_updates_row_activated_cb (GsUpdateList *update_list,
+ GsApp *app,
+ GsUpdateDialog *dialog)
+{
+ gs_update_dialog_show_update_details (dialog, app);
+}
+
+static void
+get_installed_updates_cb (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GsUpdateDialog *dialog)
+{
+ guint i;
+ guint64 install_date;
+ g_autoptr(GsAppList) list = NULL;
+ g_autoptr(GError) error = NULL;
+
+ /* get the results */
+ list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
+
+ /* if we're in teardown, short-circuit and return immediately without
+ * dereferencing priv variables */
+ if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
+ dialog->spinner == NULL) {
+ g_debug ("get installed updates cancelled");
+ return;
+ }
+
+ gtk_spinner_stop (GTK_SPINNER (dialog->spinner));
+
+ /* error */
+ if (list == NULL) {
+ g_warning ("failed to get installed updates: %s", error->message);
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "empty");
+ return;
+ }
+
+ /* no results */
+ if (gs_app_list_length (list) == 0) {
+ g_debug ("no installed updates to show");
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "empty");
+ return;
+ }
+
+ /* set the header title using any one of the applications */
+ install_date = gs_app_get_install_date (gs_app_list_index (list, 0));
+ if (install_date > 0) {
+ g_autoptr(GDateTime) date = NULL;
+ g_autofree gchar *date_str = NULL;
+ g_autofree gchar *subtitle = NULL;
+
+ date = g_date_time_new_from_unix_utc ((gint64) install_date);
+ date_str = g_date_time_format (date, "%x");
+
+ /* TRANSLATORS: this is the subtitle of the installed updates dialog window.
+ %s will be replaced by the date when the updates were installed.
+ The date format is defined by the locale's preferred date representation
+ ("%x" in strftime.) */
+ subtitle = g_strdup_printf (_("Installed on %s"), date_str);
+ adw_window_title_set_subtitle (dialog->window_title, subtitle);
+ }
+
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "installed-updates-list");
+
+ gs_update_list_remove_all (GS_UPDATE_LIST (dialog->list_box_installed_updates));
+ for (i = 0; i < gs_app_list_length (list); i++) {
+ gs_update_list_add_app (GS_UPDATE_LIST (dialog->list_box_installed_updates),
+ gs_app_list_index (list, i));
+ }
+}
+
+static void
+gs_update_dialog_show_installed_updates (GsUpdateDialog *dialog)
+{
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ dialog->showing_installed_updates = TRUE;
+
+ /* TRANSLATORS: this is the title of the installed updates dialog window */
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Installed Updates"));
+
+ gtk_spinner_start (GTK_SPINNER (dialog->spinner));
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "spinner");
+
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL,
+ "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION,
+ NULL);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+ dialog->cancellable,
+ (GAsyncReadyCallback) get_installed_updates_cb,
+ dialog);
+}
+
+static void
+unset_focus (GtkWidget *widget)
+{
+ GtkWidget *focus;
+
+ focus = gtk_window_get_focus (GTK_WINDOW (widget));
+ if (GTK_IS_LABEL (focus))
+ gtk_label_select_region (GTK_LABEL (focus), 0, 0);
+}
+
+static void
+back_clicked_cb (GtkWidget *widget, GsUpdateDialog *dialog)
+{
+ adw_leaflet_navigate (ADW_LEAFLET (dialog->leaflet), ADW_NAVIGATION_DIRECTION_BACK);
+}
+
+static void
+app_activated_cb (GtkWidget *widget, GsApp *app, GsUpdateDialog *page)
+{
+ gs_update_dialog_show_update_details (page, app);
+}
+
+static void
+gs_update_dialog_show_update_details (GsUpdateDialog *dialog, GsApp *app)
+{
+ GtkWidget *page;
+ AsComponentKind kind;
+ g_autofree gchar *str = NULL;
+
+ /* debug */
+ str = gs_app_to_string (app);
+ g_debug ("%s", str);
+
+ /* workaround a gtk+ issue where the dialog comes up with a label selected,
+ * https://bugzilla.gnome.org/show_bug.cgi?id=734033 */
+ unset_focus (GTK_WIDGET (dialog));
+
+ /* set update description */
+ kind = gs_app_get_kind (app);
+ if (kind == AS_COMPONENT_KIND_GENERIC &&
+ gs_app_get_special_kind (app) == GS_APP_SPECIAL_KIND_OS_UPDATE) {
+ page = gs_os_update_page_new ();
+ gs_os_update_page_set_app (GS_OS_UPDATE_PAGE (page), app);
+ g_signal_connect (page, "app-activated",
+ G_CALLBACK (app_activated_cb), dialog);
+ gs_os_update_page_set_show_back_button (GS_OS_UPDATE_PAGE (page), dialog->showing_installed_updates);
+ } else {
+ page = gs_app_details_page_new ();
+ gs_app_details_page_set_app (GS_APP_DETAILS_PAGE (page), app);
+ }
+
+ g_signal_connect (page, "back-clicked",
+ G_CALLBACK (back_clicked_cb), dialog);
+
+ gtk_widget_show (page);
+
+ adw_leaflet_append (ADW_LEAFLET (dialog->leaflet), page);
+ adw_leaflet_set_visible_child (ADW_LEAFLET (dialog->leaflet), page);
+}
+
+static void
+gs_update_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsUpdateDialog *dialog = GS_UPDATE_DIALOG (object);
+
+ switch ((GsUpdateDialogProperty) prop_id) {
+ case PROP_PLUGIN_LOADER:
+ g_value_set_object (value, dialog->plugin_loader);
+ break;
+ case PROP_APP:
+ g_value_set_object (value, dialog->app);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_update_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsUpdateDialog *dialog = GS_UPDATE_DIALOG (object);
+
+ switch ((GsUpdateDialogProperty) prop_id) {
+ case PROP_PLUGIN_LOADER:
+ dialog->plugin_loader = g_object_ref (g_value_get_object (value));
+ break;
+ case PROP_APP:
+ dialog->app = g_value_get_object (value);
+ if (dialog->app != NULL)
+ g_object_ref (dialog->app);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_update_dialog_constructed (GObject *object)
+{
+ GsUpdateDialog *dialog = GS_UPDATE_DIALOG (object);
+
+ g_assert (dialog->plugin_loader);
+
+ if (dialog->app) {
+ GtkWidget *child;
+
+ child = adw_leaflet_get_visible_child (ADW_LEAFLET (dialog->leaflet));
+ adw_leaflet_remove (ADW_LEAFLET (dialog->leaflet), child);
+
+ gs_update_dialog_show_update_details (dialog, dialog->app);
+
+ child = adw_leaflet_get_visible_child (ADW_LEAFLET (dialog->leaflet));
+ /* It can be either the app details page or the OS update page */
+ if (GS_IS_APP_DETAILS_PAGE (child))
+ gs_app_details_page_set_show_back_button (GS_APP_DETAILS_PAGE (child), FALSE);
+ } else {
+ gs_update_dialog_show_installed_updates (dialog);
+ }
+
+ G_OBJECT_CLASS (gs_update_dialog_parent_class)->constructed (object);
+}
+
+static void
+gs_update_dialog_dispose (GObject *object)
+{
+ GsUpdateDialog *dialog = GS_UPDATE_DIALOG (object);
+
+ g_cancellable_cancel (dialog->cancellable);
+ g_clear_object (&dialog->cancellable);
+
+ g_clear_object (&dialog->plugin_loader);
+ g_clear_object (&dialog->app);
+
+ G_OBJECT_CLASS (gs_update_dialog_parent_class)->dispose (object);
+}
+
+static void
+gs_update_dialog_init (GsUpdateDialog *dialog)
+{
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+
+ dialog->cancellable = g_cancellable_new ();
+
+ g_signal_connect (dialog->list_box_installed_updates, "show-update",
+ G_CALLBACK (installed_updates_row_activated_cb), dialog);
+
+ g_signal_connect_after (dialog, "show", G_CALLBACK (unset_focus), NULL);
+}
+
+static void
+gs_update_dialog_class_init (GsUpdateDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = gs_update_dialog_get_property;
+ object_class->set_property = gs_update_dialog_set_property;
+ object_class->constructed = gs_update_dialog_constructed;
+ object_class->dispose = gs_update_dialog_dispose;
+
+ /**
+ * GsUpdateDialog:plugin-loader
+ *
+ * The plugin loader of the dialog.
+ *
+ * Since: 41
+ */
+ obj_props[PROP_PLUGIN_LOADER] =
+ g_param_spec_object ("plugin-loader", NULL, NULL,
+ GS_TYPE_PLUGIN_LOADER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GsUpdateDialog:app: (nullable)
+ *
+ * The app whose details to display.
+ *
+ * If none is set, the intalled updates will be displayed.
+ *
+ * Since: 41
+ */
+ obj_props[PROP_APP] =
+ g_param_spec_object ("app", NULL, NULL,
+ GS_TYPE_APP,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-update-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GsUpdateDialog, leaflet);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdateDialog, list_box_installed_updates);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdateDialog, spinner);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdateDialog, stack);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdateDialog, window_title);
+ gtk_widget_class_bind_template_callback (widget_class, leaflet_child_transition_cb);
+
+ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL);
+}
+
+GtkWidget *
+gs_update_dialog_new (GsPluginLoader *plugin_loader)
+{
+ return GTK_WIDGET (g_object_new (GS_TYPE_UPDATE_DIALOG,
+ "plugin-loader", plugin_loader,
+ NULL));
+}
+
+GtkWidget *
+gs_update_dialog_new_for_app (GsPluginLoader *plugin_loader, GsApp *app)
+{
+ return GTK_WIDGET (g_object_new (GS_TYPE_UPDATE_DIALOG,
+ "plugin-loader", plugin_loader,
+ "app", app,
+ NULL));
+}