changeset 14793:8a0cfee11af8

[gaim-migrate @ 17558] Introduce actions. You can specify the bindings for the actions. Right now, only the tree and the entry widget have them. The manual includes the details. I believe Ethan had suggested something like this a while back. It made sense, so here it is. committer: Tailor Script <tailor@pidgin.im>
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sat, 21 Oct 2006 21:08:24 +0000
parents 242f948ee707
children 0c96d5d774d7
files console/libgnt/gnt-skel.c console/libgnt/gntentry.c console/libgnt/gntkeys.h console/libgnt/gntstyle.c console/libgnt/gnttree.c console/libgnt/gntutils.c console/libgnt/gntutils.h console/libgnt/gntwidget.c console/libgnt/gntwidget.h console/libgnt/test/multiwin.c console/libgnt/test/tv.c doc/gaim-text.1.in
diffstat 12 files changed, 640 insertions(+), 214 deletions(-) [+]
line wrap: on
line diff
--- a/console/libgnt/gnt-skel.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gnt-skel.c	Sat Oct 21 21:08:24 2006 +0000
@@ -50,6 +50,13 @@
 	parent_class->size_request = gnt_skel_size_request;
 	parent_class->key_pressed = gnt_skel_key_pressed;
 
+	parent_class->actions = g_hash_table_duplicate(parent_class->actions, g_str_hash,
+				g_str_equal, NULL, (GDestroyNotify)gnt_widget_action_free);
+	parent_class->bindings = g_hash_table_duplicate(parent_class->bindings, g_str_hash,
+				g_str_equal, NULL, (GDestroyNotify)gnt_widget_action_param_free);
+
+	gnt_widget_actions_read(G_OBJECT_CLASS_TYPE(klass), klass);
+
 	GNTDEBUG;
 }
 
--- a/console/libgnt/gntentry.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gntentry.c	Sat Oct 21 21:08:24 2006 +0000
@@ -157,35 +157,40 @@
 	gnt_widget_queue_update(widget);
 }
 
-static void
-move_back(GntEntry *entry)
+static gboolean
+move_back(GntWidget *widget, GList *null)
 {
+	GntEntry *entry = GNT_ENTRY(widget);
 	if (entry->cursor <= entry->start)
-		return;
+		return FALSE;
 	entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor);
 	if (entry->cursor < entry->scroll)
 		entry->scroll = entry->cursor;
 	entry_redraw(GNT_WIDGET(entry));
+	return TRUE;
 }
 
-static void
-move_forward(GntEntry *entry)
+static gboolean
+move_forward(GntWidget *widget, GList *list)
 {
+	GntEntry *entry = GNT_ENTRY(widget);
 	if (entry->cursor >= entry->end)
-		return;
+		return FALSE;
 	entry->cursor = g_utf8_find_next_char(entry->cursor, NULL);
 	while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
 		entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
 	entry_redraw(GNT_WIDGET(entry));
+	return TRUE;
 }
 
-static void
-backspace(GntEntry *entry)
+static gboolean
+backspace(GntWidget *widget, GList *null)
 {
 	int len;
+	GntEntry *entry = GNT_ENTRY(widget);
 
 	if (entry->cursor <= entry->start)
-		return;
+		return TRUE;
 	
 	len = entry->cursor - g_utf8_find_prev_char(entry->start, entry->cursor);
 	entry->cursor -= len;
@@ -198,15 +203,17 @@
 	entry_redraw(GNT_WIDGET(entry));
 	if (entry->ddown)
 		show_suggest_dropdown(entry);
+	return TRUE;
 }
 
-static void
-delkey(GntEntry *entry)
+static gboolean
+delkey(GntWidget *widget, GList *null)
 {
 	int len;
+	GntEntry *entry = GNT_ENTRY(widget);
 
 	if (entry->cursor >= entry->end)
-		return;
+		return FALSE;
 	
 	len = g_utf8_find_next_char(entry->cursor, NULL) - entry->cursor;
 	memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor - len + 1);
@@ -215,23 +222,116 @@
 
 	if (entry->ddown)
 		show_suggest_dropdown(entry);
+	return TRUE;
 }
 
-static void
-move_start(GntEntry *entry)
+static gboolean
+move_start(GntWidget *widget, GList *null)
 {
+	GntEntry *entry = GNT_ENTRY(widget);
 	entry->scroll = entry->cursor = entry->start;
 	entry_redraw(GNT_WIDGET(entry));
+	return TRUE;
 }
 
-static void
-move_end(GntEntry *entry)
+static gboolean
+move_end(GntWidget *widget, GList *null)
 {
+	GntEntry *entry = GNT_ENTRY(widget);
 	entry->cursor = entry->end;
 	/* This should be better than this */
 	while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
 		entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
 	entry_redraw(GNT_WIDGET(entry));
+	return TRUE;
+}
+
+static gboolean
+history_prev(GntWidget *widget, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	if (entry->histlength && entry->history->prev)
+	{
+		entry->history = entry->history->prev;
+		gnt_entry_set_text(entry, entry->history->data);
+		destroy_suggest(entry);
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+history_next(GntWidget *widget, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	if (entry->histlength && entry->history->next)
+	{
+		if (entry->history->prev == NULL)
+		{
+			/* Save the current contents */
+			char *text = g_strdup(gnt_entry_get_text(entry));
+			g_free(entry->history->data);
+			entry->history->data = text;
+		}
+
+		entry->history = entry->history->next;
+		gnt_entry_set_text(entry, entry->history->data);
+		destroy_suggest(entry);
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+suggest_show(GntWidget *widget, GList *null)
+{
+	return show_suggest_dropdown(GNT_ENTRY(widget));
+}
+
+static gboolean
+suggest_next(GntWidget *widget, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	if (entry->ddown) {
+		gnt_widget_perform_action_named(entry->ddown, "move-down", NULL);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+suggest_prev(GntWidget *widget, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	if (entry->ddown) {
+		gnt_widget_perform_action_named(entry->ddown, "move-up", NULL);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+del_to_home(GntWidget *widget, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	memmove(entry->start, entry->cursor, entry->end - entry->cursor);
+	entry->end -= (entry->cursor - entry->start);
+	entry->cursor = entry->scroll = entry->start;
+	memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
+	entry_redraw(widget);
+	return TRUE;
+}
+
+static gboolean
+del_to_end(GntWidget *widget, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	entry->end = entry->cursor;
+	memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
+	entry_redraw(widget);
+	return TRUE;
 }
 
 static gboolean
@@ -241,73 +341,10 @@
 
 	if (text[0] == 27)
 	{
-		if (strcmp(text + 1, GNT_KEY_DEL) == 0 && entry->cursor < entry->end)
-		{
-			delkey(entry);
-			return TRUE;
-		}
-		else if (strcmp(text + 1, GNT_KEY_LEFT) == 0)
-		{
-			move_back(entry);
-			return TRUE;
-		}
-		else if (strcmp(text + 1, GNT_KEY_RIGHT) == 0)
-		{
-			move_forward(entry);
-			return TRUE;
-		}
-		else if (strcmp(text + 1, GNT_KEY_HOME) == 0)
-		{
-			move_start(entry);
-			return TRUE;
-		}
-		else if (strcmp(text + 1, GNT_KEY_END) == 0)
-		{
-			move_end(entry);
-			return TRUE;
-		}
-		else if (strcmp(text + 1, GNT_KEY_CTRL_DOWN) == 0 && entry->histlength)
-		{
-			if (entry->history->prev)
-			{
-				entry->history = entry->history->prev;
-				gnt_entry_set_text(entry, entry->history->data);
-				destroy_suggest(entry);
-
-				return TRUE;
-			}
-		}
-		else if (strcmp(text + 1, GNT_KEY_UP) == 0 ||
-				strcmp(text + 1, GNT_KEY_DOWN) == 0)
-		{
-			if (entry->ddown)
-			{
-				gnt_widget_key_pressed(entry->ddown, text);
-				return TRUE;
-			}
-		}
-		else if (strcmp(text + 1, GNT_KEY_CTRL_UP) == 0 && entry->histlength)
-		{
-			if (entry->history->next)
-			{
-				if (entry->history->prev == NULL)
-				{
-					/* Save the current contents */
-					char *text = g_strdup(gnt_entry_get_text(entry));
-					g_free(entry->history->data);
-					entry->history->data = text;
-				}
-
-				entry->history = entry->history->next;
-				gnt_entry_set_text(entry, entry->history->data);
-				destroy_suggest(entry);
-
-				return TRUE;
-			}
-		}
-		else if (text[1] == 0)
+		if (text[1] == 0)
 		{
 			destroy_suggest(entry);
+			return TRUE;
 		}
 
 		return FALSE;
@@ -400,60 +437,6 @@
 			entry_redraw(widget);
 			return TRUE;
 		}
-		else
-		{
-			if (strcmp(text, GNT_KEY_BACKSPACE) == 0 && entry->cursor > entry->start)
-			{
-				backspace(entry);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_A) == 0)
-			{
-				move_start(entry);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_B) == 0)
-			{
-				move_back(entry);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_D) == 0)
-			{
-				delkey(entry);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_E) == 0)
-			{
-				move_end(entry);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_F) == 0)
-			{
-				move_forward(entry);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_H) == 0)
-			{
-				backspace(entry);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_K) == 0)
-			{
-				entry->end = entry->cursor;
-				memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
-				entry_redraw(widget);
-				return TRUE;
-			}
-			else if (strcmp(text, GNT_KEY_CTRL_U) == 0)
-			{
-				memmove(entry->start, entry->cursor, entry->end - entry->cursor);
-				entry->end -= (entry->cursor - entry->start);
-				entry->cursor = entry->scroll = entry->start;
-				memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
-				entry_redraw(widget);
-				return TRUE;
-			}
-		}
 	}
 
 	return FALSE;
@@ -503,6 +486,49 @@
 	parent_class->key_pressed = gnt_entry_key_pressed;
 	parent_class->lost_focus = gnt_entry_lost_focus;
 
+	parent_class->actions = g_hash_table_duplicate(parent_class->actions, g_str_hash,
+				g_str_equal, g_free, (GDestroyNotify)gnt_widget_action_free);
+	parent_class->bindings = g_hash_table_duplicate(parent_class->bindings, g_str_hash,
+				g_str_equal, g_free, (GDestroyNotify)gnt_widget_action_param_free);
+
+	gnt_widget_class_register_action(parent_class, "cursor-home", move_start,
+				GNT_KEY_CTRL_A, NULL);
+	gnt_widget_register_binding(parent_class, "cursor-home", "\033" GNT_KEY_HOME, NULL);
+	gnt_widget_class_register_action(parent_class, "cursor-end", move_end,
+				GNT_KEY_CTRL_E, NULL);
+	gnt_widget_register_binding(parent_class, "cursor-end", "\033" GNT_KEY_END, NULL);
+	gnt_widget_class_register_action(parent_class, "delete-prev", backspace,
+				GNT_KEY_BACKSPACE, NULL);
+	gnt_widget_class_register_action(parent_class, "delete-next", delkey,
+				"\033" GNT_KEY_DEL, NULL);
+	gnt_widget_register_binding(parent_class, "delete-next", GNT_KEY_CTRL_D, NULL);
+	gnt_widget_class_register_action(parent_class, "delete-start", del_to_home,
+				GNT_KEY_CTRL_U, NULL);
+	gnt_widget_class_register_action(parent_class, "delete-end", del_to_end,
+				GNT_KEY_CTRL_K, NULL);
+#if 0
+	gnt_widget_class_register_action(parent_class, "delete-prev-word", del_prev_word,
+				NULL, 1, NULL);
+	gnt_widget_class_register_action(parent_class, "delete-next-word", del_next_word,
+				NULL, 1, NULL);
+#endif
+	gnt_widget_class_register_action(parent_class, "cursor-prev", move_back,
+				"\033" GNT_KEY_LEFT, NULL);
+	gnt_widget_class_register_action(parent_class, "cursor-next", move_forward,
+				"\033" GNT_KEY_RIGHT, NULL);
+	gnt_widget_class_register_action(parent_class, "suggest-show", suggest_show,
+				"\t", NULL);
+	gnt_widget_class_register_action(parent_class, "suggest-next", suggest_next,
+				"\033" GNT_KEY_DOWN, NULL);
+	gnt_widget_class_register_action(parent_class, "suggest-prev", suggest_prev,
+				"\033" GNT_KEY_UP, NULL);
+	gnt_widget_class_register_action(parent_class, "history-prev", history_prev,
+				"\033" GNT_KEY_CTRL_UP, NULL);
+	gnt_widget_class_register_action(parent_class, "history-next", history_next,
+				"\033" GNT_KEY_CTRL_DOWN, NULL);
+
+	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), klass);
+
 	GNTDEBUG;
 }
 
--- a/console/libgnt/gntkeys.h	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gntkeys.h	Sat Oct 21 21:08:24 2006 +0000
@@ -23,6 +23,7 @@
 
 #define GNT_KEY_BACKSPACE "\177"
 #define GNT_KEY_DEL    "[3~"
+#define GNT_KEY_INS    "[2~"
 
 #define GNT_KEY_CTRL_A     "\001"
 #define GNT_KEY_CTRL_B     "\002"
@@ -47,6 +48,19 @@
 #define GNT_KEY_CTRL_X     "\030"
 #define GNT_KEY_CTRL_Y     "\031"
 
+#define GNT_KEY_F1         "[[A"
+#define GNT_KEY_F2         "[[B"
+#define GNT_KEY_F3         "[[C"
+#define GNT_KEY_F4         "[[D"
+#define GNT_KEY_F5         "[[E"
+#define GNT_KEY_F6         "[17~"
+#define GNT_KEY_F7         "[18~"
+#define GNT_KEY_F8         "[19~"
+#define GNT_KEY_F9         "[20~"
+#define GNT_KEY_F10        "[21~"
+#define GNT_KEY_F11        "[23~"
+#define GNT_KEY_F12        "[24~"
+
 /**
  * This will do stuff with the terminal settings and stuff.
  */
--- a/console/libgnt/gntstyle.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gntstyle.c	Sat Oct 21 21:08:24 2006 +0000
@@ -79,6 +79,141 @@
 	*t = '\0';
 }
 
+static char *
+parse_key(const char *key)
+{
+	char *ret = NULL;
+	int ctrl = 0, alt = 0;
+	char k;
+
+	/* XXX: Need to do something about ctrl/alt+home, end etc. */
+
+#define SPECIAL_KEY(k, code) do { \
+		if (strncasecmp(key, k, sizeof(k) - 1) == 0) \
+			return g_strdup(code); \
+	} while (0)
+
+	SPECIAL_KEY("home",     "\033" GNT_KEY_HOME);
+	SPECIAL_KEY("end",      "\033" GNT_KEY_END);
+	SPECIAL_KEY("pageup",   "\033" GNT_KEY_PGUP);
+	SPECIAL_KEY("pagedown", "\033" GNT_KEY_PGDOWN);
+	SPECIAL_KEY("insert",   "\033" GNT_KEY_INS);
+	SPECIAL_KEY("delete",   "\033" GNT_KEY_DEL);
+
+	SPECIAL_KEY("left",   "\033" GNT_KEY_LEFT);
+	SPECIAL_KEY("right",  "\033" GNT_KEY_RIGHT);
+	SPECIAL_KEY("up",     "\033" GNT_KEY_UP);
+	SPECIAL_KEY("down",   "\033" GNT_KEY_DOWN);
+
+	SPECIAL_KEY("tab",    "\t");
+	SPECIAL_KEY("menu",   "\033" GNT_KEY_POPUP);
+
+	SPECIAL_KEY("f1",   "\033" GNT_KEY_F1);
+	SPECIAL_KEY("f2",   "\033" GNT_KEY_F2);
+	SPECIAL_KEY("f3",   "\033" GNT_KEY_F3);
+	SPECIAL_KEY("f4",   "\033" GNT_KEY_F4);
+	SPECIAL_KEY("f5",   "\033" GNT_KEY_F5);
+	SPECIAL_KEY("f6",   "\033" GNT_KEY_F6);
+	SPECIAL_KEY("f7",   "\033" GNT_KEY_F7);
+	SPECIAL_KEY("f8",   "\033" GNT_KEY_F8);
+	SPECIAL_KEY("f9",   "\033" GNT_KEY_F9);
+	SPECIAL_KEY("f10",  "\033" GNT_KEY_F10);
+	SPECIAL_KEY("f11",  "\033" GNT_KEY_F11);
+	SPECIAL_KEY("f12",  "\033" GNT_KEY_F12);
+
+#undef SPECIAL_KEY
+
+#define MATCH(string, var)	do { \
+		if (strncasecmp(key, string, sizeof(string) - 1) == 0) { \
+			key += sizeof(string) - 1; \
+			var = 1; \
+		} \
+	}while (0)
+
+	MATCH("c-", ctrl);
+	MATCH("ctl-", ctrl);
+	MATCH("ctr-", ctrl);
+	MATCH("ctrl-", ctrl);
+
+	MATCH("alt-", alt);
+	MATCH("a-", alt);
+	MATCH("m-", alt);
+	MATCH("meta-", alt);
+
+	if (strlen(key) != 1)  /* We can only have stuff like "ctrl-alt-a" */
+		return NULL;
+
+	if (ctrl && (strchr("hijm", *key) != NULL || !isalpha(*key))) {
+		/* These keys cannot be used with ctrl */
+		return NULL;
+	}
+
+	if (ctrl)
+		k = *key | 0x20;
+	else
+		k = *key;
+
+	ret = g_strdup_printf("%s%c", alt ? "\033" : "", ctrl ? k - 0x60 : k);
+
+#undef MATCH
+
+	return ret;
+}
+
+void gnt_style_read_actions(GType type, GntWidgetClass *klass)
+{
+#if GLIB_CHECK_VERSION(2,6,0)
+	char *name;
+	GError *error = NULL;
+
+	name = g_strdup_printf("%s::binding", g_type_name(type));
+
+	if (g_key_file_has_group(gkfile, name))
+	{
+		unsigned int len = 0;
+		char **keys;
+		
+		keys = g_key_file_get_keys(gkfile, name, &len, &error);
+		if (error)
+		{
+			g_printerr("GntStyle: %s\n", error->message);
+			g_error_free(error);
+			g_free(name);
+			return;
+		}
+
+		while (len--)
+		{
+			char *key, *action;
+
+			key = g_strdup(keys[len]);
+			action = g_key_file_get_string(gkfile, name, keys[len], &error);
+
+			if (error)
+			{
+				g_printerr("GntStyle: %s\n", error->message);
+				g_error_free(error);
+				error = NULL;
+			}
+			else
+			{
+				char *keycode = parse_key(key);
+				if (keycode == NULL) {
+					g_printerr("GntStyle: Invalid key-binding %s\n", key);
+				} else {
+					gnt_widget_register_binding(klass, action, keycode, NULL);
+					g_free(keycode);
+				}
+			}
+			g_free(key);
+			g_free(action);
+		}
+		g_strfreev(keys);
+	}
+	g_free(name);
+#endif
+}
+
 void gnt_styles_get_keyremaps(GType type, GHashTable *hash)
 {
 #if GLIB_CHECK_VERSION(2,6,0)
@@ -97,6 +232,7 @@
 		{
 			g_printerr("GntStyle: %s\n", error->message);
 			g_error_free(error);
+			g_free(name);
 			return;
 		}
 
--- a/console/libgnt/gnttree.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gnttree.c	Sat Oct 21 21:08:24 2006 +0000
@@ -480,32 +480,95 @@
 	return g_hash_table_lookup(tree->hash, key);
 }
 
-static void
-action_down(GntTree *tree)
+static gboolean
+action_down(GntWidget *widget, GList *null)
 {
 	int dist;
+	GntTree *tree = GNT_TREE(widget);
+	GntTreeRow *old = tree->current;
 	GntTreeRow *row = get_next(tree->current);
 	if (row == NULL)
-		return;
+		return FALSE;
 	tree->current = row;
 	if ((dist = get_distance(tree->current, tree->bottom)) < 0)
 		gnt_tree_scroll(tree, -dist);
 	else
 		redraw_tree(tree);
+	if (old != tree->current)
+		tree_selection_changed(tree, old, tree->current);
+	return TRUE;
 }
 
-static void
-action_up(GntTree *tree)
+static gboolean
+action_up(GntWidget *widget, GList *list)
 {
 	int dist;
+	GntTree *tree = GNT_TREE(widget);
+	GntTreeRow *old = tree->current;
 	GntTreeRow *row = get_prev(tree->current);
 	if (!row)
-		return;
+		return FALSE;
 	tree->current = row;
 	if ((dist = get_distance(tree->current, tree->top)) > 0)
 		gnt_tree_scroll(tree, -dist);
 	else
 		redraw_tree(tree);
+	if (old != tree->current)
+		tree_selection_changed(tree, old, tree->current);
+
+	return TRUE;
+}
+
+static gboolean
+action_page_down(GntWidget *widget, GList *null)
+{
+	GntTree *tree = GNT_TREE(widget);
+	GntTreeRow *old = tree->current;
+	GntTreeRow *row = get_next(tree->bottom);
+	if (row)
+	{
+		int dist = get_distance(tree->top, tree->current);
+		tree->top = tree->bottom;
+		tree->current = get_next_n_opt(tree->top, dist, NULL);
+		redraw_tree(tree);
+	}
+	else if (tree->current != tree->bottom)
+	{
+		tree->current = tree->bottom;
+		redraw_tree(tree);
+	}
+
+	if (old != tree->current)
+		tree_selection_changed(tree, old, tree->current);
+	return TRUE;
+}
+
+static gboolean
+action_page_up(GntWidget *widget, GList *null)
+{
+	GntTree *tree = GNT_TREE(widget);
+	GntTreeRow *row;
+	GntTreeRow *old = tree->current;
+
+	if (tree->top != tree->root)
+	{
+		int dist = get_distance(tree->top, tree->current);
+		row = get_prev_n(tree->top, widget->priv.height - 1 -
+			tree->show_title * 2 - 2 * (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER) == 0));
+		if (row == NULL)
+			row = tree->root;
+		tree->top = row;
+		tree->current = get_next_n_opt(tree->top, dist, NULL);
+		redraw_tree(tree);
+	}
+	else if (tree->current != tree->top)
+	{
+		tree->current = tree->top;
+		redraw_tree(tree);
+	}
+	if (old != tree->current)
+		tree_selection_changed(tree, old, tree->current);
+	return TRUE;
 }
 
 static gboolean
@@ -516,75 +579,9 @@
 	GntTreeRow *row;
 	int dist;
 
-	if (text[0] == 27)
+	if (text[0] == '\r')
 	{
-		if (strcmp(text+1, GNT_KEY_DOWN) == 0)
-		{
-			action_down(tree);
-		}
-		else if (strcmp(text+1, GNT_KEY_UP) == 0)
-		{
-			action_up(tree);
-		}
-		else if (strcmp(text+1, GNT_KEY_PGDOWN) == 0)
-		{
-			row = get_next(tree->bottom);
-			if (row)
-			{
-				int dist = get_distance(tree->top, tree->current);
-				tree->top = tree->bottom;
-				tree->current = get_next_n_opt(tree->top, dist, NULL);
-				redraw_tree(tree);
-			}
-			else if (tree->current != tree->bottom)
-			{
-				tree->current = tree->bottom;
-				redraw_tree(tree);
-			}
-		}
-		else if (strcmp(text+1, GNT_KEY_PGUP) == 0)
-		{
-			if (tree->top != tree->root)
-			{
-				int dist = get_distance(tree->top, tree->current);
-				row = get_prev_n(tree->top, widget->priv.height - 1 -
-					tree->show_title * 2 - 2 * (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER) == 0));
-				if (row == NULL)
-					row = tree->root;
-				tree->top = row;
-				tree->current = get_next_n_opt(tree->top, dist, NULL);
-				redraw_tree(tree);
-			}
-			else if (tree->current != tree->top)
-			{
-				tree->current = tree->top;
-				redraw_tree(tree);
-			}
-		}
-	}
-	else if (iscntrl(text[0]))
-	{
-		if (strcmp(text, GNT_KEY_CTRL_N) == 0 && (row = get_next(tree->current)) != NULL)
-		{
-			tree->current = row;
-			if ((dist = get_distance(tree->current, tree->bottom)) < 0)
-				gnt_tree_scroll(tree, -dist);
-			else
-				redraw_tree(tree);
-		}
-		else if (strcmp(text, GNT_KEY_CTRL_P) == 0 && (row = get_prev(tree->current)) != NULL)
-		{
-			tree->current = row;
-
-			if ((dist = get_distance(tree->current, tree->top)) > 0)
-				gnt_tree_scroll(tree, -dist);
-			else
-				redraw_tree(tree);
-		}
-		else if (text[0] == '\r')
-		{
-			gnt_widget_activate(widget);
-		}
+		gnt_widget_activate(widget);
 	}
 	else if (text[0] == ' ' && text[1] == 0)
 	{
@@ -634,9 +631,9 @@
 	GntTree *tree = GNT_TREE(widget);
 	GntTreeRow *old = tree->current;
 	if (event == GNT_MOUSE_SCROLL_UP) {
-		action_up(GNT_TREE(widget));
+		action_up(widget, NULL);
 	} else if (event == GNT_MOUSE_SCROLL_DOWN) {
-		action_down(GNT_TREE(widget));
+		action_down(widget, NULL);
 	} else if (event == GNT_LEFT_MOUSE_DOWN) {
 		GntTreeRow *row;
 		GntTree *tree = GNT_TREE(widget);
@@ -706,6 +703,24 @@
 					 g_cclosure_marshal_VOID__POINTER,
 					 G_TYPE_NONE, 1, G_TYPE_POINTER);
 
+	parent_class->actions = g_hash_table_duplicate(parent_class->actions, g_str_hash,
+				g_str_equal, g_free, (GDestroyNotify)gnt_widget_action_free);
+	parent_class->bindings = g_hash_table_duplicate(parent_class->bindings, g_str_hash,
+				g_str_equal, g_free, (GDestroyNotify)gnt_widget_action_param_free);
+
+	gnt_widget_class_register_action(parent_class, "move-up", action_up,
+				"\033" GNT_KEY_UP, NULL);
+	gnt_widget_register_binding(parent_class, "move-up", "\033" GNT_KEY_CTRL_N, NULL);
+	gnt_widget_class_register_action(parent_class, "move-down", action_down,
+				"\033" GNT_KEY_DOWN, NULL);
+	gnt_widget_register_binding(parent_class, "move-down", "\033" GNT_KEY_CTRL_P, NULL);
+	gnt_widget_class_register_action(parent_class, "page-up", action_page_up,
+				"\033" GNT_KEY_PGUP, NULL);
+	gnt_widget_class_register_action(parent_class, "page-down", action_page_down,
+				"\033" GNT_KEY_PGDOWN, NULL);
+
+	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), klass);
+
 	GNTDEBUG;
 }
 
@@ -1350,11 +1365,9 @@
 	for (i = 0; i < tree->ncol; i++) {
 		gnt_tree_set_col_width(tree, i, widths[i]);
 		twidth += widths[i] + (tree->show_separator ? 1 : 0) + 1;
-		fprintf(stderr, "column width for col %d: %d\n", i, widths[i]);
 	}
 	g_free(widths);
 
-	fprintf(stderr, "tree width: %d\n", twidth);
 	gnt_widget_get_size(GNT_WIDGET(tree), NULL, &height);
 	gnt_widget_set_size(GNT_WIDGET(tree), twidth, height);
 }
--- a/console/libgnt/gntutils.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gntutils.c	Sat Oct 21 21:08:24 2006 +0000
@@ -105,3 +105,17 @@
 	return g_string_free(str, FALSE);
 }
 
+static void
+duplicate_values(gpointer key, gpointer value, gpointer data)
+{
+	g_hash_table_insert(data, key, value);
+}
+
+GHashTable *g_hash_table_duplicate(GHashTable *src, GHashFunc hash,
+		GEqualFunc equal, GDestroyNotify key_d, GDestroyNotify value_d)
+{
+	GHashTable *dest = g_hash_table_new_full(hash, equal, key_d, value_d);
+	g_hash_table_foreach(src, duplicate_values, dest);
+	return dest;
+}
+
--- a/console/libgnt/gntutils.h	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gntutils.h	Sat Oct 21 21:08:24 2006 +0000
@@ -17,3 +17,6 @@
  * Returns a newly allocated string.
  */
 char *gnt_util_onscreen_fit_string(const char *string, int maxw);
+
+GHashTable *g_hash_table_duplicate(GHashTable *src, GHashFunc hash,
+		GEqualFunc equal, GDestroyNotify key_d, GDestroyNotify value_d);
--- a/console/libgnt/gntwidget.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gntwidget.c	Sat Oct 21 21:08:24 2006 +0000
@@ -234,6 +234,13 @@
 					 gnt_closure_marshal_BOOLEAN__INT_INT_INT,
 					 G_TYPE_BOOLEAN, 3, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
 
+	klass->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+				(GDestroyNotify)gnt_widget_action_free);
+	klass->bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+				(GDestroyNotify)gnt_widget_action_param_free);
+
+	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), klass);
+
 	GNTDEBUG;
 }
 
@@ -384,12 +391,56 @@
 }
 
 gboolean
+gnt_widget_perform_action_named(GntWidget *widget, const char *name, ...)
+{
+	GType type = G_OBJECT_TYPE(widget);
+	GntWidgetClass *klass = GNT_WIDGET_CLASS(G_OBJECT_GET_CLASS(widget));
+	GList *list = NULL;
+	va_list args;
+	GntWidgetAction *action;
+	void *p;
+
+	va_start(args, name);
+	while ((p = va_arg(args, void *)) != NULL)
+		list = g_list_append(list, p);
+	va_end(args);
+	
+	action = g_hash_table_lookup(klass->actions, name);
+	if (action && action->u.action) {
+		if (list)
+			return action->u.action(widget, list);
+		else
+			return action->u.action_noparam(widget);
+	}
+	return FALSE;
+}
+
+static gboolean
+gnt_widget_perform_action(GntWidget *widget, const char *keys)
+{
+	GType type = G_OBJECT_TYPE(widget);
+	GntWidgetClass *klass = GNT_WIDGET_CLASS(G_OBJECT_GET_CLASS(widget));
+	GntWidgetActionParam *param = g_hash_table_lookup(klass->bindings, keys);
+
+	if (param && param->action) {
+		if (param->list)
+			return param->action->u.action(widget, param->list);
+		else
+			return param->action->u.action_noparam(widget);
+	}
+	return FALSE;
+}
+
+gboolean
 gnt_widget_key_pressed(GntWidget *widget, const char *keys)
 {
 	gboolean ret;
 	if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_CAN_TAKE_FOCUS))
 		return FALSE;
 
+	if (gnt_widget_perform_action(widget, keys))
+		return TRUE;
+
 	keys = gnt_widget_remap_keys(widget, keys);
 	g_signal_emit(widget, signals[SIG_KEY_PRESSED], 0, keys, &ret);
 	return ret;
@@ -632,3 +683,71 @@
 			gnt_style_get_bool(GNT_STYLE_SHADOW, FALSE));
 }
 
+static void
+register_binding(GntWidgetClass *klass, const char *name, const char *trigger, GList *list)
+{
+	GntWidgetActionParam *param;
+
+	if (name == NULL || *name == '\0') {
+		g_hash_table_remove(klass->bindings, (char*)trigger);
+		return;
+	}
+
+	param = g_new0(GntWidgetActionParam, 1);
+	param->action = g_hash_table_lookup(klass->actions, name);
+	param->list = list;
+	g_hash_table_replace(klass->bindings, g_strdup(trigger), param);
+}
+
+void gnt_widget_register_binding(GntWidgetClass *klass, const char *name,
+			const char *trigger, ...)
+{
+	GList *list = NULL;
+	va_list args;
+	void *data;
+
+	va_start(args, trigger);
+	while ((data = va_arg(args, void *))) {
+		list = g_list_append(list, data);
+	}
+	va_end(args);
+
+	register_binding(klass, name, trigger, list);
+}
+
+void gnt_widget_class_register_action(GntWidgetClass *klass, const char *name,
+			GntWidgetActionCallback callback,
+			const char *trigger, ...)
+{
+	void *data;
+	va_list args;
+	GntWidgetAction *action = g_new0(GntWidgetAction, 1);
+	GList *list;
+
+	action->name = g_strdup(name);
+	action->u.action = callback;
+
+	g_hash_table_replace(klass->actions, g_strdup(name), action);
+
+	list = NULL;
+	va_start(args, trigger);
+	while ((data = va_arg(args, void *))) {
+		list = g_list_append(list, data);
+	}
+	va_end(args);
+
+	register_binding(klass, name, trigger, list);
+}
+
+void gnt_widget_action_free(GntWidgetAction *action)
+{
+	g_free(action->name);
+	g_free(action);
+}
+
+void gnt_widget_action_param_free(GntWidgetActionParam *param)
+{
+	g_list_free(param->list);   /* XXX: There may be a leak here for string parameters */
+	g_free(param);
+}
+
--- a/console/libgnt/gntwidget.h	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/gntwidget.h	Sat Oct 21 21:08:24 2006 +0000
@@ -88,6 +88,8 @@
 	GObjectClass parent;
 
 	GHashTable *remaps;   /* Key remaps */
+	GHashTable *actions;  /* name -> Action */
+	GHashTable *bindings; /* key -> ActionParam */
 
 	void (*map)(GntWidget *obj);
 	void (*show)(GntWidget *obj);		/* This will call draw() and take focus (if it can take focus) */
@@ -149,6 +151,40 @@
 
 gboolean gnt_widget_has_shadow(GntWidget *widget);
 
+/******************/
+/* Widget Actions */
+/******************/
+typedef gboolean (*GntWidgetActionCallback) (GntWidget *widget, GList *params);
+typedef gboolean (*GntWidgetActionCallbackNoParam)(GntWidget *widget);
+
+typedef struct _GnWidgetAction GntWidgetAction;
+typedef struct _GnWidgetActionParam GntWidgetActionParam;
+
+struct _GnWidgetAction
+{
+	char *name;        /* The name of the action */
+	union {
+		gboolean (*action)(GntWidget *widget, GList *params);
+		gboolean (*action_noparam)(GntWidget *widget);
+	} u;
+};
+
+struct _GnWidgetActionParam
+{
+	GntWidgetAction *action;
+	GList *list;
+};
+
+
+GntWidgetAction *gnt_widget_action_parse(const char *name);
+
+void gnt_widget_action_free(GntWidgetAction *action);
+void gnt_widget_action_param_free(GntWidgetActionParam *param);
+
+
+void gnt_widget_class_register_action(GntWidgetClass *klass, const char *name,
+			GntWidgetActionCallback callback, const char *trigger, ...);
+
 G_END_DECLS
 
 #endif /* GNT_WIDGET_H */
--- a/console/libgnt/test/multiwin.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/test/multiwin.c	Sat Oct 21 21:08:24 2006 +0000
@@ -62,6 +62,7 @@
 
 	gnt_tree_add_row_after(GNT_TREE(tree), "6", gnt_tree_create_row(GNT_TREE(tree), "6", " long text", "a2"), "4", NULL);
 
+	gnt_tree_add_row_after(GNT_TREE(tree), NULL, NULL, NULL, "4");
 	int i;
 	for (i = 110; i < 430; i++)
 	{
--- a/console/libgnt/test/tv.c	Sat Oct 21 20:40:12 2006 +0000
+++ b/console/libgnt/test/tv.c	Sat Oct 21 21:08:24 2006 +0000
@@ -13,7 +13,7 @@
 	{
 		gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view),
 				gnt_entry_get_text(GNT_ENTRY(w)),
-				GNT_TEXT_FLAG_HIGHLIGHT);
+				GNT_TEXT_FLAG_UNDERLINE | GNT_TEXT_FLAG_HIGHLIGHT);
 		gnt_entry_add_to_history(GNT_ENTRY(w), gnt_entry_get_text(GNT_ENTRY(w)));
 		gnt_text_view_next_line(GNT_TEXT_VIEW(view));
 		gnt_entry_clear(GNT_ENTRY(w));
--- a/doc/gaim-text.1.in	Sat Oct 21 20:40:12 2006 +0000
+++ b/doc/gaim-text.1.in	Sat Oct 21 21:08:24 2006 +0000
@@ -192,6 +192,63 @@
 \\  = \\r
 .br
 
+.SH Widget Actions
+You can specifiy key-bindings for specific widgets. The following entries in
+\fI~/.gntrc\fR correspond to the default keybindings for the actions:
+
+.br
+[GntEntry::binding]
+.br
+c-a = cursor-home
+.br
+home = cursor-home
+.br
+c-e = cursor-end
+.br
+end = cursor-end
+.br
+backspace = delete-prev
+.br
+del = delete-next
+.br
+c-d = delete-next
+.br
+c-u = delete-start
+.br
+c-k = delete-end
+.br
+left = cursor-prev
+.br
+right = cursor-next
+.br
+tab = suggest-show
+.br
+down = suggest-next
+.br
+up = suggest-prev
+.br
+
+.br
+[GntTree::binding]
+.br
+up = move-up
+.br
+down = move-down
+.br
+c-n = move-down
+.br
+c-p = move-up
+.br
+pageup = page-up
+.br
+pagedown = page-down
+.br
+
+The \fBc-\fR corresponds to the \fBControl\fR key. You can also use \fBctrl-\fR
+or \fBctr-\fR or \fBctl-\fR to indicate a combination. For alt-keys, you can use
+one of \fBa-\fR, \fBalt-\fR, \fBm-\fR or \fBmeta-\fR. You can also use
+\fBhome\fR, \fBend\fR, \fBleft\fR, \fBright\fR etc. keys.
+
 .SH Mouse Support
 There is experimental mouse support. You can focus windows, activate buttons,
 select rows in a list, scroll using the wheel-scroll etc. Mouse support is