Re: TreeView search dialog -- new patch (take 2)



On 16 Jul 2001, Jonathan Blandford wrote:

> Kristian Rietveld <kristian planet nl> writes:
>
> > Hi,
> >
> > This is a new patch which adds the interactive search code to the
> > TreeView. New is the 'configurable search dialog position function' ;)
> > (featuring long and ugly prototypes ...), besides that, nothing has been
> > changed. I think this will be my latest patch for a long time, because I'm
> > leaving Saturday for three weeks (holiday ;).
> >
> > As always, suggestions/comments/etc are welcome.
>
> Looks extremely cool.  It basically works, though there are a few minor
> bugs.  To be specific:
>
>  * You should popdown on focus out.
>  * The first letter doesn't seem to work. (ie, typing N doesn't do
>    anything on testtreefocus, for example.)
>  * It seems to interfere with keybindings.  For example, C-n doesn't go
>    to the next line, it starts typing n.
>  * I think activating the entry should move the cursor to the current
>    highlighted location.

All fixed now, see attached patch.

regards,


	Kris

>
> But these can all be fixed.  It really helps the usability of the tree
> too.
>
> Thanks,
> -Jonathan
>

-- 
Odi et amo. Quare id faciam, fortasse requiris?
	Nescio, sed fieri sentio et excrucior.

-Catullus (Carmen 85)
Index: gtk+/gtk/gtktreeview.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreeview.c,v
retrieving revision 1.110
diff -u -r1.110 gtktreeview.c
--- gtk+/gtk/gtktreeview.c	2001/06/30 21:15:27	1.110
+++ gtk+/gtk/gtktreeview.c	2001/07/16 19:58:26
@@ -32,7 +32,10 @@
 #include "gtkarrow.h"
 #include "gtkintl.h"
 #include "gtkbindings.h"
+#include "gtkcontainer.h"
+#include "gtkentry.h"
 
+#include <string.h>
 #include <gdk/gdkkeysyms.h>
 
 #if defined (GDK_WINDOWING_X11)
@@ -344,7 +347,35 @@
 					   GtkTreePath     *path,
 					   gboolean         clear_and_select);
 
+/* interactive search */
+static void     gtk_tree_view_interactive_search_dialog_destroy     (GtkWidget        *search_dialog,
+								     GtkTreeView      *tree_view);
+static void     gtk_tree_view_interactive_search_position_func      (GtkTreeView      *tree_view,
+								     GtkWidget        *search_dialog);
+static gboolean gtk_tree_view_interactive_search_delete_event       (GtkWidget        *widget,
+								     GdkEventAny      *event,
+								     GtkTreeView      *tree_view);
+static gboolean gtk_tree_view_interactive_search_button_press_event (GtkWidget        *widget,
+								     GdkEventButton   *event,
+								     GtkTreeView      *tree_view);
+static gboolean gtk_tree_view_interactive_search_key_press_event    (GtkWidget        *entry,
+								     GdkEventKey      *event,
+								     GtkTreeView      *tree_view);
+static void gtk_tree_view_interactive_search_search_move            (GtkWidget        *window,
+								     GtkTreeView      *tree_view,
+								     gboolean          up);
+static gboolean gtk_tree_view_interactive_search_search_iter        (GtkTreeModel     *model,
+								     GtkTreeSelection *selection,
+								     GtkTreeIter      *iter,
+								     gchar            *text,
+								     gint             *count,
+								     gint              n);
+static void gtk_tree_view_interactive_search_init                   (GtkWidget        *entry,
+								     GtkTreeView      *tree_view);
+static void gtk_tree_view_interactive_search                        (GtkTreeView      *tree_view,
+								     gchar             first_char);
 
+
 static GtkContainerClass *parent_class = NULL;
 static guint tree_view_signals[LAST_SIGNAL] = { 0 };
 
@@ -835,6 +866,8 @@
   gtk_tree_view_set_adjustments (tree_view, NULL, NULL);
   tree_view->priv->selection = _gtk_tree_selection_new_with_tree_view (tree_view);
   _gtk_tree_view_update_size (tree_view);
+  tree_view->priv->search_column = 0;
+  tree_view->priv->search_dialog_position_func = gtk_tree_view_interactive_search_position_func;
 }
 
 
@@ -958,7 +991,7 @@
       tree_view->priv->model = NULL;
     }
 
-  if (tree_view->priv->columns != NULL)
+ if (tree_view->priv->columns != NULL)
     {
       for (list = tree_view->priv->columns; list; list = list->next)
 	g_object_unref (G_OBJECT (list->data));
@@ -2777,6 +2810,14 @@
 	}
       return TRUE;
     }
+
+  if (event->keyval >= GDK_A && event->keyval <= GDK_z
+      && event->state != GDK_CONTROL_MASK)
+    {
+      gtk_tree_view_interactive_search (tree_view, event->keyval);
+      return TRUE;
+    }
+
   return (* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event);
 }
 
@@ -2833,6 +2874,7 @@
 gtk_tree_view_leave_notify (GtkWidget        *widget,
 			    GdkEventCrossing *event)
 {
+  GtkWidget   *search_dialog;
   GtkTreeView *tree_view;
 
   g_return_val_if_fail (GTK_IS_TREE_VIEW (widget), FALSE);
@@ -2846,6 +2888,12 @@
                                    NULL);
 
   ensure_unprelighted (tree_view);
+  
+  /* destroy interactive search dialog */
+  search_dialog = gtk_object_get_data (GTK_OBJECT (widget), "search-dialog");
+  if (search_dialog)
+    gtk_tree_view_interactive_search_dialog_destroy (search_dialog,
+						     tree_view);
 
   return TRUE;
 }
@@ -2882,6 +2930,7 @@
   gtk_widget_queue_draw (widget);
   GTK_TREE_VIEW (widget)->priv->in_extended_selection = FALSE;
   GTK_TREE_VIEW (widget)->priv->in_free_motion = FALSE;
+
   return FALSE;
 }
 
@@ -8214,4 +8263,451 @@
   tree_view->priv->destroy_count_func = func;
   tree_view->priv->destroy_count_data = data;
   tree_view->priv->destroy_count_destroy = destroy;
+}
+
+
+/* interactive search 
+ */
+
+/**
+ * gtk_tree_view_interactive_search_get_search_column:
+ * @tree_view: A #GtkTreeView
+ *
+ * Gets the column searched on by the interactive search code.
+ *
+ * Return value: the column the interactive search code searches in.
+ */
+gint
+gtk_tree_view_interactive_search_get_search_column (GtkTreeView *tree_view)
+{
+  g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), 0);
+  
+  return (tree_view->priv->search_column);
+}
+
+/**
+ * gtk_tree_view_interactive_search_set_search_column:
+ * @tree_view: A #GtkTreeView
+ * @column: the column to search in
+ *
+ * Sets @column as the column where the interactive search code should search in.
+ */
+void
+gtk_tree_view_interactive_search_set_search_column (GtkTreeView *tree_view,
+						    gint         column)
+{
+  g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+  g_return_if_fail (column >= 0);
+
+  tree_view->priv->search_column = column;
+}
+
+/**
+ * gtk_tree_view_interactive_search_get_dialog_position_func:
+ * @tree_view: A #GtkTreeView
+ *
+ * Returns the position function currently in use.
+ *
+ * Return value: the currently used position function for the search dialog.
+ */
+GtkTreeViewSearchDialogPositionFunc
+gtk_tree_view_interactive_search_get_dialog_position_func (GtkTreeView *tree_view)
+{
+  g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), NULL);
+
+  return tree_view->priv->search_dialog_position_func;
+}
+
+/**
+ * gtk_tree_view_interactive_search_set_dialog_position_func:
+ * @tree_view: A #GtkTreeView
+ * @position_func: the position function to use for the search dialog
+ *
+ * Sets the position function to use for positioning the search dialog. See
+ * gtktreeview.c:gtk_tree_view_interactive_search_position_dialog() for an
+ * example of such a position function.
+ */
+void
+gtk_tree_view_interactive_search_set_dialog_position_func (GtkTreeView *tree_view,
+							   GtkTreeViewSearchDialogPositionFunc position_func)
+{
+  g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+  g_return_if_fail (position_func != NULL);
+
+  tree_view->priv->search_dialog_position_func = position_func;
+}
+
+/**
+ * gtk_tree_view_interactive_search_dialog_reposition:
+ * @tree_view: A #GtkTreeView
+ *
+ * Repositions the search dialog. Call this function after you set the
+ * position function with gtk_tree_view_interactive_search_set_dialog_position_func(),
+ * so the changes take effect.
+ */
+void
+gtk_tree_view_interactive_search_dialog_reposition (GtkTreeView *tree_view)
+{
+  GtkWidget *window;
+
+  g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+  
+  window = gtk_object_get_data (GTK_OBJECT (tree_view), "search-dialog");
+  
+  tree_view->priv->search_dialog_position_func (tree_view, window);
+}
+
+static void
+gtk_tree_view_interactive_search_dialog_destroy (GtkWidget   *search_dialog,
+						 GtkTreeView *tree_view)
+{
+  /* remove data from tree_view */
+  gtk_object_remove_data (GTK_OBJECT (tree_view), "search-dialog");
+  
+  gtk_widget_destroy (search_dialog);
+}
+
+static void
+gtk_tree_view_interactive_search_position_func (GtkTreeView *tree_view,
+						GtkWidget   *search_dialog)
+{
+  gint ox, oy;
+  gint width, height;
+  gint pwidth, pheight;
+  GdkWindow *parent_window;
+  
+  /* put window in the lower right corner */
+  parent_window = gtk_widget_get_parent_window (GTK_WIDGET (tree_view));
+  gdk_window_get_origin (parent_window, &ox, &oy);
+  gdk_window_get_geometry (parent_window, NULL, NULL, &pwidth, &pheight, NULL);
+  gdk_window_get_geometry (search_dialog->window, NULL, NULL, &width, &height,
+			   NULL);
+  gdk_window_move (search_dialog->window, ox + pwidth - width, oy + pheight);
+}
+
+static void
+gtk_tree_view_interactive_search (GtkTreeView *tree_view,
+				  gchar        first_char)
+{
+  gchar hack[2] = {first_char, 0};
+  GtkWidget *window;
+  GtkWidget *entry;
+  
+  g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+
+  /* set up window */
+  window = gtk_window_new (GTK_WINDOW_POPUP);
+  gtk_window_set_title (GTK_WINDOW (window), "search dialog");
+  gtk_container_set_border_width (GTK_CONTAINER (window), 3);
+  gtk_window_set_modal (GTK_WINDOW (window), TRUE);
+  gtk_signal_connect 
+    (GTK_OBJECT (window), "delete_event",
+     GTK_SIGNAL_FUNC (gtk_tree_view_interactive_search_delete_event),
+     tree_view);
+  gtk_signal_connect 
+    (GTK_OBJECT (window), "key_press_event",
+     GTK_SIGNAL_FUNC (gtk_tree_view_interactive_search_key_press_event),
+     tree_view);
+  gtk_signal_connect
+    (GTK_OBJECT (window), "button_press_event",
+     GTK_SIGNAL_FUNC (gtk_tree_view_interactive_search_button_press_event),
+     tree_view);
+
+  /* add entry */
+  entry = gtk_entry_new ();
+  gtk_entry_append_text (GTK_ENTRY (entry), hack);
+  gtk_editable_set_position (GTK_EDITABLE (entry), 2);
+  gtk_signal_connect
+    (GTK_OBJECT (entry), "changed",
+     GTK_SIGNAL_FUNC (gtk_tree_view_interactive_search_init),
+     tree_view);
+  gtk_container_add (GTK_CONTAINER (window), entry);
+
+  /* done, show it */
+  gtk_widget_show_all (window);
+  gtk_widget_grab_focus (entry);
+
+  /* position window */
+  tree_view->priv->search_dialog_position_func (tree_view, window);
+
+  gtk_object_set_data (GTK_OBJECT (window), "text",
+                       gtk_entry_get_text (GTK_ENTRY (entry)));
+  gtk_object_set_data (GTK_OBJECT (tree_view), "search-dialog", window);
+
+  /* search first matching iter */
+  gtk_tree_view_interactive_search_init (entry, tree_view);
+}
+
+static gboolean
+gtk_tree_view_interactive_search_delete_event (GtkWidget *widget,
+					       GdkEventAny *event,
+					       GtkTreeView *tree_view)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+  gtk_tree_view_interactive_search_dialog_destroy (widget, tree_view);
+  
+  return TRUE;
+}
+
+static gboolean
+gtk_tree_view_interactive_search_button_press_event (GtkWidget *widget,
+                                                     GdkEventButton *event,
+						     GtkTreeView *tree_view)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+  gtk_tree_view_interactive_search_dialog_destroy (widget, tree_view);
+  
+  return TRUE;
+}
+
+static gboolean
+gtk_tree_view_interactive_search_key_press_event (GtkWidget *widget,
+                                                  GdkEventKey *event,
+                                                  GtkTreeView *tree_view)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+  g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE);
+  
+  /* close window */
+  if (event->keyval == GDK_Escape || event->keyval == GDK_Return
+      || event->keyval == GDK_Tab)
+    {
+      gtk_tree_view_interactive_search_dialog_destroy (widget, tree_view);
+      return TRUE;
+    }
+
+  /* select previous matching iter */
+  if (event->keyval == GDK_Up)
+    {
+      gtk_tree_view_interactive_search_search_move (widget,
+                                                    tree_view,
+                                                    TRUE);
+      return TRUE;
+    }
+
+  /* select next matching iter */
+  if (event->keyval == GDK_Down)
+    {
+      gtk_tree_view_interactive_search_search_move (widget,
+                                                    tree_view,
+                                                    FALSE);
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static void
+gtk_tree_view_interactive_search_search_move (GtkWidget        *window,
+                                              GtkTreeView      *tree_view,
+                                              gboolean          up)
+{
+  gboolean ret;
+  gint *selected_iter;
+  gint len;
+  gint count = 0;
+  gchar *text;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  GtkTreeSelection *selection;
+
+  text = gtk_object_get_data (GTK_OBJECT (window), "text");
+  selected_iter = gtk_object_get_data (GTK_OBJECT (window), "selected-iter");
+
+  g_return_if_fail (text != NULL);
+
+  if (!selected_iter || (up && *selected_iter == 1))
+    return;
+
+  len = strlen (text);
+  
+  if (len < 1)
+    return;
+
+  model = gtk_tree_view_get_model (tree_view);
+  selection = gtk_tree_view_get_selection (tree_view);
+  
+  /* search */
+  gtk_tree_selection_unselect_all (selection);
+  gtk_tree_model_get_iter_root (model, &iter);
+  
+  ret = gtk_tree_view_interactive_search_search_iter
+    (model,
+     selection,
+     &iter,
+     text,
+     &count,
+     up?((*selected_iter) - 1):((*selected_iter + 1)));
+  
+  if (ret)
+    /* found */
+    *selected_iter += up?(-1):(1);
+  else
+    {
+      /* return to old iter */
+      count = 0;
+      gtk_tree_model_get_iter_root (model, &iter);
+      gtk_tree_view_interactive_search_search_iter (model,
+                                                    selection,
+                                                    &iter,
+                                                    text,
+                                                    &count,
+                                                    *selected_iter);
+
+      /* beep? */
+    }
+}
+
+static gboolean
+gtk_tree_view_interactive_search_search_iter (GtkTreeModel     *model,
+                                              GtkTreeSelection *selection,
+                                              GtkTreeIter      *iter,
+                                              gchar            *text,
+                                              gint             *count,
+                                              gint              n)
+{
+  gint len = strlen (text);
+  gchar *tmp;
+  GValue value = {0,};
+  GtkTreeView *tree_view = gtk_tree_selection_get_tree_view (selection);
+  GtkTreeViewColumn *column =
+    gtk_tree_view_get_column (tree_view, tree_view->priv->search_column);
+  
+  gtk_tree_model_get_value (model, iter, tree_view->priv->search_column,
+			    &value);
+  tmp = g_strdup (g_value_get_string (&value));
+  if (!strncmp (text, tmp, len))
+    {
+      (*count)++;
+      
+      if (*count == n)
+        {
+          GtkTreePath *path;
+          
+          gtk_tree_selection_select_iter (selection, iter);
+          
+          path = gtk_tree_model_get_path (model, iter);
+	  gtk_tree_view_scroll_to_cell (tree_view,
+					path, column, 0.5, 0.5);
+          gtk_tree_path_free (path);
+          
+          return TRUE;
+        }
+    }
+  g_value_unset (&value);
+  
+  if (gtk_tree_model_iter_has_child (model, iter))
+    {
+      gboolean ret;
+      GtkTreeIter child;
+      
+      gtk_tree_model_iter_children (model, &child, iter);
+      ret = gtk_tree_view_interactive_search_search_iter (model,
+                                                          selection,
+                                                          &child,
+                                                          text,
+                                                          count,
+                                                          n);
+      
+      if (ret)
+        return TRUE; /* iter found and selected */
+    }
+  
+  while (gtk_tree_model_iter_next (model, iter))
+    {
+      gtk_tree_model_get_value (model, iter, tree_view->priv->search_column,
+				&value);
+      tmp = g_value_get_string (&value);
+      if (!strncmp (text, tmp, len))
+        {
+          (*count)++;
+          if (*count == n)
+            {
+              GtkTreePath *path;
+              
+              gtk_tree_selection_select_iter (selection, iter);
+              
+              path = gtk_tree_model_get_path (model, iter);
+              gtk_tree_view_scroll_to_cell (tree_view,
+                                            path, column, 0.5, 0.5);
+              gtk_tree_path_free (path);
+              
+              return TRUE;
+            }
+        }
+      g_value_unset (&value);
+      
+      if (gtk_tree_model_iter_has_child (model, iter))
+        {
+          gboolean ret;
+          GtkTreeIter child;
+          
+          gtk_tree_model_iter_children (model, &child, iter);
+          ret = gtk_tree_view_interactive_search_search_iter (model,
+                                                              selection,
+                                                              &child,
+                                                              text,
+                                                              count,
+                                                              n);
+
+          if (ret)
+            return TRUE; /* iter found and selected */
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+gtk_tree_view_interactive_search_init (GtkWidget   *entry,
+                                       GtkTreeView *tree_view)
+{
+  gint ret;
+  gint *selected_iter;
+  gint len;
+  gint count = 0;
+  gchar *text;
+  GtkWidget *window;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  GtkTreeSelection *selection;
+  
+  g_return_if_fail (GTK_IS_ENTRY (entry));
+  g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+  
+  window = gtk_widget_get_parent (entry);
+  text = gtk_entry_get_text (GTK_ENTRY (entry));
+  len = strlen (text);
+  model = gtk_tree_view_get_model (tree_view);
+  selection = gtk_tree_view_get_selection (tree_view);
+
+  /* search */
+  gtk_tree_selection_unselect_all (selection);
+  selected_iter = gtk_object_get_data (GTK_OBJECT (window), "selected-iter");
+  if (selected_iter)
+    g_free (selected_iter);
+  gtk_object_remove_data (GTK_OBJECT (window), "selected-iter");
+
+  if (len < 1)
+    return;
+  
+  gtk_tree_model_get_iter_root (model, &iter);
+  
+  ret = gtk_tree_view_interactive_search_search_iter (model,
+                                                      selection,
+                                                      &iter,
+                                                      text,
+                                                      &count,
+                                                      1);
+
+  if (ret) 
+    {
+      selected_iter = g_malloc (sizeof (int));
+      *selected_iter = 1;
+      gtk_object_set_data (GTK_OBJECT (window), "selected-iter",
+                           selected_iter);
+    }
+  
+  return;
 }
Index: gtk+/gtk/gtktreeview.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreeview.h,v
retrieving revision 1.33
diff -u -r1.33 gtktreeview.h
--- gtk+/gtk/gtktreeview.h	2001/06/29 03:11:01	1.33
+++ gtk+/gtk/gtktreeview.h	2001/07/16 19:58:27
@@ -19,6 +19,7 @@
 #ifndef __GTK_TREE_VIEW_H__
 #define __GTK_TREE_VIEW_H__
 
+#include <gtk/gtkwidget.h>
 #include <gtk/gtkcontainer.h>
 #include <gtk/gtktreemodel.h>
 #include <gtk/gtktreeviewcolumn.h>
@@ -119,6 +120,8 @@
 						GtkTreePath             *path,
 						GtkTreeViewDropPosition *pos,
 						gpointer                 user_data);
+typedef void (*GtkTreeViewSearchDialogPositionFunc) (GtkTreeView        *tree_view,
+						     GtkWidget          *search_dialog);
 
 
 /* Creators */
@@ -274,6 +277,15 @@
 								    GtkTreeViewDropPosition   *pos);
 GdkPixmap             *gtk_tree_view_create_row_drag_icon          (GtkTreeView               *tree_view,
 								    GtkTreePath               *path);
+
+/* Interactive search */
+gint                   gtk_tree_view_interactive_search_get_search_column (GtkTreeView        *tree_view);
+void                   gtk_tree_view_interactive_search_set_search_column (GtkTreeView        *tree_view,
+									   gint                column);
+GtkTreeViewSearchDialogPositionFunc gtk_tree_view_interactive_search_get_dialog_position_func (GtkTreeView *tree_view);
+void                   gtk_tree_view_interactive_search_set_dialog_position_func (GtkTreeView                         *tree_view,
+										  GtkTreeViewSearchDialogPositionFunc  position_func);
+void                   gtk_tree_view_interactive_search_dialog_reposition (GtkTreeView *tree_view);
 
 
 /* This function should really never be used.  It is just for use by ATK.
Index: gtk+/gtk/gtktreeprivate.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreeprivate.h,v
retrieving revision 1.26
diff -u -r1.26 gtktreeprivate.h
--- gtk+/gtk/gtktreeprivate.h	2001/06/29 03:11:01	1.26
+++ gtk+/gtk/gtktreeprivate.h	2001/07/16 19:58:27
@@ -166,6 +166,10 @@
 
   guint in_extended_selection : 1;
   guint in_free_motion : 1;
+
+  /* interactive search */
+  gint search_column;
+  GtkTreeViewSearchDialogPositionFunc search_dialog_position_func;
 };
 
 #ifdef __GNUC__


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