1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /* e-name-selector-entry.c - Single-line text entry widget for EDestinations.
4  *
5  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Authors: Hans Petter Jansson <hpj@novell.com>
20  */
21 
22 #include "evolution-config.h"
23 
24 #include <string.h>
25 #include <glib/gi18n-lib.h>
26 
27 #include <camel/camel.h>
28 #include <libebackend/libebackend.h>
29 
30 #include "e-name-selector-entry.h"
31 
32 #define E_NAME_SELECTOR_ENTRY_GET_PRIVATE(obj) \
33 	(G_TYPE_INSTANCE_GET_PRIVATE \
34 	((obj), E_TYPE_NAME_SELECTOR_ENTRY, ENameSelectorEntryPrivate))
35 
36 struct _ENameSelectorEntryPrivate {
37 	EClientCache *client_cache;
38 	gint minimum_query_length;
39 	gboolean show_address;
40 
41 	PangoAttrList *attr_list;
42 	EContactStore *contact_store;
43 	ETreeModelGenerator *email_generator;
44 	EDestinationStore *destination_store;
45 	GtkEntryCompletion *entry_completion;
46 
47 	guint type_ahead_complete_cb_id;
48 	guint update_completions_cb_id;
49 
50 	EDestination *popup_destination;
51 
52 	gpointer	(*contact_editor_func)	(EBookClient *,
53 						 EContact *,
54 						 gboolean,
55 						 gboolean);
56 	gpointer	(*contact_list_editor_func)
57 						(EBookClient *,
58 						 EContact *,
59 						 gboolean,
60 						 gboolean);
61 
62 	gboolean is_completing;
63 	GSList *user_query_fields;
64 
65 	/* For asynchronous operations. */
66 	GQueue cancellables;
67 
68 	GHashTable *known_contacts; /* gchar * ~> 1 */
69 
70 	gboolean block_entry_changed_signal;
71 };
72 
73 enum {
74 	PROP_0,
75 	PROP_CLIENT_CACHE,
76 	PROP_MINIMUM_QUERY_LENGTH,
77 	PROP_SHOW_ADDRESS
78 };
79 
80 enum {
81 	UPDATED,
82 	LAST_SIGNAL
83 };
84 
85 static guint signals[LAST_SIGNAL] = { 0 };
86 #define ENS_DEBUG(x)
87 
88 G_DEFINE_TYPE_WITH_CODE (
89 	ENameSelectorEntry,
90 	e_name_selector_entry,
91 	GTK_TYPE_ENTRY,
92 	G_IMPLEMENT_INTERFACE (
93 		E_TYPE_EXTENSIBLE, NULL))
94 
95 /* 1/3 of the second to wait until invoking autocomplete lookup */
96 #define AUTOCOMPLETE_TIMEOUT 333
97 
98 /* 1/20 of a second to wait until show the completion results */
99 #define SHOW_RESULT_TIMEOUT 50
100 
101 #define re_set_timeout(id,func,ptr,tout) G_STMT_START { \
102 	if (id) \
103 		g_source_remove (id); \
104 	id = e_named_timeout_add (tout, func, ptr); \
105 	} G_STMT_END
106 
107 static void destination_row_inserted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
108 static void destination_row_changed  (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
109 static void destination_row_deleted  (ENameSelectorEntry *name_selector_entry, GtkTreePath *path);
110 
111 static void user_insert_text (ENameSelectorEntry *name_selector_entry, gchar *new_text, gint new_text_length, gint *position, gpointer user_data);
112 static void user_delete_text (ENameSelectorEntry *name_selector_entry, gint start_pos, gint end_pos, gpointer user_data);
113 
114 static void setup_default_contact_store (ENameSelectorEntry *name_selector_entry);
115 static void deep_free_list (GList *list);
116 
117 static void
name_selector_entry_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)118 name_selector_entry_set_property (GObject *object,
119                                   guint property_id,
120                                   const GValue *value,
121                                   GParamSpec *pspec)
122 {
123 	switch (property_id) {
124 		case PROP_CLIENT_CACHE:
125 			e_name_selector_entry_set_client_cache (
126 				E_NAME_SELECTOR_ENTRY (object),
127 				g_value_get_object (value));
128 			return;
129 
130 		case PROP_MINIMUM_QUERY_LENGTH:
131 			e_name_selector_entry_set_minimum_query_length (
132 				E_NAME_SELECTOR_ENTRY (object),
133 				g_value_get_int (value));
134 			return;
135 
136 		case PROP_SHOW_ADDRESS:
137 			e_name_selector_entry_set_show_address (
138 				E_NAME_SELECTOR_ENTRY (object),
139 				g_value_get_boolean (value));
140 			return;
141 	}
142 
143 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
144 }
145 
146 static void
name_selector_entry_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)147 name_selector_entry_get_property (GObject *object,
148                                   guint property_id,
149                                   GValue *value,
150                                   GParamSpec *pspec)
151 {
152 	switch (property_id) {
153 		case PROP_CLIENT_CACHE:
154 			g_value_take_object (
155 				value,
156 				e_name_selector_entry_ref_client_cache (
157 				E_NAME_SELECTOR_ENTRY (object)));
158 			return;
159 
160 		case PROP_MINIMUM_QUERY_LENGTH:
161 			g_value_set_int (
162 				value,
163 				e_name_selector_entry_get_minimum_query_length (
164 				E_NAME_SELECTOR_ENTRY (object)));
165 			return;
166 
167 		case PROP_SHOW_ADDRESS:
168 			g_value_set_boolean (
169 				value,
170 				e_name_selector_entry_get_show_address (
171 				E_NAME_SELECTOR_ENTRY (object)));
172 			return;
173 	}
174 
175 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
176 }
177 
178 static void
name_selector_entry_dispose(GObject * object)179 name_selector_entry_dispose (GObject *object)
180 {
181 	ENameSelectorEntryPrivate *priv;
182 
183 	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (object);
184 
185 	gtk_editable_set_position (GTK_EDITABLE (object), 0);
186 
187 	g_clear_object (&priv->client_cache);
188 	g_clear_pointer (&priv->attr_list, pango_attr_list_unref);
189 	g_clear_object (&priv->entry_completion);
190 	g_clear_object (&priv->destination_store);
191 	g_clear_object (&priv->email_generator);
192 	g_clear_object (&priv->contact_store);
193 	g_clear_pointer (&priv->known_contacts, g_hash_table_destroy);
194 
195 	g_slist_foreach (priv->user_query_fields, (GFunc) g_free, NULL);
196 	g_slist_free (priv->user_query_fields);
197 	priv->user_query_fields = NULL;
198 
199 	/* Cancel any stuck book loading operations. */
200 	while (!g_queue_is_empty (&priv->cancellables)) {
201 		GCancellable *cancellable;
202 
203 		cancellable = g_queue_pop_head (&priv->cancellables);
204 		g_cancellable_cancel (cancellable);
205 		g_object_unref (cancellable);
206 	}
207 
208 	/* Chain up to parent's dispose() method. */
209 	G_OBJECT_CLASS (e_name_selector_entry_parent_class)->dispose (object);
210 }
211 
212 static void
name_selector_entry_constructed(GObject * object)213 name_selector_entry_constructed (GObject *object)
214 {
215 	/* Chain up to parent's constructed() method. */
216 	G_OBJECT_CLASS (e_name_selector_entry_parent_class)->constructed (object);
217 
218 	e_extensible_load_extensions (E_EXTENSIBLE (object));
219 }
220 
221 static void
name_selector_entry_realize(GtkWidget * widget)222 name_selector_entry_realize (GtkWidget *widget)
223 {
224 	ENameSelectorEntryPrivate *priv;
225 
226 	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (widget);
227 
228 	/* Chain up to parent's realize() method. */
229 	GTK_WIDGET_CLASS (e_name_selector_entry_parent_class)->realize (widget);
230 
231 	if (priv->contact_store == NULL)
232 		setup_default_contact_store (E_NAME_SELECTOR_ENTRY (widget));
233 }
234 
235 static void
name_selector_entry_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time)236 name_selector_entry_drag_data_received (GtkWidget *widget,
237                                         GdkDragContext *context,
238                                         gint x,
239                                         gint y,
240                                         GtkSelectionData *selection_data,
241                                         guint info,
242                                         guint time)
243 {
244 	CamelInternetAddress *address;
245 	gint n_addresses = 0;
246 	gchar *text;
247 
248 	address = camel_internet_address_new ();
249 	text = (gchar *) gtk_selection_data_get_text (selection_data);
250 
251 	/* See if Camel can parse a valid email address from the text. */
252 	if (text != NULL && *text != '\0') {
253 		camel_url_decode (text);
254 		if (g_ascii_strncasecmp (text, "mailto:", 7) == 0)
255 			n_addresses = camel_address_decode (
256 				CAMEL_ADDRESS (address), text + 7);
257 		else
258 			n_addresses = camel_address_decode (
259 				CAMEL_ADDRESS (address), text);
260 	}
261 
262 	if (n_addresses > 0) {
263 		GtkEditable *editable;
264 		GdkDragAction action;
265 		gboolean delete;
266 		gint position;
267 
268 		editable = GTK_EDITABLE (widget);
269 		gtk_editable_set_position (editable, -1);
270 		position = gtk_editable_get_position (editable);
271 
272 		g_free (text);
273 
274 		text = camel_address_format (CAMEL_ADDRESS (address));
275 		gtk_editable_insert_text (editable, text, -1, &position);
276 
277 		action = gdk_drag_context_get_selected_action (context);
278 		delete = (action == GDK_ACTION_MOVE);
279 		gtk_drag_finish (context, TRUE, delete, time);
280 	}
281 
282 	g_object_unref (address);
283 	g_free (text);
284 
285 	if (n_addresses <= 0)
286 		/* Chain up to parent's drag_data_received() method. */
287 		GTK_WIDGET_CLASS (e_name_selector_entry_parent_class)->
288 			drag_data_received (
289 				widget, context, x, y,
290 				selection_data, info, time);
291 }
292 
293 static void
e_name_selector_entry_class_init(ENameSelectorEntryClass * class)294 e_name_selector_entry_class_init (ENameSelectorEntryClass *class)
295 {
296 	GObjectClass *object_class;
297 	GtkWidgetClass *widget_class;
298 
299 	g_type_class_add_private (class, sizeof (ENameSelectorEntryPrivate));
300 
301 	object_class = G_OBJECT_CLASS (class);
302 	object_class->set_property = name_selector_entry_set_property;
303 	object_class->get_property = name_selector_entry_get_property;
304 	object_class->dispose = name_selector_entry_dispose;
305 	object_class->constructed = name_selector_entry_constructed;
306 
307 	widget_class = GTK_WIDGET_CLASS (class);
308 	widget_class->realize = name_selector_entry_realize;
309 	widget_class->drag_data_received = name_selector_entry_drag_data_received;
310 
311 	/**
312 	 * ENameSelectorEntry:client-cache:
313 	 *
314 	 * Cache of shared #EClient instances.
315 	 **/
316 	g_object_class_install_property (
317 		object_class,
318 		PROP_CLIENT_CACHE,
319 		g_param_spec_object (
320 			"client-cache",
321 			"Client Cache",
322 			"Cache of shared EClient instances",
323 			E_TYPE_CLIENT_CACHE,
324 			G_PARAM_READWRITE |
325 			G_PARAM_CONSTRUCT |
326 			G_PARAM_STATIC_STRINGS));
327 
328 	g_object_class_install_property (
329 		object_class,
330 		PROP_MINIMUM_QUERY_LENGTH,
331 		g_param_spec_int (
332 			"minimum-query-length",
333 			"Minimum Query Length",
334 			NULL,
335 			1, G_MAXINT,
336 			3,
337 			G_PARAM_READWRITE |
338 			G_PARAM_STATIC_STRINGS));
339 
340 	g_object_class_install_property (
341 		object_class,
342 		PROP_SHOW_ADDRESS,
343 		g_param_spec_boolean (
344 			"show-address",
345 			"Show Address",
346 			NULL,
347 			FALSE,
348 			G_PARAM_READWRITE |
349 			G_PARAM_STATIC_STRINGS));
350 
351 	signals[UPDATED] = g_signal_new (
352 		"updated",
353 		E_TYPE_NAME_SELECTOR_ENTRY,
354 		G_SIGNAL_RUN_FIRST,
355 		G_STRUCT_OFFSET (ENameSelectorEntryClass, updated),
356 		NULL, NULL,
357 		g_cclosure_marshal_VOID__POINTER,
358 		G_TYPE_NONE, 1, G_TYPE_POINTER);
359 }
360 
361 static gchar *
describe_contact(EContact * contact)362 describe_contact (EContact *contact)
363 {
364 	GString *description;
365 	const gchar *str;
366 	GList *emails, *link;
367 
368 	g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
369 
370 	emails = e_contact_get (contact, E_CONTACT_EMAIL);
371 	/* Cannot merge one contact with multiple addresses with another contact */
372 	if (!e_contact_get (contact, E_CONTACT_IS_LIST) && emails && emails->next) {
373 		deep_free_list (emails);
374 		return NULL;
375 	}
376 
377 	description = g_string_new ("");
378 
379 	if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
380 		g_string_append (description, "list\n");
381 	} else {
382 		g_string_append (description, "indv\n");
383 	}
384 
385 	str = e_contact_get_const (contact, E_CONTACT_FILE_AS);
386 	g_string_append (description, str ? str : "");
387 	g_string_append_c (description, '\n');
388 
389 	str = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
390 	g_string_append (description, str ? str : "");
391 	g_string_append_c (description, '\n');
392 
393 	emails = g_list_sort (emails, (GCompareFunc) g_ascii_strcasecmp);
394 	for (link = emails; link; link = g_list_next (link)) {
395 		str = link->data;
396 
397 		g_string_append (description, str ? str : "");
398 		g_string_append_c (description, '\n');
399 	}
400 
401 	deep_free_list (emails);
402 
403 	return g_string_free (description, FALSE);
404 }
405 
406 static gchar *
describe_contact_source(EBookClient * client,EContact * contact)407 describe_contact_source (EBookClient *client,
408 			 EContact *contact)
409 {
410 	return g_strdup_printf ("%p\n%s", client, (const gchar *) e_contact_get_const (contact, E_CONTACT_UID));
411 }
412 
413 static gboolean
is_duplicate_contact_and_remember(ENameSelectorEntry * nsentry,EBookClient * client,EContact * contact)414 is_duplicate_contact_and_remember (ENameSelectorEntry *nsentry,
415 				   EBookClient *client,
416 				   EContact *contact)
417 {
418 	gchar *description;
419 	gchar *source_ident, *known_source_ident;
420 
421 	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (nsentry), FALSE);
422 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
423 
424 	description = describe_contact (contact);
425 	if (!description) {
426 		/* Might be a contact with multiple addresses */
427 		return FALSE;
428 	}
429 
430 	source_ident = describe_contact_source (client, contact);
431 	known_source_ident = g_hash_table_lookup (nsentry->priv->known_contacts, description);
432 
433 	/* It's known, from the same book with the same UID */
434 	if (g_strcmp0 (known_source_ident, source_ident) == 0) {
435 		g_free (description);
436 		g_free (source_ident);
437 		return FALSE;
438 	}
439 
440 	/* It's known, but from a different book or with a different UID - then skip it */
441 	if (known_source_ident) {
442 		g_free (description);
443 		g_free (source_ident);
444 		return TRUE;
445 	}
446 
447 	g_hash_table_insert (nsentry->priv->known_contacts, description, source_ident);
448 
449 	return FALSE;
450 }
451 
452 /* Remove unquoted commas and control characters from string */
453 static gchar *
sanitize_string(const gchar * string)454 sanitize_string (const gchar *string)
455 {
456 	GString     *gstring;
457 	gboolean     quoted = FALSE;
458 	const gchar *p;
459 
460 	gstring = g_string_new ("");
461 
462 	if (!string)
463 		return g_string_free (gstring, FALSE);
464 
465 	for (p = string; *p; p = g_utf8_next_char (p)) {
466 		gunichar c = g_utf8_get_char (p);
467 
468 		if (c == '"')
469 			quoted = ~quoted;
470 		else if (c == ',' && !quoted)
471 			continue;
472 		else if (c == '\t' || c == '\n')
473 			continue;
474 
475 		g_string_append_unichar (gstring, c);
476 	}
477 
478 	return g_string_free (gstring, FALSE);
479 }
480 
481 /* Called for each list store entry whenever the user types (but not on cut/paste) */
482 static gboolean
completion_match_cb(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * iter,gpointer user_data)483 completion_match_cb (GtkEntryCompletion *completion,
484                      const gchar *key,
485                      GtkTreeIter *iter,
486                      gpointer user_data)
487 {
488 	ENS_DEBUG (g_print ("completion_match_cb, key=%s\n", key));
489 
490 	return TRUE;
491 }
492 
493 /* Gets context of n_unichars total (n_unicars / 2, before and after position)
494  * and places them in array. If any positions would be outside the string, the
495  * corresponding unichars are set to zero. */
496 static void
get_utf8_string_context(const gchar * string,gint position,gunichar * unichars,gint n_unichars)497 get_utf8_string_context (const gchar *string,
498                          gint position,
499                          gunichar *unichars,
500                          gint n_unichars)
501 {
502 	gchar *p = NULL;
503 	gint   len;
504 	gint   gap;
505 	gint   i;
506 
507 	/* n_unichars must be even */
508 	g_return_if_fail (n_unichars % 2 == 0);
509 
510 	len = g_utf8_strlen (string, -1);
511 	gap = n_unichars / 2;
512 
513 	for (i = 0; i < n_unichars; i++) {
514 		gint char_pos = position - gap + i;
515 
516 		if (char_pos < 0 || char_pos >= len) {
517 			unichars[i] = '\0';
518 			continue;
519 		}
520 
521 		if (p)
522 			p = g_utf8_next_char (p);
523 		else
524 			p = g_utf8_offset_to_pointer (string, char_pos);
525 
526 		unichars[i] = g_utf8_get_char (p);
527 	}
528 }
529 
530 static gboolean
get_range_at_position(const gchar * string,gint pos,gint * start_pos,gint * end_pos)531 get_range_at_position (const gchar *string,
532                        gint pos,
533                        gint *start_pos,
534                        gint *end_pos)
535 {
536 	const gchar *p;
537 	gboolean     quoted = FALSE;
538 	gint         local_start_pos = 0;
539 	gint         local_end_pos = 0;
540 	gint         i;
541 
542 	if (!string || !*string)
543 		return FALSE;
544 
545 	for (p = string, i = 0; *p; p = g_utf8_next_char (p), i++) {
546 		gunichar c = g_utf8_get_char (p);
547 
548 		if (c == '"') {
549 			quoted = ~quoted;
550 		} else if (c == ',' && !quoted) {
551 			if (i < pos) {
552 				/* Start right after comma */
553 				local_start_pos = i + 1;
554 			} else {
555 				/* Stop right before comma */
556 				local_end_pos = i;
557 				break;
558 			}
559 		} else if (c == ' ' && local_start_pos == i) {
560 			/* Adjust start to skip space after first comma */
561 			local_start_pos++;
562 		}
563 	}
564 
565 	/* If we didn't hit a comma, we must've hit NULL, and ours was the last element. */
566 	if (!local_end_pos)
567 		local_end_pos = i;
568 
569 	if (start_pos)
570 		*start_pos = local_start_pos;
571 	if (end_pos)
572 		*end_pos   = local_end_pos;
573 
574 	return TRUE;
575 }
576 
577 static gboolean
is_quoted_at(const gchar * string,gint pos)578 is_quoted_at (const gchar *string,
579               gint pos)
580 {
581 	const gchar *p;
582 	gboolean     quoted = FALSE;
583 	gint         i;
584 
585 	for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
586 		gunichar c = g_utf8_get_char (p);
587 
588 		if (c == '"')
589 			quoted = !quoted;
590 	}
591 
592 	return quoted;
593 }
594 
595 static gint
get_index_at_position(const gchar * string,gint pos)596 get_index_at_position (const gchar *string,
597                        gint pos)
598 {
599 	const gchar *p;
600 	gboolean     quoted = FALSE;
601 	gint         n = 0;
602 	gint         i;
603 
604 	for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
605 		gunichar c = g_utf8_get_char (p);
606 
607 		if (c == '"')
608 			quoted = !quoted;
609 		else if (c == ',' && !quoted)
610 			n++;
611 	}
612 
613 	return n;
614 }
615 
616 static gboolean
get_range_by_index(const gchar * string,gint index,gint * start_pos,gint * end_pos)617 get_range_by_index (const gchar *string,
618                     gint index,
619                     gint *start_pos,
620                     gint *end_pos)
621 {
622 	const gchar *p;
623 	gboolean     quoted = FALSE;
624 	gint         i;
625 	gint         n = 0;
626 
627 	for (p = string, i = 0; *p && n < index; p = g_utf8_next_char (p), i++) {
628 		gunichar c = g_utf8_get_char (p);
629 
630 		if (c == '"')
631 			quoted = ~quoted;
632 		if (c == ',' && !quoted)
633 			n++;
634 	}
635 
636 	if (n < index)
637 		return FALSE;
638 
639 	return get_range_at_position (string, i, start_pos, end_pos);
640 }
641 
642 static gchar *
get_address_at_position(const gchar * string,gint pos)643 get_address_at_position (const gchar *string,
644                          gint pos)
645 {
646 	gint         start_pos;
647 	gint         end_pos;
648 	const gchar *start_p;
649 	const gchar *end_p;
650 
651 	if (!get_range_at_position (string, pos, &start_pos, &end_pos))
652 		return NULL;
653 
654 	start_p = g_utf8_offset_to_pointer (string, start_pos);
655 	end_p = g_utf8_offset_to_pointer (string, end_pos);
656 
657 	return g_strndup (start_p, end_p - start_p);
658 }
659 
660 /* Finds the destination in model */
661 static EDestination *
find_destination_by_index(ENameSelectorEntry * name_selector_entry,gint index)662 find_destination_by_index (ENameSelectorEntry *name_selector_entry,
663                            gint index)
664 {
665 	GtkTreePath  *path;
666 	GtkTreeIter   iter;
667 
668 	path = gtk_tree_path_new_from_indices (index, -1);
669 	if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (name_selector_entry->priv->destination_store),
670 				      &iter, path)) {
671 		/* If we have zero destinations, getting a NULL destination at index 0
672 		 * is valid. */
673 		if (index > 0)
674 			g_warning ("ENameSelectorEntry is out of sync with model!");
675 		gtk_tree_path_free (path);
676 		return NULL;
677 	}
678 	gtk_tree_path_free (path);
679 
680 	return e_destination_store_get_destination (name_selector_entry->priv->destination_store, &iter);
681 }
682 
683 /* Finds the destination in model */
684 static EDestination *
find_destination_at_position(ENameSelectorEntry * name_selector_entry,gint pos)685 find_destination_at_position (ENameSelectorEntry *name_selector_entry,
686                               gint pos)
687 {
688 	const gchar  *text;
689 	gint          index;
690 
691 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
692 	index = get_index_at_position (text, pos);
693 
694 	return find_destination_by_index (name_selector_entry, index);
695 }
696 
697 /* Builds destination from our text */
698 static EDestination *
build_destination_at_position(const gchar * string,gint pos)699 build_destination_at_position (const gchar *string,
700                                gint pos)
701 {
702 	EDestination *destination;
703 	gchar        *address;
704 
705 	address = get_address_at_position (string, pos);
706 	if (!address)
707 		return NULL;
708 
709 	destination = e_destination_new ();
710 	e_destination_set_raw (destination, address);
711 
712 	g_free (address);
713 	return destination;
714 }
715 
716 static gchar *
name_style_query(const gchar * field,const gchar * value)717 name_style_query (const gchar *field,
718                   const gchar *value)
719 {
720 	gchar   *spaced_str;
721 	gchar   *comma_str;
722 	GString *out = g_string_new ("");
723 	gchar  **strv;
724 
725 	spaced_str = sanitize_string (value);
726 	g_strstrip (spaced_str);
727 
728 	strv = g_strsplit (spaced_str, " ", 0);
729 
730 	if (strv[0] && strv[1]) {
731 		g_string_append (out, "(or ");
732 		comma_str = g_strjoinv (", ", strv);
733 	} else {
734 		comma_str = NULL;
735 	}
736 
737 	g_string_append (out, " (beginswith ");
738 	e_sexp_encode_string (out, field);
739 	e_sexp_encode_string (out, spaced_str);
740 	g_string_append_c (out, ')');
741 
742 	if (comma_str) {
743 		g_string_append (out, " (beginswith ");
744 
745 		e_sexp_encode_string (out, field);
746 		g_strstrip (comma_str);
747 		e_sexp_encode_string (out, comma_str);
748 		g_string_append (out, "))");
749 	}
750 
751 	g_free (spaced_str);
752 	g_free (comma_str);
753 	g_strfreev (strv);
754 
755 	return g_string_free (out, FALSE);
756 }
757 
758 static gchar *
escape_sexp_string(const gchar * string)759 escape_sexp_string (const gchar *string)
760 {
761 	GString *gstring;
762 
763 	gstring = g_string_new ("");
764 	e_sexp_encode_string (gstring, string);
765 
766 	return g_string_free (gstring, FALSE);
767 }
768 
769 /**
770  * ens_util_populate_user_query_fields:
771  *
772  * Populates list of user query fields to string usable in query string.
773  * Returned pointer is either newly allocated string, supposed to be freed with g_free,
774  * or NULL if no fields defined.
775  *
776  * Since: 2.24
777  **/
778 gchar *
ens_util_populate_user_query_fields(GSList * user_query_fields,const gchar * cue_str,const gchar * encoded_cue_str)779 ens_util_populate_user_query_fields (GSList *user_query_fields,
780                                      const gchar *cue_str,
781                                      const gchar *encoded_cue_str)
782 {
783 	GString *user_fields;
784 	GSList *s;
785 
786 	g_return_val_if_fail (cue_str != NULL, NULL);
787 	g_return_val_if_fail (encoded_cue_str != NULL, NULL);
788 
789 	user_fields = g_string_new ("");
790 
791 	for (s = user_query_fields; s; s = s->next) {
792 		const gchar *field = s->data;
793 
794 		if (!field || !*field)
795 			continue;
796 
797 		if (*field == '$') {
798 			g_string_append_printf (user_fields, " (beginswith \"%s\" %s) ", field + 1, encoded_cue_str);
799 		} else if (*field == '@') {
800 			g_string_append_printf (user_fields, " (is \"%s\" %s) ", field + 1, encoded_cue_str);
801 		} else {
802 			gchar *tmp = name_style_query (field, cue_str);
803 
804 			g_string_append_c (user_fields, ' ');
805 			g_string_append (user_fields, tmp);
806 			g_string_append_c (user_fields, ' ');
807 			g_free (tmp);
808 		}
809 	}
810 
811 	return g_string_free (user_fields, !user_fields->str || !*user_fields->str);
812 }
813 
814 static void
set_completion_query(ENameSelectorEntry * name_selector_entry,const gchar * cue_str)815 set_completion_query (ENameSelectorEntry *name_selector_entry,
816                       const gchar *cue_str)
817 {
818 	ENameSelectorEntryPrivate *priv;
819 	EBookQuery *book_query;
820 	gchar      *query_str;
821 	gchar      *encoded_cue_str;
822 	gchar      *full_name_query_str;
823 	gchar      *file_as_query_str;
824 	gchar      *user_fields_str;
825 
826 	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
827 
828 	if (!name_selector_entry->priv->contact_store)
829 		return;
830 
831 	if (!cue_str) {
832 		/* Clear the store */
833 		e_contact_store_set_query (name_selector_entry->priv->contact_store, NULL);
834 		return;
835 	}
836 
837 	encoded_cue_str = escape_sexp_string (cue_str);
838 	full_name_query_str = name_style_query ("full_name", cue_str);
839 	file_as_query_str = name_style_query ("file_as",   cue_str);
840 	user_fields_str = ens_util_populate_user_query_fields (priv->user_query_fields, cue_str, encoded_cue_str);
841 
842 	query_str = g_strdup_printf (
843 		"(or "
844 		" (beginswith \"nickname\"  %s) "
845 		" (beginswith \"email\"     %s) "
846 		" (contains \"nickname\"  %s) "
847 		" (contains \"email\"     %s) "
848 		" %s "
849 		" %s "
850 		" %s "
851 		")",
852 		encoded_cue_str, encoded_cue_str,
853 		encoded_cue_str, encoded_cue_str,
854 		full_name_query_str, file_as_query_str,
855 		user_fields_str ? user_fields_str : "");
856 
857 	g_free (user_fields_str);
858 	g_free (file_as_query_str);
859 	g_free (full_name_query_str);
860 	g_free (encoded_cue_str);
861 
862 	ENS_DEBUG (g_print ("%s\n", query_str));
863 
864 	book_query = e_book_query_from_string (query_str);
865 	e_contact_store_set_query (name_selector_entry->priv->contact_store, book_query);
866 	e_book_query_unref (book_query);
867 
868 	g_free (query_str);
869 }
870 
871 static gchar *
get_entry_substring(ENameSelectorEntry * name_selector_entry,gint range_start,gint range_end)872 get_entry_substring (ENameSelectorEntry *name_selector_entry,
873                      gint range_start,
874                      gint range_end)
875 {
876 	const gchar *entry_text;
877 	gchar       *p0, *p1;
878 
879 	entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
880 
881 	p0 = g_utf8_offset_to_pointer (entry_text, range_start);
882 	p1 = g_utf8_offset_to_pointer (entry_text, range_end);
883 
884 	return g_strndup (p0, p1 - p0);
885 }
886 
887 static gint
utf8_casefold_collate_len(const gchar * str1,const gchar * str2,gint len)888 utf8_casefold_collate_len (const gchar *str1,
889                            const gchar *str2,
890                            gint len)
891 {
892 	gchar *s1 = g_utf8_casefold (str1, len);
893 	gchar *s2 = g_utf8_casefold (str2, len);
894 	gint rv;
895 
896 	rv = g_utf8_collate (s1, s2);
897 
898 	g_free (s1);
899 	g_free (s2);
900 
901 	return rv;
902 }
903 
904 static gchar *
build_textrep_for_contact(EContact * contact,EContactField cue_field,gint email_num)905 build_textrep_for_contact (EContact *contact,
906                            EContactField cue_field,
907                            gint email_num)
908 {
909 	gchar *name = NULL;
910 	gchar *email = NULL;
911 	gchar *textrep;
912 	GList *l;
913 
914 	switch (cue_field) {
915 		case E_CONTACT_FULL_NAME:
916 		case E_CONTACT_NICKNAME:
917 		case E_CONTACT_FILE_AS:
918 			name = e_contact_get (contact, cue_field);
919 			email = e_contact_get (contact, E_CONTACT_EMAIL_1);
920 			break;
921 
922 		case E_CONTACT_EMAIL:
923 			name = NULL;
924 			l = e_contact_get (contact, cue_field);
925 			email = g_strdup (g_list_nth_data (l, email_num));
926 			g_list_free_full (l, g_free);
927 			break;
928 
929 		default:
930 			g_return_val_if_reached (NULL);
931 			break;
932 	}
933 
934 	if (email && *email) {
935 		if (name)
936 			textrep = g_strdup_printf ("%s <%s>", name, email);
937 		else
938 			textrep = g_strdup_printf ("%s", email);
939 	} else {
940 		textrep = NULL;
941 		g_warn_if_fail (email != NULL);
942 		if (email)
943 			g_warn_if_fail (*email != '\0');
944 	}
945 
946 	g_free (name);
947 	g_free (email);
948 
949 	return textrep;
950 }
951 
952 static gboolean
contact_match_cue(ENameSelectorEntry * name_selector_entry,EContact * contact,const gchar * cue_str,EContactField * matched_field,gint * matched_field_rank,gint * matched_email_num)953 contact_match_cue (ENameSelectorEntry *name_selector_entry,
954                    EContact *contact,
955                    const gchar *cue_str,
956                    EContactField *matched_field,
957                    gint *matched_field_rank,
958                    gint *matched_email_num)
959 {
960 	EContactField  fields[] = { E_CONTACT_FULL_NAME, E_CONTACT_NICKNAME, E_CONTACT_FILE_AS,
961 				    E_CONTACT_EMAIL };
962 	gchar         *email;
963 	gboolean       result = FALSE;
964 	gint           cue_len;
965 	gint           i;
966 
967 	g_return_val_if_fail (contact, FALSE);
968 	g_return_val_if_fail (cue_str, FALSE);
969 
970 	if (g_utf8_strlen (cue_str, -1) < name_selector_entry->priv->minimum_query_length)
971 		return FALSE;
972 
973 	cue_len = strlen (cue_str);
974 
975 	/* Make sure contact has an email address */
976 	email = e_contact_get (contact, E_CONTACT_EMAIL_1);
977 	if (!email || !*email) {
978 		g_free (email);
979 		return FALSE;
980 	}
981 	g_free (email);
982 
983 	for (i = 0; i < G_N_ELEMENTS (fields) && result == FALSE; i++) {
984 		gint   email_num;
985 		gchar *value;
986 		gchar *value_sane;
987 		GList *emails = NULL, *ll = NULL;
988 
989 		/* Don't match e-mail addresses in contact lists */
990 		if (e_contact_get (contact, E_CONTACT_IS_LIST) &&
991 		    fields[i] == E_CONTACT_EMAIL)
992 			continue;
993 
994 		if (fields[i] == E_CONTACT_EMAIL) {
995 			emails = e_contact_get (contact, fields[i]);
996 		} else {
997 			value = e_contact_get (contact, fields[i]);
998 			if (!value)
999 				continue;
1000 			emails = g_list_append (emails, value);
1001 		}
1002 
1003 		for (ll = emails, email_num = 0; ll; ll = ll->next, email_num++) {
1004 			value = ll->data;
1005 			value_sane = sanitize_string (value);
1006 
1007 			ENS_DEBUG (g_print ("Comparing '%s' to '%s'\n", value, cue_str));
1008 
1009 			if (!utf8_casefold_collate_len (value_sane, cue_str, cue_len)) {
1010 				if (matched_field)
1011 					*matched_field = fields[i];
1012 				if (matched_field_rank)
1013 					*matched_field_rank = i;
1014 				if (matched_email_num)
1015 					*matched_email_num = email_num;
1016 
1017 				result = TRUE;
1018 				g_free (value_sane);
1019 				break;
1020 			}
1021 			g_free (value_sane);
1022 		}
1023 		g_list_free_full (emails, g_free);
1024 	}
1025 
1026 	return result;
1027 }
1028 
1029 static gboolean
find_existing_completion(ENameSelectorEntry * name_selector_entry,const gchar * cue_str,EContact ** contact,gchar ** text,EContactField * matched_field,gint * matched_email_num,EBookClient ** book_client)1030 find_existing_completion (ENameSelectorEntry *name_selector_entry,
1031                           const gchar *cue_str,
1032                           EContact **contact,
1033                           gchar **text,
1034                           EContactField *matched_field,
1035                           gint *matched_email_num,
1036                           EBookClient **book_client)
1037 {
1038 	GtkTreeIter    iter;
1039 	EContact      *best_contact = NULL;
1040 	gint           best_field_rank = G_MAXINT;
1041 	EContactField  best_field = 0;
1042 	gint           best_email_num = -1;
1043 	EBookClient   *best_book_client = NULL;
1044 
1045 	g_return_val_if_fail (cue_str, FALSE);
1046 
1047 	if (!name_selector_entry->priv->contact_store)
1048 		return FALSE;
1049 
1050 	ENS_DEBUG (g_print ("Completing '%s'\n", cue_str));
1051 
1052 	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->priv->contact_store), &iter))
1053 		return FALSE;
1054 
1055 	do {
1056 		EContact      *current_contact;
1057 		gint           current_field_rank = best_field_rank;
1058 		gint           current_email_num = best_email_num;
1059 		EContactField  current_field = best_field;
1060 		gboolean       matches;
1061 
1062 		current_contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &iter);
1063 		if (!current_contact)
1064 			continue;
1065 
1066 		matches = contact_match_cue (name_selector_entry, current_contact, cue_str, &current_field, &current_field_rank, &current_email_num);
1067 		if (matches && current_field_rank < best_field_rank) {
1068 			best_contact = current_contact;
1069 			best_field_rank = current_field_rank;
1070 			best_field = current_field;
1071 			best_book_client = e_contact_store_get_client (name_selector_entry->priv->contact_store, &iter);
1072 			best_email_num = current_email_num;
1073 		}
1074 
1075 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->priv->contact_store), &iter));
1076 
1077 	if (!best_contact)
1078 		return FALSE;
1079 
1080 	if (contact)
1081 		*contact = best_contact;
1082 	if (text)
1083 		*text = build_textrep_for_contact (best_contact, best_field, best_email_num);
1084 	if (matched_field)
1085 		*matched_field = best_field;
1086 	if (book_client)
1087 		*book_client = best_book_client;
1088 	if (matched_email_num)
1089 		*matched_email_num = best_email_num;
1090 	return TRUE;
1091 }
1092 
1093 static void
generate_attribute_list(ENameSelectorEntry * name_selector_entry)1094 generate_attribute_list (ENameSelectorEntry *name_selector_entry)
1095 {
1096 	PangoLayout    *layout;
1097 	PangoAttrList  *attr_list;
1098 	const gchar    *text;
1099 	gint            i;
1100 
1101 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1102 	layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
1103 
1104 	/* Set up the attribute list */
1105 
1106 	attr_list = pango_attr_list_new ();
1107 
1108 	if (name_selector_entry->priv->attr_list)
1109 		pango_attr_list_unref (name_selector_entry->priv->attr_list);
1110 
1111 	name_selector_entry->priv->attr_list = attr_list;
1112 
1113 	/* Parse the entry's text and apply attributes to real contacts */
1114 
1115 	for (i = 0; ; i++) {
1116 		EDestination   *destination;
1117 		PangoAttribute *attr;
1118 		gint            start_pos;
1119 		gint            end_pos;
1120 
1121 		if (!get_range_by_index (text, i, &start_pos, &end_pos))
1122 			break;
1123 
1124 		destination = find_destination_at_position (name_selector_entry, start_pos);
1125 
1126 		/* Destination will be NULL if we have no entries */
1127 		if (!destination || !e_destination_get_contact (destination))
1128 			continue;
1129 
1130 		attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
1131 		attr->start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
1132 		attr->end_index = g_utf8_offset_to_pointer (text, end_pos) - text;
1133 		pango_attr_list_insert (attr_list, attr);
1134 	}
1135 
1136 	pango_layout_set_attributes (layout, attr_list);
1137 }
1138 
1139 static gboolean
draw_event(ENameSelectorEntry * name_selector_entry)1140 draw_event (ENameSelectorEntry *name_selector_entry)
1141 {
1142 	PangoLayout *layout;
1143 
1144 	layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
1145 	pango_layout_set_attributes (layout, name_selector_entry->priv->attr_list);
1146 
1147 	return FALSE;
1148 }
1149 
1150 static void
type_ahead_complete(ENameSelectorEntry * name_selector_entry)1151 type_ahead_complete (ENameSelectorEntry *name_selector_entry)
1152 {
1153 	EContact      *contact;
1154 	EBookClient   *book_client = NULL;
1155 	EContactField  matched_field = E_CONTACT_FIELD_LAST;
1156 	EDestination  *destination;
1157 	gint           matched_email_num = -1;
1158 	gint           cursor_pos;
1159 	gint           range_start = 0;
1160 	gint           range_end = 0;
1161 	gint           pos = 0;
1162 	gchar         *textrep;
1163 	gint           textrep_len;
1164 	gint           range_len;
1165 	const gchar   *text;
1166 	gchar         *cue_str;
1167 	gchar         *temp_str;
1168 	ENameSelectorEntryPrivate *priv;
1169 
1170 	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1171 
1172 	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1173 	if (cursor_pos < 0)
1174 		return;
1175 
1176 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1177 	get_range_at_position (text, cursor_pos, &range_start, &range_end);
1178 	range_len = range_end - range_start;
1179 	if (range_len < priv->minimum_query_length)
1180 		return;
1181 
1182 	destination = find_destination_at_position (name_selector_entry, cursor_pos);
1183 
1184 	cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
1185 	if (!find_existing_completion (name_selector_entry, cue_str, &contact,
1186 				       &textrep, &matched_field, &matched_email_num, &book_client)) {
1187 		g_free (cue_str);
1188 		return;
1189 	}
1190 
1191 	temp_str = sanitize_string (textrep);
1192 	g_free (textrep);
1193 	textrep = temp_str;
1194 
1195 	textrep_len = g_utf8_strlen (textrep, -1);
1196 	pos = range_start;
1197 
1198 	g_signal_handlers_block_by_func (
1199 		name_selector_entry,
1200 		user_insert_text, name_selector_entry);
1201 	g_signal_handlers_block_by_func (
1202 		name_selector_entry,
1203 		user_delete_text, name_selector_entry);
1204 	g_signal_handlers_block_by_func (
1205 		name_selector_entry->priv->destination_store,
1206 		destination_row_changed, name_selector_entry);
1207 
1208 	if (textrep_len > range_len) {
1209 		gint i;
1210 
1211 		/* keep character's case as user types */
1212 		for (i = 0; textrep[i] && cue_str[i]; i++)
1213 			textrep[i] = cue_str[i];
1214 
1215 		gtk_editable_delete_text (
1216 			GTK_EDITABLE (name_selector_entry),
1217 			range_start, range_end);
1218 		gtk_editable_insert_text (
1219 			GTK_EDITABLE (name_selector_entry),
1220 			textrep, -1, &pos);
1221 		gtk_editable_select_region (
1222 			GTK_EDITABLE (name_selector_entry),
1223 			range_end, range_start + textrep_len);
1224 		priv->is_completing = TRUE;
1225 	}
1226 	g_free (cue_str);
1227 
1228 	if (contact && destination) {
1229 		gint email_n = 0;
1230 
1231 		if (matched_field == E_CONTACT_EMAIL)
1232 			email_n = matched_email_num;
1233 		e_destination_set_contact (destination, contact, email_n);
1234 		if (book_client)
1235 			e_destination_set_client (destination, book_client);
1236 		generate_attribute_list (name_selector_entry);
1237 	}
1238 
1239 	g_signal_handlers_unblock_by_func (
1240 		name_selector_entry->priv->destination_store,
1241 		destination_row_changed, name_selector_entry);
1242 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1243 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1244 
1245 	g_free (textrep);
1246 }
1247 
1248 static void
clear_completion_model(ENameSelectorEntry * name_selector_entry)1249 clear_completion_model (ENameSelectorEntry *name_selector_entry)
1250 {
1251 	ENameSelectorEntryPrivate *priv;
1252 
1253 	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1254 
1255 	if (!name_selector_entry->priv->contact_store)
1256 		return;
1257 
1258 	e_contact_store_set_query (name_selector_entry->priv->contact_store, NULL);
1259 	g_hash_table_remove_all (name_selector_entry->priv->known_contacts);
1260 	priv->is_completing = FALSE;
1261 }
1262 
1263 static void
update_completion_model(ENameSelectorEntry * name_selector_entry)1264 update_completion_model (ENameSelectorEntry *name_selector_entry)
1265 {
1266 	const gchar *text;
1267 	gint         cursor_pos;
1268 	gint         range_start = 0;
1269 	gint         range_end = 0;
1270 
1271 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1272 	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1273 
1274 	if (cursor_pos >= 0)
1275 		get_range_at_position (text, cursor_pos, &range_start, &range_end);
1276 
1277 	if (range_end - range_start >= name_selector_entry->priv->minimum_query_length && cursor_pos == range_end) {
1278 		gchar *cue_str;
1279 
1280 		cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
1281 		set_completion_query (name_selector_entry, cue_str);
1282 		g_free (cue_str);
1283 
1284 		g_hash_table_remove_all (name_selector_entry->priv->known_contacts);
1285 	} else {
1286 		/* N/A; Clear completion model */
1287 		clear_completion_model (name_selector_entry);
1288 	}
1289 }
1290 
1291 static gboolean
type_ahead_complete_on_timeout_cb(gpointer user_data)1292 type_ahead_complete_on_timeout_cb (gpointer user_data)
1293 {
1294 	ENameSelectorEntry *name_selector_entry;
1295 
1296 	name_selector_entry = E_NAME_SELECTOR_ENTRY (user_data);
1297 	type_ahead_complete (name_selector_entry);
1298 	name_selector_entry->priv->type_ahead_complete_cb_id = 0;
1299 
1300 	return FALSE;
1301 }
1302 
1303 static gboolean
update_completions_on_timeout_cb(gpointer user_data)1304 update_completions_on_timeout_cb (gpointer user_data)
1305 {
1306 	ENameSelectorEntry *name_selector_entry;
1307 
1308 	name_selector_entry = E_NAME_SELECTOR_ENTRY (user_data);
1309 	update_completion_model (name_selector_entry);
1310 	name_selector_entry->priv->update_completions_cb_id = 0;
1311 
1312 	return FALSE;
1313 }
1314 
1315 static void
insert_destination_at_position(ENameSelectorEntry * name_selector_entry,gint pos)1316 insert_destination_at_position (ENameSelectorEntry *name_selector_entry,
1317                                 gint pos)
1318 {
1319 	EDestination *destination;
1320 	const gchar  *text;
1321 	gint          index;
1322 
1323 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1324 	index = get_index_at_position (text, pos);
1325 
1326 	destination = build_destination_at_position (text, pos);
1327 	g_return_if_fail (destination);
1328 
1329 	g_signal_handlers_block_by_func (
1330 		name_selector_entry->priv->destination_store,
1331 		destination_row_inserted, name_selector_entry);
1332 	e_destination_store_insert_destination (
1333 		name_selector_entry->priv->destination_store,
1334 						index, destination);
1335 	g_signal_handlers_unblock_by_func (
1336 		name_selector_entry->priv->destination_store,
1337 		destination_row_inserted, name_selector_entry);
1338 	g_object_unref (destination);
1339 }
1340 
1341 static gboolean
modify_destination_at_position(ENameSelectorEntry * name_selector_entry,gint pos)1342 modify_destination_at_position (ENameSelectorEntry *name_selector_entry,
1343                                 gint pos)
1344 {
1345 	EDestination *destination;
1346 	const gchar  *text;
1347 	gchar        *raw_address;
1348 	gboolean      rebuild_attributes = FALSE;
1349 
1350 	destination = find_destination_at_position (name_selector_entry, pos);
1351 	if (!destination)
1352 		return FALSE;
1353 
1354 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1355 	raw_address = get_address_at_position (text, pos);
1356 	g_return_val_if_fail (raw_address, FALSE);
1357 
1358 	if (e_destination_get_contact (destination))
1359 		rebuild_attributes = TRUE;
1360 
1361 	g_signal_handlers_block_by_func (
1362 		name_selector_entry->priv->destination_store,
1363 		destination_row_changed, name_selector_entry);
1364 	e_destination_set_raw (destination, raw_address);
1365 	g_signal_handlers_unblock_by_func (
1366 		name_selector_entry->priv->destination_store,
1367 		destination_row_changed, name_selector_entry);
1368 
1369 	g_free (raw_address);
1370 
1371 	if (rebuild_attributes)
1372 		generate_attribute_list (name_selector_entry);
1373 
1374 	return TRUE;
1375 }
1376 
1377 static gchar *
get_destination_textrep(ENameSelectorEntry * name_selector_entry,EDestination * destination)1378 get_destination_textrep (ENameSelectorEntry *name_selector_entry,
1379                          EDestination *destination)
1380 {
1381 	gboolean show_email = e_name_selector_entry_get_show_address (name_selector_entry);
1382 	EContact *contact;
1383 
1384 	g_return_val_if_fail (destination != NULL, NULL);
1385 
1386 	contact = e_destination_get_contact (destination);
1387 
1388 	if (!show_email) {
1389 		if (contact && !e_contact_get (contact, E_CONTACT_IS_LIST)) {
1390 			GList *email_list;
1391 
1392 			email_list = e_contact_get (contact, E_CONTACT_EMAIL);
1393 			show_email = g_list_length (email_list) > 1;
1394 			deep_free_list (email_list);
1395 		}
1396 	}
1397 
1398 	/* do not show emails for contact lists even user forces it */
1399 	if (show_email && contact && e_contact_get (contact, E_CONTACT_IS_LIST))
1400 		show_email = FALSE;
1401 
1402 	return sanitize_string (e_destination_get_textrep (destination, show_email));
1403 }
1404 
1405 static void
sync_destination_at_position(ENameSelectorEntry * name_selector_entry,gint range_pos,gint * cursor_pos)1406 sync_destination_at_position (ENameSelectorEntry *name_selector_entry,
1407                               gint range_pos,
1408                               gint *cursor_pos)
1409 {
1410 	EDestination *destination;
1411 	const gchar  *text;
1412 	gchar        *address;
1413 	gint          address_len;
1414 	gint          range_start, range_end;
1415 	gint sel_start_pos = -1, sel_end_pos = -1;
1416 
1417 	/* Get the destination we're looking at. Note that the entry may be empty, and so
1418 	 * there may not be one. */
1419 	destination = find_destination_at_position (name_selector_entry, range_pos);
1420 	if (!destination)
1421 		return;
1422 
1423 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1424 	if (!get_range_at_position (text, range_pos, &range_start, &range_end)) {
1425 		g_warning ("ENameSelectorEntry is out of sync with model!");
1426 		return;
1427 	}
1428 
1429 	address = get_destination_textrep (name_selector_entry, destination);
1430 	address_len = g_utf8_strlen (address, -1);
1431 
1432 	if (cursor_pos) {
1433 		/* Update cursor placement */
1434 		if (*cursor_pos >= range_end)
1435 			*cursor_pos += address_len - (range_end - range_start);
1436 		else if (*cursor_pos > range_start)
1437 			*cursor_pos = range_start + address_len;
1438 	}
1439 
1440 	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1441 	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1442 
1443 	gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), &sel_start_pos, &sel_end_pos);
1444 
1445 	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1446 	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), address, -1, &range_start);
1447 
1448 	if (sel_start_pos >= 0 && sel_end_pos >= 0)
1449 		gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), sel_start_pos, sel_end_pos);
1450 
1451 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1452 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1453 
1454 	generate_attribute_list (name_selector_entry);
1455 	g_free (address);
1456 }
1457 
1458 static void
remove_destination_by_index(ENameSelectorEntry * name_selector_entry,gint index)1459 remove_destination_by_index (ENameSelectorEntry *name_selector_entry,
1460                              gint index)
1461 {
1462 	EDestination *destination;
1463 
1464 	destination = find_destination_by_index (name_selector_entry, index);
1465 	if (destination) {
1466 		g_signal_handlers_block_by_func (
1467 			name_selector_entry->priv->destination_store,
1468 			destination_row_deleted, name_selector_entry);
1469 		e_destination_store_remove_destination (
1470 			name_selector_entry->priv->destination_store,
1471 						destination);
1472 		g_signal_handlers_unblock_by_func (
1473 			name_selector_entry->priv->destination_store,
1474 			destination_row_deleted, name_selector_entry);
1475 	}
1476 }
1477 
1478 static void
post_insert_update(ENameSelectorEntry * name_selector_entry,gint position)1479 post_insert_update (ENameSelectorEntry *name_selector_entry,
1480                     gint position)
1481 {
1482 	const gchar *text;
1483 	glong length;
1484 
1485 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1486 	length = g_utf8_strlen (text, -1);
1487 	text = g_utf8_next_char (text);
1488 
1489 	if (!*text)
1490 		position = 0;
1491 
1492 	/* Modified an existing destination or add a new. */
1493 	if (!*text || !modify_destination_at_position (name_selector_entry, position)) {
1494 		/* Create destination when it's the only character or when modify failed. */
1495 		insert_destination_at_position (name_selector_entry, position);
1496 	}
1497 
1498 	/* If editing within the string, regenerate attributes. */
1499 	if (position < length)
1500 		generate_attribute_list (name_selector_entry);
1501 }
1502 
1503 /* Returns the number of characters inserted */
1504 static gint
insert_unichar(ENameSelectorEntry * name_selector_entry,gint * pos,gunichar c)1505 insert_unichar (ENameSelectorEntry *name_selector_entry,
1506                 gint *pos,
1507                 gunichar c)
1508 {
1509 	const gchar *text;
1510 	gunichar     str_context[4];
1511 	gchar        buf[7];
1512 	gint         len;
1513 
1514 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1515 	get_utf8_string_context (text, *pos, str_context, 4);
1516 
1517 	/* Space is not allowed:
1518 	 * - Before or after another space.
1519 	 * - At start of string. */
1520 
1521 	if (c == ' ' && (str_context[1] == ' ' || str_context[1] == '\0' || str_context[2] == ' '))
1522 		return 0;
1523 
1524 	/* Comma is not allowed:
1525 	 * - After another comma.
1526 	 * - At start of string. */
1527 
1528 	if (c == ',' && !is_quoted_at (text, *pos)) {
1529 		gint         start_pos;
1530 		gint         end_pos;
1531 		gboolean     at_start = FALSE;
1532 		gboolean     at_end = FALSE;
1533 
1534 		if (str_context[1] == ',' || str_context[1] == '\0')
1535 			return 0;
1536 
1537 		/* We do this so we can avoid disturbing destinations with completed contacts
1538 		 * either before or after the destination being inserted. */
1539 		if (!get_range_at_position (text, *pos, &start_pos, &end_pos))
1540 			return 0;
1541 		if (*pos <= start_pos)
1542 			at_start = TRUE;
1543 		if (*pos >= end_pos)
1544 			at_end = TRUE;
1545 
1546 		/* Must insert comma first, so modify_destination_at_position can do its job
1547 		 * correctly, splitting up the contact if necessary. */
1548 		gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, pos);
1549 
1550 		/* Update model */
1551 		g_return_val_if_fail (*pos >= 2, 0);
1552 
1553 		/* If we inserted the comma at the end of, or in the middle of, an existing
1554 		 * address, add a new destination for what appears after comma. Else, we
1555 		 * have to add a destination for what appears before comma (a blank one). */
1556 		if (at_end) {
1557 			/* End: Add last, sync first */
1558 			insert_destination_at_position (name_selector_entry, *pos);
1559 			sync_destination_at_position (name_selector_entry, *pos - 2, pos);
1560 			/* Sync generates the attributes list */
1561 		} else if (at_start) {
1562 			/* Start: Add first */
1563 			insert_destination_at_position (name_selector_entry, *pos - 2);
1564 			generate_attribute_list (name_selector_entry);
1565 		} else {
1566 			/* Middle: */
1567 			insert_destination_at_position (name_selector_entry, *pos);
1568 			modify_destination_at_position (name_selector_entry, *pos - 2);
1569 			generate_attribute_list (name_selector_entry);
1570 		}
1571 
1572 		return 2;
1573 	}
1574 
1575 	/* Generic case. Allowed spaces also end up here. */
1576 
1577 	len = g_unichar_to_utf8 (c, buf);
1578 	buf[len] = '\0';
1579 
1580 	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), buf, -1, pos);
1581 
1582 	post_insert_update (name_selector_entry, *pos);
1583 
1584 	return 1;
1585 }
1586 
1587 static void
user_insert_text(ENameSelectorEntry * name_selector_entry,gchar * new_text,gint new_text_length,gint * position,gpointer user_data)1588 user_insert_text (ENameSelectorEntry *name_selector_entry,
1589                   gchar *new_text,
1590                   gint new_text_length,
1591                   gint *position,
1592                   gpointer user_data)
1593 {
1594 	gint chars_inserted = 0;
1595 	gboolean fast_insert, has_focus;
1596 
1597 	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1598 	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1599 
1600 	fast_insert =
1601 		(g_utf8_strchr (new_text, new_text_length, ' ') == NULL) &&
1602 		(g_utf8_strchr (new_text, new_text_length, ',') == NULL) &&
1603 		(g_utf8_strchr (new_text, new_text_length, '\t') == NULL) &&
1604 		(g_utf8_strchr (new_text, new_text_length, '\n') == NULL);
1605 
1606 	has_focus = gtk_widget_has_focus (GTK_WIDGET (name_selector_entry));
1607 
1608 	if (!has_focus && *position && *position == gtk_entry_get_text_length (GTK_ENTRY (name_selector_entry))) {
1609 		gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", 2, position);
1610 		insert_destination_at_position (name_selector_entry, *position);
1611 	}
1612 
1613 	/* If the text to insert does not contain spaces or commas,
1614 	 * insert all of it at once.  This avoids confusing on-going
1615 	 * input method behavior. */
1616 	if (fast_insert) {
1617 		gint old_position = *position;
1618 
1619 		gtk_editable_insert_text (
1620 			GTK_EDITABLE (name_selector_entry),
1621 			new_text, new_text_length, position);
1622 
1623 		chars_inserted = *position - old_position;
1624 		if (chars_inserted > 0)
1625 			post_insert_update (name_selector_entry, *position);
1626 
1627 	/* Otherwise, apply some rules as to where spaces and commas
1628 	 * can be inserted, and insert a trailing space after comma. */
1629 	} else {
1630 		const gchar *cp;
1631 		gboolean last_was_comma = FALSE;
1632 
1633 		for (cp = new_text; *cp; cp = g_utf8_next_char (cp)) {
1634 			gunichar uc = g_utf8_get_char (cp);
1635 
1636 			if (uc == '\n' || uc == '\t') {
1637 				if (last_was_comma)
1638 					continue;
1639 				last_was_comma = TRUE;
1640 				uc = ',';
1641 			} else if (uc == '\r') {
1642 				continue;
1643 			} else {
1644 				last_was_comma = uc == ',';
1645 			}
1646 
1647 			insert_unichar (name_selector_entry, position, uc);
1648 			chars_inserted++;
1649 		}
1650 	}
1651 
1652 	if (chars_inserted >= 1 && has_focus) {
1653 		/* If the user inserted one character, kick off completion */
1654 		re_set_timeout (
1655 			name_selector_entry->priv->update_completions_cb_id,
1656 			update_completions_on_timeout_cb,  name_selector_entry,
1657 			AUTOCOMPLETE_TIMEOUT);
1658 		re_set_timeout (
1659 			name_selector_entry->priv->type_ahead_complete_cb_id,
1660 			type_ahead_complete_on_timeout_cb, name_selector_entry,
1661 			AUTOCOMPLETE_TIMEOUT);
1662 	}
1663 
1664 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1665 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1666 
1667 	g_signal_stop_emission_by_name (name_selector_entry, "insert_text");
1668 }
1669 
1670 static void
user_delete_text(ENameSelectorEntry * name_selector_entry,gint start_pos,gint end_pos,gpointer user_data)1671 user_delete_text (ENameSelectorEntry *name_selector_entry,
1672                   gint start_pos,
1673                   gint end_pos,
1674                   gpointer user_data)
1675 {
1676 	const gchar *text;
1677 	gint         index_start, index_end;
1678 	gint	     selection_start, selection_end;
1679 	gunichar     str_context[2], str_b_context[2];
1680 	gint         len;
1681 	gint         i;
1682 	gboolean     del_comma = FALSE;
1683 
1684 	if (start_pos == end_pos)
1685 		return;
1686 
1687 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1688 	len = g_utf8_strlen (text, -1);
1689 
1690 	if (end_pos == -1)
1691 		end_pos = len;
1692 
1693 	gtk_editable_get_selection_bounds (
1694 		GTK_EDITABLE (name_selector_entry),
1695 		&selection_start, &selection_end);
1696 
1697 	get_utf8_string_context (text, start_pos, str_context, 2);
1698 	get_utf8_string_context (text, end_pos, str_b_context, 2);
1699 
1700 	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1701 
1702 	if (end_pos - start_pos == 1) {
1703 		/* Might be backspace; update completion model so dropdown is accurate */
1704 		re_set_timeout (
1705 			name_selector_entry->priv->update_completions_cb_id,
1706 			update_completions_on_timeout_cb, name_selector_entry,
1707 			AUTOCOMPLETE_TIMEOUT);
1708 	}
1709 
1710 	index_start = get_index_at_position (text, start_pos);
1711 	index_end = get_index_at_position (text, end_pos);
1712 
1713 	g_signal_stop_emission_by_name (name_selector_entry, "delete_text");
1714 
1715 	/* If the deletion touches more than one destination, the first one is changed
1716 	 * and the rest are removed. If the last destination wasn't completely deleted,
1717 	 * it becomes part of the first one, since the separator between them was
1718 	 * removed.
1719 	 *
1720 	 * Here, we let the model know about removals. */
1721 	for (i = index_end; i > index_start; i--) {
1722 		EDestination *destination = find_destination_by_index (name_selector_entry, i);
1723 		gint range_start, range_end;
1724 		gchar *ttext;
1725 		const gchar *email = NULL;
1726 		gboolean sel = FALSE;
1727 
1728 		if (destination)
1729 			email = e_destination_get_textrep (destination, TRUE);
1730 
1731 		if (!email || !*email)
1732 			continue;
1733 
1734 		if (!get_range_by_index (text, i, &range_start, &range_end)) {
1735 			g_warning ("ENameSelectorEntry is out of sync with model!");
1736 			return;
1737 		}
1738 
1739 		if ((selection_start < range_start && selection_end > range_start) ||
1740 		    (selection_end > range_start && selection_end < range_end))
1741 			sel = TRUE;
1742 
1743 		if (!sel) {
1744 			g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1745 			g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1746 
1747 			gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1748 
1749 			ttext = sanitize_string (email);
1750 			gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
1751 			g_free (ttext);
1752 
1753 			g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1754 			g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1755 
1756 		}
1757 
1758 		remove_destination_by_index (name_selector_entry, i);
1759 	}
1760 
1761 	/* Do the actual deletion */
1762 
1763 	if (end_pos == start_pos +1 && index_end == index_start + 1) {
1764 		/* We could be just deleting the empty text */
1765 		gchar *c;
1766 
1767 		/* Get the actual deleted text */
1768 		c = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), start_pos, start_pos + 1);
1769 
1770 		if (c && c[0] == ',' && !is_quoted_at (text, start_pos)) {
1771 			/* If we are at the beginning or removing junk space, let us ignore it */
1772 			del_comma = TRUE;
1773 		}
1774 		g_free (c);
1775 	}
1776 
1777 	if (del_comma) {
1778 		gint range_start=-1, range_end;
1779 		EDestination *dest = find_destination_by_index (name_selector_entry, index_end);
1780 		/* If we have deleted the last comma, let us autocomplete normally
1781 		 */
1782 
1783 		if (dest && len - end_pos != 0) {
1784 
1785 			EDestination *destination1 = find_destination_by_index (name_selector_entry, index_start);
1786 			gchar *ttext;
1787 			const gchar *email = NULL;
1788 
1789 			if (destination1)
1790 				email = e_destination_get_textrep (destination1, TRUE);
1791 
1792 			if (email && *email) {
1793 
1794 				if (!get_range_by_index (text, i, &range_start, &range_end)) {
1795 					g_warning ("ENameSelectorEntry is out of sync with model!");
1796 					return;
1797 				}
1798 
1799 				g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1800 				g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1801 
1802 				gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1803 
1804 				ttext = sanitize_string (email);
1805 				gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
1806 				g_free (ttext);
1807 
1808 				g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1809 				g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1810 			}
1811 
1812 			if (range_start != -1) {
1813 				start_pos = range_start;
1814 				end_pos = start_pos + 1;
1815 				gtk_editable_set_position (GTK_EDITABLE (name_selector_entry),start_pos);
1816 			}
1817 		}
1818 	}
1819 	gtk_editable_delete_text (
1820 		GTK_EDITABLE (name_selector_entry),
1821 		start_pos, end_pos);
1822 
1823 	/*If the user is deleting a '"' new destinations have to be created for ',' between the quoted text
1824 	 Like "fd,ty,uy" is a one entity, but if you remove the quotes it has to be broken doan into 3 seperate
1825 	 addresses.
1826 	*/
1827 
1828 	if (str_b_context[1] == '"') {
1829 		const gchar *p;
1830 		gint j;
1831 		p = text + end_pos;
1832 		for (p = text + (end_pos - 1), j = end_pos - 1; *p && *p != '"' ; p = g_utf8_next_char (p), j++) {
1833 			gunichar c = g_utf8_get_char (p);
1834 			if (c == ',') {
1835 				insert_destination_at_position (name_selector_entry, j + 1);
1836 			}
1837 		}
1838 
1839 	}
1840 
1841 	/* Let model know about changes */
1842 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1843 	if (!*text || strlen (text) <= 0) {
1844 		/* If the entry was completely cleared, remove the initial destination too */
1845 		remove_destination_by_index (name_selector_entry, 0);
1846 		generate_attribute_list (name_selector_entry);
1847 	} else {
1848 		modify_destination_at_position (name_selector_entry, start_pos);
1849 	}
1850 
1851 	/* If editing within the string, we need to regenerate attributes */
1852 	if (end_pos < len)
1853 		generate_attribute_list (name_selector_entry);
1854 
1855 	/* Prevent type-ahead completion */
1856 	if (name_selector_entry->priv->type_ahead_complete_cb_id) {
1857 		g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
1858 		name_selector_entry->priv->type_ahead_complete_cb_id = 0;
1859 	}
1860 
1861 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1862 }
1863 
1864 static gboolean
completion_match_selected(ENameSelectorEntry * name_selector_entry,ETreeModelGenerator * email_generator_model,GtkTreeIter * generator_iter)1865 completion_match_selected (ENameSelectorEntry *name_selector_entry,
1866                            ETreeModelGenerator *email_generator_model,
1867                            GtkTreeIter *generator_iter)
1868 {
1869 	EContact      *contact;
1870 	EBookClient   *book_client;
1871 	EDestination  *destination;
1872 	gint           cursor_pos;
1873 	GtkTreeIter    contact_iter;
1874 	gint           email_n;
1875 
1876 	if (!name_selector_entry->priv->contact_store)
1877 		return FALSE;
1878 
1879 	g_return_val_if_fail (name_selector_entry->priv->email_generator == email_generator_model, FALSE);
1880 
1881 	e_tree_model_generator_convert_iter_to_child_iter (
1882 		email_generator_model,
1883 		&contact_iter, &email_n,
1884 		generator_iter);
1885 
1886 	contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_iter);
1887 	book_client = e_contact_store_get_client (name_selector_entry->priv->contact_store, &contact_iter);
1888 	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1889 
1890 	/* Set the contact in the model's destination */
1891 
1892 	destination = find_destination_at_position (name_selector_entry, cursor_pos);
1893 	e_destination_set_contact (destination, contact, email_n);
1894 	if (book_client)
1895 		e_destination_set_client (destination, book_client);
1896 	sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
1897 
1898 	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1899 	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &cursor_pos);
1900 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1901 
1902 	/*Add destination at end for next entry*/
1903 	insert_destination_at_position (name_selector_entry, cursor_pos);
1904 	/* Place cursor at end of address */
1905 
1906 	gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), cursor_pos);
1907 	g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
1908 	return TRUE;
1909 }
1910 
1911 static void
entry_activate(ENameSelectorEntry * name_selector_entry)1912 entry_activate (ENameSelectorEntry *name_selector_entry)
1913 {
1914 	gint         cursor_pos;
1915 	gint         range_start, range_end;
1916 	ENameSelectorEntryPrivate *priv;
1917 	EDestination  *destination;
1918 	gint           range_len;
1919 	const gchar   *text;
1920 	gchar         *cue_str;
1921 
1922 	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1923 	if (cursor_pos < 0)
1924 		return;
1925 
1926 	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1927 
1928 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1929 	if (!get_range_at_position (text, cursor_pos, &range_start, &range_end))
1930 		return;
1931 
1932 	range_len = range_end - range_start;
1933 	if (range_len < priv->minimum_query_length)
1934 		return;
1935 
1936 	destination = find_destination_at_position (name_selector_entry, cursor_pos);
1937 	if (!destination)
1938 		return;
1939 
1940 	cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
1941 #if 0
1942 	if (!find_existing_completion (name_selector_entry, cue_str, &contact,
1943 				       &textrep, &matched_field)) {
1944 		g_free (cue_str);
1945 		return;
1946 	}
1947 #endif
1948 	g_free (cue_str);
1949 	sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
1950 
1951 	/* Place cursor at end of address */
1952 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1953 	get_range_at_position (text, cursor_pos, &range_start, &range_end);
1954 
1955 	if (priv->is_completing) {
1956 		gchar *str_context = NULL;
1957 
1958 		str_context = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), range_end, range_end + 1);
1959 
1960 		if (str_context[0] != ',') {
1961 			/* At the end*/
1962 			gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &range_end);
1963 		} else {
1964 			/* In the middle */
1965 			gint newpos = strlen (text);
1966 
1967                         /* Doing this we can make sure that It wont ask for completion again. */
1968 			gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &newpos);
1969 			g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1970 			gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), newpos - 2, newpos);
1971 			g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1972 
1973 			/* Move it close to next destination*/
1974 			range_end = range_end + 2;
1975 
1976 		}
1977 		g_free (str_context);
1978 	}
1979 
1980 	/* Set the position only if is completing or nothing is selected, because it also deselects any selection. */
1981 	if (priv->is_completing || !gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), NULL, NULL))
1982 		gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), range_end);
1983 
1984 	g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
1985 
1986 	if (priv->is_completing)
1987 		clear_completion_model (name_selector_entry);
1988 }
1989 
1990 static void
update_text(ENameSelectorEntry * name_selector_entry,const gchar * text)1991 update_text (ENameSelectorEntry *name_selector_entry,
1992              const gchar *text)
1993 {
1994 	gint start = -1, end = -1;
1995 
1996 	gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), &start, &end);
1997 
1998 	gtk_entry_set_text (GTK_ENTRY (name_selector_entry), text);
1999 
2000 	if (start >= 0 && end >= 0)
2001 		gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), start, end);
2002 }
2003 
2004 static void
sanitize_entry(ENameSelectorEntry * name_selector_entry)2005 sanitize_entry (ENameSelectorEntry *name_selector_entry)
2006 {
2007 	gint n;
2008 	GList *l, *known, *del = NULL;
2009 	GString *str = g_string_new ("");
2010 
2011 	g_signal_handlers_block_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2012 	g_signal_handlers_block_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2013 
2014 	known = e_destination_store_list_destinations (name_selector_entry->priv->destination_store);
2015 	for (l = known, n = 0; l != NULL; l = l->next, n++) {
2016 		EDestination *dest = l->data;
2017 
2018 		if (!dest || !e_destination_get_address (dest))
2019 			del = g_list_prepend (del, GINT_TO_POINTER (n));
2020 		else {
2021 			gchar *text;
2022 
2023 			text = get_destination_textrep (name_selector_entry, dest);
2024 			if (text) {
2025 				if (str->str && str->str[0])
2026 					g_string_append (str, ", ");
2027 
2028 				g_string_append (str, text);
2029 			}
2030 			g_free (text);
2031 		}
2032 	}
2033 	g_list_free (known);
2034 
2035 	for (l = del; l != NULL; l = l->next) {
2036 		e_destination_store_remove_destination_nth (name_selector_entry->priv->destination_store, GPOINTER_TO_INT (l->data));
2037 	}
2038 	g_list_free (del);
2039 
2040 	update_text (name_selector_entry, str->str);
2041 
2042 	g_string_free (str, TRUE);
2043 
2044 	g_signal_handlers_unblock_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2045 	g_signal_handlers_unblock_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2046 
2047 	generate_attribute_list (name_selector_entry);
2048 }
2049 
2050 static void
maybe_block_entry_changed_cb(ENameSelectorEntry * name_selector_entry,gpointer user_data)2051 maybe_block_entry_changed_cb (ENameSelectorEntry *name_selector_entry,
2052 			      gpointer user_data)
2053 {
2054 	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
2055 
2056 	if (name_selector_entry->priv->block_entry_changed_signal)
2057 		g_signal_stop_emission_by_name (name_selector_entry, "changed");
2058 }
2059 
2060 static gboolean
user_focus_in(ENameSelectorEntry * name_selector_entry,GdkEventFocus * event_focus)2061 user_focus_in (ENameSelectorEntry *name_selector_entry,
2062                GdkEventFocus *event_focus)
2063 {
2064 	gint n;
2065 	GList *l, *known;
2066 	GString *str = g_string_new ("");
2067 	gint sel_start_pos = -1, sel_end_pos = -1;
2068 
2069 	/* To not send fake 'changed' signals, which can influence message composer */
2070 	name_selector_entry->priv->block_entry_changed_signal = TRUE;
2071 	g_signal_handlers_block_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2072 	g_signal_handlers_block_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2073 
2074 	known = e_destination_store_list_destinations (name_selector_entry->priv->destination_store);
2075 	for (l = known, n = 0; l != NULL; l = l->next, n++) {
2076 		EDestination *dest = l->data;
2077 
2078 		if (dest) {
2079 			gchar *text;
2080 
2081 			text = get_destination_textrep (name_selector_entry, dest);
2082 			if (text) {
2083 				if (str->str && str->str[0])
2084 					g_string_append (str, ", ");
2085 
2086 				g_string_append (str, text);
2087 			}
2088 			g_free (text);
2089 		}
2090 	}
2091 	g_list_free (known);
2092 
2093 	if (str->len < 2 || (str->str && str->str[str->len - 1] != ' ' && str->str[str->len - 2] != ',')) {
2094 		EDestination *dest_dummy = e_destination_new ();
2095 
2096 		/* Add a blank destination */
2097 		e_destination_store_append_destination (name_selector_entry->priv->destination_store, dest_dummy);
2098 		if (str->str && str->str[0])
2099 			g_string_append (str, ", ");
2100 
2101 		g_clear_object (&dest_dummy);
2102 	}
2103 
2104 	gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), &sel_start_pos, &sel_end_pos);
2105 
2106 	gtk_entry_set_text (GTK_ENTRY (name_selector_entry), str->str);
2107 
2108 	if (sel_start_pos >= 0 && sel_end_pos >= 0)
2109 		gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), sel_start_pos, sel_end_pos);
2110 
2111 	g_string_free (str, TRUE);
2112 
2113 	g_signal_handlers_unblock_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2114 	g_signal_handlers_unblock_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2115 	name_selector_entry->priv->block_entry_changed_signal = FALSE;
2116 
2117 	generate_attribute_list (name_selector_entry);
2118 
2119 	return FALSE;
2120 }
2121 
2122 static gboolean
user_focus_out(ENameSelectorEntry * name_selector_entry,GdkEventFocus * event_focus)2123 user_focus_out (ENameSelectorEntry *name_selector_entry,
2124                 GdkEventFocus *event_focus)
2125 {
2126 	if (!event_focus->in) {
2127 		entry_activate (name_selector_entry);
2128 	}
2129 
2130 	if (name_selector_entry->priv->type_ahead_complete_cb_id) {
2131 		g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
2132 		name_selector_entry->priv->type_ahead_complete_cb_id = 0;
2133 	}
2134 
2135 	if (name_selector_entry->priv->update_completions_cb_id) {
2136 		g_source_remove (name_selector_entry->priv->update_completions_cb_id);
2137 		name_selector_entry->priv->update_completions_cb_id = 0;
2138 	}
2139 
2140 	clear_completion_model (name_selector_entry);
2141 
2142 	if (!event_focus->in) {
2143 		sanitize_entry (name_selector_entry);
2144 	}
2145 
2146 	return FALSE;
2147 }
2148 
2149 static gboolean
user_key_press_event_cb(ENameSelectorEntry * name_selector_entry,GdkEventKey * event_key)2150 user_key_press_event_cb (ENameSelectorEntry *name_selector_entry,
2151 			 GdkEventKey *event_key)
2152 {
2153 	gint end;
2154 
2155 	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), FALSE);
2156 	g_return_val_if_fail (event_key != NULL, FALSE);
2157 
2158 	if ((event_key->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0 &&
2159 	    event_key->keyval == GDK_KEY_comma &&
2160 	    gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), NULL, &end)) {
2161 		entry_activate (name_selector_entry);
2162 
2163 		if (name_selector_entry->priv->type_ahead_complete_cb_id) {
2164 			g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
2165 			name_selector_entry->priv->type_ahead_complete_cb_id = 0;
2166 		}
2167 
2168 		if (name_selector_entry->priv->update_completions_cb_id) {
2169 			g_source_remove (name_selector_entry->priv->update_completions_cb_id);
2170 			name_selector_entry->priv->update_completions_cb_id = 0;
2171 		}
2172 
2173 		clear_completion_model (name_selector_entry);
2174 
2175 		sanitize_entry (name_selector_entry);
2176 
2177 		gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), end, end);
2178 	}
2179 
2180 	return FALSE;
2181 }
2182 
2183 static void
deep_free_list(GList * list)2184 deep_free_list (GList *list)
2185 {
2186 	GList *l;
2187 
2188 	for (l = list; l; l = g_list_next (l))
2189 		g_free (l->data);
2190 
2191 	g_list_free (list);
2192 }
2193 
2194 /* Given a widget, determines the height that text will normally be drawn. */
2195 static guint
entry_height(GtkWidget * widget)2196 entry_height (GtkWidget *widget)
2197 {
2198 	PangoLayout *layout;
2199 	gint bound;
2200 
2201 	g_return_val_if_fail (widget != NULL, 0);
2202 
2203 	layout = gtk_widget_create_pango_layout (widget, NULL);
2204 
2205 	pango_layout_get_pixel_size (layout, NULL, &bound);
2206 
2207 	return bound;
2208 }
2209 
2210 static void
contact_layout_pixbuffer(GtkCellLayout * cell_layout,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,ENameSelectorEntry * name_selector_entry)2211 contact_layout_pixbuffer (GtkCellLayout *cell_layout,
2212                           GtkCellRenderer *cell,
2213                           GtkTreeModel *model,
2214                           GtkTreeIter *iter,
2215                           ENameSelectorEntry *name_selector_entry)
2216 {
2217 	EContact      *contact;
2218 	GtkTreeIter    generator_iter;
2219 	GtkTreeIter    contact_store_iter;
2220 	gint           email_n;
2221 	EContactPhoto *photo;
2222 	gboolean       iter_is_valid;
2223 	GdkPixbuf *pixbuf = NULL;
2224 
2225 	if (!name_selector_entry->priv->contact_store)
2226 		return;
2227 
2228 	gtk_tree_model_filter_convert_iter_to_child_iter (
2229 		GTK_TREE_MODEL_FILTER (model),
2230 		&generator_iter, iter);
2231 	iter_is_valid = e_tree_model_generator_convert_iter_to_child_iter (
2232 		name_selector_entry->priv->email_generator,
2233 		&contact_store_iter, &email_n,
2234 		&generator_iter);
2235 
2236 	if (!iter_is_valid) {
2237 		return;
2238 	}
2239 
2240 	contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_store_iter);
2241 	if (!contact) {
2242 		g_object_set (cell, "pixbuf", pixbuf, NULL);
2243 		return;
2244 	}
2245 
2246 	photo = e_contact_get (contact, E_CONTACT_PHOTO);
2247 	if (photo && photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
2248 		guint max_height = entry_height (GTK_WIDGET (name_selector_entry));
2249 		GdkPixbufLoader *loader;
2250 
2251 		loader = gdk_pixbuf_loader_new ();
2252 		if (gdk_pixbuf_loader_write (loader, (guchar *) photo->data.inlined.data, photo->data.inlined.length, NULL) &&
2253 		    gdk_pixbuf_loader_close (loader, NULL)) {
2254 			pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
2255 			if (pixbuf)
2256 				g_object_ref (pixbuf);
2257 		}
2258 		g_object_unref (loader);
2259 
2260 		if (pixbuf) {
2261 			gint w, h;
2262 			gdouble scale = 1.0;
2263 
2264 			w = gdk_pixbuf_get_width (pixbuf);
2265 			h = gdk_pixbuf_get_height (pixbuf);
2266 
2267 			if (h > w)
2268 				scale = max_height / (double) h;
2269 			else
2270 				scale = max_height / (double) w;
2271 
2272 			if (scale < 1.0) {
2273 				GdkPixbuf *tmp;
2274 
2275 				tmp = gdk_pixbuf_scale_simple (pixbuf, w * scale, h * scale, GDK_INTERP_BILINEAR);
2276 				g_object_unref (pixbuf);
2277 				pixbuf = tmp;
2278 			}
2279 
2280 		}
2281 	}
2282 
2283 	e_contact_photo_free (photo);
2284 
2285 	g_object_set (cell, "pixbuf", pixbuf, NULL);
2286 
2287 	if (pixbuf)
2288 		g_object_unref (pixbuf);
2289 }
2290 
2291 static void
contact_layout_formatter(GtkCellLayout * cell_layout,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,ENameSelectorEntry * name_selector_entry)2292 contact_layout_formatter (GtkCellLayout *cell_layout,
2293                           GtkCellRenderer *cell,
2294                           GtkTreeModel *model,
2295                           GtkTreeIter *iter,
2296                           ENameSelectorEntry *name_selector_entry)
2297 {
2298 	EContact      *contact;
2299 	GtkTreeIter    generator_iter;
2300 	GtkTreeIter    contact_store_iter;
2301 	GList         *email_list;
2302 	gchar         *string;
2303 	gchar         *file_as_str;
2304 	gchar         *email_str;
2305 	gint           email_n;
2306 	gboolean       iter_is_valid;
2307 
2308 	if (!name_selector_entry->priv->contact_store)
2309 		return;
2310 
2311 	gtk_tree_model_filter_convert_iter_to_child_iter (
2312 		GTK_TREE_MODEL_FILTER (model),
2313 		&generator_iter, iter);
2314 	iter_is_valid = e_tree_model_generator_convert_iter_to_child_iter (
2315 		name_selector_entry->priv->email_generator,
2316 		&contact_store_iter, &email_n,
2317 		&generator_iter);
2318 
2319 	if (!iter_is_valid) {
2320 		return;
2321 	}
2322 
2323 	contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_store_iter);
2324 	email_list = e_contact_get (contact, E_CONTACT_EMAIL);
2325 	email_str = g_list_nth_data (email_list, email_n);
2326 	file_as_str = e_contact_get (contact, E_CONTACT_FILE_AS);
2327 
2328 	if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
2329 		string = g_strdup_printf ("%s", file_as_str ? file_as_str : "?");
2330 	} else {
2331 		string = g_strdup_printf (
2332 			"%s%s<%s>", file_as_str ? file_as_str : "",
2333 			file_as_str ? " " : "",
2334 			email_str ? email_str : "");
2335 	}
2336 
2337 	g_free (file_as_str);
2338 	deep_free_list (email_list);
2339 
2340 	g_object_set (cell, "text", string, NULL);
2341 	g_free (string);
2342 }
2343 
2344 static gint
generate_contact_rows(EContactStore * contact_store,GtkTreeIter * iter,ENameSelectorEntry * name_selector_entry)2345 generate_contact_rows (EContactStore *contact_store,
2346                        GtkTreeIter *iter,
2347                        ENameSelectorEntry *name_selector_entry)
2348 {
2349 	EContact    *contact;
2350 	const gchar *contact_uid;
2351 	GList       *email_list;
2352 	gint         n_rows;
2353 
2354 	contact = e_contact_store_get_contact (contact_store, iter);
2355 	g_return_val_if_fail (contact != NULL, 0);
2356 
2357 	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
2358 	if (!contact_uid)
2359 		return 0;  /* Can happen with broken databases */
2360 
2361 	if (is_duplicate_contact_and_remember (name_selector_entry, e_contact_store_get_client (contact_store, iter), contact))
2362 		return 0;
2363 
2364 	if (e_contact_get (contact, E_CONTACT_IS_LIST))
2365 		return 1;
2366 
2367 	email_list = e_contact_get (contact, E_CONTACT_EMAIL);
2368 	n_rows = g_list_length (email_list);
2369 	deep_free_list (email_list);
2370 
2371 	return n_rows;
2372 }
2373 
2374 static void
ensure_type_ahead_complete_on_timeout(ENameSelectorEntry * name_selector_entry)2375 ensure_type_ahead_complete_on_timeout (ENameSelectorEntry *name_selector_entry)
2376 {
2377 	/* this is called whenever a new item is added to the model,
2378 	 * thus, to not starve when there are many matches, do not
2379 	 * postpone on each add, but show results as soon as possible */
2380 	if (!name_selector_entry->priv->type_ahead_complete_cb_id) {
2381 		re_set_timeout (
2382 			name_selector_entry->priv->type_ahead_complete_cb_id,
2383 			type_ahead_complete_on_timeout_cb, name_selector_entry,
2384 			SHOW_RESULT_TIMEOUT);
2385 	}
2386 }
2387 
2388 static void
setup_contact_store(ENameSelectorEntry * name_selector_entry)2389 setup_contact_store (ENameSelectorEntry *name_selector_entry)
2390 {
2391 	g_clear_object (&name_selector_entry->priv->email_generator);
2392 
2393 	if (name_selector_entry->priv->contact_store) {
2394 		name_selector_entry->priv->email_generator =
2395 			e_tree_model_generator_new (
2396 				GTK_TREE_MODEL (
2397 				name_selector_entry->priv->contact_store));
2398 
2399 		e_tree_model_generator_set_generate_func (
2400 			name_selector_entry->priv->email_generator,
2401 			(ETreeModelGeneratorGenerateFunc) generate_contact_rows,
2402 			name_selector_entry, NULL);
2403 
2404 		/* Assign the store to the entry completion */
2405 
2406 		gtk_entry_completion_set_model (
2407 			name_selector_entry->priv->entry_completion,
2408 			GTK_TREE_MODEL (
2409 			name_selector_entry->priv->email_generator));
2410 
2411 		/* Set up callback for incoming matches */
2412 		g_signal_connect_swapped (
2413 			name_selector_entry->priv->contact_store, "row-inserted",
2414 			G_CALLBACK (ensure_type_ahead_complete_on_timeout), name_selector_entry);
2415 	} else {
2416 		/* Remove the store from the entry completion */
2417 
2418 		gtk_entry_completion_set_model (name_selector_entry->priv->entry_completion, NULL);
2419 	}
2420 }
2421 
2422 static void
name_selector_entry_get_client_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)2423 name_selector_entry_get_client_cb (GObject *source_object,
2424                                    GAsyncResult *result,
2425                                    gpointer user_data)
2426 {
2427 	EContactStore *contact_store = user_data;
2428 	EBookClient *book_client;
2429 	EClient *client;
2430 	GError *error = NULL;
2431 
2432 	client = e_client_cache_get_client_finish (
2433 		E_CLIENT_CACHE (source_object), result, &error);
2434 
2435 	/* Sanity check. */
2436 	g_return_if_fail (
2437 		((client != NULL) && (error == NULL)) ||
2438 		((client == NULL) && (error != NULL)));
2439 
2440 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2441 		g_error_free (error);
2442 		goto exit;
2443 	}
2444 
2445 	if (error != NULL) {
2446 		g_warning ("%s: %s", G_STRFUNC, error->message);
2447 		g_error_free (error);
2448 		goto exit;
2449 	}
2450 
2451 	book_client = E_BOOK_CLIENT (client);
2452 
2453 	g_return_if_fail (E_IS_BOOK_CLIENT (book_client));
2454 	e_contact_store_add_client (contact_store, book_client);
2455 	g_object_unref (book_client);
2456 
2457  exit:
2458 	g_object_unref (contact_store);
2459 }
2460 
2461 static void
setup_default_contact_store(ENameSelectorEntry * name_selector_entry)2462 setup_default_contact_store (ENameSelectorEntry *name_selector_entry)
2463 {
2464 	EClientCache *client_cache;
2465 	ESourceRegistry *registry;
2466 	EContactStore *contact_store;
2467 	GList *list, *iter;
2468 	const gchar *extension_name;
2469 
2470 	g_return_if_fail (name_selector_entry->priv->contact_store == NULL);
2471 
2472 	/* Create a book for each completion source, and assign them to the contact store */
2473 
2474 	contact_store = e_contact_store_new ();
2475 	name_selector_entry->priv->contact_store = contact_store;
2476 
2477 	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
2478 	client_cache = e_name_selector_entry_ref_client_cache (name_selector_entry);
2479 	registry = e_client_cache_ref_registry (client_cache);
2480 
2481 	list = e_source_registry_list_enabled (registry, extension_name);
2482 
2483 	for (iter = list; iter != NULL; iter = g_list_next (iter)) {
2484 		ESource *source = E_SOURCE (iter->data);
2485 		ESourceAutocomplete *extension;
2486 		GCancellable *cancellable;
2487 		const gchar *extension_name;
2488 
2489 		extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
2490 		extension = e_source_get_extension (source, extension_name);
2491 
2492 		/* Skip non-completion address books. */
2493 		if (!e_source_autocomplete_get_include_me (extension))
2494 			continue;
2495 
2496 		cancellable = g_cancellable_new ();
2497 
2498 		g_queue_push_tail (
2499 			&name_selector_entry->priv->cancellables,
2500 			cancellable);
2501 
2502 		e_client_cache_get_client (
2503 			client_cache, source,
2504 			E_SOURCE_EXTENSION_ADDRESS_BOOK, (guint32) -1,
2505 			cancellable,
2506 			name_selector_entry_get_client_cb,
2507 			g_object_ref (contact_store));
2508 	}
2509 
2510 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
2511 
2512 	g_object_unref (registry);
2513 	g_object_unref (client_cache);
2514 
2515 	setup_contact_store (name_selector_entry);
2516 }
2517 
2518 static void
destination_row_changed(ENameSelectorEntry * name_selector_entry,GtkTreePath * path,GtkTreeIter * iter)2519 destination_row_changed (ENameSelectorEntry *name_selector_entry,
2520                          GtkTreePath *path,
2521                          GtkTreeIter *iter)
2522 {
2523 	EDestination *destination;
2524 	const gchar  *entry_text;
2525 	gchar        *text;
2526 	gint          range_start, range_end;
2527 	gint          n;
2528 
2529 	n = gtk_tree_path_get_indices (path)[0];
2530 	destination = e_destination_store_get_destination (name_selector_entry->priv->destination_store, iter);
2531 
2532 	if (!destination)
2533 		return;
2534 
2535 	g_return_if_fail (n >= 0);
2536 
2537 	entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2538 	if (!get_range_by_index (entry_text, n, &range_start, &range_end)) {
2539 		g_warning ("ENameSelectorEntry is out of sync with model!");
2540 		return;
2541 	}
2542 
2543 	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2544 	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2545 
2546 	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
2547 
2548 	text = get_destination_textrep (name_selector_entry, destination);
2549 	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &range_start);
2550 	g_free (text);
2551 
2552 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2553 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2554 
2555 	clear_completion_model (name_selector_entry);
2556 	generate_attribute_list (name_selector_entry);
2557 }
2558 
2559 static void
destination_row_inserted(ENameSelectorEntry * name_selector_entry,GtkTreePath * path,GtkTreeIter * iter)2560 destination_row_inserted (ENameSelectorEntry *name_selector_entry,
2561                           GtkTreePath *path,
2562                           GtkTreeIter *iter)
2563 {
2564 	EDestination *destination;
2565 	const gchar  *entry_text;
2566 	gchar        *text;
2567 	gboolean      comma_before = FALSE;
2568 	gboolean      comma_after = FALSE;
2569 	gint          range_start, range_end;
2570 	gint          insert_pos;
2571 	gint          n;
2572 
2573 	n = gtk_tree_path_get_indices (path)[0];
2574 	destination = e_destination_store_get_destination (name_selector_entry->priv->destination_store, iter);
2575 
2576 	g_return_if_fail (n >= 0);
2577 	g_return_if_fail (destination != NULL);
2578 
2579 	entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2580 
2581 	if (get_range_by_index (entry_text, n, &range_start, &range_end) && range_start != range_end) {
2582 		/* Another destination comes after us */
2583 		insert_pos = range_start;
2584 		comma_after = TRUE;
2585 	} else if (n > 0 && get_range_by_index (entry_text, n - 1, &range_start, &range_end)) {
2586 		/* Another destination comes before us */
2587 		insert_pos = range_end;
2588 		comma_before = TRUE;
2589 	} else if (n == 0) {
2590 		/* We're the sole destination */
2591 		insert_pos = 0;
2592 	} else {
2593 		g_warning ("ENameSelectorEntry is out of sync with model!");
2594 		return;
2595 	}
2596 
2597 	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2598 
2599 	if (comma_before)
2600 		gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
2601 
2602 	text = get_destination_textrep (name_selector_entry, destination);
2603 	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &insert_pos);
2604 	g_free (text);
2605 
2606 	if (comma_after)
2607 		gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
2608 
2609 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2610 
2611 	clear_completion_model (name_selector_entry);
2612 	generate_attribute_list (name_selector_entry);
2613 }
2614 
2615 static void
destination_row_deleted(ENameSelectorEntry * name_selector_entry,GtkTreePath * path)2616 destination_row_deleted (ENameSelectorEntry *name_selector_entry,
2617                          GtkTreePath *path)
2618 {
2619 	const gchar *text;
2620 	gboolean     deleted_comma = FALSE;
2621 	gint         range_start, range_end;
2622 	gchar       *p0;
2623 	gint         n;
2624 
2625 	n = gtk_tree_path_get_indices (path)[0];
2626 	g_return_if_fail (n >= 0);
2627 
2628 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2629 
2630 	if (!get_range_by_index (text, n, &range_start, &range_end)) {
2631 		g_warning ("ENameSelectorEntry is out of sync with model!");
2632 		return;
2633 	}
2634 
2635 	/* Expand range for deletion forwards */
2636 	for (p0 = g_utf8_offset_to_pointer (text, range_end); *p0;
2637 	     p0 = g_utf8_next_char (p0), range_end++) {
2638 		gunichar c = g_utf8_get_char (p0);
2639 
2640 		/* Gobble spaces directly after comma */
2641 		if (c != ' ' && deleted_comma) {
2642 			range_end--;
2643 			break;
2644 		}
2645 
2646 		if (c == ',') {
2647 			deleted_comma = TRUE;
2648 			range_end++;
2649 		}
2650 	}
2651 
2652 	/* Expand range for deletion backwards */
2653 	for (p0 = g_utf8_offset_to_pointer (text, range_start); range_start > 0;
2654 	     p0 = g_utf8_prev_char (p0), range_start--) {
2655 		gunichar c = g_utf8_get_char (p0);
2656 
2657 		if (c == ',') {
2658 			if (!deleted_comma) {
2659 				deleted_comma = TRUE;
2660 				break;
2661 			}
2662 
2663 			range_start++;
2664 
2665 			/* Leave a space in front; we deleted the comma and spaces before the
2666 			 * following destination */
2667 			p0 = g_utf8_next_char (p0);
2668 			c = g_utf8_get_char (p0);
2669 			if (c == ' ')
2670 				range_start++;
2671 
2672 			break;
2673 		}
2674 	}
2675 
2676 	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2677 	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
2678 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2679 
2680 	clear_completion_model (name_selector_entry);
2681 	generate_attribute_list (name_selector_entry);
2682 }
2683 
2684 static void
setup_destination_store(ENameSelectorEntry * name_selector_entry)2685 setup_destination_store (ENameSelectorEntry *name_selector_entry)
2686 {
2687 	GtkTreeIter  iter;
2688 
2689 	g_signal_connect_swapped (
2690 		name_selector_entry->priv->destination_store, "row-changed",
2691 		G_CALLBACK (destination_row_changed), name_selector_entry);
2692 	g_signal_connect_swapped (
2693 		name_selector_entry->priv->destination_store, "row-deleted",
2694 		G_CALLBACK (destination_row_deleted), name_selector_entry);
2695 	g_signal_connect_swapped (
2696 		name_selector_entry->priv->destination_store, "row-inserted",
2697 		G_CALLBACK (destination_row_inserted), name_selector_entry);
2698 
2699 	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter))
2700 		return;
2701 
2702 	do {
2703 		GtkTreePath *path;
2704 
2705 		path = gtk_tree_model_get_path (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter);
2706 		g_return_if_fail (path);
2707 
2708 		destination_row_inserted (name_selector_entry, path, &iter);
2709 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter));
2710 }
2711 
2712 static gboolean
prepare_popup_destination(ENameSelectorEntry * name_selector_entry,GdkEventButton * event_button)2713 prepare_popup_destination (ENameSelectorEntry *name_selector_entry,
2714                            GdkEventButton *event_button)
2715 {
2716 	EDestination *destination;
2717 	PangoLayout  *layout;
2718 	gint          layout_offset_x;
2719 	gint          layout_offset_y;
2720 	gint          x, y;
2721 	gint          index;
2722 
2723 	if (event_button->type != GDK_BUTTON_PRESS)
2724 		return FALSE;
2725 
2726 	if (event_button->button != 3)
2727 		return FALSE;
2728 
2729 	g_clear_object (&name_selector_entry->priv->popup_destination);
2730 
2731 	gtk_entry_get_layout_offsets (
2732 		GTK_ENTRY (name_selector_entry),
2733 		&layout_offset_x, &layout_offset_y);
2734 	x = (event_button->x + 0.5) - layout_offset_x;
2735 	y = (event_button->y + 0.5) - layout_offset_y;
2736 
2737 	if (x < 0 || y < 0)
2738 		return FALSE;
2739 
2740 	layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
2741 	if (!pango_layout_xy_to_index (layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, NULL))
2742 		return FALSE;
2743 
2744 	index = gtk_entry_layout_index_to_text_index (GTK_ENTRY (name_selector_entry), index);
2745 	destination = find_destination_at_position (name_selector_entry, index);
2746 	/* FIXME: Add this to a private variable, in ENameSelectorEntry Class*/
2747 	g_object_set_data ((GObject *) name_selector_entry, "index", GINT_TO_POINTER (index));
2748 
2749 	if (!destination || !e_destination_get_contact (destination))
2750 		return FALSE;
2751 
2752 	/* TODO: Unref destination when we finalize */
2753 	name_selector_entry->priv->popup_destination = g_object_ref (destination);
2754 	return FALSE;
2755 }
2756 
2757 static EBookClient *
find_client_by_contact(GSList * clients,const gchar * contact_uid,const gchar * source_uid)2758 find_client_by_contact (GSList *clients,
2759                         const gchar *contact_uid,
2760                         const gchar *source_uid)
2761 {
2762 	GSList *l;
2763 
2764 	if (source_uid && *source_uid) {
2765 		/* this is much quicket than asking each client for an existence */
2766 		for (l = clients; l; l = g_slist_next (l)) {
2767 			EBookClient *client = l->data;
2768 			ESource *source = e_client_get_source (E_CLIENT (client));
2769 
2770 			if (!source)
2771 				continue;
2772 
2773 			if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
2774 				return client;
2775 		}
2776 	}
2777 
2778 	for (l = clients; l; l = g_slist_next (l)) {
2779 		EBookClient *client = l->data;
2780 		EContact *contact = NULL;
2781 		gboolean  result;
2782 
2783 		result = e_book_client_get_contact_sync (client, contact_uid, &contact, NULL, NULL);
2784 		if (contact)
2785 			g_object_unref (contact);
2786 
2787 		if (result)
2788 			return client;
2789 	}
2790 
2791 	return NULL;
2792 }
2793 
2794 static void
editor_closed_cb(GtkWidget * editor,gpointer data)2795 editor_closed_cb (GtkWidget *editor,
2796                   gpointer data)
2797 {
2798 	EContact *contact;
2799 	gchar *contact_uid;
2800 	EDestination *destination;
2801 	GSList *clients;
2802 	EBookClient *book_client;
2803 	gint email_num;
2804 	ENameSelectorEntry *name_selector_entry = E_NAME_SELECTOR_ENTRY (data);
2805 
2806 	destination = name_selector_entry->priv->popup_destination;
2807 	contact = e_destination_get_contact (destination);
2808 	if (!contact) {
2809 		g_object_unref (name_selector_entry);
2810 		return;
2811 	}
2812 
2813 	contact_uid = e_contact_get (contact, E_CONTACT_UID);
2814 	if (!contact_uid) {
2815 		g_object_unref (contact);
2816 		g_object_unref (name_selector_entry);
2817 		return;
2818 	}
2819 
2820 	if (name_selector_entry->priv->contact_store) {
2821 		clients = e_contact_store_get_clients (name_selector_entry->priv->contact_store);
2822 		book_client = find_client_by_contact (clients, contact_uid, e_destination_get_source_uid (destination));
2823 		g_slist_free (clients);
2824 	} else {
2825 		book_client = NULL;
2826 	}
2827 
2828 	if (book_client) {
2829 		contact = NULL;
2830 
2831 		g_warn_if_fail (e_book_client_get_contact_sync (book_client, contact_uid, &contact, NULL, NULL));
2832 		email_num = e_destination_get_email_num (destination);
2833 		e_destination_set_contact (destination, contact, email_num);
2834 		e_destination_set_client (destination, book_client);
2835 	} else {
2836 		contact = NULL;
2837 	}
2838 
2839 	g_free (contact_uid);
2840 	if (contact)
2841 		g_object_unref (contact);
2842 	g_object_unref (name_selector_entry);
2843 }
2844 
2845 /* To parse something like...
2846  * =?UTF-8?Q?=E0=A4=95=E0=A4=95=E0=A4=AC=E0=A5=82=E0=A5=8B=E0=A5=87?=\t\n=?UTF-8?Q?=E0=A4=B0?=\t\n<aa@aa.ccom>
2847  * and return the decoded representation of name & email parts.
2848  * */
2849 static gboolean
eab_parse_qp_email(const gchar * string,gchar ** name,gchar ** email)2850 eab_parse_qp_email (const gchar *string,
2851                     gchar **name,
2852                     gchar **email)
2853 {
2854 	struct _camel_header_address *address;
2855 	gboolean res = FALSE;
2856 
2857 	address = camel_header_address_decode (string, "UTF-8");
2858 
2859 	if (!address)
2860 		return FALSE;
2861 
2862         /* report success only when we have filled both name and email address */
2863 	if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name && address->v.addr && *address->v.addr) {
2864                 *name = g_strdup (address->name);
2865                 *email = g_strdup (address->v.addr);
2866 		res = TRUE;
2867 	}
2868 
2869 	camel_header_address_unref (address);
2870 
2871 	return res;
2872 }
2873 
2874 static void
popup_activate_inline_expand(ENameSelectorEntry * name_selector_entry,GtkWidget * menu_item)2875 popup_activate_inline_expand (ENameSelectorEntry *name_selector_entry,
2876                               GtkWidget *menu_item)
2877 {
2878 	const gchar *text;
2879 	GString *sanitized_text = g_string_new ("");
2880 	EDestination *destination = name_selector_entry->priv->popup_destination;
2881 	gint position, start, end;
2882 	const GList *dests;
2883 
2884 	position = GPOINTER_TO_INT (g_object_get_data ((GObject *) name_selector_entry, "index"));
2885 
2886 	for (dests = e_destination_list_get_dests (destination); dests; dests = dests->next) {
2887 		const EDestination *dest = dests->data;
2888 		gchar *sanitized;
2889 		gchar *name = NULL, *email = NULL, *tofree = NULL;
2890 
2891 		if (!dest)
2892 			continue;
2893 
2894 		text = e_destination_get_textrep (dest, TRUE);
2895 
2896 		if (!text || !*text)
2897 			continue;
2898 
2899 		if (eab_parse_qp_email (text, &name, &email)) {
2900 			tofree = g_strdup_printf ("%s <%s>", name, email);
2901 			text = tofree;
2902 			g_free (name);
2903 			g_free (email);
2904 		}
2905 
2906 		sanitized = sanitize_string (text);
2907 		g_free (tofree);
2908 		if (!sanitized)
2909 			continue;
2910 
2911 		if (*sanitized) {
2912 			if (*sanitized_text->str)
2913 				g_string_append (sanitized_text, ", ");
2914 
2915 			g_string_append (sanitized_text, sanitized);
2916 		}
2917 
2918 		g_free (sanitized);
2919 	}
2920 
2921 	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2922 	if (get_range_at_position (text, position, &start, &end))
2923 		gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), start, end);
2924 	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), sanitized_text->str, -1, &start);
2925 	g_string_free (sanitized_text, TRUE);
2926 
2927 	clear_completion_model (name_selector_entry);
2928 	generate_attribute_list (name_selector_entry);
2929 }
2930 
2931 static void
popup_activate_contact(ENameSelectorEntry * name_selector_entry,GtkWidget * menu_item)2932 popup_activate_contact (ENameSelectorEntry *name_selector_entry,
2933                         GtkWidget *menu_item)
2934 {
2935 	EBookClient  *book_client;
2936 	GSList       *clients;
2937 	EDestination *destination;
2938 	EContact     *contact;
2939 	gchar        *contact_uid;
2940 
2941 	destination = name_selector_entry->priv->popup_destination;
2942 	if (!destination)
2943 		return;
2944 
2945 	contact = e_destination_get_contact (destination);
2946 	if (!contact)
2947 		return;
2948 
2949 	contact_uid = e_contact_get (contact, E_CONTACT_UID);
2950 	if (!contact_uid)
2951 		return;
2952 
2953 	if (name_selector_entry->priv->contact_store) {
2954 		clients = e_contact_store_get_clients (name_selector_entry->priv->contact_store);
2955 		book_client = find_client_by_contact (clients, contact_uid, e_destination_get_source_uid (destination));
2956 		g_slist_free (clients);
2957 		g_free (contact_uid);
2958 	} else {
2959 		book_client = NULL;
2960 	}
2961 
2962 	if (!book_client)
2963 		return;
2964 
2965 	if (e_destination_is_evolution_list (destination)) {
2966 		GtkWidget *contact_list_editor;
2967 
2968 		if (!name_selector_entry->priv->contact_list_editor_func)
2969 			return;
2970 
2971 		contact_list_editor = (*name_selector_entry->priv->contact_list_editor_func) (book_client, contact, FALSE, TRUE);
2972 		g_object_ref (name_selector_entry);
2973 		g_signal_connect (
2974 			contact_list_editor, "editor_closed",
2975 			G_CALLBACK (editor_closed_cb), name_selector_entry);
2976 	} else {
2977 		GtkWidget *contact_editor;
2978 
2979 		if (!name_selector_entry->priv->contact_editor_func)
2980 			return;
2981 
2982 		contact_editor = (*name_selector_entry->priv->contact_editor_func) (book_client, contact, FALSE, TRUE);
2983 		g_object_ref (name_selector_entry);
2984 		g_signal_connect (
2985 			contact_editor, "editor_closed",
2986 			G_CALLBACK (editor_closed_cb), name_selector_entry);
2987 	}
2988 }
2989 
2990 static void
popup_activate_email(ENameSelectorEntry * name_selector_entry,GtkWidget * menu_item)2991 popup_activate_email (ENameSelectorEntry *name_selector_entry,
2992                       GtkWidget *menu_item)
2993 {
2994 	EDestination *destination;
2995 	EContact     *contact;
2996 	gint          email_num;
2997 
2998 	destination = name_selector_entry->priv->popup_destination;
2999 	if (!destination)
3000 		return;
3001 
3002 	contact = e_destination_get_contact (destination);
3003 	if (!contact)
3004 		return;
3005 
3006 	email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "order"));
3007 	e_destination_set_contact (destination, contact, email_num);
3008 }
3009 
3010 static void
popup_activate_list(EDestination * destination,GtkWidget * item)3011 popup_activate_list (EDestination *destination,
3012                      GtkWidget *item)
3013 {
3014 	gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
3015 
3016 	e_destination_set_ignored (destination, !status);
3017 }
3018 
3019 static void
popup_activate_cut(ENameSelectorEntry * name_selector_entry,GtkWidget * menu_item)3020 popup_activate_cut (ENameSelectorEntry *name_selector_entry,
3021                     GtkWidget *menu_item)
3022 {
3023 	EDestination *destination;
3024 	const gchar *contact_email;
3025 	gchar *pemail = NULL;
3026 	GtkClipboard *clipboard;
3027 
3028 	destination = name_selector_entry->priv->popup_destination;
3029 	contact_email =e_destination_get_textrep (destination, TRUE);
3030 
3031 	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
3032 	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
3033 
3034 	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
3035 	pemail = g_strconcat (contact_email, ",", NULL);
3036 	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3037 
3038 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
3039 	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3040 
3041 	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), 0, 0);
3042 	e_destination_store_remove_destination (name_selector_entry->priv->destination_store, destination);
3043 
3044 	g_free (pemail);
3045 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
3046 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
3047 }
3048 
3049 static void
popup_activate_copy(ENameSelectorEntry * name_selector_entry,GtkWidget * menu_item)3050 popup_activate_copy (ENameSelectorEntry *name_selector_entry,
3051                      GtkWidget *menu_item)
3052 {
3053 	EDestination *destination;
3054 	const gchar *contact_email;
3055 	gchar *pemail;
3056 	GtkClipboard *clipboard;
3057 
3058 	destination = name_selector_entry->priv->popup_destination;
3059 	contact_email = e_destination_get_textrep (destination, TRUE);
3060 
3061 	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
3062 	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
3063 
3064 	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
3065 	pemail = g_strconcat (contact_email, ",", NULL);
3066 	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3067 
3068 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
3069 	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3070 	g_free (pemail);
3071 	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
3072 	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
3073 }
3074 
3075 static void
destination_set_list(GtkWidget * item,EDestination * destination)3076 destination_set_list (GtkWidget *item,
3077                       EDestination *destination)
3078 {
3079 	EContact *contact;
3080 	gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
3081 
3082 	contact = e_destination_get_contact (destination);
3083 	if (!contact)
3084 		return;
3085 
3086 	e_destination_set_ignored (destination, !status);
3087 }
3088 
3089 static void
destination_set_email(GtkWidget * item,EDestination * destination)3090 destination_set_email (GtkWidget *item,
3091                        EDestination *destination)
3092 {
3093 	gint email_num;
3094 	EContact *contact;
3095 
3096 	if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
3097 		return;
3098 	contact = e_destination_get_contact (destination);
3099 	if (!contact)
3100 		return;
3101 
3102 	email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "order"));
3103 	e_destination_set_contact (destination, contact, email_num);
3104 }
3105 
3106 static void
populate_popup(ENameSelectorEntry * name_selector_entry,GtkMenu * menu)3107 populate_popup (ENameSelectorEntry *name_selector_entry,
3108                 GtkMenu *menu)
3109 {
3110 	EDestination *destination;
3111 	EContact     *contact;
3112 	GtkWidget    *menu_item;
3113 	GList        *email_list = NULL;
3114 	GList        *l;
3115 	gint          i;
3116 	gchar	     *edit_label;
3117 	gchar	     *cut_label;
3118 	gchar         *copy_label;
3119 	gint	      email_num, len;
3120 	GSList	     *group = NULL;
3121 	gboolean      is_list;
3122 	gboolean      show_menu = FALSE;
3123 
3124 	destination = name_selector_entry->priv->popup_destination;
3125 	if (!destination)
3126 		return;
3127 
3128 	contact = e_destination_get_contact (destination);
3129 	if (!contact)
3130 		return;
3131 
3132 	/* Prepend the menu items, backwards */
3133 
3134 	/* Separator */
3135 
3136 	menu_item = gtk_separator_menu_item_new ();
3137 	gtk_widget_show (menu_item);
3138 	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3139 	email_num = e_destination_get_email_num (destination);
3140 
3141 	/* Addresses */
3142 	is_list = e_contact_get (contact, E_CONTACT_IS_LIST) ? TRUE : FALSE;
3143 	if (is_list) {
3144 		const GList *dests = e_destination_list_get_dests (destination);
3145 		GList *iter;
3146 		gint length = g_list_length ((GList *) dests);
3147 
3148 		for (iter = (GList *) dests; iter; iter = iter->next) {
3149 			EDestination *dest = (EDestination *) iter->data;
3150 			const gchar *email = e_destination_get_email (dest);
3151 
3152 			if (!email || *email == '\0')
3153 				continue;
3154 
3155 			if (length > 1) {
3156 				menu_item = gtk_check_menu_item_new_with_label (email);
3157 				g_signal_connect (
3158 					menu_item, "toggled",
3159 					G_CALLBACK (destination_set_list), dest);
3160 			} else {
3161 				menu_item = gtk_menu_item_new_with_label (email);
3162 			}
3163 
3164 			gtk_widget_show (menu_item);
3165 			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3166 			show_menu = TRUE;
3167 
3168 			if (length > 1) {
3169 				gtk_check_menu_item_set_active (
3170 					GTK_CHECK_MENU_ITEM (menu_item),
3171 					!e_destination_is_ignored (dest));
3172 				g_signal_connect_swapped (
3173 					menu_item, "activate",
3174 					G_CALLBACK (popup_activate_list), dest);
3175 			}
3176 		}
3177 
3178 	} else {
3179 		email_list = e_contact_get (contact, E_CONTACT_EMAIL);
3180 		len = g_list_length (email_list);
3181 
3182 		for (l = email_list, i = 0; l; l = g_list_next (l), i++) {
3183 			gchar *email = l->data;
3184 
3185 			if (!email || *email == '\0')
3186 				continue;
3187 
3188 			if (len > 1) {
3189 				menu_item = gtk_radio_menu_item_new_with_label (group, email);
3190 				group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menu_item));
3191 				g_signal_connect (menu_item, "toggled", G_CALLBACK (destination_set_email), destination);
3192 			} else {
3193 				menu_item = gtk_menu_item_new_with_label (email);
3194 			}
3195 
3196 			gtk_widget_show (menu_item);
3197 			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3198 			show_menu = TRUE;
3199 			g_object_set_data (G_OBJECT (menu_item), "order", GINT_TO_POINTER (i));
3200 
3201 			if (i == email_num && len > 1) {
3202 				gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), TRUE);
3203 				g_signal_connect_swapped (
3204 					menu_item, "activate",
3205 					G_CALLBACK (popup_activate_email),
3206 					name_selector_entry);
3207 			}
3208 		}
3209 	}
3210 
3211 	/* Separator */
3212 
3213 	if (show_menu) {
3214 		menu_item = gtk_separator_menu_item_new ();
3215 		gtk_widget_show (menu_item);
3216 		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3217 	}
3218 
3219 	/* Expand a list inline */
3220 	if (is_list) {
3221 		/* To Translators: This would be similiar to "Expand MyList Inline" where MyList is a Contact List*/
3222 		edit_label = g_strdup_printf (_("E_xpand %s Inline"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3223 		menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
3224 		g_free (edit_label);
3225 		gtk_widget_show (menu_item);
3226 		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3227 		g_signal_connect_swapped (
3228 			menu_item, "activate", G_CALLBACK (popup_activate_inline_expand),
3229 			name_selector_entry);
3230 
3231 		/* Separator */
3232 		menu_item = gtk_separator_menu_item_new ();
3233 		gtk_widget_show (menu_item);
3234 		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3235 	}
3236 
3237 	/* Copy Contact Item */
3238 	copy_label = g_strdup_printf (_("Cop_y %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3239 	menu_item = gtk_menu_item_new_with_mnemonic (copy_label);
3240 	g_free (copy_label);
3241 	gtk_widget_show (menu_item);
3242 	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3243 
3244 	g_signal_connect_swapped (
3245 		menu_item, "activate", G_CALLBACK (popup_activate_copy),
3246 		name_selector_entry);
3247 
3248 	/* Cut Contact Item */
3249 	cut_label = g_strdup_printf (_("C_ut %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3250 	menu_item = gtk_menu_item_new_with_mnemonic (cut_label);
3251 	g_free (cut_label);
3252 	gtk_widget_show (menu_item);
3253 	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3254 
3255 	g_signal_connect_swapped (
3256 		menu_item, "activate", G_CALLBACK (popup_activate_cut),
3257 		name_selector_entry);
3258 
3259 	if (show_menu) {
3260 		menu_item = gtk_separator_menu_item_new ();
3261 		gtk_widget_show (menu_item);
3262 		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3263 	}
3264 
3265 	/* Edit Contact item */
3266 
3267 	edit_label = g_strdup_printf (_("_Edit %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3268 	menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
3269 	g_free (edit_label);
3270 	gtk_widget_show (menu_item);
3271 	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3272 
3273 	g_signal_connect_swapped (
3274 		menu_item, "activate", G_CALLBACK (popup_activate_contact),
3275 		name_selector_entry);
3276 
3277 	deep_free_list (email_list);
3278 }
3279 
3280 static gint
compare_gint_ptr_cb(gconstpointer a,gconstpointer b)3281 compare_gint_ptr_cb (gconstpointer a,
3282                      gconstpointer b)
3283 {
3284 	return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b);
3285 }
3286 
3287 static void
copy_or_cut_clipboard(ENameSelectorEntry * name_selector_entry,gboolean is_cut)3288 copy_or_cut_clipboard (ENameSelectorEntry *name_selector_entry,
3289                        gboolean is_cut)
3290 {
3291 	GtkClipboard *clipboard;
3292 	GtkEditable *editable;
3293 	const gchar *text, *cp;
3294 	GHashTable *hash;
3295 	GHashTableIter iter;
3296 	gpointer key, value;
3297 	GSList *sorted, *siter;
3298 	GString *addresses;
3299 	gint ii, start, end, ostart, oend;
3300 	gunichar uc;
3301 
3302 	editable = GTK_EDITABLE (name_selector_entry);
3303 	text = gtk_entry_get_text (GTK_ENTRY (editable));
3304 
3305 	if (!gtk_editable_get_selection_bounds (editable, &start, &end))
3306 		return;
3307 
3308 	g_return_if_fail (end > start);
3309 
3310 	hash = g_hash_table_new (g_direct_hash, g_direct_equal);
3311 
3312 	/* convert from character indexes to pointer indexes */
3313 	ostart = g_utf8_offset_to_pointer (text, start) - text;
3314 	oend = g_utf8_offset_to_pointer (text, end) - text;
3315 
3316 	ii = end;
3317 	cp = g_utf8_offset_to_pointer (text, end);
3318 	uc = g_utf8_get_char (cp);
3319 
3320 	/* Exclude trailing whitespace and commas. */
3321 	while (ii >= start && (uc == ',' || g_unichar_isspace (uc))) {
3322 		cp = g_utf8_prev_char (cp);
3323 		uc = g_utf8_get_char (cp);
3324 		ii--;
3325 	}
3326 
3327 	/* Determine the index of each remaining character. */
3328 	while (ii >= start) {
3329 		gint index = get_index_at_position (text, ii--);
3330 		g_hash_table_insert (hash, GINT_TO_POINTER (index), NULL);
3331 	}
3332 
3333 	sorted = NULL;
3334 	g_hash_table_iter_init (&iter, hash);
3335 	while (g_hash_table_iter_next (&iter, &key, &value)) {
3336 		sorted = g_slist_prepend (sorted, key);
3337 	}
3338 
3339 	sorted = g_slist_sort (sorted, compare_gint_ptr_cb);
3340 	addresses = g_string_new ("");
3341 
3342 	for (siter = sorted; siter != NULL; siter = g_slist_next (siter)) {
3343 		gint index = GPOINTER_TO_INT (siter->data);
3344 		EDestination *dest;
3345 		gint rstart, rend;
3346 
3347 		if (!get_range_by_index (text, index, &rstart, &rend))
3348 			continue;
3349 
3350 		/* convert from character indexes to pointer indexes */
3351 		rstart = g_utf8_offset_to_pointer (text, rstart) - text;
3352 		rend = g_utf8_offset_to_pointer (text, rend) - text;
3353 
3354 		if (rstart < ostart) {
3355 			if (addresses->str && *addresses->str)
3356 				g_string_append (addresses, ", ");
3357 
3358 			g_string_append_len (addresses, text + ostart, MIN (oend - ostart, rend - ostart));
3359 		} else if (rend > oend) {
3360 			if (addresses->str && *addresses->str)
3361 				g_string_append (addresses, ", ");
3362 
3363 			g_string_append_len (addresses, text + rstart, oend - rstart);
3364 		} else {
3365 			/* the contact is whole selected */
3366 			dest = find_destination_by_index (name_selector_entry, index);
3367 			if (dest && e_destination_get_textrep (dest, TRUE)) {
3368 				if (addresses->str && *addresses->str)
3369 					g_string_append (addresses, ", ");
3370 
3371 				g_string_append (addresses, e_destination_get_textrep (dest, TRUE));
3372 			} else
3373 				g_string_append_len (addresses, text + rstart, rend - rstart);
3374 		}
3375 	}
3376 
3377 	g_slist_free (sorted);
3378 
3379 	if (is_cut)
3380 		gtk_editable_delete_text (editable, start, end);
3381 
3382 	g_hash_table_unref (hash);
3383 
3384 	clipboard = gtk_widget_get_clipboard (
3385 		GTK_WIDGET (name_selector_entry), GDK_SELECTION_CLIPBOARD);
3386 	gtk_clipboard_set_text (clipboard, addresses->str, -1);
3387 
3388 	g_string_free (addresses, TRUE);
3389 }
3390 
3391 static void
copy_clipboard(GtkEntry * entry,ENameSelectorEntry * name_selector_entry)3392 copy_clipboard (GtkEntry *entry,
3393                 ENameSelectorEntry *name_selector_entry)
3394 {
3395 	copy_or_cut_clipboard (name_selector_entry, FALSE);
3396 	g_signal_stop_emission_by_name (entry, "copy-clipboard");
3397 }
3398 
3399 static void
cut_clipboard(GtkEntry * entry,ENameSelectorEntry * name_selector_entry)3400 cut_clipboard (GtkEntry *entry,
3401                ENameSelectorEntry *name_selector_entry)
3402 {
3403 	copy_or_cut_clipboard (name_selector_entry, TRUE);
3404 	g_signal_stop_emission_by_name (entry, "cut-clipboard");
3405 }
3406 
3407 static void
e_name_selector_entry_init(ENameSelectorEntry * name_selector_entry)3408 e_name_selector_entry_init (ENameSelectorEntry *name_selector_entry)
3409 {
3410 	GtkCellRenderer *renderer;
3411 
3412 	name_selector_entry->priv =
3413 		E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
3414 
3415 	g_queue_init (&name_selector_entry->priv->cancellables);
3416 
3417 	name_selector_entry->priv->minimum_query_length = 3;
3418 	name_selector_entry->priv->show_address = FALSE;
3419 	name_selector_entry->priv->block_entry_changed_signal = FALSE;
3420 	name_selector_entry->priv->known_contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
3421 
3422 	/* Edit signals */
3423 
3424 	g_signal_connect (
3425 		name_selector_entry, "changed",
3426 		G_CALLBACK (maybe_block_entry_changed_cb), NULL);
3427 	g_signal_connect (
3428 		name_selector_entry, "insert-text",
3429 		G_CALLBACK (user_insert_text), name_selector_entry);
3430 	g_signal_connect (
3431 		name_selector_entry, "delete-text",
3432 		G_CALLBACK (user_delete_text), name_selector_entry);
3433 	g_signal_connect (
3434 		name_selector_entry, "focus-out-event",
3435 		G_CALLBACK (user_focus_out), name_selector_entry);
3436 	g_signal_connect_after (
3437 		name_selector_entry, "focus-in-event",
3438 		G_CALLBACK (user_focus_in), name_selector_entry);
3439 	g_signal_connect (
3440 		name_selector_entry, "key-press-event",
3441 		G_CALLBACK (user_key_press_event_cb), name_selector_entry);
3442 
3443 	/* Drawing */
3444 
3445 	g_signal_connect (
3446 		name_selector_entry, "draw",
3447 		G_CALLBACK (draw_event), name_selector_entry);
3448 
3449 	/* Activation: Complete current entry if possible */
3450 
3451 	g_signal_connect (
3452 		name_selector_entry, "activate",
3453 		G_CALLBACK (entry_activate), name_selector_entry);
3454 
3455 	/* Pop-up menu */
3456 
3457 	g_signal_connect (
3458 		name_selector_entry, "button-press-event",
3459 		G_CALLBACK (prepare_popup_destination), name_selector_entry);
3460 	g_signal_connect (
3461 		name_selector_entry, "populate-popup",
3462 		G_CALLBACK (populate_popup), name_selector_entry);
3463 
3464 	/* Clipboard signals */
3465 	g_signal_connect (
3466 		name_selector_entry, "copy-clipboard",
3467 		G_CALLBACK (copy_clipboard), name_selector_entry);
3468 	g_signal_connect (
3469 		name_selector_entry, "cut-clipboard",
3470 		G_CALLBACK (cut_clipboard), name_selector_entry);
3471 
3472 	/* Completion */
3473 
3474 	name_selector_entry->priv->email_generator = NULL;
3475 
3476 	name_selector_entry->priv->entry_completion = gtk_entry_completion_new ();
3477 	gtk_entry_completion_set_match_func (
3478 		name_selector_entry->priv->entry_completion,
3479 		(GtkEntryCompletionMatchFunc) completion_match_cb, NULL, NULL);
3480 	g_signal_connect_swapped (
3481 		name_selector_entry->priv->entry_completion, "match-selected",
3482 		G_CALLBACK (completion_match_selected), name_selector_entry);
3483 
3484 	gtk_entry_set_completion (
3485 		GTK_ENTRY (name_selector_entry),
3486 		name_selector_entry->priv->entry_completion);
3487 
3488 	renderer = gtk_cell_renderer_pixbuf_new ();
3489 	gtk_cell_layout_pack_start (
3490 		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3491 		renderer, FALSE);
3492 	gtk_cell_layout_set_cell_data_func (
3493 		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3494 		GTK_CELL_RENDERER (renderer),
3495 		(GtkCellLayoutDataFunc) contact_layout_pixbuffer,
3496 		name_selector_entry, NULL);
3497 
3498 	/* Completion list name renderer */
3499 	renderer = gtk_cell_renderer_text_new ();
3500 	gtk_cell_layout_pack_start (
3501 		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3502 		renderer, TRUE);
3503 	gtk_cell_layout_set_cell_data_func (
3504 		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3505 		GTK_CELL_RENDERER (renderer),
3506 		(GtkCellLayoutDataFunc) contact_layout_formatter,
3507 		name_selector_entry, NULL);
3508 
3509 	/* Destination store */
3510 
3511 	name_selector_entry->priv->destination_store = e_destination_store_new ();
3512 	setup_destination_store (name_selector_entry);
3513 	name_selector_entry->priv->is_completing = FALSE;
3514 }
3515 
3516 /**
3517  * e_name_selector_entry_new:
3518  * @client_cache: an #EClientCache
3519  *
3520  * Creates a new #ENameSelectorEntry.
3521  *
3522  * Returns: A new #ENameSelectorEntry.
3523  **/
3524 GtkWidget *
e_name_selector_entry_new(EClientCache * client_cache)3525 e_name_selector_entry_new (EClientCache *client_cache)
3526 {
3527 	g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
3528 
3529 	return g_object_new (
3530 		E_TYPE_NAME_SELECTOR_ENTRY,
3531 		"client-cache", client_cache, NULL);
3532 }
3533 
3534 /**
3535  * e_name_selector_entry_ref_client_cache:
3536  * @name_selector_entry: an #ENameSelectorEntry
3537  *
3538  * Returns the #EClientCache passed to e_name_selector_entry_new().
3539  *
3540  * The returned #EClientCache is referenced for thread-safety and must be
3541  * unreferenced with g_object_unref() when finished with it.
3542  *
3543  * Returns: an #EClientCache
3544  *
3545  * Since: 3.8
3546  **/
3547 EClientCache *
e_name_selector_entry_ref_client_cache(ENameSelectorEntry * name_selector_entry)3548 e_name_selector_entry_ref_client_cache (ENameSelectorEntry *name_selector_entry)
3549 {
3550 	g_return_val_if_fail (
3551 		E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3552 
3553 	if (name_selector_entry->priv->client_cache == NULL)
3554 		return NULL;
3555 
3556 	return g_object_ref (name_selector_entry->priv->client_cache);
3557 }
3558 
3559 /**
3560  * e_name_selector_entry_set_client_cache:
3561  * @name_selector_entry: an #ENameSelectorEntry
3562  * @client_cache: an #EClientCache
3563  *
3564  * Sets the #EClientCache used to query address books.
3565  *
3566  * This function is intended for cases where @name_selector_entry is
3567  * instantiated by a #GtkBuilder and has to be given an #EClientCache
3568  * after it is fully constructed.
3569  *
3570  * Since: 3.6
3571  **/
3572 void
e_name_selector_entry_set_client_cache(ENameSelectorEntry * name_selector_entry,EClientCache * client_cache)3573 e_name_selector_entry_set_client_cache (ENameSelectorEntry *name_selector_entry,
3574                                         EClientCache *client_cache)
3575 {
3576 	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3577 
3578 	if (client_cache == name_selector_entry->priv->client_cache)
3579 		return;
3580 
3581 	if (client_cache != NULL) {
3582 		g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
3583 		g_object_ref (client_cache);
3584 	}
3585 
3586 	if (name_selector_entry->priv->client_cache != NULL)
3587 		g_object_unref (name_selector_entry->priv->client_cache);
3588 
3589 	name_selector_entry->priv->client_cache = client_cache;
3590 
3591 	g_object_notify (G_OBJECT (name_selector_entry), "client-cache");
3592 }
3593 
3594 /**
3595  * e_name_selector_entry_get_minimum_query_length:
3596  * @name_selector_entry: an #ENameSelectorEntry
3597  *
3598  * Returns: Minimum length of query before completion starts
3599  *
3600  * Since: 3.6
3601  **/
3602 gint
e_name_selector_entry_get_minimum_query_length(ENameSelectorEntry * name_selector_entry)3603 e_name_selector_entry_get_minimum_query_length (ENameSelectorEntry *name_selector_entry)
3604 {
3605 	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), -1);
3606 
3607 	return name_selector_entry->priv->minimum_query_length;
3608 }
3609 
3610 /**
3611  * e_name_selector_entry_set_minimum_query_length:
3612  * @name_selector_entry: an #ENameSelectorEntry
3613  * @length: minimum query length
3614  *
3615  * Sets minimum length of query before completion starts.
3616  *
3617  * Since: 3.6
3618  **/
3619 void
e_name_selector_entry_set_minimum_query_length(ENameSelectorEntry * name_selector_entry,gint length)3620 e_name_selector_entry_set_minimum_query_length (ENameSelectorEntry *name_selector_entry,
3621                                                 gint length)
3622 {
3623 	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3624 	g_return_if_fail (length > 0);
3625 
3626 	if (name_selector_entry->priv->minimum_query_length == length)
3627 		return;
3628 
3629 	name_selector_entry->priv->minimum_query_length = length;
3630 
3631 	g_object_notify (G_OBJECT (name_selector_entry), "minimum-query-length");
3632 }
3633 
3634 /**
3635  * e_name_selector_entry_get_show_address:
3636  * @name_selector_entry: an #ENameSelectorEntry
3637  *
3638  * Returns: Whether always show email address for an auto-completed contact.
3639  *
3640  * Since: 3.6
3641  **/
3642 gboolean
e_name_selector_entry_get_show_address(ENameSelectorEntry * name_selector_entry)3643 e_name_selector_entry_get_show_address (ENameSelectorEntry *name_selector_entry)
3644 {
3645 	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), FALSE);
3646 
3647 	return name_selector_entry->priv->show_address;
3648 }
3649 
3650 /**
3651  * e_name_selector_entry_set_show_address:
3652  * @name_selector_entry: an #ENameSelectorEntry
3653  * @show: new value to set
3654  *
3655  * Sets whether always show email address for an auto-completed contact.
3656  *
3657  * Since: 3.6
3658  **/
3659 void
e_name_selector_entry_set_show_address(ENameSelectorEntry * name_selector_entry,gboolean show)3660 e_name_selector_entry_set_show_address (ENameSelectorEntry *name_selector_entry,
3661                                         gboolean show)
3662 {
3663 	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3664 
3665 	if ((name_selector_entry->priv->show_address ? 1 : 0) == (show ? 1 : 0))
3666 		return;
3667 
3668 	name_selector_entry->priv->show_address = show;
3669 
3670 	sanitize_entry (name_selector_entry);
3671 
3672 	g_object_notify (G_OBJECT (name_selector_entry), "show-address");
3673 }
3674 
3675 /**
3676  * e_name_selector_entry_peek_contact_store:
3677  * @name_selector_entry: an #ENameSelectorEntry
3678  *
3679  * Gets the #EContactStore being used by @name_selector_entry.
3680  *
3681  * Returns: An #EContactStore.
3682  **/
3683 EContactStore *
e_name_selector_entry_peek_contact_store(ENameSelectorEntry * name_selector_entry)3684 e_name_selector_entry_peek_contact_store (ENameSelectorEntry *name_selector_entry)
3685 {
3686 	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3687 
3688 	return name_selector_entry->priv->contact_store;
3689 }
3690 
3691 /**
3692  * e_name_selector_entry_set_contact_store:
3693  * @name_selector_entry: an #ENameSelectorEntry
3694  * @contact_store: an #EContactStore to use
3695  *
3696  * Sets the #EContactStore being used by @name_selector_entry to @contact_store.
3697  **/
3698 void
e_name_selector_entry_set_contact_store(ENameSelectorEntry * name_selector_entry,EContactStore * contact_store)3699 e_name_selector_entry_set_contact_store (ENameSelectorEntry *name_selector_entry,
3700                                          EContactStore *contact_store)
3701 {
3702 	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3703 	g_return_if_fail (contact_store == NULL || E_IS_CONTACT_STORE (contact_store));
3704 
3705 	if (contact_store == name_selector_entry->priv->contact_store)
3706 		return;
3707 
3708 	if (name_selector_entry->priv->contact_store)
3709 		g_object_unref (name_selector_entry->priv->contact_store);
3710 	name_selector_entry->priv->contact_store = contact_store;
3711 	if (name_selector_entry->priv->contact_store)
3712 		g_object_ref (name_selector_entry->priv->contact_store);
3713 
3714 	setup_contact_store (name_selector_entry);
3715 }
3716 
3717 /**
3718  * e_name_selector_entry_peek_destination_store:
3719  * @name_selector_entry: an #ENameSelectorEntry
3720  *
3721  * Gets the #EDestinationStore being used to store @name_selector_entry's destinations.
3722  *
3723  * Returns: An #EDestinationStore.
3724  **/
3725 EDestinationStore *
e_name_selector_entry_peek_destination_store(ENameSelectorEntry * name_selector_entry)3726 e_name_selector_entry_peek_destination_store (ENameSelectorEntry *name_selector_entry)
3727 {
3728 	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3729 
3730 	return name_selector_entry->priv->destination_store;
3731 }
3732 
3733 /**
3734  * e_name_selector_entry_set_destination_store:
3735  * @name_selector_entry: an #ENameSelectorEntry
3736  * @destination_store: an #EDestinationStore to use
3737  *
3738  * Sets @destination_store as the #EDestinationStore to be used to store
3739  * destinations for @name_selector_entry.
3740  **/
3741 void
e_name_selector_entry_set_destination_store(ENameSelectorEntry * name_selector_entry,EDestinationStore * destination_store)3742 e_name_selector_entry_set_destination_store (ENameSelectorEntry *name_selector_entry,
3743                                              EDestinationStore *destination_store)
3744 {
3745 	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3746 	g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
3747 
3748 	if (destination_store == name_selector_entry->priv->destination_store)
3749 		return;
3750 
3751 	g_object_unref (name_selector_entry->priv->destination_store);
3752 	name_selector_entry->priv->destination_store = g_object_ref (destination_store);
3753 
3754 	setup_destination_store (name_selector_entry);
3755 }
3756 
3757 /**
3758  * e_name_selector_entry_get_popup_destination:
3759  *
3760  * Since: 2.32
3761  **/
3762 EDestination *
e_name_selector_entry_get_popup_destination(ENameSelectorEntry * name_selector_entry)3763 e_name_selector_entry_get_popup_destination (ENameSelectorEntry *name_selector_entry)
3764 {
3765 	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3766 
3767 	return name_selector_entry->priv->popup_destination;
3768 }
3769 
3770 /**
3771  * e_name_selector_entry_set_contact_editor_func:
3772  *
3773  * DO NOT USE.
3774  **/
3775 void
e_name_selector_entry_set_contact_editor_func(ENameSelectorEntry * name_selector_entry,gpointer func)3776 e_name_selector_entry_set_contact_editor_func (ENameSelectorEntry *name_selector_entry,
3777                                                gpointer func)
3778 {
3779 	name_selector_entry->priv->contact_editor_func = func;
3780 }
3781 
3782 /**
3783  * e_name_selector_entry_set_contact_list_editor_func:
3784  *
3785  * DO NOT USE.
3786  **/
3787 void
e_name_selector_entry_set_contact_list_editor_func(ENameSelectorEntry * name_selector_entry,gpointer func)3788 e_name_selector_entry_set_contact_list_editor_func (ENameSelectorEntry *name_selector_entry,
3789                                                     gpointer func)
3790 {
3791 	name_selector_entry->priv->contact_list_editor_func = func;
3792 }
3793