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