summaryrefslogtreecommitdiffstats
path: root/src/gs-page.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gs-page.c')
-rw-r--r--src/gs-page.c691
1 files changed, 691 insertions, 0 deletions
diff --git a/src/gs-page.c b/src/gs-page.c
new file mode 100644
index 0000000..ab5bdc6
--- /dev/null
+++ b/src/gs-page.c
@@ -0,0 +1,691 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2013 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2015-2016 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "gs-page.h"
+#include "gs-common.h"
+#include "gs-screenshot-image.h"
+
+typedef struct
+{
+ GsPluginLoader *plugin_loader;
+ GsShell *shell;
+ GtkWidget *header_start_widget;
+ GtkWidget *header_end_widget;
+ gboolean is_active;
+} GsPagePrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GsPage, gs_page, GTK_TYPE_BIN)
+
+GsShell *
+gs_page_get_shell (GsPage *page)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ return priv->shell;
+}
+
+typedef struct {
+ GsApp *app;
+ GsPage *page;
+ GCancellable *cancellable;
+ gulong notify_quirk_id;
+ GtkWidget *button_install;
+ GsPluginAction action;
+ GsShellInteraction interaction;
+} GsPageHelper;
+
+static void
+gs_page_helper_free (GsPageHelper *helper)
+{
+ if (helper->notify_quirk_id > 0)
+ g_signal_handler_disconnect (helper->app, helper->notify_quirk_id);
+ if (helper->app != NULL)
+ g_object_unref (helper->app);
+ if (helper->page != NULL)
+ g_object_unref (helper->page);
+ if (helper->cancellable != NULL)
+ g_object_unref (helper->cancellable);
+ g_slice_free (GsPageHelper, helper);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free);
+
+static void
+gs_page_update_app_response_close_cb (GtkDialog *dialog, gint response, gpointer user_data)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+gs_page_show_update_message (GsPageHelper *helper, AsScreenshot *ss)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+ GPtrArray *images;
+ GtkWidget *dialog;
+ g_autofree gchar *escaped = NULL;
+
+ dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_USE_HEADER_BAR,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK,
+ "%s", gs_app_get_name (helper->app));
+ escaped = g_markup_escape_text (as_screenshot_get_caption (ss, NULL), -1);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", escaped);
+
+ /* image is optional */
+ images = as_screenshot_get_images (ss);
+ if (images->len) {
+ GtkWidget *content_area;
+ GtkWidget *ssimg;
+ g_autoptr(SoupSession) soup_session = NULL;
+
+ /* load screenshot */
+ soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
+ gs_user_agent (), NULL);
+ ssimg = gs_screenshot_image_new (soup_session);
+ gs_screenshot_image_set_screenshot (GS_SCREENSHOT_IMAGE (ssimg), ss);
+ gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (ssimg), 400, 225);
+ gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (ssimg),
+ helper->cancellable);
+ gtk_widget_set_margin_start (ssimg, 24);
+ gtk_widget_set_margin_end (ssimg, 24);
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_container_add (GTK_CONTAINER (content_area), ssimg);
+ gtk_container_child_set (GTK_CONTAINER (content_area), ssimg, "pack-type", GTK_PACK_END, NULL);
+ }
+
+ /* handle this async */
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_page_update_app_response_close_cb), helper);
+ gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+}
+
+static void
+gs_page_app_installed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ GsPage *page = helper->page;
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ gboolean ret;
+ g_autoptr(GError) error = NULL;
+
+ ret = gs_plugin_loader_job_action_finish (plugin_loader,
+ res,
+ &error);
+ if (g_error_matches (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_CANCELLED)) {
+ g_debug ("%s", error->message);
+ return;
+ }
+ if (!ret) {
+ g_warning ("failed to install %s: %s",
+ gs_app_get_id (helper->app),
+ error->message);
+ return;
+ }
+
+ /* the single update needs system reboot, e.g. for firmware */
+ if (gs_app_has_quirk (helper->app, GS_APP_QUIRK_NEEDS_REBOOT)) {
+ g_autoptr(GsAppList) list = gs_app_list_new ();
+ gs_app_list_add (list, helper->app);
+ gs_utils_reboot_notify (list);
+ }
+
+ /* tell the user what they have to do */
+ if (gs_app_get_kind (helper->app) == AS_APP_KIND_FIRMWARE &&
+ gs_app_has_quirk (helper->app, GS_APP_QUIRK_NEEDS_USER_ACTION)) {
+ AsScreenshot *ss = gs_app_get_action_screenshot (helper->app);
+ if (ss != NULL && as_screenshot_get_caption (ss, NULL) != NULL)
+ gs_page_show_update_message (helper, ss);
+ }
+
+ /* only show this if the window is not active */
+ if (gs_app_is_installed (helper->app) &&
+ helper->action == GS_PLUGIN_ACTION_INSTALL &&
+ !gs_shell_is_active (priv->shell) &&
+ ((helper->interaction) & GS_SHELL_INTERACTION_NOTIFY) != 0)
+ gs_app_notify_installed (helper->app);
+
+ if (gs_app_is_installed (helper->app) &&
+ GS_PAGE_GET_CLASS (page)->app_installed != NULL) {
+ GS_PAGE_GET_CLASS (page)->app_installed (page, helper->app);
+ }
+}
+
+static void
+gs_page_app_removed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ GsPage *page = helper->page;
+ gboolean ret;
+ g_autoptr(GError) error = NULL;
+
+ ret = gs_plugin_loader_job_action_finish (plugin_loader,
+ res,
+ &error);
+ if (g_error_matches (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_CANCELLED)) {
+ g_debug ("%s", error->message);
+ return;
+ }
+ if (!ret) {
+ g_warning ("failed to remove: %s", error->message);
+ return;
+ }
+
+ if (!gs_app_is_installed (helper->app) &&
+ GS_PAGE_GET_CLASS (page)->app_removed != NULL) {
+ GS_PAGE_GET_CLASS (page)->app_removed (page, helper->app);
+ }
+}
+
+GtkWidget *
+gs_page_get_header_start_widget (GsPage *page)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+
+ return priv->header_start_widget;
+}
+
+void
+gs_page_set_header_start_widget (GsPage *page, GtkWidget *widget)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+
+ g_set_object (&priv->header_start_widget, widget);
+}
+
+GtkWidget *
+gs_page_get_header_end_widget (GsPage *page)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+
+ return priv->header_end_widget;
+}
+
+void
+gs_page_set_header_end_widget (GsPage *page, GtkWidget *widget)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+
+ g_set_object (&priv->header_end_widget, widget);
+}
+
+void
+gs_page_install_app (GsPage *page,
+ GsApp *app,
+ GsShellInteraction interaction,
+ GCancellable *cancellable)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ GsPageHelper *helper;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* probably non-free */
+ if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) {
+ GtkResponseType response;
+
+ response = gs_app_notify_unavailable (app, gs_shell_get_window (priv->shell));
+ if (response != GTK_RESPONSE_OK)
+ return;
+ }
+
+ helper = g_slice_new0 (GsPageHelper);
+ helper->action = GS_PLUGIN_ACTION_INSTALL;
+ helper->app = g_object_ref (app);
+ helper->page = g_object_ref (page);
+ helper->cancellable = g_object_ref (cancellable);
+ helper->interaction = interaction;
+
+ plugin_job = gs_plugin_job_newv (helper->action,
+ "interactive", TRUE,
+ "app", helper->app,
+ NULL);
+ gs_plugin_loader_job_process_async (priv->plugin_loader,
+ plugin_job,
+ helper->cancellable,
+ gs_page_app_installed_cb,
+ helper);
+}
+
+static void
+gs_page_update_app_response_cb (GtkDialog *dialog,
+ gint response,
+ gpointer user_data)
+{
+ g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
+ GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* unmap the dialog */
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ /* not agreed */
+ if (response != GTK_RESPONSE_OK)
+ return;
+
+ g_debug ("update %s", gs_app_get_id (helper->app));
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE,
+ "interactive", TRUE,
+ "app", helper->app,
+ NULL);
+ gs_plugin_loader_job_process_async (priv->plugin_loader,
+ plugin_job,
+ helper->cancellable,
+ gs_page_app_installed_cb,
+ helper);
+ g_steal_pointer (&helper);
+}
+
+static void
+gs_page_notify_quirk_cb (GsApp *app, GParamSpec *pspec, GsPageHelper *helper)
+{
+ gtk_widget_set_sensitive (helper->button_install,
+ !gs_app_has_quirk (helper->app,
+ GS_APP_QUIRK_NEEDS_USER_ACTION));
+}
+
+static void
+gs_page_needs_user_action (GsPageHelper *helper, AsScreenshot *ss)
+{
+ GtkWidget *content_area;
+ GtkWidget *dialog;
+ g_autoptr(SoupSession) soup_session = NULL;
+ GtkWidget *ssimg;
+ g_autofree gchar *escaped = NULL;
+ GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+
+ dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_USE_HEADER_BAR,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CANCEL,
+ /* TRANSLATORS: this is a prompt message, and
+ * '%s' is an application summary, e.g. 'GNOME Clocks' */
+ _("Prepare %s"),
+ gs_app_get_name (helper->app));
+ escaped = g_markup_escape_text (as_screenshot_get_caption (ss, NULL), -1);
+ gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
+ "%s", escaped);
+
+ /* this will be enabled when the device is in the right mode */
+ helper->button_install = gtk_dialog_add_button (GTK_DIALOG (dialog),
+ /* TRANSLATORS: update the fw */
+ _("Install"),
+ GTK_RESPONSE_OK);
+ helper->notify_quirk_id =
+ g_signal_connect (helper->app, "notify::quirk",
+ G_CALLBACK (gs_page_notify_quirk_cb),
+ helper);
+ gtk_widget_set_sensitive (helper->button_install, FALSE);
+
+ /* load screenshot */
+ soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
+ gs_user_agent (), NULL);
+ ssimg = gs_screenshot_image_new (soup_session);
+ gs_screenshot_image_set_screenshot (GS_SCREENSHOT_IMAGE (ssimg), ss);
+ gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (ssimg), 400, 225);
+ gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (ssimg),
+ helper->cancellable);
+ gtk_widget_set_margin_start (ssimg, 24);
+ gtk_widget_set_margin_end (ssimg, 24);
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_container_add (GTK_CONTAINER (content_area), ssimg);
+ gtk_container_child_set (GTK_CONTAINER (content_area), ssimg, "pack-type", GTK_PACK_END, NULL);
+
+ /* handle this async */
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_page_update_app_response_cb), helper);
+ gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+}
+
+void
+gs_page_update_app (GsPage *page, GsApp *app, GCancellable *cancellable)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ GsPageHelper *helper;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* non-firmware applications do not have to be prepared */
+ helper = g_slice_new0 (GsPageHelper);
+ helper->action = GS_PLUGIN_ACTION_UPDATE;
+ helper->app = g_object_ref (app);
+ helper->page = g_object_ref (page);
+ helper->cancellable = g_object_ref (cancellable);
+
+ /* tell the user what they have to do */
+ if (gs_app_get_kind (app) == AS_APP_KIND_FIRMWARE &&
+ gs_app_has_quirk (app, GS_APP_QUIRK_NEEDS_USER_ACTION)) {
+ AsScreenshot *ss = gs_app_get_action_screenshot (app);
+ if (ss != NULL && as_screenshot_get_caption (ss, NULL) != NULL) {
+ gs_page_needs_user_action (helper, ss);
+ return;
+ }
+ }
+
+ /* generic fallback */
+ plugin_job = gs_plugin_job_newv (helper->action,
+ "interactive", TRUE,
+ "app", app,
+ NULL);
+ gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+ helper->cancellable,
+ gs_page_app_installed_cb,
+ helper);
+}
+
+static void
+gs_page_remove_app_response_cb (GtkDialog *dialog,
+ gint response,
+ gpointer user_data)
+{
+ g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
+ GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* unmap the dialog */
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ /* not agreed */
+ if (response != GTK_RESPONSE_OK)
+ return;
+
+ g_debug ("remove %s", gs_app_get_id (helper->app));
+ plugin_job = gs_plugin_job_newv (helper->action,
+ "interactive", TRUE,
+ "app", helper->app,
+ NULL);
+ gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+ helper->cancellable,
+ gs_page_app_removed_cb,
+ helper);
+ g_steal_pointer (&helper);
+}
+
+void
+gs_page_remove_app (GsPage *page, GsApp *app, GCancellable *cancellable)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ GsPageHelper *helper;
+ GtkWidget *dialog;
+ g_autofree gchar *message = NULL;
+ g_autofree gchar *title = NULL;
+ GtkWidget *remove_button;
+ GtkStyleContext *context;
+
+ /* pending install */
+ helper = g_slice_new0 (GsPageHelper);
+ helper->action = GS_PLUGIN_ACTION_REMOVE;
+ helper->app = g_object_ref (app);
+ helper->page = g_object_ref (page);
+ helper->cancellable = g_object_ref (cancellable);
+ if (gs_app_get_state (app) == AS_APP_STATE_QUEUED_FOR_INSTALL) {
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+ "interactive", TRUE,
+ "app", app,
+ NULL);
+ g_debug ("remove %s", gs_app_get_id (app));
+ gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+ helper->cancellable,
+ gs_page_app_removed_cb,
+ helper);
+ return;
+ }
+
+ /* use different name and summary */
+ switch (gs_app_get_kind (app)) {
+ case AS_APP_KIND_SOURCE:
+ /* TRANSLATORS: this is a prompt message, and '%s' is an
+ * repository name, e.g. 'GNOME Nightly' */
+ title = g_strdup_printf (_("Are you sure you want to remove "
+ "the %s repository?"),
+ gs_app_get_name (app));
+ /* TRANSLATORS: longer dialog text */
+ message = g_strdup_printf (_("All applications from %s will be "
+ "removed, and you will have to "
+ "re-install the repository to use them again."),
+ gs_app_get_name (app));
+ break;
+ default:
+ /* TRANSLATORS: this is a prompt message, and '%s' is an
+ * application summary, e.g. 'GNOME Clocks' */
+ title = g_strdup_printf (_("Are you sure you want to remove %s?"),
+ gs_app_get_name (app));
+ /* TRANSLATORS: longer dialog text */
+ message = g_strdup_printf (_("%s will be removed, and you will "
+ "have to install it to use it again."),
+ gs_app_get_name (app));
+ break;
+ }
+
+ /* ask for confirmation */
+ dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_CANCEL,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", message);
+
+ /* TRANSLATORS: this is button text to remove the application */
+ remove_button = gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
+ context = gtk_widget_get_style_context (remove_button);
+ gtk_style_context_add_class (context, "destructive-action");
+
+ /* handle this async */
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_page_remove_app_response_cb), helper);
+ gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+}
+
+static void
+gs_page_app_launched_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ g_autoptr(GError) error = NULL;
+ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
+ g_warning ("failed to launch GsApp: %s", error->message);
+ return;
+ }
+}
+
+void
+gs_page_launch_app (GsPage *page, GsApp *app, GCancellable *cancellable)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_LAUNCH,
+ "interactive", TRUE,
+ "app", app,
+ NULL);
+ gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+ cancellable,
+ gs_page_app_launched_cb,
+ NULL);
+}
+
+static void
+gs_page_app_shortcut_added_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ g_autoptr(GError) error = NULL;
+ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
+ g_warning ("failed to add a shortcut to GsApp: %s", error->message);
+ return;
+ }
+}
+
+void
+gs_page_shortcut_add (GsPage *page, GsApp *app, GCancellable *cancellable)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_ADD_SHORTCUT,
+ "interactive", TRUE,
+ "app", app,
+ NULL);
+ gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+ cancellable,
+ gs_page_app_shortcut_added_cb,
+ NULL);
+}
+
+static void
+gs_page_app_shortcut_removed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ g_autoptr(GError) error = NULL;
+ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
+ g_warning ("failed to remove the shortcut to GsApp: %s", error->message);
+ return;
+ }
+}
+
+void
+gs_page_shortcut_remove (GsPage *page, GsApp *app, GCancellable *cancellable)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE_SHORTCUT,
+ "interactive", TRUE,
+ "app", app,
+ NULL);
+ gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+ cancellable,
+ gs_page_app_shortcut_removed_cb,
+ NULL);
+}
+
+gboolean
+gs_page_is_active (GsPage *page)
+{
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ g_return_val_if_fail (GS_IS_PAGE (page), FALSE);
+ return priv->is_active;
+}
+
+/**
+ * gs_page_switch_to:
+ *
+ * Pure virtual method that subclasses have to override to show page specific
+ * widgets.
+ */
+void
+gs_page_switch_to (GsPage *page,
+ gboolean scroll_up)
+{
+ GsPageClass *klass = GS_PAGE_GET_CLASS (page);
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ priv->is_active = TRUE;
+ if (klass->switch_to != NULL)
+ klass->switch_to (page, scroll_up);
+}
+
+/**
+ * gs_page_switch_from:
+ *
+ * Pure virtual method that subclasses have to override to show page specific
+ * widgets.
+ */
+void
+gs_page_switch_from (GsPage *page)
+{
+ GsPageClass *klass = GS_PAGE_GET_CLASS (page);
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+ priv->is_active = FALSE;
+ if (klass->switch_from != NULL)
+ klass->switch_from (page);
+}
+
+void
+gs_page_reload (GsPage *page)
+{
+ GsPageClass *klass;
+ g_return_if_fail (GS_IS_PAGE (page));
+ klass = GS_PAGE_GET_CLASS (page);
+ if (klass->reload != NULL)
+ klass->reload (page);
+}
+
+gboolean
+gs_page_setup (GsPage *page,
+ GsShell *shell,
+ GsPluginLoader *plugin_loader,
+ GtkBuilder *builder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPageClass *klass;
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+
+ g_return_val_if_fail (GS_IS_PAGE (page), FALSE);
+
+ klass = GS_PAGE_GET_CLASS (page);
+ g_assert (klass->setup != NULL);
+
+ priv->plugin_loader = g_object_ref (plugin_loader);
+ priv->shell = shell;
+
+ return klass->setup (page, shell, plugin_loader, builder, cancellable, error);
+}
+
+static void
+gs_page_dispose (GObject *object)
+{
+ GsPage *page = GS_PAGE (object);
+ GsPagePrivate *priv = gs_page_get_instance_private (page);
+
+ g_clear_object (&priv->plugin_loader);
+ g_clear_object (&priv->header_start_widget);
+ g_clear_object (&priv->header_end_widget);
+
+ G_OBJECT_CLASS (gs_page_parent_class)->dispose (object);
+}
+
+static void
+gs_page_init (GsPage *page)
+{
+}
+
+static void
+gs_page_class_init (GsPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gs_page_dispose;
+}
+
+GsPage *
+gs_page_new (void)
+{
+ GsPage *page;
+ page = g_object_new (GS_TYPE_PAGE, NULL);
+ return GS_PAGE (page);
+}