summaryrefslogtreecommitdiff
path: root/src/addr_compl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/addr_compl.c')
-rw-r--r--src/addr_compl.c1142
1 files changed, 1142 insertions, 0 deletions
diff --git a/src/addr_compl.c b/src/addr_compl.c
new file mode 100644
index 0000000..1fcc513
--- /dev/null
+++ b/src/addr_compl.c
@@ -0,0 +1,1142 @@
1/*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 *
4 * Copyright (C) 2000-2005 by Alfons Hoogervorst & The Sylpheed Claws Team.
5 * Copyright (C) 1999-2014 Hiroyuki Yamamoto
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22#ifdef HAVE_CONFIG_H
23# include "config.h"
24#endif
25#include "defs.h"
26
27#include <glib.h>
28#include <glib/gi18n.h>
29#include <gdk/gdkkeysyms.h>
30#include <gtk/gtk.h>
31
32#include <string.h>
33#include <ctype.h>
34
35#include "xml.h"
36#include "addr_compl.h"
37#include "utils.h"
38#include "addressbook.h"
39#include "main.h"
40#include "prefs_common.h"
41
42/* How it works:
43 *
44 * The address book is read into memory. We set up an address list
45 * containing all address book entries. Next we make the completion
46 * list, which contains all the completable strings, and store a
47 * reference to the address entry it belongs to.
48 * After calling the g_completion_complete(), we get a reference
49 * to a valid email address.
50 *
51 * Completion is very simplified. We never complete on another prefix,
52 * i.e. we neglect the next smallest possible prefix for the current
53 * completion cache. This is simply done so we might break up the
54 * addresses a little more (e.g. break up alfons@proteus.demon.nl into
55 * something like alfons, proteus, demon, nl; and then completing on
56 * any of those words).
57 */
58
59/* address_entry - structure which refers to the original address entry in the
60 * address book
61 */
62typedef struct
63{
64 gchar *name;
65 gchar *address;
66} address_entry;
67
68/* completion_entry - structure used to complete addresses, with a reference
69 * the the real address information.
70 */
71typedef struct
72{
73 gchar *string; /* string to complete */
74 address_entry *ref; /* address the string belongs to */
75} completion_entry;
76
77/*******************************************************************************/
78
79static gint ref_count; /* list ref count */
80static GList *completion_list; /* list of strings to be checked */
81static GList *address_list; /* address storage */
82static GCompletion *completion; /* completion object */
83
84/* To allow for continuing completion we have to keep track of the state
85 * using the following variables. No need to create a context object. */
86
87static gint completion_count; /* nr of addresses incl. the prefix */
88static gint completion_next; /* next prev address */
89static GSList *completion_addresses; /* unique addresses found in the
90 completion cache. */
91static gchar *completion_prefix; /* last prefix. (this is cached here
92 * because the prefix passed to g_completion
93 * is g_strdown()'ed */
94
95/*******************************************************************************/
96
97
98static void address_completion_entry_changed (GtkEditable *editable,
99 gpointer data);
100
101
102/* completion_func() - used by GTK to find the string data to be used for
103 * completion
104 */
105static gchar *completion_func(gpointer data)
106{
107 g_return_val_if_fail(data != NULL, NULL);
108
109 return ((completion_entry *)data)->string;
110}
111
112static void init_all(void)
113{
114 completion = g_completion_new(completion_func);
115 g_return_if_fail(completion != NULL);
116}
117
118static void free_all(void)
119{
120 GList *walk;
121
122 walk = g_list_first(completion_list);
123 for (; walk != NULL; walk = g_list_next(walk)) {
124 completion_entry *ce = (completion_entry *) walk->data;
125 g_free(ce->string);
126 g_free(ce);
127 }
128 g_list_free(completion_list);
129 completion_list = NULL;
130
131 walk = address_list;
132 for (; walk != NULL; walk = g_list_next(walk)) {
133 address_entry *ae = (address_entry *) walk->data;
134 g_free(ae->name);
135 g_free(ae->address);
136 g_free(ae);
137 }
138 g_list_free(address_list);
139 address_list = NULL;
140
141 g_completion_free(completion);
142 completion = NULL;
143}
144
145static gint address_entry_find_func(gconstpointer a, gconstpointer b)
146{
147 const address_entry *ae1 = a;
148 const address_entry *ae2 = b;
149 gint val;
150
151 if (!a || !b)
152 return -1;
153
154 val = strcmp(ae1->name, ae2->name);
155 if (val != 0)
156 return val;
157 val = strcmp(ae1->address, ae2->address);
158 if (val != 0)
159 return val;
160
161 return 0;
162}
163
164static void add_completion_entry(const gchar *str, address_entry *ae)
165{
166 completion_entry *ce;
167
168 if (!str || *str == '\0')
169 return;
170 if (!ae)
171 return;
172
173 ce = g_new0(completion_entry, 1);
174 /* GCompletion list is case sensitive */
175 ce->string = g_utf8_strdown(str, -1);
176 ce->ref = ae;
177 completion_list = g_list_append(completion_list, ce);
178}
179
180/* add_address() - adds address to the completion list. this function looks
181 * complicated, but it's only allocation checks.
182 */
183static gint add_address(const gchar *name, const gchar *firstname, const gchar *lastname, const gchar *nickname, const gchar *address)
184{
185 address_entry *ae;
186 GList *found;
187
188 if (!address || *address == '\0')
189 return -1;
190
191 /* debugg_print("add_address: [%s] [%s] [%s] [%s] [%s]\n", name, firstname, lastname, nickname, address); */
192
193 ae = g_new0(address_entry, 1);
194 ae->name = g_strdup(name ? name : "");
195 ae->address = g_strdup(address);
196 if ((found = g_list_find_custom(address_list, ae,
197 address_entry_find_func))) {
198 g_free(ae->name);
199 g_free(ae->address);
200 g_free(ae);
201 ae = (address_entry *)found->data;
202 } else
203 address_list = g_list_append(address_list, ae);
204
205 if (name) {
206 const gchar *p = name;
207
208 while (*p != '\0') {
209 add_completion_entry(p, ae);
210 while (*p && *p != '-' && *p != '.' && !g_ascii_isspace(*p))
211 p++;
212 while (*p == '-' || *p == '.' || g_ascii_isspace(*p))
213 p++;
214 }
215 }
216 add_completion_entry(firstname, ae);
217 add_completion_entry(lastname, ae);
218 add_completion_entry(nickname, ae);
219 add_completion_entry(address, ae);
220
221 return 0;
222}
223
224/* read_address_book()
225 */
226static void read_address_book(void) {
227 addressbook_load_completion_full( add_address );
228}
229
230/* start_address_completion() - returns the number of addresses
231 * that should be matched for completion.
232 */
233gint start_address_completion(void)
234{
235 clear_completion_cache();
236 if (!ref_count) {
237 init_all();
238 /* open the address book */
239 read_address_book();
240 /* merge the completion entry list into g_completion */
241 if (completion_list)
242 g_completion_add_items(completion, completion_list);
243 }
244 ref_count++;
245 debug_print("start_address_completion ref count %d\n", ref_count);
246
247 return g_list_length(completion_list);
248}
249
250/* get_address_from_edit() - returns a possible address (or a part)
251 * from an entry box. To make life easier, we only look at the last valid address
252 * component; address completion only works at the last string component in
253 * the entry box.
254 */
255gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
256{
257 const gchar *edit_text, *p;
258 gint cur_pos;
259 gboolean in_quote = FALSE;
260 gboolean in_bracket = FALSE;
261 gchar *str;
262
263 edit_text = gtk_entry_get_text(entry);
264 if (edit_text == NULL) return NULL;
265
266 cur_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
267
268 /* scan for a separator. doesn't matter if walk points at null byte. */
269 for (p = g_utf8_offset_to_pointer(edit_text, cur_pos);
270 p > edit_text;
271 p = g_utf8_prev_char(p)) {
272 if (*p == '"')
273 in_quote ^= TRUE;
274 else if (!in_quote) {
275 if (!in_bracket && *p == ',')
276 break;
277 else if (*p == '>')
278 in_bracket = TRUE;
279 else if (*p == '<')
280 in_bracket = FALSE;
281 }
282 }
283
284 /* have something valid */
285 if (g_utf8_strlen(p, -1) == 0)
286 return NULL;
287
288 /* now scan back until we hit a valid character */
289 for (; *p && (*p == ',' || g_ascii_isspace(*p));
290 p = g_utf8_next_char(p))
291 ;
292
293 if (g_utf8_strlen(p, -1) == 0)
294 return NULL;
295
296 if (start_pos) *start_pos = g_utf8_pointer_to_offset(edit_text, p);
297
298 str = g_strdup(p);
299
300 return str;
301}
302
303/* replace_address_in_edit() - replaces an incompleted address with a completed one.
304 */
305void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
306 gint start_pos)
307{
308 gchar *origtext;
309
310 if (!newtext) return;
311
312 debug_print("replace_address_in_edit: %s\n", newtext);
313
314 origtext = gtk_editable_get_chars(GTK_EDITABLE(entry), start_pos, -1);
315 if (!strcmp(origtext, newtext)) {
316 g_free(origtext);
317 return;
318 }
319 g_free(origtext);
320
321 g_signal_handlers_block_by_func
322 (entry, address_completion_entry_changed, NULL);
323 gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, -1);
324 gtk_editable_insert_text(GTK_EDITABLE(entry), newtext, strlen(newtext),
325 &start_pos);
326 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
327 g_signal_handlers_unblock_by_func
328 (entry, address_completion_entry_changed, NULL);
329}
330
331#if 0
332static gint insert_address_func(gconstpointer a, gconstpointer b)
333{
334 const address_entry *ae1 = a;
335 const address_entry *ae2 = b;
336 gchar *s1, *s2;
337 gint val;
338
339 if (!a || !b)
340 return -1;
341
342 s1 = g_utf8_casefold(ae1->address, -1);
343 s2 = g_utf8_casefold(ae2->address, -1);
344 val = g_utf8_collate(s1, s2);
345 g_free(s2);
346 g_free(s1);
347 if (val != 0)
348 return val;
349 s1 = g_utf8_casefold(ae1->name, -1);
350 s2 = g_utf8_casefold(ae2->name, -1);
351 val = g_utf8_collate(s1, s2);
352 g_free(s2);
353 g_free(s1);
354 if (val != 0)
355 return val;
356
357 return 0;
358}
359#endif
360
361/* complete_address() - tries to complete an addres, and returns the
362 * number of addresses found. use get_complete_address() to get one.
363 * returns zero if no match was found, otherwise the number of addresses,
364 * with the original prefix at index 0.
365 */
366guint complete_address(const gchar *str)
367{
368 GList *result;
369 gchar *d;
370 guint count, cpl;
371 completion_entry *ce;
372
373 g_return_val_if_fail(str != NULL, 0);
374
375 clear_completion_cache();
376 completion_prefix = g_strdup(str);
377
378 /* g_completion is case sensitive */
379 d = g_utf8_strdown(str, -1);
380 result = g_completion_complete(completion, d, NULL);
381
382 count = g_list_length(result);
383 if (count) {
384 /* create list with unique addresses */
385 for (cpl = 0, result = g_list_first(result);
386 result != NULL;
387 result = g_list_next(result)) {
388 ce = (completion_entry *)(result->data);
389 if (NULL == g_slist_find(completion_addresses,
390 ce->ref)) {
391 cpl++;
392 completion_addresses =
393 g_slist_append(completion_addresses,
394 ce->ref);
395#if 0
396 g_slist_insert_sorted
397 (completion_addresses, ce->ref,
398 insert_address_func);
399#endif
400 }
401 }
402 count = cpl + 1; /* index 0 is the original prefix */
403 completion_next = 1; /* we start at the first completed one */
404 } else {
405 g_free(completion_prefix);
406 completion_prefix = NULL;
407 }
408
409 completion_count = count;
410
411 g_free(d);
412
413 return count;
414}
415
416/* get_complete_address() - returns a complete address. the returned
417 * string should be freed
418 */
419gchar *get_complete_address(gint index)
420{
421 const address_entry *p;
422 gchar *address = NULL;
423
424 if (index < completion_count) {
425 if (index == 0)
426 address = g_strdup(completion_prefix);
427 else {
428 /* get something from the unique addresses */
429 p = (address_entry *)g_slist_nth_data
430 (completion_addresses, index - 1);
431 if (p != NULL) {
432 if (!p->name || p->name[0] == '\0')
433 address = g_strdup(p->address);
434 else if (p->name[0] != '"' &&
435 strpbrk(p->name, "(),.:;<>@[]") != NULL)
436 address = g_strdup_printf
437 ("\"%s\" <%s>", p->name, p->address);
438 else
439 address = g_strdup_printf
440 ("%s <%s>", p->name, p->address);
441 }
442 }
443 }
444
445 return address;
446}
447
448gchar *get_next_complete_address(void)
449{
450 if (is_completion_pending()) {
451 gchar *res;
452
453 res = get_complete_address(completion_next);
454 completion_next += 1;
455 if (completion_next >= completion_count)
456 completion_next = 0;
457
458 return res;
459 } else
460 return NULL;
461}
462
463gchar *get_prev_complete_address(void)
464{
465 if (is_completion_pending()) {
466 int n = completion_next - 2;
467
468 /* real previous */
469 n = (n + (completion_count * 5)) % completion_count;
470
471 /* real next */
472 completion_next = n + 1;
473 if (completion_next >= completion_count)
474 completion_next = 0;
475 return get_complete_address(n);
476 } else
477 return NULL;
478}
479
480guint get_completion_count(void)
481{
482 if (is_completion_pending())
483 return completion_count;
484 else
485 return 0;
486}
487
488/* should clear up anything after complete_address() */
489void clear_completion_cache(void)
490{
491 if (is_completion_pending()) {
492 if (completion_prefix)
493 g_free(completion_prefix);
494
495 if (completion_addresses) {
496 g_slist_free(completion_addresses);
497 completion_addresses = NULL;
498 }
499
500 completion_count = completion_next = 0;
501 }
502}
503
504gboolean is_completion_pending(void)
505{
506 /* check if completion pending, i.e. we might satisfy a request for the next
507 * or previous address */
508 return completion_count;
509}
510
511/* invalidate_address_completion() - should be called if address book
512 * changed;
513 */
514gint invalidate_address_completion(void)
515{
516 if (ref_count) {
517 /* simply the same as start_address_completion() */
518 debug_print("Invalidation request for address completion\n");
519 free_all();
520 init_all();
521 read_address_book();
522 if (completion_list)
523 g_completion_add_items(completion, completion_list);
524 clear_completion_cache();
525 }
526
527 return g_list_length(completion_list);
528}
529
530gint end_address_completion(void)
531{
532 clear_completion_cache();
533
534 if (0 == --ref_count)
535 free_all();
536
537 debug_print("end_address_completion ref count %d\n", ref_count);
538
539 return ref_count;
540}
541
542
543/* address completion entry ui. the ui (completion list was inspired by galeon's
544 * auto completion list). remaining things powered by sylpheed's completion engine.
545 */
546
547#define ENTRY_DATA_TAB_HOOK "tab_hook" /* used to lookup entry */
548#define WINDOW_DATA_COMPL_ENTRY "compl_entry" /* used to store entry for compl. window */
549#define WINDOW_DATA_COMPL_CLIST "compl_clist" /* used to store clist for compl. window */
550
551static void address_completion_mainwindow_set_focus (GtkWindow *window,
552 GtkWidget *widget,
553 gpointer data);
554static gboolean address_completion_entry_key_pressed (GtkEntry *entry,
555 GdkEventKey *ev,
556 gpointer data);
557static gboolean address_completion_complete_address_in_entry
558 (GtkEntry *entry,
559 gboolean next);
560static void address_completion_create_completion_window (GtkEntry *entry,
561 gboolean select_next);
562
563static void completion_window_select_row(GtkCList *clist,
564 gint row,
565 gint col,
566 GdkEvent *event,
567 GtkWidget **window);
568static gboolean completion_window_button_press
569 (GtkWidget *widget,
570 GdkEventButton *event,
571 GtkWidget **window);
572static gboolean completion_window_key_press
573 (GtkWidget *widget,
574 GdkEventKey *event,
575 GtkWidget **window);
576
577
578static void completion_window_advance_to_row(GtkCList *clist, gint row)
579{
580 g_return_if_fail(row < completion_count);
581 gtk_clist_select_row(clist, row, 0);
582}
583
584static void completion_window_advance_selection(GtkCList *clist, gboolean forward)
585{
586 int row;
587
588 g_return_if_fail(clist != NULL);
589 g_return_if_fail(clist->selection != NULL);
590
591 row = GPOINTER_TO_INT(clist->selection->data);
592
593 row = forward ? (row + 1) % completion_count :
594 (row - 1) < 0 ? completion_count - 1 : row - 1;
595
596 gtk_clist_freeze(clist);
597 completion_window_advance_to_row(clist, row);
598 gtk_clist_thaw(clist);
599
600#ifdef __APPLE__
601 /* workaround for a draw bug in OS X */
602 gtk_widget_queue_draw(GTK_WIDGET(clist));
603#endif
604}
605
606#if 0
607/* completion_window_accept_selection() - accepts the current selection in the
608 * clist, and destroys the window */
609static void completion_window_accept_selection(GtkWidget **window,
610 GtkCList *clist,
611 GtkEntry *entry)
612{
613 gchar *address = NULL, *text = NULL;
614 gint cursor_pos, row;
615
616 g_return_if_fail(window != NULL);
617 g_return_if_fail(*window != NULL);
618 g_return_if_fail(clist != NULL);
619 g_return_if_fail(entry != NULL);
620 g_return_if_fail(clist->selection != NULL);
621
622 /* FIXME: I believe it's acceptable to access the selection member directly */
623 row = GPOINTER_TO_INT(clist->selection->data);
624
625 /* we just need the cursor position */
626 address = get_address_from_edit(entry, &cursor_pos);
627 g_free(address);
628 gtk_clist_get_text(clist, row, 0, &text);
629 replace_address_in_edit(entry, text, cursor_pos);
630
631 clear_completion_cache();
632 gtk_widget_destroy(*window);
633 *window = NULL;
634}
635#endif
636
637/* completion_window_apply_selection() - apply the current selection in the
638 * clist */
639static void completion_window_apply_selection(GtkCList *clist, GtkEntry *entry)
640{
641 gchar *address = NULL, *text = NULL;
642 gint cursor_pos, row;
643
644 g_return_if_fail(clist != NULL);
645 g_return_if_fail(entry != NULL);
646 g_return_if_fail(clist->selection != NULL);
647
648 row = GPOINTER_TO_INT(clist->selection->data);
649
650 address = get_address_from_edit(entry, &cursor_pos);
651 g_free(address);
652 gtk_clist_get_text(clist, row, 0, &text);
653 replace_address_in_edit(entry, text, cursor_pos);
654}
655
656static void completion_window_apply_selection_address_only(GtkCList *clist, GtkEntry *entry)
657{
658 gchar *address = NULL;
659 address_entry *ae;
660 gint cursor_pos, row;
661
662 g_return_if_fail(clist != NULL);
663 g_return_if_fail(entry != NULL);
664 g_return_if_fail(clist->selection != NULL);
665
666 row = GPOINTER_TO_INT(clist->selection->data);
667
668 ae = (address_entry *)g_slist_nth_data(completion_addresses, row - 1);
669 if (ae && ae->address) {
670 address = get_address_from_edit(entry, &cursor_pos);
671 g_free(address);
672 replace_address_in_edit(entry, ae->address, cursor_pos);
673 }
674}
675
676/* should be called when creating the main window containing address
677 * completion entries */
678void address_completion_start(GtkWidget *mainwindow)
679{
680 start_address_completion();
681
682 /* register focus change hook */
683 g_signal_connect(G_OBJECT(mainwindow), "set_focus",
684 G_CALLBACK(address_completion_mainwindow_set_focus),
685 mainwindow);
686}
687
688/* Need unique data to make unregistering signal handler possible for the auto
689 * completed entry */
690#define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
691
692void address_completion_register_entry(GtkEntry *entry)
693{
694 g_return_if_fail(entry != NULL);
695 g_return_if_fail(GTK_IS_ENTRY(entry));
696
697 /* add hooked property */
698 g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
699
700 /* add keypress event */
701 g_signal_connect_closure
702 (G_OBJECT(entry), "key_press_event",
703 g_cclosure_new
704 (G_CALLBACK(address_completion_entry_key_pressed),
705 COMPLETION_UNIQUE_DATA, NULL),
706 FALSE);
707 g_signal_connect(G_OBJECT(entry), "changed",
708 G_CALLBACK(address_completion_entry_changed),
709 NULL);
710}
711
712void address_completion_unregister_entry(GtkEntry *entry)
713{
714 GObject *entry_obj;
715
716 g_return_if_fail(entry != NULL);
717 g_return_if_fail(GTK_IS_ENTRY(entry));
718
719 entry_obj = g_object_get_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
720 g_return_if_fail(entry_obj);
721 g_return_if_fail(entry_obj == G_OBJECT(entry));
722
723 /* has the hooked property? */
724 g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
725
726 /* remove the hook */
727 g_signal_handlers_disconnect_by_func
728 (G_OBJECT(entry),
729 G_CALLBACK(address_completion_entry_key_pressed),
730 COMPLETION_UNIQUE_DATA);
731}
732
733/* should be called when main window with address completion entries
734 * terminates.
735 * NOTE: this function assumes that it is called upon destruction of
736 * the window */
737void address_completion_end(GtkWidget *mainwindow)
738{
739 /* if address_completion_end() is really called on closing the window,
740 * we don't need to unregister the set_focus_cb */
741 end_address_completion();
742}
743
744/* if focus changes to another entry, then clear completion cache */
745static void address_completion_mainwindow_set_focus(GtkWindow *window,
746 GtkWidget *widget,
747 gpointer data)
748{
749 if (widget && GTK_IS_ENTRY(widget) &&
750 g_object_get_data(G_OBJECT(widget), ENTRY_DATA_TAB_HOOK)) {
751 clear_completion_cache();
752 }
753}
754
755static GtkWidget *completion_window;
756
757/* watch for tabs in one of the address entries. if no tab then clear the
758 * completion cache */
759static gboolean address_completion_entry_key_pressed(GtkEntry *entry,
760 GdkEventKey *ev,
761 gpointer data)
762{
763 if (!prefs_common.fullauto_completion_mode && ev->keyval == GDK_Tab &&
764 !completion_window) {
765 if (address_completion_complete_address_in_entry(entry, TRUE)) {
766 address_completion_create_completion_window(entry, TRUE);
767 /* route a void character to the default handler */
768 /* this is a dirty hack; we're actually changing a key
769 * reported by the system. */
770 ev->keyval = GDK_AudibleBell_Enable;
771 ev->state &= ~GDK_SHIFT_MASK;
772 return TRUE;
773 }
774 }
775
776 if (!completion_window)
777 return FALSE;
778
779 if ( ev->keyval == GDK_Up
780 || ev->keyval == GDK_Down
781 || ev->keyval == GDK_Page_Up
782 || ev->keyval == GDK_Page_Down
783 || ev->keyval == GDK_Return
784 || ev->keyval == GDK_Escape
785 || ev->keyval == GDK_Tab
786 || ev->keyval == GDK_ISO_Left_Tab) {
787 completion_window_key_press(completion_window, ev, &completion_window);
788 return TRUE;
789 } else if (ev->keyval == GDK_Shift_L
790 || ev->keyval == GDK_Shift_R
791 || ev->keyval == GDK_Control_L
792 || ev->keyval == GDK_Control_R
793 || ev->keyval == GDK_Caps_Lock
794 || ev->keyval == GDK_Shift_Lock
795 || ev->keyval == GDK_Meta_L
796 || ev->keyval == GDK_Meta_R
797 || ev->keyval == GDK_Alt_L
798 || ev->keyval == GDK_Alt_R) {
799 /* these buttons should not clear the cache... */
800 } else {
801 clear_completion_cache();
802 gtk_widget_destroy(completion_window);
803 completion_window = NULL;
804 }
805
806 return FALSE;
807}
808
809static void address_completion_entry_changed(GtkEditable *editable,
810 gpointer data)
811{
812 GtkEntry *entry = GTK_ENTRY(editable);
813
814 if (!prefs_common.fullauto_completion_mode)
815 return;
816
817 g_signal_handlers_block_by_func
818 (editable, address_completion_entry_changed, data);
819 if (address_completion_complete_address_in_entry(entry, TRUE)) {
820 address_completion_create_completion_window(entry, FALSE);
821 } else {
822 clear_completion_cache();
823 if (completion_window) {
824 gtk_widget_destroy(completion_window);
825 completion_window = NULL;
826 }
827 }
828 g_signal_handlers_unblock_by_func
829 (editable, address_completion_entry_changed, data);
830}
831
832/* initialize the completion cache and put first completed string
833 * in entry. this function used to do back cycling but this is not
834 * currently used. since the address completion behaviour has been
835 * changed regularly, we keep the feature in case someone changes
836 * his / her mind again. :) */
837static gboolean address_completion_complete_address_in_entry(GtkEntry *entry,
838 gboolean next)
839{
840 gint ncount = 0, cursor_pos;
841 gchar *address, *new = NULL;
842 gboolean completed = FALSE;
843
844 g_return_val_if_fail(entry != NULL, FALSE);
845
846 if (!GTK_WIDGET_HAS_FOCUS(entry)) return FALSE;
847
848 /* get an address component from the cursor */
849 address = get_address_from_edit(entry, &cursor_pos);
850 if (!address) return FALSE;
851
852 /* still something in the cache */
853 if (is_completion_pending()) {
854 new = next ? get_next_complete_address() :
855 get_prev_complete_address();
856 } else {
857 if (0 < (ncount = complete_address(address)))
858 new = get_next_complete_address();
859 }
860
861 if (new) {
862 /* prevent "change" signal */
863 /* replace_address_in_edit(entry, new, cursor_pos); */
864
865 /* don't complete if entry equals to the completed address */
866 if (ncount == 2 && !strcmp(address, new))
867 completed = FALSE;
868 else
869 completed = TRUE;
870 g_free(new);
871 }
872
873 g_free(address);
874
875 return completed;
876}
877
878static void address_completion_create_completion_window(GtkEntry *entry_,
879 gboolean select_next)
880{
881 gint x, y, width;
882 GtkWidget *scroll, *clist;
883 GtkRequisition r;
884 guint count = 0;
885 GtkWidget *entry = GTK_WIDGET(entry_);
886
887 debug_print("address_completion_create_completion_window\n");
888
889 if (completion_window) {
890 gtk_widget_destroy(completion_window);
891 completion_window = NULL;
892 }
893
894 scroll = gtk_scrolled_window_new(NULL, NULL);
895 clist = gtk_clist_new(1);
896 gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
897
898 completion_window = gtk_window_new(GTK_WINDOW_POPUP);
899
900 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
901 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
902 gtk_container_add(GTK_CONTAINER(completion_window), scroll);
903 gtk_container_add(GTK_CONTAINER(scroll), clist);
904
905 /* set the unique data so we can always get back the entry and
906 * clist window to which this completion window has been attached */
907 g_object_set_data(G_OBJECT(completion_window),
908 WINDOW_DATA_COMPL_ENTRY, entry_);
909 g_object_set_data(G_OBJECT(completion_window),
910 WINDOW_DATA_COMPL_CLIST, clist);
911
912 g_signal_connect(G_OBJECT(clist), "select_row",
913 G_CALLBACK(completion_window_select_row),
914 &completion_window);
915
916 for (count = 0; count < get_completion_count(); count++) {
917 gchar *text[] = {NULL, NULL};
918
919 text[0] = get_complete_address(count);
920 gtk_clist_append(GTK_CLIST(clist), text);
921 g_free(text[0]);
922 }
923
924 gdk_window_get_origin(entry->window, &x, &y);
925 gtk_widget_size_request(entry, &r);
926 width = entry->allocation.width;
927 y += r.height;
928 gtk_window_move(GTK_WINDOW(completion_window), x, y);
929
930 gtk_widget_size_request(clist, &r);
931 gtk_widget_set_size_request(completion_window, width, r.height);
932 gtk_widget_show_all(completion_window);
933 gtk_widget_size_request(clist, &r);
934
935 if ((y + r.height) > gdk_screen_height()) {
936 gtk_window_set_policy(GTK_WINDOW(completion_window),
937 TRUE, FALSE, FALSE);
938 gtk_widget_set_size_request(completion_window, width,
939 gdk_screen_height () - y);
940 }
941
942 g_signal_connect(G_OBJECT(completion_window),
943 "button-press-event",
944 G_CALLBACK(completion_window_button_press),
945 &completion_window);
946 g_signal_connect(G_OBJECT(completion_window),
947 "key-press-event",
948 G_CALLBACK(completion_window_key_press),
949 &completion_window);
950 gdk_pointer_grab(completion_window->window, TRUE,
951 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
952 GDK_BUTTON_RELEASE_MASK,
953 NULL, NULL, GDK_CURRENT_TIME);
954 gtk_grab_add(completion_window);
955
956 /* this gets rid of the irritating focus rectangle that doesn't
957 * follow the selection */
958 GTK_WIDGET_UNSET_FLAGS(clist, GTK_CAN_FOCUS);
959 gtk_clist_select_row(GTK_CLIST(clist), select_next ? 1 : 0, 0);
960
961 debug_print("address_completion_create_completion_window done\n");
962}
963
964
965/* row selection sends completed address to entry.
966 * note: event is NULL if selected by anything else than a mouse button. */
967static void completion_window_select_row(GtkCList *clist, gint row, gint col,
968 GdkEvent *event,
969 GtkWidget **window)
970{
971 GtkEntry *entry;
972
973 g_return_if_fail(window != NULL);
974 g_return_if_fail(*window != NULL);
975
976 entry = GTK_ENTRY(g_object_get_data(G_OBJECT(*window),
977 WINDOW_DATA_COMPL_ENTRY));
978 g_return_if_fail(entry != NULL);
979
980 completion_window_apply_selection(clist, entry);
981
982 if (!event || event->type != GDK_BUTTON_RELEASE)
983 return;
984
985 clear_completion_cache();
986 gtk_widget_destroy(*window);
987 *window = NULL;
988}
989
990/* completion_window_button_press() - check is mouse click is anywhere
991 * else (not in the completion window). in that case the completion
992 * window is destroyed, and the original prefix is restored */
993static gboolean completion_window_button_press(GtkWidget *widget,
994 GdkEventButton *event,
995 GtkWidget **window)
996{
997 GtkWidget *event_widget, *entry;
998 gchar *prefix;
999 gint cursor_pos;
1000 gboolean restore = TRUE;
1001
1002 g_return_val_if_fail(window != NULL, FALSE);
1003 g_return_val_if_fail(*window != NULL, FALSE);
1004
1005 entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*window),
1006 WINDOW_DATA_COMPL_ENTRY));
1007 g_return_val_if_fail(entry != NULL, FALSE);
1008
1009 event_widget = gtk_get_event_widget((GdkEvent *)event);
1010 if (event_widget != widget) {
1011 while (event_widget) {
1012 if (event_widget == widget)
1013 return FALSE;
1014 else if (event_widget == entry) {
1015 restore = FALSE;
1016 break;
1017 }
1018 event_widget = event_widget->parent;
1019 }
1020 }
1021
1022 if (restore) {
1023 prefix = get_complete_address(0);
1024 g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
1025 replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
1026 g_free(prefix);
1027 }
1028
1029 clear_completion_cache();
1030 gtk_widget_destroy(*window);
1031 *window = NULL;
1032
1033 return TRUE;
1034}
1035
1036static gboolean completion_window_key_press(GtkWidget *widget,
1037 GdkEventKey *event,
1038 GtkWidget **window)
1039{
1040 GtkWidget *entry;
1041 gchar *prefix;
1042 gint cursor_pos;
1043 GtkWidget *clist;
1044
1045 g_return_val_if_fail(window != NULL, FALSE);
1046 g_return_val_if_fail(*window != NULL, FALSE);
1047
1048 if (!is_completion_pending())
1049 g_warning("completion is not pending!\n");
1050
1051 entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*window),
1052 WINDOW_DATA_COMPL_ENTRY));
1053 clist = GTK_WIDGET(g_object_get_data(G_OBJECT(*window),
1054 WINDOW_DATA_COMPL_CLIST));
1055 g_return_val_if_fail(entry != NULL, FALSE);
1056
1057 /* allow keyboard navigation in the alternatives clist */
1058 if (event->keyval == GDK_Up || event->keyval == GDK_Down ||
1059 event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down) {
1060 completion_window_advance_selection
1061 (GTK_CLIST(clist),
1062 event->keyval == GDK_Down ||
1063 event->keyval == GDK_Page_Down ? TRUE : FALSE);
1064 return FALSE;
1065 }
1066
1067 /* also make tab / shift tab go to next previous completion entry. we're
1068 * changing the key value */
1069 if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab) {
1070 event->keyval = (event->state & GDK_SHIFT_MASK)
1071 ? GDK_Up : GDK_Down;
1072 /* need to reset shift state if going up */
1073 if (event->state & GDK_SHIFT_MASK)
1074 event->state &= ~GDK_SHIFT_MASK;
1075 completion_window_advance_selection(GTK_CLIST(clist),
1076 event->keyval == GDK_Down ? TRUE : FALSE);
1077 return FALSE;
1078 }
1079
1080 /* look for presses that accept the selection */
1081 if (event->keyval == GDK_Return ||
1082 (!prefs_common.fullauto_completion_mode &&
1083 event->keyval == GDK_space)) {
1084 /* insert address only if shift or control is pressed */
1085 if (event->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK) ||
1086 prefs_common.always_add_address_only) {
1087 completion_window_apply_selection_address_only
1088 (GTK_CLIST(clist), GTK_ENTRY(entry));
1089 }
1090 clear_completion_cache();
1091 gtk_widget_destroy(*window);
1092 *window = NULL;
1093 return FALSE;
1094 }
1095
1096 /* key state keys should never be handled */
1097 if (event->keyval == GDK_Shift_L
1098 || event->keyval == GDK_Shift_R
1099 || event->keyval == GDK_Control_L
1100 || event->keyval == GDK_Control_R
1101 || event->keyval == GDK_Caps_Lock
1102 || event->keyval == GDK_Shift_Lock
1103 || event->keyval == GDK_Meta_L
1104 || event->keyval == GDK_Meta_R
1105 || event->keyval == GDK_Alt_L
1106 || event->keyval == GDK_Alt_R) {
1107 return FALSE;
1108 }
1109
1110 /* other key, let's restore the prefix (orignal text) */
1111 if (!prefs_common.fullauto_completion_mode ||
1112 event->keyval == GDK_Escape) {
1113 prefix = get_complete_address(0);
1114 g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
1115 replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
1116 g_free(prefix);
1117 }
1118
1119 /* make sure anything we typed comes in the edit box */
1120 if ((!prefs_common.fullauto_completion_mode && event->length > 0 &&
1121 event->keyval != GDK_Escape) ||
1122 (prefs_common.fullauto_completion_mode &&
1123 event->keyval != GDK_Escape)) {
1124 GtkWidget *pwin = entry;
1125
1126 while ((pwin = gtk_widget_get_parent(pwin)) != NULL) {
1127 if (GTK_WIDGET_TOPLEVEL(pwin)) {
1128 gtk_window_propagate_key_event
1129 (GTK_WINDOW(pwin), event);
1130 if (prefs_common.fullauto_completion_mode)
1131 return TRUE;
1132 }
1133 }
1134 }
1135
1136 /* and close the completion window */
1137 clear_completion_cache();
1138 gtk_widget_destroy(*window);
1139 *window = NULL;
1140
1141 return TRUE;
1142}