[yelp/yelp-3-0] Adding YelpLocationEntry to yelp-3-0 branch



commit c54f10a187e7b738edff2f7930c82d6ca495bb99
Author: Shaun McCance <shaunm gnome org>
Date:   Tue Sep 8 22:08:53 2009 -0500

    Adding YelpLocationEntry to yelp-3-0 branch

 configure.in                  |    2 +-
 libyelp/Makefile.am           |    1 +
 libyelp/yelp-location-entry.c |  618 +++++++++++++++++++++++++++++++++++++++++
 libyelp/yelp-location-entry.h |   72 +++++
 tests/Makefile.am             |    7 +-
 tests/test-location-entry.c   |  291 +++++++++++++++++++
 6 files changed, 989 insertions(+), 2 deletions(-)
---
diff --git a/configure.in b/configure.in
index 5afa805..20cfde0 100644
--- a/configure.in
+++ b/configure.in
@@ -59,7 +59,7 @@ PKG_CHECK_MODULES(YELP,
 	gio-unix-2.0
 	gnome-doc-utils >= 0.17.2
 	gtk+-unix-print-2.0
-	gtk+-2.0 >= 2.10.0
+	gtk+-2.0 >= 2.16.0
 	libxml-2.0 >= 2.6.5
 	libxslt >= 1.1.4
 	libexslt >= 0.8.1
diff --git a/libyelp/Makefile.am b/libyelp/Makefile.am
index e51e225..7ee9080 100644
--- a/libyelp/Makefile.am
+++ b/libyelp/Makefile.am
@@ -2,6 +2,7 @@ noinst_LTLIBRARIES = libyelp.la
 
 libyelp_la_SOURCES =		\
 	yelp-debug.c		\
+	yelp-location-entry.c	\
 	yelp-uri.c		\
 	yelp-view.c
 
diff --git a/libyelp/yelp-location-entry.c b/libyelp/yelp-location-entry.c
new file mode 100644
index 0000000..3380505
--- /dev/null
+++ b/libyelp/yelp-location-entry.c
@@ -0,0 +1,618 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright (C) 2009 Shaun McCance <shaunm gnome org>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm gnome org>
+ */
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "yelp-location-entry.h"
+
+struct _YelpLocationEntryPrivate
+{
+  GtkWidget *text_entry;
+
+  gint       icon_column;
+  gint       flags_column;
+  gboolean   enable_search;
+
+  GtkTreeRowReference *row;
+
+  gboolean   search_mode;
+
+  GtkCellRenderer *icon_cell;
+};
+
+#define YELP_LOCATION_ENTRY_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE((object), YELP_TYPE_LOCATION_ENTRY, YelpLocationEntryPrivate))
+
+static void     location_entry_get_property    (GObject           *object,
+                                                guint              prop_id,
+                                                GValue            *value,
+                                                GParamSpec        *pspec);
+static void     location_entry_set_property    (GObject           *object,
+                                                guint              prop_id,
+                                                const GValue      *value,
+                                                GParamSpec        *pspec);
+static void     location_entry_start_search    (YelpLocationEntry *entry,
+                                                gboolean           clear);
+static void     location_entry_set_entry       (YelpLocationEntry *entry,
+                                                gboolean           emit);
+static gboolean location_entry_pulse           (gpointer           data);
+
+/* GtkTreeModel callbacks */
+static void     yelp_location_entry_row_changed     (GtkTreeModel      *model,
+                                                     GtkTreePath       *path,
+                                                     GtkTreeIter       *iter,
+                                                     gpointer           user_data);
+
+/* GtkComboBox callbacks */
+static void     combo_box_changed_cb                (GtkComboBox       *widget,
+                                                     gpointer           user_data);
+static gboolean combo_box_row_separator_func        (GtkTreeModel      *model,
+                                                     GtkTreeIter       *iter,
+                                                     gpointer           user_data);
+
+/* GtkEntry callbacks */
+static gboolean entry_focus_in_cb                   (GtkWidget         *widget,
+                                                     GdkEventFocus     *event,
+                                                     gpointer           user_data);
+static gboolean entry_focus_out_cb                  (GtkWidget         *widget,
+                                                     GdkEventFocus     *event,
+                                                     gpointer           user_data);
+static void     entry_activate_cb                   (GtkEntry          *text_entry,
+                                                     gpointer           user_data);
+static void     entry_icon_press_cb                 (GtkEntry          *entry,
+                                                     GtkEntryIconPosition icon_pos,
+                                                     GdkEvent          *event,
+                                                     gpointer           user_data);
+static gboolean entry_key_press_cb                  (GtkWidget         *widget,
+                                                     GdkEventKey       *event,
+                                                     gpointer           user_data);
+
+
+static GtkComboBoxEntryClass *parent_class = NULL;
+
+enum {
+  LOCATION_SELECTED,
+  SEARCH_ACTIVATED,
+  LAST_SIGNAL
+};
+
+enum {  
+  PROP_0,
+  PROP_ICON_COLUMN,
+  PROP_FLAGS_COLUMN,
+  PROP_ENABLE_SEARCH
+};
+
+static guint location_entry_signals[LAST_SIGNAL] = {0,};
+
+G_DEFINE_TYPE (YelpLocationEntry, yelp_location_entry, GTK_TYPE_COMBO_BOX_ENTRY)
+
+static void
+yelp_location_entry_class_init (YelpLocationEntryClass *klass)
+{
+  GObjectClass *gobject_class;
+  GtkObjectClass *object_class;
+  GtkWidgetClass *widget_class;
+  GtkComboBoxEntryClass *entry_class;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  widget_class = GTK_WIDGET_CLASS (klass);
+  entry_class = GTK_COMBO_BOX_ENTRY_CLASS (klass);
+
+  gobject_class->get_property = location_entry_get_property;
+  gobject_class->set_property = location_entry_set_property;
+
+  /**
+   * YelpLocationEntry::location-selected
+   * @widget: The object which received the signal
+   */
+  location_entry_signals[LOCATION_SELECTED] =
+    g_signal_new ("location_selected",
+                  G_OBJECT_CLASS_TYPE (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * YelpLocationEntry::search_activated
+   * @widget: The object which received the signal
+   * @text: The search text
+   */
+  location_entry_signals[SEARCH_ACTIVATED] =
+    g_signal_new ("search-activated",
+                  G_OBJECT_CLASS_TYPE (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__STRING,
+                  G_TYPE_NONE, 1,
+                  G_TYPE_STRING);
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_ICON_COLUMN,
+                                   g_param_spec_int ("icon-column",
+                                                     N_("Icon Column"),
+                                                     N_("A column in the model to get icon names from"),
+                                                     -1,
+                                                     G_MAXINT,
+                                                     -1,
+                                                     G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class,
+                                   PROP_FLAGS_COLUMN,
+                                   g_param_spec_int ("flags-column",
+                                                     N_("Flags Column"),
+                                                     N_("A column in the model with YelpLocationEntryFlags flags"),
+                                                     -1,
+                                                     G_MAXINT,
+                                                     -1,
+                                                     G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class,
+                                   PROP_ENABLE_SEARCH,
+                                   g_param_spec_boolean ("enable-search",
+                                                         N_("Enable Search"),
+                                                         N_("Whether the location entry can be used as a search field"),
+                                                         TRUE,
+                                                         G_PARAM_READWRITE));
+
+  g_type_class_add_private ((GObjectClass *) klass,
+                            sizeof (YelpLocationEntryPrivate));
+}
+
+static void yelp_location_entry_init (YelpLocationEntry *entry)
+{
+  GList *cells;
+  entry->priv = YELP_LOCATION_ENTRY_GET_PRIVATE (entry);
+
+  entry->priv->enable_search = TRUE;
+  entry->priv->search_mode = FALSE;
+
+  entry->priv->text_entry = gtk_bin_get_child (GTK_BIN (entry));
+  gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                     GTK_ENTRY_ICON_PRIMARY,
+                                     "help-browser");
+  gtk_entry_set_icon_activatable (GTK_ENTRY (entry->priv->text_entry),
+                                  GTK_ENTRY_ICON_PRIMARY,
+                                  FALSE);
+  gtk_entry_set_icon_activatable (GTK_ENTRY (entry->priv->text_entry),
+                                  GTK_ENTRY_ICON_PRIMARY,
+                                  TRUE);
+
+  /* Trying to get the text to line up with the text in the GtkEntry.
+   * The text cell is the zeroeth cell right now, since we haven't
+   * yet called reorder.  I realize using a guesstimate pixel value
+   * won't be perfect all the time, but 4 looks niceish.
+   */
+  cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (entry));
+  g_object_set (cells->data, "xpad", 4, NULL);
+  g_list_free (cells);
+
+  entry->priv->icon_cell = gtk_cell_renderer_pixbuf_new ();
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (entry), entry->priv->icon_cell, FALSE);
+  gtk_cell_layout_reorder (GTK_CELL_LAYOUT (entry), entry->priv->icon_cell, 0);
+
+  g_signal_connect (entry, "changed",
+                    G_CALLBACK (combo_box_changed_cb), NULL);
+
+  g_signal_connect (entry->priv->text_entry, "focus-in-event",
+                    G_CALLBACK (entry_focus_in_cb), entry);
+  g_signal_connect (entry->priv->text_entry, "focus-out-event",
+                    G_CALLBACK (entry_focus_out_cb), entry);
+  g_signal_connect (entry->priv->text_entry, "icon-press",
+                    G_CALLBACK (entry_icon_press_cb), entry);
+  g_signal_connect (entry->priv->text_entry, "key-press-event",
+                    G_CALLBACK (entry_key_press_cb), entry);
+  g_signal_connect (entry->priv->text_entry, "activate",
+                    G_CALLBACK (entry_activate_cb), entry);
+}
+
+static void
+location_entry_get_property   (GObject      *object,
+                               guint         prop_id,
+                               GValue       *value,
+                               GParamSpec   *pspec)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (object);
+
+  switch (prop_id)
+    {
+    case PROP_ICON_COLUMN:
+      g_value_set_int (value, entry->priv->icon_column);
+      break;
+    case PROP_FLAGS_COLUMN:
+      g_value_set_int (value, entry->priv->flags_column);
+      break;
+    case PROP_ENABLE_SEARCH:
+      g_value_set_boolean (value, entry->priv->enable_search);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+location_entry_set_property   (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (object);
+
+  switch (prop_id)
+    {
+    case PROP_ICON_COLUMN:
+      entry->priv->icon_column = g_value_get_int (value);
+      gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (entry),
+                                      entry->priv->icon_cell,
+                                      "icon-name",
+                                      entry->priv->icon_column,
+                                      NULL);
+      break;
+    case PROP_FLAGS_COLUMN:
+      entry->priv->flags_column = g_value_get_int (value);
+      /* If this is in yelp_location_entry_init, it gets called the first
+       * time before flags_column is set, and isn't called again until the
+       * "row-changed" signal on the model.  The prevents application code
+       * from populating the model before initializing the widget.
+       */
+      gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (entry),
+                                            (GtkTreeViewRowSeparatorFunc)
+                                            combo_box_row_separator_func,
+                                            entry, NULL);
+      break;
+    case PROP_ENABLE_SEARCH:
+      entry->priv->enable_search = g_value_get_boolean (value);
+      gtk_editable_set_editable (GTK_EDITABLE (entry->priv->text_entry),
+                                 entry->priv->enable_search);
+      if (!entry->priv->enable_search)
+        {
+          entry->priv->search_mode = FALSE;
+          location_entry_set_entry (entry, FALSE);
+        }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+location_entry_start_search (YelpLocationEntry *entry,
+                             gboolean           clear)
+{
+  if (!entry->priv->enable_search)
+    return;
+  if (clear && !entry->priv->search_mode)
+    {
+      gtk_entry_set_text (GTK_ENTRY (entry->priv->text_entry), "");
+    }
+  entry->priv->search_mode = TRUE;
+  location_entry_set_entry (entry, FALSE);
+  gtk_widget_grab_focus (entry->priv->text_entry);
+}
+
+static void
+location_entry_set_entry (YelpLocationEntry *entry, gboolean emit)
+{
+  GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
+  GtkTreePath *path = NULL;
+  GtkTreeIter iter;
+  gchar *icon_name;
+
+  if (entry->priv->search_mode)
+    {
+      gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                         GTK_ENTRY_ICON_PRIMARY,
+                                         "system-search");
+      gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                         GTK_ENTRY_ICON_SECONDARY,
+                                         "edit-clear");
+      gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry->priv->text_entry),
+                                       GTK_ENTRY_ICON_SECONDARY,
+                                       "Clear the search text");
+      return;
+    }
+
+  if (entry->priv->row)
+    path = gtk_tree_row_reference_get_path (entry->priv->row);
+
+  if (path)
+    {
+      gchar *text;
+      gint flags;
+      gtk_tree_model_get_iter (model, &iter, path);
+      gtk_tree_model_get (model, &iter,
+                          gtk_combo_box_entry_get_text_column (GTK_COMBO_BOX_ENTRY (entry)),
+                          &text,
+                          entry->priv->icon_column, &icon_name,
+                          entry->priv->flags_column, &flags,
+                          -1);
+      if (flags & YELP_LOCATION_ENTRY_IS_LOADING)
+        {
+          gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                             GTK_ENTRY_ICON_PRIMARY,
+                                             "image-loading");
+          g_timeout_add (80, location_entry_pulse, entry);
+        }
+      else
+        {
+          gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                             GTK_ENTRY_ICON_PRIMARY,
+                                             icon_name);
+        }
+      if (flags & YELP_LOCATION_ENTRY_CAN_BOOKMARK)
+        {
+          gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                             GTK_ENTRY_ICON_SECONDARY,
+                                             "bookmark-new");
+          gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry->priv->text_entry),
+                                           GTK_ENTRY_ICON_SECONDARY,
+                                           "Bookmark this page");
+        }
+      else
+        {
+          gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                             GTK_ENTRY_ICON_SECONDARY,
+                                             NULL);
+        }
+      gtk_entry_set_text (GTK_ENTRY (entry->priv->text_entry), text);
+      if (emit)
+        g_signal_emit (entry, location_entry_signals[LOCATION_SELECTED], 0);
+      g_free (text);
+      gtk_tree_path_free (path);
+    }
+  else
+    {
+      gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                         GTK_ENTRY_ICON_PRIMARY,
+                                         "help-browser");
+      gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->text_entry),
+                                         GTK_ENTRY_ICON_SECONDARY,
+                                         NULL);
+    }
+}
+
+static gboolean
+location_entry_pulse (gpointer data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (data);
+  GtkTreeModel *model;
+  GtkTreePath *path;
+  GtkTreeIter iter;
+  gint flags;
+
+  model = gtk_tree_row_reference_get_model (entry->priv->row);
+  path = gtk_tree_row_reference_get_path (entry->priv->row);
+  if (path)
+    {
+      gtk_tree_model_get_iter (model, &iter, path);
+      gtk_tree_model_get (model, &iter,
+                          entry->priv->flags_column, &flags,
+                          -1);
+      gtk_tree_path_free (path);
+    }
+
+  if (flags & YELP_LOCATION_ENTRY_IS_LOADING && !entry->priv->search_mode)
+    {
+      gtk_entry_progress_pulse (GTK_ENTRY (entry->priv->text_entry));
+    }
+  else
+    {
+      gtk_entry_set_progress_fraction (GTK_ENTRY (entry->priv->text_entry), 0.0);
+    }
+
+  return flags & YELP_LOCATION_ENTRY_IS_LOADING;
+}
+
+static void
+combo_box_changed_cb (GtkComboBox  *widget,
+                      gpointer      user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (widget);
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  GtkTreePath *path;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
+
+  if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (entry), &iter))
+    {
+      gint flags;
+      gtk_tree_model_get (model, &iter,
+                          entry->priv->flags_column, &flags,
+                          -1);
+      if (flags & YELP_LOCATION_ENTRY_IS_SEARCH)
+        {
+          location_entry_start_search (entry, TRUE);
+        }
+      else
+        {
+          path = gtk_tree_model_get_path (model, &iter);
+          if (entry->priv->row)
+            gtk_tree_row_reference_free (entry->priv->row);
+          entry->priv->row = gtk_tree_row_reference_new (model, path);
+          gtk_tree_path_free (path);
+          entry->priv->search_mode = FALSE;
+          location_entry_set_entry (YELP_LOCATION_ENTRY (widget), TRUE);
+        }
+    }
+}
+
+static gboolean
+combo_box_row_separator_func (GtkTreeModel  *model,
+                              GtkTreeIter   *iter,
+                              gpointer       user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+  gint flags;
+  gtk_tree_model_get (model, iter,
+                      entry->priv->flags_column, &flags,
+                      -1);
+  return (flags & YELP_LOCATION_ENTRY_IS_SEPARATOR);
+}
+
+static void
+yelp_location_entry_row_changed (GtkTreeModel  *model,
+                                 GtkTreePath   *path,
+                                 GtkTreeIter   *iter,
+                                 gpointer       user_data)
+{
+  /* FIXME: Should we bother checking path/iter against entry->priv->row?
+     It doesn't really make a difference, since set_entry is pretty much
+     safe to call whenever.  Does it make a performance impact?
+  */
+  location_entry_set_entry (YELP_LOCATION_ENTRY (user_data), FALSE);
+}
+
+static gboolean
+entry_focus_in_cb (GtkWidget     *widget,
+                   GdkEventFocus *event,
+                   gpointer       user_data)
+{
+  GtkEntry *text_entry = GTK_ENTRY (widget);
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+
+  if (entry->priv->enable_search && !entry->priv->search_mode)
+    location_entry_start_search (entry, TRUE);
+
+  return FALSE;
+}
+
+static gboolean
+entry_focus_out_cb (GtkWidget         *widget,
+                    GdkEventFocus     *event,
+                    gpointer           user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+
+  if (gtk_entry_get_text_length (GTK_ENTRY (widget)) == 0)
+    {
+      entry->priv->search_mode = FALSE;
+      location_entry_set_entry (entry, FALSE);
+    }
+}
+
+static void
+entry_activate_cb (GtkEntry  *text_entry,
+                   gpointer   user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+  gchar *text = g_strdup (gtk_entry_get_text (text_entry));
+
+  if (!entry->priv->enable_search)
+    return;
+
+  if (!entry->priv->search_mode || text == NULL || strlen(text) == 0)
+    return;
+
+  g_signal_emit (entry, location_entry_signals[SEARCH_ACTIVATED], 0, text);
+
+  g_free (text);
+}
+
+static void
+entry_icon_press_cb (GtkEntry            *text_entry,
+                     GtkEntryIconPosition icon_pos,
+                     GdkEvent            *event,
+                     gpointer             user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+  if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
+    {
+      const gchar *name = gtk_entry_get_icon_name (text_entry, icon_pos);
+      if (g_str_equal (name, "edit-clear"))
+        {
+          entry->priv->search_mode = FALSE;
+          location_entry_set_entry (entry, FALSE);
+        }
+      else if  (g_str_equal (name, "bookmark-new"))
+        {
+          /* FIXME: emit bookmark signal */
+        }
+    }
+}
+
+static gboolean
+entry_key_press_cb (GtkWidget   *widget,
+                    GdkEventKey *event,
+                    gpointer     user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+  if (event->keyval == GDK_Escape)
+    {
+      entry->priv->search_mode = FALSE;
+      location_entry_set_entry (entry, FALSE);
+      return TRUE;
+    }
+  else if (!entry->priv->search_mode)
+    {
+      location_entry_start_search (entry, FALSE);
+    }
+
+  return FALSE;
+}
+
+/**
+ * yelp_location_entry_ew_with_model:
+ * @model: A #GtkTreeModel.
+ * @text_column: The column in @model containing the title of each entry.
+ * @icon_column: The column in @model containing the icon name of each entry.
+ * @bookmark_column: The column in @model containing a gboolean specifying
+ * whether or not each entry is bookmarked.
+ *
+ * Creates a new #YelpLocationEntry widget.
+ *
+ * Return value: A new #YelpLocationEntry.
+ */
+GtkWidget*
+yelp_location_entry_new_with_model (GtkTreeModel *model,
+                                    gint          text_column,
+                                    gint          icon_column,
+                                    gint          flags_column)
+{
+  GtkWidget *ret;
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
+
+  ret = GTK_WIDGET (g_object_new (YELP_TYPE_LOCATION_ENTRY,
+                                  "model", model,
+                                  "text-column", text_column,
+                                  "icon-column", icon_column,
+                                  "flags-column", flags_column,
+                                  NULL));
+
+  /* FIXME: This isn't a good place for this.  Probably should
+   * put it in a notify handler for the model property.
+   */
+  g_signal_connect (model, "row-changed",
+                    G_CALLBACK (yelp_location_entry_row_changed),
+                    YELP_LOCATION_ENTRY (ret));
+  return ret;
+}
+
+void
+yelp_location_entry_start_search (YelpLocationEntry *entry)
+{
+  location_entry_start_search (entry, TRUE);
+}
diff --git a/libyelp/yelp-location-entry.h b/libyelp/yelp-location-entry.h
new file mode 100644
index 0000000..11af99f
--- /dev/null
+++ b/libyelp/yelp-location-entry.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Copyright (C) 2009 Shaun McCance <shaunm gnome org>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm gnome org>
+ */
+
+typedef struct _YelpLocationEntry         YelpLocationEntry;
+typedef struct _YelpLocationEntryClass    YelpLocationEntryClass;
+typedef struct _YelpLocationEntryPrivate  YelpLocationEntryPrivate;
+
+#include <gtk/gtk.h>
+
+#define YELP_TYPE_LOCATION_ENTRY (yelp_location_entry_get_type ())
+#define YELP_LOCATION_ENTRY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), YELP_TYPE_LOCATION_ENTRY, \
+			      YelpLocationEntry))
+#define YELP_LOCATION_ENTRY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), YELP_TYPE_LOCATION_ENTRY, \
+			   YelpLocationEntryClass))
+#define YELP_IS_LOCATION_ENTRY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), YELP_TYPE_LOCATION_ENTRY))
+#define YELP_IS_LOCATION_ENTRY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), YELP_TYPE_LOCATION_ENTRY))
+#define YELP_LOCATION_ENTRY_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS((obj), YELP_TYPE_LOCATION_ENTRY, \
+			     YelpLocationEntryClass))
+
+struct _YelpLocationEntry
+{
+  GtkComboBoxEntry          parent;
+  YelpLocationEntryPrivate *priv;
+};
+
+struct _YelpLocationEntryClass
+{
+  GtkComboBoxEntryClass     parent;
+};
+
+typedef enum {
+    YELP_LOCATION_ENTRY_CAN_BOOKMARK = 1 << 0,
+    YELP_LOCATION_ENTRY_IS_LOADING   = 1 << 1,
+    YELP_LOCATION_ENTRY_IS_SEPARATOR = 1 << 2,
+    YELP_LOCATION_ENTRY_IS_SEARCH    = 1 << 3
+} YelpLocationEntryFlags;
+
+G_BEGIN_DECLS
+
+GType           yelp_location_entry_get_type        (void);
+GtkWidget*      yelp_location_entry_new_with_model  (GtkTreeModel *model,
+                                                     gint          text_column,
+                                                     gint          icon_column,
+                                                     gint          flags_column);
+
+void            yelp_location_entry_start_search    (YelpLocationEntry *entry);
+
+G_END_DECLS
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6277334..b1efc6a 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -7,7 +7,12 @@ YELP_COMMON_LDADD =				\
 	$(YELP_LIBS)				\
 	$(top_builddir)/libyelp/libyelp.la
 
-check_PROGRAMS = test-uri
+check_PROGRAMS =				\
+	test-location-entry			\
+	test-uri
+
+test_location_entry_CFLAGS = $(YELP_COMMON_CFLAGS)
+test_location_entry_LDADD = $(YELP_COMMON_LDADD)
 
 test_uri_CFLAGS = $(YELP_COMMON_CFLAGS)
 test_uri_LDADD = $(YELP_COMMON_LDADD)
diff --git a/tests/test-location-entry.c b/tests/test-location-entry.c
new file mode 100644
index 0000000..37200df
--- /dev/null
+++ b/tests/test-location-entry.c
@@ -0,0 +1,291 @@
+#include <gtk/gtk.h>
+#include "yelp-location-entry.h"
+
+enum {
+  COL_ICON,
+  COL_TITLE,
+  COL_FLAGS,
+  COL_URI,
+  COL_TERMS
+};
+
+YelpLocationEntry *entry;
+GtkListStore *event_list;
+
+static gboolean
+loading_callback (gpointer data)
+{
+  GtkTreeRowReference *row = (GtkTreeRowReference *) data;
+  GtkTreePath *path = gtk_tree_row_reference_get_path (row);
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  model = gtk_tree_row_reference_get_model (row);
+  gtk_tree_model_get_iter (model, &iter, path);
+
+  gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+		      COL_ICON, "gnome-main-menu",
+		      COL_TITLE, "Desktop Help",
+		      COL_FLAGS, YELP_LOCATION_ENTRY_CAN_BOOKMARK,
+		      -1);
+  gtk_tree_path_free (path);
+  gtk_tree_row_reference_free (row);
+  return FALSE;
+}
+
+static void
+check_clicked (GtkToggleButton *button, gpointer user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+  g_object_set (G_OBJECT (entry), "enable-search",
+		gtk_toggle_button_get_active (button), NULL);
+}
+
+static void
+search_clicked (GtkToggleButton *button, gpointer user_data)
+{
+  YelpLocationEntry *entry = YELP_LOCATION_ENTRY (user_data);
+  yelp_location_entry_start_search (entry);
+}
+
+static void
+button_clicked (GtkButton *button, gpointer user_data)
+{
+  GtkListStore *model = GTK_LIST_STORE (user_data);
+  const gchar *label = gtk_button_get_label (button);
+  GtkTreeIter iter;
+  gchar *uri = NULL, *icon, *title;
+  gboolean loading = FALSE;
+
+  if (g_str_equal (label, "Empathy"))
+    {
+      uri = "help:empathy";
+      icon = "empathy";
+    }
+  else if (g_str_equal (label, "Calculator"))
+    {
+      uri = "help:gcalctool";
+      icon = "accessories-calculator";
+    }
+  else if (g_str_equal (label, "Terminal"))
+    {
+      uri = "help:gnome-terminal";
+      icon = "gnome-terminal";
+    }
+  else if (g_str_equal (label, "Slow-loading document"))
+    {
+      uri = "help:gnumeric";
+      icon = NULL;
+      loading = TRUE;
+    }
+
+  if (uri)
+    {
+      gchar *iter_uri;
+      gboolean cont;
+      cont = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
+      while (cont)
+	{
+	  gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+			      COL_URI, &iter_uri,
+			      -1);
+	  if (iter_uri && g_str_equal (uri, iter_uri))
+	    {
+	      g_free (iter_uri);
+	      break;
+	    }
+	  else
+	    {
+	      g_free (iter_uri);
+	      cont = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
+	    }
+	}
+      if (cont)
+	{
+	  GtkTreeIter first;
+	  gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &first);
+	  gtk_list_store_move_before (model, &iter, &first);
+	}
+      else
+	{
+	  gtk_list_store_prepend (model, &iter);
+	  if (!loading)
+	    {
+	      title = g_strconcat (label, " Help", NULL);
+	    }
+	  else
+	    {
+	      GtkTreePath *path;
+	      GtkTreeRowReference *row;
+	      title = g_strdup ("Loading...");
+	      path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+	      row = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), path);
+	      gtk_tree_path_free (path);
+	      g_timeout_add_seconds (5, loading_callback, row);
+	    }
+	  gtk_list_store_set (model, &iter,
+			      COL_ICON, icon,
+			      COL_TITLE, title,
+			      COL_FLAGS, YELP_LOCATION_ENTRY_CAN_BOOKMARK | (loading ? YELP_LOCATION_ENTRY_IS_LOADING : 0),
+			      COL_URI, uri,
+			      -1);
+	  g_free (title);
+	}
+      gtk_combo_box_set_active_iter (GTK_COMBO_BOX (entry), &iter);
+    }
+}
+
+static void
+location_selected_cb (GtkEntry *entry, gpointer user_data)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter, first;
+  gchar *title, *icon;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
+  gtk_combo_box_get_active_iter (GTK_COMBO_BOX (entry), &iter);
+  gtk_tree_model_get_iter_first (model, &first);
+
+  gtk_list_store_move_before (GTK_LIST_STORE (model), &iter, &first);
+
+  gtk_tree_model_get (model, &iter,
+		      COL_TITLE, &title,
+		      COL_ICON, &icon,
+		      -1);
+
+  gtk_list_store_prepend (event_list, &iter);
+  gtk_list_store_set (event_list, &iter,
+		      0, icon,
+		      1, title,
+		      -1);
+  g_free (icon);
+  g_free (title);
+}
+
+static void
+search_activated_cb (GtkEntry *entry, gchar *text, gpointer user_data)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  model = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
+  gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
+  gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+		      COL_ICON, "folder-saved-search",
+		      COL_TITLE, text,
+		      COL_FLAGS, 0,
+		      COL_URI, "search:",
+		      -1);
+  gtk_combo_box_set_active_iter (GTK_COMBO_BOX (entry), &iter);
+}
+
+int
+main (int argc, char **argv)
+{
+  GtkWidget *window, *vbox, *scroll, *list, *hbox, *button;
+  GtkTreeViewColumn *col;
+  GtkCellRenderer *cell;
+  GtkListStore *model;
+  GtkTreeIter iter;
+
+  gtk_init (&argc, &argv);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_default_size (GTK_WINDOW (window), 450, 600);
+  gtk_container_set_border_width (GTK_CONTAINER (window), 6);
+
+  vbox = gtk_vbox_new (FALSE, 6);
+  gtk_container_add (GTK_CONTAINER (window), vbox);
+
+  model = gtk_list_store_new (5,
+			      G_TYPE_STRING,  /* icon name */
+			      G_TYPE_STRING,  /* title */
+			      G_TYPE_INT,     /* flags */
+			      G_TYPE_STRING,  /* uri */
+			      G_TYPE_STRING   /* search terms */
+			      );
+  gtk_list_store_append (model, &iter);
+  gtk_list_store_set (model, &iter,
+		      COL_FLAGS, YELP_LOCATION_ENTRY_IS_SEPARATOR,
+		      -1);
+  gtk_list_store_append (model, &iter);
+  gtk_list_store_set (model, &iter,
+		      COL_ICON, "system-search",
+		      COL_TITLE, "Search...",
+		      COL_FLAGS, YELP_LOCATION_ENTRY_IS_SEARCH,
+		      -1);
+
+  entry = (YelpLocationEntry *) yelp_location_entry_new_with_model (GTK_TREE_MODEL (model),
+								    COL_TITLE,
+								    COL_ICON,
+								    COL_FLAGS);
+  g_signal_connect (entry, "location-selected",
+		    G_CALLBACK (location_selected_cb), NULL);
+  g_signal_connect (entry, "search-activated",
+		    G_CALLBACK (search_activated_cb), NULL);
+  gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (entry), FALSE, FALSE, 0);
+
+  scroll = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
+				  GTK_POLICY_AUTOMATIC,
+				  GTK_POLICY_AUTOMATIC);
+  gtk_box_pack_start (GTK_BOX (vbox), scroll, TRUE, TRUE, 0);
+  event_list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+  list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (event_list));
+  col = gtk_tree_view_column_new ();
+  gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
+  cell = gtk_cell_renderer_pixbuf_new ();
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), cell, FALSE);
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (col), cell,
+				  "icon-name", 0,
+				  NULL);
+  cell = gtk_cell_renderer_text_new ();
+  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (col), cell, TRUE);
+  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (col), cell,
+				  "text", 1,
+				  NULL);
+  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
+  gtk_container_add (GTK_CONTAINER (scroll), list);
+
+  hbox = gtk_hbox_new (TRUE, 6);
+  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+  button = gtk_check_button_new_with_label ("Enable search");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+  g_signal_connect (button, "clicked",
+		    G_CALLBACK (check_clicked), entry);
+  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
+
+  button = gtk_button_new_with_label ("Search");
+  g_signal_connect (button, "clicked",
+		    G_CALLBACK (search_clicked), entry);
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
+
+  hbox = gtk_hbox_new (TRUE, 6);
+  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+  button = gtk_button_new_with_label ("Empathy");
+  g_signal_connect (button, "clicked",
+		    G_CALLBACK (button_clicked), model);
+  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
+
+  button = gtk_button_new_with_label ("Calculator");
+  g_signal_connect (button, "clicked",
+		    G_CALLBACK (button_clicked), model);
+  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
+
+  button = gtk_button_new_with_label ("Terminal");
+  g_signal_connect (button, "clicked",
+		    G_CALLBACK (button_clicked), model);
+  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
+
+  button = gtk_button_new_with_label ("Slow-loading document");
+  g_signal_connect (button, "clicked",
+		    G_CALLBACK (button_clicked), model);
+  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+  gtk_widget_show_all (GTK_WIDGET (window));
+  gtk_widget_grab_focus (list);
+
+  gtk_main ();
+}



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