diff finch/libgnt/gntentry.c @ 15818:0e3a8505ebbe

renamed gaim-text to finch
author Sean Egan <seanegan@gmail.com>
date Sun, 18 Mar 2007 19:38:15 +0000
parents
children efbced3f38ac
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/gntentry.c	Sun Mar 18 19:38:15 2007 +0000
@@ -0,0 +1,932 @@
+#include <ctype.h>
+#include <string.h>
+
+#include "gntbox.h"
+#include "gntentry.h"
+#include "gntstyle.h"
+#include "gnttree.h"
+#include "gntutils.h"
+
+enum
+{
+	SIG_TEXT_CHANGED,
+	SIGS,
+};
+static guint signals[SIGS] = { 0 };
+
+static GntWidgetClass *parent_class = NULL;
+
+static void gnt_entry_set_text_internal(GntEntry *entry, const char *text);
+
+static void
+destroy_suggest(GntEntry *entry)
+{
+	if (entry->ddown)
+	{
+		gnt_widget_destroy(entry->ddown->parent);
+		entry->ddown = NULL;
+	}
+}
+
+static char *
+get_beginning_of_word(GntEntry *entry)
+{
+	char *s = entry->cursor;
+	while (s > entry->start)
+	{
+		char *t = g_utf8_find_prev_char(entry->start, s);
+		if (isspace(*t))
+			break;
+		s = t;
+	}
+	return s;
+}
+
+static gboolean
+show_suggest_dropdown(GntEntry *entry)
+{
+	char *suggest = NULL;
+	int len;
+	int offset = 0, x, y;
+	int count = 0;
+	GList *iter;
+
+	if (entry->word)
+	{
+		char *s = get_beginning_of_word(entry);
+		suggest = g_strndup(s, entry->cursor - s);
+		if (entry->scroll < s)
+			offset = gnt_util_onscreen_width(entry->scroll, s);
+	}
+	else
+		suggest = g_strdup(entry->start);
+	len = strlen(suggest);  /* Don't need to use the utf8-function here */
+	
+	if (entry->ddown == NULL)
+	{
+		GntWidget *box = gnt_vbox_new(FALSE);
+		entry->ddown = gnt_tree_new();
+		gnt_tree_set_compare_func(GNT_TREE(entry->ddown), (GCompareFunc)g_utf8_collate);
+		gnt_box_add_widget(GNT_BOX(box), entry->ddown);
+		/* XXX: Connect to the "activate" signal for the dropdown tree */
+
+		GNT_WIDGET_SET_FLAGS(box, GNT_WIDGET_TRANSIENT);
+
+		gnt_widget_get_position(GNT_WIDGET(entry), &x, &y);
+		x += offset;
+		y++;
+		if (y + 10 >= getmaxy(stdscr))
+			y -= 11;
+		gnt_widget_set_position(box, x, y);
+	}
+	else
+		gnt_tree_remove_all(GNT_TREE(entry->ddown));
+
+	for (count = 0, iter = entry->suggests; iter; iter = iter->next)
+	{
+		const char *text = iter->data;
+		if (g_ascii_strncasecmp(suggest, text, len) == 0 && strlen(text) >= len)
+		{
+			gnt_tree_add_row_after(GNT_TREE(entry->ddown), (gpointer)text,
+					gnt_tree_create_row(GNT_TREE(entry->ddown), text),
+					NULL, NULL);
+			count++;
+		}
+	}
+	g_free(suggest);
+
+	if (count == 0)
+	{
+		destroy_suggest(entry);
+		return FALSE;
+	}
+
+	gnt_widget_draw(entry->ddown->parent);
+	return TRUE;
+}
+
+static void
+gnt_entry_draw(GntWidget *widget)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	int stop;
+	gboolean focus;
+
+	if ((focus = gnt_widget_has_focus(widget)))
+		wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_TEXT_NORMAL));
+	else
+		wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D));
+
+	if (entry->masked)
+	{
+		mvwhline(widget->window, 0, 0, gnt_ascii_only() ? '*' : ACS_BULLET,
+				g_utf8_pointer_to_offset(entry->scroll, entry->end));
+	}
+	else
+		mvwprintw(widget->window, 0, 0, "%s", entry->scroll);
+
+	stop = gnt_util_onscreen_width(entry->scroll, entry->end);
+	if (stop < widget->priv.width)
+		whline(widget->window, ENTRY_CHAR, widget->priv.width - stop);
+
+	if (focus)
+		mvwchgat(widget->window, 0, gnt_util_onscreen_width(entry->scroll, entry->cursor),
+				1, A_REVERSE, GNT_COLOR_TEXT_NORMAL, NULL);
+
+	GNTDEBUG;
+}
+
+static void
+gnt_entry_size_request(GntWidget *widget)
+{
+	if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED))
+	{
+		widget->priv.height = 1;
+		widget->priv.width = 20;
+	}
+}
+
+static void
+gnt_entry_map(GntWidget *widget)
+{
+	if (widget->priv.width == 0 || widget->priv.height == 0)
+		gnt_widget_size_request(widget);
+	GNTDEBUG;
+}
+
+static void
+entry_redraw(GntWidget *widget)
+{
+	gnt_entry_draw(widget);
+	gnt_widget_queue_update(widget);
+}
+
+static void
+entry_text_changed(GntEntry *entry)
+{
+	g_signal_emit(entry, signals[SIG_TEXT_CHANGED], 0);
+}
+
+static gboolean
+move_back(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	if (entry->cursor <= entry->start)
+		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 gboolean
+move_forward(GntBindable *bind, GList *list)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	if (entry->cursor >= entry->end)
+		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 gboolean
+backspace(GntBindable *bind, GList *null)
+{
+	int len;
+	GntEntry *entry = GNT_ENTRY(bind);
+
+	if (entry->cursor <= entry->start)
+		return TRUE;
+	
+	len = entry->cursor - g_utf8_find_prev_char(entry->start, entry->cursor);
+	entry->cursor -= len;
+	memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor);
+	entry->end -= len;
+
+	if (entry->scroll > entry->start)
+		entry->scroll = g_utf8_find_prev_char(entry->start, entry->scroll);
+
+	entry_redraw(GNT_WIDGET(entry));
+	if (entry->ddown)
+		show_suggest_dropdown(entry);
+	entry_text_changed(entry);
+	return TRUE;
+}
+
+static gboolean
+delkey(GntBindable *bind, GList *null)
+{
+	int len;
+	GntEntry *entry = GNT_ENTRY(bind);
+
+	if (entry->cursor >= entry->end)
+		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);
+	entry->end -= len;
+	entry_redraw(GNT_WIDGET(entry));
+
+	if (entry->ddown)
+		show_suggest_dropdown(entry);
+	entry_text_changed(entry);
+	return TRUE;
+}
+
+static gboolean
+move_start(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	entry->scroll = entry->cursor = entry->start;
+	entry_redraw(GNT_WIDGET(entry));
+	return TRUE;
+}
+
+static gboolean
+move_end(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	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(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	if (entry->histlength && entry->history->prev)
+	{
+		entry->history = entry->history->prev;
+		gnt_entry_set_text_internal(entry, entry->history->data);
+		destroy_suggest(entry);
+		entry_text_changed(entry);
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+history_next(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	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_internal(entry, entry->history->data);
+		destroy_suggest(entry);
+		entry_text_changed(entry);
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+clipboard_paste(GntBindable *bind, GList *n)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	gchar *i, *text, *a, *all;
+	text = i = gnt_get_clipboard_string();
+	while (*i != '\0') {
+		i = g_utf8_next_char(i);
+		if (*i == '\r' || *i == '\n')
+			*i = ' ';
+	}
+	a = g_strndup(entry->start, entry->cursor - entry->start);
+	all = g_strconcat(a, text, entry->cursor, NULL);
+	gnt_entry_set_text_internal(entry, all);
+	g_free(a);
+	g_free(text);
+	g_free(all);
+	return TRUE;
+}
+
+static gboolean
+suggest_show(GntBindable *bind, GList *null)
+{
+	return show_suggest_dropdown(GNT_ENTRY(bind));
+}
+
+static gboolean
+suggest_next(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	if (entry->ddown) {
+		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down", NULL);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+suggest_prev(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	if (entry->ddown) {
+		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-up", NULL);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+del_to_home(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	if (entry->cursor <= entry->start)
+		return TRUE;
+	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(GNT_WIDGET(bind));
+	entry_text_changed(entry);
+	return TRUE;
+}
+
+static gboolean
+del_to_end(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	if (entry->end <= entry->cursor)
+		return TRUE;
+	entry->end = entry->cursor;
+	memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
+	entry_redraw(GNT_WIDGET(bind));
+	entry_text_changed(entry);
+	return TRUE;
+}
+
+#define SAME(a,b)    ((g_unichar_isalpha(a) && g_unichar_isalpha(b)) || \
+				(g_unichar_isdigit(a) && g_unichar_isdigit(b)) || \
+				(g_unichar_isspace(a) && g_unichar_isspace(b)) || \
+				(g_unichar_iswide(a) && g_unichar_iswide(b)))
+
+static const char *
+begin_word(const char *text, const char *begin)
+{
+	gunichar ch = 0;
+	while (text > begin && (!*text || g_unichar_isspace(g_utf8_get_char(text))))
+		text = g_utf8_find_prev_char(begin, text);
+	ch = g_utf8_get_char(text);
+	while ((text = g_utf8_find_prev_char(begin, text)) >= begin) {
+		gunichar cur = g_utf8_get_char(text);
+		if (!SAME(ch, cur))
+			break;
+	}
+
+	return (text ? g_utf8_find_next_char(text, NULL) : begin);
+}
+
+static const char *
+next_begin_word(const char *text, const char *end)
+{
+	gunichar ch = 0;
+	ch = g_utf8_get_char(text);
+	while ((text = g_utf8_find_next_char(text, end)) != NULL && text <= end) {
+		gunichar cur = g_utf8_get_char(text);
+		if (!SAME(ch, cur))
+			break;
+	}
+
+	while (text && text < end && g_unichar_isspace(g_utf8_get_char(text)))
+		text = g_utf8_find_next_char(text, end);
+	return (text ? text : end);
+}
+
+#undef SAME
+static gboolean
+move_back_word(GntBindable *bind, GList *null)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	const char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
+
+	if (iter < entry->start)
+		return TRUE;
+	iter = begin_word(iter, entry->start);
+	entry->cursor = (char*)iter;
+	if (entry->cursor < entry->scroll)
+		entry->scroll = entry->cursor;
+	entry_redraw(GNT_WIDGET(bind));
+	return TRUE;
+}
+
+static gboolean
+del_prev_word(GntBindable *bind, GList *null)
+{
+	GntWidget *widget = GNT_WIDGET(bind);
+	GntEntry *entry = GNT_ENTRY(bind);
+	char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
+	int count;
+
+	if (iter < entry->start)
+		return TRUE;
+	iter = (char*)begin_word(iter, entry->start);
+	count = entry->cursor - iter;
+	memmove(iter, entry->cursor, entry->end - entry->cursor);
+	entry->end -= count;
+	entry->cursor = iter;
+	if (entry->cursor <= entry->scroll) {
+		entry->scroll = entry->cursor - widget->priv.width + 2;
+		if (entry->scroll < entry->start)
+			entry->scroll = entry->start;
+	}
+	memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
+	entry_redraw(widget);
+	entry_text_changed(entry);
+
+	return TRUE;
+}
+
+static gboolean
+move_forward_word(GntBindable *bind, GList *list)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	GntWidget *widget = GNT_WIDGET(bind);
+	entry->cursor = (char *)next_begin_word(entry->cursor, entry->end);
+	while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width) {
+		entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
+	}
+	entry_redraw(widget);
+	return TRUE;
+}
+
+static gboolean
+delete_forward_word(GntBindable *bind, GList *list)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	GntWidget *widget = GNT_WIDGET(bind);
+	char *iter = (char *)next_begin_word(entry->cursor, entry->end);
+	int len = entry->end - iter + 1;
+	if (len <= 0)
+		return TRUE;
+	memmove(entry->cursor, iter, len);
+	len = iter - entry->cursor;
+	entry->end -= len;
+	memset(entry->end, '\0', len);
+	entry_redraw(widget);
+	entry_text_changed(entry);
+	return TRUE;
+}
+
+static gboolean
+gnt_entry_key_pressed(GntWidget *widget, const char *text)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+
+	if (text[0] == 27)
+	{
+		if (text[1] == 0)
+		{
+			destroy_suggest(entry);
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+	else
+	{
+		if (text[0] == '\t')
+		{
+			if (entry->ddown)
+				destroy_suggest(entry);
+			else if (entry->suggests)
+				return show_suggest_dropdown(entry);
+
+			return FALSE;
+		}
+		else if (text[0] == '\r' && entry->ddown)
+		{
+			char *text = g_strdup(gnt_tree_get_selection_data(GNT_TREE(entry->ddown)));
+			destroy_suggest(entry);
+			if (entry->word)
+			{
+				char *s = get_beginning_of_word(entry);
+				char *iter = text;
+				while (*iter && toupper(*s) == toupper(*iter))
+				{
+					*s++ = *iter++;
+				}
+				gnt_entry_key_pressed(widget, iter);
+			}
+			else
+			{
+				gnt_entry_set_text_internal(entry, text);
+			}
+			g_free(text);
+			entry_text_changed(entry);
+			return TRUE;
+		}
+
+		if (!iscntrl(text[0]))
+		{
+			const char *str, *next;
+
+			for (str = text; *str; str = next)
+			{
+				int len;
+				next = g_utf8_find_next_char(str, NULL);
+				len = next - str;
+
+				/* Valid input? */
+				/* XXX: Is it necessary to use _unichar_ variants here? */
+				if (ispunct(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_PUNCT))
+					continue;
+				if (isspace(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_SPACE))
+					continue;
+				if (isalpha(*str) && !(entry->flag & GNT_ENTRY_FLAG_ALPHA))
+					continue;
+				if (isdigit(*str) && !(entry->flag & GNT_ENTRY_FLAG_INT))
+					continue;
+
+				/* Reached the max? */
+				if (entry->max && g_utf8_pointer_to_offset(entry->start, entry->end) >= entry->max)
+					continue;
+
+				if (entry->end + len - entry->start >= entry->buffer)
+				{
+					/* This will cause the buffer to grow */
+					char *tmp = g_strdup(entry->start);
+					gnt_entry_set_text_internal(entry, tmp);
+					g_free(tmp);
+				}
+
+				memmove(entry->cursor + len, entry->cursor, entry->end - entry->cursor + 1);
+				entry->end += len;
+
+				while (str < next)
+				{
+					if (*str == '\r' || *str == '\n')
+						*entry->cursor = ' ';
+					else
+						*entry->cursor = *str;
+					entry->cursor++;
+					str++;
+				}
+
+				while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width)
+					entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
+
+				if (entry->ddown)
+					show_suggest_dropdown(entry);
+			}
+			entry_redraw(widget);
+			entry_text_changed(entry);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static void
+gnt_entry_destroy(GntWidget *widget)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	g_free(entry->start);
+
+	if (entry->history)
+	{
+		entry->history = g_list_first(entry->history);
+		g_list_foreach(entry->history, (GFunc)g_free, NULL);
+		g_list_free(entry->history);
+	}
+
+	if (entry->suggests)
+	{
+		g_list_foreach(entry->suggests, (GFunc)g_free, NULL);
+		g_list_free(entry->suggests);
+	}
+
+	if (entry->ddown)
+	{
+		gnt_widget_destroy(entry->ddown->parent);
+	}
+}
+
+static void
+gnt_entry_lost_focus(GntWidget *widget)
+{
+	GntEntry *entry = GNT_ENTRY(widget);
+	destroy_suggest(entry);
+	entry_redraw(widget);
+}
+
+static void
+gnt_entry_class_init(GntEntryClass *klass)
+{
+	GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass);
+	char s[2] = {erasechar(), 0};
+
+	parent_class = GNT_WIDGET_CLASS(klass);
+	parent_class->destroy = gnt_entry_destroy;
+	parent_class->draw = gnt_entry_draw;
+	parent_class->map = gnt_entry_map;
+	parent_class->size_request = gnt_entry_size_request;
+	parent_class->key_pressed = gnt_entry_key_pressed;
+	parent_class->lost_focus = gnt_entry_lost_focus;
+
+	signals[SIG_TEXT_CHANGED] =
+		g_signal_new("text_changed",
+					 G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST,
+					 G_STRUCT_OFFSET(GntEntryClass, text_changed),
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+
+	gnt_bindable_class_register_action(bindable, "cursor-home", move_start,
+				GNT_KEY_CTRL_A, NULL);
+	gnt_bindable_register_binding(bindable, "cursor-home", GNT_KEY_HOME, NULL);
+	gnt_bindable_class_register_action(bindable, "cursor-end", move_end,
+				GNT_KEY_CTRL_E, NULL);
+	gnt_bindable_register_binding(bindable, "cursor-end", GNT_KEY_END, NULL);
+	gnt_bindable_class_register_action(bindable, "delete-prev", backspace,
+				GNT_KEY_BACKSPACE, NULL);
+	gnt_bindable_register_binding(bindable, "delete-prev", s, NULL);
+	gnt_bindable_register_binding(bindable, "delete-prev", GNT_KEY_CTRL_H, NULL);
+	gnt_bindable_class_register_action(bindable, "delete-next", delkey,
+				GNT_KEY_DEL, NULL);
+	gnt_bindable_register_binding(bindable, "delete-next", GNT_KEY_CTRL_D, NULL);
+	gnt_bindable_class_register_action(bindable, "delete-start", del_to_home,
+				GNT_KEY_CTRL_U, NULL);
+	gnt_bindable_class_register_action(bindable, "delete-end", del_to_end,
+				GNT_KEY_CTRL_K, NULL);
+	gnt_bindable_class_register_action(bindable, "delete-prev-word", del_prev_word,
+				GNT_KEY_CTRL_W, NULL);
+	gnt_bindable_class_register_action(bindable, "cursor-prev-word", move_back_word,
+				"\033" "b", NULL);
+	gnt_bindable_class_register_action(bindable, "cursor-prev", move_back,
+				GNT_KEY_LEFT, NULL);
+	gnt_bindable_register_binding(bindable, "cursor-prev", GNT_KEY_CTRL_B, NULL);
+	gnt_bindable_class_register_action(bindable, "cursor-next", move_forward,
+				GNT_KEY_RIGHT, NULL);
+	gnt_bindable_register_binding(bindable, "cursor-next", GNT_KEY_CTRL_F, NULL);
+	gnt_bindable_class_register_action(bindable, "cursor-next-word", move_forward_word,
+				"\033" "f", NULL);
+	gnt_bindable_class_register_action(bindable, "delete-next-word", delete_forward_word,
+				"\033" "d", NULL);
+	gnt_bindable_class_register_action(bindable, "suggest-show", suggest_show,
+				"\t", NULL);
+	gnt_bindable_class_register_action(bindable, "suggest-next", suggest_next,
+				GNT_KEY_DOWN, NULL);
+	gnt_bindable_class_register_action(bindable, "suggest-prev", suggest_prev,
+				GNT_KEY_UP, NULL);
+	gnt_bindable_class_register_action(bindable, "history-prev", history_prev,
+				GNT_KEY_CTRL_DOWN, NULL);
+	gnt_bindable_class_register_action(bindable, "history-next", history_next,
+				GNT_KEY_CTRL_UP, NULL);
+	gnt_bindable_class_register_action(bindable, "clipboard-paste", clipboard_paste,
+				GNT_KEY_CTRL_V, NULL);
+
+	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
+	GNTDEBUG;
+}
+
+static void
+gnt_entry_init(GTypeInstance *instance, gpointer class)
+{
+	GntWidget *widget = GNT_WIDGET(instance);
+	GntEntry *entry = GNT_ENTRY(instance);
+
+	entry->flag = GNT_ENTRY_FLAG_ALL;
+	entry->max = 0;
+	
+	entry->histlength = 0;
+	entry->history = NULL;
+
+	entry->word = TRUE;
+	entry->always = FALSE;
+	entry->suggests = NULL;
+
+	GNT_WIDGET_SET_FLAGS(GNT_WIDGET(entry),
+			GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | GNT_WIDGET_CAN_TAKE_FOCUS);
+	GNT_WIDGET_SET_FLAGS(GNT_WIDGET(entry), GNT_WIDGET_GROW_X);
+
+	widget->priv.minw = 3;
+	widget->priv.minh = 1;
+	
+	GNTDEBUG;
+}
+
+/******************************************************************************
+ * GntEntry API
+ *****************************************************************************/
+GType
+gnt_entry_get_gtype(void)
+{
+	static GType type = 0;
+
+	if(type == 0)
+	{
+		static const GTypeInfo info = {
+			sizeof(GntEntryClass),
+			NULL,					/* base_init		*/
+			NULL,					/* base_finalize	*/
+			(GClassInitFunc)gnt_entry_class_init,
+			NULL,					/* class_finalize	*/
+			NULL,					/* class_data		*/
+			sizeof(GntEntry),
+			0,						/* n_preallocs		*/
+			gnt_entry_init,			/* instance_init	*/
+			NULL					/* value_table		*/
+		};
+
+		type = g_type_register_static(GNT_TYPE_WIDGET,
+									  "GntEntry",
+									  &info, 0);
+	}
+
+	return type;
+}
+
+GntWidget *gnt_entry_new(const char *text)
+{
+	GntWidget *widget = g_object_new(GNT_TYPE_ENTRY, NULL);
+	GntEntry *entry = GNT_ENTRY(widget);
+
+	gnt_entry_set_text_internal(entry, text);
+
+	return widget;
+}
+
+static void
+gnt_entry_set_text_internal(GntEntry *entry, const char *text)
+{
+	int len;
+	int scroll, cursor;
+
+	g_free(entry->start);
+
+	if (text && text[0])
+	{
+		len = strlen(text);
+	}
+	else
+	{
+		len = 0;
+	}
+
+	entry->buffer = len + 128;
+
+	scroll = entry->scroll - entry->start;
+	cursor = entry->end - entry->cursor;
+
+	entry->start = g_new0(char, entry->buffer);
+	if (text)
+		snprintf(entry->start, len + 1, "%s", text);
+	entry->end = entry->start + len;
+
+	entry->scroll = entry->start + scroll;
+	entry->cursor = entry->end - cursor;
+
+	if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(entry), GNT_WIDGET_MAPPED))
+		entry_redraw(GNT_WIDGET(entry));
+}
+
+void gnt_entry_set_text(GntEntry *entry, const char *text)
+{
+	gboolean changed = TRUE;
+	if (text == NULL && entry->start == NULL)
+		changed = FALSE;
+	if (text && entry->start && g_utf8_collate(text, entry->start) == 0)
+		changed = FALSE;
+	gnt_entry_set_text_internal(entry, text);
+	if (changed)
+		entry_text_changed(entry);
+}
+
+void gnt_entry_set_max(GntEntry *entry, int max)
+{
+	entry->max = max;
+}
+
+void gnt_entry_set_flag(GntEntry *entry, GntEntryFlag flag)
+{
+	entry->flag = flag;
+	/* XXX: Check the existing string to make sure the flags are respected? */
+}
+
+const char *gnt_entry_get_text(GntEntry *entry)
+{
+	return entry->start;
+}
+
+void gnt_entry_clear(GntEntry *entry)
+{
+	gnt_entry_set_text_internal(entry, NULL);
+	entry->scroll = entry->cursor = entry->end = entry->start;
+	entry_redraw(GNT_WIDGET(entry));
+	destroy_suggest(entry);
+	entry_text_changed(entry);
+}
+
+void gnt_entry_set_masked(GntEntry *entry, gboolean set)
+{
+	entry->masked = set;
+}
+
+void gnt_entry_add_to_history(GntEntry *entry, const char *text)
+{
+	g_return_if_fail(entry->history != NULL);   /* Need to set_history_length first */
+
+	if (g_list_length(entry->history) >= entry->histlength)
+		return;
+
+	entry->history = g_list_first(entry->history);
+	g_free(entry->history->data);
+	entry->history->data = g_strdup(text);
+	entry->history = g_list_prepend(entry->history, NULL);
+}
+
+void gnt_entry_set_history_length(GntEntry *entry, int num)
+{
+	if (num == 0)
+	{
+		entry->histlength = num;
+		if (entry->history)
+		{
+			entry->history = g_list_first(entry->history);
+			g_list_foreach(entry->history, (GFunc)g_free, NULL);
+			g_list_free(entry->history);
+			entry->history = NULL;
+		}
+		return;
+	}
+
+	if (entry->histlength == 0)
+	{
+		entry->histlength = num;
+		entry->history = g_list_append(NULL, NULL);
+		return;
+	}
+
+	if (num > 0 && num < entry->histlength)
+	{
+		GList *first, *iter;
+		int index = 0;
+		for (first = entry->history, index = 0; first->prev; first = first->prev, index++);
+		while ((iter = g_list_nth(first, num)) != NULL)
+		{
+			g_free(iter->data);
+			first = g_list_delete_link(first, iter);
+		}
+		entry->histlength = num;
+		if (index >= num)
+			entry->history = g_list_last(first);
+		return;
+	}
+
+	entry->histlength = num;
+}
+
+void gnt_entry_set_word_suggest(GntEntry *entry, gboolean word)
+{
+	entry->word = word;
+}
+
+void gnt_entry_set_always_suggest(GntEntry *entry, gboolean always)
+{
+	entry->always = always;
+}
+
+void gnt_entry_add_suggest(GntEntry *entry, const char *text)
+{
+	GList *find;
+
+	if (!text || !*text)
+		return;
+	
+	find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
+	if (find)
+		return;
+	entry->suggests = g_list_append(entry->suggests, g_strdup(text));
+}
+
+void gnt_entry_remove_suggest(GntEntry *entry, const char *text)
+{
+	GList *find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
+	if (find)
+	{
+		g_free(find->data);
+		entry->suggests = g_list_delete_link(entry->suggests, find);
+	}
+}
+