talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

addr_compl.c (51543B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  *
      4  * Copyright (C) 2000-2024 the Claws Mail team and Alfons Hoogervorst
      5  *
      6  * This program is free software; you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation; either version 3 of the License, or
      9  * (at your option) any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     18  */
     19 
     20 #include "defs.h"
     21 
     22 #include <glib.h>
     23 #include <glib/gi18n.h>
     24 #include <gdk/gdkkeysyms.h>
     25 #include <gtk/gtk.h>
     26 
     27 #include <string.h>
     28 #include <ctype.h>
     29 #include <wchar.h>
     30 #include <wctype.h>
     31 
     32 #include "addr_compl.h"
     33 #include "addritem.h"
     34 #include "utils.h"
     35 #include "prefs_common.h"
     36 #include "claws.h"
     37 #include "hooks.h"
     38 #include "gtkutils.h"
     39 #include "stock_pixmap.h"
     40 #include <pthread.h>
     41 
     42 #include "addrindex.h"
     43 
     44 
     45 /*!
     46  *\brief	For the GtkListStore
     47  */
     48 enum {
     49 	ADDR_COMPL_ICON,
     50 	ADDR_COMPL_ADDRESS,
     51 	ADDR_COMPL_ISGROUP,
     52 	ADDR_COMPL_GROUPLIST,
     53 	N_ADDR_COMPL_COLUMNS
     54 };
     55 
     56 /*
     57  * How it works:
     58  *
     59  * The address book is read into memory. We set up an address list
     60  * containing all address book entries. Next we make the completion
     61  * list, which contains all the completable strings, and store a
     62  * reference to the address entry it belongs to.
     63  * After calling the g_completion_complete(), we get a reference
     64  * to a valid email address.
     65  *
     66  * Completion is very simplified. We never complete on another prefix,
     67  * i.e. we neglect the next smallest possible prefix for the current
     68  * completion cache. This is simply done so we might break up the
     69  * addresses a little more (e.g. break up alfons@proteus.demon.nl into
     70  * something like alfons, proteus, demon, nl; and then completing on
     71  * any of those words).
     72  */
     73 
     74 /**
     75  * completion_entry - structure used to complete addresses, with a reference
     76  * the the real address information.
     77  */
     78 typedef struct
     79 {
     80 	gchar		*string; /* string to complete */
     81 	address_entry	*ref;	 /* address the string belongs to  */
     82 } completion_entry;
     83 
     84 /*******************************************************************************/
     85 
     86 static gint	    g_ref_count;	/* list ref count */
     87 static GList 	   *g_completion_list = NULL;	/* list of strings to be checked */
     88 static GList 	   *g_address_list = NULL;	/* address storage */
     89 static GCompletion *g_completion;	/* completion object */
     90 
     91 static GHashTable *_groupAddresses_ = NULL;
     92 static gboolean _allowCommas_ = TRUE;
     93 
     94 /* To allow for continuing completion we have to keep track of the state
     95  * using the following variables. No need to create a context object. */
     96 
     97 static gint	    g_completion_count;		/* nr of addresses incl. the prefix */
     98 static gint	    g_completion_next;		/* next prev address */
     99 static GSList	   *g_completion_addresses;	/* unique addresses found in the
    100 						   completion cache. */
    101 static gchar	   *g_completion_prefix;	/* last prefix. (this is cached here
    102 						 * because the prefix passed to g_completion
    103 						 * is g_utf8_strdown()'ed */
    104 
    105 static gchar *completion_folder_path = NULL;
    106 
    107 /*******************************************************************************/
    108 
    109 /*
    110  * Define the structure of the completion window.
    111  */
    112 typedef struct _CompletionWindow CompletionWindow;
    113 struct _CompletionWindow {
    114 	gint      listCount;
    115 	gchar     *searchTerm;
    116 	GtkWidget *window;
    117 	GtkWidget *entry;
    118 	GtkWidget *list_view;
    119 
    120 	gboolean   in_mouse;	/*!< mouse press pending... */
    121 	gboolean   destroying;  /*!< destruction in progress */
    122 };
    123 
    124 static GtkListStore *addr_compl_create_store	(void);
    125 
    126 static GtkWidget *addr_compl_list_view_create	(CompletionWindow *window);
    127 
    128 static void addr_compl_create_list_view_columns	(GtkWidget *list_view);
    129 
    130 static gboolean list_view_button_press		(GtkWidget *widget,
    131 						 GdkEventButton *event,
    132 						 CompletionWindow *window);
    133 
    134 static gboolean list_view_button_release	(GtkWidget *widget,
    135 						 GdkEventButton *event,
    136 						 CompletionWindow *window);
    137 
    138 static gboolean addr_compl_selected		(GtkTreeSelection *selector,
    139 						 GtkTreeModel *model,
    140 						 GtkTreePath *path,
    141 						 gboolean currently_selected,
    142 						 gpointer data);
    143 
    144 static gboolean addr_compl_defer_select_destruct(CompletionWindow *window);
    145 
    146 /**
    147  * Function used by GTK to find the string data to be used for completion.
    148  * \param data Pointer to data being processed.
    149  */
    150 static gchar *completion_func(gpointer data)
    151 {
    152 	cm_return_val_if_fail(data != NULL, NULL);
    153 
    154 	return ((completion_entry *)data)->string;
    155 }
    156 
    157 static gint addr_completion_func(const gchar *needle, const gchar *haystack,
    158 		gsize n)
    159 {
    160 	if (needle == NULL || haystack == NULL)
    161 		return 1;
    162 
    163 	return (strcasestr(haystack, needle) != NULL ? 0 : 1);
    164 }
    165 
    166 /**
    167  * Function used by GTK to compare elements for sorting
    168  * name match beginning > name match after space > email address
    169  *   match beginning and full match before @ > email adress
    170  *   match beginning. Otherwise match position in string.
    171  * \param a first element in comparsion
    172  * \param b second element in comparison
    173  */
    174 static gint weight_addr_match(const address_entry* addr)
    175 {
    176 	gint	n_weight = addr->name ? strlen(addr->name): 0;
    177 	gint	a_weight = addr->address ? strlen(addr->address) : n_weight;
    178 	gchar* 	match = NULL;
    179 
    180 	if (addr->name)
    181 		match = strcasestr(addr->name, g_completion_prefix);
    182 
    183 	if (match != NULL) {
    184 		if (match == addr->name)
    185 			n_weight = -4;
    186 		else if (match > addr->name && *(match - 1) == ' ')
    187 			n_weight = -3;
    188 		else
    189 			n_weight = match - addr->name;
    190 	}
    191 
    192 	if (addr->address) {
    193 		match = strcasestr(addr->address, g_completion_prefix);
    194 		if (match != NULL) {
    195 			if (match == addr->address)
    196 				a_weight = -1;
    197 			else
    198 				a_weight = match - addr->address;
    199 
    200 			if (strlen(match) > strlen(g_completion_prefix)
    201 			 && *(match + strlen(g_completion_prefix)) == '@')
    202 				a_weight--;
    203 		}
    204 	}
    205 
    206 	if (n_weight == -4 && a_weight < 0)
    207 		n_weight = -5;
    208 
    209 	return MIN(a_weight, n_weight);
    210 }
    211 
    212 static gint addr_comparison_func(gconstpointer a, gconstpointer b)
    213 {
    214 	const address_entry*	a_ref = (const address_entry*)a;
    215 	const address_entry*	b_ref = (const address_entry*)b;
    216 	gint			a_weight = weight_addr_match(a_ref);
    217 	gint			b_weight = weight_addr_match(b_ref);
    218 	gint			cmp;
    219 
    220 	if (a_weight < b_weight)
    221 		return -1;
    222 	else if (a_weight > b_weight)
    223 		return 1;
    224 	else {
    225                 if (!a_ref->name || !b_ref->name)
    226                   cmp = !!a_ref->name - !!b_ref->name;
    227                 else
    228                   cmp = strcmp(a_ref->name, b_ref->name);
    229                 if (!cmp)
    230                   {
    231                     if (!a_ref->address || !b_ref->address)
    232                       cmp = !!a_ref->address - !!b_ref->address;
    233                     else
    234                       cmp = g_strcmp0(a_ref->address, b_ref->address);
    235                   }
    236                 return cmp;
    237 	}
    238 }
    239 
    240 /**
    241  * Initialize all completion index data.
    242  */
    243 static void init_all(void)
    244 {
    245 	g_completion = g_completion_new(completion_func);
    246 	cm_return_if_fail(g_completion != NULL);
    247 }
    248 
    249 /**
    250  * set the compare function (default is strncmp)
    251  */
    252 static void set_match_any_part(const gboolean any_part)
    253 {
    254 	if (any_part && prefs_common.address_search_wildcard)
    255 		g_completion_set_compare(g_completion, addr_completion_func);
    256 	else
    257 		g_completion_set_compare(g_completion, strncmp);
    258 }
    259 
    260 static void free_all_addresses(void)
    261 {
    262 	GList *walk;
    263 	if (!g_address_list)
    264 		return;
    265 	walk = g_address_list;
    266 	for (; walk != NULL; walk = g_list_next(walk)) {
    267 		address_entry *ae = (address_entry *) walk->data;
    268 		g_free(ae->name);
    269 		g_free(ae->address);
    270 		g_list_free(ae->grp_emails);
    271 		g_free(walk->data);
    272 	}
    273 	g_list_free(g_address_list);
    274 	g_address_list = NULL;
    275 	if (_groupAddresses_)
    276 		g_hash_table_destroy(_groupAddresses_);
    277 	_groupAddresses_ = NULL;
    278 }
    279 
    280 static void clear_completion_cache(void);
    281 static void free_completion_list(void)
    282 {
    283 	GList *walk;
    284 	if (!g_completion_list)
    285 		return;
    286 
    287 	clear_completion_cache();
    288 	if (g_completion)
    289 		g_completion_clear_items(g_completion);
    290 
    291 	walk = g_list_first(g_completion_list);
    292 	for (; walk != NULL; walk = g_list_next(walk)) {
    293 		completion_entry *ce = (completion_entry *) walk->data;
    294 		g_free(ce->string);
    295 		g_free(walk->data);
    296 	}
    297 	g_list_free(g_completion_list);
    298 	g_completion_list = NULL;
    299 }
    300 /**
    301  * Free up all completion index data.
    302  */
    303 static void free_all(void)
    304 {
    305 	free_completion_list();
    306 	free_all_addresses();
    307 	g_completion_free(g_completion);
    308 	g_completion = NULL;
    309 }
    310 
    311 /**
    312  * Append specified address entry to the index.
    313  * \param str Index string value.
    314  * \param ae  Entry containing address data.
    315  */
    316 void addr_compl_add_address1(const char *str, address_entry *ae)
    317 {
    318 	completion_entry *ce1;
    319 	ce1 = g_new0(completion_entry, 1),
    320 	/* GCompletion list is case sensitive */
    321 	ce1->string = g_utf8_strdown(str, -1);
    322 	ce1->ref = ae;
    323 
    324 	g_completion_list = g_list_prepend(g_completion_list, ce1);
    325 }
    326 
    327 /**
    328  * Adds address to the completion list. This function looks complicated, but
    329  * it's only allocation checks. Each value will be included in the index.
    330  * \param name    Recipient name.
    331  * \param address EMail address.
    332  * \param alias   Alias to append.
    333  * \param grp_emails the emails in case of a group. List should be freed later,
    334  * but not its strings
    335  * \return <code>0</code> if entry appended successfully, or <code>-1</code>
    336  *         if failure.
    337  */
    338 static gint add_address(const gchar *name, const gchar *address,
    339 			const gchar *nick, const gchar *alias, GList *grp_emails)
    340 {
    341 	address_entry *ae;
    342 
    343 	if (!address && !grp_emails)
    344 		return -1;
    345 
    346 	if (!name)
    347 		name = "";
    348 
    349 	ae = g_new0(address_entry, 1);
    350 	cm_return_val_if_fail(ae != NULL, -1);
    351 
    352 	ae->name = g_strdup(name);
    353 	ae->address = g_strdup(address);
    354 	ae->grp_emails = grp_emails;
    355 	g_address_list = g_list_prepend(g_address_list, ae);
    356 
    357 	addr_compl_add_address1(name, ae);
    358 
    359 	if (address != NULL && *address != '\0')
    360 		addr_compl_add_address1(address, ae);
    361 
    362 	if (nick != NULL && *nick != '\0')
    363 		addr_compl_add_address1(nick, ae);
    364 
    365 	if (alias != NULL && *alias != '\0')
    366 		addr_compl_add_address1(alias, ae);
    367 
    368 	return 0;
    369 }
    370 
    371 /**
    372  * Read address book, creating all entries in the completion index.
    373  */
    374 static void read_address_book(gchar *folderpath) {
    375 	free_all_addresses();
    376 	free_completion_list();
    377 
    378 	addrindex_load_completion( add_address, folderpath );
    379 
    380 	/* plugins may hook in here to modify/extend the completion list */
    381 	if(!folderpath) {
    382 		hooks_invoke(ADDDRESS_COMPLETION_BUILD_ADDRESS_LIST_HOOKLIST, &g_address_list);
    383 	}
    384 
    385 	g_address_list = g_list_reverse(g_address_list);
    386 	g_completion_list = g_list_reverse(g_completion_list);
    387 	/* merge the completion entry list into g_completion */
    388 	if (g_completion_list) {
    389 		g_completion_add_items(g_completion, g_completion_list);
    390 		if (debug_get_mode())
    391 			debug_print("read %d items in %s\n",
    392 				g_list_length(g_completion_list),
    393 				folderpath?folderpath:"(null)");
    394 	}
    395 }
    396 
    397 /**
    398  * Test whether there is a completion pending.
    399  * \return <code>TRUE</code> if pending.
    400  */
    401 static gboolean is_completion_pending(void)
    402 {
    403 	/* check if completion pending, i.e. we might satisfy a request for the next
    404 	 * or previous address */
    405 	 return g_completion_count;
    406 }
    407 
    408 /**
    409  * Clear the completion cache.
    410  */
    411 static void clear_completion_cache(void)
    412 {
    413 	if (is_completion_pending()) {
    414 		g_free(g_completion_prefix);
    415 
    416 		if (g_completion_addresses) {
    417 			g_slist_free(g_completion_addresses);
    418 			g_completion_addresses = NULL;
    419 		}
    420 
    421 		g_completion_count = g_completion_next = 0;
    422 	}
    423 }
    424 
    425 /**
    426  * Prepare completion index. This function should be called prior to attempting
    427  * address completion.
    428  * \return The number of addresses in the completion list.
    429  */
    430 guint start_address_completion(gchar *folderpath)
    431 {
    432 	gboolean different_book = FALSE;
    433 	clear_completion_cache();
    434 
    435 	if (g_strcmp0(completion_folder_path,folderpath))
    436 		different_book = TRUE;
    437 
    438 	g_free(completion_folder_path);
    439 	if (folderpath != NULL)
    440 		completion_folder_path = g_strdup(folderpath);
    441 	else
    442 		completion_folder_path = NULL;
    443 
    444 	if (!g_ref_count) {
    445 		init_all();
    446 		/* open the address book */
    447 		read_address_book(folderpath);
    448 	} else if (different_book)
    449 		read_address_book(folderpath);
    450 
    451 	g_ref_count++;
    452 	debug_print("start_address_completion(%s) ref count %d\n",
    453 				folderpath?folderpath:"(null)", g_ref_count);
    454 
    455 	return g_list_length(g_completion_list);
    456 }
    457 
    458 /**
    459  * Retrieve a possible address (or a part) from an entry box. To make life
    460  * easier, we only look at the last valid address component; address
    461  * completion only works at the last string component in the entry box.
    462  *
    463  * \param entry Address entry field.
    464  * \param start_pos Address of start position of address.
    465  * \return Possible address.
    466  */
    467 static gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
    468 {
    469 	const gchar *edit_text, *p;
    470 	gint cur_pos;
    471 	gboolean in_quote = FALSE;
    472 	gboolean in_bracket = FALSE;
    473 	gchar *str;
    474 
    475 	edit_text = gtk_entry_get_text(entry);
    476 	if (edit_text == NULL) return NULL;
    477 
    478 	cur_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
    479 
    480 	/* scan for a separator. doesn't matter if walk points at null byte. */
    481 	for (p = g_utf8_offset_to_pointer(edit_text, cur_pos);
    482 	     p > edit_text;
    483 	     p = g_utf8_prev_char(p)) {
    484 		if (*p == '"') {
    485 			in_quote = TRUE;
    486 		} else if (!in_quote) {
    487 			if (!in_bracket && *p == ',') {
    488 				break;
    489 			} else if (*p == '<')
    490 				in_bracket = TRUE;
    491 			else if (*p == '>')
    492 				in_bracket = FALSE;
    493 		}
    494 	}
    495 
    496 	/* have something valid */
    497 	if (g_utf8_strlen(p, -1) == 0)
    498 		return NULL;
    499 
    500 #define IS_VALID_CHAR(x) \
    501 	(g_ascii_isalnum(x) || (x) == '"' || (x) == '<' || (((unsigned char)(x)) > 0x7f))
    502 
    503 	/* now scan back until we hit a valid character */
    504 	for (; *p && !IS_VALID_CHAR(*p); p = g_utf8_next_char(p))
    505 		;
    506 
    507 #undef IS_VALID_CHAR
    508 
    509 	if (g_utf8_strlen(p, -1) == 0)
    510 		return NULL;
    511 
    512 	if (start_pos) *start_pos = g_utf8_pointer_to_offset(edit_text, p);
    513 
    514 	str = g_strdup(p);
    515 
    516 	return str;
    517 }
    518 
    519 static gchar *get_complete_address_from_name_email(const gchar *name, const gchar *email)
    520 {
    521 	gchar *address = NULL;
    522 	if (!name || name[0] == '\0')
    523 		address = g_strdup_printf("<%s>", email);
    524 	else if (strchr_with_skip_quote(name, '"', ','))
    525 		address = g_strdup_printf
    526 			("\"%s\" <%s>", name, email);
    527 	else
    528 		address = g_strdup_printf
    529 			("%s <%s>", name, email);
    530 	return address;
    531 }
    532 
    533 /**
    534  * Replace an incompleted address with a completed one.
    535  * \param entry     Address entry field.
    536  * \param newtext   New text.
    537  * \param start_pos Insertion point in entry field.
    538  */
    539 static void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
    540 			     gint start_pos, gboolean is_group, GList *grp_emails)
    541 {
    542 	if (!newtext) return;
    543 	gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, -1);
    544 	if (!is_group) {
    545 		gtk_editable_insert_text(GTK_EDITABLE(entry), newtext, strlen(newtext),
    546 				 &start_pos);
    547 	} else {
    548 		gchar *addresses = NULL;
    549 		GList *cur = grp_emails;
    550 		for (; cur; cur = cur->next) {
    551 			gchar *tmp;
    552 			ItemEMail *email = (ItemEMail *)cur->data;
    553 			ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(email);
    554 
    555 			gchar *addr = get_complete_address_from_name_email(
    556 				ADDRITEM_NAME(person), email->address);
    557 			if (addresses)
    558 				tmp = g_strdup_printf("%s, %s", addresses, addr);
    559 			else
    560 				tmp = g_strdup_printf("%s", addr);
    561 			g_free(addr);
    562 			g_free(addresses);
    563 			addresses = tmp;
    564 		}
    565 		gtk_editable_insert_text(GTK_EDITABLE(entry), addresses, strlen(addresses),
    566 				 &start_pos);
    567 		g_free(addresses);
    568 	}
    569 	gtk_editable_set_position(GTK_EDITABLE(entry), -1);
    570 }
    571 
    572 /**
    573  * Attempt to complete an address, and returns the number of addresses found.
    574  * Use <code>get_complete_address()</code> to get an entry from the index.
    575  *
    576  * \param  str Search string to find.
    577  * \return Zero if no match was found, otherwise the number of addresses; the
    578  *         original prefix (search string) will appear at index 0.
    579  */
    580 guint complete_address(const gchar *str)
    581 {
    582 	GList *result = NULL;
    583 	gchar *d = NULL;
    584 	guint  count = 0;
    585 	guint  cpl = 0;
    586 	completion_entry *ce = NULL;
    587 
    588 	cm_return_val_if_fail(str != NULL, 0);
    589 
    590 	/* g_completion is case sensitive */
    591 	d = g_utf8_strdown(str, -1);
    592 
    593 	clear_completion_cache();
    594 	g_completion_prefix = g_strdup(str);
    595 
    596 	result = g_completion_complete(g_completion, d, NULL);
    597 
    598 	count = g_list_length(result);
    599 	if (count) {
    600 		/* create list with unique addresses  */
    601 		for (cpl = 0, result = g_list_first(result);
    602 		     result != NULL;
    603 		     result = g_list_next(result)) {
    604 			ce = (completion_entry *)(result->data);
    605 			if (NULL == g_slist_find(g_completion_addresses,
    606 						 ce->ref)) {
    607 				cpl++;
    608 				g_completion_addresses =
    609 					g_slist_append(g_completion_addresses,
    610 						       ce->ref);
    611 			}
    612 		}
    613 		count = cpl + 1;	/* index 0 is the original prefix */
    614 		g_completion_next = 1;	/* we start at the first completed one */
    615 		if (prefs_common.address_search_wildcard)
    616 		    g_completion_addresses = g_slist_sort(g_completion_addresses,
    617 							  addr_comparison_func);
    618 	} else {
    619 		g_free(g_completion_prefix);
    620 		g_completion_prefix = NULL;
    621 	}
    622 
    623 	g_completion_count = count;
    624 
    625 	g_free(d);
    626 
    627 	return count;
    628 }
    629 
    630 /**
    631  * complete_matches_found() returns the number of matched addresses according
    632  * to the completion mechanism. Unlike complete_address(), the returned value
    633  * doesn't count str itself. If there's no match, it returns 0.
    634  * To get a list of completion matches, see complete_address() instead.
    635  */
    636 guint complete_matches_found(const gchar *str)
    637 {
    638 	GList *result = NULL;
    639 	gchar *d = NULL;
    640 
    641 	cm_return_val_if_fail(str != NULL, 0);
    642 
    643 	/* g_completion is case sensitive */
    644 	d = g_utf8_strdown(str, -1);
    645 
    646 	clear_completion_cache();
    647 	g_completion_prefix = g_strdup(str);
    648 
    649 	result = g_completion_complete(g_completion, d, NULL);
    650 
    651 	g_free(g_completion_prefix);
    652 	g_free(d);
    653 
    654 	return g_list_length(result);
    655 }
    656 
    657 /**
    658  * Return a complete address from the index.
    659  * \param index Index of entry that was found (by the previous call to
    660  *              <code>complete_address()</code>
    661  * \return Completed address string; this should be freed when done.
    662  */
    663 gchar *get_complete_address(gint index)
    664 {
    665 	const address_entry *p;
    666 	gchar *address = NULL;
    667 
    668 	if (index < g_completion_count) {
    669 		if (index == 0)
    670 			address = g_strdup(g_completion_prefix);
    671 		else {
    672 			/* get something from the unique addresses */
    673 			p = (address_entry *)g_slist_nth_data
    674 				(g_completion_addresses, index - 1);
    675 			if (p != NULL && p->address != NULL) {
    676 				address = get_complete_address_from_name_email(p->name, p->address);
    677 			} else if (p != NULL && p->address == NULL && p->name != NULL) {
    678 				/* that's a group */
    679 				address = g_strdup_printf("%s (%s) <!--___group___-->", p->name, _("Group"));
    680 				if (!_groupAddresses_) {
    681 					_groupAddresses_ = g_hash_table_new(NULL, g_direct_equal);
    682 				}
    683 				if (!g_hash_table_lookup(_groupAddresses_, GINT_TO_POINTER(g_str_hash(address)))) {
    684 					g_hash_table_insert(_groupAddresses_, GINT_TO_POINTER(g_str_hash(address)), p->grp_emails);
    685 
    686 				}
    687 			}
    688 		}
    689 	}
    690 
    691 	return address;
    692 }
    693 
    694 /**
    695  * Return the next complete address match from the completion index.
    696  * \return Completed address string; this should be freed when done.
    697  */
    698 static gchar *get_next_complete_address(void)
    699 {
    700 	if (is_completion_pending()) {
    701 		gchar *res;
    702 
    703 		res = get_complete_address(g_completion_next);
    704 		g_completion_next += 1;
    705 		if (g_completion_next >= g_completion_count)
    706 			g_completion_next = 0;
    707 
    708 		return res;
    709 	} else
    710 		return NULL;
    711 }
    712 
    713 /**
    714  * Return a count of the completed matches in the completion index.
    715  * \return Number of matched entries.
    716  */
    717 static guint get_completion_count(void)
    718 {
    719 	if (is_completion_pending())
    720 		return g_completion_count;
    721 	else
    722 		return 0;
    723 }
    724 
    725 /**
    726  * Invalidate address completion index. This function should be called whenever
    727  * the address book changes. This forces data to be read into the completion
    728  * data.
    729  * \return Number of entries in index.
    730  */
    731 gint invalidate_address_completion(void)
    732 {
    733 	if (g_ref_count) {
    734 		/* simply the same as start_address_completion() */
    735 		debug_print("Invalidation request for address completion\n");
    736 		read_address_book(completion_folder_path);
    737 		clear_completion_cache();
    738 	}
    739 
    740 	return g_list_length(g_completion_list);
    741 }
    742 
    743 /**
    744  * Finished with completion index. This function should be called after
    745  * matching addresses.
    746  * \return Reference count.
    747  */
    748 gint end_address_completion(void)
    749 {
    750 	gboolean different_folder = FALSE;
    751 	clear_completion_cache();
    752 
    753 	/* reset the folderpath to NULL */
    754 	if (completion_folder_path) {
    755 		g_free(completion_folder_path);
    756 		completion_folder_path = NULL;
    757 		different_folder = TRUE;
    758 	}
    759 	if (0 == --g_ref_count)
    760 		free_all();
    761 
    762 	debug_print("end_address_completion ref count %d\n", g_ref_count);
    763 	if (g_ref_count && different_folder) {
    764 		debug_print("still ref'd, different folder\n");
    765 		invalidate_address_completion();
    766 	}
    767 
    768 	return g_ref_count;
    769 }
    770 
    771 /**
    772  * Completion window.
    773  */
    774 static CompletionWindow *_compWindow_ = NULL;
    775 
    776 /**
    777  * Mutex to protect callback from multiple threads.
    778  */
    779 static pthread_mutex_t _completionMutex_ = PTHREAD_MUTEX_INITIALIZER;
    780 
    781 /**
    782  * Completion queue list.
    783  */
    784 static GList *_displayQueue_ = NULL;
    785 /**
    786  * Current query ID.
    787  */
    788 static gint _queryID_ = 0;
    789 
    790 /**
    791  * Completion idle ID.
    792  */
    793 static guint _completionIdleID_ = 0;
    794 
    795 /*
    796  * address completion entry ui. the ui (completion list was inspired by galeon's
    797  * auto completion list). remaining things powered by claws's completion engine.
    798  */
    799 
    800 #define ENTRY_DATA_TAB_HOOK	"tab_hook"	/* used to lookup entry */
    801 #define ENTRY_DATA_ALLOW_COMMAS	"allowcommas"	/* used to know whether to present groups */
    802 
    803 static void address_completion_mainwindow_set_focus	(GtkWindow   *window,
    804 							 GtkWidget   *widget,
    805 							 gpointer     data);
    806 static gboolean address_completion_entry_key_pressed	(GtkEntry    *entry,
    807 							 GdkEventKey *ev,
    808 							 gpointer     data);
    809 static gboolean address_completion_complete_address_in_entry
    810 							(GtkEntry    *entry,
    811 							 gboolean     next);
    812 static void address_completion_create_completion_window	(GtkEntry    *entry);
    813 
    814 static gboolean completion_window_button_press
    815 					(GtkWidget	 *widget,
    816 					 GdkEventButton  *event,
    817 					 CompletionWindow *compWin );
    818 
    819 static gboolean completion_window_key_press
    820 					(GtkWidget	 *widget,
    821 					 GdkEventKey	 *event,
    822 					 CompletionWindow *compWin );
    823 static void address_completion_create_completion_window( GtkEntry *entry_ );
    824 
    825 /**
    826  * Create a completion window object.
    827  * \return Initialized completion window.
    828  */
    829 static CompletionWindow *addrcompl_create_window( void ) {
    830 	CompletionWindow *cw;
    831 
    832 	cw = g_new0( CompletionWindow, 1 );
    833 	cw->listCount = 0;
    834 	cw->searchTerm = NULL;
    835 	cw->window = NULL;
    836 	cw->entry = NULL;
    837 	cw->list_view = NULL;
    838 	cw->in_mouse = FALSE;
    839 	cw->destroying = FALSE;
    840 
    841 	return cw;
    842 }
    843 
    844 /**
    845  * Destroy completion window.
    846  * \param cw Window to destroy.
    847  */
    848 static void addrcompl_destroy_window( CompletionWindow *cw ) {
    849 	GdkDisplay *display;
    850 	GdkSeat    *seat;
    851 
    852 	display = gdk_display_get_default();
    853 	seat = gdk_display_get_default_seat(display);
    854 	/* Stop all searches currently in progress */
    855 	addrindex_stop_search( _queryID_ );
    856 	/* Remove idler function... or application may not terminate */
    857 	if( _completionIdleID_ != 0 ) {
    858 		g_source_remove( _completionIdleID_ );
    859 		_completionIdleID_ = 0;
    860 	}
    861 
    862 	/* Now destroy window */
    863 	if( cw ) {
    864 		/* Clear references to widgets */
    865 		cw->entry = NULL;
    866 		cw->list_view = NULL;
    867 
    868 		/* Free objects */
    869 		if( cw->window ) {
    870 			gtk_widget_hide( cw->window );
    871 			gtk_widget_destroy( cw->window );
    872 		}
    873 		cw->window = NULL;
    874 		cw->destroying = FALSE;
    875 		cw->in_mouse = FALSE;
    876 	}
    877 
    878 	/* Re-enable keyboard, required at least for Gtk3/Win32 */
    879 	gdk_seat_ungrab(seat);
    880 }
    881 
    882 /**
    883  * Free up completion window.
    884  * \param cw Window to free.
    885  */
    886 static void addrcompl_free_window( CompletionWindow *cw ) {
    887 	if( cw ) {
    888 		addrcompl_destroy_window( cw );
    889 
    890 		g_free( cw->searchTerm );
    891 		cw->searchTerm = NULL;
    892 
    893 		/* Clear references */
    894 		cw->listCount = 0;
    895 
    896 		/* Free object */
    897 		g_free( cw );
    898 	}
    899 }
    900 
    901 /**
    902  * Advance selection to previous/next item in list.
    903  * \param list_view List to process.
    904  * \param forward Set to <i>TRUE</i> to select next or <i>FALSE</i> for
    905  *                previous entry.
    906  */
    907 static void completion_window_advance_selection(GtkTreeView *list_view, gboolean forward)
    908 {
    909 	GtkTreeSelection *selection;
    910 	GtkTreeIter iter;
    911 	GtkTreeModel *model;
    912 
    913 	cm_return_if_fail(list_view != NULL);
    914 
    915 	selection = gtk_tree_view_get_selection(list_view);
    916 	if (!gtk_tree_selection_get_selected(selection, &model, &iter))
    917 		return;
    918 
    919 	if (forward) {
    920 		forward = gtk_tree_model_iter_next(model, &iter);
    921 		if (forward)
    922 			gtk_tree_selection_select_iter(selection, &iter);
    923 	} else {
    924 		GtkTreePath *prev;
    925 
    926 		prev = gtk_tree_model_get_path(model, &iter);
    927 		if (!prev)
    928 			return;
    929 
    930 		if (gtk_tree_path_prev(prev))
    931 			gtk_tree_selection_select_path(selection, prev);
    932 
    933 		gtk_tree_path_free(prev);
    934 	}
    935 }
    936 
    937 /**
    938  * Resize window to accommodate maximum number of address entries.
    939  * \param cw Completion window.
    940  */
    941 static void addrcompl_resize_window( CompletionWindow *cw ) {
    942 	GdkDisplay *display;
    943 	GtkRequisition r;
    944 	GdkGrabStatus status;
    945 	gint x, y, width;
    946 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(cw->list_view));
    947 	GtkTreeIter iter;
    948 	GdkRectangle cell_rect;
    949 	gint extra_height = 0;
    950 
    951 	gdk_window_get_position(gtk_widget_get_window(cw->window), &x, &y);
    952 	width = gdk_window_get_width(gtk_widget_get_window(cw->window));
    953 
    954 	gtk_widget_queue_resize_no_redraw(cw->list_view);
    955 	gtk_widget_get_preferred_size(cw->list_view, &r, NULL);
    956 
    957 	if (model && gtk_tree_model_get_iter_first(model, &iter)) {
    958 		GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
    959 
    960 		gtk_tree_view_get_cell_area(GTK_TREE_VIEW(cw->list_view), path, NULL, &cell_rect);
    961 		gtk_tree_path_free(path);
    962 		extra_height = cell_rect.height + 4;
    963 	}
    964 	gtk_widget_set_size_request(cw->window, width, r.height + extra_height);
    965 
    966 	display = gdk_display_get_default();
    967 	status = gdk_seat_grab(gdk_display_get_default_seat(display),
    968 			       gtk_widget_get_window(cw->window),
    969 			       GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
    970 			       GDK_BUTTON_RELEASE_MASK,
    971 			       TRUE, NULL, NULL, NULL, NULL);
    972 	if (status != GDK_GRAB_SUCCESS)
    973 		g_warning("gdk_seat_grab failed with status %d", status);
    974 	gtk_grab_add(cw->window);
    975 
    976 }
    977 
    978 static GdkPixbuf *group_pixbuf = NULL;
    979 static GdkPixbuf *email_pixbuf = NULL;
    980 
    981 /**
    982  * Add an address the completion window address list.
    983  * \param cw      Completion window.
    984  * \param address Address to add.
    985  */
    986 static void addrcompl_add_entry( CompletionWindow *cw, gchar *address ) {
    987 	GtkListStore *store;
    988 	GtkTreeIter iter;
    989 	GtkTreeSelection *selection;
    990 	gboolean is_group = FALSE;
    991 	GList *grp_emails = NULL;
    992 	store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(cw->list_view)));
    993 	GdkPixbuf *pixbuf;
    994 
    995 	if (!group_pixbuf) {
    996 		stock_pixbuf_gdk(STOCK_PIXMAP_ADDR_TWO, &group_pixbuf);
    997 		g_object_ref(G_OBJECT(group_pixbuf));
    998 	}
    999 	if (!email_pixbuf) {
   1000 		stock_pixbuf_gdk(STOCK_PIXMAP_ADDR_ONE, &email_pixbuf);
   1001 		g_object_ref(G_OBJECT(email_pixbuf));
   1002 	}
   1003 	/* g_print( "\t\tAdding :%s\n", address ); */
   1004 	if (strstr(address, " <!--___group___-->")) {
   1005 		is_group = TRUE;
   1006 		if (_groupAddresses_)
   1007 			grp_emails = g_hash_table_lookup(_groupAddresses_, GINT_TO_POINTER(g_str_hash(address)));
   1008 		*(strstr(address, " <!--___group___-->")) = '\0';
   1009 		pixbuf = group_pixbuf;
   1010 	} else if (strchr(address, '@') && strchr(address, '<') &&
   1011 		   strchr(address, '>')) {
   1012 		pixbuf = email_pixbuf;
   1013 	} else
   1014 		pixbuf = NULL;
   1015 
   1016 	if (is_group && !_allowCommas_)
   1017 		return;
   1018 	gtk_list_store_append(store, &iter);
   1019 	gtk_list_store_set(store, &iter,
   1020 				ADDR_COMPL_ICON, pixbuf,
   1021 				ADDR_COMPL_ADDRESS, address,
   1022 				ADDR_COMPL_ISGROUP, is_group,
   1023 				ADDR_COMPL_GROUPLIST, grp_emails,
   1024 				-1);
   1025 	cw->listCount++;
   1026 
   1027 	/* Resize window */
   1028 	addrcompl_resize_window( cw );
   1029 	gtk_grab_add( cw->window );
   1030 
   1031 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(cw->list_view));
   1032 	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter))
   1033 		return;
   1034 
   1035 	if (cw->listCount == 1) {
   1036 		/* Select first row for now */
   1037 		gtk_tree_selection_select_iter(selection, &iter);
   1038 	}
   1039 }
   1040 
   1041 void addrcompl_reflect_prefs_pixmap_theme(void) {
   1042 	if (group_pixbuf) {
   1043 		g_object_unref(G_OBJECT(group_pixbuf));
   1044 		group_pixbuf = NULL;
   1045 	}
   1046 	if (email_pixbuf) {
   1047 		g_object_unref(G_OBJECT(email_pixbuf));
   1048 		email_pixbuf = NULL;
   1049 	}
   1050 }
   1051 
   1052 /**
   1053  * Completion idle function. This function is called by the main (UI) thread
   1054  * during UI idle time while an address search is in progress. Items from the
   1055  * display queue are processed and appended to the address list.
   1056  *
   1057  * \param data Target completion window to receive email addresses.
   1058  * \return <i>TRUE</i> to ensure that idle event do not get ignored.
   1059  */
   1060 static gboolean addrcompl_idle( gpointer data ) {
   1061 	GList *node;
   1062 	gchar *address;
   1063 
   1064 	/* Process all entries in display queue */
   1065 	pthread_mutex_lock( & _completionMutex_ );
   1066 	if( _displayQueue_ ) {
   1067 		node = _displayQueue_;
   1068 		node = g_list_next(node); /* skip search term */
   1069 		while( node ) {
   1070 			address = node->data;
   1071 			/* g_print( "address ::: %s :::\n", address ); */
   1072 			addrcompl_add_entry( _compWindow_, address );
   1073 			g_free( address );
   1074 			node = g_list_next( node );
   1075 		}
   1076 		g_list_free( _displayQueue_ );
   1077 		_displayQueue_ = NULL;
   1078 	}
   1079 	pthread_mutex_unlock( & _completionMutex_ );
   1080 	claws_do_idle();
   1081 
   1082 	return TRUE;
   1083 }
   1084 
   1085 /**
   1086  * Callback entry point. The background thread (if any) appends the address
   1087  * list to the display queue.
   1088  * \param sender     Sender of query.
   1089  * \param queryID    Query ID of search request.
   1090  * \param listEMail  List of zero of more email objects that met search
   1091  *                   criteria.
   1092  * \param data       Query data.
   1093  */
   1094 static gint addrcompl_callback_entry(
   1095 	gpointer sender, gint queryID, GList *listEMail, gpointer data )
   1096 {
   1097 	GList *node;
   1098 	gchar *address;
   1099 
   1100 	/* g_print( "addrcompl_callback_entry::queryID=%d\n", queryID ); */
   1101 	pthread_mutex_lock( & _completionMutex_ );
   1102 	if( queryID == _queryID_ ) {
   1103 		/* Append contents to end of display queue */
   1104 		node = listEMail;
   1105 		while( node ) {
   1106 			ItemEMail *email = node->data;
   1107 
   1108 			address = addritem_format_email( email );
   1109 			/* g_print( "\temail/address ::%s::\n", address ); */
   1110 			_displayQueue_ = g_list_append( _displayQueue_, address );
   1111 			node = g_list_next( node );
   1112 		}
   1113 	}
   1114 	g_list_free( listEMail );
   1115 	pthread_mutex_unlock( & _completionMutex_ );
   1116 
   1117 	return 0;
   1118 }
   1119 
   1120 /**
   1121  * Clear the display queue.
   1122  */
   1123 static void addrcompl_clear_queue( void ) {
   1124 	/* Clear out display queue */
   1125 	pthread_mutex_lock( & _completionMutex_ );
   1126 
   1127 	g_list_free_full( _displayQueue_, g_free );
   1128 	_displayQueue_ = NULL;
   1129 
   1130 	pthread_mutex_unlock( & _completionMutex_ );
   1131 }
   1132 
   1133 /**
   1134  * Add a single address entry into the display queue.
   1135  * \param address Address to append.
   1136  */
   1137 static void addrcompl_add_queue( gchar *address ) {
   1138 	pthread_mutex_lock( & _completionMutex_ );
   1139 	_displayQueue_ = g_list_append( _displayQueue_, address );
   1140 	pthread_mutex_unlock( & _completionMutex_ );
   1141 }
   1142 
   1143 /**
   1144  * Load list with entries from local completion index.
   1145  */
   1146 static void addrcompl_load_local( void ) {
   1147 	guint count = 0;
   1148 
   1149 	for (count = 0; count < get_completion_count(); count++) {
   1150 		gchar *address;
   1151 
   1152 		address = get_complete_address( count );
   1153 		/* g_print( "\taddress ::%s::\n", address ); */
   1154 
   1155 		/* Append contents to end of display queue */
   1156 		addrcompl_add_queue( address );
   1157 	}
   1158 }
   1159 
   1160 /**
   1161  * Start the search.
   1162  */
   1163 static void addrcompl_start_search( void ) {
   1164 	gchar *searchTerm;
   1165 
   1166 	searchTerm = g_strdup( _compWindow_->searchTerm );
   1167 
   1168 	/* Setup the search */
   1169 	_queryID_ = addrindex_setup_search(
   1170 		searchTerm, NULL, addrcompl_callback_entry );
   1171 	g_free( searchTerm );
   1172 	/* g_print( "addrcompl_start_search::queryID=%d\n", _queryID_ ); */
   1173 
   1174 	/* Load local stuff */
   1175 	addrcompl_load_local();
   1176 
   1177 	/* Sit back and wait until something happens */
   1178 	_completionIdleID_ =
   1179 		g_idle_add( (GSourceFunc) addrcompl_idle, NULL );
   1180 	/* g_print( "addrindex_start_search::queryID=%d\n", _queryID_ ); */
   1181 
   1182 	addrindex_start_search( _queryID_ );
   1183 }
   1184 
   1185 /**
   1186  * Apply the current selection in the list to the entry field. Focus is also
   1187  * moved to the next widget so that Tab key works correctly.
   1188  * \param list_view List to process.
   1189  * \param entry Address entry field.
   1190  * \param move_focus Move focus to the next widget ?
   1191  */
   1192 static void completion_window_apply_selection(GtkTreeView *list_view,
   1193 						GtkEntry *entry,
   1194 						gboolean move_focus)
   1195 {
   1196 	gchar *address = NULL, *text = NULL;
   1197 	gint   cursor_pos;
   1198 	GtkWidget *parent;
   1199 	GtkTreeSelection *selection;
   1200 	GtkTreeModel *model;
   1201 	GtkTreeIter iter;
   1202 	gboolean is_group = FALSE;
   1203 	cm_return_if_fail(list_view != NULL);
   1204 	cm_return_if_fail(entry != NULL);
   1205 	GList *grp_emails = NULL;
   1206 
   1207 	selection = gtk_tree_view_get_selection(list_view);
   1208 	if (!gtk_tree_selection_get_selected(selection, &model, &iter))
   1209 		return;
   1210 
   1211 	/* First remove the idler */
   1212 	if( _completionIdleID_ != 0 ) {
   1213 		g_source_remove( _completionIdleID_ );
   1214 		_completionIdleID_ = 0;
   1215 	}
   1216 
   1217 	/* Process selected item */
   1218 	gtk_tree_model_get(model, &iter, ADDR_COMPL_ADDRESS, &text,
   1219 				ADDR_COMPL_ISGROUP, &is_group,
   1220 				ADDR_COMPL_GROUPLIST, &grp_emails,
   1221 				-1);
   1222 
   1223 	address = get_address_from_edit(entry, &cursor_pos);
   1224 	g_free(address);
   1225 	replace_address_in_edit(entry, text, cursor_pos, is_group, grp_emails);
   1226 	g_free(text);
   1227 
   1228 	/* Move focus to next widget */
   1229 	parent = gtk_widget_get_parent(GTK_WIDGET(entry));
   1230 	if( parent && move_focus) {
   1231 		gtk_widget_child_focus( parent, GTK_DIR_TAB_FORWARD );
   1232 	}
   1233 }
   1234 
   1235 /**
   1236  * Start address completion. Should be called when creating the main window
   1237  * containing address completion entries.
   1238  * \param mainwindow Main window.
   1239  */
   1240 void address_completion_start(GtkWidget *mainwindow)
   1241 {
   1242 	start_address_completion(NULL);
   1243 	set_match_any_part(TRUE);
   1244 
   1245 	/* register focus change hook */
   1246 	g_signal_connect(G_OBJECT(mainwindow), "set_focus",
   1247 			 G_CALLBACK(address_completion_mainwindow_set_focus),
   1248 			 mainwindow);
   1249 }
   1250 
   1251 /**
   1252  * Need unique data to make unregistering signal handler possible for the auto
   1253  * completed entry.
   1254  */
   1255 #define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
   1256 
   1257 /**
   1258  * Register specified entry widget for address completion.
   1259  * \param entry Address entry field.
   1260  */
   1261 void address_completion_register_entry(GtkEntry *entry, gboolean allow_commas)
   1262 {
   1263 	cm_return_if_fail(entry != NULL);
   1264 	cm_return_if_fail(GTK_IS_ENTRY(entry));
   1265 
   1266 	/* add hooked property */
   1267 	g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
   1268 	g_object_set_data(G_OBJECT(entry), ENTRY_DATA_ALLOW_COMMAS, GINT_TO_POINTER(allow_commas));
   1269 
   1270 	/* add keypress event */
   1271 	g_signal_connect_closure
   1272 		(G_OBJECT(entry), "key_press_event",
   1273 		 g_cclosure_new(G_CALLBACK(address_completion_entry_key_pressed),
   1274 				COMPLETION_UNIQUE_DATA,
   1275 				NULL),
   1276 		 FALSE); /* magic */
   1277 }
   1278 
   1279 /**
   1280  * Unregister specified entry widget from address completion operations.
   1281  * \param entry Address entry field.
   1282  */
   1283 void address_completion_unregister_entry(GtkEntry *entry)
   1284 {
   1285 	GObject *entry_obj;
   1286 
   1287 	cm_return_if_fail(entry != NULL);
   1288 	cm_return_if_fail(GTK_IS_ENTRY(entry));
   1289 
   1290 	entry_obj = g_object_get_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
   1291 	cm_return_if_fail(entry_obj);
   1292 	cm_return_if_fail(G_OBJECT(entry_obj) == G_OBJECT(entry));
   1293 
   1294 	/* has the hooked property? */
   1295 	g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
   1296 
   1297 	/* remove the hook */
   1298 	g_signal_handlers_disconnect_by_func(G_OBJECT(entry),
   1299 			G_CALLBACK(address_completion_entry_key_pressed),
   1300 			COMPLETION_UNIQUE_DATA);
   1301 }
   1302 
   1303 /**
   1304  * End address completion. Should be called when main window with address
   1305  * completion entries terminates. NOTE: this function assumes that it is
   1306  * called upon destruction of the window.
   1307  * \param mainwindow Main window.
   1308  */
   1309 void address_completion_end(GtkWidget *mainwindow)
   1310 {
   1311 	/* if address_completion_end() is really called on closing the window,
   1312 	 * we don't need to unregister the set_focus_cb */
   1313 	end_address_completion();
   1314 }
   1315 
   1316 /* if focus changes to another entry, then clear completion cache */
   1317 static void address_completion_mainwindow_set_focus(GtkWindow *window,
   1318 						    GtkWidget *widget,
   1319 						    gpointer   data)
   1320 {
   1321 
   1322 	if (widget && GTK_IS_ENTRY(widget) &&
   1323 	    g_object_get_data(G_OBJECT(widget), ENTRY_DATA_TAB_HOOK)) {
   1324 		_allowCommas_ = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), ENTRY_DATA_ALLOW_COMMAS));
   1325 		clear_completion_cache();
   1326 	}
   1327 }
   1328 
   1329 /**
   1330  * Listener that watches for tab or other keystroke in address entry field.
   1331  * \param entry Address entry field.
   1332  * \param ev    Event object.
   1333  * \param data  User data.
   1334  * \return <i>TRUE</i>.
   1335  */
   1336 static gboolean address_completion_entry_key_pressed(GtkEntry    *entry,
   1337 						     GdkEventKey *ev,
   1338 						     gpointer     data)
   1339 {
   1340 	if (ev->keyval == GDK_KEY_Tab) {
   1341 		addrcompl_clear_queue();
   1342 		_allowCommas_ = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(entry), ENTRY_DATA_ALLOW_COMMAS));
   1343 		if( address_completion_complete_address_in_entry( entry, TRUE ) ) {
   1344 			/* route a void character to the default handler */
   1345 			/* this is a dirty hack; we're actually changing a key
   1346 			 * reported by the system. */
   1347 			ev->keyval = GDK_KEY_AudibleBell_Enable;
   1348 			ev->state &= ~GDK_SHIFT_MASK;
   1349 
   1350 			/* Create window */
   1351 			address_completion_create_completion_window(entry);
   1352 
   1353 			/* Start remote queries */
   1354 			addrcompl_start_search();
   1355 
   1356 			return TRUE;
   1357 		}
   1358 		else {
   1359 			/* old behaviour */
   1360 		}
   1361 	} else if (ev->keyval == GDK_KEY_Shift_L
   1362 		|| ev->keyval == GDK_KEY_Shift_R
   1363 		|| ev->keyval == GDK_KEY_Control_L
   1364 		|| ev->keyval == GDK_KEY_Control_R
   1365 		|| ev->keyval == GDK_KEY_Caps_Lock
   1366 		|| ev->keyval == GDK_KEY_Shift_Lock
   1367 		|| ev->keyval == GDK_KEY_Meta_L
   1368 		|| ev->keyval == GDK_KEY_Meta_R
   1369 		|| ev->keyval == GDK_KEY_Alt_L
   1370 		|| ev->keyval == GDK_KEY_Alt_R) {
   1371 		/* these buttons should not clear the cache... */
   1372 	} else
   1373 		clear_completion_cache();
   1374 
   1375 	return FALSE;
   1376 }
   1377 /**
   1378  * Initialize search term for address completion.
   1379  * \param entry Address entry field.
   1380  */
   1381 static gboolean address_completion_complete_address_in_entry(GtkEntry *entry,
   1382 							     gboolean  next)
   1383 {
   1384 	gint ncount, cursor_pos;
   1385 	gchar *searchTerm, *new = NULL;
   1386 
   1387 	cm_return_val_if_fail(entry != NULL, FALSE);
   1388 
   1389 	if (!gtk_widget_has_focus(GTK_WIDGET(entry))) return FALSE;
   1390 
   1391 	/* get an address component from the cursor */
   1392 	searchTerm = get_address_from_edit( entry, &cursor_pos );
   1393 	if( ! searchTerm ) return FALSE;
   1394 	/* g_print( "search for :::%s:::\n", searchTerm ); */
   1395 
   1396 	/* Clear any existing search */
   1397 	g_free( _compWindow_->searchTerm );
   1398 	_compWindow_->searchTerm = g_strdup( searchTerm );
   1399 
   1400 	/* Perform search on local completion index */
   1401 	ncount = complete_address( searchTerm );
   1402 	if( 0 < ncount ) {
   1403 		new = get_next_complete_address();
   1404 		g_free( new );
   1405 	}
   1406 	/* Select the address if there is only one match */
   1407 	if (ncount == 2) {
   1408 		/* Display selected address in entry field */
   1409 		gchar *addr = get_complete_address(1);
   1410 		if (addr && !strstr(addr, " <!--___group___-->")) {
   1411 			replace_address_in_edit(entry, addr, cursor_pos, FALSE, NULL);
   1412 			/* Discard the window */
   1413 			clear_completion_cache();
   1414 		}
   1415 		g_free(addr);
   1416 	}
   1417 	/* Make sure that drop-down appears uniform! */
   1418 	else if( ncount == 0 ) {
   1419 		addrcompl_add_queue( searchTerm );
   1420 	} else {
   1421 		g_free( searchTerm );
   1422 	}
   1423 
   1424 	return TRUE;
   1425 }
   1426 
   1427 /**
   1428  * Create new address completion window for specified entry.
   1429  * \param entry_ Entry widget to associate with window.
   1430  */
   1431 static void address_completion_create_completion_window( GtkEntry *entry_ )
   1432 {
   1433 	gint x, y;
   1434 	GdkRectangle rect;
   1435 	GtkWidget *scroll, *list_view;
   1436 	GdkGrabStatus status;
   1437 	GdkDisplay *display;
   1438 	GtkRequisition r;
   1439 	GtkWidget *window;
   1440 	GtkWidget *entry = GTK_WIDGET(entry_);
   1441 
   1442 	/* Create new window and list */
   1443 	window = gtk_window_new(GTK_WINDOW_POPUP);
   1444 	list_view  = addr_compl_list_view_create(_compWindow_);
   1445 
   1446 	/* Destroy any existing window */
   1447 	addrcompl_destroy_window( _compWindow_ );
   1448 
   1449 	/* Create new object */
   1450 	_compWindow_->window    = window;
   1451 	_compWindow_->entry     = entry;
   1452 	_compWindow_->list_view = list_view;
   1453 	_compWindow_->listCount = 0;
   1454 	_compWindow_->in_mouse  = FALSE;
   1455 
   1456 	scroll = gtk_scrolled_window_new(NULL, NULL);
   1457 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
   1458 				       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
   1459 	gtk_container_add(GTK_CONTAINER(window), scroll);
   1460 	gtk_container_add(GTK_CONTAINER(scroll), list_view);
   1461 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
   1462 		GTK_SHADOW_OUT);
   1463 
   1464 	/* Use entry widget to position initial window */
   1465 	gtk_widget_get_allocation(entry, &rect);
   1466 
   1467 	/* rect.x and rect.y are relative to parent of our GtkEntry,
   1468 	 * we need to convert them to absolute coordinates */
   1469 	gdk_window_get_root_coords(
   1470 			gtk_widget_get_window(gtk_widget_get_parent(entry)),
   1471 			rect.x, rect.y, &x, &y);
   1472 
   1473 	/* Move the window to just below the GtkEntry */
   1474 	gtk_window_move(GTK_WINDOW(window), x, y + rect.height);
   1475 
   1476 	/* Resize window to fit initial (empty) address list */
   1477 	gtk_widget_get_preferred_size(list_view, &r, NULL);
   1478 	gtk_widget_set_size_request(window, rect.width, r.height);
   1479 	gtk_widget_show_all(window);
   1480 
   1481 	/* Setup handlers */
   1482 	g_signal_connect(G_OBJECT(list_view), "button_press_event",
   1483 			 G_CALLBACK(list_view_button_press),
   1484 			 _compWindow_);
   1485 
   1486 	g_signal_connect(G_OBJECT(list_view), "button_release_event",
   1487 			 G_CALLBACK(list_view_button_release),
   1488 			 _compWindow_);
   1489 
   1490 	g_signal_connect(G_OBJECT(window),
   1491 			 "button-press-event",
   1492 			 G_CALLBACK(completion_window_button_press),
   1493 			 _compWindow_ );
   1494 	g_signal_connect(G_OBJECT(window),
   1495 			 "key-press-event",
   1496 			 G_CALLBACK(completion_window_key_press),
   1497 			 _compWindow_ );
   1498 	display = gdk_display_get_default();
   1499 	status = gdk_seat_grab(gdk_display_get_default_seat(display),
   1500 			       gtk_widget_get_window(window),
   1501 			       GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
   1502 			       GDK_BUTTON_RELEASE_MASK,
   1503 			       TRUE, NULL, NULL, NULL, NULL);
   1504 	if (status != GDK_GRAB_SUCCESS)
   1505 		g_warning("gdk_seat_grab failed with status %d", status);
   1506 	gtk_grab_add( window );
   1507 }
   1508 
   1509 /**
   1510  * Respond to button press in completion window. Check if mouse click is
   1511  * anywhere outside the completion window. In that case the completion
   1512  * window is destroyed, and the original searchTerm is restored.
   1513  *
   1514  * \param widget   Window object.
   1515  * \param event    Event.
   1516  * \param compWin  Reference to completion window.
   1517  */
   1518 static gboolean completion_window_button_press(GtkWidget *widget,
   1519 					       GdkEventButton *event,
   1520 					       CompletionWindow *compWin )
   1521 {
   1522 	GtkWidget *event_widget, *entry;
   1523 	gchar *searchTerm;
   1524 	gint cursor_pos;
   1525 	gboolean restore = TRUE;
   1526 
   1527 	cm_return_val_if_fail(compWin != NULL, FALSE);
   1528 
   1529 	entry = compWin->entry;
   1530 	cm_return_val_if_fail(entry != NULL, FALSE);
   1531 
   1532 	/* Test where mouse was clicked */
   1533 	event_widget = gtk_get_event_widget((GdkEvent *)event);
   1534 	if (event_widget != widget) {
   1535 		while (event_widget) {
   1536 			if (event_widget == widget)
   1537 				return FALSE;
   1538 			else if (event_widget == entry) {
   1539 				restore = FALSE;
   1540 				break;
   1541 			}
   1542 			event_widget = gtk_widget_get_parent(event_widget);
   1543 		}
   1544 	}
   1545 
   1546 	if (restore) {
   1547 		/* Clicked outside of completion window - restore */
   1548 		searchTerm = _compWindow_->searchTerm;
   1549 		g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
   1550 		replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos, FALSE, NULL);
   1551 	}
   1552 
   1553 	clear_completion_cache();
   1554 	addrcompl_destroy_window( _compWindow_ );
   1555 
   1556 	return TRUE;
   1557 }
   1558 
   1559 /**
   1560  * Respond to key press in completion window.
   1561  * \param widget   Window object.
   1562  * \param event    Event.
   1563  * \param compWind Reference to completion window.
   1564  */
   1565 static gboolean completion_window_key_press(GtkWidget *widget,
   1566 					    GdkEventKey *event,
   1567 					    CompletionWindow *compWin )
   1568 {
   1569 	GdkEventKey tmp_event;
   1570 	GtkWidget *entry;
   1571 	gchar *searchTerm;
   1572 	gint cursor_pos;
   1573 	GtkWidget *list_view;
   1574 	GtkWidget *parent;
   1575 	cm_return_val_if_fail(compWin != NULL, FALSE);
   1576 
   1577 	entry = compWin->entry;
   1578 	list_view = compWin->list_view;
   1579 	cm_return_val_if_fail(entry != NULL, FALSE);
   1580 
   1581 	/* allow keyboard navigation in the alternatives tree view */
   1582 	if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down ||
   1583 	    event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down) {
   1584 		completion_window_advance_selection
   1585 			(GTK_TREE_VIEW(list_view),
   1586 			 event->keyval == GDK_KEY_Down ||
   1587 			 event->keyval == GDK_KEY_Page_Down ? TRUE : FALSE);
   1588 		return TRUE;
   1589 	}
   1590 
   1591 	/* make tab move to next field */
   1592 	if( event->keyval == GDK_KEY_Tab ) {
   1593 		/* Reference to parent */
   1594 		parent = gtk_widget_get_parent(GTK_WIDGET(entry));
   1595 
   1596 		/* Discard the window */
   1597 		clear_completion_cache();
   1598 		addrcompl_destroy_window( _compWindow_ );
   1599 
   1600 		/* Move focus to next widget */
   1601 		if( parent ) {
   1602 			gtk_widget_child_focus( parent, GTK_DIR_TAB_FORWARD );
   1603 		}
   1604 		return FALSE;
   1605 	}
   1606 
   1607 	/* make backtab move to previous field */
   1608 	if( event->keyval == GDK_KEY_ISO_Left_Tab ) {
   1609 		/* Reference to parent */
   1610 		parent = gtk_widget_get_parent(GTK_WIDGET(entry));
   1611 
   1612 		/* Discard the window */
   1613 		clear_completion_cache();
   1614 		addrcompl_destroy_window( _compWindow_ );
   1615 
   1616 		/* Move focus to previous widget */
   1617 		if( parent ) {
   1618 			gtk_widget_child_focus( parent, GTK_DIR_TAB_BACKWARD );
   1619 		}
   1620 		return FALSE;
   1621 	}
   1622 	_allowCommas_ = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(entry), ENTRY_DATA_ALLOW_COMMAS));
   1623 
   1624 	/* look for presses that accept the selection */
   1625 	if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_space ||
   1626 			event->keyval == GDK_KEY_KP_Enter ||
   1627 			(_allowCommas_ && event->keyval == GDK_KEY_comma)) {
   1628 		/* User selected address with a key press */
   1629 
   1630 		/* Display selected address in entry field */
   1631 		completion_window_apply_selection(
   1632 			GTK_TREE_VIEW(list_view), GTK_ENTRY(entry),
   1633 			event->keyval != GDK_KEY_comma);
   1634 
   1635 		if (event->keyval == GDK_KEY_comma) {
   1636 			gint pos = gtk_editable_get_position(GTK_EDITABLE(entry));
   1637 			gtk_editable_insert_text(GTK_EDITABLE(entry), ", ", 2, &pos);
   1638 			gtk_editable_set_position(GTK_EDITABLE(entry), pos + 1);
   1639 		}
   1640 
   1641 		/* Discard the window */
   1642 		clear_completion_cache();
   1643 		addrcompl_destroy_window( _compWindow_ );
   1644 		return FALSE;
   1645 	}
   1646 
   1647 	/* key state keys should never be handled */
   1648 	if (event->keyval == GDK_KEY_Shift_L
   1649 		 || event->keyval == GDK_KEY_Shift_R
   1650 		 || event->keyval == GDK_KEY_Control_L
   1651 		 || event->keyval == GDK_KEY_Control_R
   1652 		 || event->keyval == GDK_KEY_Caps_Lock
   1653 		 || event->keyval == GDK_KEY_Shift_Lock
   1654 		 || event->keyval == GDK_KEY_Meta_L
   1655 		 || event->keyval == GDK_KEY_Meta_R
   1656 		 || event->keyval == GDK_KEY_Alt_L
   1657 		 || event->keyval == GDK_KEY_Alt_R) {
   1658 		return FALSE;
   1659 	}
   1660 
   1661 	/* some other key, let's restore the searchTerm (orignal text) */
   1662 	searchTerm = _compWindow_->searchTerm;
   1663 	g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
   1664 	replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos, FALSE, NULL);
   1665 
   1666 	/* make sure anything we typed comes in the edit box */
   1667 	tmp_event.type       = event->type;
   1668 	tmp_event.window     = gtk_widget_get_window(GTK_WIDGET(entry));
   1669 	tmp_event.send_event = TRUE;
   1670 	tmp_event.time       = event->time;
   1671 	tmp_event.state      = event->state;
   1672 	tmp_event.keyval     = event->keyval;
   1673 	tmp_event.length     = event->length;
   1674 	tmp_event.string     = event->string;
   1675 	gtk_widget_event(entry, (GdkEvent *)&tmp_event);
   1676 
   1677 	/* and close the completion window */
   1678 	clear_completion_cache();
   1679 	addrcompl_destroy_window( _compWindow_ );
   1680 
   1681 	return TRUE;
   1682 }
   1683 
   1684 /*
   1685  * ============================================================================
   1686  * Publically accessible functions.
   1687  * ============================================================================
   1688  */
   1689 
   1690 /**
   1691  * Setup completion object.
   1692  */
   1693 void addrcompl_initialize( void ) {
   1694 	/* g_print( "addrcompl_initialize...\n" ); */
   1695 	if( ! _compWindow_ ) {
   1696 		_compWindow_ = addrcompl_create_window();
   1697 	}
   1698 	_queryID_ = 0;
   1699 	_completionIdleID_ = 0;
   1700 	/* g_print( "addrcompl_initialize...done\n" ); */
   1701 }
   1702 
   1703 /**
   1704  * Teardown completion object.
   1705  */
   1706 void addrcompl_teardown( void ) {
   1707 	/* g_print( "addrcompl_teardown...\n" ); */
   1708 	addrcompl_free_window( _compWindow_ );
   1709 	_compWindow_ = NULL;
   1710 
   1711 	addrcompl_clear_queue();
   1712 
   1713 	_completionIdleID_ = 0;
   1714 	/* g_print( "addrcompl_teardown...done\n" ); */
   1715 }
   1716 
   1717 /*
   1718  * tree view functions
   1719  */
   1720 
   1721 static GtkListStore *addr_compl_create_store(void)
   1722 {
   1723 	return gtk_list_store_new(N_ADDR_COMPL_COLUMNS,
   1724 				  GDK_TYPE_PIXBUF,
   1725 				  G_TYPE_STRING,
   1726 				  G_TYPE_BOOLEAN,
   1727 				  G_TYPE_POINTER,
   1728 				  -1);
   1729 }
   1730 
   1731 static GtkWidget *addr_compl_list_view_create(CompletionWindow *window)
   1732 {
   1733 	GtkTreeView *list_view;
   1734 	GtkTreeSelection *selector;
   1735 	GtkTreeModel *model;
   1736 
   1737 	model = GTK_TREE_MODEL(addr_compl_create_store());
   1738 	list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
   1739 	g_object_unref(model);
   1740 
   1741 	gtk_tree_view_set_headers_visible(list_view, FALSE);
   1742 
   1743 	selector = gtk_tree_view_get_selection(list_view);
   1744 	gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
   1745 	gtk_tree_selection_set_select_function(selector, addr_compl_selected,
   1746 					       window, NULL);
   1747 
   1748 	/* create the columns */
   1749 	addr_compl_create_list_view_columns(GTK_WIDGET(list_view));
   1750 
   1751 	return GTK_WIDGET(list_view);
   1752 }
   1753 
   1754 static void addr_compl_create_list_view_columns(GtkWidget *list_view)
   1755 {
   1756 	GtkTreeViewColumn *column;
   1757 	GtkCellRenderer *renderer;
   1758 
   1759 	renderer = gtk_cell_renderer_pixbuf_new();
   1760 	column = gtk_tree_view_column_new_with_attributes
   1761 		("", renderer,
   1762 	         "pixbuf", ADDR_COMPL_ICON, NULL);
   1763 	gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
   1764 	renderer = gtk_cell_renderer_text_new();
   1765 	column = gtk_tree_view_column_new_with_attributes
   1766 		("", renderer, "text", ADDR_COMPL_ADDRESS, NULL);
   1767 	gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
   1768 }
   1769 
   1770 static gboolean list_view_button_press(GtkWidget *widget, GdkEventButton *event,
   1771 				       CompletionWindow *window)
   1772 {
   1773 	if (window && event && event->type == GDK_BUTTON_PRESS) {
   1774 		window->in_mouse = TRUE;
   1775 	}
   1776 	return FALSE;
   1777 }
   1778 
   1779 static gboolean list_view_button_release(GtkWidget *widget, GdkEventButton *event,
   1780 				         CompletionWindow *window)
   1781 {
   1782 	if (window && event && event->type == GDK_BUTTON_RELEASE) {
   1783 		window->in_mouse = FALSE;
   1784 	}
   1785 	return FALSE;
   1786 }
   1787 
   1788 static gboolean addr_compl_selected(GtkTreeSelection *selector,
   1789 			            GtkTreeModel *model,
   1790 				    GtkTreePath *path,
   1791 				    gboolean currently_selected,
   1792 				    gpointer data)
   1793 {
   1794 	CompletionWindow *window = data;
   1795 
   1796 	if (currently_selected)
   1797 		return TRUE;
   1798 
   1799 	if (!window->in_mouse)
   1800 		return TRUE;
   1801 
   1802 	/* XXX: select the entry and kill window later... select is called before
   1803 	 * any other mouse events handlers including the tree view internal one;
   1804 	 * not using a time out would result in a crash. if this doesn't work
   1805 	 * safely, maybe we should set variables when receiving button presses
   1806 	 * in the tree view. */
   1807 	if (!window->destroying) {
   1808 		window->destroying = TRUE;
   1809 		g_idle_add((GSourceFunc) addr_compl_defer_select_destruct, data);
   1810 	}
   1811 
   1812 	return TRUE;
   1813 }
   1814 
   1815 static gboolean addr_compl_defer_select_destruct(CompletionWindow *window)
   1816 {
   1817 	GtkEntry *entry = GTK_ENTRY(window->entry);
   1818 
   1819 	completion_window_apply_selection(GTK_TREE_VIEW(window->list_view),
   1820 					  entry, TRUE);
   1821 
   1822 	clear_completion_cache();
   1823 
   1824 	addrcompl_destroy_window(window);
   1825 	return FALSE;
   1826 }
   1827 
   1828 gboolean found_in_addressbook(const gchar *address)
   1829 {
   1830 	gchar *addr = NULL;
   1831 	gboolean found = FALSE;
   1832 	gint num_addr = 0;
   1833 
   1834 	if (!address)
   1835 		return FALSE;
   1836 
   1837 	addr = g_strdup(address);
   1838 	extract_address(addr);
   1839 	num_addr = complete_address(addr);
   1840 	if (num_addr > 1) {
   1841 		/* skip first item (this is the search string itself) */
   1842 		int i = 1;
   1843 		for (; i < num_addr && !found; i++) {
   1844 			gchar *caddr = get_complete_address(i);
   1845 			extract_address(caddr);
   1846 			if (strcasecmp(caddr, addr) == 0)
   1847 				found = TRUE;
   1848 			g_free(caddr);
   1849 		}
   1850 	}
   1851 	g_free(addr);
   1852 	return found;
   1853 }