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

gEDA-user: command autocomplete (gtk)



I have thrown together a prototype of a gtk autocomplete
feature for the pcb command line. Right now it ignores
Tab/Up/Down since I am unsure how to manipulate the TreeView
used for the suggestion list.

The patch is as follows. Suggestions and comments are welcome.


diff --git a/src/hid/common/actions.c b/src/hid/common/actions.c
index 43c50f8..f511cc4 100644
--- a/src/hid/common/actions.c
+++ b/src/hid/common/actions.c
@@ -37,6 +37,19 @@ check_action_name (const char *s)
   return NULL;
 }
 
+/* Accessors needed by autocomplete list */
+int
+get_n_actions (void)
+{
+  return n_actions;
+}
+
+HID_Action **
+get_all_actions (void)
+{
+  return all_actions;
+}
+
 void
 hid_register_actions (HID_Action * a, int n)
 {
diff --git a/src/hid/common/actions.h b/src/hid/common/actions.h
index 1cc6f24..a90d923 100644
--- a/src/hid/common/actions.h
+++ b/src/hid/common/actions.h
@@ -3,5 +3,7 @@
 #define __HID_ACTIONS_INCLUDED__
 
 void print_actions (void);
+HID_Action **get_all_actions (void);
+int get_n_actions (void);
 
 #endif
diff --git a/src/hid/gtk/gui-command-window.c b/src/hid/gtk/gui-command-window.c
index b08836e..dbfa9b6 100644
--- a/src/hid/gtk/gui-command-window.c
+++ b/src/hid/gtk/gui-command-window.c
@@ -35,6 +35,7 @@
 #include "gui.h"
 #include <gdk/gdkkeysyms.h>
 
+#include "hid/common/actions.h"
 #include "command.h"
 #include "crosshair.h"
 
@@ -46,10 +47,14 @@ RCSID ("$Id$");
 
 static GtkWidget *command_window;
 static GtkWidget *combo_vbox;
-static GList *history_list;
 static gchar *command_entered;
 static GMainLoop *loop;
 
+#define AUTO_MAX_HEIGHT	250
+static GtkListStore *auto_complete;
+static GtkTreePath *auto_treepath;
+static gint auto_row_height;
+
 
 /* gui-command-window.c provides two interfaces for getting user input
 |  for executing a command.
@@ -135,61 +140,44 @@ static gchar *command_ref_text[] = {
   N_("\tLoad a vendor file.  If 'filename' omitted, pop up file select dialog.\n"),
 };
 
-
-  /* Put an allocated string on the history list and combo text list
-     |  if it is not a duplicate.  The history_list is just a shadow of the
-     |  combo list, but I think is needed because I don't see an api for reading
-     |  the combo strings.  The combo box strings take "const gchar *", so the
-     |  same allocated string can go in both the history list and the combo list.
-     |  If removed from both lists, a string can be freed.
-   */
-static void
-command_history_add (gchar * cmd)
+static GtkTreeModel *
+create_autocomplete_model (void)
 {
-  GList *list;
-  gchar *s;
-  gint i;
-
-  if (!cmd || !*cmd)
-    return;
+  auto_complete = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+  return GTK_TREE_MODEL (auto_complete);
+}
 
-  /* Check for a duplicate command.  If found, move it to the
-     |  top of the list and similarly modify the combo box strings.
-   */
-  for (i = 0, list = history_list; list; list = list->next, ++i)
-    {
-      s = (gchar *) list->data;
-      if (!strcmp (cmd, s))
-	{
-	  history_list = g_list_remove (history_list, s);
-	  history_list = g_list_prepend (history_list, s);
-	  gtk_combo_box_remove_text (GTK_COMBO_BOX
-				     (ghidgui->command_combo_box), i);
-	  gtk_combo_box_prepend_text (GTK_COMBO_BOX
-				      (ghidgui->command_combo_box), s);
-	  return;
-	}
-    }
+/* Populates the autocomplete model with all actions 
+ * that start with prefix; returns the number of actions
+ * added. */
+static int
+populate_autocomplete_model (const char *prefix)
+{
+  int n = get_n_actions ();
+  HID_Action **actions = get_all_actions ();
+  size_t plen = strlen (prefix);
+  GtkTreeIter iter;
+  int i;
+  int rv = 0;
 
-  /* Not a duplicate, so put first in history list and combo box text list.
-   */
-  s = g_strdup (cmd);
-  history_list = g_list_prepend (history_list, s);
-  gtk_combo_box_prepend_text (GTK_COMBO_BOX (ghidgui->command_combo_box), s);
+  auto_treepath = NULL;
 
-  /* And keep the lists trimmed!
-   */
-  if (g_list_length (history_list) > ghidgui->history_size)
+  gtk_list_store_clear (auto_complete);
+  for (i = 0; i < n; ++i)
     {
-      s = (gchar *) g_list_nth_data (history_list, ghidgui->history_size);
-      history_list = g_list_remove (history_list, s);
-      gtk_combo_box_remove_text (GTK_COMBO_BOX (ghidgui->command_combo_box),
-				 ghidgui->history_size);
-      g_free (s);
+      if (strncasecmp (prefix, actions[i]->name, plen) == 0)
+        {
+          gtk_list_store_append (auto_complete, &iter);
+          gtk_list_store_set (auto_complete, &iter,
+                              0, actions[i]->name, 
+                              1, actions[i]->description,
+                              -1);
+          ++rv;
+        }
     }
+  return rv;
 }
 
-
   /* Called when user hits "Enter" key in command entry.  The action to take
      |  depends on where the combo box is.  If it's in the command window, we can
      |  immediately execute the command and carry on.  If it's in the status
@@ -202,12 +190,8 @@ command_entry_activate_cb (GtkWidget * widget, gpointer data)
 {
   gchar *command;
 
-  command =
-    g_strdup (ghid_entry_get_text (GTK_WIDGET (ghidgui->command_entry)));
-  gtk_entry_set_text (ghidgui->command_entry, "");
-
-  if (*command)
-    command_history_add (command);
+  command = g_strdup (ghid_entry_get_text (ghidgui->command_entry));
+  gtk_entry_set_text (GTK_ENTRY (ghidgui->command_entry), "");
 
   if (ghidgui->use_command_window)
     {
@@ -222,29 +206,58 @@ command_entry_activate_cb (GtkWidget * widget, gpointer data)
     }
 }
 
-  /* Create the command_combo_box.  Called once, either by
+  /* Create the command_entry.  Called once, either by
      |  ghid_command_window_show() or ghid_command_entry_get().  Then as long as
-     |  ghidgui->use_command_window is TRUE, the command_combo_box will live
+     |  ghidgui->use_command_window is TRUE, the command_enty will live
      |  in a command window vbox or float if the command window is not up.
-     |  But if ghidgui->use_command_window is FALSE, the command_combo_box
+     |  But if ghidgui->use_command_window is FALSE, the command_entry
      |  will live in the status_line_hbox either shown or hidden. 
-     |  Since it's never destroyed, the combo history strings never need
-     |  rebuilding and history is maintained if the combo box location is moved.
    */
 static void
-command_combo_box_entry_create (void)
+command_text_entry_create (void)
 {
-  ghidgui->command_combo_box = gtk_combo_box_entry_new_text ();
-  ghidgui->command_entry =
-    GTK_ENTRY (GTK_BIN (ghidgui->command_combo_box)->child);
+  GtkWidget *scroll;
+  GtkAdjustment *hadj, *vadj;
+  GtkCellRenderer *renderer1 = gtk_cell_renderer_text_new ();
+  GtkCellRenderer *renderer2 = gtk_cell_renderer_text_new ();
+
+  /* Build textbox and autocomplete popup */
+  ghidgui->command_entry = gtk_entry_new ();
+  ghidgui->command_hist_window = gtk_window_new (GTK_WINDOW_POPUP);
+  ghidgui->command_hist_tree_view = gtk_tree_view_new ();
+
+  hadj = gtk_tree_view_get_hadjustment (GTK_TREE_VIEW (ghidgui->command_hist_tree_view));
+  vadj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (ghidgui->command_hist_tree_view));
+  scroll = gtk_scrolled_window_new (hadj, vadj);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+
+  gtk_container_add (GTK_CONTAINER (ghidgui->command_hist_window), scroll);
+  gtk_container_add (GTK_CONTAINER (scroll), ghidgui->command_hist_tree_view);
+
+  /* Configure treeview for minimalist look */
+  gtk_cell_renderer_set_padding (renderer2, 15, 0);
+  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ghidgui->command_hist_tree_view),
+                                     FALSE);
+  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (ghidgui->command_hist_tree_view),
+                                               -1, "", renderer1, "text", 0, NULL);
+  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (ghidgui->command_hist_tree_view),
+                                               -1, "", renderer2, "text", 1, NULL);
+  gtk_cell_renderer_get_size (renderer1, ghidgui->command_hist_tree_view,
+                              NULL, NULL, NULL, NULL, &auto_row_height);
 
-  gtk_entry_set_width_chars (ghidgui->command_entry, 40);
-  gtk_entry_set_activates_default (ghidgui->command_entry, TRUE);
+/*
+  gtk_entry_set_has_frame (GTK_ENTRY (ghidgui->command_entry), FALSE);
+*/
+  gtk_entry_set_activates_default (GTK_ENTRY (ghidgui->command_entry), TRUE);
+
+  /* Hookup autocomplete model */
+  gtk_tree_view_set_model (GTK_TREE_VIEW (ghidgui->command_hist_tree_view),
+                           create_autocomplete_model());
 
   g_signal_connect (G_OBJECT (ghidgui->command_entry), "activate",
 		    G_CALLBACK (command_entry_activate_cb), NULL);
 
-  g_object_ref (G_OBJECT (ghidgui->command_combo_box));	/* so can move it */
+  g_object_ref (G_OBJECT (ghidgui->command_entry));	/* so can move it */
 }
 
 static void
@@ -253,7 +266,7 @@ command_window_close_cb (void)
   if (command_window)
     {
       gtk_container_remove (GTK_CONTAINER (combo_vbox),	/* Float it */
-			    ghidgui->command_combo_box);
+			    ghidgui->command_entry);
       gtk_widget_destroy (command_window);
     }
   combo_vbox = NULL;
@@ -267,7 +280,7 @@ command_destroy_cb (GtkWidget * widget, gpointer data)
 }
 
   /* If ghidgui->use_command_window toggles, the config code calls
-     |  this to ensure the command_combo_box is set up for living in the
+     |  this to ensure the command_entry is set up for living in the
      |  right place.
    */
 void
@@ -276,22 +289,22 @@ ghid_command_use_command_window_sync (void)
   /* The combo box will be NULL and not living anywhere until the
      |  first command entry.
    */
-  if (!ghidgui->command_combo_box)
+  if (!ghidgui->command_entry)
     return;
 
   if (ghidgui->use_command_window)
     gtk_container_remove (GTK_CONTAINER (ghidgui->status_line_hbox),
-			  ghidgui->command_combo_box);
+			  ghidgui->command_entry);
   else
     {
-      /* Destroy the window (if it's up) which floats the command_combo_box
+      /* Destroy the window (if it's up) which floats the command_entry
          |  so we can pack it back into the status line hbox.  If the window
-         |  wasn't up, the command_combo_box was already floating.
+         |  wasn't up, the command_entry was already floating.
        */
       command_window_close_cb ();
-      gtk_widget_hide (ghidgui->command_combo_box);
+      gtk_widget_hide (ghidgui->command_entry);
       gtk_box_pack_start (GTK_BOX (ghidgui->status_line_hbox),
-			  ghidgui->command_combo_box, FALSE, FALSE, 0);
+			  ghidgui->command_entry, FALSE, FALSE, 5);
     }
 }
 
@@ -321,10 +334,10 @@ ghid_command_window_show (gboolean raise)
   gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
   gtk_container_add (GTK_CONTAINER (command_window), vbox);
 
-  if (!ghidgui->command_combo_box)
-    command_combo_box_entry_create ();
+  if (!ghidgui->command_entry)
+    command_text_entry_create ();
 
-  gtk_box_pack_start (GTK_BOX (vbox), ghidgui->command_combo_box,
+  gtk_box_pack_start (GTK_BOX (vbox), ghidgui->command_entry,
 		      FALSE, FALSE, 0);
   combo_vbox = vbox;
 
@@ -355,42 +368,82 @@ ghid_command_window_show (gboolean raise)
   gtk_widget_show_all (command_window);
 }
 
-
 static gboolean
-command_escape_cb (GtkWidget * widget, GdkEventKey * kev, gpointer data)
+command_changed_cb (GtkEntry *entry, gchar *text, gpointer data)
 {
-  gint ksym = kev->keyval;
+  gint x, y, height;
+  GtkAllocation allocation;
+
+  text = ghid_entry_get_text (ghidgui->command_entry);
+
+  /* Copy width, origin from text entry */
+  gtk_widget_get_allocation (ghidgui->command_entry, &allocation);
+  gdk_window_get_origin (gtk_widget_get_window (ghidgui->command_entry), &x, &y);
+
+  if (text && *text)
+    {
+      int n = populate_autocomplete_model (text);
+      if (n > 0)
+        {
+          height = MIN (AUTO_MAX_HEIGHT, n * auto_row_height);
+          /* Position and show autocomplete */
+          gtk_widget_set_size_request (ghidgui->command_hist_window, allocation.width, height);
+          gtk_window_resize (GTK_WINDOW (ghidgui->command_hist_window), allocation.width, height);
+          gtk_window_move (GTK_WINDOW (ghidgui->command_hist_window), x, y - height);
+          gtk_widget_show_all (ghidgui->command_hist_window);
+        }
+      else
+        gtk_widget_hide (ghidgui->command_hist_window);
+    }
+  else
+    gtk_widget_hide (ghidgui->command_hist_window);
 
-  if (ksym != GDK_Escape)
-    return FALSE;
+  return FALSE;
+}
 
-  if (loop && g_main_loop_is_running (loop))	/* should always be */
-    g_main_loop_quit (loop);
-  command_entered = NULL;	/* We are aborting */
+static gboolean
+command_keyboard_cb (GtkWidget * widget, GdkEventKey * kev, gpointer data)
+{
+  gint ksym = kev->keyval;
 
-  return TRUE;
+  switch (ksym)
+    {
+    case GDK_Up:
+    case GDK_Down:
+    case GDK_Tab:
+      /* TODO: move through list */
+      return TRUE;
+    case GDK_Escape:
+      if (loop && g_main_loop_is_running (loop))	/* should always be */
+        g_main_loop_quit (loop);
+      command_entered = NULL;	/* We are aborting */
+      return TRUE;
+    default:
+      return FALSE;
+    }
 }
 
 
   /* This is the command entry function called from ActionCommand() when
-     |  ghidgui->use_command_window is FALSE.  The command_combo_box is already
+     |  ghidgui->use_command_window is FALSE.  The command_text_entry is already
      |  packed into the status line label hbox in this case.
    */
 gchar *
 ghid_command_entry_get (gchar * prompt, gchar * command)
 {
   gchar *s;
-  gint escape_sig_id;
+  gint keyboard_sig_id;
   GHidPort *out = &ghid_port;
 
   /* If this is the first user command entry, we have to create the
      |  command_combo_box and pack it into the status_line_hbox.
    */
-  if (!ghidgui->command_combo_box)
+  if (!ghidgui->command_entry)
     {
-      command_combo_box_entry_create ();
+      command_text_entry_create ();
       gtk_box_pack_start (GTK_BOX (ghidgui->status_line_hbox),
-			  ghidgui->command_combo_box, FALSE, FALSE, 0);
+			  GTK_WIDGET (ghidgui->command_entry),
+                          TRUE, TRUE, 0);
     }
 
   /* Make the prompt bold and set the label before showing the combo to
@@ -402,12 +455,12 @@ ghid_command_entry_get (gchar * prompt, gchar * command)
 
   /* Flag so output drawing area won't try to get focus away from us and
      |  so resetting the status line label can be blocked when resize
-     |  callbacks are invokded from the resize caused by showing the combo box.
+     |  callbacks are invoked (from the resize caused by showing the entry!).
    */
   ghidgui->command_entry_status_line_active = TRUE;
 
-  gtk_entry_set_text (ghidgui->command_entry, command ? command : "");
-  gtk_widget_show_all (ghidgui->command_combo_box);
+  gtk_entry_set_text (GTK_ENTRY (ghidgui->command_entry), command ? command : "");
+  gtk_widget_show (ghidgui->command_entry);
 
   /* Remove the top window accel group so keys intended for the entry
      |  don't get intercepted by the menu system.  Set the interface
@@ -420,9 +473,12 @@ ghid_command_entry_get (gchar * prompt, gchar * command)
   ghid_interface_input_signals_disconnect ();
   ghid_interface_set_sensitive (FALSE);
   gtk_widget_grab_focus (GTK_WIDGET (ghidgui->command_entry));
-  escape_sig_id = g_signal_connect (G_OBJECT (ghidgui->command_entry),
+  g_signal_connect (G_OBJECT (ghidgui->command_entry),
+		    "changed",
+		    G_CALLBACK (command_changed_cb), NULL);
+  keyboard_sig_id = g_signal_connect (G_OBJECT (ghidgui->command_entry),
 				    "key_press_event",
-				    G_CALLBACK (command_escape_cb), NULL);
+				    G_CALLBACK (command_keyboard_cb), NULL);
 
   loop = g_main_loop_new (NULL, FALSE);
   g_main_loop_run (loop);
@@ -434,7 +490,7 @@ ghid_command_entry_get (gchar * prompt, gchar * command)
 
   /* Restore the damage we did before entering the loop.
    */
-  g_signal_handler_disconnect (ghidgui->command_entry, escape_sig_id);
+  g_signal_handler_disconnect (ghidgui->command_entry, keyboard_sig_id);
   ghid_interface_input_signals_connect ();
   ghid_interface_set_sensitive (TRUE);
   gtk_window_add_accel_group (GTK_WINDOW (out->top_window),
@@ -443,7 +499,8 @@ ghid_command_entry_get (gchar * prompt, gchar * command)
 
   /* Restore the status line label and give focus back to the drawing area
    */
-  gtk_widget_hide (ghidgui->command_combo_box);
+  gtk_widget_hide (ghidgui->command_hist_window);
+  gtk_widget_hide (ghidgui->command_entry);
   gtk_widget_grab_focus (out->drawing_area);
 
   return command_entered;
@@ -460,7 +517,7 @@ ghid_handle_user_command (gboolean raise)
     ghid_command_window_show (raise);
   else
     {
-      command = ghid_command_entry_get (_("Enter command:"),
+      command = ghid_command_entry_get (_("Command:"),
 					(Settings.SaveLastCommand && previous) ? previous : (gchar *)"");
       if (command != NULL)
 	{
diff --git a/src/hid/gtk/gui.h b/src/hid/gtk/gui.h
index 31ee636..2e1bc40 100644
--- a/src/hid/gtk/gui.h
+++ b/src/hid/gtk/gui.h
@@ -106,7 +106,7 @@ typedef struct
     *cursor_position_relative_label,
     *cursor_position_absolute_label,
     *grid_units_label, *status_line_hbox, *command_combo_box;
-  GtkEntry *command_entry;
+  GtkWidget *command_entry, *command_hist_window, *command_hist_tree_view;
 
   GtkWidget *top_hbox,
     *menu_hbox, *compact_vbox, *compact_hbox, *position_hbox, *label_hbox,


_______________________________________________
geda-user mailing list
geda-user@xxxxxxxxxxxxxx
http://www.seul.org/cgi-bin/mailman/listinfo/geda-user