[gnome-control-center/extensible-shell] [shell] improve search results by matching on and displaying descriptions



commit 82da73f3c17b3e11ab9a3b733bdafe49abce0b09
Author: Thomas Wood <thomas wood intel com>
Date:   Fri Jan 29 17:41:03 2010 +0000

    [shell] improve search results by matching on and displaying descriptions
    
    Show the reason for match in the search results by highlighting the
    search term in bold within the searched string.

 shell/Makefile.am             |    2 +
 shell/control-center.c        |   56 ++++++--
 shell/shell-search-renderer.c |  291 +++++++++++++++++++++++++++++++++++++++++
 shell/shell-search-renderer.h |   74 +++++++++++
 shell/shell.ui                |    2 +
 5 files changed, 410 insertions(+), 15 deletions(-)
---
diff --git a/shell/Makefile.am b/shell/Makefile.am
index a11fc8d..7cccb27 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -11,6 +11,8 @@ bin_PROGRAMS = gnome-control-center
 
 gnome_control_center_SOURCES =			\
 	control-center.c			\
+	shell-search-renderer.c			\
+	shell-search-renderer.h			\
 	$(NULL)
 
 gnome_control_center_LDADD =			\
diff --git a/shell/control-center.c b/shell/control-center.c
index dccb8ba..f34a8c0 100644
--- a/shell/control-center.c
+++ b/shell/control-center.c
@@ -29,6 +29,7 @@
 #include <gnome-menus/gmenu-tree.h>
 
 #include "cc-panel.h"
+#include "shell-search-renderer.h"
 
 #define W(b,x) GTK_WIDGET (gtk_builder_get_object (b, x))
 
@@ -45,6 +46,7 @@ typedef struct
 
   GtkListStore *store;
   GtkTreeModel *search_filter;
+  GtkCellRenderer *search_renderer;
   gchar *filter_string;
 
 } ShellData;
@@ -63,6 +65,7 @@ enum
   COL_ID,
   COL_PIXBUF,
   COL_CATEGORY,
+  COL_DESCRIPTION,
 
   N_COLS
 };
@@ -221,24 +224,35 @@ model_filter_func (GtkTreeModel *model,
                    GtkTreeIter  *iter,
                    ShellData    *data)
 {
-  gchar *name;
+  gchar *name, *description;
   gchar *needle, *haystack;
+  gchar *full_string;
+  gboolean result;
 
-  gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
+  gtk_tree_model_get (model, iter, COL_NAME, &name,
+                      COL_DESCRIPTION, &description, -1);
 
-  if (!data->filter_string)
-    return FALSE;
+  if (!data->filter_string || !name || !description)
+    {
+      g_free (name);
+      g_free (description);
+      return FALSE;
+    }
 
-  if (!name)
-    return FALSE;
+  full_string = g_strdup_printf ("%s - %s", name, description);
 
   needle = g_utf8_casefold (data->filter_string, -1);
-  haystack = g_utf8_casefold (name, -1);
+  haystack = g_utf8_casefold (full_string, -1);
 
-  if (strstr (haystack, needle))
-    return TRUE;
-  else
-    return FALSE;
+  result = (strstr (haystack, needle) != NULL);
+
+  g_free (name);
+  g_free (description);
+  g_free (haystack);
+  g_free (needle);
+  g_free (full_string);
+
+  return result;
 }
 
 static gboolean
@@ -274,8 +288,9 @@ fill_model (ShellData *data)
 
   list = gmenu_tree_directory_get_contents (d);
 
-  data->store = gtk_list_store_new (N_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
-                                    GDK_TYPE_PIXBUF, G_TYPE_STRING);
+  data->store = gtk_list_store_new (N_COLS, G_TYPE_STRING, G_TYPE_STRING,
+                                    G_TYPE_STRING, GDK_TYPE_PIXBUF,
+                                    G_TYPE_STRING, G_TYPE_STRING);
 
   data->search_filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (data->store), NULL);
 
@@ -287,8 +302,15 @@ fill_model (ShellData *data)
   w = (GtkWidget *) gtk_builder_get_object (data->builder, "search-view");
   gtk_icon_view_set_model (GTK_ICON_VIEW (w), GTK_TREE_MODEL (data->search_filter));
   gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (w), COL_PIXBUF);
-  gtk_icon_view_set_text_column (GTK_ICON_VIEW (w), COL_NAME);
-  gtk_icon_view_set_item_width (GTK_ICON_VIEW (w), 120);
+
+
+  data->search_renderer = (GtkCellRenderer*) shell_search_renderer_new ();
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w), data->search_renderer, TRUE);
+  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (w), data->search_renderer,
+                                 "title", COL_NAME);
+  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (w), data->search_renderer,
+                                 "description", COL_DESCRIPTION);
+
   g_signal_connect (w, "item-activated",
                     G_CALLBACK (item_activated_cb), data);
   g_signal_connect (w, "button-release-event",
@@ -355,6 +377,7 @@ fill_model (ShellData *data)
                   const gchar *name = gmenu_tree_entry_get_name (f->data);
                   const gchar *id = gmenu_tree_entry_get_desktop_file_id (f->data);
                   const gchar *exec = gmenu_tree_entry_get_exec (f->data);
+                  const gchar *comment = gmenu_tree_entry_get_comment (f->data);
                   GdkPixbuf *pixbuf = NULL;
                   char *icon2 = NULL;
 
@@ -388,6 +411,7 @@ fill_model (ShellData *data)
                                                      COL_ID, id,
                                                      COL_PIXBUF, pixbuf,
                                                      COL_CATEGORY, dir_name,
+                                                     COL_DESCRIPTION, comment,
                                                      -1);
                 }
             }
@@ -474,6 +498,8 @@ search_entry_changed_cb (GtkEntry  *entry,
   g_free (data->filter_string);
   data->filter_string = g_strdup (gtk_entry_get_text (entry));
 
+  g_object_set (data->search_renderer, "search-string", data->filter_string, NULL);
+
   if (!g_strcmp0 (data->filter_string, ""))
     {
       home_button_clicked_cb (GTK_BUTTON (gtk_builder_get_object (data->builder,
diff --git a/shell/shell-search-renderer.c b/shell/shell-search-renderer.c
new file mode 100644
index 0000000..0bb1d3b
--- /dev/null
+++ b/shell/shell-search-renderer.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2010 Intel, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Thomas Wood <thos gnome org>
+ */
+
+#include "shell-search-renderer.h"
+#include <string.h>
+
+G_DEFINE_TYPE (ShellSearchRenderer, shell_search_renderer, GTK_TYPE_CELL_RENDERER)
+
+#define SEARCH_RENDERER_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SHELL_TYPE_SEARCH_RENDERER, ShellSearchRendererPrivate))
+
+struct _ShellSearchRendererPrivate
+{
+  gchar *title;
+  gchar *description;
+  gchar *search_string;
+
+  PangoLayout *layout;
+};
+
+enum
+{
+  PROP_TITLE = 1,
+  PROP_DESCRIPTION,
+  PROP_SEARCH_STRING
+};
+
+
+static void
+shell_search_renderer_get_property (GObject    *object,
+                                    guint       property_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  switch (property_id)
+    {
+  case PROP_TITLE:
+  case PROP_DESCRIPTION:
+  case PROP_SEARCH_STRING:
+    break;
+
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+shell_search_renderer_set_property (GObject      *object,
+                                    guint         property_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  ShellSearchRendererPrivate *priv = SHELL_SEARCH_RENDERER (object)->priv;
+
+  switch (property_id)
+    {
+  case PROP_TITLE:
+    g_free (priv->title);
+    priv->title = g_value_dup_string (value);
+    break;
+
+  case PROP_DESCRIPTION:
+    g_free (priv->description);
+    priv->description = g_value_dup_string (value);
+    break;
+
+  case PROP_SEARCH_STRING:
+    g_free (priv->search_string);
+    priv->search_string = g_value_dup_string (value);
+    break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+shell_search_renderer_dispose (GObject *object)
+{
+  G_OBJECT_CLASS (shell_search_renderer_parent_class)->dispose (object);
+}
+
+static void
+shell_search_renderer_finalize (GObject *object)
+{
+  ShellSearchRendererPrivate *priv = SHELL_SEARCH_RENDERER (object)->priv;
+
+  if (priv->title)
+    {
+      g_free (priv->title);
+      priv->title = NULL;
+    }
+
+  if (priv->description)
+    {
+      g_free (priv->description);
+      priv->description = NULL;
+    }
+
+  if (priv->search_string)
+    {
+      g_free (priv->search_string);
+      priv->search_string = NULL;
+    }
+
+  G_OBJECT_CLASS (shell_search_renderer_parent_class)->finalize (object);
+}
+
+static void
+shell_search_renderer_set_layout (ShellSearchRenderer *cell, GtkWidget *widget)
+{
+  gchar *display_string;
+  ShellSearchRendererPrivate *priv = cell->priv;
+  gchar *needle, *haystack;
+  gchar *full_string;
+
+  if (!priv->layout)
+    {
+      priv->layout = pango_layout_new (gtk_widget_get_pango_context (widget));
+      pango_layout_set_ellipsize (priv->layout, PANGO_ELLIPSIZE_END);
+    }
+
+  full_string = g_strdup_printf ("%s - %s", priv->title, priv->description);
+
+  needle = g_utf8_casefold (priv->search_string, -1);
+  haystack = g_utf8_casefold (full_string, -1);
+
+  /* clear any previous attributes */
+  pango_layout_set_attributes (priv->layout, NULL);
+
+  if (priv->search_string && priv->title
+      && (strstr (haystack, needle)))
+    {
+      gchar *start;
+      gchar *lead, *trail, *leaddot;
+      gint count;
+#define CONTEXT 10
+
+      count = strlen (needle);
+      start = strstr (haystack, needle);
+
+      lead = MAX (start - CONTEXT, haystack);
+      trail = start + count;
+
+      if (lead == haystack)
+        leaddot = "";
+      else
+        leaddot = "...";
+
+      lead = g_strndup (lead, start - lead);
+
+      display_string = g_markup_printf_escaped ("%s\n"
+                                                "<small>%s%s<b>%s</b>%s</small>",
+                                                priv->title, leaddot, lead,
+                                                needle, trail);
+
+      g_free (lead);
+    }
+  else
+    display_string = g_markup_escape_text (priv->title, -1);
+
+
+  pango_layout_set_markup (priv->layout, display_string, -1);
+  g_free (display_string);
+  g_free (needle);
+  g_free (haystack);
+}
+
+static void
+shell_search_renderer_get_size (GtkCellRenderer    *cell,
+                                GtkWidget          *widget,
+                                GdkRectangle       *cell_area,
+                                gint               *x_offset,
+                                gint               *y_offset,
+                                gint               *width,
+                                gint               *height)
+{
+  ShellSearchRendererPrivate *priv = SHELL_SEARCH_RENDERER (cell)->priv;
+  PangoRectangle rect;
+
+  shell_search_renderer_set_layout (SHELL_SEARCH_RENDERER (cell), widget);
+
+  pango_layout_set_width (priv->layout, PANGO_SCALE * 180);
+  pango_layout_get_pixel_extents (priv->layout, NULL, &rect);
+
+  if (width) *width = rect.width;
+  if (height) *height = rect.height;
+
+  if (x_offset) *x_offset = 0;
+  if (y_offset) *y_offset = 0;
+
+}
+
+static void
+shell_search_renderer_render (GtkCellRenderer      *cell,
+                              GdkDrawable          *window,
+                              GtkWidget            *widget,
+                              GdkRectangle         *background_area,
+                              GdkRectangle         *cell_area,
+                              GdkRectangle         *expose_area,
+                              GtkCellRendererState  flags)
+{
+  ShellSearchRendererPrivate *priv = SHELL_SEARCH_RENDERER (cell)->priv;
+  cairo_t *cr;
+  PangoRectangle rect;
+
+  shell_search_renderer_set_layout (SHELL_SEARCH_RENDERER (cell), widget);
+
+  cr = gdk_cairo_create (window);
+
+  pango_layout_get_pixel_extents (priv->layout, NULL, &rect);
+
+  cairo_move_to (cr, cell_area->x, cell_area->y);
+
+  /* FIXME: get the correct colour from the theme */
+  cairo_set_source_rgb (cr, 0, 0, 0);
+  if (priv->layout)
+    pango_cairo_layout_path (cr, priv->layout);
+  cairo_fill (cr);
+
+  cairo_destroy (cr);
+}
+
+static void
+shell_search_renderer_class_init (ShellSearchRendererClass *klass)
+{
+  GParamSpec *pspec;
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkCellRendererClass *cell_renderer = GTK_CELL_RENDERER_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (ShellSearchRendererPrivate));
+
+  object_class->get_property = shell_search_renderer_get_property;
+  object_class->set_property = shell_search_renderer_set_property;
+  object_class->dispose = shell_search_renderer_dispose;
+  object_class->finalize = shell_search_renderer_finalize;
+
+  cell_renderer->get_size = shell_search_renderer_get_size;
+  cell_renderer->render = shell_search_renderer_render;
+
+  pspec = g_param_spec_string ("title",
+                               "Title",
+                               "Item title",
+                               "",
+                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_TITLE, pspec);
+
+  pspec = g_param_spec_string ("description",
+                               "Description",
+                               "Item description",
+                               "",
+                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_DESCRIPTION, pspec);
+
+  pspec = g_param_spec_string ("search-string",
+                               "Search String",
+                               "Current search string",
+                               "",
+                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_SEARCH_STRING, pspec);
+}
+
+static void
+shell_search_renderer_init (ShellSearchRenderer *self)
+{
+  self->priv = SEARCH_RENDERER_PRIVATE (self);
+}
+
+ShellSearchRenderer *
+shell_search_renderer_new (void)
+{
+  return g_object_new (SHELL_TYPE_SEARCH_RENDERER, NULL);
+}
diff --git a/shell/shell-search-renderer.h b/shell/shell-search-renderer.h
new file mode 100644
index 0000000..d0dfd82
--- /dev/null
+++ b/shell/shell-search-renderer.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010 Intel, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Thomas Wood <thos gnome org>
+ */
+
+#ifndef _SHELL_SEARCH_RENDERER_H
+#define _SHELL_SEARCH_RENDERER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_SEARCH_RENDERER shell_search_renderer_get_type()
+
+#define SHELL_SEARCH_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+  SHELL_TYPE_SEARCH_RENDERER, ShellSearchRenderer))
+
+#define SHELL_SEARCH_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+  SHELL_TYPE_SEARCH_RENDERER, ShellSearchRendererClass))
+
+#define SHELL_IS_SEARCH_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+  SHELL_TYPE_SEARCH_RENDERER))
+
+#define SHELL_IS_SEARCH_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+  SHELL_TYPE_SEARCH_RENDERER))
+
+#define SHELL_SEARCH_RENDERER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+  SHELL_TYPE_SEARCH_RENDERER, ShellSearchRendererClass))
+
+typedef struct _ShellSearchRenderer ShellSearchRenderer;
+typedef struct _ShellSearchRendererClass ShellSearchRendererClass;
+typedef struct _ShellSearchRendererPrivate ShellSearchRendererPrivate;
+
+struct _ShellSearchRenderer
+{
+  GtkCellRenderer parent;
+
+  ShellSearchRendererPrivate *priv;
+};
+
+struct _ShellSearchRendererClass
+{
+  GtkCellRendererClass parent_class;
+};
+
+GType shell_search_renderer_get_type (void) G_GNUC_CONST;
+
+ShellSearchRenderer *shell_search_renderer_new (void);
+
+G_END_DECLS
+
+#endif /* _SHELL_SEARCH_RENDERER_H */
diff --git a/shell/shell.ui b/shell/shell.ui
index 8a1de88..7e45002 100644
--- a/shell/shell.ui
+++ b/shell/shell.ui
@@ -110,7 +110,9 @@
                   <object class="GtkIconView" id="search-view">
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
+                    <property name="orientation">horizontal</property>
                     <property name="model">liststore</property>
+                    <property name="spacing">6</property>
                   </object>
                 </child>
               </object>



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