[gtk+] inspector: Add search to the object tree



commit 992c5f0dd68413ccab04f7f5d5863b13402ab3ca
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Oct 31 14:16:39 2014 -0400

    inspector: Add search to the object tree
    
    This is also an attempt to figure out a better story for search
    in treeviews.

 gtk/inspector/Makefile.am    |    2 +
 gtk/inspector/object-tree.c  |  237 ++++++++++++++++++++++++++++++++++++++++++
 gtk/inspector/object-tree.ui |   59 ++++++++++-
 gtk/inspector/treewalk.c     |  223 +++++++++++++++++++++++++++++++++++++++
 gtk/inspector/treewalk.h     |   41 +++++++
 5 files changed, 559 insertions(+), 3 deletions(-)
---
diff --git a/gtk/inspector/Makefile.am b/gtk/inspector/Makefile.am
index 7e1ecdd..ff3806c 100644
--- a/gtk/inspector/Makefile.am
+++ b/gtk/inspector/Makefile.am
@@ -62,6 +62,8 @@ libgtkinspector_la_SOURCES =          \
        statistics.c                    \
        style-prop-list.h               \
        style-prop-list.c               \
+       treewalk.h                      \
+       treewalk.c                      \
        visual.h                        \
        visual.c                        \
        window.h                        \
diff --git a/gtk/inspector/object-tree.c b/gtk/inspector/object-tree.c
index bc6e78b..b601859 100644
--- a/gtk/inspector/object-tree.c
+++ b/gtk/inspector/object-tree.c
@@ -46,6 +46,9 @@
 #include "gtktreemodelfilter.h"
 #include "gtkwidgetprivate.h"
 #include "gtkstylecontext.h"
+#include "gtksearchbar.h"
+#include "gtksearchentry.h"
+#include "treewalk.h"
 
 enum
 {
@@ -73,6 +76,11 @@ struct _GtkInspectorObjectTreePrivate
   GHashTable *iters;
   gulong map_hook;
   gulong unmap_hook;
+  GtkTreeViewColumn *object_column;
+  GtkWidget *search_bar;
+  GtkWidget *search_entry;
+  GtkTreeWalk *walk;
+  gint search_length;
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -123,7 +131,12 @@ on_selection_changed (GtkTreeSelection       *selection,
                       GtkInspectorObjectTree *wt)
 {
   GObject *object;
+  GtkTreeIter iter;
 
+  if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+    gtk_tree_walk_reset (wt->priv->walk, &iter);
+  else
+    gtk_tree_walk_reset (wt->priv->walk, NULL);
   object = gtk_inspector_object_tree_get_selected (wt);
   g_signal_emit (wt, signals[OBJECT_SELECTED], 0, object);
 }
@@ -187,6 +200,214 @@ map_or_unmap (GSignalInvocationHint *ihint,
 }
 
 static void
+move_search_to_row (GtkInspectorObjectTree *wt,
+                    GtkTreeIter            *iter)
+{
+  GtkTreeSelection *selection;
+  GtkTreePath *path;
+
+  selection = gtk_tree_view_get_selection (wt->priv->tree);
+  path = gtk_tree_model_get_path (GTK_TREE_MODEL (wt->priv->model), iter);
+  gtk_tree_view_expand_to_path (wt->priv->tree, path);
+  gtk_tree_selection_select_path (selection, path);
+  gtk_tree_view_scroll_to_cell (wt->priv->tree, path, wt->priv->object_column, FALSE, 0.0, 0.0);
+  gtk_tree_path_free (path);
+}
+
+static gboolean
+key_press_event (GtkWidget              *window,
+                 GdkEvent               *event,
+                 GtkInspectorObjectTree *wt)
+{
+  if (gtk_widget_get_mapped (GTK_WIDGET (wt)))
+    {
+      GdkModifierType default_accel;
+      gboolean search_started;
+
+      search_started = gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar));
+      default_accel = gtk_widget_get_modifier_mask (GTK_WIDGET (wt), 
GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
+
+      if (search_started &&
+          (event->key.keyval == GDK_KEY_Return ||
+           event->key.keyval == GDK_KEY_ISO_Enter ||
+           event->key.keyval == GDK_KEY_KP_Enter))
+        {
+          GtkTreeSelection *selection;
+          GtkTreeModel *model;
+          GtkTreeIter iter;
+          GtkTreePath *path;
+
+          selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (wt->priv->tree));
+          if (gtk_tree_selection_get_selected (selection, &model, &iter))
+            {
+              path = gtk_tree_model_get_path (model, &iter);
+              gtk_tree_view_row_activated (GTK_TREE_VIEW (wt->priv->tree),
+                                           path,
+                                           wt->priv->object_column);
+              gtk_tree_path_free (path);
+
+              return GDK_EVENT_STOP;
+            }
+          else
+            return GDK_EVENT_PROPAGATE;
+        }
+      else if (search_started &&
+               (event->key.keyval == GDK_KEY_Escape))
+        {
+          gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar), FALSE);
+          return GDK_EVENT_STOP;
+        }
+      else if (search_started &&
+               ((event->key.state & (default_accel | GDK_SHIFT_MASK)) == (default_accel | GDK_SHIFT_MASK)) &&
+               (event->key.keyval == GDK_KEY_g || event->key.keyval == GDK_KEY_G))
+        {
+          GtkTreeIter iter;
+          if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, TRUE, &iter))
+            move_search_to_row (wt, &iter);
+          else
+            gtk_widget_error_bell (GTK_WIDGET (wt));
+
+          return GDK_EVENT_STOP;
+        }
+      else if (search_started &&
+               ((event->key.state & (default_accel | GDK_SHIFT_MASK)) == default_accel) &&
+               (event->key.keyval == GDK_KEY_g || event->key.keyval == GDK_KEY_G))
+        {
+          GtkTreeIter iter;
+
+          if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, FALSE, &iter))
+            move_search_to_row (wt, &iter);
+          else
+            gtk_widget_error_bell (GTK_WIDGET (wt));
+
+          return GDK_EVENT_STOP;
+        }
+
+      return gtk_search_bar_handle_event (GTK_SEARCH_BAR (wt->priv->search_bar), event);
+    }
+  else
+    return GDK_EVENT_PROPAGATE;
+}
+
+static void
+on_hierarchy_changed (GtkWidget *widget,
+                      GtkWidget *previous_toplevel)
+{
+  if (previous_toplevel)
+    g_signal_handlers_disconnect_by_func (previous_toplevel, key_press_event, widget);
+  g_signal_connect (gtk_widget_get_toplevel (widget), "key-press-event",
+                    G_CALLBACK (key_press_event), widget);
+}
+
+static void
+on_search_changed (GtkSearchEntry         *entry,
+                   GtkInspectorObjectTree *wt)
+{
+  GtkTreeIter iter;
+  gint length;
+  gboolean backwards;
+
+  length = strlen (gtk_entry_get_text (GTK_ENTRY (entry)));
+  backwards = length < wt->priv->search_length;
+  wt->priv->search_length = length;
+
+  if (length == 0)
+    return;
+
+  if (gtk_tree_walk_next_match (wt->priv->walk, backwards, backwards, &iter))
+    move_search_to_row (wt, &iter);
+  else if (!backwards)
+    gtk_widget_error_bell (GTK_WIDGET (wt));
+}
+
+static gboolean
+match_string (const gchar *string,
+              const gchar *text)
+{
+  gchar *lower;
+  gboolean match = FALSE;
+
+  if (string)
+    {
+      lower = g_ascii_strdown (string, -1);
+      match = g_str_has_prefix (lower, text);
+      g_free (lower);
+    }
+
+  return match;
+}
+
+static gboolean
+match_row (GtkTreeModel *model,
+           GtkTreeIter  *iter,
+           gpointer      data)
+{
+  GtkInspectorObjectTree *wt = data;
+  gchar *type, *name, *label;
+  const gchar *text;
+  gboolean match;
+
+  text = gtk_entry_get_text (GTK_ENTRY (wt->priv->search_entry));
+  gtk_tree_model_get (model, iter,
+                      OBJECT_TYPE, &type,
+                      OBJECT_NAME, &name,
+                      OBJECT_LABEL, &label,
+                      -1);
+
+  match = (match_string (type, text) ||
+           match_string (name, text) ||
+           match_string (label, text));
+
+  g_free (type);
+  g_free (name);
+  g_free (label);
+
+  return match;
+}
+
+static void
+search_mode_changed (GObject                *search_bar,
+                     GParamSpec             *pspec,
+                     GtkInspectorObjectTree *wt)
+{
+  if (!gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (search_bar)))
+    {
+      gtk_tree_walk_reset (wt->priv->walk, NULL);
+      wt->priv->search_length = 0;
+    }
+}
+
+static void
+next_match (GtkButton              *button,
+            GtkInspectorObjectTree *wt)
+{
+  if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar)))
+    {
+      GtkTreeIter iter;
+
+      if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, FALSE, &iter))
+        move_search_to_row (wt, &iter);
+      else
+        gtk_widget_error_bell (GTK_WIDGET (wt));
+    }
+}
+
+static void
+previous_match (GtkButton              *button,
+                GtkInspectorObjectTree *wt)
+{
+  if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar)))
+    {
+      GtkTreeIter iter;
+
+      if (gtk_tree_walk_next_match (wt->priv->walk, TRUE, TRUE, &iter))
+        move_search_to_row (wt, &iter);
+      else
+        gtk_widget_error_bell (GTK_WIDGET (wt));
+    }
+}
+
+static void
 gtk_inspector_object_tree_init (GtkInspectorObjectTree *wt)
 {
   guint signal_id;
@@ -198,6 +419,13 @@ gtk_inspector_object_tree_init (GtkInspectorObjectTree *wt)
                                            (GDestroyNotify) object_data_free);
   gtk_widget_init_template (GTK_WIDGET (wt));
 
+  gtk_search_bar_connect_entry (GTK_SEARCH_BAR (wt->priv->search_bar),
+                                GTK_ENTRY (wt->priv->search_entry));
+
+  g_signal_connect (wt->priv->search_bar, "notify::search-mode-enabled",
+                    G_CALLBACK (search_mode_changed), wt);
+  wt->priv->walk = gtk_tree_walk_new (GTK_TREE_MODEL (wt->priv->model), match_row, wt, NULL);
+
   signal_id = g_signal_lookup ("map", GTK_TYPE_WIDGET);
   wt->priv->map_hook = g_signal_add_emission_hook (signal_id, 0,
                                                    map_or_unmap, wt, NULL);
@@ -221,6 +449,8 @@ gtk_inspector_object_tree_finalize (GObject *object)
   signal_id = g_signal_lookup ("unmap", GTK_TYPE_WIDGET);
   g_signal_remove_emission_hook (signal_id, wt->priv->unmap_hook);
 
+  gtk_tree_walk_free (wt->priv->walk);
+
   G_OBJECT_CLASS (gtk_inspector_object_tree_parent_class)->finalize (object);
 }
 
@@ -253,8 +483,15 @@ gtk_inspector_object_tree_class_init (GtkInspectorObjectTreeClass *klass)
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/inspector/object-tree.ui");
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, model);
   gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, tree);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, object_column);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, search_bar);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorObjectTree, search_entry);
   gtk_widget_class_bind_template_callback (widget_class, on_selection_changed);
   gtk_widget_class_bind_template_callback (widget_class, on_row_activated);
+  gtk_widget_class_bind_template_callback (widget_class, on_hierarchy_changed);
+  gtk_widget_class_bind_template_callback (widget_class, on_search_changed);
+  gtk_widget_class_bind_template_callback (widget_class, next_match);
+  gtk_widget_class_bind_template_callback (widget_class, previous_match);
 }
 
 typedef struct
diff --git a/gtk/inspector/object-tree.ui b/gtk/inspector/object-tree.ui
index 6478e98..98a39c1 100644
--- a/gtk/inspector/object-tree.ui
+++ b/gtk/inspector/object-tree.ui
@@ -13,6 +13,60 @@
   <template class="GtkInspectorObjectTree" parent="GtkBox">
     <property name="visible">True</property>
     <property name="orientation">vertical</property>
+    <signal name="hierarchy-changed" handler="on_hierarchy_changed"/>
+    <child>
+      <object class="GtkSearchBar" id="search_bar">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">horizontal</property>
+            <style>
+              <class name="linked"/>
+            </style>
+            <child>
+              <object class="GtkSearchEntry" id="search_entry">
+                <property name="visible">True</property>
+                <property name="max-width-chars">40</property>
+                <signal name="search-changed" handler="on_search_changed"/>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <signal name="clicked" handler="next_match"/>
+                <style>
+                  <class name="image-button"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="icon-name">go-down-symbolic</property>
+                    <property name="icon-size">1</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <signal name="clicked" handler="previous_match"/>
+                <style>
+                  <class name="image-button"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="icon-name">go-up-symbolic</property>
+                    <property name="icon-size">1</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
     <child>
       <object class="GtkScrolledWindow">
         <property name="visible">True</property>
@@ -23,8 +77,7 @@
           <object class="GtkTreeView" id="tree">
             <property name="visible">True</property>
             <property name="model">model</property>
-            <property name="enable-search">True</property>
-            <property name="search-column">2</property>
+            <property name="enable-search">False</property>
             <signal name="row-activated" handler="on_row_activated"/>
             <child internal-child="selection">
               <object class="GtkTreeSelection">
@@ -33,7 +86,7 @@
               </object>
             </child>
             <child>
-              <object class="GtkTreeViewColumn">
+              <object class="GtkTreeViewColumn" id="object_column">
                 <property name="title" translatable="yes">Object</property>
                 <property name="resizable">True</property>
                 <child>
diff --git a/gtk/inspector/treewalk.c b/gtk/inspector/treewalk.c
new file mode 100644
index 0000000..5280ffc
--- /dev/null
+++ b/gtk/inspector/treewalk.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "treewalk.h"
+
+struct _GtkTreeWalk
+{
+  GtkTreeModel *model;
+  GtkTreeIter position;
+  gboolean visited;
+  RowPredicate predicate;
+  gpointer data;
+  GDestroyNotify destroy;
+};
+
+GtkTreeWalk *
+gtk_tree_walk_new (GtkTreeModel   *model,
+                   RowPredicate    predicate,
+                   gpointer        data,
+                   GDestroyNotify  destroy)
+{
+  GtkTreeWalk *walk;
+
+  walk = g_new (GtkTreeWalk, 1);
+  walk->model = g_object_ref (model);
+  walk->visited = FALSE;
+  walk->predicate = predicate;
+  walk->data = data;
+  walk->destroy = destroy;
+
+  return walk;
+}
+
+void
+gtk_tree_walk_free (GtkTreeWalk *walk)
+{
+  g_object_unref (walk->model);
+
+  if (walk->destroy)
+    walk->destroy (walk->data);
+
+  g_free (walk);
+}
+
+void
+gtk_tree_walk_reset (GtkTreeWalk *walk,
+                     GtkTreeIter *iter)
+{
+  if (iter)
+    {
+      walk->position = *iter;
+      walk->visited = TRUE;
+    }
+  else
+    {
+      walk->visited = FALSE;
+    }
+}
+
+static gboolean
+gtk_tree_walk_step_forward (GtkTreeWalk *walk)
+{
+  GtkTreeIter next, up;
+
+  if (!walk->visited)
+    {
+      if (!gtk_tree_model_get_iter_first (walk->model, &walk->position))
+        return FALSE;
+
+      walk->visited = TRUE;
+      return TRUE;
+    }
+
+  if (gtk_tree_model_iter_children (walk->model, &next, &walk->position))
+    {
+      walk->position = next;
+      return TRUE; 
+    }
+
+  next = walk->position;
+  do
+    {
+      up = next;
+      if (gtk_tree_model_iter_next (walk->model, &next))
+        {
+          walk->position = next;
+          return TRUE; 
+        }
+    }
+  while (gtk_tree_model_iter_parent (walk->model, &next, &up));
+
+  return FALSE;
+}
+
+static gboolean
+gtk_tree_model_iter_last_child (GtkTreeModel *model,
+                                GtkTreeIter  *iter,
+                                GtkTreeIter  *parent)
+{
+  GtkTreeIter next;
+
+  if (!gtk_tree_model_iter_children (model, &next, parent))
+    return FALSE;
+
+  do 
+    *iter = next;
+  while (gtk_tree_model_iter_next (model, &next));
+
+  return TRUE;
+}
+
+static gboolean
+gtk_tree_model_get_iter_last (GtkTreeModel *model,
+                              GtkTreeIter  *iter)
+{
+  GtkTreeIter next;
+
+  if (!gtk_tree_model_iter_last_child (model, &next, NULL))
+    return FALSE;
+
+  do
+    *iter = next;
+  while (gtk_tree_model_iter_last_child (model, &next, &next));
+
+  return TRUE;
+}
+
+static gboolean
+gtk_tree_walk_step_back (GtkTreeWalk *walk)
+{
+  GtkTreeIter previous, down;
+
+  if (!walk->visited)
+    {
+      if (!gtk_tree_model_get_iter_last (walk->model, &walk->position))
+        return FALSE;
+
+      walk->visited = TRUE;
+      return TRUE;
+    }
+
+  previous = walk->position;
+  if (gtk_tree_model_iter_previous (walk->model, &previous))
+    {
+      while (gtk_tree_model_iter_last_child (walk->model, &down, &previous))
+        previous = down;
+
+      walk->position = previous;
+      return TRUE; 
+    }
+
+  if (gtk_tree_model_iter_parent (walk->model, &previous, &walk->position))
+    {
+      walk->position = previous;
+      return TRUE; 
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_tree_walk_step (GtkTreeWalk *walk, gboolean backwards)
+{
+  if (backwards)
+    return gtk_tree_walk_step_back (walk);
+  else
+    return gtk_tree_walk_step_forward (walk);
+}
+
+static gboolean
+row_is_match (GtkTreeWalk *walk)
+{
+  if (walk->predicate)
+    return walk->predicate (walk->model, &walk->position, walk->data);
+  return TRUE;
+}
+
+gboolean 
+gtk_tree_walk_next_match (GtkTreeWalk *walk,
+                          gboolean     force_move,
+                          gboolean     backwards,
+                          GtkTreeIter *iter)
+{
+  gboolean moved = FALSE;
+  gboolean was_visited;
+  GtkTreeIter position;
+
+  was_visited = walk->visited;
+  position = walk->position;
+
+  do 
+    {
+      if (moved || (!force_move && walk->visited))
+        {
+          if (row_is_match (walk))
+            {
+              *iter = walk->position;
+              return TRUE;
+            }
+        }
+      moved = TRUE;
+    }
+  while (gtk_tree_walk_step (walk, backwards));
+
+  walk->visited = was_visited;
+  walk->position = position;
+
+  return FALSE;
+}
diff --git a/gtk/inspector/treewalk.h b/gtk/inspector/treewalk.h
new file mode 100644
index 0000000..57f885a
--- /dev/null
+++ b/gtk/inspector/treewalk.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+//#include "gtktreemodel.h"
+
+
+typedef gboolean (*RowPredicate) (GtkTreeModel *model,
+                                  GtkTreeIter  *iter,
+                                  gpointer      data);
+
+typedef struct _GtkTreeWalk GtkTreeWalk;
+
+GtkTreeWalk * gtk_tree_walk_new        (GtkTreeModel   *model,
+                                        RowPredicate    predicate,
+                                        gpointer        data,
+                                        GDestroyNotify  destroy);
+
+void          gtk_tree_walk_free       (GtkTreeWalk *walk);
+
+void          gtk_tree_walk_reset      (GtkTreeWalk *walk,
+                                        GtkTreeIter *iter);
+
+gboolean      gtk_tree_walk_next_match (GtkTreeWalk *walk,
+                                        gboolean     force_move,
+                                        gboolean     backwards,
+                                        GtkTreeIter *iter);


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