Add missing AUTHORS and ChangeLog.
[gnt.git] / gntentry.c
blobb07194332b3065820d68593de470c516479549de
1 /**
2 * GNT - The GLib Ncurses Toolkit
4 * GNT is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This library is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include <ctype.h>
24 #include <string.h>
26 #include "gntbox.h"
27 #include "gntentry.h"
28 #include "gntmarshal.h"
29 #include "gntstyle.h"
30 #include "gnttree.h"
31 #include "gntutils.h"
33 enum
35 SIG_TEXT_CHANGED,
36 SIG_COMPLETION,
37 SIGS,
40 typedef enum
42 ENTRY_JAIL = -1, /* Suspend the kill ring. */
43 ENTRY_DEL_BWD_WORD = 1,
44 ENTRY_DEL_BWD_CHAR,
45 ENTRY_DEL_FWD_WORD,
46 ENTRY_DEL_FWD_CHAR,
47 ENTRY_DEL_EOL,
48 ENTRY_DEL_BOL,
49 } GntEntryAction;
51 struct _GntEntryKillRing
53 GString *buffer;
54 GntEntryAction last;
57 static guint signals[SIGS] = { 0 };
59 static GntWidgetClass *parent_class = NULL;
61 static gboolean gnt_entry_key_pressed(GntWidget *widget, const char *text);
62 static void gnt_entry_set_text_internal(GntEntry *entry, const char *text);
64 static gboolean
65 update_kill_ring(GntEntry *entry, GntEntryAction action, const char *text, int len)
67 if (action < 0) {
68 entry->killring->last = action;
69 return FALSE;
72 if (len == 0)
73 len = strlen(text);
74 else if (len < 0) {
75 text += len;
76 len = -len;
79 if (action != entry->killring->last) {
80 struct {
81 GntEntryAction one;
82 GntEntryAction two;
83 } merges[] = {
84 {ENTRY_DEL_BWD_WORD, ENTRY_DEL_FWD_WORD},
85 {ENTRY_DEL_BWD_CHAR, ENTRY_DEL_FWD_CHAR},
86 {ENTRY_DEL_BOL, ENTRY_DEL_EOL},
87 {ENTRY_JAIL, ENTRY_JAIL},
89 int i;
91 for (i = 0; merges[i].one != ENTRY_JAIL; i++) {
92 if (merges[i].one == entry->killring->last &&
93 merges[i].two == action) {
94 g_string_append_len(entry->killring->buffer, text, len);
95 break;
96 } else if (merges[i].one == action &&
97 merges[i].two == entry->killring->last) {
98 g_string_prepend_len(entry->killring->buffer, text, len);
99 break;
102 if (merges[i].one == ENTRY_JAIL) {
103 g_string_assign(entry->killring->buffer, text);
104 g_string_truncate(entry->killring->buffer, len);
106 entry->killring->last = action;
107 } else {
108 if (action == ENTRY_DEL_BWD_CHAR || action == ENTRY_DEL_BWD_WORD)
109 g_string_prepend_len(entry->killring->buffer, text, len);
110 else
111 g_string_append_len(entry->killring->buffer, text, len);
113 return TRUE;
116 static void
117 destroy_suggest(GntEntry *entry)
119 if (entry->ddown)
121 gnt_widget_destroy(entry->ddown->parent);
122 entry->ddown = NULL;
126 static char *
127 get_beginning_of_word(GntEntry *entry)
129 char *s = entry->cursor;
130 while (s > entry->start)
132 char *t = g_utf8_find_prev_char(entry->start, s);
133 if (isspace(*t))
134 break;
135 s = t;
137 return s;
140 static gboolean
141 complete_suggest(GntEntry *entry, const char *text)
143 gboolean changed = FALSE;
144 int offstart = 0, offend = 0;
146 if (entry->word) {
147 char *s = get_beginning_of_word(entry);
148 const char *iter = text;
149 offstart = g_utf8_pointer_to_offset(entry->start, s);
150 while (*iter && toupper(*s) == toupper(*iter)) {
151 if (*s != *iter)
152 changed = TRUE;
153 *s++ = *iter++;
155 if (*iter) {
156 gnt_entry_key_pressed(GNT_WIDGET(entry), iter);
157 changed = TRUE;
159 offend = g_utf8_pointer_to_offset(entry->start, entry->cursor);
160 } else {
161 offstart = 0;
162 gnt_entry_set_text_internal(entry, text);
163 changed = TRUE;
164 offend = g_utf8_strlen(text, -1);
167 if (changed)
168 g_signal_emit(G_OBJECT(entry), signals[SIG_COMPLETION], 0,
169 entry->start + offstart, entry->start + offend);
170 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
171 return changed;
174 static int
175 max_common_prefix(const char *s, const char *t)
177 const char *f = s;
178 while (*f && *t && *f == *t++)
179 f++;
180 return f - s;
183 static gboolean
184 show_suggest_dropdown(GntEntry *entry)
186 char *suggest = NULL;
187 int len;
188 int offset = 0, x, y;
189 int count = 0;
190 GList *iter;
191 const char *text = NULL;
192 const char *sgst = NULL;
193 int max = -1;
195 if (entry->word)
197 char *s = get_beginning_of_word(entry);
198 suggest = g_strndup(s, entry->cursor - s);
199 if (entry->scroll < s)
200 offset = gnt_util_onscreen_width(entry->scroll, s);
202 else
203 suggest = g_strdup(entry->start);
204 len = strlen(suggest); /* Don't need to use the utf8-function here */
206 if (entry->ddown == NULL)
208 GntWidget *box = gnt_vbox_new(FALSE);
209 entry->ddown = gnt_tree_new();
210 gnt_tree_set_compare_func(GNT_TREE(entry->ddown), (GCompareFunc)g_utf8_collate);
211 gnt_box_add_widget(GNT_BOX(box), entry->ddown);
213 GNT_WIDGET_SET_FLAGS(box, GNT_WIDGET_TRANSIENT);
215 gnt_widget_get_position(GNT_WIDGET(entry), &x, &y);
216 x += offset;
217 y++;
218 if (y + 10 >= getmaxy(stdscr))
219 y -= 11;
220 gnt_widget_set_position(box, x, y);
222 else
223 gnt_tree_remove_all(GNT_TREE(entry->ddown));
225 for (count = 0, iter = entry->suggests; iter; iter = iter->next)
227 text = iter->data;
228 if (g_ascii_strncasecmp(suggest, text, len) == 0 && strlen(text) >= len)
230 gnt_tree_add_row_after(GNT_TREE(entry->ddown), (gpointer)text,
231 gnt_tree_create_row(GNT_TREE(entry->ddown), text),
232 NULL, NULL);
233 count++;
234 if (max == -1)
235 max = strlen(text) - len;
236 else if (max)
237 max = MIN(max, max_common_prefix(sgst + len, text + len));
238 sgst = text;
241 g_free(suggest);
243 if (count == 0) {
244 destroy_suggest(entry);
245 return FALSE;
246 } else if (count == 1) {
247 destroy_suggest(entry);
248 return complete_suggest(entry, sgst);
249 } else {
250 if (max > 0) {
251 GntWidget *ddown = entry->ddown;
252 char *match = g_strndup(sgst + len, max);
253 entry->ddown = NULL;
254 gnt_entry_key_pressed(GNT_WIDGET(entry), match);
255 g_free(match);
256 if (entry->ddown)
257 gnt_widget_destroy(ddown);
258 else
259 entry->ddown = ddown;
261 gnt_widget_draw(entry->ddown->parent);
264 return TRUE;
267 static void
268 gnt_entry_draw(GntWidget *widget)
270 GntEntry *entry = GNT_ENTRY(widget);
271 int stop;
272 gboolean focus;
274 if ((focus = gnt_widget_has_focus(widget)))
275 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_TEXT_NORMAL));
276 else
277 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
279 if (entry->masked)
281 mvwhline(widget->window, 0, 0, gnt_ascii_only() ? '*' : ACS_BULLET,
282 g_utf8_pointer_to_offset(entry->scroll, entry->end));
284 else
285 mvwprintw(widget->window, 0, 0, "%s", entry->scroll);
287 stop = gnt_util_onscreen_width(entry->scroll, entry->end);
288 if (stop < widget->priv.width)
289 mvwhline(widget->window, 0, stop, ENTRY_CHAR, widget->priv.width - stop);
291 if (focus)
292 mvwchgat(widget->window, 0, gnt_util_onscreen_width(entry->scroll, entry->cursor),
293 1, A_REVERSE, GNT_COLOR_TEXT_NORMAL, NULL);
295 GNTDEBUG;
298 static void
299 gnt_entry_size_request(GntWidget *widget)
301 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED))
303 widget->priv.height = 1;
304 widget->priv.width = 20;
308 static void
309 gnt_entry_map(GntWidget *widget)
311 if (widget->priv.width == 0 || widget->priv.height == 0)
312 gnt_widget_size_request(widget);
313 GNTDEBUG;
316 static void
317 entry_redraw(GntWidget *widget)
319 gnt_entry_draw(widget);
320 gnt_widget_queue_update(widget);
323 static void
324 entry_text_changed(GntEntry *entry)
326 g_signal_emit(entry, signals[SIG_TEXT_CHANGED], 0);
329 static gboolean
330 move_back(GntBindable *bind, GList *null)
332 GntEntry *entry = GNT_ENTRY(bind);
333 if (entry->cursor <= entry->start)
334 return FALSE;
335 entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor);
336 if (entry->cursor < entry->scroll)
337 entry->scroll = entry->cursor;
338 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
339 entry_redraw(GNT_WIDGET(entry));
340 return TRUE;
343 static gboolean
344 move_forward(GntBindable *bind, GList *list)
346 GntEntry *entry = GNT_ENTRY(bind);
347 if (entry->cursor >= entry->end)
348 return FALSE;
349 entry->cursor = g_utf8_find_next_char(entry->cursor, NULL);
350 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
351 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
352 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
353 entry_redraw(GNT_WIDGET(entry));
354 return TRUE;
357 static gboolean
358 backspace(GntBindable *bind, GList *null)
360 int len;
361 GntEntry *entry = GNT_ENTRY(bind);
363 if (entry->cursor <= entry->start)
364 return TRUE;
366 len = entry->cursor - g_utf8_find_prev_char(entry->start, entry->cursor);
367 update_kill_ring(entry, ENTRY_DEL_BWD_CHAR, entry->cursor, -len);
368 entry->cursor -= len;
370 memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor);
371 entry->end -= len;
373 if (entry->scroll > entry->start)
374 entry->scroll = g_utf8_find_prev_char(entry->start, entry->scroll);
376 entry_redraw(GNT_WIDGET(entry));
377 if (entry->ddown)
378 show_suggest_dropdown(entry);
379 entry_text_changed(entry);
380 return TRUE;
383 static gboolean
384 delkey(GntBindable *bind, GList *null)
386 int len;
387 GntEntry *entry = GNT_ENTRY(bind);
389 if (entry->cursor >= entry->end)
390 return FALSE;
392 len = g_utf8_find_next_char(entry->cursor, NULL) - entry->cursor;
393 update_kill_ring(entry, ENTRY_DEL_FWD_CHAR, entry->cursor, len);
394 memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor - len + 1);
395 entry->end -= len;
396 entry_redraw(GNT_WIDGET(entry));
398 if (entry->ddown)
399 show_suggest_dropdown(entry);
400 entry_text_changed(entry);
401 return TRUE;
404 static gboolean
405 move_start(GntBindable *bind, GList *null)
407 GntEntry *entry = GNT_ENTRY(bind);
408 entry->scroll = entry->cursor = entry->start;
409 entry_redraw(GNT_WIDGET(entry));
410 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
411 return TRUE;
414 static gboolean
415 move_end(GntBindable *bind, GList *null)
417 GntEntry *entry = GNT_ENTRY(bind);
418 entry->cursor = entry->end;
419 /* This should be better than this */
420 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
421 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
422 entry_redraw(GNT_WIDGET(entry));
423 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
424 return TRUE;
427 static gboolean
428 history_prev(GntBindable *bind, GList *null)
430 GntEntry *entry = GNT_ENTRY(bind);
431 if (entry->histlength && entry->history->prev)
433 entry->history = entry->history->prev;
434 gnt_entry_set_text_internal(entry, entry->history->data);
435 destroy_suggest(entry);
436 entry_text_changed(entry);
438 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
439 return TRUE;
441 return FALSE;
444 static gboolean
445 history_next(GntBindable *bind, GList *null)
447 GntEntry *entry = GNT_ENTRY(bind);
448 if (entry->histlength && entry->history->next)
450 if (entry->history->prev == NULL)
452 /* Save the current contents */
453 char *text = g_strdup(gnt_entry_get_text(entry));
454 g_free(entry->history->data);
455 entry->history->data = text;
458 entry->history = entry->history->next;
459 gnt_entry_set_text_internal(entry, entry->history->data);
460 destroy_suggest(entry);
461 entry_text_changed(entry);
463 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
464 return TRUE;
466 return FALSE;
469 static gboolean
470 clipboard_paste(GntBindable *bind, GList *n)
472 GntEntry *entry = GNT_ENTRY(bind);
473 gchar *i, *text, *a, *all;
474 text = i = gnt_get_clipboard_string();
475 while (*i != '\0') {
476 i = g_utf8_next_char(i);
477 if (*i == '\r' || *i == '\n')
478 *i = ' ';
480 a = g_strndup(entry->start, entry->cursor - entry->start);
481 all = g_strconcat(a, text, entry->cursor, NULL);
482 gnt_entry_set_text_internal(entry, all);
483 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
484 g_free(a);
485 g_free(text);
486 g_free(all);
487 return TRUE;
490 static gboolean
491 suggest_show(GntBindable *bind, GList *null)
493 GntEntry *entry = GNT_ENTRY(bind);
494 if (entry->ddown) {
495 gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down");
496 return TRUE;
498 return show_suggest_dropdown(entry);
501 static gboolean
502 suggest_next(GntBindable *bind, GList *null)
504 GntEntry *entry = GNT_ENTRY(bind);
505 if (entry->ddown) {
506 gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down", NULL);
507 return TRUE;
509 return FALSE;
512 static gboolean
513 suggest_prev(GntBindable *bind, GList *null)
515 GntEntry *entry = GNT_ENTRY(bind);
516 if (entry->ddown) {
517 gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-up", NULL);
518 return TRUE;
520 return FALSE;
523 static gboolean
524 del_to_home(GntBindable *bind, GList *null)
526 GntEntry *entry = GNT_ENTRY(bind);
527 if (entry->cursor <= entry->start)
528 return TRUE;
529 update_kill_ring(entry, ENTRY_DEL_BOL, entry->start, entry->cursor - entry->start);
530 memmove(entry->start, entry->cursor, entry->end - entry->cursor);
531 entry->end -= (entry->cursor - entry->start);
532 entry->cursor = entry->scroll = entry->start;
533 memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
534 entry_redraw(GNT_WIDGET(bind));
535 entry_text_changed(entry);
536 return TRUE;
539 static gboolean
540 del_to_end(GntBindable *bind, GList *null)
542 GntEntry *entry = GNT_ENTRY(bind);
543 if (entry->end <= entry->cursor)
544 return TRUE;
545 update_kill_ring(entry, ENTRY_DEL_EOL, entry->cursor, entry->end - entry->cursor);
546 entry->end = entry->cursor;
547 memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
548 entry_redraw(GNT_WIDGET(bind));
549 entry_text_changed(entry);
550 return TRUE;
553 #define SAME(a,b) ((g_unichar_isalpha(a) && g_unichar_isalpha(b)) || \
554 (g_unichar_isdigit(a) && g_unichar_isdigit(b)) || \
555 (g_unichar_isspace(a) && g_unichar_isspace(b)) || \
556 (g_unichar_iswide(a) && g_unichar_iswide(b)))
558 static const char *
559 begin_word(const char *text, const char *begin)
561 gunichar ch = 0;
562 while (text > begin && (!*text || g_unichar_isspace(g_utf8_get_char(text))))
563 text = g_utf8_find_prev_char(begin, text);
564 ch = g_utf8_get_char(text);
565 while ((text = g_utf8_find_prev_char(begin, text)) >= begin) {
566 gunichar cur = g_utf8_get_char(text);
567 if (!SAME(ch, cur))
568 break;
571 return (text ? g_utf8_find_next_char(text, NULL) : begin);
574 static const char *
575 next_begin_word(const char *text, const char *end)
577 gunichar ch = 0;
578 ch = g_utf8_get_char(text);
579 while ((text = g_utf8_find_next_char(text, end)) != NULL && text <= end) {
580 gunichar cur = g_utf8_get_char(text);
581 if (!SAME(ch, cur))
582 break;
585 while (text && text < end && g_unichar_isspace(g_utf8_get_char(text)))
586 text = g_utf8_find_next_char(text, end);
587 return (text ? text : end);
590 #undef SAME
591 static gboolean
592 move_back_word(GntBindable *bind, GList *null)
594 GntEntry *entry = GNT_ENTRY(bind);
595 const char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
597 if (iter < entry->start)
598 return TRUE;
599 iter = begin_word(iter, entry->start);
600 entry->cursor = (char*)iter;
601 if (entry->cursor < entry->scroll)
602 entry->scroll = entry->cursor;
603 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
604 entry_redraw(GNT_WIDGET(bind));
605 return TRUE;
608 static gboolean
609 del_prev_word(GntBindable *bind, GList *null)
611 GntWidget *widget = GNT_WIDGET(bind);
612 GntEntry *entry = GNT_ENTRY(bind);
613 char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
614 int count;
616 if (iter < entry->start)
617 return TRUE;
618 iter = (char*)begin_word(iter, entry->start);
619 count = entry->cursor - iter;
620 update_kill_ring(entry, ENTRY_DEL_BWD_WORD, iter, count);
621 memmove(iter, entry->cursor, entry->end - entry->cursor);
622 entry->end -= count;
623 entry->cursor = iter;
624 if (entry->cursor <= entry->scroll) {
625 entry->scroll = entry->cursor - widget->priv.width + 2;
626 if (entry->scroll < entry->start)
627 entry->scroll = entry->start;
629 memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
630 entry_redraw(widget);
631 entry_text_changed(entry);
633 return TRUE;
636 static gboolean
637 move_forward_word(GntBindable *bind, GList *list)
639 GntEntry *entry = GNT_ENTRY(bind);
640 GntWidget *widget = GNT_WIDGET(bind);
641 entry->cursor = (char *)next_begin_word(entry->cursor, entry->end);
642 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width) {
643 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
645 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
646 entry_redraw(widget);
647 return TRUE;
650 static gboolean
651 delete_forward_word(GntBindable *bind, GList *list)
653 GntEntry *entry = GNT_ENTRY(bind);
654 GntWidget *widget = GNT_WIDGET(bind);
655 char *iter = (char *)next_begin_word(entry->cursor, entry->end);
656 int len = entry->end - iter + 1;
657 if (len <= 0)
658 return TRUE;
659 update_kill_ring(entry, ENTRY_DEL_FWD_WORD, entry->cursor, iter - entry->cursor);
660 memmove(entry->cursor, iter, len);
661 len = iter - entry->cursor;
662 entry->end -= len;
663 memset(entry->end, '\0', len);
664 entry_redraw(widget);
665 entry_text_changed(entry);
666 return TRUE;
669 static gboolean
670 transpose_chars(GntBindable *bind, GList *null)
672 GntEntry *entry = GNT_ENTRY(bind);
673 char *current, *prev;
674 char hold[8]; /* that's right */
676 if (entry->cursor <= entry->start)
677 return FALSE;
679 if (!*entry->cursor)
680 entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor);
682 current = entry->cursor;
683 prev = g_utf8_find_prev_char(entry->start, entry->cursor);
684 move_forward(bind, null);
686 /* Let's do this dance! */
687 memcpy(hold, prev, current - prev);
688 memmove(prev, current, entry->cursor - current);
689 memcpy(prev + (entry->cursor - current), hold, current - prev);
691 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
692 entry_redraw(GNT_WIDGET(entry));
693 entry_text_changed(entry);
694 return TRUE;
697 static gboolean
698 entry_yank(GntBindable *bind, GList *null)
700 GntEntry *entry = GNT_ENTRY(bind);
701 gnt_entry_key_pressed(GNT_WIDGET(entry), entry->killring->buffer->str);
702 return TRUE;
705 static gboolean
706 gnt_entry_key_pressed(GntWidget *widget, const char *text)
708 GntEntry *entry = GNT_ENTRY(widget);
710 if (text[0] == 27)
712 if (text[1] == 0)
714 destroy_suggest(entry);
715 return TRUE;
718 return FALSE;
720 else
722 if ((text[0] == '\r' || text[0] == ' ') && entry->ddown)
724 char *text = g_strdup(gnt_tree_get_selection_data(GNT_TREE(entry->ddown)));
725 destroy_suggest(entry);
726 complete_suggest(entry, text);
727 g_free(text);
728 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
729 entry_text_changed(entry);
730 return TRUE;
733 if (!iscntrl(text[0]))
735 const char *str, *next;
737 for (str = text; *str; str = next)
739 int len;
740 next = g_utf8_find_next_char(str, NULL);
741 len = next - str;
743 /* Valid input? */
744 /* XXX: Is it necessary to use _unichar_ variants here? */
745 if (ispunct(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_PUNCT))
746 continue;
747 if (isspace(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_SPACE))
748 continue;
749 if (isalpha(*str) && !(entry->flag & GNT_ENTRY_FLAG_ALPHA))
750 continue;
751 if (isdigit(*str) && !(entry->flag & GNT_ENTRY_FLAG_INT))
752 continue;
754 /* Reached the max? */
755 if (entry->max && g_utf8_pointer_to_offset(entry->start, entry->end) >= entry->max)
756 continue;
758 if (entry->end + len - entry->start >= entry->buffer)
760 /* This will cause the buffer to grow */
761 char *tmp = g_strdup(entry->start);
762 gnt_entry_set_text_internal(entry, tmp);
763 g_free(tmp);
766 memmove(entry->cursor + len, entry->cursor, entry->end - entry->cursor + 1);
767 entry->end += len;
769 while (str < next)
771 if (*str == '\r' || *str == '\n')
772 *entry->cursor = ' ';
773 else
774 *entry->cursor = *str;
775 entry->cursor++;
776 str++;
779 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width)
780 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
782 if (entry->ddown)
783 show_suggest_dropdown(entry);
785 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
786 entry_redraw(widget);
787 entry_text_changed(entry);
788 return TRUE;
792 return FALSE;
795 static void
796 jail_killring(GntEntryKillRing *kr)
798 g_string_free(kr->buffer, TRUE);
799 g_free(kr);
802 static void
803 gnt_entry_destroy(GntWidget *widget)
805 GntEntry *entry = GNT_ENTRY(widget);
806 g_free(entry->start);
808 if (entry->history)
810 entry->history = g_list_first(entry->history);
811 g_list_foreach(entry->history, (GFunc)g_free, NULL);
812 g_list_free(entry->history);
815 if (entry->suggests)
817 g_list_foreach(entry->suggests, (GFunc)g_free, NULL);
818 g_list_free(entry->suggests);
821 if (entry->ddown)
823 gnt_widget_destroy(entry->ddown->parent);
826 jail_killring(entry->killring);
829 static void
830 gnt_entry_lost_focus(GntWidget *widget)
832 GntEntry *entry = GNT_ENTRY(widget);
833 destroy_suggest(entry);
834 entry_redraw(widget);
837 static void
838 gnt_entry_class_init(GntEntryClass *klass)
840 GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass);
841 char s[2] = {erasechar(), 0};
843 parent_class = GNT_WIDGET_CLASS(klass);
844 parent_class->destroy = gnt_entry_destroy;
845 parent_class->draw = gnt_entry_draw;
846 parent_class->map = gnt_entry_map;
847 parent_class->size_request = gnt_entry_size_request;
848 parent_class->key_pressed = gnt_entry_key_pressed;
849 parent_class->lost_focus = gnt_entry_lost_focus;
851 signals[SIG_TEXT_CHANGED] =
852 g_signal_new("text_changed",
853 G_TYPE_FROM_CLASS(klass),
854 G_SIGNAL_RUN_LAST,
855 G_STRUCT_OFFSET(GntEntryClass, text_changed),
856 NULL, NULL,
857 g_cclosure_marshal_VOID__VOID,
858 G_TYPE_NONE, 0);
860 signals[SIG_COMPLETION] =
861 g_signal_new("completion",
862 G_TYPE_FROM_CLASS(klass),
863 G_SIGNAL_RUN_LAST,
864 0, NULL, NULL,
865 gnt_closure_marshal_VOID__POINTER_POINTER,
866 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
868 gnt_bindable_class_register_action(bindable, "cursor-home", move_start,
869 GNT_KEY_CTRL_A, NULL);
870 gnt_bindable_register_binding(bindable, "cursor-home", GNT_KEY_HOME, NULL);
871 gnt_bindable_class_register_action(bindable, "cursor-end", move_end,
872 GNT_KEY_CTRL_E, NULL);
873 gnt_bindable_register_binding(bindable, "cursor-end", GNT_KEY_END, NULL);
874 gnt_bindable_class_register_action(bindable, "delete-prev", backspace,
875 GNT_KEY_BACKSPACE, NULL);
876 gnt_bindable_register_binding(bindable, "delete-prev", s, NULL);
877 gnt_bindable_register_binding(bindable, "delete-prev", GNT_KEY_CTRL_H, NULL);
878 gnt_bindable_class_register_action(bindable, "delete-next", delkey,
879 GNT_KEY_DEL, NULL);
880 gnt_bindable_register_binding(bindable, "delete-next", GNT_KEY_CTRL_D, NULL);
881 gnt_bindable_class_register_action(bindable, "delete-start", del_to_home,
882 GNT_KEY_CTRL_U, NULL);
883 gnt_bindable_class_register_action(bindable, "delete-end", del_to_end,
884 GNT_KEY_CTRL_K, NULL);
885 gnt_bindable_class_register_action(bindable, "delete-prev-word", del_prev_word,
886 GNT_KEY_CTRL_W, NULL);
887 gnt_bindable_class_register_action(bindable, "cursor-prev-word", move_back_word,
888 "\033" "b", NULL);
889 gnt_bindable_class_register_action(bindable, "cursor-prev", move_back,
890 GNT_KEY_LEFT, NULL);
891 gnt_bindable_register_binding(bindable, "cursor-prev", GNT_KEY_CTRL_B, NULL);
892 gnt_bindable_class_register_action(bindable, "cursor-next", move_forward,
893 GNT_KEY_RIGHT, NULL);
894 gnt_bindable_register_binding(bindable, "cursor-next", GNT_KEY_CTRL_F, NULL);
895 gnt_bindable_class_register_action(bindable, "cursor-next-word", move_forward_word,
896 "\033" "f", NULL);
897 gnt_bindable_class_register_action(bindable, "delete-next-word", delete_forward_word,
898 "\033" "d", NULL);
899 gnt_bindable_class_register_action(bindable, "transpose-chars", transpose_chars,
900 GNT_KEY_CTRL_T, NULL);
901 gnt_bindable_class_register_action(bindable, "yank", entry_yank,
902 GNT_KEY_CTRL_Y, NULL);
903 gnt_bindable_class_register_action(bindable, "suggest-show", suggest_show,
904 "\t", NULL);
905 gnt_bindable_class_register_action(bindable, "suggest-next", suggest_next,
906 GNT_KEY_DOWN, NULL);
907 gnt_bindable_class_register_action(bindable, "suggest-prev", suggest_prev,
908 GNT_KEY_UP, NULL);
909 gnt_bindable_class_register_action(bindable, "history-prev", history_prev,
910 GNT_KEY_CTRL_DOWN, NULL);
911 gnt_bindable_class_register_action(bindable, "history-next", history_next,
912 GNT_KEY_CTRL_UP, NULL);
913 gnt_bindable_class_register_action(bindable, "clipboard-paste", clipboard_paste,
914 GNT_KEY_CTRL_V, NULL);
916 gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
917 GNTDEBUG;
920 static GntEntryKillRing *
921 new_killring()
923 GntEntryKillRing *kr = g_new0(GntEntryKillRing, 1);
924 kr->buffer = g_string_new(NULL);
925 return kr;
928 static void
929 gnt_entry_init(GTypeInstance *instance, gpointer class)
931 GntWidget *widget = GNT_WIDGET(instance);
932 GntEntry *entry = GNT_ENTRY(instance);
934 entry->flag = GNT_ENTRY_FLAG_ALL;
935 entry->max = 0;
937 entry->histlength = 0;
938 entry->history = NULL;
940 entry->word = TRUE;
941 entry->always = FALSE;
942 entry->suggests = NULL;
943 entry->killring = new_killring();
945 GNT_WIDGET_SET_FLAGS(GNT_WIDGET(entry),
946 GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | GNT_WIDGET_CAN_TAKE_FOCUS);
947 GNT_WIDGET_SET_FLAGS(GNT_WIDGET(entry), GNT_WIDGET_GROW_X);
949 widget->priv.minw = 3;
950 widget->priv.minh = 1;
952 GNTDEBUG;
955 /******************************************************************************
956 * GntEntry API
957 *****************************************************************************/
958 GType
959 gnt_entry_get_gtype(void)
961 static GType type = 0;
963 if(type == 0)
965 static const GTypeInfo info = {
966 sizeof(GntEntryClass),
967 NULL, /* base_init */
968 NULL, /* base_finalize */
969 (GClassInitFunc)gnt_entry_class_init,
970 NULL, /* class_finalize */
971 NULL, /* class_data */
972 sizeof(GntEntry),
973 0, /* n_preallocs */
974 gnt_entry_init, /* instance_init */
975 NULL /* value_table */
978 type = g_type_register_static(GNT_TYPE_WIDGET,
979 "GntEntry",
980 &info, 0);
983 return type;
986 GntWidget *gnt_entry_new(const char *text)
988 GntWidget *widget = g_object_new(GNT_TYPE_ENTRY, NULL);
989 GntEntry *entry = GNT_ENTRY(widget);
991 gnt_entry_set_text_internal(entry, text);
993 return widget;
996 static void
997 gnt_entry_set_text_internal(GntEntry *entry, const char *text)
999 int len;
1000 int scroll, cursor;
1002 g_free(entry->start);
1004 if (text && text[0])
1006 len = strlen(text);
1008 else
1010 len = 0;
1013 entry->buffer = len + 128;
1015 scroll = entry->scroll - entry->start;
1016 cursor = entry->end - entry->cursor;
1018 entry->start = g_new0(char, entry->buffer);
1019 if (text)
1020 snprintf(entry->start, len + 1, "%s", text);
1021 entry->end = entry->start + len;
1023 entry->scroll = entry->start + scroll;
1024 entry->cursor = entry->end - cursor;
1026 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(entry), GNT_WIDGET_MAPPED))
1027 entry_redraw(GNT_WIDGET(entry));
1030 void gnt_entry_set_text(GntEntry *entry, const char *text)
1032 gboolean changed = TRUE;
1033 if (text == NULL && entry->start == NULL)
1034 changed = FALSE;
1035 if (text && entry->start && g_utf8_collate(text, entry->start) == 0)
1036 changed = FALSE;
1037 gnt_entry_set_text_internal(entry, text);
1038 if (changed)
1039 entry_text_changed(entry);
1042 void gnt_entry_set_max(GntEntry *entry, int max)
1044 entry->max = max;
1047 void gnt_entry_set_flag(GntEntry *entry, GntEntryFlag flag)
1049 entry->flag = flag;
1050 /* XXX: Check the existing string to make sure the flags are respected? */
1053 const char *gnt_entry_get_text(GntEntry *entry)
1055 return entry->start;
1058 void gnt_entry_clear(GntEntry *entry)
1060 gnt_entry_set_text_internal(entry, NULL);
1061 entry->scroll = entry->cursor = entry->end = entry->start;
1062 entry_redraw(GNT_WIDGET(entry));
1063 destroy_suggest(entry);
1064 entry_text_changed(entry);
1067 void gnt_entry_set_masked(GntEntry *entry, gboolean set)
1069 entry->masked = set;
1072 void gnt_entry_add_to_history(GntEntry *entry, const char *text)
1074 g_return_if_fail(entry->history != NULL); /* Need to set_history_length first */
1076 if (g_list_length(entry->history) >= entry->histlength)
1077 return;
1079 entry->history = g_list_first(entry->history);
1080 g_free(entry->history->data);
1081 entry->history->data = g_strdup(text);
1082 entry->history = g_list_prepend(entry->history, NULL);
1085 void gnt_entry_set_history_length(GntEntry *entry, int num)
1087 if (num == 0)
1089 entry->histlength = num;
1090 if (entry->history)
1092 entry->history = g_list_first(entry->history);
1093 g_list_foreach(entry->history, (GFunc)g_free, NULL);
1094 g_list_free(entry->history);
1095 entry->history = NULL;
1097 return;
1100 if (entry->histlength == 0)
1102 entry->histlength = num;
1103 entry->history = g_list_append(NULL, NULL);
1104 return;
1107 if (num > 0 && num < entry->histlength)
1109 GList *first, *iter;
1110 int index = 0;
1111 for (first = entry->history, index = 0; first->prev; first = first->prev, index++);
1112 while ((iter = g_list_nth(first, num)) != NULL)
1114 g_free(iter->data);
1115 first = g_list_delete_link(first, iter);
1117 entry->histlength = num;
1118 if (index >= num)
1119 entry->history = g_list_last(first);
1120 return;
1123 entry->histlength = num;
1126 void gnt_entry_set_word_suggest(GntEntry *entry, gboolean word)
1128 entry->word = word;
1131 void gnt_entry_set_always_suggest(GntEntry *entry, gboolean always)
1133 entry->always = always;
1136 void gnt_entry_add_suggest(GntEntry *entry, const char *text)
1138 GList *find;
1140 if (!text || !*text)
1141 return;
1143 find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
1144 if (find)
1145 return;
1146 entry->suggests = g_list_append(entry->suggests, g_strdup(text));
1149 void gnt_entry_remove_suggest(GntEntry *entry, const char *text)
1151 GList *find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
1152 if (find)
1154 g_free(find->data);
1155 entry->suggests = g_list_delete_link(entry->suggests, find);