[gnome-software: 5/10] Add a common base class that all shell pages derive from



commit 93d467e5f07040a4518f81f14b5aa786d64ecb7f
Author: Kalev Lember <kalevlember gmail com>
Date:   Thu Feb 5 16:07:45 2015 +0100

    Add a common base class that all shell pages derive from
    
    This splits out common install / remove helpers, avoiding code
    duplication and making it easier to share code between different pages
    in the future.

 po/POTFILES.in            |    1 +
 src/Makefile.am           |    2 +
 src/gs-page.c             |  245 +++++++++++++++++++++++++++++++++++++++++++++
 src/gs-page.h             |   74 ++++++++++++++
 src/gs-shell-category.c   |    8 ++-
 src/gs-shell-category.h   |    5 +-
 src/gs-shell-category.ui  |    2 +-
 src/gs-shell-details.c    |  198 ++++++------------------------------
 src/gs-shell-details.h    |    5 +-
 src/gs-shell-details.ui   |    2 +-
 src/gs-shell-installed.c  |  105 ++++---------------
 src/gs-shell-installed.h  |    5 +-
 src/gs-shell-installed.ui |    2 +-
 src/gs-shell-overview.c   |    8 ++-
 src/gs-shell-overview.h   |    5 +-
 src/gs-shell-overview.ui  |    2 +-
 src/gs-shell-search.c     |  188 +++++------------------------------
 src/gs-shell-search.h     |    5 +-
 src/gs-shell-search.ui    |    2 +-
 src/gs-shell-updates.c    |    8 ++-
 src/gs-shell-updates.h    |    5 +-
 src/gs-shell-updates.ui   |    2 +-
 22 files changed, 448 insertions(+), 431 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index da8f25b..2d6edc4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,6 +16,7 @@ src/gs-history-dialog.c
 [type: gettext/glade]src/gs-history-dialog.ui
 src/gs-main.c
 src/gs-offline-updates.c
+src/gs-page.c
 src/gs-plugin-loader.c
 src/gs-popular-tile.c
 src/gs-screenshot-image.c
diff --git a/src/Makefile.am b/src/Makefile.am
index d1a62d3..b27988b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -123,6 +123,8 @@ gnome_software_SOURCES =                            \
        gs-history-dialog.h                             \
        gs-box.h                                        \
        gs-box.c                                        \
+       gs-page.c                                       \
+       gs-page.h                                       \
        gs-plugin.c                                     \
        gs-plugin.h                                     \
        gs-profile.c                                    \
diff --git a/src/gs-page.c b/src/gs-page.c
new file mode 100644
index 0000000..0a1917f
--- /dev/null
+++ b/src/gs-page.c
@@ -0,0 +1,245 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "gs-app.h"
+#include "gs-app-row.h"
+#include "gs-cleanup.h"
+#include "gs-page.h"
+#include "gs-shell.h"
+#include "gs-utils.h"
+
+struct _GsPagePrivate
+{
+       GsPluginLoader          *plugin_loader;
+       GCancellable            *cancellable;
+       GsShell                 *shell;
+};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GsPage, gs_page, GTK_TYPE_BIN)
+
+typedef struct {
+       GsApp           *app;
+       GsPage          *page;
+} InstallRemoveData;
+
+static void
+install_remove_data_free (InstallRemoveData *data)
+{
+       if (data->app != NULL)
+               g_object_unref (data->app);
+       if (data->page != NULL)
+               g_object_unref (data->page);
+       g_slice_free (InstallRemoveData, data);
+}
+
+static void
+gs_page_app_installed_cb (GObject *source,
+                          GAsyncResult *res,
+                          gpointer user_data)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+       InstallRemoveData *data = (InstallRemoveData *) user_data;
+       GsPage *page = data->page;
+       GsPagePrivate *priv = gs_page_get_instance_private (page);
+       gboolean ret;
+       _cleanup_error_free_ GError *error = NULL;
+
+       ret = gs_plugin_loader_app_action_finish (plugin_loader,
+                                                 res,
+                                                 &error);
+       if (!ret) {
+               g_warning ("failed to install %s: %s",
+                          gs_app_get_id (data->app),
+                          error->message);
+               gs_app_notify_failed_modal (data->app,
+                                           gs_shell_get_window (priv->shell),
+                                           GS_PLUGIN_LOADER_ACTION_INSTALL,
+                                           error);
+               return;
+       }
+
+       /* only show this if the window is not active */
+       if (gs_app_get_state (data->app) != AS_APP_STATE_QUEUED_FOR_INSTALL &&
+           !gs_shell_is_active (priv->shell))
+               gs_app_notify_installed (data->app);
+
+       if (GS_PAGE_GET_CLASS (page)->app_installed != NULL)
+               GS_PAGE_GET_CLASS (page)->app_installed (page, data->app);
+
+       install_remove_data_free (data);
+}
+
+static void
+gs_page_app_removed_cb (GObject *source,
+                        GAsyncResult *res,
+                        gpointer user_data)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+       InstallRemoveData *data = (InstallRemoveData *) user_data;
+       GsPage *page = data->page;
+       GsPagePrivate *priv = gs_page_get_instance_private (page);
+       gboolean ret;
+       _cleanup_error_free_ GError *error = NULL;
+
+       ret = gs_plugin_loader_app_action_finish (plugin_loader,
+                                                 res,
+                                                 &error);
+       if (!ret) {
+               g_warning ("failed to remove: %s", error->message);
+               gs_app_notify_failed_modal (data->app,
+                                           gs_shell_get_window (priv->shell),
+                                           GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                           error);
+       }
+
+       if (GS_PAGE_GET_CLASS (page)->app_removed != NULL)
+               GS_PAGE_GET_CLASS (page)->app_removed (page, data->app);
+
+       install_remove_data_free (data);
+}
+
+void
+gs_page_install_app (GsPage *page, GsApp *app)
+{
+       GsPagePrivate *priv = gs_page_get_instance_private (page);
+       InstallRemoveData *data;
+       GtkResponseType response;
+
+       /* probably non-free */
+       if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) {
+               response = gs_app_notify_unavailable (app, gs_shell_get_window (priv->shell));
+               if (response != GTK_RESPONSE_OK)
+                       return;
+       }
+
+       data = g_slice_new0 (InstallRemoveData);
+       data->app = g_object_ref (app);
+       data->page = g_object_ref (page);
+       gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                          app,
+                                          GS_PLUGIN_LOADER_ACTION_INSTALL,
+                                          priv->cancellable,
+                                          gs_page_app_installed_cb,
+                                          data);
+}
+
+void
+gs_page_remove_app (GsPage *page, GsApp *app)
+{
+       GsPagePrivate *priv = gs_page_get_instance_private (page);
+       GtkResponseType response;
+       GtkWidget *dialog;
+       _cleanup_string_free_ GString *markup = NULL;
+
+       markup = g_string_new ("");
+       g_string_append_printf (markup,
+                               /* TRANSLATORS: this is a prompt message, and
+                                * '%s' is an application summary, e.g. 'GNOME Clocks' */
+                               _("Are you sure you want to remove %s?"),
+                               gs_app_get_name (app));
+       g_string_prepend (markup, "<b>");
+       g_string_append (markup, "</b>");
+       dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
+                                        GTK_DIALOG_MODAL,
+                                        GTK_MESSAGE_QUESTION,
+                                        GTK_BUTTONS_CANCEL,
+                                        NULL);
+       gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), markup->str);
+       gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
+                                                   /* TRANSLATORS: longer dialog text */
+                                                    _("%s will be removed, and you will have to install it 
to use it again."),
+                                                    gs_app_get_name (app));
+       /* TRANSLATORS: this is button text to remove the application */
+       gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
+       if (gs_app_get_state (app) == AS_APP_STATE_QUEUED_FOR_INSTALL)
+               response = GTK_RESPONSE_OK; /* pending install */
+       else
+               response = gtk_dialog_run (GTK_DIALOG (dialog));
+       if (response == GTK_RESPONSE_OK) {
+               InstallRemoveData *data;
+               g_debug ("remove %s", gs_app_get_id (app));
+               data = g_slice_new0 (InstallRemoveData);
+               data->app = g_object_ref (app);
+               data->page = g_object_ref (page);
+               gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                                  app,
+                                                  GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                                  priv->cancellable,
+                                                  gs_page_app_removed_cb,
+                                                  data);
+       }
+       gtk_widget_destroy (dialog);
+}
+
+void
+gs_page_setup (GsPage *page,
+               GsShell *shell,
+               GsPluginLoader *plugin_loader,
+               GCancellable *cancellable)
+{
+       GsPagePrivate *priv = gs_page_get_instance_private (page);
+
+       g_return_if_fail (GS_IS_PAGE (page));
+
+       priv->plugin_loader = g_object_ref (plugin_loader);
+       priv->cancellable = g_object_ref (cancellable);
+       priv->shell = shell;
+}
+
+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->cancellable);
+
+       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);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-page.h b/src/gs-page.h
new file mode 100644
index 0000000..9f349e7
--- /dev/null
+++ b/src/gs-page.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GS_PAGE_H
+#define GS_PAGE_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gs-shell.h"
+#include "gs-plugin-loader.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PAGE           (gs_page_get_type ())
+#define GS_PAGE(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_PAGE, GsPage))
+#define GS_PAGE_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), GS_TYPE_PAGE, GsPageClass))
+#define GS_IS_PAGE(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_PAGE))
+#define GS_IS_PAGE_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GS_TYPE_PAGE))
+#define GS_PAGE_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), GS_TYPE_PAGE, GsPageClass))
+
+typedef struct _GsPage         GsPage;
+typedef struct _GsPageClass    GsPageClass;
+typedef struct _GsPagePrivate  GsPagePrivate;
+
+struct _GsPageClass
+{
+       GtkBinClass      parent_class;
+
+       void            (*app_installed)        (GsPage          *page,
+                                                GsApp           *app);
+       void            (*app_removed)          (GsPage          *page,
+                                                GsApp           *app);
+};
+
+struct _GsPage
+{
+        GtkBin          parent;
+};
+
+GType           gs_page_get_type                       (void);
+GsPage         *gs_page_new                            (void);
+void            gs_page_install_app                    (GsPage         *page,
+                                                        GsApp          *app);
+void            gs_page_remove_app                     (GsPage         *page,
+                                                        GsApp          *app);
+void            gs_page_setup                          (GsPage         *page,
+                                                        GsShell        *shell,
+                                                        GsPluginLoader *plugin_loader,
+                                                        GCancellable   *cancellable);
+
+G_END_DECLS
+
+#endif /* GS_PAGE_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-shell-category.c b/src/gs-shell-category.c
index e6af9a5..dfa535a 100644
--- a/src/gs-shell-category.c
+++ b/src/gs-shell-category.c
@@ -45,7 +45,7 @@ struct GsShellCategoryPrivate {
        GtkWidget       *scrolledwindow_filter;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GsShellCategory, gs_shell_category, GTK_TYPE_BIN)
+G_DEFINE_TYPE_WITH_PRIVATE (GsShellCategory, gs_shell_category, GS_TYPE_PAGE)
 
 /**
  * gs_shell_category_reload:
@@ -363,6 +363,12 @@ gs_shell_category_setup (GsShellCategory *shell_category,
 
        g_signal_connect (priv->listbox_filter, "key-press-event",
                          G_CALLBACK (key_event), shell_category);
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (shell_category),
+                      shell,
+                      plugin_loader,
+                      cancellable);
 }
 
 GsShellCategory *
diff --git a/src/gs-shell-category.h b/src/gs-shell-category.h
index abf3f91..b85f992 100644
--- a/src/gs-shell-category.h
+++ b/src/gs-shell-category.h
@@ -26,6 +26,7 @@
 #include <gtk/gtk.h>
 
 #include "gs-category.h"
+#include "gs-page.h"
 #include "gs-shell.h"
 #include "gs-plugin-loader.h"
 
@@ -42,13 +43,13 @@ typedef struct GsShellCategoryPrivate GsShellCategoryPrivate;
 
 typedef struct
 {
-        GtkBin                          parent;
+        GsPage                          parent;
         GsShellCategoryPrivate         *priv;
 } GsShellCategory;
 
 typedef struct
 {
-       GtkBinClass                      parent_class;
+       GsPageClass                      parent_class;
 } GsShellCategoryClass;
 
 GType           gs_shell_category_get_type     (void);
diff --git a/src/gs-shell-category.ui b/src/gs-shell-category.ui
index 9172ad5..fa896ac 100644
--- a/src/gs-shell-category.ui
+++ b/src/gs-shell-category.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.10"/>
-  <template class="GsShellCategory" parent="GtkBin">
+  <template class="GsShellCategory" parent="GsPage">
     <child>
       <object class="GtkBox" id="box_category">
         <property name="visible">True</property>
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index 95bce63..58531f0 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -92,7 +92,7 @@ struct GsShellDetailsPrivate
        GtkWidget               *stack_details;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GsShellDetails, gs_shell_details, GTK_TYPE_BIN)
+G_DEFINE_TYPE_WITH_PRIVATE (GsShellDetails, gs_shell_details, GS_TYPE_PAGE)
 
 /**
  * gs_shell_details_set_state:
@@ -1044,136 +1044,6 @@ gs_shell_details_get_app (GsShellDetails *shell_details)
        return shell_details->priv->app;
 }
 
-typedef struct {
-       GsShellDetails  *shell_details;
-       GsApp           *app;
-} GsShellDetailsHelper;
-
-/**
- * gs_shell_details_app_installed_cb:
- **/
-static void
-gs_shell_details_app_installed_cb (GObject *source,
-                                  GAsyncResult *res,
-                                  gpointer user_data)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsShellDetailsHelper *helper = (GsShellDetailsHelper *) user_data;
-       gboolean ret;
-       _cleanup_error_free_ GError *error = NULL;
-
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
-       if (!ret) {
-               g_warning ("failed to install %s: %s",
-                          gs_app_get_id (helper->app),
-                          error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (helper->shell_details->priv->shell),
-                                           GS_PLUGIN_LOADER_ACTION_INSTALL,
-                                           error);
-               return;
-       }
-
-       /* only show this if the window is not active */
-       if (gs_app_get_state (helper->app) != AS_APP_STATE_QUEUED_FOR_INSTALL &&
-           !gs_shell_is_active (helper->shell_details->priv->shell))
-               gs_app_notify_installed (helper->app);
-       gs_shell_details_reload (helper->shell_details);
-       g_object_unref (helper->shell_details);
-       g_object_unref (helper->app);
-       g_free (helper);
-}
-
-/**
- * gs_shell_details_app_removed_cb:
- **/
-static void
-gs_shell_details_app_removed_cb (GObject *source,
-                                GAsyncResult *res,
-                                gpointer user_data)
-{
-       _cleanup_error_free_ GError *error = NULL;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsShellDetailsHelper *helper = (GsShellDetailsHelper *) user_data;
-       gboolean ret;
-
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
-       if (!ret) {
-               g_warning ("failed to remove %s: %s",
-                          gs_app_get_id (helper->app),
-                          error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (helper->shell_details->priv->shell),
-                                           GS_PLUGIN_LOADER_ACTION_REMOVE,
-                                           error);
-               return;
-       }
-
-       gs_shell_details_reload (helper->shell_details);
-       g_object_unref (helper->shell_details);
-       g_object_unref (helper->app);
-       g_free (helper);
-}
-
-/**
- * gs_shell_details_app_remove
- **/
-static void
-gs_shell_details_app_remove (GsShellDetails *shell_details, GsApp *app)
-{
-       GsShellDetailsHelper *helper;
-       GsShellDetailsPrivate *priv = shell_details->priv;
-       GtkResponseType response;
-       GtkWidget *dialog;
-       _cleanup_string_free_ GString *markup = NULL;
-
-       markup = g_string_new ("");
-       g_string_append_printf (markup,
-                               /* TRANSLATORS: this is a prompt message, and
-                                * '%s' is an application summary, e.g. 'GNOME Clocks' */
-                               _("Are you sure you want to remove %s?"),
-                               gs_app_get_name (app));
-       g_string_prepend (markup, "<b>");
-       g_string_append (markup, "</b>");
-       dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
-                                        GTK_DIALOG_MODAL,
-                                        GTK_MESSAGE_QUESTION,
-                                        GTK_BUTTONS_CANCEL,
-                                        NULL);
-       gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), markup->str);
-       gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
-                                                   /* TRANSLATORS: longer dialog text */
-                                                   _("%s will be removed, and you will have to install it to 
use it again."),
-                                                   gs_app_get_name (app));
-       /* TRANSLATORS: this is button text to remove the application */
-       gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
-       if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED)
-               response = gtk_dialog_run (GTK_DIALOG (dialog));
-       else
-               response = GTK_RESPONSE_OK; /* pending install */
-       if (response == GTK_RESPONSE_OK) {
-               g_debug ("remove %s", gs_app_get_id (app));
-               helper = g_new0 (GsShellDetailsHelper, 1);
-               helper->shell_details = g_object_ref (shell_details);
-               helper->app = g_object_ref (app);
-               gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                                  app,
-                                                  GS_PLUGIN_LOADER_ACTION_REMOVE,
-                                                  priv->cancellable,
-                                                  gs_shell_details_app_removed_cb,
-                                                  helper);
-       }
-       g_string_free (markup, TRUE);
-       gtk_widget_destroy (dialog);
-
-       gs_shell_details_refresh_addons (shell_details);
-       gs_shell_details_refresh_all (shell_details);
-}
-
 /**
  * gs_shell_details_app_remove_button_cb:
  **/
@@ -1182,35 +1052,7 @@ gs_shell_details_app_remove_button_cb (GtkWidget *widget, GsShellDetails *shell_
 {
        GsShellDetailsPrivate *priv = shell_details->priv;
 
-       gs_shell_details_app_remove (shell_details, priv->app);
-}
-
-/**
- * gs_shell_details_app_install:
- **/
-static void
-gs_shell_details_app_install (GsShellDetails *shell_details, GsApp *app)
-{
-       GsShellDetailsPrivate *priv = shell_details->priv;
-       GsShellDetailsHelper *helper;
-       GtkResponseType response;
-
-       /* probably non-free */
-       if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) {
-               response = gs_app_notify_unavailable (app, gs_shell_get_window (priv->shell));
-               if (response != GTK_RESPONSE_OK)
-                       return;
-       }
-
-       helper = g_new0 (GsShellDetailsHelper, 1);
-       helper->shell_details = g_object_ref (shell_details);
-       helper->app = g_object_ref (app);
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_LOADER_ACTION_INSTALL,
-                                          priv->cancellable,
-                                          gs_shell_details_app_installed_cb,
-                                          helper);
+       gs_page_remove_app (GS_PAGE (shell_details), priv->app);
 }
 
 /**
@@ -1234,7 +1076,7 @@ gs_shell_details_app_install_button_cb (GtkWidget *widget, GsShellDetails *shell
                }
        }
 
-       gs_shell_details_app_install (shell_details, priv->app);
+       gs_page_install_app (GS_PAGE (shell_details), priv->app);
 }
 
 /**
@@ -1256,10 +1098,15 @@ gs_shell_details_addon_selected_cb (GsAppAddonRow *row,
        switch (gs_app_get_state (priv->app)) {
        case AS_APP_STATE_INSTALLED:
        case AS_APP_STATE_UPDATABLE:
-               if (gs_app_addon_row_get_selected (row))
-                       gs_shell_details_app_install (shell_details, addon);
-               else
-                       gs_shell_details_app_remove (shell_details, addon);
+               if (gs_app_addon_row_get_selected (row)) {
+                       gs_page_install_app (GS_PAGE (shell_details), addon);
+               } else {
+                       gs_page_remove_app (GS_PAGE (shell_details), addon);
+                       /* make sure the addon checkboxes are synced if the
+                        * user clicks cancel in the remove confirmation dialog */
+                       gs_shell_details_refresh_addons (shell_details);
+                       gs_shell_details_refresh_all (shell_details);
+               }
                break;
        default:
                break;
@@ -1348,6 +1195,18 @@ gs_shell_details_rating_changed_cb (GsStarWidget *star,
                                           shell_details);
 }
 
+static void
+gs_shell_details_app_installed (GsPage *page, GsApp *app)
+{
+       gs_shell_details_reload (GS_SHELL_DETAILS (page));
+}
+
+static void
+gs_shell_details_app_removed (GsPage *page, GsApp *app)
+{
+       gs_shell_details_reload (GS_SHELL_DETAILS (page));
+}
+
 /**
  * gs_shell_details_setup:
  */
@@ -1402,6 +1261,12 @@ gs_shell_details_setup (GsShellDetails *shell_details,
 
        adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow_details));
        gtk_container_set_focus_vadjustment (GTK_CONTAINER (priv->box_details), adj);
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (shell_details),
+                      shell,
+                      plugin_loader,
+                      cancellable);
 }
 
 /**
@@ -1411,9 +1276,12 @@ static void
 gs_shell_details_class_init (GsShellDetailsClass *klass)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GsPageClass *page_class = GS_PAGE_CLASS (klass);
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
        object_class->finalize = gs_shell_details_finalize;
+       page_class->app_installed = gs_shell_details_app_installed;
+       page_class->app_removed = gs_shell_details_app_removed;
 
        gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-shell-details.ui");
 
diff --git a/src/gs-shell-details.h b/src/gs-shell-details.h
index e293f29..5bf0169 100644
--- a/src/gs-shell-details.h
+++ b/src/gs-shell-details.h
@@ -27,6 +27,7 @@
 
 #include "gs-app.h"
 #include "gs-shell.h"
+#include "gs-page.h"
 #include "gs-plugin-loader.h"
 
 G_BEGIN_DECLS
@@ -42,13 +43,13 @@ typedef struct GsShellDetailsPrivate GsShellDetailsPrivate;
 
 typedef struct
 {
-        GtkBin                          parent;
+        GsPage                          parent;
         GsShellDetailsPrivate          *priv;
 } GsShellDetails;
 
 typedef struct
 {
-       GtkBinClass                      parent_class;
+       GsPageClass                      parent_class;
 } GsShellDetailsClass;
 
 GType           gs_shell_details_get_type      (void);
diff --git a/src/gs-shell-details.ui b/src/gs-shell-details.ui
index 786185c..595b546 100644
--- a/src/gs-shell-details.ui
+++ b/src/gs-shell-details.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.10"/>
-  <template class="GsShellDetails" parent="GtkBin">
+  <template class="GsShellDetails" parent="GsPage">
     <child internal-child="accessible">
       <object class="AtkObject" id="details-accessible">
         <property name="accessible-name" translatable="yes">Details page</property>
diff --git a/src/gs-shell-installed.c b/src/gs-shell-installed.c
index 7ce9f0d..e079e83 100644
--- a/src/gs-shell-installed.c
+++ b/src/gs-shell-installed.c
@@ -59,7 +59,7 @@ struct GsShellInstalledPrivate
        GtkWidget               *stack_install;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GsShellInstalled, gs_shell_installed, GTK_TYPE_BIN)
+G_DEFINE_TYPE_WITH_PRIVATE (GsShellInstalled, gs_shell_installed, GS_TYPE_PAGE)
 
 static void gs_shell_installed_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
                                                        GsShellInstalled *shell_installed);
@@ -90,11 +90,6 @@ gs_shell_installed_app_row_activated_cb (GtkListBox *list_box,
        }
 }
 
-typedef struct {
-       GsAppRow                *app_row;
-       GsShellInstalled        *shell_installed;
-} GsShellInstalledHelper;
-
 static void
 row_unrevealed (GObject *row, GParamSpec *pspec, gpointer data)
 {
@@ -104,44 +99,23 @@ row_unrevealed (GObject *row, GParamSpec *pspec, gpointer data)
        gtk_container_remove (GTK_CONTAINER (list), GTK_WIDGET (row));
 }
 
-/**
- * gs_shell_installed_app_removed_cb:
- **/
 static void
-gs_shell_installed_app_removed_cb (GObject *source,
-                                  GAsyncResult *res,
-                                  gpointer user_data)
+gs_shell_installed_app_removed (GsPage *page, GsApp *app)
 {
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsShellInstalledHelper *helper = (GsShellInstalledHelper *) user_data;
-       GsShellInstalledPrivate *priv = helper->shell_installed->priv;
-       GsApp *app;
-       gboolean ret;
-       _cleanup_error_free_ GError *error = NULL;
+       GsShellInstalled *shell_installed = GS_SHELL_INSTALLED (page);
+       GsShellInstalledPrivate *priv = shell_installed->priv;
+       GList *l;
+       _cleanup_list_free_ GList *children = NULL;
 
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
-       if (!ret) {
-               app = gs_app_row_get_app (helper->app_row);
-               g_warning ("failed to remove %s: %s",
-                          gs_app_get_id (app), error->message);
-               gs_app_notify_failed_modal (app,
-                                           gs_shell_get_window (priv->shell),
-                                           GS_PLUGIN_LOADER_ACTION_REMOVE,
-                                           error);
-       } else {
-               /* remove from the list */
-               app = gs_app_row_get_app (helper->app_row);
-               g_debug ("removed %s", gs_app_get_id (app));
-               gs_app_row_unreveal (helper->app_row);
-               g_signal_connect (helper->app_row, "unrevealed",
-                                 G_CALLBACK (row_unrevealed), NULL);
+       children = gtk_container_get_children (GTK_CONTAINER (priv->list_box_install));
+       for (l = children; l; l = l->next) {
+               GsAppRow *app_row = GS_APP_ROW (l->data);
+               if (gs_app_row_get_app (app_row) == app) {
+                       gs_app_row_unreveal (app_row);
+                       g_signal_connect (app_row, "unrevealed",
+                                         G_CALLBACK (row_unrevealed), NULL);
+               }
        }
-
-       g_object_unref (helper->app_row);
-       g_object_unref (helper->shell_installed);
-       g_free (helper);
 }
 
 /**
@@ -152,50 +126,9 @@ gs_shell_installed_app_remove_cb (GsAppRow *app_row,
                                  GsShellInstalled *shell_installed)
 {
        GsApp *app;
-       GsShellInstalledPrivate *priv = shell_installed->priv;
-       GtkResponseType response;
-       GtkWidget *dialog;
-       GsShellInstalledHelper *helper;
-       _cleanup_string_free_ GString *markup = NULL;
 
-       markup = g_string_new ("");
        app = gs_app_row_get_app (app_row);
-       g_string_append_printf (markup,
-                               /* TRANSLATORS: this is a prompt message, and
-                                * '%s' is an application summary, e.g. 'GNOME Clocks' */
-                               _("Are you sure you want to remove %s?"),
-                               gs_app_get_name (app));
-       g_string_prepend (markup, "<b>");
-       g_string_append (markup, "</b>");
-       dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
-                                        GTK_DIALOG_MODAL,
-                                        GTK_MESSAGE_QUESTION,
-                                        GTK_BUTTONS_CANCEL,
-                                        NULL);
-       gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), markup->str);
-       gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
-                                                   /* TRANSLATORS: longer dialog text */
-                                                   _("%s will be removed, and you will have to install it to 
use it again."),
-                                                   gs_app_get_name (app));
-       /* TRANSLATORS: this is button text to remove the application */
-       gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
-       if (gs_app_get_state (app) == AS_APP_STATE_QUEUED_FOR_INSTALL)
-               response = GTK_RESPONSE_OK; /* pending install */
-       else
-               response = gtk_dialog_run (GTK_DIALOG (dialog));
-       if (response == GTK_RESPONSE_OK) {
-               g_debug ("removing %s", gs_app_get_id (app));
-               helper = g_new0 (GsShellInstalledHelper, 1);
-               helper->shell_installed = g_object_ref (shell_installed);
-               helper->app_row = g_object_ref (app_row);
-               gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                                  app,
-                                                  GS_PLUGIN_LOADER_ACTION_REMOVE,
-                                                  priv->cancellable,
-                                                  gs_shell_installed_app_removed_cb,
-                                                  helper);
-       }
-       gtk_widget_destroy (dialog);
+       gs_page_remove_app (GS_PAGE (shell_installed), app);
 }
 
 static gboolean
@@ -821,6 +754,12 @@ gs_shell_installed_setup (GsShellInstalled *shell_installed,
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "select_none_menuitem"));
        g_signal_connect (widget, "activate",
                          G_CALLBACK (select_none_cb), shell_installed);
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (shell_installed),
+                      shell,
+                      plugin_loader,
+                      cancellable);
 }
 
 /**
@@ -830,9 +769,11 @@ static void
 gs_shell_installed_class_init (GsShellInstalledClass *klass)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GsPageClass *page_class = GS_PAGE_CLASS (klass);
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
        object_class->finalize = gs_shell_installed_finalize;
+       page_class->app_removed = gs_shell_installed_app_removed;
 
        gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Software/gs-shell-installed.ui");
 
diff --git a/src/gs-shell-installed.h b/src/gs-shell-installed.h
index 0552779..be33258 100644
--- a/src/gs-shell-installed.h
+++ b/src/gs-shell-installed.h
@@ -25,6 +25,7 @@
 #include <glib-object.h>
 #include <gtk/gtk.h>
 
+#include "gs-page.h"
 #include "gs-plugin-loader.h"
 
 G_BEGIN_DECLS
@@ -40,13 +41,13 @@ typedef struct GsShellInstalledPrivate GsShellInstalledPrivate;
 
 typedef struct
 {
-        GtkBin                          parent;
+        GsPage                          parent;
         GsShellInstalledPrivate        *priv;
 } GsShellInstalled;
 
 typedef struct
 {
-       GtkBinClass                      parent_class;
+       GsPageClass                      parent_class;
 } GsShellInstalledClass;
 
 GType           gs_shell_installed_get_type    (void);
diff --git a/src/gs-shell-installed.ui b/src/gs-shell-installed.ui
index f4f4f15..bb9ae42 100644
--- a/src/gs-shell-installed.ui
+++ b/src/gs-shell-installed.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.10"/>
-  <template class="GsShellInstalled" parent="GtkBin">
+  <template class="GsShellInstalled" parent="GsPage">
     <child internal-child="accessible">
       <object class="AtkObject" id="installed-accessible">
         <property name="accessible-name" translatable="yes">Installed page</property>
diff --git a/src/gs-shell-overview.c b/src/gs-shell-overview.c
index 8c2203c..12e95ad 100644
--- a/src/gs-shell-overview.c
+++ b/src/gs-shell-overview.c
@@ -62,7 +62,7 @@ struct GsShellOverviewPrivate
        GtkWidget               *stack_overview;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GsShellOverview, gs_shell_overview, GTK_TYPE_BIN)
+G_DEFINE_TYPE_WITH_PRIVATE (GsShellOverview, gs_shell_overview, GS_TYPE_PAGE)
 
 enum {
        SIGNAL_REFRESHED,
@@ -475,6 +475,12 @@ gs_shell_overview_setup (GsShellOverview *shell_overview,
                tile = gs_popular_tile_new (NULL);
                gtk_box_pack_start (GTK_BOX (priv->box_popular_rotating), tile, TRUE, TRUE, 0);
        }
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (shell_overview),
+                      shell,
+                      plugin_loader,
+                      cancellable);
 }
 
 static void
diff --git a/src/gs-shell-overview.h b/src/gs-shell-overview.h
index 0efbe1a..0f2acfd 100644
--- a/src/gs-shell-overview.h
+++ b/src/gs-shell-overview.h
@@ -26,6 +26,7 @@
 #include <gtk/gtk.h>
 
 #include "gs-app.h"
+#include "gs-page.h"
 #include "gs-shell.h"
 #include "gs-plugin-loader.h"
 
@@ -42,13 +43,13 @@ typedef struct GsShellOverviewPrivate GsShellOverviewPrivate;
 
 typedef struct
 {
-        GtkBin                  parent;
+        GsPage                  parent;
         GsShellOverviewPrivate *priv;
 } GsShellOverview;
 
 typedef struct
 {
-       GtkBinClass              parent_class;
+       GsPageClass              parent_class;
 
        void    (*refreshed)    (GsShellOverview *shell);
 } GsShellOverviewClass;
diff --git a/src/gs-shell-overview.ui b/src/gs-shell-overview.ui
index 3a16937..0a7415f 100644
--- a/src/gs-shell-overview.ui
+++ b/src/gs-shell-overview.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.10"/>
-  <template class="GsShellOverview" parent="GtkBin">
+  <template class="GsShellOverview" parent="GsPage">
     <child internal-child="accessible">
       <object class="AtkObject" id="overview-accessible">
         <property name="accessible-name" translatable="yes">Overview page</property>
diff --git a/src/gs-shell-search.c b/src/gs-shell-search.c
index ba0f5a7..f09791f 100644
--- a/src/gs-shell-search.c
+++ b/src/gs-shell-search.c
@@ -51,7 +51,7 @@ struct GsShellSearchPrivate
        GtkWidget               *stack_search;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GsShellSearch, gs_shell_search, GTK_TYPE_BIN)
+G_DEFINE_TYPE_WITH_PRIVATE (GsShellSearch, gs_shell_search, GS_TYPE_PAGE)
 
 static void
 gs_shell_search_app_row_activated_cb (GtkListBox *list_box,
@@ -63,165 +63,6 @@ gs_shell_search_app_row_activated_cb (GtkListBox *list_box,
        gs_shell_show_app (shell_search->priv->shell, app);
 }
 
-typedef struct {
-       GsApp                   *app;
-       GsShellSearch           *shell_search;
-} GsShellSearchHelper;
-
-static void
-gs_shell_search_app_installed_cb (GObject *source,
-                                 GAsyncResult *res,
-                                 gpointer user_data)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsShellSearchHelper *helper = (GsShellSearchHelper *) user_data;
-       gboolean ret;
-       _cleanup_error_free_ GError *error = NULL;
-
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
-       if (!ret) {
-               g_warning ("failed to install %s: %s",
-                          gs_app_get_id (helper->app),
-                          error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (helper->shell_search->priv->shell),
-                                           GS_PLUGIN_LOADER_ACTION_INSTALL,
-                                           error);
-       } else {
-               /* only show this if the window is not active */
-               if (!gs_shell_is_active (helper->shell_search->priv->shell))
-                       gs_app_notify_installed (helper->app);
-       }
-       gs_shell_search_reload (helper->shell_search);
-       g_object_unref (helper->app);
-       g_object_unref (helper->shell_search);
-       g_free (helper);
-}
-
-/**
- * gs_shell_search_finished_func:
- **/
-static void
-gs_shell_search_app_removed_cb (GObject *source,
-                               GAsyncResult *res,
-                               gpointer user_data)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsShellSearchHelper *helper = (GsShellSearchHelper *) user_data;
-       gboolean ret;
-       _cleanup_error_free_ GError *error = NULL;
-
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
-       if (!ret) {
-               g_warning ("failed to remove: %s", error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (helper->shell_search->priv->shell),
-                                           GS_PLUGIN_LOADER_ACTION_REMOVE,
-                                           error);
-       }
-       gs_shell_search_reload (helper->shell_search);
-       g_object_unref (helper->app);
-       g_object_unref (helper->shell_search);
-       g_free (helper);
-}
-
-/**
- * gs_shell_search_app_remove:
- **/
-static void
-gs_shell_search_app_remove (GsShellSearch *shell_search, GsApp *app)
-{
-       GsShellSearchPrivate *priv = shell_search->priv;
-       GtkResponseType response;
-       GtkWidget *dialog;
-       _cleanup_string_free_ GString *markup = NULL;
-
-       markup = g_string_new ("");
-       g_string_append_printf (markup,
-                               /* TRANSLATORS: this is a prompt message, and
-                                * '%s' is an application summary, e.g. 'GNOME Clocks' */
-                               _("Are you sure you want to remove %s?"),
-                               gs_app_get_name (app));
-       g_string_prepend (markup, "<b>");
-       g_string_append (markup, "</b>");
-       dialog = gtk_message_dialog_new (gs_shell_get_window (priv->shell),
-                                        GTK_DIALOG_MODAL,
-                                        GTK_MESSAGE_QUESTION,
-                                        GTK_BUTTONS_CANCEL,
-                                        NULL);
-       gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), markup->str);
-       gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
-                                                   /* TRANSLATORS: longer dialog text */
-                                                   _("%s will be removed, and you will have to install it to 
use it again."),
-                                                   gs_app_get_name (app));
-       /* TRANSLATORS: this is button text to remove the application */
-       gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
-       response = gtk_dialog_run (GTK_DIALOG (dialog));
-       if (response == GTK_RESPONSE_OK) {
-               GsShellSearchHelper *helper;
-               g_debug ("remove %s", gs_app_get_id (app));
-               helper = g_new0 (GsShellSearchHelper, 1);
-               helper->app = g_object_ref (app);
-               helper->shell_search = g_object_ref (shell_search);
-               gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                                  app,
-                                                  GS_PLUGIN_LOADER_ACTION_REMOVE,
-                                                  priv->cancellable,
-                                                  gs_shell_search_app_removed_cb,
-                                                  helper);
-       }
-       gtk_widget_destroy (dialog);
-}
-
-/**
- * gs_shell_search_app_install:
- **/
-static void
-gs_shell_search_app_install (GsShellSearch *shell_search, GsApp *app)
-{
-       GsShellSearchPrivate *priv = shell_search->priv;
-       GsShellSearchHelper *helper;
-       helper = g_new0 (GsShellSearchHelper, 1);
-       helper->app = g_object_ref (app);
-       helper->shell_search = g_object_ref (shell_search);
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_LOADER_ACTION_INSTALL,
-                                          priv->cancellable,
-                                          gs_shell_search_app_installed_cb,
-                                          helper);
-}
-
-/**
- * gs_shell_search_install_unavailable_app:
- **/
-static void
-gs_shell_search_install_unavailable_app (GsShellSearch *shell_search, GsApp *app)
-{
-       GsShellSearchPrivate *priv = shell_search->priv;
-       GtkResponseType response;
-       GsShellSearchHelper *helper;
-
-       /* get confirmation */
-       response = gs_app_notify_unavailable (app, gs_shell_get_window (priv->shell));
-       if (response == GTK_RESPONSE_OK) {
-               g_debug ("installing %s", gs_app_get_id (app));
-               helper = g_new0 (GsShellSearchHelper, 1);
-               helper->shell_search = g_object_ref (shell_search);
-               helper->app = g_object_ref (app);
-               gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                                  app,
-                                                  GS_PLUGIN_LOADER_ACTION_INSTALL,
-                                                  priv->cancellable,
-                                                  gs_shell_search_app_installed_cb,
-                                                  helper);
-       }
-}
-
 /**
  * gs_shell_search_app_row_clicked_cb:
  **/
@@ -232,12 +73,12 @@ gs_shell_search_app_row_clicked_cb (GsAppRow *app_row,
        GsApp *app;
        app = gs_app_row_get_app (app_row);
        if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE)
-               gs_shell_search_app_install (shell_search, app);
+               gs_page_install_app (GS_PAGE (shell_search), app);
        else if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED)
-               gs_shell_search_app_remove (shell_search, app);
+               gs_page_remove_app (GS_PAGE (shell_search), app);
        else if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) {
                if (gs_app_get_url (app, AS_URL_KIND_MISSING) == NULL) {
-                       gs_shell_search_install_unavailable_app (shell_search, app);
+                       gs_page_install_app (GS_PAGE (shell_search), app);
                        return;
                }
                gs_app_show_url (app, AS_URL_KIND_MISSING);
@@ -512,6 +353,18 @@ gs_shell_search_cancel_cb (GCancellable *cancellable,
                g_cancellable_cancel (priv->search_cancellable);
 }
 
+static void
+gs_shell_search_app_installed (GsPage *page, GsApp *app)
+{
+       gs_shell_search_reload (GS_SHELL_SEARCH (page));
+}
+
+static void
+gs_shell_search_app_removed (GsPage *page, GsApp *app)
+{
+       gs_shell_search_reload (GS_SHELL_SEARCH (page));
+}
+
 /**
  * gs_shell_search_setup:
  */
@@ -545,6 +398,12 @@ gs_shell_search_setup (GsShellSearch *shell_search,
        gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->list_box_search),
                                    gs_shell_search_sort_func,
                                    shell_search, NULL);
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (shell_search),
+                      shell,
+                      plugin_loader,
+                      cancellable);
 }
 
 /**
@@ -554,9 +413,12 @@ static void
 gs_shell_search_class_init (GsShellSearchClass *klass)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GsPageClass *page_class = GS_PAGE_CLASS (klass);
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
        object_class->finalize = gs_shell_search_finalize;
+       page_class->app_installed = gs_shell_search_app_installed;
+       page_class->app_removed = gs_shell_search_app_removed;
 
        gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-shell-search.ui");
 
diff --git a/src/gs-shell-search.h b/src/gs-shell-search.h
index 49678a3..494f314 100644
--- a/src/gs-shell-search.h
+++ b/src/gs-shell-search.h
@@ -25,6 +25,7 @@
 #include <glib-object.h>
 #include <gtk/gtk.h>
 
+#include "gs-page.h"
 #include "gs-shell.h"
 #include "gs-plugin-loader.h"
 
@@ -41,13 +42,13 @@ typedef struct GsShellSearchPrivate GsShellSearchPrivate;
 
 typedef struct
 {
-        GtkBin                          parent;
+        GsPage                          parent;
         GsShellSearchPrivate           *priv;
 } GsShellSearch;
 
 typedef struct
 {
-       GtkBinClass                      parent_class;
+       GsPageClass                      parent_class;
 } GsShellSearchClass;
 
 GType           gs_shell_search_get_type       (void);
diff --git a/src/gs-shell-search.ui b/src/gs-shell-search.ui
index 59a6537..5b99ef7 100644
--- a/src/gs-shell-search.ui
+++ b/src/gs-shell-search.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.10"/>
-  <template class="GsShellSearch" parent="GtkBin">
+  <template class="GsShellSearch" parent="GsPage">
     <child internal-child="accessible">
       <object class="AtkObject" id="search-accessible">
         <property name="accessible-name" translatable="yes">Search page</property>
diff --git a/src/gs-shell-updates.c b/src/gs-shell-updates.c
index f9f3f11..51407b1 100644
--- a/src/gs-shell-updates.c
+++ b/src/gs-shell-updates.c
@@ -86,7 +86,7 @@ enum {
        COLUMN_UPDATE_LAST
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GsShellUpdates, gs_shell_updates, GTK_TYPE_BIN)
+G_DEFINE_TYPE_WITH_PRIVATE (GsShellUpdates, gs_shell_updates, GS_TYPE_PAGE)
 
 /**
  * gs_shell_updates_invalidate:
@@ -934,6 +934,12 @@ gs_shell_updates_setup (GsShellUpdates *shell_updates,
        pk_control_get_properties_async (priv->control, cancellable,
                                         gs_shell_updates_get_properties_cb,
                                         shell_updates);
+
+       /* chain up */
+       gs_page_setup (GS_PAGE (shell_updates),
+                      shell,
+                      plugin_loader,
+                      cancellable);
 }
 
 /**
diff --git a/src/gs-shell-updates.h b/src/gs-shell-updates.h
index 6c18247..ab6ad55 100644
--- a/src/gs-shell-updates.h
+++ b/src/gs-shell-updates.h
@@ -25,6 +25,7 @@
 #include <glib-object.h>
 #include <gtk/gtk.h>
 
+#include "gs-page.h"
 #include "gs-shell.h"
 #include "gs-plugin-loader.h"
 
@@ -41,13 +42,13 @@ typedef struct GsShellUpdatesPrivate GsShellUpdatesPrivate;
 
 typedef struct
 {
-        GtkBin                  parent;
+        GsPage                  parent;
         GsShellUpdatesPrivate  *priv;
 } GsShellUpdates;
 
 typedef struct
 {
-       GtkBinClass              parent_class;
+       GsPageClass              parent_class;
 } GsShellUpdatesClass;
 
 GType           gs_shell_updates_get_type      (void);
diff --git a/src/gs-shell-updates.ui b/src/gs-shell-updates.ui
index 3fefe8d..d514d24 100644
--- a/src/gs-shell-updates.ui
+++ b/src/gs-shell-updates.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.10"/>
-  <template class="GsShellUpdates" parent="GtkBin">
+  <template class="GsShellUpdates" parent="GsPage">
     <child internal-child="accessible">
       <object class="AtkObject" id="updates-accessible">
         <property name="accessible-name" translatable="yes">Updates page</property>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]