[gnome-software] Add a search shell and use the plugins to return results



commit 209e261d085a77f94f7b3b15cbbb9a1ced654c0f
Author: Richard Hughes <richard hughsie com>
Date:   Thu Aug 29 09:52:02 2013 +0100

    Add a search shell and use the plugins to return results

 src/Makefile.am         |    2 +
 src/gnome-software.ui   |   35 +++
 src/gs-shell-category.c |    3 +
 src/gs-shell-details.c  |    3 +
 src/gs-shell-overview.c |   27 +++-
 src/gs-shell-search.c   |  549 +++++++++++++++++++++++++++++++++++++++++++++++
 src/gs-shell-search.h   |   62 ++++++
 src/gs-shell-updates.c  |    3 +
 src/gs-shell.c          |   16 ++
 src/gs-shell.h          |    1 +
 10 files changed, 699 insertions(+), 2 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 0e45541..403e0e5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -50,6 +50,8 @@ gnome_software_SOURCES =                              \
        gs-shell-overview.h                             \
        gs-shell-updates.c                              \
        gs-shell-updates.h                              \
+       gs-shell-search.c                               \
+       gs-shell-search.h                               \
        gs-plugin-loader.c                              \
        gs-plugin-loader.h                              \
        gs-plugin-loader-sync.c                         \
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index 09a0ad0..7fd23c4 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -486,6 +486,41 @@
               </object>
             </child>
             <child>
+              <object class="GtkOverlay" id="overlay_search">
+                <property name="visible">True</property>
+                <child type="overlay">
+                  <object class="GtkSpinner" id="spinner_search">
+                    <property name="width_request">128</property>
+                    <property name="height_request">128</property>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow_search">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="shadow_type">none</property>
+                    <style>
+                      <class name="main-scrolled-software"/>
+                    </style>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel" id="label_tab_search">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label">Search</property>
+              </object>
+            </child>
+            <child>
               <object class="GtkOverlay" id="overlay_updates">
                 <property name="visible">True</property>
                 <child type="overlay">
diff --git a/src/gs-shell-category.c b/src/gs-shell-category.c
index 15146cb..b51d19d 100644
--- a/src/gs-shell-category.c
+++ b/src/gs-shell-category.c
@@ -50,6 +50,9 @@ gs_shell_category_refresh (GsShellCategory *shell)
         widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "application_details_header"));
         gtk_widget_show (widget);
         gtk_label_set_label (GTK_LABEL (widget), priv->category);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "search_bar"));
+       gtk_widget_hide (widget);
 }
 
 static void
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index e69e3e7..122d109 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -70,6 +70,9 @@ gs_shell_details_refresh (GsShellDetails *shell_details)
         widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_back"));
         gtk_widget_show (widget);
 
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "search_bar"));
+       gtk_widget_hide (widget);
+
        kind = gs_app_get_kind (priv->app);
        state = gs_app_get_state (priv->app);
 
diff --git a/src/gs-shell-overview.c b/src/gs-shell-overview.c
index d05a106..1b0acad 100644
--- a/src/gs-shell-overview.c
+++ b/src/gs-shell-overview.c
@@ -196,6 +196,7 @@ gs_shell_overview_get_featured_cb (GObject *source_object,
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
        GtkImage *image;
        GtkWidget *button;
+       GtkWidget *widget;
 
        list = gs_plugin_loader_get_featured_finish (plugin_loader,
                                                     res,
@@ -216,11 +217,9 @@ gs_shell_overview_get_featured_cb (GObject *source_object,
        g_signal_connect (button, "clicked",
                          G_CALLBACK (app_tile_clicked), shell_overview);
 
-#ifdef SEARCH
        /* focus back to the text extry */
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
        gtk_widget_grab_focus (widget);
-#endif
 out:
        g_list_free (list);
        return;
@@ -262,11 +261,20 @@ gs_shell_overview_refresh (GsShellOverview *shell_overview)
 
         widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
         gtk_widget_show (widget);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "search_bar"));
+       gtk_widget_show (widget);
 
        /* no need to refresh */
        if (priv->cache_valid)
                return;
 
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "search_bar"));
+       gtk_widget_show (widget);
+
+       /* clear search items */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
+       gtk_entry_set_text (GTK_ENTRY (widget), "");
+
        grid = GTK_WIDGET (gtk_builder_get_object (priv->builder, "box_popular"));
        container_remove_all (GTK_CONTAINER (grid));
 
@@ -291,6 +299,16 @@ gs_shell_overview_refresh (GsShellOverview *shell_overview)
 }
 
 /**
+ * gs_shell_overview_search_activated_cb:
+ */
+static void
+gs_shell_overview_search_activated_cb (GtkEntry *entry, GsShellOverview *shell_overview)
+{
+       GsShellOverviewPrivate *priv = shell_overview->priv;
+       gs_shell_set_mode (priv->shell, GS_SHELL_MODE_SEARCH);
+}
+
+/**
  * gs_shell_overview_setup:
  */
 void
@@ -301,6 +319,7 @@ gs_shell_overview_setup (GsShellOverview *shell_overview,
                         GCancellable *cancellable)
 {
        GsShellOverviewPrivate *priv = shell_overview->priv;
+       GtkWidget *widget;
 
        g_return_if_fail (GS_IS_SHELL_OVERVIEW (shell_overview));
 
@@ -308,6 +327,10 @@ gs_shell_overview_setup (GsShellOverview *shell_overview,
        priv->builder = g_object_ref (builder);
        priv->cancellable = g_object_ref (cancellable);
 
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
+       g_signal_connect (GTK_EDITABLE (widget), "activate",
+                         G_CALLBACK (gs_shell_overview_search_activated_cb), shell_overview);
+
         /* avoid a ref cycle */
         priv->shell = shell;
 }
diff --git a/src/gs-shell-search.c b/src/gs-shell-search.c
new file mode 100644
index 0000000..dc9f6e2
--- /dev/null
+++ b/src/gs-shell-search.c
@@ -0,0 +1,549 @@
+/* -*- 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-shell-search.h"
+#include "gs-app.h"
+#include "gs-utils.h"
+#include "gs-app-widget.h"
+
+static void    gs_shell_search_finalize        (GObject        *object);
+
+#define GS_SHELL_SEARCH_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GS_TYPE_SHELL_SEARCH, 
GsShellSearchPrivate))
+
+struct GsShellSearchPrivate
+{
+       GsPluginLoader          *plugin_loader;
+       GtkBuilder              *builder;
+       GCancellable            *cancellable;
+       GtkListBox              *list_box_search;
+       GtkSizeGroup            *sizegroup_image;
+       GtkSizeGroup            *sizegroup_name;
+       gboolean                 waiting;
+};
+
+G_DEFINE_TYPE (GsShellSearch, gs_shell_search, G_TYPE_OBJECT)
+
+/**
+ * _gtk_container_remove_all_cb:
+ **/
+static void
+_gtk_container_remove_all_cb (GtkWidget *widget, gpointer user_data)
+{
+       GtkContainer *container = GTK_CONTAINER (user_data);
+       gtk_container_remove (container, widget);
+}
+
+/**
+ * _gtk_container_remove_all:
+ **/
+static void
+_gtk_container_remove_all (GtkContainer *container)
+{
+       gtk_container_foreach (container,
+                              _gtk_container_remove_all_cb,
+                              container);
+}
+
+static void
+gs_shell_search_app_widget_activated_cb (GtkListBox *list_box,
+                                         GtkListBoxRow *row,
+                                        GsShellSearch *shell_search)
+{
+       const gchar *tmp;
+       GsApp *app;
+       GtkWidget *details, *button, *grid;
+       GtkWidget *image, *label;
+       PangoAttrList *attr_list;
+        GsAppWidget *app_widget;
+
+        app_widget = GS_APP_WIDGET (gtk_bin_get_child (GTK_BIN (row)));
+       app = gs_app_widget_get_app (app_widget);
+
+       details = gtk_dialog_new_with_buttons (_("Details"),
+                                              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (list_box))),
+                                              GTK_DIALOG_MODAL,
+                                              _("_Done"), GTK_RESPONSE_CLOSE,
+                                              NULL);
+       gtk_container_set_border_width (GTK_CONTAINER (details), 20);
+       button = gtk_dialog_get_widget_for_response (GTK_DIALOG (details), GTK_RESPONSE_CLOSE);
+       gtk_style_context_add_class (gtk_widget_get_style_context (button), "suggested-action");
+       g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), details);
+
+       grid = gtk_grid_new ();
+       gtk_widget_show (grid);
+       gtk_widget_set_halign (grid, GTK_ALIGN_FILL);
+       gtk_grid_set_column_spacing (GTK_GRID (grid), 20);
+       gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (details))), grid);
+
+       image = gtk_image_new ();
+       if (gs_app_get_pixbuf (app)) {
+               gtk_image_set_from_pixbuf (GTK_IMAGE (image), gs_app_get_pixbuf (app));  gtk_widget_show 
(image);
+       }
+       gtk_grid_attach (GTK_GRID (grid), image, 0, 0, 1, 3);
+
+       label = gtk_label_new (gs_app_get_name (app));
+       gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+       gtk_widget_set_hexpand (label, TRUE);
+       gtk_widget_set_margin_bottom (label, 10);
+       attr_list = pango_attr_list_new ();
+       pango_attr_list_insert (attr_list, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+       pango_attr_list_insert (attr_list, pango_attr_scale_new (1));
+       gtk_label_set_attributes (GTK_LABEL (label), attr_list);
+       pango_attr_list_unref (attr_list);
+       gtk_widget_show (label);
+       gtk_grid_attach (GTK_GRID (grid), label, 1, 0, 1, 1);
+
+       label = gtk_label_new (NULL);
+       gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+       gtk_widget_set_hexpand (label, TRUE);
+       gtk_widget_set_margin_bottom (label, 20);
+       if (gs_app_get_summary (app)) {
+               gtk_label_set_label (GTK_LABEL (label), gs_app_get_summary (app));
+               gtk_widget_show (label);
+       }
+       gtk_grid_attach (GTK_GRID (grid), label, 1, 1, 1, 1);
+       tmp = gs_app_get_description (app);
+       label = gtk_label_new (tmp);
+       gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+       gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+       gtk_widget_show (label);
+       gtk_grid_attach (GTK_GRID (grid), label, 0, 3, 2, 1);
+
+       if (gs_app_get_url (app)) {
+               button = gtk_link_button_new_with_label (gs_app_get_url (app), _("Visit website"));
+               gtk_widget_set_halign (button, GTK_ALIGN_START);
+               gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+               gtk_widget_show (button);
+               gtk_grid_attach (GTK_GRID (grid), button, 0, 4, 2, 1);
+       }
+
+       gtk_window_present (GTK_WINDOW (details));
+}
+
+/**
+ * gs_shell_search_finished_func:
+ **/
+static void
+gs_shell_search_finished_func (GsPluginLoader *plugin_loader, GsApp *app, gpointer user_data)
+{
+}
+
+/**
+ * gs_shell_search_app_remove:
+ **/
+static void
+gs_shell_search_app_remove (GsShellSearch *shell_search, GsApp *app)
+{
+       GsShellSearchPrivate *priv = shell_search->priv;
+       GString *markup;
+       GtkResponseType response;
+       GtkWidget *dialog;
+       GtkWindow *window;
+
+       window = GTK_WINDOW (gtk_builder_get_object (priv->builder, "window_software"));
+       markup = g_string_new ("");
+       g_string_append_printf (markup,
+                               _("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 (window,
+                                        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),
+                                                   _("%s will be removed, and you will have to install it to 
use it again."),
+                                                   gs_app_get_name (app));
+       gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
+       response = gtk_dialog_run (GTK_DIALOG (dialog));
+       if (response == GTK_RESPONSE_OK) {
+               g_debug ("remove %s", gs_app_get_id (app));
+               gs_plugin_loader_app_remove (priv->plugin_loader,
+                                            app,
+                                            priv->cancellable,
+                                            gs_shell_search_finished_func,
+                                            shell_search);
+       }
+       g_string_free (markup, TRUE);
+       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;
+       gs_plugin_loader_app_install (priv->plugin_loader,
+                                     app,
+                                     priv->cancellable,
+                                     gs_shell_search_finished_func,
+                                     shell_search);
+}
+
+/**
+ * gs_shell_search_app_widget_clicked_cb:
+ **/
+static void
+gs_shell_search_app_widget_clicked_cb (GsAppWidget *app_widget,
+                                      GsShellSearch *shell_search)
+{
+       GsApp *app;
+       app = gs_app_widget_get_app (app_widget);
+       if (gs_app_get_state (app) == GS_APP_STATE_AVAILABLE)
+               gs_shell_search_app_install (shell_search, app);
+       else if (gs_app_get_state (app) == GS_APP_STATE_INSTALLED)
+               gs_shell_search_app_remove (shell_search, app);
+}
+
+/**
+ * gs_shell_search_get_search_cb:
+ **/
+static void
+gs_shell_search_get_search_cb (GObject *source_object,
+                                    GAsyncResult *res,
+                                    gpointer user_data)
+{
+       GError *error = NULL;
+       GList *l;
+       GList *list;
+       GsApp *app;
+       GsShellSearch *shell_search = GS_SHELL_SEARCH (user_data);
+       GsShellSearchPrivate *priv = shell_search->priv;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+       GtkWidget *widget;
+
+        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "spinner_search"));
+        gs_stop_spinner (GTK_SPINNER (widget));
+
+        priv->waiting = FALSE;
+
+       list = gs_plugin_loader_search_finish (plugin_loader,
+                                                     res,
+                                                     &error);
+       if (list == NULL) {
+               g_warning ("failed to get search apps: %s", error->message);
+               g_error_free (error);
+               goto out;
+       }
+       for (l = list; l != NULL; l = l->next) {
+               app = GS_APP (l->data);
+               g_debug ("adding search %s", gs_app_get_id (app));
+               widget = gs_app_widget_new ();
+               g_signal_connect (widget, "button-clicked",
+                                 G_CALLBACK (gs_shell_search_app_widget_clicked_cb),
+                                 shell_search);
+               gs_app_widget_set_app (GS_APP_WIDGET (widget), app);
+               gtk_container_add (GTK_CONTAINER (priv->list_box_search), widget);
+               gs_app_widget_set_size_groups (GS_APP_WIDGET (widget),
+                                              priv->sizegroup_image,
+                                              priv->sizegroup_name);
+               gtk_widget_show (widget);
+       }
+
+       /* focus back to the text extry */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
+       gtk_widget_grab_focus (widget);
+out: ;
+}
+
+/**
+ * gs_shell_search_refresh:
+ **/
+void
+gs_shell_search_refresh (GsShellSearch *shell_search, const gchar *value)
+{
+       GsShellSearchPrivate *priv = shell_search->priv;
+        GtkWidget *widget;
+        GtkSpinner *spinner;
+
+        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
+        gtk_widget_show (widget);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "search_bar"));
+       gtk_widget_show (widget);
+
+        if (priv->waiting)
+                return;
+
+       /* remove old entries */
+       _gtk_container_remove_all (GTK_CONTAINER (priv->list_box_search));
+
+       /* search for apps */
+       gs_plugin_loader_search_async (priv->plugin_loader,
+                                      value,
+                                      priv->cancellable,
+                                      gs_shell_search_get_search_cb,
+                                      shell_search);
+
+        spinner = GTK_SPINNER (gtk_builder_get_object (priv->builder, "spinner_search"));
+        gs_start_spinner (spinner);
+       priv->waiting = TRUE;
+}
+
+/**
+ * gs_shell_search_filter_text_changed_cb:
+ **/
+static void
+gs_shell_search_filter_text_changed_cb (GtkEntry *entry,
+                                       GsShellSearch *shell_search)
+{
+       /* FIXME: do something? */
+}
+
+/**
+ * gs_shell_search_sort_func:
+ **/
+static gint
+gs_shell_search_sort_func (GtkListBoxRow *a,
+                             GtkListBoxRow *b,
+                             gpointer user_data)
+{
+       GsAppWidget *aw1 = GS_APP_WIDGET (gtk_bin_get_child (GTK_BIN (a)));
+       GsAppWidget *aw2 = GS_APP_WIDGET (gtk_bin_get_child (GTK_BIN (b)));
+       GsApp *a1 = gs_app_widget_get_app (aw1);
+       GsApp *a2 = gs_app_widget_get_app (aw2);
+        guint64 date1 = gs_app_get_install_date (a1);
+        guint64 date2 = gs_app_get_install_date (a2);
+
+        if (date1 < date2)
+                return 1;
+        else if (date2 < date1)
+                return -1;
+
+       return g_strcmp0 (gs_app_get_name (a1),
+                         gs_app_get_name (a2));
+}
+
+/**
+ * gs_shell_search_utf8_filter_helper:
+ **/
+static gboolean
+gs_shell_search_utf8_filter_helper (const gchar *haystack,
+                                      const gchar *needle_utf8)
+{
+       gboolean ret;
+       gchar *haystack_utf8;
+       haystack_utf8 = g_utf8_casefold (haystack, -1);
+       ret = strstr (haystack_utf8, needle_utf8) != NULL;
+       g_free (haystack_utf8);
+       return ret;
+}
+
+/**
+ * gs_shell_search_filter_func:
+ **/
+static gboolean
+gs_shell_search_filter_func (GtkListBoxRow *row, void *user_data)
+{
+       const gchar *tmp;
+       gboolean ret = TRUE;
+       gchar *needle_utf8 = NULL;
+       GsApp *app;
+       GsAppWidget *app_widget = GS_APP_WIDGET (gtk_bin_get_child (GTK_BIN (row)));
+       GsShellSearch *shell_search = GS_SHELL_SEARCH (user_data);
+       GsShellSearchPrivate *priv = shell_search->priv;
+       GtkWidget *widget;
+
+       app = gs_app_widget_get_app (app_widget);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
+       tmp = gtk_entry_get_text (GTK_ENTRY (widget));
+       if (tmp[0] == '\0')
+               goto out;
+
+       needle_utf8 = g_utf8_casefold (tmp, -1);
+       ret = gs_shell_search_utf8_filter_helper (gs_app_get_name (app),
+                                         needle_utf8);
+       if (ret)
+               goto out;
+       ret = gs_shell_search_utf8_filter_helper (gs_app_get_summary (app),
+                                         needle_utf8);
+       if (ret)
+               goto out;
+       ret = gs_shell_search_utf8_filter_helper (gs_app_get_version (app),
+                                         needle_utf8);
+       if (ret)
+               goto out;
+       ret = gs_shell_search_utf8_filter_helper (gs_app_get_id (app),
+                                         needle_utf8);
+       if (ret)
+               goto out;
+out:
+       g_free (needle_utf8);
+       return ret;
+}
+
+/**
+ * gs_shell_search_list_header_func
+ **/
+static void
+gs_shell_search_list_header_func (GtkListBoxRow *row,
+                                    GtkListBoxRow *before,
+                                    gpointer user_data)
+{
+       GtkWidget *header;
+
+       /* first entry */
+       header = gtk_list_box_row_get_header (row);
+       if (before == NULL) {
+               gtk_list_box_row_set_header (row, NULL);
+               return;
+       }
+
+       /* already set */
+       if (header != NULL)
+               return;
+
+       /* set new */
+       header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+       gtk_list_box_row_set_header (row, header);
+}
+
+/**
+ * gs_shell_search_pending_apps_changed_cb:
+ */
+static void
+gs_shell_search_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
+                                           GsShellSearch *shell_search)
+{
+       gchar *label;
+       GPtrArray *pending;
+       GtkWidget *widget;
+
+       widget = GTK_WIDGET (gtk_builder_get_object (shell_search->priv->builder,
+                                                    "label_button_search"));
+       pending = gs_plugin_loader_get_pending (plugin_loader);
+       if (pending->len == 0)
+               label = g_strdup (_("Search"));
+       else
+               label = g_strdup_printf (_("Search (%d)"), pending->len);
+       gtk_label_set_label (GTK_LABEL (widget), label);
+       g_free (label);
+       g_ptr_array_unref (pending);
+}
+
+/**
+ * gs_shell_search_setup:
+ */
+void
+gs_shell_search_setup (GsShellSearch *shell_search,
+                         GsPluginLoader *plugin_loader,
+                         GtkBuilder *builder,
+                         GCancellable *cancellable)
+{
+       GsShellSearchPrivate *priv = shell_search->priv;
+       GtkWidget *widget;
+
+       g_return_if_fail (GS_IS_SHELL_SEARCH (shell_search));
+
+       priv->plugin_loader = g_object_ref (plugin_loader);
+       g_signal_connect (priv->plugin_loader, "pending-apps-changed",
+                         G_CALLBACK (gs_shell_search_pending_apps_changed_cb),
+                         shell_search);
+
+       priv->builder = g_object_ref (builder);
+       priv->cancellable = g_object_ref (cancellable);
+
+       /* refilter on search box changing */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
+       g_signal_connect (GTK_EDITABLE (widget), "changed",
+                         G_CALLBACK (gs_shell_search_filter_text_changed_cb), shell_search);
+
+       /* setup search */
+       priv->list_box_search = GTK_LIST_BOX (gtk_list_box_new ());
+       g_signal_connect (priv->list_box_search, "row-activated",
+                         G_CALLBACK (gs_shell_search_app_widget_activated_cb), shell_search);
+       gtk_list_box_set_header_func (priv->list_box_search,
+                                     gs_shell_search_list_header_func,
+                                     shell_search,
+                                     NULL);
+       gtk_list_box_set_filter_func (priv->list_box_search,
+                                     gs_shell_search_filter_func,
+                                     shell_search,
+                                     NULL);
+       gtk_list_box_set_sort_func (priv->list_box_search,
+                                   gs_shell_search_sort_func,
+                                   shell_search,
+                                   NULL);
+       gtk_list_box_set_selection_mode (priv->list_box_search,
+                                        GTK_SELECTION_NONE);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "scrolledwindow_search"));
+       gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (priv->list_box_search));
+       gtk_widget_show (GTK_WIDGET (priv->list_box_search));
+}
+
+/**
+ * gs_shell_search_class_init:
+ **/
+static void
+gs_shell_search_class_init (GsShellSearchClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = gs_shell_search_finalize;
+
+       g_type_class_add_private (klass, sizeof (GsShellSearchPrivate));
+}
+
+/**
+ * gs_shell_search_init:
+ **/
+static void
+gs_shell_search_init (GsShellSearch *shell_search)
+{
+       shell_search->priv = GS_SHELL_SEARCH_GET_PRIVATE (shell_search);
+       shell_search->priv->sizegroup_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+       shell_search->priv->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+}
+
+/**
+ * gs_shell_search_finalize:
+ **/
+static void
+gs_shell_search_finalize (GObject *object)
+{
+       GsShellSearch *shell_search = GS_SHELL_SEARCH (object);
+       GsShellSearchPrivate *priv = shell_search->priv;
+
+       g_object_unref (priv->builder);
+       g_object_unref (priv->plugin_loader);
+       g_object_unref (priv->cancellable);
+
+       G_OBJECT_CLASS (gs_shell_search_parent_class)->finalize (object);
+}
+
+/**
+ * gs_shell_search_new:
+ **/
+GsShellSearch *
+gs_shell_search_new (void)
+{
+       GsShellSearch *shell_search;
+       shell_search = g_object_new (GS_TYPE_SHELL_SEARCH, NULL);
+       return GS_SHELL_SEARCH (shell_search);
+}
diff --git a/src/gs-shell-search.h b/src/gs-shell-search.h
new file mode 100644
index 0000000..fcd8b17
--- /dev/null
+++ b/src/gs-shell-search.h
@@ -0,0 +1,62 @@
+/* -*- 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_SHELL_SEARCH_H
+#define __GS_SHELL_SEARCH_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gs-plugin-loader.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SHELL_SEARCH           (gs_shell_search_get_type ())
+#define GS_SHELL_SEARCH(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_SHELL_SEARCH, 
GsShellSearch))
+#define GS_SHELL_SEARCH_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), GS_TYPE_SHELL_SEARCH, 
GsShellSearchClass))
+#define GS_IS_SHELL_SEARCH(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_SHELL_SEARCH))
+#define GS_IS_SHELL_SEARCH_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GS_TYPE_SHELL_SEARCH))
+#define GS_SHELL_SEARCH_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), GS_TYPE_SHELL_SEARCH, 
GsShellSearchClass))
+
+typedef struct GsShellSearchPrivate GsShellSearchPrivate;
+
+typedef struct
+{
+        GObject                         parent;
+        GsShellSearchPrivate           *priv;
+} GsShellSearch;
+
+typedef struct
+{
+       GObjectClass                     parent_class;
+} GsShellSearchClass;
+
+GType           gs_shell_search_get_type       (void);
+
+GsShellSearch  *gs_shell_search_new            (void);
+void            gs_shell_search_refresh        (GsShellSearch          *shell_search,
+                                                const gchar            *text);
+void            gs_shell_search_setup          (GsShellSearch          *shell_search,
+                                                GsPluginLoader         *plugin_loader,
+                                                GtkBuilder             *builder,
+                                                GCancellable           *cancellable);
+
+#endif /* __GS_SHELL_SEARCH_H */
diff --git a/src/gs-shell-updates.c b/src/gs-shell-updates.c
index 9c46d69..b0608e6 100644
--- a/src/gs-shell-updates.c
+++ b/src/gs-shell-updates.c
@@ -170,6 +170,9 @@ gs_shell_updates_refresh (GsShellUpdates *shell_updates)
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "scrolledwindow_updates"));
        gtk_widget_show (widget);
 
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "search_bar"));
+       gtk_widget_hide (widget);
+
         if (priv->waiting)
                 return;
 
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 3465c5b..331f190 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -27,6 +27,7 @@
 #include "gs-shell.h"
 #include "gs-shell-details.h"
 #include "gs-shell-installed.h"
+#include "gs-shell-search.h"
 #include "gs-shell-overview.h"
 #include "gs-shell-updates.h"
 #include "gs-shell-category.h"
@@ -43,6 +44,7 @@ struct GsShellPrivate
        GsShellMode              mode;
        GsShellOverview         *shell_overview;
        GsShellInstalled        *shell_installed;
+       GsShellSearch           *shell_search;
        GsShellUpdates          *shell_updates;
        GsShellDetails          *shell_details;
        GsShellCategory         *shell_category;
@@ -73,6 +75,7 @@ gs_shell_set_overview_mode (GsShell *shell, GsShellMode mode, GsApp *app, const
 {
        GsShellPrivate *priv = shell->priv;
         GtkWidget *widget;
+        const gchar *text;
 
        if (priv->ignore_primary_buttons)
                return;
@@ -92,6 +95,8 @@ gs_shell_set_overview_mode (GsShell *shell, GsShellMode mode, GsApp *app, const
        gtk_widget_hide (widget);
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "buttonbox_main"));
        gtk_widget_hide (widget);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "search_bar"));
+       gtk_widget_hide (widget);
 
         /* update main buttons according to mode */
        priv->ignore_primary_buttons = TRUE;
@@ -118,6 +123,11 @@ gs_shell_set_overview_mode (GsShell *shell, GsShellMode mode, GsApp *app, const
        case GS_SHELL_MODE_INSTALLED:
                gs_shell_installed_refresh (priv->shell_installed);
                break;
+       case GS_SHELL_MODE_SEARCH:
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "entry_search"));
+               text = gtk_entry_get_text (GTK_ENTRY (widget));
+               gs_shell_search_refresh (priv->shell_search, text);
+               break;
        case GS_SHELL_MODE_UPDATES:
                gs_shell_updates_refresh (priv->shell_updates);
                break;
@@ -222,6 +232,10 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
                                  priv->plugin_loader,
                                  priv->builder,
                                  priv->cancellable);
+       gs_shell_search_setup (priv->shell_search,
+                              priv->plugin_loader,
+                              priv->builder,
+                              priv->cancellable);
        gs_shell_details_setup (priv->shell_details,
                                 shell,
                                priv->plugin_loader,
@@ -290,6 +304,7 @@ gs_shell_init (GsShell *shell)
        shell->priv->shell_installed = gs_shell_installed_new ();
        shell->priv->shell_details = gs_shell_details_new ();
        shell->priv->shell_category = gs_shell_category_new ();
+       shell->priv->shell_search = gs_shell_search_new ();
        shell->priv->ignore_primary_buttons = FALSE;
 }
 
@@ -310,6 +325,7 @@ gs_shell_finalize (GObject *object)
        g_object_unref (priv->shell_updates);
        g_object_unref (priv->shell_details);
        g_object_unref (priv->shell_category);
+       g_object_unref (priv->shell_search);
 
        G_OBJECT_CLASS (gs_shell_parent_class)->finalize (object);
 }
diff --git a/src/gs-shell.h b/src/gs-shell.h
index f31c6b7..4a22915 100644
--- a/src/gs-shell.h
+++ b/src/gs-shell.h
@@ -52,6 +52,7 @@ typedef struct
 typedef enum {
        GS_SHELL_MODE_OVERVIEW,
        GS_SHELL_MODE_INSTALLED,
+       GS_SHELL_MODE_SEARCH,
        GS_SHELL_MODE_UPDATES,
        GS_SHELL_MODE_DETAILS,
        GS_SHELL_MODE_CATEGORY


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