Update to 24f58c58bb8d22c0e8e6c5ce43c536c47b719bc6
[gnt.git] / gntentry.c
blob61e2a6b6a75aa2ce4e7c1da46fec3b4890afeea0
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 int offstart = 0, offend = 0;
145 if (entry->word) {
146 char *s = get_beginning_of_word(entry);
147 const char *iter = text;
148 offstart = g_utf8_pointer_to_offset(entry->start, s);
149 while (*iter && toupper(*s) == toupper(*iter)) {
150 *s++ = *iter++;
152 if (*iter) {
153 gnt_entry_key_pressed(GNT_WIDGET(entry), iter);
155 offend = g_utf8_pointer_to_offset(entry->start, entry->cursor);
156 } else {
157 offstart = 0;
158 gnt_entry_set_text_internal(entry, text);
159 offend = g_utf8_strlen(text, -1);
162 g_signal_emit(G_OBJECT(entry), signals[SIG_COMPLETION], 0,
163 entry->start + offstart, entry->start + offend);
164 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
165 return TRUE;
168 static int
169 max_common_prefix(const char *s, const char *t)
171 const char *f = s;
172 while (*f && *t && *f == *t++)
173 f++;
174 return f - s;
177 static gboolean
178 show_suggest_dropdown(GntEntry *entry)
180 char *suggest = NULL;
181 int len;
182 int offset = 0, x, y;
183 int count = 0;
184 GList *iter;
185 const char *text = NULL;
186 const char *sgst = NULL;
187 int max = -1;
189 if (entry->word)
191 char *s = get_beginning_of_word(entry);
192 suggest = g_strndup(s, entry->cursor - s);
193 if (entry->scroll < s)
194 offset = gnt_util_onscreen_width(entry->scroll, s);
196 else
197 suggest = g_strdup(entry->start);
198 len = strlen(suggest); /* Don't need to use the utf8-function here */
200 if (entry->ddown == NULL)
202 GntWidget *box = gnt_vbox_new(FALSE);
203 entry->ddown = gnt_tree_new();
204 gnt_tree_set_compare_func(GNT_TREE(entry->ddown), (GCompareFunc)g_utf8_collate);
205 gnt_box_add_widget(GNT_BOX(box), entry->ddown);
207 GNT_WIDGET_SET_FLAGS(box, GNT_WIDGET_TRANSIENT);
209 gnt_widget_get_position(GNT_WIDGET(entry), &x, &y);
210 x += offset;
211 y++;
212 if (y + 10 >= getmaxy(stdscr))
213 y -= 11;
214 gnt_widget_set_position(box, x, y);
216 else
217 gnt_tree_remove_all(GNT_TREE(entry->ddown));
219 for (count = 0, iter = entry->suggests; iter; iter = iter->next)
221 text = iter->data;
222 if (g_ascii_strncasecmp(suggest, text, len) == 0 && strlen(text) >= len)
224 gnt_tree_add_row_after(GNT_TREE(entry->ddown), (gpointer)text,
225 gnt_tree_create_row(GNT_TREE(entry->ddown), text),
226 NULL, NULL);
227 count++;
228 if (max == -1)
229 max = strlen(text) - len;
230 else if (max)
231 max = MIN(max, max_common_prefix(sgst + len, text + len));
232 sgst = text;
235 g_free(suggest);
237 if (count == 0) {
238 destroy_suggest(entry);
239 return FALSE;
240 } else if (count == 1) {
241 destroy_suggest(entry);
242 return complete_suggest(entry, sgst);
243 } else {
244 if (max > 0) {
245 GntWidget *ddown = entry->ddown;
246 char *match = g_strndup(sgst + len, max);
247 entry->ddown = NULL;
248 gnt_entry_key_pressed(GNT_WIDGET(entry), match);
249 g_free(match);
250 if (entry->ddown)
251 gnt_widget_destroy(ddown);
252 else
253 entry->ddown = ddown;
255 gnt_widget_draw(entry->ddown->parent);
258 return TRUE;
261 static void
262 gnt_entry_draw(GntWidget *widget)
264 GntEntry *entry = GNT_ENTRY(widget);
265 int stop;
266 gboolean focus;
268 if ((focus = gnt_widget_has_focus(widget)))
269 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_TEXT_NORMAL));
270 else
271 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
273 if (entry->masked)
275 mvwhline(widget->window, 0, 0, gnt_ascii_only() ? '*' : ACS_BULLET,
276 g_utf8_pointer_to_offset(entry->scroll, entry->end));
278 else
279 mvwprintw(widget->window, 0, 0, "%s", entry->scroll);
281 stop = gnt_util_onscreen_width(entry->scroll, entry->end);
282 if (stop < widget->priv.width)
283 mvwhline(widget->window, 0, stop, ENTRY_CHAR, widget->priv.width - stop);
285 if (focus)
286 mvwchgat(widget->window, 0, gnt_util_onscreen_width(entry->scroll, entry->cursor),
287 1, A_REVERSE, GNT_COLOR_TEXT_NORMAL, NULL);
289 GNTDEBUG;
292 static void
293 gnt_entry_size_request(GntWidget *widget)
295 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED))
297 widget->priv.height = 1;
298 widget->priv.width = 20;
302 static void
303 gnt_entry_map(GntWidget *widget)
305 if (widget->priv.width == 0 || widget->priv.height == 0)
306 gnt_widget_size_request(widget);
307 GNTDEBUG;
310 static void
311 entry_redraw(GntWidget *widget)
313 gnt_entry_draw(widget);
314 gnt_widget_queue_update(widget);
317 static void
318 entry_text_changed(GntEntry *entry)
320 g_signal_emit(entry, signals[SIG_TEXT_CHANGED], 0);
323 static gboolean
324 move_back(GntBindable *bind, GList *null)
326 GntEntry *entry = GNT_ENTRY(bind);
327 if (entry->cursor <= entry->start)
328 return FALSE;
329 entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor);
330 if (entry->cursor < entry->scroll)
331 entry->scroll = entry->cursor;
332 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
333 entry_redraw(GNT_WIDGET(entry));
334 return TRUE;
337 static gboolean
338 move_forward(GntBindable *bind, GList *list)
340 GntEntry *entry = GNT_ENTRY(bind);
341 if (entry->cursor >= entry->end)
342 return FALSE;
343 entry->cursor = g_utf8_find_next_char(entry->cursor, NULL);
344 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
345 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
346 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
347 entry_redraw(GNT_WIDGET(entry));
348 return TRUE;
351 static gboolean
352 backspace(GntBindable *bind, GList *null)
354 int len;
355 GntEntry *entry = GNT_ENTRY(bind);
357 if (entry->cursor <= entry->start)
358 return TRUE;
360 len = entry->cursor - g_utf8_find_prev_char(entry->start, entry->cursor);
361 update_kill_ring(entry, ENTRY_DEL_BWD_CHAR, entry->cursor, -len);
362 entry->cursor -= len;
364 memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor);
365 entry->end -= len;
367 if (entry->scroll > entry->start)
368 entry->scroll = g_utf8_find_prev_char(entry->start, entry->scroll);
370 entry_redraw(GNT_WIDGET(entry));
371 if (entry->ddown)
372 show_suggest_dropdown(entry);
373 entry_text_changed(entry);
374 return TRUE;
377 static gboolean
378 delkey(GntBindable *bind, GList *null)
380 int len;
381 GntEntry *entry = GNT_ENTRY(bind);
383 if (entry->cursor >= entry->end)
384 return FALSE;
386 len = g_utf8_find_next_char(entry->cursor, NULL) - entry->cursor;
387 update_kill_ring(entry, ENTRY_DEL_FWD_CHAR, entry->cursor, len);
388 memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor - len + 1);
389 entry->end -= len;
390 entry_redraw(GNT_WIDGET(entry));
392 if (entry->ddown)
393 show_suggest_dropdown(entry);
394 entry_text_changed(entry);
395 return TRUE;
398 static gboolean
399 move_start(GntBindable *bind, GList *null)
401 GntEntry *entry = GNT_ENTRY(bind);
402 entry->scroll = entry->cursor = entry->start;
403 entry_redraw(GNT_WIDGET(entry));
404 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
405 return TRUE;
408 static gboolean
409 move_end(GntBindable *bind, GList *null)
411 GntEntry *entry = GNT_ENTRY(bind);
412 entry->cursor = entry->end;
413 /* This should be better than this */
414 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
415 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
416 entry_redraw(GNT_WIDGET(entry));
417 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
418 return TRUE;
421 static gboolean
422 history_prev(GntBindable *bind, GList *null)
424 GntEntry *entry = GNT_ENTRY(bind);
425 if (entry->histlength && entry->history->prev)
427 entry->history = entry->history->prev;
428 gnt_entry_set_text_internal(entry, entry->history->data);
429 destroy_suggest(entry);
430 entry_text_changed(entry);
432 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
433 return TRUE;
435 return FALSE;
438 static gboolean
439 history_next(GntBindable *bind, GList *null)
441 GntEntry *entry = GNT_ENTRY(bind);
442 if (entry->histlength && entry->history->next)
444 if (entry->history->prev == NULL)
446 /* Save the current contents */
447 char *text = g_strdup(gnt_entry_get_text(entry));
448 g_free(entry->history->data);
449 entry->history->data = text;
452 entry->history = entry->history->next;
453 gnt_entry_set_text_internal(entry, entry->history->data);
454 destroy_suggest(entry);
455 entry_text_changed(entry);
457 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
458 return TRUE;
460 return FALSE;
463 static gboolean
464 clipboard_paste(GntBindable *bind, GList *n)
466 GntEntry *entry = GNT_ENTRY(bind);
467 gchar *i, *text, *a, *all;
468 text = i = gnt_get_clipboard_string();
469 while (*i != '\0') {
470 i = g_utf8_next_char(i);
471 if (*i == '\r' || *i == '\n')
472 *i = ' ';
474 a = g_strndup(entry->start, entry->cursor - entry->start);
475 all = g_strconcat(a, text, entry->cursor, NULL);
476 gnt_entry_set_text_internal(entry, all);
477 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
478 g_free(a);
479 g_free(text);
480 g_free(all);
481 return TRUE;
484 static gboolean
485 suggest_show(GntBindable *bind, GList *null)
487 GntEntry *entry = GNT_ENTRY(bind);
488 if (entry->ddown) {
489 gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down");
490 return TRUE;
492 return show_suggest_dropdown(entry);
495 static gboolean
496 suggest_next(GntBindable *bind, GList *null)
498 GntEntry *entry = GNT_ENTRY(bind);
499 if (entry->ddown) {
500 gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down", NULL);
501 return TRUE;
503 return FALSE;
506 static gboolean
507 suggest_prev(GntBindable *bind, GList *null)
509 GntEntry *entry = GNT_ENTRY(bind);
510 if (entry->ddown) {
511 gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-up", NULL);
512 return TRUE;
514 return FALSE;
517 static gboolean
518 del_to_home(GntBindable *bind, GList *null)
520 GntEntry *entry = GNT_ENTRY(bind);
521 if (entry->cursor <= entry->start)
522 return TRUE;
523 update_kill_ring(entry, ENTRY_DEL_BOL, entry->start, entry->cursor - entry->start);
524 memmove(entry->start, entry->cursor, entry->end - entry->cursor);
525 entry->end -= (entry->cursor - entry->start);
526 entry->cursor = entry->scroll = entry->start;
527 memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
528 entry_redraw(GNT_WIDGET(bind));
529 entry_text_changed(entry);
530 return TRUE;
533 static gboolean
534 del_to_end(GntBindable *bind, GList *null)
536 GntEntry *entry = GNT_ENTRY(bind);
537 if (entry->end <= entry->cursor)
538 return TRUE;
539 update_kill_ring(entry, ENTRY_DEL_EOL, entry->cursor, entry->end - entry->cursor);
540 entry->end = entry->cursor;
541 memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
542 entry_redraw(GNT_WIDGET(bind));
543 entry_text_changed(entry);
544 return TRUE;
547 #define SAME(a,b) ((g_unichar_isalpha(a) && g_unichar_isalpha(b)) || \
548 (g_unichar_isdigit(a) && g_unichar_isdigit(b)) || \
549 (g_unichar_isspace(a) && g_unichar_isspace(b)) || \
550 (g_unichar_iswide(a) && g_unichar_iswide(b)))
552 static const char *
553 begin_word(const char *text, const char *begin)
555 gunichar ch = 0;
556 while (text > begin && (!*text || g_unichar_isspace(g_utf8_get_char(text))))
557 text = g_utf8_find_prev_char(begin, text);
558 ch = g_utf8_get_char(text);
559 while ((text = g_utf8_find_prev_char(begin, text)) >= begin) {
560 gunichar cur = g_utf8_get_char(text);
561 if (!SAME(ch, cur))
562 break;
565 return (text ? g_utf8_find_next_char(text, NULL) : begin);
568 static const char *
569 next_begin_word(const char *text, const char *end)
571 gunichar ch = 0;
573 while (text && text < end && g_unichar_isspace(g_utf8_get_char(text)))
574 text = g_utf8_find_next_char(text, end);
576 ch = g_utf8_get_char(text);
577 while ((text = g_utf8_find_next_char(text, end)) != NULL && text <= end) {
578 gunichar cur = g_utf8_get_char(text);
579 if (!SAME(ch, cur))
580 break;
582 return (text ? text : end);
585 #undef SAME
586 static gboolean
587 move_back_word(GntBindable *bind, GList *null)
589 GntEntry *entry = GNT_ENTRY(bind);
590 const char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
592 if (iter < entry->start)
593 return TRUE;
594 iter = begin_word(iter, entry->start);
595 entry->cursor = (char*)iter;
596 if (entry->cursor < entry->scroll)
597 entry->scroll = entry->cursor;
598 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
599 entry_redraw(GNT_WIDGET(bind));
600 return TRUE;
603 static gboolean
604 del_prev_word(GntBindable *bind, GList *null)
606 GntWidget *widget = GNT_WIDGET(bind);
607 GntEntry *entry = GNT_ENTRY(bind);
608 char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
609 int count;
611 if (iter < entry->start)
612 return TRUE;
613 iter = (char*)begin_word(iter, entry->start);
614 count = entry->cursor - iter;
615 update_kill_ring(entry, ENTRY_DEL_BWD_WORD, iter, count);
616 memmove(iter, entry->cursor, entry->end - entry->cursor);
617 entry->end -= count;
618 entry->cursor = iter;
619 if (entry->cursor <= entry->scroll) {
620 entry->scroll = entry->cursor - widget->priv.width + 2;
621 if (entry->scroll < entry->start)
622 entry->scroll = entry->start;
624 memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
625 entry_redraw(widget);
626 entry_text_changed(entry);
628 return TRUE;
631 static gboolean
632 move_forward_word(GntBindable *bind, GList *list)
634 GntEntry *entry = GNT_ENTRY(bind);
635 GntWidget *widget = GNT_WIDGET(bind);
636 entry->cursor = (char *)next_begin_word(entry->cursor, entry->end);
637 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width) {
638 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
640 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
641 entry_redraw(widget);
642 return TRUE;
645 static gboolean
646 delete_forward_word(GntBindable *bind, GList *list)
648 GntEntry *entry = GNT_ENTRY(bind);
649 GntWidget *widget = GNT_WIDGET(bind);
650 char *iter = (char *)next_begin_word(entry->cursor, entry->end);
651 int len = entry->end - iter + 1;
652 if (len <= 0)
653 return TRUE;
654 update_kill_ring(entry, ENTRY_DEL_FWD_WORD, entry->cursor, iter - entry->cursor);
655 memmove(entry->cursor, iter, len);
656 len = iter - entry->cursor;
657 entry->end -= len;
658 memset(entry->end, '\0', len);
659 entry_redraw(widget);
660 entry_text_changed(entry);
661 return TRUE;
664 static gboolean
665 transpose_chars(GntBindable *bind, GList *null)
667 GntEntry *entry = GNT_ENTRY(bind);
668 char *current, *prev;
669 char hold[8]; /* that's right */
671 if (entry->cursor <= entry->start)
672 return FALSE;
674 if (!*entry->cursor)
675 entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor);
677 current = entry->cursor;
678 prev = g_utf8_find_prev_char(entry->start, entry->cursor);
679 move_forward(bind, null);
681 /* Let's do this dance! */
682 memcpy(hold, prev, current - prev);
683 memmove(prev, current, entry->cursor - current);
684 memcpy(prev + (entry->cursor - current), hold, current - prev);
686 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
687 entry_redraw(GNT_WIDGET(entry));
688 entry_text_changed(entry);
689 return TRUE;
692 static gboolean
693 entry_yank(GntBindable *bind, GList *null)
695 GntEntry *entry = GNT_ENTRY(bind);
696 gnt_entry_key_pressed(GNT_WIDGET(entry), entry->killring->buffer->str);
697 return TRUE;
700 static gboolean
701 gnt_entry_key_pressed(GntWidget *widget, const char *text)
703 GntEntry *entry = GNT_ENTRY(widget);
705 if (text[0] == 27)
707 if (text[1] == 0)
709 destroy_suggest(entry);
710 return TRUE;
713 return FALSE;
716 if ((text[0] == '\r' || text[0] == ' ' || text[0] == '\n') && entry->ddown)
718 char *text = g_strdup(gnt_tree_get_selection_data(GNT_TREE(entry->ddown)));
719 destroy_suggest(entry);
720 complete_suggest(entry, text);
721 g_free(text);
722 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
723 entry_text_changed(entry);
724 return TRUE;
727 if (!iscntrl(text[0]))
729 const char *str, *next;
731 for (str = text; *str; str = next)
733 int len;
734 next = g_utf8_find_next_char(str, NULL);
735 len = next - str;
737 /* Valid input? */
738 /* XXX: Is it necessary to use _unichar_ variants here? */
739 if (ispunct(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_PUNCT))
740 continue;
741 if (isspace(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_SPACE))
742 continue;
743 if (isalpha(*str) && !(entry->flag & GNT_ENTRY_FLAG_ALPHA))
744 continue;
745 if (isdigit(*str) && !(entry->flag & GNT_ENTRY_FLAG_INT))
746 continue;
748 /* Reached the max? */
749 if (entry->max && g_utf8_pointer_to_offset(entry->start, entry->end) >= entry->max)
750 continue;
752 if (entry->end + len - entry->start >= entry->buffer)
754 /* This will cause the buffer to grow */
755 char *tmp = g_strdup(entry->start);
756 gnt_entry_set_text_internal(entry, tmp);
757 g_free(tmp);
760 memmove(entry->cursor + len, entry->cursor, entry->end - entry->cursor + 1);
761 entry->end += len;
763 while (str < next)
765 if (*str == '\r' || *str == '\n')
766 *entry->cursor = ' ';
767 else
768 *entry->cursor = *str;
769 entry->cursor++;
770 str++;
773 while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width)
774 entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
776 if (entry->ddown)
777 show_suggest_dropdown(entry);
779 update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
780 entry_redraw(widget);
781 entry_text_changed(entry);
782 return TRUE;
785 if (text[0] == '\r' || text[0] == '\n') {
786 gnt_widget_activate(widget);
787 return TRUE;
790 return FALSE;
793 static void
794 jail_killring(GntEntryKillRing *kr)
796 g_string_free(kr->buffer, TRUE);
797 g_free(kr);
800 static void
801 gnt_entry_destroy(GntWidget *widget)
803 GntEntry *entry = GNT_ENTRY(widget);
804 g_free(entry->start);
806 if (entry->history)
808 entry->history = g_list_first(entry->history);
809 g_list_foreach(entry->history, (GFunc)g_free, NULL);
810 g_list_free(entry->history);
813 if (entry->suggests)
815 g_list_foreach(entry->suggests, (GFunc)g_free, NULL);
816 g_list_free(entry->suggests);
819 if (entry->ddown)
821 gnt_widget_destroy(entry->ddown->parent);
824 jail_killring(entry->killring);
827 static void
828 gnt_entry_lost_focus(GntWidget *widget)
830 GntEntry *entry = GNT_ENTRY(widget);
831 destroy_suggest(entry);
832 entry_redraw(widget);
835 static void
836 gnt_entry_class_init(GntEntryClass *klass)
838 GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass);
839 char s[2] = {erasechar(), 0};
841 parent_class = GNT_WIDGET_CLASS(klass);
842 parent_class->destroy = gnt_entry_destroy;
843 parent_class->draw = gnt_entry_draw;
844 parent_class->map = gnt_entry_map;
845 parent_class->size_request = gnt_entry_size_request;
846 parent_class->key_pressed = gnt_entry_key_pressed;
847 parent_class->lost_focus = gnt_entry_lost_focus;
849 signals[SIG_TEXT_CHANGED] =
850 g_signal_new("text_changed",
851 G_TYPE_FROM_CLASS(klass),
852 G_SIGNAL_RUN_LAST,
853 G_STRUCT_OFFSET(GntEntryClass, text_changed),
854 NULL, NULL,
855 g_cclosure_marshal_VOID__VOID,
856 G_TYPE_NONE, 0);
858 signals[SIG_COMPLETION] =
859 g_signal_new("completion",
860 G_TYPE_FROM_CLASS(klass),
861 G_SIGNAL_RUN_LAST,
862 0, NULL, NULL,
863 gnt_closure_marshal_VOID__POINTER_POINTER,
864 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
866 gnt_bindable_class_register_action(bindable, "cursor-home", move_start,
867 GNT_KEY_CTRL_A, NULL);
868 gnt_bindable_register_binding(bindable, "cursor-home", GNT_KEY_HOME, NULL);
869 gnt_bindable_class_register_action(bindable, "cursor-end", move_end,
870 GNT_KEY_CTRL_E, NULL);
871 gnt_bindable_register_binding(bindable, "cursor-end", GNT_KEY_END, NULL);
872 gnt_bindable_class_register_action(bindable, "delete-prev", backspace,
873 GNT_KEY_BACKSPACE, NULL);
874 gnt_bindable_register_binding(bindable, "delete-prev", s, NULL);
875 gnt_bindable_register_binding(bindable, "delete-prev", GNT_KEY_CTRL_H, NULL);
876 gnt_bindable_class_register_action(bindable, "delete-next", delkey,
877 GNT_KEY_DEL, NULL);
878 gnt_bindable_register_binding(bindable, "delete-next", GNT_KEY_CTRL_D, NULL);
879 gnt_bindable_class_register_action(bindable, "delete-start", del_to_home,
880 GNT_KEY_CTRL_U, NULL);
881 gnt_bindable_class_register_action(bindable, "delete-end", del_to_end,
882 GNT_KEY_CTRL_K, NULL);
883 gnt_bindable_class_register_action(bindable, "delete-prev-word", del_prev_word,
884 GNT_KEY_CTRL_W, NULL);
885 gnt_bindable_class_register_action(bindable, "cursor-prev-word", move_back_word,
886 "\033" "b", NULL);
887 gnt_bindable_class_register_action(bindable, "cursor-prev", move_back,
888 GNT_KEY_LEFT, NULL);
889 gnt_bindable_register_binding(bindable, "cursor-prev", GNT_KEY_CTRL_B, NULL);
890 gnt_bindable_class_register_action(bindable, "cursor-next", move_forward,
891 GNT_KEY_RIGHT, NULL);
892 gnt_bindable_register_binding(bindable, "cursor-next", GNT_KEY_CTRL_F, NULL);
893 gnt_bindable_class_register_action(bindable, "cursor-next-word", move_forward_word,
894 "\033" "f", NULL);
895 gnt_bindable_class_register_action(bindable, "delete-next-word", delete_forward_word,
896 "\033" "d", NULL);
897 gnt_bindable_class_register_action(bindable, "transpose-chars", transpose_chars,
898 GNT_KEY_CTRL_T, NULL);
899 gnt_bindable_class_register_action(bindable, "yank", entry_yank,
900 GNT_KEY_CTRL_Y, NULL);
901 gnt_bindable_class_register_action(bindable, "suggest-show", suggest_show,
902 "\t", NULL);
903 gnt_bindable_class_register_action(bindable, "suggest-next", suggest_next,
904 GNT_KEY_DOWN, NULL);
905 gnt_bindable_class_register_action(bindable, "suggest-prev", suggest_prev,
906 GNT_KEY_UP, NULL);
907 gnt_bindable_class_register_action(bindable, "history-prev", history_prev,
908 GNT_KEY_CTRL_DOWN, NULL);
909 gnt_bindable_class_register_action(bindable, "history-next", history_next,
910 GNT_KEY_CTRL_UP, NULL);
911 gnt_bindable_class_register_action(bindable, "clipboard-paste", clipboard_paste,
912 GNT_KEY_CTRL_V, NULL);
914 gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
915 GNTDEBUG;
918 static GntEntryKillRing *
919 new_killring(void)
921 GntEntryKillRing *kr = g_new0(GntEntryKillRing, 1);
922 kr->buffer = g_string_new(NULL);
923 return kr;
926 static void
927 gnt_entry_init(GTypeInstance *instance, gpointer class)
929 GntWidget *widget = GNT_WIDGET(instance);
930 GntEntry *entry = GNT_ENTRY(instance);
932 entry->flag = GNT_ENTRY_FLAG_ALL;
933 entry->max = 0;
935 entry->histlength = 0;
936 entry->history = NULL;
938 entry->word = TRUE;
939 entry->always = FALSE;
940 entry->suggests = NULL;
941 entry->killring = new_killring();
943 GNT_WIDGET_SET_FLAGS(GNT_WIDGET(entry),
944 GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | GNT_WIDGET_CAN_TAKE_FOCUS);
945 GNT_WIDGET_SET_FLAGS(GNT_WIDGET(entry), GNT_WIDGET_GROW_X);
947 widget->priv.minw = 3;
948 widget->priv.minh = 1;
950 GNTDEBUG;
953 /******************************************************************************
954 * GntEntry API
955 *****************************************************************************/
956 GType
957 gnt_entry_get_gtype(void)
959 static GType type = 0;
961 if(type == 0)
963 static const GTypeInfo info = {
964 sizeof(GntEntryClass),
965 NULL, /* base_init */
966 NULL, /* base_finalize */
967 (GClassInitFunc)gnt_entry_class_init,
968 NULL, /* class_finalize */
969 NULL, /* class_data */
970 sizeof(GntEntry),
971 0, /* n_preallocs */
972 gnt_entry_init, /* instance_init */
973 NULL /* value_table */
976 type = g_type_register_static(GNT_TYPE_WIDGET,
977 "GntEntry",
978 &info, 0);
981 return type;
984 GntWidget *gnt_entry_new(const char *text)
986 GntWidget *widget = g_object_new(GNT_TYPE_ENTRY, NULL);
987 GntEntry *entry = GNT_ENTRY(widget);
989 gnt_entry_set_text_internal(entry, text);
991 return widget;
994 static void
995 gnt_entry_set_text_internal(GntEntry *entry, const char *text)
997 int len;
998 int scroll, cursor;
1000 g_free(entry->start);
1002 if (text && text[0])
1004 len = strlen(text);
1006 else
1008 len = 0;
1011 entry->buffer = len + 128;
1013 scroll = entry->scroll - entry->start;
1014 cursor = entry->end - entry->cursor;
1016 entry->start = g_new0(char, entry->buffer);
1017 if (text)
1018 snprintf(entry->start, len + 1, "%s", text);
1019 entry->end = entry->start + len;
1021 entry->scroll = entry->start + scroll;
1022 entry->cursor = entry->end - cursor;
1024 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(entry), GNT_WIDGET_MAPPED))
1025 entry_redraw(GNT_WIDGET(entry));
1028 void gnt_entry_set_text(GntEntry *entry, const char *text)
1030 gboolean changed = TRUE;
1031 if (text == NULL && entry->start == NULL)
1032 changed = FALSE;
1033 if (text && entry->start && g_utf8_collate(text, entry->start) == 0)
1034 changed = FALSE;
1035 gnt_entry_set_text_internal(entry, text);
1036 if (changed)
1037 entry_text_changed(entry);
1040 void gnt_entry_set_max(GntEntry *entry, int max)
1042 entry->max = max;
1045 void gnt_entry_set_flag(GntEntry *entry, GntEntryFlag flag)
1047 entry->flag = flag;
1048 /* XXX: Check the existing string to make sure the flags are respected? */
1051 const char *gnt_entry_get_text(GntEntry *entry)
1053 return entry->start;
1056 void gnt_entry_clear(GntEntry *entry)
1058 gnt_entry_set_text_internal(entry, NULL);
1059 entry->scroll = entry->cursor = entry->end = entry->start;
1060 entry_redraw(GNT_WIDGET(entry));
1061 destroy_suggest(entry);
1062 entry_text_changed(entry);
1065 void gnt_entry_set_masked(GntEntry *entry, gboolean set)
1067 entry->masked = set;
1070 void gnt_entry_add_to_history(GntEntry *entry, const char *text)
1072 g_return_if_fail(entry->history != NULL); /* Need to set_history_length first */
1074 if (g_list_length(entry->history) >= entry->histlength)
1075 return;
1077 entry->history = g_list_first(entry->history);
1078 g_free(entry->history->data);
1079 entry->history->data = g_strdup(text);
1080 entry->history = g_list_prepend(entry->history, NULL);
1083 void gnt_entry_set_history_length(GntEntry *entry, int num)
1085 if (num == 0)
1087 entry->histlength = num;
1088 if (entry->history)
1090 entry->history = g_list_first(entry->history);
1091 g_list_foreach(entry->history, (GFunc)g_free, NULL);
1092 g_list_free(entry->history);
1093 entry->history = NULL;
1095 return;
1098 if (entry->histlength == 0)
1100 entry->histlength = num;
1101 entry->history = g_list_append(NULL, NULL);
1102 return;
1105 if (num > 0 && num < entry->histlength)
1107 GList *first, *iter;
1108 int index = 0;
1109 for (first = entry->history, index = 0; first->prev; first = first->prev, index++);
1110 while ((iter = g_list_nth(first, num)) != NULL)
1112 g_free(iter->data);
1113 first = g_list_delete_link(first, iter);
1115 entry->histlength = num;
1116 if (index >= num)
1117 entry->history = g_list_last(first);
1118 return;
1121 entry->histlength = num;
1124 void gnt_entry_set_word_suggest(GntEntry *entry, gboolean word)
1126 entry->word = word;
1129 void gnt_entry_set_always_suggest(GntEntry *entry, gboolean always)
1131 entry->always = always;
1134 void gnt_entry_add_suggest(GntEntry *entry, const char *text)
1136 GList *find;
1138 if (!text || !*text)
1139 return;
1141 find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
1142 if (find)
1143 return;
1144 entry->suggests = g_list_append(entry->suggests, g_strdup(text));
1147 void gnt_entry_remove_suggest(GntEntry *entry, const char *text)
1149 GList *find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
1150 if (find)
1152 g_free(find->data);
1153 entry->suggests = g_list_delete_link(entry->suggests, find);