1 /*
2  * GNT - The GLib Ncurses Toolkit
3  *
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.
7  *
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.
12  *
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.
17  *
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
21  */
22 
23 #include <ctype.h>
24 #include <string.h>
25 
26 #include "gntinternal.h"
27 #include "gntbox.h"
28 #include "gntentry.h"
29 #include "gntmarshal.h"
30 #include "gntstyle.h"
31 #include "gnttree.h"
32 #include "gntutils.h"
33 
34 enum
35 {
36 	SIG_TEXT_CHANGED,
37 	SIG_COMPLETION,
38 	SIGS,
39 };
40 
41 typedef enum
42 {
43 	ENTRY_JAIL = -1,    /* Suspend the kill ring. */
44 	ENTRY_DEL_BWD_WORD = 1,
45 	ENTRY_DEL_BWD_CHAR,
46 	ENTRY_DEL_FWD_WORD,
47 	ENTRY_DEL_FWD_CHAR,
48 	ENTRY_DEL_EOL,
49 	ENTRY_DEL_BOL,
50 } GntEntryAction;
51 
52 struct _GntEntryKillRing
53 {
54 	GString *buffer;
55 	GntEntryAction last;
56 };
57 
58 struct _GntEntrySearch
59 {
60 	char *needle;
61 };
62 
63 static guint signals[SIGS] = { 0 };
64 
65 static GntWidgetClass *parent_class = NULL;
66 
67 static gboolean gnt_entry_key_pressed(GntWidget *widget, const char *text);
68 static void gnt_entry_set_text_internal(GntEntry *entry, const char *text);
69 
70 static gboolean
update_kill_ring(GntEntry * entry,GntEntryAction action,const char * text,int len)71 update_kill_ring(GntEntry *entry, GntEntryAction action, const char *text, int len)
72 {
73 	if (action < 0) {
74 		entry->killring->last = action;
75 		return FALSE;
76 	}
77 
78 	if (len == 0)
79 		len = strlen(text);
80 	else if (len < 0) {
81 		text += len;
82 		len = -len;
83 	}
84 
85 	if (action != entry->killring->last) {
86 		struct {
87 			GntEntryAction one;
88 			GntEntryAction two;
89 		} merges[] = {
90 			{ENTRY_DEL_BWD_WORD, ENTRY_DEL_FWD_WORD},
91 			{ENTRY_DEL_BWD_CHAR, ENTRY_DEL_FWD_CHAR},
92 			{ENTRY_DEL_BOL, ENTRY_DEL_EOL},
93 			{ENTRY_JAIL, ENTRY_JAIL},
94 		};
95 		int i;
96 
97 		for (i = 0; merges[i].one != ENTRY_JAIL; i++) {
98 			if (merges[i].one == entry->killring->last &&
99 					merges[i].two == action) {
100 				g_string_append_len(entry->killring->buffer, text, len);
101 				break;
102 			} else if (merges[i].one == action &&
103 					merges[i].two == entry->killring->last) {
104 				g_string_prepend_len(entry->killring->buffer, text, len);
105 				break;
106 			}
107 		}
108 		if (merges[i].one == ENTRY_JAIL) {
109 			g_string_assign(entry->killring->buffer, text);
110 			g_string_truncate(entry->killring->buffer, len);
111 		}
112 		entry->killring->last = action;
113 	} else {
114 		if (action == ENTRY_DEL_BWD_CHAR || action == ENTRY_DEL_BWD_WORD)
115 			g_string_prepend_len(entry->killring->buffer, text, len);
116 		else
117 			g_string_append_len(entry->killring->buffer, text, len);
118 	}
119 	return TRUE;
120 }
121 
122 static void
destroy_suggest(GntEntry * entry)123 destroy_suggest(GntEntry *entry)
124 {
125 	if (entry->ddown)
126 	{
127 		gnt_widget_destroy(entry->ddown->parent);
128 		entry->ddown = NULL;
129 	}
130 }
131 
132 static char *
get_beginning_of_word(GntEntry * entry)133 get_beginning_of_word(GntEntry *entry)
134 {
135 	char *s = entry->cursor;
136 	while (s > entry->start)
137 	{
138 		char *t = g_utf8_find_prev_char(entry->start, s);
139 		if (isspace(*t))
140 			break;
141 		s = t;
142 	}
143 	return s;
144 }
145 
146 static gboolean
complete_suggest(GntEntry * entry,const char * text)147 complete_suggest(GntEntry *entry, const char *text)
148 {
149 	int offstart = 0, offend = 0;
150 
151 	if (entry->word) {
152 		char *s = get_beginning_of_word(entry);
153 		const char *iter = text;
154 		offstart = g_utf8_pointer_to_offset(entry->start, s);
155 		while (*iter && toupper(*s) == toupper(*iter)) {
156 			*s++ = *iter++;
157 		}
158 		if (*iter) {
159 			gnt_entry_key_pressed(GNT_WIDGET(entry), iter);
160 		}
161 		offend = g_utf8_pointer_to_offset(entry->start, entry->cursor);
162 	} else {
163 		offstart = 0;
164 		gnt_entry_set_text_internal(entry, text);
165 		offend = g_utf8_strlen(text, -1);
166 	}
167 
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 TRUE;
172 }
173 
174 static int
max_common_prefix(const char * s,const char * t)175 max_common_prefix(const char *s, const char *t)
176 {
177 	const char *f = s;
178 	while (*f && *t && *f == *t++)
179 		f++;
180 	return f - s;
181 }
182 
183 static gboolean
show_suggest_dropdown(GntEntry * entry)184 show_suggest_dropdown(GntEntry *entry)
185 {
186 	char *suggest = NULL;
187 	gsize 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;
194 
195 	if (entry->word)
196 	{
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);
201 	}
202 	else
203 		suggest = g_strdup(entry->start);
204 	len = strlen(suggest);  /* Don't need to use the utf8-function here */
205 
206 	if (entry->ddown == NULL)
207 	{
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);
212 
213 		gnt_widget_set_transient(box, TRUE);
214 
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);
221 	}
222 	else
223 		gnt_tree_remove_all(GNT_TREE(entry->ddown));
224 
225 	for (count = 0, iter = entry->suggests; iter; iter = iter->next)
226 	{
227 		text = iter->data;
228 		if (g_ascii_strncasecmp(suggest, text, len) == 0 && strlen(text) >= len)
229 		{
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;
239 		}
240 	}
241 	g_free(suggest);
242 
243 	if (count == 0) {
244 		destroy_suggest(entry);
245 		return FALSE;
246 	} else if (count == 1) {
247 		char *store = g_strndup(entry->start, entry->end - entry->start);
248 		gboolean ret;
249 
250 		destroy_suggest(entry);
251 		complete_suggest(entry, sgst);
252 
253 		ret = (strncmp(store, entry->start, entry->end - entry->start) != 0);
254 		g_free(store);
255 		return ret;
256 	} else {
257 		if (max > 0) {
258 			GntWidget *ddown = entry->ddown;
259 			char *match = g_strndup(sgst + len, max);
260 			entry->ddown = NULL;
261 			gnt_entry_key_pressed(GNT_WIDGET(entry), match);
262 			g_free(match);
263 			if (entry->ddown)
264 				gnt_widget_destroy(ddown);
265 			else
266 				entry->ddown = ddown;
267 		}
268 		gnt_widget_draw(entry->ddown->parent);
269 	}
270 
271 	return TRUE;
272 }
273 
274 static void
gnt_entry_draw(GntWidget * widget)275 gnt_entry_draw(GntWidget *widget)
276 {
277 	GntEntry *entry = GNT_ENTRY(widget);
278 	int stop;
279 	gboolean focus;
280 	int curpos;
281 
282 	if ((focus = gnt_widget_has_focus(widget)))
283 		wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_TEXT_NORMAL));
284 	else
285 		wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
286 
287 	if (entry->masked)
288 	{
289 		mvwhline(widget->window, 0, 0, gnt_ascii_only() ? '*' : ACS_BULLET,
290 				g_utf8_pointer_to_offset(entry->scroll, entry->end));
291 	}
292 	else
293 		mvwprintw(widget->window, 0, 0, "%s", C_(entry->scroll));
294 
295 	stop = gnt_util_onscreen_width(entry->scroll, entry->end);
296 	if (stop < widget->priv.width)
297 		mvwhline(widget->window, 0, stop, ENTRY_CHAR, widget->priv.width - stop);
298 
299 	curpos = gnt_util_onscreen_width(entry->scroll, entry->cursor);
300 	if (focus)
301 		mvwchgat(widget->window, 0, curpos, 1, A_REVERSE, GNT_COLOR_TEXT_NORMAL, NULL);
302 	(void)wmove(widget->window, 0, curpos);
303 
304 	GNTDEBUG;
305 }
306 
307 static void
gnt_entry_size_request(GntWidget * widget)308 gnt_entry_size_request(GntWidget *widget)
309 {
310 	if (!gnt_widget_get_mapped(widget)) {
311 		widget->priv.height = 1;
312 		widget->priv.width = 20;
313 	}
314 }
315 
316 static void
gnt_entry_map(GntWidget * widget)317 gnt_entry_map(GntWidget *widget)
318 {
319 	if (widget->priv.width == 0 || widget->priv.height == 0)
320 		gnt_widget_size_request(widget);
321 	GNTDEBUG;
322 }
323 
324 static void
entry_redraw(GntWidget * widget)325 entry_redraw(GntWidget *widget)
326 {
327 	gnt_entry_draw(widget);
328 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
329 	gnt_widget_queue_update(widget);
330 G_GNUC_END_IGNORE_DEPRECATIONS
331 }
332 
333 static void
entry_text_changed(GntEntry * entry)334 entry_text_changed(GntEntry *entry)
335 {
336 	g_signal_emit(entry, signals[SIG_TEXT_CHANGED], 0);
337 }
338 
339 static gboolean
move_back(GntBindable * bind,GList * null)340 move_back(GntBindable *bind, GList *null)
341 {
342 	GntEntry *entry = GNT_ENTRY(bind);
343 	if (entry->cursor <= entry->start)
344 		return FALSE;
345 	entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor);
346 	if (entry->cursor < entry->scroll)
347 		entry->scroll = entry->cursor;
348 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
349 	entry_redraw(GNT_WIDGET(entry));
350 	return TRUE;
351 }
352 
353 static gboolean
move_forward(GntBindable * bind,GList * list)354 move_forward(GntBindable *bind, GList *list)
355 {
356 	GntEntry *entry = GNT_ENTRY(bind);
357 	if (entry->cursor >= entry->end)
358 		return FALSE;
359 	entry->cursor = g_utf8_find_next_char(entry->cursor, NULL);
360 	while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
361 		entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
362 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
363 	entry_redraw(GNT_WIDGET(entry));
364 	return TRUE;
365 }
366 
367 static gboolean
backspace(GntBindable * bind,GList * null)368 backspace(GntBindable *bind, GList *null)
369 {
370 	int len;
371 	GntEntry *entry = GNT_ENTRY(bind);
372 
373 	if (entry->cursor <= entry->start)
374 		return TRUE;
375 
376 	len = entry->cursor - g_utf8_find_prev_char(entry->start, entry->cursor);
377 	update_kill_ring(entry, ENTRY_JAIL, entry->cursor, -len);
378 	entry->cursor -= len;
379 
380 	memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor);
381 	entry->end -= len;
382 
383 	if (entry->scroll > entry->start)
384 		entry->scroll = g_utf8_find_prev_char(entry->start, entry->scroll);
385 
386 	entry_redraw(GNT_WIDGET(entry));
387 	if (entry->ddown)
388 		show_suggest_dropdown(entry);
389 	entry_text_changed(entry);
390 	return TRUE;
391 }
392 
393 static gboolean
delkey(GntBindable * bind,GList * null)394 delkey(GntBindable *bind, GList *null)
395 {
396 	int len;
397 	GntEntry *entry = GNT_ENTRY(bind);
398 
399 	if (entry->cursor >= entry->end)
400 		return FALSE;
401 
402 	len = g_utf8_find_next_char(entry->cursor, NULL) - entry->cursor;
403 	update_kill_ring(entry, ENTRY_JAIL, entry->cursor, len);
404 	memmove(entry->cursor, entry->cursor + len, entry->end - entry->cursor - len + 1);
405 	entry->end -= len;
406 	entry_redraw(GNT_WIDGET(entry));
407 
408 	if (entry->ddown)
409 		show_suggest_dropdown(entry);
410 	entry_text_changed(entry);
411 	return TRUE;
412 }
413 
414 static gboolean
move_start(GntBindable * bind,GList * null)415 move_start(GntBindable *bind, GList *null)
416 {
417 	GntEntry *entry = GNT_ENTRY(bind);
418 	entry->scroll = entry->cursor = entry->start;
419 	entry_redraw(GNT_WIDGET(entry));
420 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
421 	return TRUE;
422 }
423 
424 static gboolean
move_end(GntBindable * bind,GList * null)425 move_end(GntBindable *bind, GList *null)
426 {
427 	GntEntry *entry = GNT_ENTRY(bind);
428 	entry->cursor = entry->end;
429 	/* This should be better than this */
430 	while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= GNT_WIDGET(entry)->priv.width)
431 		entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
432 	entry_redraw(GNT_WIDGET(entry));
433 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
434 	return TRUE;
435 }
436 
437 static gboolean
history_next(GntBindable * bind,GList * null)438 history_next(GntBindable *bind, GList *null)
439 {
440 	GntEntry *entry = GNT_ENTRY(bind);
441 	if (entry->histlength && entry->history->prev)
442 	{
443 		entry->history = entry->history->prev;
444 		gnt_entry_set_text_internal(entry, entry->history->data);
445 		destroy_suggest(entry);
446 		entry_text_changed(entry);
447 
448 		update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
449 		return TRUE;
450 	}
451 	return FALSE;
452 }
453 
454 static gboolean
history_prev(GntBindable * bind,GList * null)455 history_prev(GntBindable *bind, GList *null)
456 {
457 	GntEntry *entry = GNT_ENTRY(bind);
458 	if (entry->histlength && entry->history->next)
459 	{
460 		if (entry->history->prev == NULL)
461 		{
462 			/* Save the current contents */
463 			char *text = g_strdup(gnt_entry_get_text(entry));
464 			g_free(entry->history->data);
465 			entry->history->data = text;
466 		}
467 
468 		entry->history = entry->history->next;
469 		gnt_entry_set_text_internal(entry, entry->history->data);
470 		destroy_suggest(entry);
471 		entry_text_changed(entry);
472 
473 		update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
474 		return TRUE;
475 	}
476 	return FALSE;
477 }
478 
479 static gboolean
history_search(GntBindable * bind,GList * null)480 history_search(GntBindable *bind, GList *null)
481 {
482 	GntEntry *entry = GNT_ENTRY(bind);
483 	GList *iter;
484 	const char *current;
485 
486 	if (entry->history->prev && entry->search->needle)
487 		current = entry->search->needle;
488 	else
489 		current = gnt_entry_get_text(entry);
490 
491 	if (!entry->histlength || !entry->history->next || !*current)
492 		return FALSE;
493 
494 	for (iter = entry->history->next; iter; iter = iter->next) {
495 		const char *str = iter->data;
496 		/* A more utf8-friendly version of strstr would have been better, but
497 		 * for now, this will have to do. */
498 		if (strstr(str, current) != NULL)
499 			break;
500 	}
501 
502 	if (!iter)
503 		return TRUE;
504 
505 	if (entry->history->prev == NULL) {
506 		/* We are doing it for the first time. Save the current contents */
507 		char *text = g_strdup(gnt_entry_get_text(entry));
508 
509 		g_free(entry->search->needle);
510 		entry->search->needle = g_strdup(current);
511 
512 		g_free(entry->history->data);
513 		entry->history->data = text;
514 	}
515 
516 	entry->history = iter;
517 	gnt_entry_set_text_internal(entry, entry->history->data);
518 	destroy_suggest(entry);
519 	entry_text_changed(entry);
520 
521 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
522 	return TRUE;
523 }
524 
525 static gboolean
clipboard_paste(GntBindable * bind,GList * n)526 clipboard_paste(GntBindable *bind, GList *n)
527 {
528 	GntEntry *entry = GNT_ENTRY(bind);
529 	gchar *i, *text, *a, *all;
530 	text = i = gnt_get_clipboard_string();
531 	while (*i != '\0') {
532 		i = g_utf8_next_char(i);
533 		if (*i == '\r' || *i == '\n')
534 			*i = ' ';
535 	}
536 	a = g_strndup(entry->start, entry->cursor - entry->start);
537 	all = g_strconcat(a, text, entry->cursor, NULL);
538 	gnt_entry_set_text_internal(entry, all);
539 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
540 	g_free(a);
541 	g_free(text);
542 	g_free(all);
543 	return TRUE;
544 }
545 
546 static gboolean
suggest_show(GntBindable * bind,GList * null)547 suggest_show(GntBindable *bind, GList *null)
548 {
549 	GntEntry *entry = GNT_ENTRY(bind);
550 	if (entry->ddown) {
551 		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down", NULL);
552 		return TRUE;
553 	}
554 	return show_suggest_dropdown(entry);
555 }
556 
557 static gboolean
suggest_next(GntBindable * bind,GList * null)558 suggest_next(GntBindable *bind, GList *null)
559 {
560 	GntEntry *entry = GNT_ENTRY(bind);
561 	if (entry->ddown) {
562 		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-down", NULL);
563 		return TRUE;
564 	}
565 	return FALSE;
566 }
567 
568 static gboolean
suggest_prev(GntBindable * bind,GList * null)569 suggest_prev(GntBindable *bind, GList *null)
570 {
571 	GntEntry *entry = GNT_ENTRY(bind);
572 	if (entry->ddown) {
573 		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "move-up", NULL);
574 		return TRUE;
575 	}
576 	return FALSE;
577 }
578 
579 static gboolean
suggest_next_page(GntBindable * bind,GList * null)580 suggest_next_page(GntBindable *bind, GList *null)
581 {
582 	GntEntry *entry = GNT_ENTRY(bind);
583 	if (entry->ddown) {
584 		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "page-down", NULL);
585 		return TRUE;
586 	}
587 	return FALSE;
588 }
589 
590 static gboolean
suggest_prev_page(GntBindable * bind,GList * null)591 suggest_prev_page(GntBindable *bind, GList *null)
592 {
593 	GntEntry *entry = GNT_ENTRY(bind);
594 	if (entry->ddown) {
595 		gnt_bindable_perform_action_named(GNT_BINDABLE(entry->ddown), "page-up", NULL);
596 		return TRUE;
597 	}
598 	return FALSE;
599 }
600 
601 static gboolean
del_to_home(GntBindable * bind,GList * null)602 del_to_home(GntBindable *bind, GList *null)
603 {
604 	GntEntry *entry = GNT_ENTRY(bind);
605 	if (entry->cursor <= entry->start)
606 		return TRUE;
607 	update_kill_ring(entry, ENTRY_DEL_BOL, entry->start, entry->cursor - entry->start);
608 	memmove(entry->start, entry->cursor, entry->end - entry->cursor);
609 	entry->end -= (entry->cursor - entry->start);
610 	entry->cursor = entry->scroll = entry->start;
611 	memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
612 	entry_redraw(GNT_WIDGET(bind));
613 	entry_text_changed(entry);
614 	return TRUE;
615 }
616 
617 static gboolean
del_to_end(GntBindable * bind,GList * null)618 del_to_end(GntBindable *bind, GList *null)
619 {
620 	GntEntry *entry = GNT_ENTRY(bind);
621 	if (entry->end <= entry->cursor)
622 		return TRUE;
623 	update_kill_ring(entry, ENTRY_DEL_EOL, entry->cursor, entry->end - entry->cursor);
624 	entry->end = entry->cursor;
625 	memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
626 	entry_redraw(GNT_WIDGET(bind));
627 	entry_text_changed(entry);
628 	return TRUE;
629 }
630 
631 #define SAME(a,b)    ((g_unichar_isalnum(a) && g_unichar_isalnum(b)) || \
632 				(g_unichar_isspace(a) && g_unichar_isspace(b)) || \
633 				(g_unichar_iswide(a) && g_unichar_iswide(b)) || \
634 				(g_unichar_ispunct(a) && g_unichar_ispunct(b)))
635 
636 static const char *
begin_word(const char * text,const char * begin)637 begin_word(const char *text, const char *begin)
638 {
639 	gunichar ch = 0;
640 	while (text > begin && (!*text || g_unichar_isspace(g_utf8_get_char(text))))
641 		text = g_utf8_find_prev_char(begin, text);
642 	ch = g_utf8_get_char(text);
643 	while ((text = g_utf8_find_prev_char(begin, text)) >= begin) {
644 		gunichar cur = g_utf8_get_char(text);
645 		if (!SAME(ch, cur))
646 			break;
647 	}
648 
649 	return (text ? g_utf8_find_next_char(text, NULL) : begin);
650 }
651 
652 static const char *
next_begin_word(const char * text,const char * end)653 next_begin_word(const char *text, const char *end)
654 {
655 	gunichar ch = 0;
656 
657 	while (text && text < end && g_unichar_isspace(g_utf8_get_char(text)))
658 		text = g_utf8_find_next_char(text, end);
659 
660 	if (text) {
661 		ch = g_utf8_get_char(text);
662 		while ((text = g_utf8_find_next_char(text, end)) != NULL && text <= end) {
663 			gunichar cur = g_utf8_get_char(text);
664 			if (!SAME(ch, cur))
665 				break;
666 		}
667 	}
668 	return (text ? text : end);
669 }
670 
671 #undef SAME
672 static gboolean
move_back_word(GntBindable * bind,GList * null)673 move_back_word(GntBindable *bind, GList *null)
674 {
675 	GntEntry *entry = GNT_ENTRY(bind);
676 	const char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
677 
678 	if (iter < entry->start)
679 		return TRUE;
680 	iter = begin_word(iter, entry->start);
681 	entry->cursor = (char*)iter;
682 	if (entry->cursor < entry->scroll)
683 		entry->scroll = entry->cursor;
684 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
685 	entry_redraw(GNT_WIDGET(bind));
686 	return TRUE;
687 }
688 
689 static gboolean
del_prev_word(GntBindable * bind,GList * null)690 del_prev_word(GntBindable *bind, GList *null)
691 {
692 	GntWidget *widget = GNT_WIDGET(bind);
693 	GntEntry *entry = GNT_ENTRY(bind);
694 	char *iter = g_utf8_find_prev_char(entry->start, entry->cursor);
695 	int count;
696 
697 	if (iter < entry->start)
698 		return TRUE;
699 	iter = (char*)begin_word(iter, entry->start);
700 	count = entry->cursor - iter;
701 	update_kill_ring(entry, ENTRY_DEL_BWD_WORD, iter, count);
702 	memmove(iter, entry->cursor, entry->end - entry->cursor);
703 	entry->end -= count;
704 	entry->cursor = iter;
705 	if (entry->cursor <= entry->scroll) {
706 		entry->scroll = entry->cursor - widget->priv.width + 2;
707 		if (entry->scroll < entry->start)
708 			entry->scroll = entry->start;
709 	}
710 	memset(entry->end, '\0', entry->buffer - (entry->end - entry->start));
711 	entry_redraw(widget);
712 	entry_text_changed(entry);
713 
714 	return TRUE;
715 }
716 
717 static gboolean
move_forward_word(GntBindable * bind,GList * list)718 move_forward_word(GntBindable *bind, GList *list)
719 {
720 	GntEntry *entry = GNT_ENTRY(bind);
721 	GntWidget *widget = GNT_WIDGET(bind);
722 	entry->cursor = (char *)next_begin_word(entry->cursor, entry->end);
723 	while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width) {
724 		entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
725 	}
726 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
727 	entry_redraw(widget);
728 	return TRUE;
729 }
730 
731 static gboolean
delete_forward_word(GntBindable * bind,GList * list)732 delete_forward_word(GntBindable *bind, GList *list)
733 {
734 	GntEntry *entry = GNT_ENTRY(bind);
735 	GntWidget *widget = GNT_WIDGET(bind);
736 	char *iter = (char *)next_begin_word(entry->cursor, entry->end);
737 	int len = entry->end - iter + 1;
738 	if (len <= 0)
739 		return TRUE;
740 	update_kill_ring(entry, ENTRY_DEL_FWD_WORD, entry->cursor, iter - entry->cursor);
741 	memmove(entry->cursor, iter, len);
742 	len = iter - entry->cursor;
743 	entry->end -= len;
744 	memset(entry->end, '\0', len);
745 	entry_redraw(widget);
746 	entry_text_changed(entry);
747 	return TRUE;
748 }
749 
750 static gboolean
transpose_chars(GntBindable * bind,GList * null)751 transpose_chars(GntBindable *bind, GList *null)
752 {
753 	GntEntry *entry = GNT_ENTRY(bind);
754 	char *current, *prev;
755 	char hold[8];  /* that's right */
756 
757 	if (entry->cursor <= entry->start)
758 		return FALSE;
759 
760 	if (!*entry->cursor)
761 		entry->cursor = g_utf8_find_prev_char(entry->start, entry->cursor);
762 
763 	current = entry->cursor;
764 	prev = g_utf8_find_prev_char(entry->start, entry->cursor);
765 	move_forward(bind, null);
766 
767 	/* Let's do this dance! */
768 	memcpy(hold, prev, current - prev);
769 	memmove(prev, current, entry->cursor - current);
770 	memcpy(prev + (entry->cursor - current), hold, current - prev);
771 
772 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
773 	entry_redraw(GNT_WIDGET(entry));
774 	entry_text_changed(entry);
775 	return TRUE;
776 }
777 
778 static gboolean
entry_yank(GntBindable * bind,GList * null)779 entry_yank(GntBindable *bind, GList *null)
780 {
781 	GntEntry *entry = GNT_ENTRY(bind);
782 	gnt_entry_key_pressed(GNT_WIDGET(entry), entry->killring->buffer->str);
783 	return TRUE;
784 }
785 
786 static gboolean
gnt_entry_key_pressed(GntWidget * widget,const char * text)787 gnt_entry_key_pressed(GntWidget *widget, const char *text)
788 {
789 	GntEntry *entry = GNT_ENTRY(widget);
790 
791 	if (text[0] == 27)
792 	{
793 		if (text[1] == 0)
794 		{
795 			destroy_suggest(entry);
796 			return TRUE;
797 		}
798 
799 		return FALSE;
800 	}
801 
802 	if ((text[0] == '\r' || text[0] == ' ' || text[0] == '\n') && entry->ddown)
803 	{
804 		char *text = g_strdup(gnt_tree_get_selection_data(GNT_TREE(entry->ddown)));
805 		destroy_suggest(entry);
806 		complete_suggest(entry, text);
807 		g_free(text);
808 		update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
809 		entry_text_changed(entry);
810 		return TRUE;
811 	}
812 
813 	if (!iscntrl(text[0]))
814 	{
815 		const char *str, *next;
816 
817 		for (str = text; *str; str = next)
818 		{
819 			gsize len;
820 			next = g_utf8_find_next_char(str, NULL);
821 			len = next - str;
822 
823 			/* Valid input? */
824 			/* XXX: Is it necessary to use _unichar_ variants here? */
825 			if (ispunct(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_PUNCT))
826 				continue;
827 			if (isspace(*str) && (entry->flag & GNT_ENTRY_FLAG_NO_SPACE))
828 				continue;
829 			if (isalpha(*str) && !(entry->flag & GNT_ENTRY_FLAG_ALPHA))
830 				continue;
831 			if (isdigit(*str) && !(entry->flag & GNT_ENTRY_FLAG_INT))
832 				continue;
833 
834 			/* Reached the max? */
835 			if (entry->max && g_utf8_pointer_to_offset(entry->start, entry->end) >= entry->max)
836 				continue;
837 
838 			if ((gsize)(entry->end + len - entry->start) >= entry->buffer)
839 			{
840 				/* This will cause the buffer to grow */
841 				char *tmp = g_strdup(entry->start);
842 				gnt_entry_set_text_internal(entry, tmp);
843 				g_free(tmp);
844 			}
845 
846 			memmove(entry->cursor + len, entry->cursor, entry->end - entry->cursor + 1);
847 			entry->end += len;
848 
849 			while (str < next)
850 			{
851 				if (*str == '\r' || *str == '\n')
852 					*entry->cursor = ' ';
853 				else
854 					*entry->cursor = *str;
855 				entry->cursor++;
856 				str++;
857 			}
858 
859 			while (gnt_util_onscreen_width(entry->scroll, entry->cursor) >= widget->priv.width)
860 				entry->scroll = g_utf8_find_next_char(entry->scroll, NULL);
861 
862 			if (entry->ddown)
863 				show_suggest_dropdown(entry);
864 		}
865 		update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
866 		entry_redraw(widget);
867 		entry_text_changed(entry);
868 		return TRUE;
869 	}
870 
871 	if (text[0] == '\r' || text[0] == '\n') {
872 		gnt_widget_activate(widget);
873 		return TRUE;
874 	}
875 
876 	return FALSE;
877 }
878 
879 static void
jail_killring(GntEntryKillRing * kr)880 jail_killring(GntEntryKillRing *kr)
881 {
882 	g_string_free(kr->buffer, TRUE);
883 	g_free(kr);
884 }
885 
886 static void
gnt_entry_destroy(GntWidget * widget)887 gnt_entry_destroy(GntWidget *widget)
888 {
889 	GntEntry *entry = GNT_ENTRY(widget);
890 	g_free(entry->start);
891 
892 	if (entry->history)
893 	{
894 		entry->history = g_list_first(entry->history);
895 		g_list_foreach(entry->history, (GFunc)g_free, NULL);
896 		g_list_free(entry->history);
897 	}
898 
899 	if (entry->suggests)
900 	{
901 		g_list_foreach(entry->suggests, (GFunc)g_free, NULL);
902 		g_list_free(entry->suggests);
903 	}
904 
905 	if (entry->ddown)
906 	{
907 		gnt_widget_destroy(entry->ddown->parent);
908 	}
909 
910 	g_free(entry->search->needle);
911 	g_free(entry->search);
912 
913 	jail_killring(entry->killring);
914 }
915 
916 static void
gnt_entry_lost_focus(GntWidget * widget)917 gnt_entry_lost_focus(GntWidget *widget)
918 {
919 	GntEntry *entry = GNT_ENTRY(widget);
920 	destroy_suggest(entry);
921 	entry_redraw(widget);
922 }
923 
924 static gboolean
gnt_entry_clicked(GntWidget * widget,GntMouseEvent event,int x,int y)925 gnt_entry_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
926 {
927 	if (event == GNT_MIDDLE_MOUSE_DOWN) {
928 		clipboard_paste(GNT_BINDABLE(widget), NULL);
929 		return TRUE;
930 	}
931 	return FALSE;
932 
933 }
934 
935 static void
gnt_entry_class_init(GntEntryClass * klass)936 gnt_entry_class_init(GntEntryClass *klass)
937 {
938 	GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass);
939 	char s[3] = {'\033', erasechar(), 0};
940 
941 	parent_class = GNT_WIDGET_CLASS(klass);
942 	parent_class->clicked = gnt_entry_clicked;
943 	parent_class->destroy = gnt_entry_destroy;
944 	parent_class->draw = gnt_entry_draw;
945 	parent_class->map = gnt_entry_map;
946 	parent_class->size_request = gnt_entry_size_request;
947 	parent_class->key_pressed = gnt_entry_key_pressed;
948 	parent_class->lost_focus = gnt_entry_lost_focus;
949 
950 	signals[SIG_TEXT_CHANGED] =
951 		g_signal_new("text_changed",
952 					 G_TYPE_FROM_CLASS(klass),
953 					 G_SIGNAL_RUN_LAST,
954 					 G_STRUCT_OFFSET(GntEntryClass, text_changed),
955 					 NULL, NULL,
956 					 g_cclosure_marshal_VOID__VOID,
957 					 G_TYPE_NONE, 0);
958 
959 	/**
960 	 * GntEntry::completion:
961 	 *
962 	 * Since: 2.1.0
963 	 */
964 	signals[SIG_COMPLETION] =
965 		g_signal_new("completion",
966 					 G_TYPE_FROM_CLASS(klass),
967 					 G_SIGNAL_RUN_LAST,
968 					 0, NULL, NULL,
969 					 gnt_closure_marshal_VOID__POINTER_POINTER,
970 					 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
971 
972 	gnt_bindable_class_register_action(bindable, "cursor-home", move_start,
973 				GNT_KEY_CTRL_A, NULL);
974 	gnt_bindable_register_binding(bindable, "cursor-home", GNT_KEY_HOME, NULL);
975 	gnt_bindable_class_register_action(bindable, "cursor-end", move_end,
976 				GNT_KEY_CTRL_E, NULL);
977 	gnt_bindable_register_binding(bindable, "cursor-end", GNT_KEY_END, NULL);
978 	gnt_bindable_class_register_action(bindable, "delete-prev", backspace,
979 				GNT_KEY_BACKSPACE, NULL);
980 	gnt_bindable_register_binding(bindable, "delete-prev", s + 1, NULL);
981 	gnt_bindable_register_binding(bindable, "delete-prev", GNT_KEY_CTRL_H, NULL);
982 	gnt_bindable_class_register_action(bindable, "delete-next", delkey,
983 				GNT_KEY_DEL, NULL);
984 	gnt_bindable_register_binding(bindable, "delete-next", GNT_KEY_CTRL_D, NULL);
985 	gnt_bindable_class_register_action(bindable, "delete-start", del_to_home,
986 				GNT_KEY_CTRL_U, NULL);
987 	gnt_bindable_class_register_action(bindable, "delete-end", del_to_end,
988 				GNT_KEY_CTRL_K, NULL);
989 	gnt_bindable_class_register_action(bindable, "delete-prev-word", del_prev_word,
990 				GNT_KEY_CTRL_W, NULL);
991 	gnt_bindable_register_binding(bindable, "delete-prev-word", s, NULL);
992 	gnt_bindable_class_register_action(bindable, "cursor-prev-word", move_back_word,
993 				"\033" "b", NULL);
994 	gnt_bindable_class_register_action(bindable, "cursor-prev", move_back,
995 				GNT_KEY_LEFT, NULL);
996 	gnt_bindable_register_binding(bindable, "cursor-prev", GNT_KEY_CTRL_B, NULL);
997 	gnt_bindable_class_register_action(bindable, "cursor-next", move_forward,
998 				GNT_KEY_RIGHT, NULL);
999 	gnt_bindable_register_binding(bindable, "cursor-next", GNT_KEY_CTRL_F, NULL);
1000 	gnt_bindable_class_register_action(bindable, "cursor-next-word", move_forward_word,
1001 				"\033" "f", NULL);
1002 	gnt_bindable_class_register_action(bindable, "delete-next-word", delete_forward_word,
1003 				"\033" "d", NULL);
1004 	gnt_bindable_class_register_action(bindable, "transpose-chars", transpose_chars,
1005 				GNT_KEY_CTRL_T, NULL);
1006 	gnt_bindable_class_register_action(bindable, "yank", entry_yank,
1007 				GNT_KEY_CTRL_Y, NULL);
1008 	gnt_bindable_class_register_action(bindable, "suggest-show", suggest_show,
1009 				"\t", NULL);
1010 	gnt_bindable_class_register_action(bindable, "suggest-next", suggest_next,
1011 				GNT_KEY_DOWN, NULL);
1012 	gnt_bindable_class_register_action(bindable, "suggest-prev", suggest_prev,
1013 				GNT_KEY_UP, NULL);
1014 	gnt_bindable_class_register_action(bindable, "suggest-next-page", suggest_next_page,
1015 				GNT_KEY_PGDOWN, NULL);
1016 	gnt_bindable_class_register_action(bindable, "suggest-prev-page", suggest_prev_page,
1017 				GNT_KEY_PGUP, NULL);
1018 	gnt_bindable_class_register_action(bindable, "history-next", history_next,
1019 				GNT_KEY_CTRL_DOWN, NULL);
1020 	gnt_bindable_class_register_action(bindable, "history-prev", history_prev,
1021 				GNT_KEY_CTRL_UP, NULL);
1022 	gnt_bindable_register_binding(bindable, "history-prev", GNT_KEY_CTRL_P, NULL);
1023 	gnt_bindable_register_binding(bindable, "history-next", GNT_KEY_CTRL_N, NULL);
1024 	gnt_bindable_class_register_action(bindable, "history-search", history_search,
1025 				GNT_KEY_CTRL_R, NULL);
1026 	gnt_bindable_class_register_action(bindable, "clipboard-paste", clipboard_paste,
1027 				GNT_KEY_CTRL_V, NULL);
1028 
1029 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
1030 	GNTDEBUG;
1031 }
1032 
1033 static GntEntryKillRing *
new_killring(void)1034 new_killring(void)
1035 {
1036 	GntEntryKillRing *kr = g_new0(GntEntryKillRing, 1);
1037 	kr->buffer = g_string_new(NULL);
1038 	return kr;
1039 }
1040 
1041 static void
gnt_entry_init(GTypeInstance * instance,gpointer class)1042 gnt_entry_init(GTypeInstance *instance, gpointer class)
1043 {
1044 	GntWidget *widget = GNT_WIDGET(instance);
1045 	GntEntry *entry = GNT_ENTRY(instance);
1046 
1047 	entry->flag = GNT_ENTRY_FLAG_ALL;
1048 	entry->max = 0;
1049 
1050 	entry->histlength = 0;
1051 	entry->history = NULL;
1052 
1053 	entry->word = TRUE;
1054 	entry->always = FALSE;
1055 	entry->suggests = NULL;
1056 	entry->killring = new_killring();
1057 	entry->search = g_new0(GntEntrySearch, 1);
1058 
1059 	gnt_widget_set_has_border(widget, FALSE);
1060 	gnt_widget_set_has_shadow(widget, FALSE);
1061 	gnt_widget_set_take_focus(widget, TRUE);
1062 	gnt_widget_set_grow_x(widget, TRUE);
1063 
1064 	widget->priv.minw = 3;
1065 	widget->priv.minh = 1;
1066 
1067 	GNTDEBUG;
1068 }
1069 
1070 /******************************************************************************
1071  * GntEntry API
1072  *****************************************************************************/
1073 GType
gnt_entry_get_gtype(void)1074 gnt_entry_get_gtype(void)
1075 {
1076 	static GType type = 0;
1077 
1078 	if(type == 0)
1079 	{
1080 		static const GTypeInfo info = {
1081 			sizeof(GntEntryClass),
1082 			NULL,					/* base_init		*/
1083 			NULL,					/* base_finalize	*/
1084 			(GClassInitFunc)gnt_entry_class_init,
1085 			NULL,					/* class_finalize	*/
1086 			NULL,					/* class_data		*/
1087 			sizeof(GntEntry),
1088 			0,						/* n_preallocs		*/
1089 			gnt_entry_init,			/* instance_init	*/
1090 			NULL					/* value_table		*/
1091 		};
1092 
1093 		type = g_type_register_static(GNT_TYPE_WIDGET,
1094 									  "GntEntry",
1095 									  &info, 0);
1096 	}
1097 
1098 	return type;
1099 }
1100 
gnt_entry_new(const char * text)1101 GntWidget *gnt_entry_new(const char *text)
1102 {
1103 	GntWidget *widget = g_object_new(GNT_TYPE_ENTRY, NULL);
1104 	GntEntry *entry = GNT_ENTRY(widget);
1105 
1106 	gnt_entry_set_text_internal(entry, text);
1107 
1108 	return widget;
1109 }
1110 
1111 static void
gnt_entry_set_text_internal(GntEntry * entry,const char * text)1112 gnt_entry_set_text_internal(GntEntry *entry, const char *text)
1113 {
1114 	int len;
1115 	int scroll, cursor;
1116 
1117 	g_free(entry->start);
1118 
1119 	if (text && text[0])
1120 	{
1121 		len = strlen(text);
1122 	}
1123 	else
1124 	{
1125 		len = 0;
1126 	}
1127 
1128 	entry->buffer = len + 128;
1129 
1130 	scroll = entry->scroll - entry->start;
1131 	cursor = entry->end - entry->cursor;
1132 
1133 	entry->start = g_new0(char, entry->buffer);
1134 	if (text)
1135 		snprintf(entry->start, len + 1, "%s", text);
1136 	entry->end = entry->start + len;
1137 
1138 	if ((entry->scroll = entry->start + scroll) > entry->end)
1139 		entry->scroll = entry->end;
1140 
1141 	if ((entry->cursor = entry->end - cursor) > entry->end)
1142 		entry->cursor = entry->end;
1143 
1144 	if (gnt_widget_get_mapped(GNT_WIDGET(entry)))
1145 		entry_redraw(GNT_WIDGET(entry));
1146 }
1147 
gnt_entry_set_text(GntEntry * entry,const char * text)1148 void gnt_entry_set_text(GntEntry *entry, const char *text)
1149 {
1150 	gboolean changed = TRUE;
1151 	if (text == NULL && entry->start == NULL)
1152 		changed = FALSE;
1153 	if (text && entry->start && g_utf8_collate(text, entry->start) == 0)
1154 		changed = FALSE;
1155 	gnt_entry_set_text_internal(entry, text);
1156 	if (changed)
1157 		entry_text_changed(entry);
1158 }
1159 
gnt_entry_set_max(GntEntry * entry,int max)1160 void gnt_entry_set_max(GntEntry *entry, int max)
1161 {
1162 	entry->max = max;
1163 }
1164 
gnt_entry_set_flag(GntEntry * entry,GntEntryFlag flag)1165 void gnt_entry_set_flag(GntEntry *entry, GntEntryFlag flag)
1166 {
1167 	entry->flag = flag;
1168 	/* XXX: Check the existing string to make sure the flags are respected? */
1169 }
1170 
gnt_entry_get_text(GntEntry * entry)1171 const char *gnt_entry_get_text(GntEntry *entry)
1172 {
1173 	return entry->start;
1174 }
1175 
gnt_entry_clear(GntEntry * entry)1176 void gnt_entry_clear(GntEntry *entry)
1177 {
1178 	gnt_entry_set_text_internal(entry, NULL);
1179 	entry->scroll = entry->cursor = entry->end = entry->start;
1180 	entry_redraw(GNT_WIDGET(entry));
1181 	destroy_suggest(entry);
1182 	entry_text_changed(entry);
1183 }
1184 
gnt_entry_set_masked(GntEntry * entry,gboolean set)1185 void gnt_entry_set_masked(GntEntry *entry, gboolean set)
1186 {
1187 	entry->masked = set;
1188 }
1189 
gnt_entry_add_to_history(GntEntry * entry,const char * text)1190 void gnt_entry_add_to_history(GntEntry *entry, const char *text)
1191 {
1192 	g_return_if_fail(entry->history != NULL);   /* Need to set_history_length first */
1193 
1194 	if (entry->histlength >= 0 &&
1195 		g_list_length(entry->history) >= (gsize)entry->histlength)
1196 	{
1197 		return;
1198 	}
1199 
1200 	entry->history = g_list_first(entry->history);
1201 	g_free(entry->history->data);
1202 	entry->history->data = g_strdup(text);
1203 	entry->history = g_list_prepend(entry->history, NULL);
1204 }
1205 
gnt_entry_set_history_length(GntEntry * entry,int num)1206 void gnt_entry_set_history_length(GntEntry *entry, int num)
1207 {
1208 	if (num == 0)
1209 	{
1210 		entry->histlength = num;
1211 		if (entry->history)
1212 		{
1213 			entry->history = g_list_first(entry->history);
1214 			g_list_foreach(entry->history, (GFunc)g_free, NULL);
1215 			g_list_free(entry->history);
1216 			entry->history = NULL;
1217 		}
1218 		return;
1219 	}
1220 
1221 	if (entry->histlength == 0)
1222 	{
1223 		entry->histlength = num;
1224 		entry->history = g_list_append(NULL, NULL);
1225 		return;
1226 	}
1227 
1228 	if (num > 0 && num < entry->histlength)
1229 	{
1230 		GList *first, *iter;
1231 		int index = 0;
1232 		for (first = entry->history, index = 0; first->prev; first = first->prev, index++);
1233 		while ((iter = g_list_nth(first, num)) != NULL)
1234 		{
1235 			g_free(iter->data);
1236 			first = g_list_delete_link(first, iter);
1237 		}
1238 		entry->histlength = num;
1239 		if (index >= num)
1240 			entry->history = g_list_last(first);
1241 		return;
1242 	}
1243 
1244 	entry->histlength = num;
1245 }
1246 
gnt_entry_set_word_suggest(GntEntry * entry,gboolean word)1247 void gnt_entry_set_word_suggest(GntEntry *entry, gboolean word)
1248 {
1249 	entry->word = word;
1250 }
1251 
gnt_entry_set_always_suggest(GntEntry * entry,gboolean always)1252 void gnt_entry_set_always_suggest(GntEntry *entry, gboolean always)
1253 {
1254 	entry->always = always;
1255 }
1256 
gnt_entry_add_suggest(GntEntry * entry,const char * text)1257 void gnt_entry_add_suggest(GntEntry *entry, const char *text)
1258 {
1259 	GList *find;
1260 
1261 	if (!text || !*text)
1262 		return;
1263 
1264 	find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
1265 	if (find)
1266 		return;
1267 	entry->suggests = g_list_append(entry->suggests, g_strdup(text));
1268 }
1269 
gnt_entry_remove_suggest(GntEntry * entry,const char * text)1270 void gnt_entry_remove_suggest(GntEntry *entry, const char *text)
1271 {
1272 	GList *find = g_list_find_custom(entry->suggests, text, (GCompareFunc)g_utf8_collate);
1273 	if (find)
1274 	{
1275 		g_free(find->data);
1276 		entry->suggests = g_list_delete_link(entry->suggests, find);
1277 	}
1278 }
1279 
1280