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 */
62 typedef 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 */
71 typedef struct
72 {
73 gchar *string; /* string to complete */
74 address_entry *ref; /* address the string belongs to */
75 } completion_entry;
76
77 /*******************************************************************************/
78
79 static gint ref_count; /* list ref count */
80 static GList *completion_list; /* list of strings to be checked */
81 static GList *address_list; /* address storage */
82 static 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
87 static gint completion_count; /* nr of addresses incl. the prefix */
88 static gint completion_next; /* next prev address */
89 static GSList *completion_addresses; /* unique addresses found in the
90 completion cache. */
91 static 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
98 static 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 */
completion_func(gpointer data)105 static gchar *completion_func(gpointer data)
106 {
107 g_return_val_if_fail(data != NULL, NULL);
108
109 return ((completion_entry *)data)->string;
110 }
111
init_all(void)112 static void init_all(void)
113 {
114 completion = g_completion_new(completion_func);
115 g_return_if_fail(completion != NULL);
116 }
117
free_all(void)118 static 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
address_entry_find_func(gconstpointer a,gconstpointer b)145 static 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
add_completion_entry(const gchar * str,address_entry * ae)164 static 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 */
add_address(const gchar * name,const gchar * firstname,const gchar * lastname,const gchar * nickname,const gchar * address)183 static 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 */
read_address_book(void)226 static 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 */
start_address_completion(void)233 gint 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 */
get_address_from_edit(GtkEntry * entry,gint * start_pos)255 gchar *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 */
replace_address_in_edit(GtkEntry * entry,const gchar * newtext,gint start_pos)305 void 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
332 static 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 */
complete_address(const gchar * str)366 guint 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 */
get_complete_address(gint index)419 gchar *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
get_next_complete_address(void)448 gchar *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
get_prev_complete_address(void)463 gchar *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
get_completion_count(void)480 guint 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() */
clear_completion_cache(void)489 void 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
is_completion_pending(void)504 gboolean 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 */
invalidate_address_completion(void)514 gint 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
end_address_completion(void)530 gint 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
551 static void address_completion_mainwindow_set_focus (GtkWindow *window,
552 GtkWidget *widget,
553 gpointer data);
554 static gboolean address_completion_entry_key_pressed (GtkEntry *entry,
555 GdkEventKey *ev,
556 gpointer data);
557 static gboolean address_completion_complete_address_in_entry
558 (GtkEntry *entry,
559 gboolean next);
560 static void address_completion_create_completion_window (GtkEntry *entry,
561 gboolean select_next);
562
563 static void completion_window_select_row(GtkCList *clist,
564 gint row,
565 gint col,
566 GdkEvent *event,
567 GtkWidget **window);
568 static gboolean completion_window_button_press
569 (GtkWidget *widget,
570 GdkEventButton *event,
571 GtkWidget **window);
572 static gboolean completion_window_key_press
573 (GtkWidget *widget,
574 GdkEventKey *event,
575 GtkWidget **window);
576
577
completion_window_advance_to_row(GtkCList * clist,gint row)578 static 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
completion_window_advance_selection(GtkCList * clist,gboolean forward)584 static 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 */
609 static 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 */
completion_window_apply_selection(GtkCList * clist,GtkEntry * entry)639 static 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
completion_window_apply_selection_address_only(GtkCList * clist,GtkEntry * entry)656 static 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 */
address_completion_start(GtkWidget * mainwindow)678 void 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
address_completion_register_entry(GtkEntry * entry)692 void 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
address_completion_unregister_entry(GtkEntry * entry)712 void 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 */
address_completion_end(GtkWidget * mainwindow)737 void 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 */
address_completion_mainwindow_set_focus(GtkWindow * window,GtkWidget * widget,gpointer data)745 static 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
755 static GtkWidget *completion_window;
756
757 /* watch for tabs in one of the address entries. if no tab then clear the
758 * completion cache */
address_completion_entry_key_pressed(GtkEntry * entry,GdkEventKey * ev,gpointer data)759 static 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
address_completion_entry_changed(GtkEditable * editable,gpointer data)809 static 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. :) */
address_completion_complete_address_in_entry(GtkEntry * entry,gboolean next)837 static 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
address_completion_create_completion_window(GtkEntry * entry_,gboolean select_next)878 static 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. */
completion_window_select_row(GtkCList * clist,gint row,gint col,GdkEvent * event,GtkWidget ** window)967 static 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 */
completion_window_button_press(GtkWidget * widget,GdkEventButton * event,GtkWidget ** window)993 static 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
completion_window_key_press(GtkWidget * widget,GdkEventKey * event,GtkWidget ** window)1036 static 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 }
1143