Mercurial > grim > libgnt
view gntentry.c @ 1320:93fc8c41ded2
Fix incorrect Since tags.
Anything on default-only would never have gone out in 2.8.0; update them to
3.0.0.
author | Elliott Sales de Andrade <quantum.analyst@gmail.com> |
---|---|
date | Sun, 19 May 2019 02:50:51 -0400 |
parents | 2b331e084d56 |
children | d61c212123f5 |
line wrap: on
line source
/* * GNT - The GLib Ncurses Toolkit * * GNT is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <ctype.h> #include <string.h> #include "gntinternal.h" #include "gntbox.h" #include "gntentry.h" #include "gntstyle.h" #include "gnttree.h" #include "gntutils.h" #include "gntwidgetprivate.h" enum { SIG_TEXT_CHANGED, SIG_COMPLETION, SIGS, }; typedef enum { ENTRY_JAIL = -1, /* Suspend the kill ring. */ ENTRY_DEL_BWD_WORD = 1, ENTRY_DEL_BWD_CHAR, ENTRY_DEL_FWD_WORD, ENTRY_DEL_FWD_CHAR, ENTRY_DEL_EOL, ENTRY_DEL_BOL, } GntEntryAction; typedef struct { GString *buffer; GntEntryAction last; } GntEntryKillRing; typedef struct { char *needle; } GntEntrySearch; typedef struct { GntEntryFlag flag; char *start; char *end; char *scroll; /* Current scrolling position */ char *cursor; /* Cursor location */ /* 0 <= cursor - scroll < widget-width */ size_t buffer; /* Size of the buffer */ int max; /* 0 means infinite */ gboolean masked; GList *history; /* History of the strings. User can use this by pressing ctrl+up/down */ int histlength; /* How long can the history be? */ GList *suggests; /* List of suggestions */ gboolean word; /* Are the suggestions for only a word, or for the whole thing? */ gboolean always; /* Should the list of suggestions show at all times, or only on tab-press? */ GntWidget *ddown; /* The dropdown with the suggested list */ GntEntryKillRing *killring; GntEntrySearch *search; } GntEntryPrivate; static guint signals[SIGS] = { 0 }; static gboolean gnt_entry_key_pressed(GntWidget *widget, const char *text); static void gnt_entry_set_text_internal(GntEntry *entry, const char *text); G_DEFINE_TYPE_WITH_PRIVATE(GntEntry, gnt_entry, GNT_TYPE_WIDGET) static gboolean update_kill_ring(GntEntryPrivate *priv, GntEntryAction action, const char *text, int len) { if (action < 0) { priv->killring->last = action; return FALSE; } if (len == 0) len = strlen(text); else if (len < 0) { text += len; len = -len; } if (action != priv->killring->last) { struct { GntEntryAction one; GntEntryAction two; } merges[] = { {ENTRY_DEL_BWD_WORD, ENTRY_DEL_FWD_WORD}, {ENTRY_DEL_BWD_CHAR, ENTRY_DEL_FWD_CHAR}, {ENTRY_DEL_BOL, ENTRY_DEL_EOL}, {ENTRY_JAIL, ENTRY_JAIL}, }; int i; for (i = 0; merges[i].one != ENTRY_JAIL; i++) { if (merges[i].one == priv->killring->last && merges[i].two == action) { g_string_append_len(priv->killring->buffer, text, len); break; } else if (merges[i].one == action && merges[i].two == priv->killring->last) { g_string_prepend_len(priv->killring->buffer, text, len); break; } } if (merges[i].one == ENTRY_JAIL) { g_string_assign(priv->killring->buffer, text); g_string_truncate(priv->killring->buffer, len); } priv->killring->last = action; } else { if (action == ENTRY_DEL_BWD_CHAR || action == ENTRY_DEL_BWD_WORD) g_string_prepend_len(priv->killring->buffer, text, len); else g_string_append_len(priv->killring->buffer, text, len); } return TRUE; } static void destroy_suggest(GntEntryPrivate *priv) { if (priv->ddown) { gnt_widget_destroy(gnt_widget_get_parent(priv->ddown)); priv->ddown = NULL; } } static char * get_beginning_of_word(GntEntryPrivate *priv) { char *s = priv->cursor; while (s > priv->start) { char *t = g_utf8_find_prev_char(priv->start, s); if (isspace(*t)) break; s = t; } return s; } static gboolean complete_suggest(GntEntry *entry, const char *text) { GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); int offstart = 0, offend = 0; if (priv->word) { char *s = get_beginning_of_word(priv); const char *iter = text; offstart = g_utf8_pointer_to_offset(priv->start, s); while (*iter && toupper(*s) == toupper(*iter)) { *s++ = *iter++; } if (*iter) { gnt_entry_key_pressed(GNT_WIDGET(entry), iter); } offend = g_utf8_pointer_to_offset(priv->start, priv->cursor); } else { offstart = 0; gnt_entry_set_text_internal(entry, text); offend = g_utf8_strlen(text, -1); } g_signal_emit(G_OBJECT(entry), signals[SIG_COMPLETION], 0, priv->start + offstart, priv->start + offend); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); return TRUE; } static int max_common_prefix(const char *s, const char *t) { const char *f = s; while (*f && *t && *f == *t++) f++; return f - s; } static gboolean show_suggest_dropdown(GntEntry *entry) { GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); char *suggest = NULL; gsize len; int offset = 0, x, y; int count = 0; GList *iter; const char *text = NULL; const char *sgst = NULL; int max = -1; if (priv->word) { char *s = get_beginning_of_word(priv); suggest = g_strndup(s, priv->cursor - s); if (priv->scroll < s) { offset = gnt_util_onscreen_width(priv->scroll, s); } } else { suggest = g_strdup(priv->start); } len = strlen(suggest); /* Don't need to use the utf8-function here */ if (priv->ddown == NULL) { GntWidget *box = gnt_vbox_new(FALSE); priv->ddown = gnt_tree_new(); gnt_tree_set_compare_func(GNT_TREE(priv->ddown), (GCompareFunc)g_utf8_collate); gnt_box_add_widget(GNT_BOX(box), priv->ddown); gnt_widget_set_transient(box, TRUE); 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(priv->ddown)); } for (count = 0, iter = priv->suggests; iter; iter = iter->next) { text = iter->data; if (g_ascii_strncasecmp(suggest, text, len) == 0 && strlen(text) >= len) { gnt_tree_add_row_after( GNT_TREE(priv->ddown), (gpointer)text, gnt_tree_create_row(GNT_TREE(priv->ddown), text), NULL, NULL); count++; if (max == -1) max = strlen(text) - len; else if (max) max = MIN(max, max_common_prefix(sgst + len, text + len)); sgst = text; } } g_free(suggest); if (count == 0) { destroy_suggest(priv); return FALSE; } else if (count == 1) { char *store = g_strndup(priv->start, priv->end - priv->start); gboolean ret; destroy_suggest(priv); complete_suggest(entry, sgst); ret = (strncmp(store, priv->start, priv->end - priv->start) != 0); g_free(store); return ret; } else { if (max > 0) { GntWidget *ddown = priv->ddown; char *match = g_strndup(sgst + len, max); priv->ddown = NULL; gnt_entry_key_pressed(GNT_WIDGET(entry), match); g_free(match); if (priv->ddown) { gnt_widget_destroy(ddown); } else { priv->ddown = ddown; } } gnt_widget_draw(gnt_widget_get_parent(priv->ddown)); } return TRUE; } static void gnt_entry_draw(GntWidget *widget) { GntEntry *entry = GNT_ENTRY(widget); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); WINDOW *window = gnt_widget_get_window(widget); gint width; int stop; gboolean focus; int curpos; if ((focus = gnt_widget_has_focus(widget))) wbkgdset(window, '\0' | gnt_color_pair(GNT_COLOR_TEXT_NORMAL)); else wbkgdset(window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); if (priv->masked) { mvwhline(window, 0, 0, gnt_ascii_only() ? '*' : ACS_BULLET, g_utf8_pointer_to_offset(priv->scroll, priv->end)); } else mvwprintw(window, 0, 0, "%s", C_(priv->scroll)); stop = gnt_util_onscreen_width(priv->scroll, priv->end); gnt_widget_get_internal_size(GNT_WIDGET(entry), &width, NULL); if (stop < width) { mvwhline(window, 0, stop, GNT_ENTRY_CHAR, width - stop); } curpos = gnt_util_onscreen_width(priv->scroll, priv->cursor); if (focus) { mvwchgat(window, 0, curpos, 1, A_REVERSE, GNT_COLOR_TEXT_NORMAL, NULL); } (void)wmove(window, 0, curpos); } static void gnt_entry_size_request(GntWidget *widget) { if (!gnt_widget_get_mapped(widget)) { gnt_widget_set_internal_size(widget, 20, 1); } } static void gnt_entry_map(GntWidget *widget) { gint width, height; gnt_widget_get_internal_size(widget, &width, &height); if (width == 0 || height == 0) { gnt_widget_size_request(widget); } } 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, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->cursor <= priv->start) { return FALSE; } priv->cursor = g_utf8_find_prev_char(priv->start, priv->cursor); if (priv->cursor < priv->scroll) { priv->scroll = priv->cursor; } update_kill_ring(priv, ENTRY_JAIL, NULL, 0); entry_redraw(GNT_WIDGET(entry)); return TRUE; } static void scroll_to_fit(GntEntry *entry) { GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); gint width; gnt_widget_get_internal_size(GNT_WIDGET(entry), &width, NULL); while (gnt_util_onscreen_width(priv->scroll, priv->cursor) >= width) { priv->scroll = g_utf8_find_next_char(priv->scroll, NULL); } } static gboolean move_forward(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->cursor >= priv->end) { return FALSE; } priv->cursor = g_utf8_find_next_char(priv->cursor, NULL); scroll_to_fit(entry); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); entry_redraw(GNT_WIDGET(entry)); return TRUE; } static gboolean backspace(GntBindable *bind, G_GNUC_UNUSED GList *params) { int len; GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->cursor <= priv->start) { return TRUE; } len = priv->cursor - g_utf8_find_prev_char(priv->start, priv->cursor); update_kill_ring(priv, ENTRY_JAIL, priv->cursor, -len); priv->cursor -= len; memmove(priv->cursor, priv->cursor + len, priv->end - priv->cursor); priv->end -= len; if (priv->scroll > priv->start) { priv->scroll = g_utf8_find_prev_char(priv->start, priv->scroll); } entry_redraw(GNT_WIDGET(entry)); if (priv->ddown) { show_suggest_dropdown(entry); } entry_text_changed(entry); return TRUE; } static gboolean delkey(GntBindable *bind, G_GNUC_UNUSED GList *params) { int len; GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->cursor >= priv->end) { return FALSE; } len = g_utf8_find_next_char(priv->cursor, NULL) - priv->cursor; update_kill_ring(priv, ENTRY_JAIL, priv->cursor, len); memmove(priv->cursor, priv->cursor + len, priv->end - priv->cursor - len + 1); priv->end -= len; entry_redraw(GNT_WIDGET(entry)); if (priv->ddown) { show_suggest_dropdown(entry); } entry_text_changed(entry); return TRUE; } static gboolean move_start(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); priv->scroll = priv->cursor = priv->start; entry_redraw(GNT_WIDGET(entry)); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); return TRUE; } static gboolean move_end(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); priv->cursor = priv->end; /* This should be better than this */ scroll_to_fit(entry); entry_redraw(GNT_WIDGET(entry)); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); return TRUE; } static gboolean history_next(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->histlength && priv->history->prev) { priv->history = priv->history->prev; gnt_entry_set_text_internal(entry, priv->history->data); destroy_suggest(priv); entry_text_changed(entry); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); return TRUE; } return FALSE; } static gboolean history_prev(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->histlength && priv->history->next) { if (priv->history->prev == NULL) { /* Save the current contents */ char *text = g_strdup(gnt_entry_get_text(entry)); g_free(priv->history->data); priv->history->data = text; } priv->history = priv->history->next; gnt_entry_set_text_internal(entry, priv->history->data); destroy_suggest(priv); entry_text_changed(entry); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); return TRUE; } return FALSE; } static gboolean history_search(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); GList *iter; const char *current; if (priv->history->prev && priv->search->needle) { current = priv->search->needle; } else { current = gnt_entry_get_text(entry); } if (!priv->histlength || !priv->history->next || !*current) { return FALSE; } for (iter = priv->history->next; iter; iter = iter->next) { const char *str = iter->data; /* A more utf8-friendly version of strstr would have been better, but * for now, this will have to do. */ if (strstr(str, current) != NULL) break; } if (!iter) return TRUE; if (priv->history->prev == NULL) { /* We are doing it for the first time. Save the current contents */ char *text = g_strdup(gnt_entry_get_text(entry)); g_free(priv->search->needle); priv->search->needle = g_strdup(current); g_free(priv->history->data); priv->history->data = text; } priv->history = iter; gnt_entry_set_text_internal(entry, priv->history->data); destroy_suggest(priv); entry_text_changed(entry); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); return TRUE; } static gboolean clipboard_paste(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); 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(priv->start, priv->cursor - priv->start); all = g_strconcat(a, text, priv->cursor, NULL); gnt_entry_set_text_internal(entry, all); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); g_free(a); g_free(text); g_free(all); return TRUE; } static gboolean suggest_show(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->ddown) { gnt_bindable_perform_action_named(GNT_BINDABLE(priv->ddown), "move-down", NULL); return TRUE; } return show_suggest_dropdown(entry); } static gboolean suggest_next(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->ddown) { gnt_bindable_perform_action_named(GNT_BINDABLE(priv->ddown), "move-down", NULL); return TRUE; } return FALSE; } static gboolean suggest_prev(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->ddown) { gnt_bindable_perform_action_named(GNT_BINDABLE(priv->ddown), "move-up", NULL); return TRUE; } return FALSE; } static gboolean suggest_next_page(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->ddown) { gnt_bindable_perform_action_named(GNT_BINDABLE(priv->ddown), "page-down", NULL); return TRUE; } return FALSE; } static gboolean suggest_prev_page(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->ddown) { gnt_bindable_perform_action_named(GNT_BINDABLE(priv->ddown), "page-up", NULL); return TRUE; } return FALSE; } static gboolean del_to_home(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->cursor <= priv->start) { return TRUE; } update_kill_ring(priv, ENTRY_DEL_BOL, priv->start, priv->cursor - priv->start); memmove(priv->start, priv->cursor, priv->end - priv->cursor); priv->end -= (priv->cursor - priv->start); priv->cursor = priv->scroll = priv->start; memset(priv->end, '\0', priv->buffer - (priv->end - priv->start)); entry_redraw(GNT_WIDGET(bind)); entry_text_changed(entry); return TRUE; } static gboolean del_to_end(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (priv->end <= priv->cursor) { return TRUE; } update_kill_ring(priv, ENTRY_DEL_EOL, priv->cursor, priv->end - priv->cursor); priv->end = priv->cursor; memset(priv->end, '\0', priv->buffer - (priv->end - priv->start)); entry_redraw(GNT_WIDGET(bind)); entry_text_changed(entry); return TRUE; } #define SAME(a,b) ((g_unichar_isalnum(a) && g_unichar_isalnum(b)) || \ (g_unichar_isspace(a) && g_unichar_isspace(b)) || \ (g_unichar_iswide(a) && g_unichar_iswide(b)) || \ (g_unichar_ispunct(a) && g_unichar_ispunct(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; while (text && text < end && g_unichar_isspace(g_utf8_get_char(text))) text = g_utf8_find_next_char(text, end); if (text) { 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; } } return (text ? text : end); } #undef SAME static gboolean move_back_word(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); const char *iter = g_utf8_find_prev_char(priv->start, priv->cursor); if (iter < priv->start) { return TRUE; } iter = begin_word(iter, priv->start); priv->cursor = (char *)iter; if (priv->cursor < priv->scroll) { priv->scroll = priv->cursor; } update_kill_ring(priv, ENTRY_JAIL, NULL, 0); entry_redraw(GNT_WIDGET(bind)); return TRUE; } static gboolean del_prev_word(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntWidget *widget = GNT_WIDGET(bind); GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); char *iter = g_utf8_find_prev_char(priv->start, priv->cursor); int count; if (iter < priv->start) { return TRUE; } iter = (char *)begin_word(iter, priv->start); count = priv->cursor - iter; update_kill_ring(priv, ENTRY_DEL_BWD_WORD, iter, count); memmove(iter, priv->cursor, priv->end - priv->cursor); priv->end -= count; priv->cursor = iter; if (priv->cursor <= priv->scroll) { gint width; gnt_widget_get_internal_size(widget, &width, NULL); priv->scroll = priv->cursor - width + 2; if (priv->scroll < priv->start) { priv->scroll = priv->start; } } memset(priv->end, '\0', priv->buffer - (priv->end - priv->start)); entry_redraw(widget); entry_text_changed(entry); return TRUE; } static gboolean move_forward_word(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); GntWidget *widget = GNT_WIDGET(bind); priv->cursor = (char *)next_begin_word(priv->cursor, priv->end); scroll_to_fit(entry); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); entry_redraw(widget); return TRUE; } static gboolean delete_forward_word(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); GntWidget *widget = GNT_WIDGET(bind); char *iter = (char *)next_begin_word(priv->cursor, priv->end); int len = priv->end - iter + 1; if (len <= 0) return TRUE; update_kill_ring(priv, ENTRY_DEL_FWD_WORD, priv->cursor, iter - priv->cursor); memmove(priv->cursor, iter, len); len = iter - priv->cursor; priv->end -= len; memset(priv->end, '\0', len); entry_redraw(widget); entry_text_changed(entry); return TRUE; } static gboolean transpose_chars(GntBindable *bind, GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); char *current, *prev; char hold[8]; /* that's right */ if (priv->cursor <= priv->start) { return FALSE; } if (!*priv->cursor) { priv->cursor = g_utf8_find_prev_char(priv->start, priv->cursor); } current = priv->cursor; prev = g_utf8_find_prev_char(priv->start, priv->cursor); move_forward(bind, params); /* Let's do this dance! */ memcpy(hold, prev, current - prev); memmove(prev, current, priv->cursor - current); memcpy(prev + (priv->cursor - current), hold, current - prev); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); entry_redraw(GNT_WIDGET(entry)); entry_text_changed(entry); return TRUE; } static gboolean entry_yank(GntBindable *bind, G_GNUC_UNUSED GList *params) { GntEntry *entry = GNT_ENTRY(bind); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); gnt_entry_key_pressed(GNT_WIDGET(entry), priv->killring->buffer->str); return TRUE; } static gboolean gnt_entry_key_pressed(GntWidget *widget, const char *text) { GntEntry *entry = GNT_ENTRY(widget); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); if (text[0] == 27) { if (text[1] == 0) { destroy_suggest(priv); return TRUE; } return FALSE; } if ((text[0] == '\r' || text[0] == ' ' || text[0] == '\n') && priv->ddown) { char *text = g_strdup( gnt_tree_get_selection_data(GNT_TREE(priv->ddown))); destroy_suggest(priv); complete_suggest(entry, text); g_free(text); update_kill_ring(priv, ENTRY_JAIL, NULL, 0); entry_text_changed(entry); return TRUE; } if (!iscntrl(text[0])) { const char *str, *next; for (str = text; *str; str = next) { gsize 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) && (priv->flag & GNT_ENTRY_FLAG_NO_PUNCT)) { continue; } if (isspace(*str) && (priv->flag & GNT_ENTRY_FLAG_NO_SPACE)) { continue; } if (isalpha(*str) && !(priv->flag & GNT_ENTRY_FLAG_ALPHA)) { continue; } if (isdigit(*str) && !(priv->flag & GNT_ENTRY_FLAG_INT)) { continue; } /* Reached the max? */ if (priv->max && g_utf8_pointer_to_offset(priv->start, priv->end) >= priv->max) { continue; } if ((gsize)(priv->end + len - priv->start) >= priv->buffer) { /* This will cause the buffer to grow */ char *tmp = g_strdup(priv->start); gnt_entry_set_text_internal(entry, tmp); g_free(tmp); } memmove(priv->cursor + len, priv->cursor, priv->end - priv->cursor + 1); priv->end += len; while (str < next) { if (*str == '\r' || *str == '\n') *priv->cursor = ' '; else *priv->cursor = *str; priv->cursor++; str++; } scroll_to_fit(entry); if (priv->ddown) { show_suggest_dropdown(entry); } } update_kill_ring(priv, ENTRY_JAIL, NULL, 0); entry_redraw(widget); entry_text_changed(entry); return TRUE; } if (text[0] == '\r' || text[0] == '\n') { gnt_widget_activate(widget); return TRUE; } return FALSE; } static void jail_killring(GntEntryKillRing *kr) { g_string_free(kr->buffer, TRUE); g_free(kr); } static void gnt_entry_destroy(GntWidget *widget) { GntEntry *entry = GNT_ENTRY(widget); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); g_free(priv->start); if (priv->history) { priv->history = g_list_first(priv->history); g_list_free_full(priv->history, g_free); } if (priv->suggests) { g_list_free_full(priv->suggests, g_free); } if (priv->ddown) { gnt_widget_destroy(gnt_widget_get_parent(priv->ddown)); } g_free(priv->search->needle); g_free(priv->search); jail_killring(priv->killring); } static void gnt_entry_lost_focus(GntWidget *widget) { GntEntry *entry = GNT_ENTRY(widget); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); destroy_suggest(priv); entry_redraw(widget); } static gboolean gnt_entry_clicked(GntWidget *widget, GntMouseEvent event, G_GNUC_UNUSED int x, G_GNUC_UNUSED int y) { if (event == GNT_MIDDLE_MOUSE_DOWN) { clipboard_paste(GNT_BINDABLE(widget), NULL); return TRUE; } return FALSE; } static void gnt_entry_class_init(GntEntryClass *klass) { GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass); GntWidgetClass *widget_class = GNT_WIDGET_CLASS(klass); char s[3] = {'\033', erasechar(), 0}; widget_class->clicked = gnt_entry_clicked; widget_class->destroy = gnt_entry_destroy; widget_class->draw = gnt_entry_draw; widget_class->map = gnt_entry_map; widget_class->size_request = gnt_entry_size_request; widget_class->key_pressed = gnt_entry_key_pressed; widget_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, NULL, G_TYPE_NONE, 0); /** * GntEntry::completion: * * Since: 2.1.0 */ signals[SIG_COMPLETION] = g_signal_new("completion", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER); 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 + 1, 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_register_binding(bindable, "delete-prev-word", s, 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, "transpose-chars", transpose_chars, GNT_KEY_CTRL_T, NULL); gnt_bindable_class_register_action(bindable, "yank", entry_yank, GNT_KEY_CTRL_Y, 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, "suggest-next-page", suggest_next_page, GNT_KEY_PGDOWN, NULL); gnt_bindable_class_register_action(bindable, "suggest-prev-page", suggest_prev_page, GNT_KEY_PGUP, NULL); gnt_bindable_class_register_action(bindable, "history-next", history_next, GNT_KEY_CTRL_DOWN, NULL); gnt_bindable_class_register_action(bindable, "history-prev", history_prev, GNT_KEY_CTRL_UP, NULL); gnt_bindable_register_binding(bindable, "history-prev", GNT_KEY_CTRL_P, NULL); gnt_bindable_register_binding(bindable, "history-next", GNT_KEY_CTRL_N, NULL); gnt_bindable_class_register_action(bindable, "history-search", history_search, GNT_KEY_CTRL_R, 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)); } static GntEntryKillRing * new_killring(void) { GntEntryKillRing *kr = g_new0(GntEntryKillRing, 1); kr->buffer = g_string_new(NULL); return kr; } static void gnt_entry_init(GntEntry *entry) { GntWidget *widget = GNT_WIDGET(entry); GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); priv->flag = GNT_ENTRY_FLAG_ALL; priv->max = 0; priv->histlength = 0; priv->history = NULL; priv->word = TRUE; priv->always = FALSE; priv->suggests = NULL; priv->killring = new_killring(); priv->search = g_new0(GntEntrySearch, 1); gnt_widget_set_has_border(widget, FALSE); gnt_widget_set_has_shadow(widget, FALSE); gnt_widget_set_take_focus(widget, TRUE); gnt_widget_set_grow_x(widget, TRUE); gnt_widget_set_minimum_size(widget, 3, 1); } /****************************************************************************** * GntEntry API *****************************************************************************/ 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) { GntEntryPrivate *priv = gnt_entry_get_instance_private(entry); int len; int scroll, cursor; g_free(priv->start); if (text && text[0]) { len = strlen(text); } else { len = 0; } priv->buffer = len + 128; scroll = priv->scroll - priv->start; cursor = priv->end - priv->cursor; priv->start = g_new0(char, priv->buffer); if (text) { snprintf(priv->start, len + 1, "%s", text); } priv->end = priv->start + len; if ((priv->scroll = priv->start + scroll) > priv->end) { priv->scroll = priv->end; } if ((priv->cursor = priv->end - cursor) > priv->end) { priv->cursor = priv->end; } if (gnt_widget_get_mapped(GNT_WIDGET(entry))) entry_redraw(GNT_WIDGET(entry)); } void gnt_entry_set_text(GntEntry *entry, const char *text) { GntEntryPrivate *priv = NULL; gboolean changed = TRUE; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); if (text == NULL && priv->start == NULL) { changed = FALSE; } if (text && priv->start && g_utf8_collate(text, priv->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) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); priv->max = max; } void gnt_entry_set_flag(GntEntry *entry, GntEntryFlag flag) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); priv->flag = flag; /* XXX: Check the existing string to make sure the flags are respected? */ } const char *gnt_entry_get_text(GntEntry *entry) { GntEntryPrivate *priv = NULL; g_return_val_if_fail(GNT_IS_ENTRY(entry), NULL); priv = gnt_entry_get_instance_private(entry); return priv->start; } void gnt_entry_clear(GntEntry *entry) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); gnt_entry_set_text_internal(entry, NULL); priv->scroll = priv->cursor = priv->end = priv->start; entry_redraw(GNT_WIDGET(entry)); destroy_suggest(priv); entry_text_changed(entry); } void gnt_entry_set_masked(GntEntry *entry, gboolean set) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); priv->masked = set; } void gnt_entry_add_to_history(GntEntry *entry, const char *text) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); /* Must have called set_history_length first */ g_return_if_fail(priv->history != NULL); if (priv->histlength >= 0 && g_list_length(priv->history) >= (gsize)priv->histlength) { return; } priv->history = g_list_first(priv->history); g_free(priv->history->data); priv->history->data = g_strdup(text); priv->history = g_list_prepend(priv->history, NULL); } void gnt_entry_set_history_length(GntEntry *entry, int num) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); if (num == 0) { priv->histlength = num; if (priv->history) { priv->history = g_list_first(priv->history); g_list_free_full(priv->history, g_free); priv->history = NULL; } return; } if (priv->histlength == 0) { priv->histlength = num; priv->history = g_list_append(NULL, NULL); return; } if (num > 0 && num < priv->histlength) { GList *first, *iter; int index = 0; for (first = priv->history, index = 0; first->prev; first = first->prev, index++) { /* Nothing. */ } while ((iter = g_list_nth(first, num)) != NULL) { g_free(iter->data); first = g_list_delete_link(first, iter); } priv->histlength = num; if (index >= num) priv->history = g_list_last(first); return; } priv->histlength = num; } void gnt_entry_set_word_suggest(GntEntry *entry, gboolean word) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); priv->word = word; } void gnt_entry_set_always_suggest(GntEntry *entry, gboolean always) { GntEntryPrivate *priv = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); priv->always = always; } void gnt_entry_add_suggest(GntEntry *entry, const char *text) { GntEntryPrivate *priv = NULL; GList *find; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); if (!text || !*text) return; find = g_list_find_custom(priv->suggests, text, (GCompareFunc)g_utf8_collate); if (find) return; priv->suggests = g_list_append(priv->suggests, g_strdup(text)); } void gnt_entry_remove_suggest(GntEntry *entry, const char *text) { GntEntryPrivate *priv = NULL; GList *find = NULL; g_return_if_fail(GNT_IS_ENTRY(entry)); priv = gnt_entry_get_instance_private(entry); find = g_list_find_custom(priv->suggests, text, (GCompareFunc)g_utf8_collate); if (find) { g_free(find->data); priv->suggests = g_list_delete_link(priv->suggests, find); } }