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, ¤t_field, ¤t_field_rank, ¤t_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