[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