Updated Spanish translation
[evolution.git] / e-util / e-name-selector-entry.c
blob94b92a59ef2c43b810c18138c2eae3f16c0ea37a
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /* e-name-selector-entry.c - Single-line text entry widget for EDestinations.
5 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
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.
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.
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/>.
19 * Authors: Hans Petter Jansson <hpj@novell.com>
22 #include <config.h>
23 #include <string.h>
24 #include <glib/gi18n-lib.h>
26 #include <camel/camel.h>
27 #include <libebackend/libebackend.h>
29 #include "e-name-selector-entry.h"
31 #define E_NAME_SELECTOR_ENTRY_GET_PRIVATE(obj) \
32 (G_TYPE_INSTANCE_GET_PRIVATE \
33 ((obj), E_TYPE_NAME_SELECTOR_ENTRY, ENameSelectorEntryPrivate))
35 struct _ENameSelectorEntryPrivate {
36 EClientCache *client_cache;
37 gint minimum_query_length;
38 gboolean show_address;
40 PangoAttrList *attr_list;
41 EContactStore *contact_store;
42 ETreeModelGenerator *email_generator;
43 EDestinationStore *destination_store;
44 GtkEntryCompletion *entry_completion;
46 guint type_ahead_complete_cb_id;
47 guint update_completions_cb_id;
49 EDestination *popup_destination;
51 gpointer (*contact_editor_func) (EBookClient *,
52 EContact *,
53 gboolean,
54 gboolean);
55 gpointer (*contact_list_editor_func)
56 (EBookClient *,
57 EContact *,
58 gboolean,
59 gboolean);
61 gboolean is_completing;
62 GSList *user_query_fields;
64 /* For asynchronous operations. */
65 GQueue cancellables;
67 GHashTable *known_contacts; /* gchar * ~> 1 */
70 enum {
71 PROP_0,
72 PROP_CLIENT_CACHE,
73 PROP_MINIMUM_QUERY_LENGTH,
74 PROP_SHOW_ADDRESS
77 enum {
78 UPDATED,
79 LAST_SIGNAL
82 static guint signals[LAST_SIGNAL] = { 0 };
83 #define ENS_DEBUG(x)
85 G_DEFINE_TYPE_WITH_CODE (
86 ENameSelectorEntry,
87 e_name_selector_entry,
88 GTK_TYPE_ENTRY,
89 G_IMPLEMENT_INTERFACE (
90 E_TYPE_EXTENSIBLE, NULL))
92 /* 1/3 of the second to wait until invoking autocomplete lookup */
93 #define AUTOCOMPLETE_TIMEOUT 333
95 /* 1/20 of a second to wait until show the completion results */
96 #define SHOW_RESULT_TIMEOUT 50
98 #define re_set_timeout(id,func,ptr,tout) G_STMT_START { \
99 if (id) \
100 g_source_remove (id); \
101 id = e_named_timeout_add (tout, func, ptr); \
102 } G_STMT_END
104 static void destination_row_inserted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
105 static void destination_row_changed (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
106 static void destination_row_deleted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path);
108 static void user_insert_text (ENameSelectorEntry *name_selector_entry, gchar *new_text, gint new_text_length, gint *position, gpointer user_data);
109 static void user_delete_text (ENameSelectorEntry *name_selector_entry, gint start_pos, gint end_pos, gpointer user_data);
111 static void setup_default_contact_store (ENameSelectorEntry *name_selector_entry);
112 static void deep_free_list (GList *list);
114 static void
115 name_selector_entry_set_property (GObject *object,
116 guint property_id,
117 const GValue *value,
118 GParamSpec *pspec)
120 switch (property_id) {
121 case PROP_CLIENT_CACHE:
122 e_name_selector_entry_set_client_cache (
123 E_NAME_SELECTOR_ENTRY (object),
124 g_value_get_object (value));
125 return;
127 case PROP_MINIMUM_QUERY_LENGTH:
128 e_name_selector_entry_set_minimum_query_length (
129 E_NAME_SELECTOR_ENTRY (object),
130 g_value_get_int (value));
131 return;
133 case PROP_SHOW_ADDRESS:
134 e_name_selector_entry_set_show_address (
135 E_NAME_SELECTOR_ENTRY (object),
136 g_value_get_boolean (value));
137 return;
140 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
143 static void
144 name_selector_entry_get_property (GObject *object,
145 guint property_id,
146 GValue *value,
147 GParamSpec *pspec)
149 switch (property_id) {
150 case PROP_CLIENT_CACHE:
151 g_value_take_object (
152 value,
153 e_name_selector_entry_ref_client_cache (
154 E_NAME_SELECTOR_ENTRY (object)));
155 return;
157 case PROP_MINIMUM_QUERY_LENGTH:
158 g_value_set_int (
159 value,
160 e_name_selector_entry_get_minimum_query_length (
161 E_NAME_SELECTOR_ENTRY (object)));
162 return;
164 case PROP_SHOW_ADDRESS:
165 g_value_set_boolean (
166 value,
167 e_name_selector_entry_get_show_address (
168 E_NAME_SELECTOR_ENTRY (object)));
169 return;
172 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
175 static void
176 name_selector_entry_dispose (GObject *object)
178 ENameSelectorEntryPrivate *priv;
180 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (object);
182 if (priv->client_cache != NULL) {
183 g_object_unref (priv->client_cache);
184 priv->client_cache = NULL;
187 if (priv->attr_list != NULL) {
188 pango_attr_list_unref (priv->attr_list);
189 priv->attr_list = NULL;
192 if (priv->entry_completion) {
193 g_object_unref (priv->entry_completion);
194 priv->entry_completion = NULL;
197 if (priv->destination_store) {
198 g_object_unref (priv->destination_store);
199 priv->destination_store = NULL;
202 if (priv->email_generator) {
203 g_object_unref (priv->email_generator);
204 priv->email_generator = NULL;
207 if (priv->contact_store) {
208 g_object_unref (priv->contact_store);
209 priv->contact_store = NULL;
212 if (priv->known_contacts) {
213 g_hash_table_destroy (priv->known_contacts);
214 priv->known_contacts = NULL;
217 g_slist_foreach (priv->user_query_fields, (GFunc) g_free, NULL);
218 g_slist_free (priv->user_query_fields);
219 priv->user_query_fields = NULL;
221 /* Cancel any stuck book loading operations. */
222 while (!g_queue_is_empty (&priv->cancellables)) {
223 GCancellable *cancellable;
225 cancellable = g_queue_pop_head (&priv->cancellables);
226 g_cancellable_cancel (cancellable);
227 g_object_unref (cancellable);
230 /* Chain up to parent's dispose() method. */
231 G_OBJECT_CLASS (e_name_selector_entry_parent_class)->dispose (object);
234 static void
235 name_selector_entry_constructed (GObject *object)
237 /* Chain up to parent's constructed() method. */
238 G_OBJECT_CLASS (e_name_selector_entry_parent_class)->constructed (object);
240 e_extensible_load_extensions (E_EXTENSIBLE (object));
243 static void
244 name_selector_entry_realize (GtkWidget *widget)
246 ENameSelectorEntryPrivate *priv;
248 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (widget);
250 /* Chain up to parent's realize() method. */
251 GTK_WIDGET_CLASS (e_name_selector_entry_parent_class)->realize (widget);
253 if (priv->contact_store == NULL)
254 setup_default_contact_store (E_NAME_SELECTOR_ENTRY (widget));
257 static void
258 name_selector_entry_drag_data_received (GtkWidget *widget,
259 GdkDragContext *context,
260 gint x,
261 gint y,
262 GtkSelectionData *selection_data,
263 guint info,
264 guint time)
266 CamelInternetAddress *address;
267 gint n_addresses = 0;
268 gchar *text;
270 address = camel_internet_address_new ();
271 text = (gchar *) gtk_selection_data_get_text (selection_data);
273 /* See if Camel can parse a valid email address from the text. */
274 if (text != NULL && *text != '\0') {
275 camel_url_decode (text);
276 if (g_ascii_strncasecmp (text, "mailto:", 7) == 0)
277 n_addresses = camel_address_decode (
278 CAMEL_ADDRESS (address), text + 7);
279 else
280 n_addresses = camel_address_decode (
281 CAMEL_ADDRESS (address), text);
284 if (n_addresses > 0) {
285 GtkEditable *editable;
286 GdkDragAction action;
287 gboolean delete;
288 gint position;
290 editable = GTK_EDITABLE (widget);
291 gtk_editable_set_position (editable, -1);
292 position = gtk_editable_get_position (editable);
294 g_free (text);
296 text = camel_address_format (CAMEL_ADDRESS (address));
297 gtk_editable_insert_text (editable, text, -1, &position);
299 action = gdk_drag_context_get_selected_action (context);
300 delete = (action == GDK_ACTION_MOVE);
301 gtk_drag_finish (context, TRUE, delete, time);
304 g_object_unref (address);
305 g_free (text);
307 if (n_addresses <= 0)
308 /* Chain up to parent's drag_data_received() method. */
309 GTK_WIDGET_CLASS (e_name_selector_entry_parent_class)->
310 drag_data_received (
311 widget, context, x, y,
312 selection_data, info, time);
315 static void
316 e_name_selector_entry_class_init (ENameSelectorEntryClass *class)
318 GObjectClass *object_class;
319 GtkWidgetClass *widget_class;
321 g_type_class_add_private (class, sizeof (ENameSelectorEntryPrivate));
323 object_class = G_OBJECT_CLASS (class);
324 object_class->set_property = name_selector_entry_set_property;
325 object_class->get_property = name_selector_entry_get_property;
326 object_class->dispose = name_selector_entry_dispose;
327 object_class->constructed = name_selector_entry_constructed;
329 widget_class = GTK_WIDGET_CLASS (class);
330 widget_class->realize = name_selector_entry_realize;
331 widget_class->drag_data_received = name_selector_entry_drag_data_received;
334 * ENameSelectorEntry:client-cache:
336 * Cache of shared #EClient instances.
338 g_object_class_install_property (
339 object_class,
340 PROP_CLIENT_CACHE,
341 g_param_spec_object (
342 "client-cache",
343 "Client Cache",
344 "Cache of shared EClient instances",
345 E_TYPE_CLIENT_CACHE,
346 G_PARAM_READWRITE |
347 G_PARAM_CONSTRUCT |
348 G_PARAM_STATIC_STRINGS));
350 g_object_class_install_property (
351 object_class,
352 PROP_MINIMUM_QUERY_LENGTH,
353 g_param_spec_int (
354 "minimum-query-length",
355 "Minimum Query Length",
356 NULL,
357 1, G_MAXINT,
359 G_PARAM_READWRITE |
360 G_PARAM_STATIC_STRINGS));
362 g_object_class_install_property (
363 object_class,
364 PROP_SHOW_ADDRESS,
365 g_param_spec_boolean (
366 "show-address",
367 "Show Address",
368 NULL,
369 FALSE,
370 G_PARAM_READWRITE |
371 G_PARAM_STATIC_STRINGS));
373 signals[UPDATED] = g_signal_new (
374 "updated",
375 E_TYPE_NAME_SELECTOR_ENTRY,
376 G_SIGNAL_RUN_FIRST,
377 G_STRUCT_OFFSET (ENameSelectorEntryClass, updated),
378 NULL, NULL,
379 g_cclosure_marshal_VOID__POINTER,
380 G_TYPE_NONE, 1, G_TYPE_POINTER);
383 static gchar *
384 describe_contact (EContact *contact)
386 GString *description;
387 const gchar *str;
388 GList *emails, *link;
390 g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
392 emails = e_contact_get (contact, E_CONTACT_EMAIL);
393 /* Cannot merge one contact with multiple addresses with another contact */
394 if (!e_contact_get (contact, E_CONTACT_IS_LIST) && emails && emails->next) {
395 deep_free_list (emails);
396 return NULL;
399 description = g_string_new ("");
401 if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
402 g_string_append (description, "list\n");
403 } else {
404 g_string_append (description, "indv\n");
407 str = e_contact_get_const (contact, E_CONTACT_FILE_AS);
408 g_string_append (description, str ? str : "");
409 g_string_append (description, "\n");
411 str = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
412 g_string_append (description, str ? str : "");
413 g_string_append (description, "\n");
415 emails = e_contact_get (contact, E_CONTACT_EMAIL);
416 emails = g_list_sort (emails, (GCompareFunc) g_ascii_strcasecmp);
417 for (link = emails; link; link = g_list_next (link)) {
418 str = link->data;
420 g_string_append (description, str ? str : "");
421 g_string_append (description, "\n");
424 deep_free_list (emails);
426 return g_string_free (description, FALSE);
429 static gboolean
430 is_duplicate_contact_and_remember (ENameSelectorEntry *nsentry,
431 EContact *contact)
433 gchar *description;
435 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (nsentry), FALSE);
436 g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
438 description = describe_contact (contact);
439 if (!description) {
440 /* Might be a contact with multiple addresses */
441 return FALSE;
444 if (g_hash_table_lookup (nsentry->priv->known_contacts, description)) {
445 g_free (description);
446 return TRUE;
449 g_hash_table_insert (nsentry->priv->known_contacts, description, GINT_TO_POINTER (1));
451 return FALSE;
454 /* Remove unquoted commas and control characters from string */
455 static gchar *
456 sanitize_string (const gchar *string)
458 GString *gstring;
459 gboolean quoted = FALSE;
460 const gchar *p;
462 gstring = g_string_new ("");
464 if (!string)
465 return g_string_free (gstring, FALSE);
467 for (p = string; *p; p = g_utf8_next_char (p)) {
468 gunichar c = g_utf8_get_char (p);
470 if (c == '"')
471 quoted = ~quoted;
472 else if (c == ',' && !quoted)
473 continue;
474 else if (c == '\t' || c == '\n')
475 continue;
477 g_string_append_unichar (gstring, c);
480 return g_string_free (gstring, FALSE);
483 /* Called for each list store entry whenever the user types (but not on cut/paste) */
484 static gboolean
485 completion_match_cb (GtkEntryCompletion *completion,
486 const gchar *key,
487 GtkTreeIter *iter,
488 gpointer user_data)
490 ENS_DEBUG (g_print ("completion_match_cb, key=%s\n", key));
492 return TRUE;
495 /* Gets context of n_unichars total (n_unicars / 2, before and after position)
496 * and places them in array. If any positions would be outside the string, the
497 * corresponding unichars are set to zero. */
498 static void
499 get_utf8_string_context (const gchar *string,
500 gint position,
501 gunichar *unichars,
502 gint n_unichars)
504 gchar *p = NULL;
505 gint len;
506 gint gap;
507 gint i;
509 /* n_unichars must be even */
510 g_return_if_fail (n_unichars % 2 == 0);
512 len = g_utf8_strlen (string, -1);
513 gap = n_unichars / 2;
515 for (i = 0; i < n_unichars; i++) {
516 gint char_pos = position - gap + i;
518 if (char_pos < 0 || char_pos >= len) {
519 unichars[i] = '\0';
520 continue;
523 if (p)
524 p = g_utf8_next_char (p);
525 else
526 p = g_utf8_offset_to_pointer (string, char_pos);
528 unichars[i] = g_utf8_get_char (p);
532 static gboolean
533 get_range_at_position (const gchar *string,
534 gint pos,
535 gint *start_pos,
536 gint *end_pos)
538 const gchar *p;
539 gboolean quoted = FALSE;
540 gint local_start_pos = 0;
541 gint local_end_pos = 0;
542 gint i;
544 if (!string || !*string)
545 return FALSE;
547 for (p = string, i = 0; *p; p = g_utf8_next_char (p), i++) {
548 gunichar c = g_utf8_get_char (p);
550 if (c == '"') {
551 quoted = ~quoted;
552 } else if (c == ',' && !quoted) {
553 if (i < pos) {
554 /* Start right after comma */
555 local_start_pos = i + 1;
556 } else {
557 /* Stop right before comma */
558 local_end_pos = i;
559 break;
561 } else if (c == ' ' && local_start_pos == i) {
562 /* Adjust start to skip space after first comma */
563 local_start_pos++;
567 /* If we didn't hit a comma, we must've hit NULL, and ours was the last element. */
568 if (!local_end_pos)
569 local_end_pos = i;
571 if (start_pos)
572 *start_pos = local_start_pos;
573 if (end_pos)
574 *end_pos = local_end_pos;
576 return TRUE;
579 static gboolean
580 is_quoted_at (const gchar *string,
581 gint pos)
583 const gchar *p;
584 gboolean quoted = FALSE;
585 gint i;
587 for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
588 gunichar c = g_utf8_get_char (p);
590 if (c == '"')
591 quoted = !quoted;
594 return quoted;
597 static gint
598 get_index_at_position (const gchar *string,
599 gint pos)
601 const gchar *p;
602 gboolean quoted = FALSE;
603 gint n = 0;
604 gint i;
606 for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
607 gunichar c = g_utf8_get_char (p);
609 if (c == '"')
610 quoted = !quoted;
611 else if (c == ',' && !quoted)
612 n++;
615 return n;
618 static gboolean
619 get_range_by_index (const gchar *string,
620 gint index,
621 gint *start_pos,
622 gint *end_pos)
624 const gchar *p;
625 gboolean quoted = FALSE;
626 gint i;
627 gint n = 0;
629 for (p = string, i = 0; *p && n < index; p = g_utf8_next_char (p), i++) {
630 gunichar c = g_utf8_get_char (p);
632 if (c == '"')
633 quoted = ~quoted;
634 if (c == ',' && !quoted)
635 n++;
638 if (n < index)
639 return FALSE;
641 return get_range_at_position (string, i, start_pos, end_pos);
644 static gchar *
645 get_address_at_position (const gchar *string,
646 gint pos)
648 gint start_pos;
649 gint end_pos;
650 const gchar *start_p;
651 const gchar *end_p;
653 if (!get_range_at_position (string, pos, &start_pos, &end_pos))
654 return NULL;
656 start_p = g_utf8_offset_to_pointer (string, start_pos);
657 end_p = g_utf8_offset_to_pointer (string, end_pos);
659 return g_strndup (start_p, end_p - start_p);
662 /* Finds the destination in model */
663 static EDestination *
664 find_destination_by_index (ENameSelectorEntry *name_selector_entry,
665 gint index)
667 GtkTreePath *path;
668 GtkTreeIter iter;
670 path = gtk_tree_path_new_from_indices (index, -1);
671 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (name_selector_entry->priv->destination_store),
672 &iter, path)) {
673 /* If we have zero destinations, getting a NULL destination at index 0
674 * is valid. */
675 if (index > 0)
676 g_warning ("ENameSelectorEntry is out of sync with model!");
677 gtk_tree_path_free (path);
678 return NULL;
680 gtk_tree_path_free (path);
682 return e_destination_store_get_destination (name_selector_entry->priv->destination_store, &iter);
685 /* Finds the destination in model */
686 static EDestination *
687 find_destination_at_position (ENameSelectorEntry *name_selector_entry,
688 gint pos)
690 const gchar *text;
691 gint index;
693 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
694 index = get_index_at_position (text, pos);
696 return find_destination_by_index (name_selector_entry, index);
699 /* Builds destination from our text */
700 static EDestination *
701 build_destination_at_position (const gchar *string,
702 gint pos)
704 EDestination *destination;
705 gchar *address;
707 address = get_address_at_position (string, pos);
708 if (!address)
709 return NULL;
711 destination = e_destination_new ();
712 e_destination_set_raw (destination, address);
714 g_free (address);
715 return destination;
718 static gchar *
719 name_style_query (const gchar *field,
720 const gchar *value)
722 gchar *spaced_str;
723 gchar *comma_str;
724 GString *out = g_string_new ("");
725 gchar **strv;
726 gchar *query;
728 spaced_str = sanitize_string (value);
729 g_strstrip (spaced_str);
731 strv = g_strsplit (spaced_str, " ", 0);
733 if (strv[0] && strv[1]) {
734 g_string_append (out, "(or ");
735 comma_str = g_strjoinv (", ", strv);
736 } else {
737 comma_str = NULL;
740 g_string_append (out, " (beginswith ");
741 e_sexp_encode_string (out, field);
742 e_sexp_encode_string (out, spaced_str);
743 g_string_append (out, ")");
745 if (comma_str) {
746 g_string_append (out, " (beginswith ");
748 e_sexp_encode_string (out, field);
749 g_strstrip (comma_str);
750 e_sexp_encode_string (out, comma_str);
751 g_string_append (out, "))");
754 query = g_string_free (out, FALSE);
756 g_free (spaced_str);
757 g_free (comma_str);
758 g_strfreev (strv);
760 return query;
763 static gchar *
764 escape_sexp_string (const gchar *string)
766 GString *gstring;
767 gchar *encoded_string;
769 gstring = g_string_new ("");
770 e_sexp_encode_string (gstring, string);
772 encoded_string = gstring->str;
773 g_string_free (gstring, FALSE);
775 return encoded_string;
779 * ens_util_populate_user_query_fields:
781 * Populates list of user query fields to string usable in query string.
782 * Returned pointer is either newly allocated string, supposed to be freed with g_free,
783 * or NULL if no fields defined.
785 * Since: 2.24
787 gchar *
788 ens_util_populate_user_query_fields (GSList *user_query_fields,
789 const gchar *cue_str,
790 const gchar *encoded_cue_str)
792 GString *user_fields;
793 GSList *s;
795 g_return_val_if_fail (cue_str != NULL, NULL);
796 g_return_val_if_fail (encoded_cue_str != NULL, NULL);
798 user_fields = g_string_new ("");
800 for (s = user_query_fields; s; s = s->next) {
801 const gchar *field = s->data;
803 if (!field || !*field)
804 continue;
806 if (*field == '$') {
807 g_string_append_printf (user_fields, " (beginswith \"%s\" %s) ", field + 1, encoded_cue_str);
808 } else if (*field == '@') {
809 g_string_append_printf (user_fields, " (is \"%s\" %s) ", field + 1, encoded_cue_str);
810 } else {
811 gchar *tmp = name_style_query (field, cue_str);
813 g_string_append (user_fields, " ");
814 g_string_append (user_fields, tmp);
815 g_string_append (user_fields, " ");
816 g_free (tmp);
820 return g_string_free (user_fields, !user_fields->str || !*user_fields->str);
823 static void
824 set_completion_query (ENameSelectorEntry *name_selector_entry,
825 const gchar *cue_str)
827 ENameSelectorEntryPrivate *priv;
828 EBookQuery *book_query;
829 gchar *query_str;
830 gchar *encoded_cue_str;
831 gchar *full_name_query_str;
832 gchar *file_as_query_str;
833 gchar *user_fields_str;
835 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
837 if (!name_selector_entry->priv->contact_store)
838 return;
840 if (!cue_str) {
841 /* Clear the store */
842 e_contact_store_set_query (name_selector_entry->priv->contact_store, NULL);
843 return;
846 encoded_cue_str = escape_sexp_string (cue_str);
847 full_name_query_str = name_style_query ("full_name", cue_str);
848 file_as_query_str = name_style_query ("file_as", cue_str);
849 user_fields_str = ens_util_populate_user_query_fields (priv->user_query_fields, cue_str, encoded_cue_str);
851 query_str = g_strdup_printf (
852 "(or "
853 " (beginswith \"nickname\" %s) "
854 " (beginswith \"email\" %s) "
855 " (contains \"nickname\" %s) "
856 " (contains \"email\" %s) "
857 " %s "
858 " %s "
859 " %s "
860 ")",
861 encoded_cue_str, encoded_cue_str,
862 encoded_cue_str, encoded_cue_str,
863 full_name_query_str, file_as_query_str,
864 user_fields_str ? user_fields_str : "");
866 g_free (user_fields_str);
867 g_free (file_as_query_str);
868 g_free (full_name_query_str);
869 g_free (encoded_cue_str);
871 ENS_DEBUG (g_print ("%s\n", query_str));
873 book_query = e_book_query_from_string (query_str);
874 e_contact_store_set_query (name_selector_entry->priv->contact_store, book_query);
875 e_book_query_unref (book_query);
877 g_free (query_str);
880 static gchar *
881 get_entry_substring (ENameSelectorEntry *name_selector_entry,
882 gint range_start,
883 gint range_end)
885 const gchar *entry_text;
886 gchar *p0, *p1;
888 entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
890 p0 = g_utf8_offset_to_pointer (entry_text, range_start);
891 p1 = g_utf8_offset_to_pointer (entry_text, range_end);
893 return g_strndup (p0, p1 - p0);
896 static gint
897 utf8_casefold_collate_len (const gchar *str1,
898 const gchar *str2,
899 gint len)
901 gchar *s1 = g_utf8_casefold (str1, len);
902 gchar *s2 = g_utf8_casefold (str2, len);
903 gint rv;
905 rv = g_utf8_collate (s1, s2);
907 g_free (s1);
908 g_free (s2);
910 return rv;
913 static gchar *
914 build_textrep_for_contact (EContact *contact,
915 EContactField cue_field,
916 gint email_num)
918 gchar *name = NULL;
919 gchar *email = NULL;
920 gchar *textrep;
921 GList *l;
923 switch (cue_field) {
924 case E_CONTACT_FULL_NAME:
925 case E_CONTACT_NICKNAME:
926 case E_CONTACT_FILE_AS:
927 name = e_contact_get (contact, cue_field);
928 email = e_contact_get (contact, E_CONTACT_EMAIL_1);
929 break;
931 case E_CONTACT_EMAIL:
932 name = NULL;
933 l = e_contact_get (contact, cue_field);
934 email = strdup (g_list_nth_data (l, email_num));
935 g_list_free_full (l, g_free);
936 break;
938 default:
939 g_return_val_if_reached (NULL);
940 break;
943 g_return_val_if_fail (email, NULL);
944 g_return_val_if_fail (strlen (email) > 0, NULL);
946 if (name)
947 textrep = g_strdup_printf ("%s <%s>", name, email);
948 else
949 textrep = g_strdup_printf ("%s", email);
951 g_free (name);
952 g_free (email);
953 return textrep;
956 static gboolean
957 contact_match_cue (ENameSelectorEntry *name_selector_entry,
958 EContact *contact,
959 const gchar *cue_str,
960 EContactField *matched_field,
961 gint *matched_field_rank,
962 gint *matched_email_num)
964 EContactField fields[] = { E_CONTACT_FULL_NAME, E_CONTACT_NICKNAME, E_CONTACT_FILE_AS,
965 E_CONTACT_EMAIL };
966 gchar *email;
967 gboolean result = FALSE;
968 gint cue_len;
969 gint i;
971 g_return_val_if_fail (contact, FALSE);
972 g_return_val_if_fail (cue_str, FALSE);
974 if (g_utf8_strlen (cue_str, -1) < name_selector_entry->priv->minimum_query_length)
975 return FALSE;
977 cue_len = strlen (cue_str);
979 /* Make sure contact has an email address */
980 email = e_contact_get (contact, E_CONTACT_EMAIL_1);
981 if (!email || !*email) {
982 g_free (email);
983 return FALSE;
985 g_free (email);
987 for (i = 0; i < G_N_ELEMENTS (fields) && result == FALSE; i++) {
988 gint email_num;
989 gchar *value;
990 gchar *value_sane;
991 GList *emails = NULL, *ll = NULL;
993 /* Don't match e-mail addresses in contact lists */
994 if (e_contact_get (contact, E_CONTACT_IS_LIST) &&
995 fields[i] == E_CONTACT_EMAIL)
996 continue;
998 if (fields[i] == E_CONTACT_EMAIL) {
999 emails = e_contact_get (contact, fields[i]);
1000 } else {
1001 value = e_contact_get (contact, fields[i]);
1002 if (!value)
1003 continue;
1004 emails = g_list_append (emails, value);
1007 for (ll = emails, email_num = 0; ll; ll = ll->next, email_num++) {
1008 value = ll->data;
1009 value_sane = sanitize_string (value);
1011 ENS_DEBUG (g_print ("Comparing '%s' to '%s'\n", value, cue_str));
1013 if (!utf8_casefold_collate_len (value_sane, cue_str, cue_len)) {
1014 if (matched_field)
1015 *matched_field = fields[i];
1016 if (matched_field_rank)
1017 *matched_field_rank = i;
1018 if (matched_email_num)
1019 *matched_email_num = email_num;
1021 result = TRUE;
1022 g_free (value_sane);
1023 break;
1025 g_free (value_sane);
1027 g_list_free_full (emails, g_free);
1030 return result;
1033 static gboolean
1034 find_existing_completion (ENameSelectorEntry *name_selector_entry,
1035 const gchar *cue_str,
1036 EContact **contact,
1037 gchar **text,
1038 EContactField *matched_field,
1039 gint *matched_email_num,
1040 EBookClient **book_client)
1042 GtkTreeIter iter;
1043 EContact *best_contact = NULL;
1044 gint best_field_rank = G_MAXINT;
1045 EContactField best_field = 0;
1046 gint best_email_num = -1;
1047 EBookClient *best_book_client = NULL;
1049 g_return_val_if_fail (cue_str, FALSE);
1051 if (!name_selector_entry->priv->contact_store)
1052 return FALSE;
1054 ENS_DEBUG (g_print ("Completing '%s'\n", cue_str));
1056 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->priv->contact_store), &iter))
1057 return FALSE;
1059 do {
1060 EContact *current_contact;
1061 gint current_field_rank = best_field_rank;
1062 gint current_email_num = best_email_num;
1063 EContactField current_field = best_field;
1064 gboolean matches;
1066 current_contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &iter);
1067 if (!current_contact)
1068 continue;
1070 matches = contact_match_cue (name_selector_entry, current_contact, cue_str, &current_field, &current_field_rank, &current_email_num);
1071 if (matches && current_field_rank < best_field_rank) {
1072 best_contact = current_contact;
1073 best_field_rank = current_field_rank;
1074 best_field = current_field;
1075 best_book_client = e_contact_store_get_client (name_selector_entry->priv->contact_store, &iter);
1076 best_email_num = current_email_num;
1079 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->priv->contact_store), &iter));
1081 if (!best_contact)
1082 return FALSE;
1084 if (contact)
1085 *contact = best_contact;
1086 if (text)
1087 *text = build_textrep_for_contact (best_contact, best_field, best_email_num);
1088 if (matched_field)
1089 *matched_field = best_field;
1090 if (book_client)
1091 *book_client = best_book_client;
1092 if (matched_email_num)
1093 *matched_email_num = best_email_num;
1094 return TRUE;
1097 static void
1098 generate_attribute_list (ENameSelectorEntry *name_selector_entry)
1100 PangoLayout *layout;
1101 PangoAttrList *attr_list;
1102 const gchar *text;
1103 gint i;
1105 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1106 layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
1108 /* Set up the attribute list */
1110 attr_list = pango_attr_list_new ();
1112 if (name_selector_entry->priv->attr_list)
1113 pango_attr_list_unref (name_selector_entry->priv->attr_list);
1115 name_selector_entry->priv->attr_list = attr_list;
1117 /* Parse the entry's text and apply attributes to real contacts */
1119 for (i = 0; ; i++) {
1120 EDestination *destination;
1121 PangoAttribute *attr;
1122 gint start_pos;
1123 gint end_pos;
1125 if (!get_range_by_index (text, i, &start_pos, &end_pos))
1126 break;
1128 destination = find_destination_at_position (name_selector_entry, start_pos);
1130 /* Destination will be NULL if we have no entries */
1131 if (!destination || !e_destination_get_contact (destination))
1132 continue;
1134 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
1135 attr->start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
1136 attr->end_index = g_utf8_offset_to_pointer (text, end_pos) - text;
1137 pango_attr_list_insert (attr_list, attr);
1140 pango_layout_set_attributes (layout, attr_list);
1143 static gboolean
1144 draw_event (ENameSelectorEntry *name_selector_entry)
1146 PangoLayout *layout;
1148 layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
1149 pango_layout_set_attributes (layout, name_selector_entry->priv->attr_list);
1151 return FALSE;
1154 static void
1155 type_ahead_complete (ENameSelectorEntry *name_selector_entry)
1157 EContact *contact;
1158 EBookClient *book_client = NULL;
1159 EContactField matched_field = E_CONTACT_FIELD_LAST;
1160 EDestination *destination;
1161 gint matched_email_num = -1;
1162 gint cursor_pos;
1163 gint range_start = 0;
1164 gint range_end = 0;
1165 gint pos = 0;
1166 gchar *textrep;
1167 gint textrep_len;
1168 gint range_len;
1169 const gchar *text;
1170 gchar *cue_str;
1171 gchar *temp_str;
1172 ENameSelectorEntryPrivate *priv;
1174 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1176 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1177 if (cursor_pos < 0)
1178 return;
1180 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1181 get_range_at_position (text, cursor_pos, &range_start, &range_end);
1182 range_len = range_end - range_start;
1183 if (range_len < priv->minimum_query_length)
1184 return;
1186 destination = find_destination_at_position (name_selector_entry, cursor_pos);
1188 cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
1189 if (!find_existing_completion (name_selector_entry, cue_str, &contact,
1190 &textrep, &matched_field, &matched_email_num, &book_client)) {
1191 g_free (cue_str);
1192 return;
1195 temp_str = sanitize_string (textrep);
1196 g_free (textrep);
1197 textrep = temp_str;
1199 textrep_len = g_utf8_strlen (textrep, -1);
1200 pos = range_start;
1202 g_signal_handlers_block_by_func (
1203 name_selector_entry,
1204 user_insert_text, name_selector_entry);
1205 g_signal_handlers_block_by_func (
1206 name_selector_entry,
1207 user_delete_text, name_selector_entry);
1208 g_signal_handlers_block_by_func (
1209 name_selector_entry->priv->destination_store,
1210 destination_row_changed, name_selector_entry);
1212 if (textrep_len > range_len) {
1213 gint i;
1215 /* keep character's case as user types */
1216 for (i = 0; textrep[i] && cue_str[i]; i++)
1217 textrep[i] = cue_str[i];
1219 gtk_editable_delete_text (
1220 GTK_EDITABLE (name_selector_entry),
1221 range_start, range_end);
1222 gtk_editable_insert_text (
1223 GTK_EDITABLE (name_selector_entry),
1224 textrep, -1, &pos);
1225 gtk_editable_select_region (
1226 GTK_EDITABLE (name_selector_entry),
1227 range_end, range_start + textrep_len);
1228 priv->is_completing = TRUE;
1230 g_free (cue_str);
1232 if (contact && destination) {
1233 gint email_n = 0;
1235 if (matched_field == E_CONTACT_EMAIL)
1236 email_n = matched_email_num;
1237 e_destination_set_contact (destination, contact, email_n);
1238 if (book_client)
1239 e_destination_set_client (destination, book_client);
1240 generate_attribute_list (name_selector_entry);
1243 g_signal_handlers_unblock_by_func (
1244 name_selector_entry->priv->destination_store,
1245 destination_row_changed, name_selector_entry);
1246 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1247 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1249 g_free (textrep);
1252 static void
1253 clear_completion_model (ENameSelectorEntry *name_selector_entry)
1255 ENameSelectorEntryPrivate *priv;
1257 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1259 if (!name_selector_entry->priv->contact_store)
1260 return;
1262 e_contact_store_set_query (name_selector_entry->priv->contact_store, NULL);
1263 g_hash_table_remove_all (name_selector_entry->priv->known_contacts);
1264 priv->is_completing = FALSE;
1267 static void
1268 update_completion_model (ENameSelectorEntry *name_selector_entry)
1270 const gchar *text;
1271 gint cursor_pos;
1272 gint range_start = 0;
1273 gint range_end = 0;
1275 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1276 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1278 if (cursor_pos >= 0)
1279 get_range_at_position (text, cursor_pos, &range_start, &range_end);
1281 if (range_end - range_start >= name_selector_entry->priv->minimum_query_length && cursor_pos == range_end) {
1282 gchar *cue_str;
1284 cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
1285 set_completion_query (name_selector_entry, cue_str);
1286 g_free (cue_str);
1288 g_hash_table_remove_all (name_selector_entry->priv->known_contacts);
1289 } else {
1290 /* N/A; Clear completion model */
1291 clear_completion_model (name_selector_entry);
1295 static gboolean
1296 type_ahead_complete_on_timeout_cb (gpointer user_data)
1298 ENameSelectorEntry *name_selector_entry;
1300 name_selector_entry = E_NAME_SELECTOR_ENTRY (user_data);
1301 type_ahead_complete (name_selector_entry);
1302 name_selector_entry->priv->type_ahead_complete_cb_id = 0;
1304 return FALSE;
1307 static gboolean
1308 update_completions_on_timeout_cb (gpointer user_data)
1310 ENameSelectorEntry *name_selector_entry;
1312 name_selector_entry = E_NAME_SELECTOR_ENTRY (user_data);
1313 update_completion_model (name_selector_entry);
1314 name_selector_entry->priv->update_completions_cb_id = 0;
1316 return FALSE;
1319 static void
1320 insert_destination_at_position (ENameSelectorEntry *name_selector_entry,
1321 gint pos)
1323 EDestination *destination;
1324 const gchar *text;
1325 gint index;
1327 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1328 index = get_index_at_position (text, pos);
1330 destination = build_destination_at_position (text, pos);
1331 g_return_if_fail (destination);
1333 g_signal_handlers_block_by_func (
1334 name_selector_entry->priv->destination_store,
1335 destination_row_inserted, name_selector_entry);
1336 e_destination_store_insert_destination (
1337 name_selector_entry->priv->destination_store,
1338 index, destination);
1339 g_signal_handlers_unblock_by_func (
1340 name_selector_entry->priv->destination_store,
1341 destination_row_inserted, name_selector_entry);
1342 g_object_unref (destination);
1345 static void
1346 modify_destination_at_position (ENameSelectorEntry *name_selector_entry,
1347 gint pos)
1349 EDestination *destination;
1350 const gchar *text;
1351 gchar *raw_address;
1352 gboolean rebuild_attributes = FALSE;
1354 destination = find_destination_at_position (name_selector_entry, pos);
1355 if (!destination)
1356 return;
1358 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1359 raw_address = get_address_at_position (text, pos);
1360 g_return_if_fail (raw_address);
1362 if (e_destination_get_contact (destination))
1363 rebuild_attributes = TRUE;
1365 g_signal_handlers_block_by_func (
1366 name_selector_entry->priv->destination_store,
1367 destination_row_changed, name_selector_entry);
1368 e_destination_set_raw (destination, raw_address);
1369 g_signal_handlers_unblock_by_func (
1370 name_selector_entry->priv->destination_store,
1371 destination_row_changed, name_selector_entry);
1373 g_free (raw_address);
1375 if (rebuild_attributes)
1376 generate_attribute_list (name_selector_entry);
1379 static gchar *
1380 get_destination_textrep (ENameSelectorEntry *name_selector_entry,
1381 EDestination *destination)
1383 gboolean show_email = e_name_selector_entry_get_show_address (name_selector_entry);
1384 EContact *contact;
1386 g_return_val_if_fail (destination != NULL, NULL);
1388 contact = e_destination_get_contact (destination);
1390 if (!show_email) {
1391 if (contact && !e_contact_get (contact, E_CONTACT_IS_LIST)) {
1392 GList *email_list;
1394 email_list = e_contact_get (contact, E_CONTACT_EMAIL);
1395 show_email = g_list_length (email_list) > 1;
1396 deep_free_list (email_list);
1400 /* do not show emails for contact lists even user forces it */
1401 if (show_email && contact && e_contact_get (contact, E_CONTACT_IS_LIST))
1402 show_email = FALSE;
1404 return sanitize_string (e_destination_get_textrep (destination, show_email));
1407 static void
1408 sync_destination_at_position (ENameSelectorEntry *name_selector_entry,
1409 gint range_pos,
1410 gint *cursor_pos)
1412 EDestination *destination;
1413 const gchar *text;
1414 gchar *address;
1415 gint address_len;
1416 gint range_start, range_end;
1418 /* Get the destination we're looking at. Note that the entry may be empty, and so
1419 * there may not be one. */
1420 destination = find_destination_at_position (name_selector_entry, range_pos);
1421 if (!destination)
1422 return;
1424 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1425 if (!get_range_at_position (text, range_pos, &range_start, &range_end)) {
1426 g_warning ("ENameSelectorEntry is out of sync with model!");
1427 return;
1430 address = get_destination_textrep (name_selector_entry, destination);
1431 address_len = g_utf8_strlen (address, -1);
1433 if (cursor_pos) {
1434 /* Update cursor placement */
1435 if (*cursor_pos >= range_end)
1436 *cursor_pos += address_len - (range_end - range_start);
1437 else if (*cursor_pos > range_start)
1438 *cursor_pos = range_start + address_len;
1441 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1442 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1444 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1445 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), address, -1, &range_start);
1447 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1448 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1450 generate_attribute_list (name_selector_entry);
1451 g_free (address);
1454 static void
1455 remove_destination_by_index (ENameSelectorEntry *name_selector_entry,
1456 gint index)
1458 EDestination *destination;
1460 destination = find_destination_by_index (name_selector_entry, index);
1461 if (destination) {
1462 g_signal_handlers_block_by_func (
1463 name_selector_entry->priv->destination_store,
1464 destination_row_deleted, name_selector_entry);
1465 e_destination_store_remove_destination (
1466 name_selector_entry->priv->destination_store,
1467 destination);
1468 g_signal_handlers_unblock_by_func (
1469 name_selector_entry->priv->destination_store,
1470 destination_row_deleted, name_selector_entry);
1474 static void
1475 post_insert_update (ENameSelectorEntry *name_selector_entry,
1476 gint position)
1478 const gchar *text;
1479 glong length;
1481 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1482 length = g_utf8_strlen (text, -1);
1483 text = g_utf8_next_char (text);
1485 if (*text == '\0') {
1486 /* First and only character, create initial destination. */
1487 insert_destination_at_position (name_selector_entry, 0);
1488 } else {
1489 /* Modified an existing destination. */
1490 modify_destination_at_position (name_selector_entry, position);
1493 /* If editing within the string, regenerate attributes. */
1494 if (position < length)
1495 generate_attribute_list (name_selector_entry);
1498 /* Returns the number of characters inserted */
1499 static gint
1500 insert_unichar (ENameSelectorEntry *name_selector_entry,
1501 gint *pos,
1502 gunichar c)
1504 const gchar *text;
1505 gunichar str_context[4];
1506 gchar buf[7];
1507 gint len;
1509 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1510 get_utf8_string_context (text, *pos, str_context, 4);
1512 /* Space is not allowed:
1513 * - Before or after another space.
1514 * - At start of string. */
1516 if (c == ' ' && (str_context[1] == ' ' || str_context[1] == '\0' || str_context[2] == ' '))
1517 return 0;
1519 /* Comma is not allowed:
1520 * - After another comma.
1521 * - At start of string. */
1523 if (c == ',' && !is_quoted_at (text, *pos)) {
1524 gint start_pos;
1525 gint end_pos;
1526 gboolean at_start = FALSE;
1527 gboolean at_end = FALSE;
1529 if (str_context[1] == ',' || str_context[1] == '\0')
1530 return 0;
1532 /* We do this so we can avoid disturbing destinations with completed contacts
1533 * either before or after the destination being inserted. */
1534 get_range_at_position (text, *pos, &start_pos, &end_pos);
1535 if (*pos <= start_pos)
1536 at_start = TRUE;
1537 if (*pos >= end_pos)
1538 at_end = TRUE;
1540 /* Must insert comma first, so modify_destination_at_position can do its job
1541 * correctly, splitting up the contact if necessary. */
1542 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, pos);
1544 /* Update model */
1545 g_return_val_if_fail (*pos >= 2, 0);
1547 /* If we inserted the comma at the end of, or in the middle of, an existing
1548 * address, add a new destination for what appears after comma. Else, we
1549 * have to add a destination for what appears before comma (a blank one). */
1550 if (at_end) {
1551 /* End: Add last, sync first */
1552 insert_destination_at_position (name_selector_entry, *pos);
1553 sync_destination_at_position (name_selector_entry, *pos - 2, pos);
1554 /* Sync generates the attributes list */
1555 } else if (at_start) {
1556 /* Start: Add first */
1557 insert_destination_at_position (name_selector_entry, *pos - 2);
1558 generate_attribute_list (name_selector_entry);
1559 } else {
1560 /* Middle: */
1561 insert_destination_at_position (name_selector_entry, *pos);
1562 modify_destination_at_position (name_selector_entry, *pos - 2);
1563 generate_attribute_list (name_selector_entry);
1566 return 2;
1569 /* Generic case. Allowed spaces also end up here. */
1571 len = g_unichar_to_utf8 (c, buf);
1572 buf[len] = '\0';
1574 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), buf, -1, pos);
1576 post_insert_update (name_selector_entry, *pos);
1578 return 1;
1581 static void
1582 user_insert_text (ENameSelectorEntry *name_selector_entry,
1583 gchar *new_text,
1584 gint new_text_length,
1585 gint *position,
1586 gpointer user_data)
1588 gint chars_inserted = 0;
1589 gboolean fast_insert;
1591 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1592 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1594 fast_insert =
1595 (g_utf8_strchr (new_text, new_text_length, ' ') == NULL) &&
1596 (g_utf8_strchr (new_text, new_text_length, ',') == NULL);
1598 /* If the text to insert does not contain spaces or commas,
1599 * insert all of it at once. This avoids confusing on-going
1600 * input method behavior. */
1601 if (fast_insert) {
1602 gint old_position = *position;
1604 gtk_editable_insert_text (
1605 GTK_EDITABLE (name_selector_entry),
1606 new_text, new_text_length, position);
1608 chars_inserted = *position - old_position;
1609 if (chars_inserted > 0)
1610 post_insert_update (name_selector_entry, *position);
1612 /* Otherwise, apply some rules as to where spaces and commas
1613 * can be inserted, and insert a trailing space after comma. */
1614 } else {
1615 const gchar *cp;
1617 for (cp = new_text; *cp; cp = g_utf8_next_char (cp)) {
1618 gunichar uc = g_utf8_get_char (cp);
1619 insert_unichar (name_selector_entry, position, uc);
1620 chars_inserted++;
1624 if (chars_inserted >= 1) {
1625 /* If the user inserted one character, kick off completion */
1626 re_set_timeout (
1627 name_selector_entry->priv->update_completions_cb_id,
1628 update_completions_on_timeout_cb, name_selector_entry,
1629 AUTOCOMPLETE_TIMEOUT);
1630 re_set_timeout (
1631 name_selector_entry->priv->type_ahead_complete_cb_id,
1632 type_ahead_complete_on_timeout_cb, name_selector_entry,
1633 AUTOCOMPLETE_TIMEOUT);
1636 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1637 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1639 g_signal_stop_emission_by_name (name_selector_entry, "insert_text");
1642 static void
1643 user_delete_text (ENameSelectorEntry *name_selector_entry,
1644 gint start_pos,
1645 gint end_pos,
1646 gpointer user_data)
1648 const gchar *text;
1649 gint index_start, index_end;
1650 gint selection_start, selection_end;
1651 gunichar str_context[2], str_b_context[2];
1652 gint len;
1653 gint i;
1654 gboolean del_space = FALSE, del_comma = FALSE;
1656 if (start_pos == end_pos)
1657 return;
1659 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1660 len = g_utf8_strlen (text, -1);
1662 if (end_pos == -1)
1663 end_pos = len;
1665 gtk_editable_get_selection_bounds (
1666 GTK_EDITABLE (name_selector_entry),
1667 &selection_start, &selection_end);
1669 get_utf8_string_context (text, start_pos, str_context, 2);
1670 get_utf8_string_context (text, end_pos, str_b_context, 2);
1672 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1674 if (end_pos - start_pos == 1) {
1675 /* Might be backspace; update completion model so dropdown is accurate */
1676 re_set_timeout (
1677 name_selector_entry->priv->update_completions_cb_id,
1678 update_completions_on_timeout_cb, name_selector_entry,
1679 AUTOCOMPLETE_TIMEOUT);
1682 index_start = get_index_at_position (text, start_pos);
1683 index_end = get_index_at_position (text, end_pos);
1685 g_signal_stop_emission_by_name (name_selector_entry, "delete_text");
1687 /* If the deletion touches more than one destination, the first one is changed
1688 * and the rest are removed. If the last destination wasn't completely deleted,
1689 * it becomes part of the first one, since the separator between them was
1690 * removed.
1692 * Here, we let the model know about removals. */
1693 for (i = index_end; i > index_start; i--) {
1694 EDestination *destination = find_destination_by_index (name_selector_entry, i);
1695 gint range_start, range_end;
1696 gchar *ttext;
1697 const gchar *email = NULL;
1698 gboolean sel = FALSE;
1700 if (destination)
1701 email = e_destination_get_textrep (destination, TRUE);
1703 if (!email || !*email)
1704 continue;
1706 if (!get_range_by_index (text, i, &range_start, &range_end)) {
1707 g_warning ("ENameSelectorEntry is out of sync with model!");
1708 return;
1711 if ((selection_start < range_start && selection_end > range_start) ||
1712 (selection_end > range_start && selection_end < range_end))
1713 sel = TRUE;
1715 if (!sel) {
1716 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1717 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1719 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1721 ttext = sanitize_string (email);
1722 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
1723 g_free (ttext);
1725 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1726 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1730 remove_destination_by_index (name_selector_entry, i);
1733 /* Do the actual deletion */
1735 if (end_pos == start_pos +1 && index_end == index_start) {
1736 /* We could be just deleting the empty text */
1737 gchar *c;
1739 /* Get the actual deleted text */
1740 c = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), start_pos, start_pos + 1);
1742 if ( c[0] == ' ') {
1743 /* If we are at the beginning or removing junk space, let us ignore it */
1744 del_space = TRUE;
1746 g_free (c);
1747 } else if (end_pos == start_pos +1 && index_end == index_start + 1) {
1748 /* We could be just deleting the empty text */
1749 gchar *c;
1751 /* Get the actual deleted text */
1752 c = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), start_pos, start_pos + 1);
1754 if ( c[0] == ',' && !is_quoted_at (text, start_pos)) {
1755 /* If we are at the beginning or removing junk space, let us ignore it */
1756 del_comma = TRUE;
1758 g_free (c);
1761 if (del_comma) {
1762 gint range_start=-1, range_end;
1763 EDestination *dest = find_destination_by_index (name_selector_entry, index_end);
1764 /* If we have deleted the last comma, let us autocomplete normally
1767 if (dest && len - end_pos != 0) {
1769 EDestination *destination1 = find_destination_by_index (name_selector_entry, index_start);
1770 gchar *ttext;
1771 const gchar *email = NULL;
1773 if (destination1)
1774 email = e_destination_get_textrep (destination1, TRUE);
1776 if (email && *email) {
1778 if (!get_range_by_index (text, i, &range_start, &range_end)) {
1779 g_warning ("ENameSelectorEntry is out of sync with model!");
1780 return;
1783 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1784 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1786 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1788 ttext = sanitize_string (email);
1789 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
1790 g_free (ttext);
1792 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1793 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1796 if (range_start != -1) {
1797 start_pos = range_start;
1798 end_pos = start_pos + 1;
1799 gtk_editable_set_position (GTK_EDITABLE (name_selector_entry),start_pos);
1803 gtk_editable_delete_text (
1804 GTK_EDITABLE (name_selector_entry),
1805 start_pos, end_pos);
1807 /*If the user is deleting a '"' new destinations have to be created for ',' between the quoted text
1808 Like "fd,ty,uy" is a one entity, but if you remove the quotes it has to be broken doan into 3 seperate
1809 addresses.
1812 if (str_b_context[1] == '"') {
1813 const gchar *p;
1814 gint j;
1815 p = text + end_pos;
1816 for (p = text + (end_pos - 1), j = end_pos - 1; *p && *p != '"' ; p = g_utf8_next_char (p), j++) {
1817 gunichar c = g_utf8_get_char (p);
1818 if (c == ',') {
1819 insert_destination_at_position (name_selector_entry, j + 1);
1825 /* Let model know about changes */
1826 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1827 if (!*text || strlen (text) <= 0) {
1828 /* If the entry was completely cleared, remove the initial destination too */
1829 remove_destination_by_index (name_selector_entry, 0);
1830 generate_attribute_list (name_selector_entry);
1831 } else if (!del_space) {
1832 modify_destination_at_position (name_selector_entry, start_pos);
1835 /* If editing within the string, we need to regenerate attributes */
1836 if (end_pos < len)
1837 generate_attribute_list (name_selector_entry);
1839 /* Prevent type-ahead completion */
1840 if (name_selector_entry->priv->type_ahead_complete_cb_id) {
1841 g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
1842 name_selector_entry->priv->type_ahead_complete_cb_id = 0;
1845 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1848 static gboolean
1849 completion_match_selected (ENameSelectorEntry *name_selector_entry,
1850 ETreeModelGenerator *email_generator_model,
1851 GtkTreeIter *generator_iter)
1853 EContact *contact;
1854 EBookClient *book_client;
1855 EDestination *destination;
1856 gint cursor_pos;
1857 GtkTreeIter contact_iter;
1858 gint email_n;
1860 if (!name_selector_entry->priv->contact_store)
1861 return FALSE;
1863 g_return_val_if_fail (name_selector_entry->priv->email_generator == email_generator_model, FALSE);
1865 e_tree_model_generator_convert_iter_to_child_iter (
1866 email_generator_model,
1867 &contact_iter, &email_n,
1868 generator_iter);
1870 contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_iter);
1871 book_client = e_contact_store_get_client (name_selector_entry->priv->contact_store, &contact_iter);
1872 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1874 /* Set the contact in the model's destination */
1876 destination = find_destination_at_position (name_selector_entry, cursor_pos);
1877 e_destination_set_contact (destination, contact, email_n);
1878 if (book_client)
1879 e_destination_set_client (destination, book_client);
1880 sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
1882 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1883 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &cursor_pos);
1884 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1886 /*Add destination at end for next entry*/
1887 insert_destination_at_position (name_selector_entry, cursor_pos);
1888 /* Place cursor at end of address */
1890 gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), cursor_pos);
1891 g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
1892 return TRUE;
1895 static void
1896 entry_activate (ENameSelectorEntry *name_selector_entry)
1898 gint cursor_pos;
1899 gint range_start, range_end;
1900 ENameSelectorEntryPrivate *priv;
1901 EDestination *destination;
1902 gint range_len;
1903 const gchar *text;
1904 gchar *cue_str;
1906 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1907 if (cursor_pos < 0)
1908 return;
1910 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1912 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1913 if (!get_range_at_position (text, cursor_pos, &range_start, &range_end))
1914 return;
1916 range_len = range_end - range_start;
1917 if (range_len < priv->minimum_query_length)
1918 return;
1920 destination = find_destination_at_position (name_selector_entry, cursor_pos);
1921 if (!destination)
1922 return;
1924 cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
1925 #if 0
1926 if (!find_existing_completion (name_selector_entry, cue_str, &contact,
1927 &textrep, &matched_field)) {
1928 g_free (cue_str);
1929 return;
1931 #endif
1932 g_free (cue_str);
1933 sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
1935 /* Place cursor at end of address */
1936 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1937 get_range_at_position (text, cursor_pos, &range_start, &range_end);
1939 if (priv->is_completing) {
1940 gchar *str_context = NULL;
1942 str_context = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), range_end, range_end + 1);
1944 if (str_context[0] != ',') {
1945 /* At the end*/
1946 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &range_end);
1947 } else {
1948 /* In the middle */
1949 gint newpos = strlen (text);
1951 /* Doing this we can make sure that It wont ask for completion again. */
1952 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &newpos);
1953 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1954 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), newpos - 2, newpos);
1955 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1957 /* Move it close to next destination*/
1958 range_end = range_end + 2;
1961 g_free (str_context);
1964 gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), range_end);
1965 g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
1967 if (priv->is_completing)
1968 clear_completion_model (name_selector_entry);
1971 static void
1972 update_text (ENameSelectorEntry *name_selector_entry,
1973 const gchar *text)
1975 gint start = 0, end = 0;
1976 gboolean has_selection;
1978 has_selection = gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), &start, &end);
1980 gtk_entry_set_text (GTK_ENTRY (name_selector_entry), text);
1982 if (has_selection)
1983 gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), start, end);
1986 static void
1987 sanitize_entry (ENameSelectorEntry *name_selector_entry)
1989 gint n;
1990 GList *l, *known, *del = NULL;
1991 GString *str = g_string_new ("");
1993 g_signal_handlers_block_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
1994 g_signal_handlers_block_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
1996 known = e_destination_store_list_destinations (name_selector_entry->priv->destination_store);
1997 for (l = known, n = 0; l != NULL; l = l->next, n++) {
1998 EDestination *dest = l->data;
2000 if (!dest || !e_destination_get_address (dest))
2001 del = g_list_prepend (del, GINT_TO_POINTER (n));
2002 else {
2003 gchar *text;
2005 text = get_destination_textrep (name_selector_entry, dest);
2006 if (text) {
2007 if (str->str && str->str[0])
2008 g_string_append (str, ", ");
2010 g_string_append (str, text);
2012 g_free (text);
2015 g_list_free (known);
2017 for (l = del; l != NULL; l = l->next) {
2018 e_destination_store_remove_destination_nth (name_selector_entry->priv->destination_store, GPOINTER_TO_INT (l->data));
2020 g_list_free (del);
2022 update_text (name_selector_entry, str->str);
2024 g_string_free (str, TRUE);
2026 g_signal_handlers_unblock_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2027 g_signal_handlers_unblock_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2029 generate_attribute_list (name_selector_entry);
2032 static gboolean
2033 user_focus_in (ENameSelectorEntry *name_selector_entry,
2034 GdkEventFocus *event_focus)
2036 gint n;
2037 GList *l, *known;
2038 GString *str = g_string_new ("");
2039 EDestination *dest_dummy = e_destination_new ();
2041 g_signal_handlers_block_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2042 g_signal_handlers_block_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2044 known = e_destination_store_list_destinations (name_selector_entry->priv->destination_store);
2045 for (l = known, n = 0; l != NULL; l = l->next, n++) {
2046 EDestination *dest = l->data;
2048 if (dest) {
2049 gchar *text;
2051 text = get_destination_textrep (name_selector_entry, dest);
2052 if (text) {
2053 if (str->str && str->str[0])
2054 g_string_append (str, ", ");
2056 g_string_append (str, text);
2058 g_free (text);
2061 g_list_free (known);
2063 /* Add a blank destination */
2064 e_destination_store_append_destination (name_selector_entry->priv->destination_store, dest_dummy);
2065 if (str->str && str->str[0])
2066 g_string_append (str, ", ");
2068 gtk_entry_set_text (GTK_ENTRY (name_selector_entry), str->str);
2070 g_string_free (str, TRUE);
2072 g_signal_handlers_unblock_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2073 g_signal_handlers_unblock_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
2075 generate_attribute_list (name_selector_entry);
2077 return FALSE;
2080 static gboolean
2081 user_focus_out (ENameSelectorEntry *name_selector_entry,
2082 GdkEventFocus *event_focus)
2084 if (!event_focus->in) {
2085 entry_activate (name_selector_entry);
2088 if (name_selector_entry->priv->type_ahead_complete_cb_id) {
2089 g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
2090 name_selector_entry->priv->type_ahead_complete_cb_id = 0;
2093 if (name_selector_entry->priv->update_completions_cb_id) {
2094 g_source_remove (name_selector_entry->priv->update_completions_cb_id);
2095 name_selector_entry->priv->update_completions_cb_id = 0;
2098 clear_completion_model (name_selector_entry);
2100 if (!event_focus->in) {
2101 sanitize_entry (name_selector_entry);
2104 return FALSE;
2107 static gboolean
2108 user_key_press_event_cb (ENameSelectorEntry *name_selector_entry,
2109 GdkEventKey *event_key)
2111 gint end;
2113 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), FALSE);
2114 g_return_val_if_fail (event_key != NULL, FALSE);
2116 if ((event_key->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0 &&
2117 event_key->keyval == GDK_KEY_comma &&
2118 gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), NULL, &end)) {
2119 entry_activate (name_selector_entry);
2121 if (name_selector_entry->priv->type_ahead_complete_cb_id) {
2122 g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
2123 name_selector_entry->priv->type_ahead_complete_cb_id = 0;
2126 if (name_selector_entry->priv->update_completions_cb_id) {
2127 g_source_remove (name_selector_entry->priv->update_completions_cb_id);
2128 name_selector_entry->priv->update_completions_cb_id = 0;
2131 clear_completion_model (name_selector_entry);
2133 sanitize_entry (name_selector_entry);
2135 gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), end, end);
2138 return FALSE;
2141 static void
2142 deep_free_list (GList *list)
2144 GList *l;
2146 for (l = list; l; l = g_list_next (l))
2147 g_free (l->data);
2149 g_list_free (list);
2152 /* Given a widget, determines the height that text will normally be drawn. */
2153 static guint
2154 entry_height (GtkWidget *widget)
2156 PangoLayout *layout;
2157 gint bound;
2159 g_return_val_if_fail (widget != NULL, 0);
2161 layout = gtk_widget_create_pango_layout (widget, NULL);
2163 pango_layout_get_pixel_size (layout, NULL, &bound);
2165 return bound;
2168 static void
2169 contact_layout_pixbuffer (GtkCellLayout *cell_layout,
2170 GtkCellRenderer *cell,
2171 GtkTreeModel *model,
2172 GtkTreeIter *iter,
2173 ENameSelectorEntry *name_selector_entry)
2175 EContact *contact;
2176 GtkTreeIter generator_iter;
2177 GtkTreeIter contact_store_iter;
2178 gint email_n;
2179 EContactPhoto *photo;
2180 gboolean iter_is_valid;
2181 GdkPixbuf *pixbuf = NULL;
2183 if (!name_selector_entry->priv->contact_store)
2184 return;
2186 gtk_tree_model_filter_convert_iter_to_child_iter (
2187 GTK_TREE_MODEL_FILTER (model),
2188 &generator_iter, iter);
2189 iter_is_valid = e_tree_model_generator_convert_iter_to_child_iter (
2190 name_selector_entry->priv->email_generator,
2191 &contact_store_iter, &email_n,
2192 &generator_iter);
2194 if (!iter_is_valid) {
2195 return;
2198 contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_store_iter);
2199 if (!contact) {
2200 g_object_set (cell, "pixbuf", pixbuf, NULL);
2201 return;
2204 photo = e_contact_get (contact, E_CONTACT_PHOTO);
2205 if (photo && photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
2206 guint max_height = entry_height (GTK_WIDGET (name_selector_entry));
2207 GdkPixbufLoader *loader;
2209 loader = gdk_pixbuf_loader_new ();
2210 if (gdk_pixbuf_loader_write (loader, (guchar *) photo->data.inlined.data, photo->data.inlined.length, NULL) &&
2211 gdk_pixbuf_loader_close (loader, NULL)) {
2212 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
2213 if (pixbuf)
2214 g_object_ref (pixbuf);
2216 g_object_unref (loader);
2218 if (pixbuf) {
2219 gint w, h;
2220 gdouble scale = 1.0;
2222 w = gdk_pixbuf_get_width (pixbuf);
2223 h = gdk_pixbuf_get_height (pixbuf);
2225 if (h > w)
2226 scale = max_height / (double) h;
2227 else
2228 scale = max_height / (double) w;
2230 if (scale < 1.0) {
2231 GdkPixbuf *tmp;
2233 tmp = gdk_pixbuf_scale_simple (pixbuf, w * scale, h * scale, GDK_INTERP_BILINEAR);
2234 g_object_unref (pixbuf);
2235 pixbuf = tmp;
2241 e_contact_photo_free (photo);
2243 g_object_set (cell, "pixbuf", pixbuf, NULL);
2245 if (pixbuf)
2246 g_object_unref (pixbuf);
2249 static void
2250 contact_layout_formatter (GtkCellLayout *cell_layout,
2251 GtkCellRenderer *cell,
2252 GtkTreeModel *model,
2253 GtkTreeIter *iter,
2254 ENameSelectorEntry *name_selector_entry)
2256 EContact *contact;
2257 GtkTreeIter generator_iter;
2258 GtkTreeIter contact_store_iter;
2259 GList *email_list;
2260 gchar *string;
2261 gchar *file_as_str;
2262 gchar *email_str;
2263 gint email_n;
2264 gboolean iter_is_valid;
2266 if (!name_selector_entry->priv->contact_store)
2267 return;
2269 gtk_tree_model_filter_convert_iter_to_child_iter (
2270 GTK_TREE_MODEL_FILTER (model),
2271 &generator_iter, iter);
2272 iter_is_valid = e_tree_model_generator_convert_iter_to_child_iter (
2273 name_selector_entry->priv->email_generator,
2274 &contact_store_iter, &email_n,
2275 &generator_iter);
2277 if (!iter_is_valid) {
2278 return;
2281 contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_store_iter);
2282 email_list = e_contact_get (contact, E_CONTACT_EMAIL);
2283 email_str = g_list_nth_data (email_list, email_n);
2284 file_as_str = e_contact_get (contact, E_CONTACT_FILE_AS);
2286 if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
2287 string = g_strdup_printf ("%s", file_as_str ? file_as_str : "?");
2288 } else {
2289 string = g_strdup_printf (
2290 "%s%s<%s>", file_as_str ? file_as_str : "",
2291 file_as_str ? " " : "",
2292 email_str ? email_str : "");
2295 g_free (file_as_str);
2296 deep_free_list (email_list);
2298 g_object_set (cell, "text", string, NULL);
2299 g_free (string);
2302 static gint
2303 generate_contact_rows (EContactStore *contact_store,
2304 GtkTreeIter *iter,
2305 ENameSelectorEntry *name_selector_entry)
2307 EContact *contact;
2308 const gchar *contact_uid;
2309 GList *email_list;
2310 gint n_rows;
2312 contact = e_contact_store_get_contact (contact_store, iter);
2313 g_return_val_if_fail (contact != NULL, 0);
2315 contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
2316 if (!contact_uid)
2317 return 0; /* Can happen with broken databases */
2319 if (is_duplicate_contact_and_remember (name_selector_entry, contact))
2320 return 0;
2322 if (e_contact_get (contact, E_CONTACT_IS_LIST))
2323 return 1;
2325 email_list = e_contact_get (contact, E_CONTACT_EMAIL);
2326 n_rows = g_list_length (email_list);
2327 deep_free_list (email_list);
2329 return n_rows;
2332 static void
2333 ensure_type_ahead_complete_on_timeout (ENameSelectorEntry *name_selector_entry)
2335 /* this is called whenever a new item is added to the model,
2336 * thus, to not starve when there are many matches, do not
2337 * postpone on each add, but show results as soon as possible */
2338 if (!name_selector_entry->priv->type_ahead_complete_cb_id) {
2339 re_set_timeout (
2340 name_selector_entry->priv->type_ahead_complete_cb_id,
2341 type_ahead_complete_on_timeout_cb, name_selector_entry,
2342 SHOW_RESULT_TIMEOUT);
2346 static void
2347 setup_contact_store (ENameSelectorEntry *name_selector_entry)
2349 if (name_selector_entry->priv->email_generator) {
2350 g_object_unref (name_selector_entry->priv->email_generator);
2351 name_selector_entry->priv->email_generator = NULL;
2354 if (name_selector_entry->priv->contact_store) {
2355 name_selector_entry->priv->email_generator =
2356 e_tree_model_generator_new (
2357 GTK_TREE_MODEL (
2358 name_selector_entry->priv->contact_store));
2360 e_tree_model_generator_set_generate_func (
2361 name_selector_entry->priv->email_generator,
2362 (ETreeModelGeneratorGenerateFunc) generate_contact_rows,
2363 name_selector_entry, NULL);
2365 /* Assign the store to the entry completion */
2367 gtk_entry_completion_set_model (
2368 name_selector_entry->priv->entry_completion,
2369 GTK_TREE_MODEL (
2370 name_selector_entry->priv->email_generator));
2372 /* Set up callback for incoming matches */
2373 g_signal_connect_swapped (
2374 name_selector_entry->priv->contact_store, "row-inserted",
2375 G_CALLBACK (ensure_type_ahead_complete_on_timeout), name_selector_entry);
2376 } else {
2377 /* Remove the store from the entry completion */
2379 gtk_entry_completion_set_model (name_selector_entry->priv->entry_completion, NULL);
2383 static void
2384 name_selector_entry_get_client_cb (GObject *source_object,
2385 GAsyncResult *result,
2386 gpointer user_data)
2388 EContactStore *contact_store = user_data;
2389 EBookClient *book_client;
2390 EClient *client;
2391 GError *error = NULL;
2393 client = e_client_cache_get_client_finish (
2394 E_CLIENT_CACHE (source_object), result, &error);
2396 /* Sanity check. */
2397 g_return_if_fail (
2398 ((client != NULL) && (error == NULL)) ||
2399 ((client == NULL) && (error != NULL)));
2401 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2402 g_error_free (error);
2403 goto exit;
2406 if (error != NULL) {
2407 g_warning ("%s: %s", G_STRFUNC, error->message);
2408 g_error_free (error);
2409 goto exit;
2412 book_client = E_BOOK_CLIENT (client);
2414 g_return_if_fail (E_IS_BOOK_CLIENT (book_client));
2415 e_contact_store_add_client (contact_store, book_client);
2416 g_object_unref (book_client);
2418 exit:
2419 g_object_unref (contact_store);
2422 static void
2423 setup_default_contact_store (ENameSelectorEntry *name_selector_entry)
2425 EClientCache *client_cache;
2426 ESourceRegistry *registry;
2427 EContactStore *contact_store;
2428 GList *list, *iter;
2429 const gchar *extension_name;
2431 g_return_if_fail (name_selector_entry->priv->contact_store == NULL);
2433 /* Create a book for each completion source, and assign them to the contact store */
2435 contact_store = e_contact_store_new ();
2436 name_selector_entry->priv->contact_store = contact_store;
2438 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
2439 client_cache = e_name_selector_entry_ref_client_cache (name_selector_entry);
2440 registry = e_client_cache_ref_registry (client_cache);
2442 list = e_source_registry_list_enabled (registry, extension_name);
2444 for (iter = list; iter != NULL; iter = g_list_next (iter)) {
2445 ESource *source = E_SOURCE (iter->data);
2446 ESourceAutocomplete *extension;
2447 GCancellable *cancellable;
2448 const gchar *extension_name;
2450 extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
2451 extension = e_source_get_extension (source, extension_name);
2453 /* Skip non-completion address books. */
2454 if (!e_source_autocomplete_get_include_me (extension))
2455 continue;
2457 cancellable = g_cancellable_new ();
2459 g_queue_push_tail (
2460 &name_selector_entry->priv->cancellables,
2461 cancellable);
2463 e_client_cache_get_client (
2464 client_cache, source,
2465 E_SOURCE_EXTENSION_ADDRESS_BOOK, (guint32) -1,
2466 cancellable,
2467 name_selector_entry_get_client_cb,
2468 g_object_ref (contact_store));
2471 g_list_free_full (list, (GDestroyNotify) g_object_unref);
2473 g_object_unref (registry);
2474 g_object_unref (client_cache);
2476 setup_contact_store (name_selector_entry);
2479 static void
2480 destination_row_changed (ENameSelectorEntry *name_selector_entry,
2481 GtkTreePath *path,
2482 GtkTreeIter *iter)
2484 EDestination *destination;
2485 const gchar *entry_text;
2486 gchar *text;
2487 gint range_start, range_end;
2488 gint n;
2490 n = gtk_tree_path_get_indices (path)[0];
2491 destination = e_destination_store_get_destination (name_selector_entry->priv->destination_store, iter);
2493 if (!destination)
2494 return;
2496 g_return_if_fail (n >= 0);
2498 entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2499 if (!get_range_by_index (entry_text, n, &range_start, &range_end)) {
2500 g_warning ("ENameSelectorEntry is out of sync with model!");
2501 return;
2504 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2505 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2507 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
2509 text = get_destination_textrep (name_selector_entry, destination);
2510 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &range_start);
2511 g_free (text);
2513 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2514 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2516 clear_completion_model (name_selector_entry);
2517 generate_attribute_list (name_selector_entry);
2520 static void
2521 destination_row_inserted (ENameSelectorEntry *name_selector_entry,
2522 GtkTreePath *path,
2523 GtkTreeIter *iter)
2525 EDestination *destination;
2526 const gchar *entry_text;
2527 gchar *text;
2528 gboolean comma_before = FALSE;
2529 gboolean comma_after = FALSE;
2530 gint range_start, range_end;
2531 gint insert_pos;
2532 gint n;
2534 n = gtk_tree_path_get_indices (path)[0];
2535 destination = e_destination_store_get_destination (name_selector_entry->priv->destination_store, iter);
2537 g_return_if_fail (n >= 0);
2538 g_return_if_fail (destination != NULL);
2540 entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2542 if (get_range_by_index (entry_text, n, &range_start, &range_end) && range_start != range_end) {
2543 /* Another destination comes after us */
2544 insert_pos = range_start;
2545 comma_after = TRUE;
2546 } else if (n > 0 && get_range_by_index (entry_text, n - 1, &range_start, &range_end)) {
2547 /* Another destination comes before us */
2548 insert_pos = range_end;
2549 comma_before = TRUE;
2550 } else if (n == 0) {
2551 /* We're the sole destination */
2552 insert_pos = 0;
2553 } else {
2554 g_warning ("ENameSelectorEntry is out of sync with model!");
2555 return;
2558 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2560 if (comma_before)
2561 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
2563 text = get_destination_textrep (name_selector_entry, destination);
2564 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &insert_pos);
2565 g_free (text);
2567 if (comma_after)
2568 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
2570 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2572 clear_completion_model (name_selector_entry);
2573 generate_attribute_list (name_selector_entry);
2576 static void
2577 destination_row_deleted (ENameSelectorEntry *name_selector_entry,
2578 GtkTreePath *path)
2580 const gchar *text;
2581 gboolean deleted_comma = FALSE;
2582 gint range_start, range_end;
2583 gchar *p0;
2584 gint n;
2586 n = gtk_tree_path_get_indices (path)[0];
2587 g_return_if_fail (n >= 0);
2589 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2591 if (!get_range_by_index (text, n, &range_start, &range_end)) {
2592 g_warning ("ENameSelectorEntry is out of sync with model!");
2593 return;
2596 /* Expand range for deletion forwards */
2597 for (p0 = g_utf8_offset_to_pointer (text, range_end); *p0;
2598 p0 = g_utf8_next_char (p0), range_end++) {
2599 gunichar c = g_utf8_get_char (p0);
2601 /* Gobble spaces directly after comma */
2602 if (c != ' ' && deleted_comma) {
2603 range_end--;
2604 break;
2607 if (c == ',') {
2608 deleted_comma = TRUE;
2609 range_end++;
2613 /* Expand range for deletion backwards */
2614 for (p0 = g_utf8_offset_to_pointer (text, range_start); range_start > 0;
2615 p0 = g_utf8_prev_char (p0), range_start--) {
2616 gunichar c = g_utf8_get_char (p0);
2618 if (c == ',') {
2619 if (!deleted_comma) {
2620 deleted_comma = TRUE;
2621 break;
2624 range_start++;
2626 /* Leave a space in front; we deleted the comma and spaces before the
2627 * following destination */
2628 p0 = g_utf8_next_char (p0);
2629 c = g_utf8_get_char (p0);
2630 if (c == ' ')
2631 range_start++;
2633 break;
2637 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2638 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
2639 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2641 clear_completion_model (name_selector_entry);
2642 generate_attribute_list (name_selector_entry);
2645 static void
2646 setup_destination_store (ENameSelectorEntry *name_selector_entry)
2648 GtkTreeIter iter;
2650 g_signal_connect_swapped (
2651 name_selector_entry->priv->destination_store, "row-changed",
2652 G_CALLBACK (destination_row_changed), name_selector_entry);
2653 g_signal_connect_swapped (
2654 name_selector_entry->priv->destination_store, "row-deleted",
2655 G_CALLBACK (destination_row_deleted), name_selector_entry);
2656 g_signal_connect_swapped (
2657 name_selector_entry->priv->destination_store, "row-inserted",
2658 G_CALLBACK (destination_row_inserted), name_selector_entry);
2660 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter))
2661 return;
2663 do {
2664 GtkTreePath *path;
2666 path = gtk_tree_model_get_path (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter);
2667 g_return_if_fail (path);
2669 destination_row_inserted (name_selector_entry, path, &iter);
2670 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter));
2673 static gboolean
2674 prepare_popup_destination (ENameSelectorEntry *name_selector_entry,
2675 GdkEventButton *event_button)
2677 EDestination *destination;
2678 PangoLayout *layout;
2679 gint layout_offset_x;
2680 gint layout_offset_y;
2681 gint x, y;
2682 gint index;
2684 if (event_button->type != GDK_BUTTON_PRESS)
2685 return FALSE;
2687 if (event_button->button != 3)
2688 return FALSE;
2690 if (name_selector_entry->priv->popup_destination) {
2691 g_object_unref (name_selector_entry->priv->popup_destination);
2692 name_selector_entry->priv->popup_destination = NULL;
2695 gtk_entry_get_layout_offsets (
2696 GTK_ENTRY (name_selector_entry),
2697 &layout_offset_x, &layout_offset_y);
2698 x = (event_button->x + 0.5) - layout_offset_x;
2699 y = (event_button->y + 0.5) - layout_offset_y;
2701 if (x < 0 || y < 0)
2702 return FALSE;
2704 layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
2705 if (!pango_layout_xy_to_index (layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, NULL))
2706 return FALSE;
2708 index = gtk_entry_layout_index_to_text_index (GTK_ENTRY (name_selector_entry), index);
2709 destination = find_destination_at_position (name_selector_entry, index);
2710 /* FIXME: Add this to a private variable, in ENameSelectorEntry Class*/
2711 g_object_set_data ((GObject *) name_selector_entry, "index", GINT_TO_POINTER (index));
2713 if (!destination || !e_destination_get_contact (destination))
2714 return FALSE;
2716 /* TODO: Unref destination when we finalize */
2717 name_selector_entry->priv->popup_destination = g_object_ref (destination);
2718 return FALSE;
2721 static EBookClient *
2722 find_client_by_contact (GSList *clients,
2723 const gchar *contact_uid,
2724 const gchar *source_uid)
2726 GSList *l;
2728 if (source_uid && *source_uid) {
2729 /* this is much quicket than asking each client for an existence */
2730 for (l = clients; l; l = g_slist_next (l)) {
2731 EBookClient *client = l->data;
2732 ESource *source = e_client_get_source (E_CLIENT (client));
2734 if (!source)
2735 continue;
2737 if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
2738 return client;
2742 for (l = clients; l; l = g_slist_next (l)) {
2743 EBookClient *client = l->data;
2744 EContact *contact = NULL;
2745 gboolean result;
2747 result = e_book_client_get_contact_sync (client, contact_uid, &contact, NULL, NULL);
2748 if (contact)
2749 g_object_unref (contact);
2751 if (result)
2752 return client;
2755 return NULL;
2758 static void
2759 editor_closed_cb (GtkWidget *editor,
2760 gpointer data)
2762 EContact *contact;
2763 gchar *contact_uid;
2764 EDestination *destination;
2765 GSList *clients;
2766 EBookClient *book_client;
2767 gint email_num;
2768 ENameSelectorEntry *name_selector_entry = E_NAME_SELECTOR_ENTRY (data);
2770 destination = name_selector_entry->priv->popup_destination;
2771 contact = e_destination_get_contact (destination);
2772 if (!contact) {
2773 g_object_unref (name_selector_entry);
2774 return;
2777 contact_uid = e_contact_get (contact, E_CONTACT_UID);
2778 if (!contact_uid) {
2779 g_object_unref (contact);
2780 g_object_unref (name_selector_entry);
2781 return;
2784 if (name_selector_entry->priv->contact_store) {
2785 clients = e_contact_store_get_clients (name_selector_entry->priv->contact_store);
2786 book_client = find_client_by_contact (clients, contact_uid, e_destination_get_source_uid (destination));
2787 g_slist_free (clients);
2788 } else {
2789 book_client = NULL;
2792 if (book_client) {
2793 contact = NULL;
2795 g_warn_if_fail (e_book_client_get_contact_sync (book_client, contact_uid, &contact, NULL, NULL));
2796 email_num = e_destination_get_email_num (destination);
2797 e_destination_set_contact (destination, contact, email_num);
2798 e_destination_set_client (destination, book_client);
2799 } else {
2800 contact = NULL;
2803 g_free (contact_uid);
2804 if (contact)
2805 g_object_unref (contact);
2806 g_object_unref (name_selector_entry);
2809 /* To parse something like...
2810 * =?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>
2811 * and return the decoded representation of name & email parts.
2812 * */
2813 static gboolean
2814 eab_parse_qp_email (const gchar *string,
2815 gchar **name,
2816 gchar **email)
2818 struct _camel_header_address *address;
2819 gboolean res = FALSE;
2821 address = camel_header_address_decode (string, "UTF-8");
2823 if (!address)
2824 return FALSE;
2826 /* report success only when we have filled both name and email address */
2827 if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name && address->v.addr && *address->v.addr) {
2828 *name = g_strdup (address->name);
2829 *email = g_strdup (address->v.addr);
2830 res = TRUE;
2833 camel_header_address_unref (address);
2835 return res;
2838 static void
2839 popup_activate_inline_expand (ENameSelectorEntry *name_selector_entry,
2840 GtkWidget *menu_item)
2842 const gchar *text;
2843 GString *sanitized_text = g_string_new ("");
2844 EDestination *destination = name_selector_entry->priv->popup_destination;
2845 gint position, start, end;
2846 const GList *dests;
2848 position = GPOINTER_TO_INT (g_object_get_data ((GObject *) name_selector_entry, "index"));
2850 for (dests = e_destination_list_get_dests (destination); dests; dests = dests->next) {
2851 const EDestination *dest = dests->data;
2852 gchar *sanitized;
2853 gchar *name = NULL, *email = NULL, *tofree = NULL;
2855 if (!dest)
2856 continue;
2858 text = e_destination_get_textrep (dest, TRUE);
2860 if (!text || !*text)
2861 continue;
2863 if (eab_parse_qp_email (text, &name, &email)) {
2864 tofree = g_strdup_printf ("%s <%s>", name, email);
2865 text = tofree;
2866 g_free (name);
2867 g_free (email);
2870 sanitized = sanitize_string (text);
2871 g_free (tofree);
2872 if (!sanitized)
2873 continue;
2875 if (*sanitized) {
2876 if (*sanitized_text->str)
2877 g_string_append (sanitized_text, ", ");
2879 g_string_append (sanitized_text, sanitized);
2882 g_free (sanitized);
2885 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
2886 get_range_at_position (text, position, &start, &end);
2887 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), start, end);
2888 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), sanitized_text->str, -1, &start);
2889 g_string_free (sanitized_text, TRUE);
2891 clear_completion_model (name_selector_entry);
2892 generate_attribute_list (name_selector_entry);
2895 static void
2896 popup_activate_contact (ENameSelectorEntry *name_selector_entry,
2897 GtkWidget *menu_item)
2899 EBookClient *book_client;
2900 GSList *clients;
2901 EDestination *destination;
2902 EContact *contact;
2903 gchar *contact_uid;
2905 destination = name_selector_entry->priv->popup_destination;
2906 if (!destination)
2907 return;
2909 contact = e_destination_get_contact (destination);
2910 if (!contact)
2911 return;
2913 contact_uid = e_contact_get (contact, E_CONTACT_UID);
2914 if (!contact_uid)
2915 return;
2917 if (name_selector_entry->priv->contact_store) {
2918 clients = e_contact_store_get_clients (name_selector_entry->priv->contact_store);
2919 book_client = find_client_by_contact (clients, contact_uid, e_destination_get_source_uid (destination));
2920 g_slist_free (clients);
2921 g_free (contact_uid);
2922 } else {
2923 book_client = NULL;
2926 if (!book_client)
2927 return;
2929 if (e_destination_is_evolution_list (destination)) {
2930 GtkWidget *contact_list_editor;
2932 if (!name_selector_entry->priv->contact_list_editor_func)
2933 return;
2935 contact_list_editor = (*name_selector_entry->priv->contact_list_editor_func) (book_client, contact, FALSE, TRUE);
2936 g_object_ref (name_selector_entry);
2937 g_signal_connect (
2938 contact_list_editor, "editor_closed",
2939 G_CALLBACK (editor_closed_cb), name_selector_entry);
2940 } else {
2941 GtkWidget *contact_editor;
2943 if (!name_selector_entry->priv->contact_editor_func)
2944 return;
2946 contact_editor = (*name_selector_entry->priv->contact_editor_func) (book_client, contact, FALSE, TRUE);
2947 g_object_ref (name_selector_entry);
2948 g_signal_connect (
2949 contact_editor, "editor_closed",
2950 G_CALLBACK (editor_closed_cb), name_selector_entry);
2954 static void
2955 popup_activate_email (ENameSelectorEntry *name_selector_entry,
2956 GtkWidget *menu_item)
2958 EDestination *destination;
2959 EContact *contact;
2960 gint email_num;
2962 destination = name_selector_entry->priv->popup_destination;
2963 if (!destination)
2964 return;
2966 contact = e_destination_get_contact (destination);
2967 if (!contact)
2968 return;
2970 email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "order"));
2971 e_destination_set_contact (destination, contact, email_num);
2974 static void
2975 popup_activate_list (EDestination *destination,
2976 GtkWidget *item)
2978 gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
2980 e_destination_set_ignored (destination, !status);
2983 static void
2984 popup_activate_cut (ENameSelectorEntry *name_selector_entry,
2985 GtkWidget *menu_item)
2987 EDestination *destination;
2988 const gchar *contact_email;
2989 gchar *pemail = NULL;
2990 GtkClipboard *clipboard;
2992 destination = name_selector_entry->priv->popup_destination;
2993 contact_email =e_destination_get_textrep (destination, TRUE);
2995 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2996 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2998 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
2999 pemail = g_strconcat (contact_email, ",", NULL);
3000 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3002 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
3003 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3005 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), 0, 0);
3006 e_destination_store_remove_destination (name_selector_entry->priv->destination_store, destination);
3008 g_free (pemail);
3009 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
3010 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
3013 static void
3014 popup_activate_copy (ENameSelectorEntry *name_selector_entry,
3015 GtkWidget *menu_item)
3017 EDestination *destination;
3018 const gchar *contact_email;
3019 gchar *pemail;
3020 GtkClipboard *clipboard;
3022 destination = name_selector_entry->priv->popup_destination;
3023 contact_email = e_destination_get_textrep (destination, TRUE);
3025 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
3026 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
3028 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
3029 pemail = g_strconcat (contact_email, ",", NULL);
3030 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3032 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
3033 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
3034 g_free (pemail);
3035 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
3036 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
3039 static void
3040 destination_set_list (GtkWidget *item,
3041 EDestination *destination)
3043 EContact *contact;
3044 gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
3046 contact = e_destination_get_contact (destination);
3047 if (!contact)
3048 return;
3050 e_destination_set_ignored (destination, !status);
3053 static void
3054 destination_set_email (GtkWidget *item,
3055 EDestination *destination)
3057 gint email_num;
3058 EContact *contact;
3060 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
3061 return;
3062 contact = e_destination_get_contact (destination);
3063 if (!contact)
3064 return;
3066 email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "order"));
3067 e_destination_set_contact (destination, contact, email_num);
3070 static void
3071 populate_popup (ENameSelectorEntry *name_selector_entry,
3072 GtkMenu *menu)
3074 EDestination *destination;
3075 EContact *contact;
3076 GtkWidget *menu_item;
3077 GList *email_list = NULL;
3078 GList *l;
3079 gint i;
3080 gchar *edit_label;
3081 gchar *cut_label;
3082 gchar *copy_label;
3083 gint email_num, len;
3084 GSList *group = NULL;
3085 gboolean is_list;
3086 gboolean show_menu = FALSE;
3088 destination = name_selector_entry->priv->popup_destination;
3089 if (!destination)
3090 return;
3092 contact = e_destination_get_contact (destination);
3093 if (!contact)
3094 return;
3096 /* Prepend the menu items, backwards */
3098 /* Separator */
3100 menu_item = gtk_separator_menu_item_new ();
3101 gtk_widget_show (menu_item);
3102 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3103 email_num = e_destination_get_email_num (destination);
3105 /* Addresses */
3106 is_list = e_contact_get (contact, E_CONTACT_IS_LIST) ? TRUE : FALSE;
3107 if (is_list) {
3108 const GList *dests = e_destination_list_get_dests (destination);
3109 GList *iter;
3110 gint length = g_list_length ((GList *) dests);
3112 for (iter = (GList *) dests; iter; iter = iter->next) {
3113 EDestination *dest = (EDestination *) iter->data;
3114 const gchar *email = e_destination_get_email (dest);
3116 if (!email || *email == '\0')
3117 continue;
3119 if (length > 1) {
3120 menu_item = gtk_check_menu_item_new_with_label (email);
3121 g_signal_connect (
3122 menu_item, "toggled",
3123 G_CALLBACK (destination_set_list), dest);
3124 } else {
3125 menu_item = gtk_menu_item_new_with_label (email);
3128 gtk_widget_show (menu_item);
3129 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3130 show_menu = TRUE;
3132 if (length > 1) {
3133 gtk_check_menu_item_set_active (
3134 GTK_CHECK_MENU_ITEM (menu_item),
3135 !e_destination_is_ignored (dest));
3136 g_signal_connect_swapped (
3137 menu_item, "activate",
3138 G_CALLBACK (popup_activate_list), dest);
3142 } else {
3143 email_list = e_contact_get (contact, E_CONTACT_EMAIL);
3144 len = g_list_length (email_list);
3146 for (l = email_list, i = 0; l; l = g_list_next (l), i++) {
3147 gchar *email = l->data;
3149 if (!email || *email == '\0')
3150 continue;
3152 if (len > 1) {
3153 menu_item = gtk_radio_menu_item_new_with_label (group, email);
3154 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menu_item));
3155 g_signal_connect (menu_item, "toggled", G_CALLBACK (destination_set_email), destination);
3156 } else {
3157 menu_item = gtk_menu_item_new_with_label (email);
3160 gtk_widget_show (menu_item);
3161 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3162 show_menu = TRUE;
3163 g_object_set_data (G_OBJECT (menu_item), "order", GINT_TO_POINTER (i));
3165 if (i == email_num && len > 1) {
3166 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), TRUE);
3167 g_signal_connect_swapped (
3168 menu_item, "activate",
3169 G_CALLBACK (popup_activate_email),
3170 name_selector_entry);
3175 /* Separator */
3177 if (show_menu) {
3178 menu_item = gtk_separator_menu_item_new ();
3179 gtk_widget_show (menu_item);
3180 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3183 /* Expand a list inline */
3184 if (is_list) {
3185 /* To Translators: This would be similiar to "Expand MyList Inline" where MyList is a Contact List*/
3186 edit_label = g_strdup_printf (_("E_xpand %s Inline"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3187 menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
3188 g_free (edit_label);
3189 gtk_widget_show (menu_item);
3190 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3191 g_signal_connect_swapped (
3192 menu_item, "activate", G_CALLBACK (popup_activate_inline_expand),
3193 name_selector_entry);
3195 /* Separator */
3196 menu_item = gtk_separator_menu_item_new ();
3197 gtk_widget_show (menu_item);
3198 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3201 /* Copy Contact Item */
3202 copy_label = g_strdup_printf (_("Cop_y %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3203 menu_item = gtk_menu_item_new_with_mnemonic (copy_label);
3204 g_free (copy_label);
3205 gtk_widget_show (menu_item);
3206 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3208 g_signal_connect_swapped (
3209 menu_item, "activate", G_CALLBACK (popup_activate_copy),
3210 name_selector_entry);
3212 /* Cut Contact Item */
3213 cut_label = g_strdup_printf (_("C_ut %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3214 menu_item = gtk_menu_item_new_with_mnemonic (cut_label);
3215 g_free (cut_label);
3216 gtk_widget_show (menu_item);
3217 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3219 g_signal_connect_swapped (
3220 menu_item, "activate", G_CALLBACK (popup_activate_cut),
3221 name_selector_entry);
3223 if (show_menu) {
3224 menu_item = gtk_separator_menu_item_new ();
3225 gtk_widget_show (menu_item);
3226 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3229 /* Edit Contact item */
3231 edit_label = g_strdup_printf (_("_Edit %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
3232 menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
3233 g_free (edit_label);
3234 gtk_widget_show (menu_item);
3235 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
3237 g_signal_connect_swapped (
3238 menu_item, "activate", G_CALLBACK (popup_activate_contact),
3239 name_selector_entry);
3241 deep_free_list (email_list);
3244 static gint
3245 compare_gint_ptr_cb (gconstpointer a,
3246 gconstpointer b)
3248 return GPOINTER_TO_INT (a) - GPOINTER_TO_INT (b);
3251 static void
3252 copy_or_cut_clipboard (ENameSelectorEntry *name_selector_entry,
3253 gboolean is_cut)
3255 GtkClipboard *clipboard;
3256 GtkEditable *editable;
3257 const gchar *text, *cp;
3258 GHashTable *hash;
3259 GHashTableIter iter;
3260 gpointer key, value;
3261 GSList *sorted, *siter;
3262 GString *addresses;
3263 gint ii, start, end, ostart, oend;
3264 gunichar uc;
3266 editable = GTK_EDITABLE (name_selector_entry);
3267 text = gtk_entry_get_text (GTK_ENTRY (editable));
3269 if (!gtk_editable_get_selection_bounds (editable, &start, &end))
3270 return;
3272 g_return_if_fail (end > start);
3274 hash = g_hash_table_new (g_direct_hash, g_direct_equal);
3276 /* convert from character indexes to pointer indexes */
3277 ostart = g_utf8_offset_to_pointer (text, start) - text;
3278 oend = g_utf8_offset_to_pointer (text, end) - text;
3280 ii = end;
3281 cp = g_utf8_offset_to_pointer (text, end);
3282 uc = g_utf8_get_char (cp);
3284 /* Exclude trailing whitespace and commas. */
3285 while (ii >= start && (uc == ',' || g_unichar_isspace (uc))) {
3286 cp = g_utf8_prev_char (cp);
3287 uc = g_utf8_get_char (cp);
3288 ii--;
3291 /* Determine the index of each remaining character. */
3292 while (ii >= start) {
3293 gint index = get_index_at_position (text, ii--);
3294 g_hash_table_insert (hash, GINT_TO_POINTER (index), NULL);
3297 sorted = NULL;
3298 g_hash_table_iter_init (&iter, hash);
3299 while (g_hash_table_iter_next (&iter, &key, &value)) {
3300 sorted = g_slist_prepend (sorted, key);
3303 sorted = g_slist_sort (sorted, compare_gint_ptr_cb);
3304 addresses = g_string_new ("");
3306 for (siter = sorted; siter != NULL; siter = g_slist_next (siter)) {
3307 gint index = GPOINTER_TO_INT (siter->data);
3308 EDestination *dest;
3309 gint rstart, rend;
3311 if (!get_range_by_index (text, index, &rstart, &rend))
3312 continue;
3314 /* convert from character indexes to pointer indexes */
3315 rstart = g_utf8_offset_to_pointer (text, rstart) - text;
3316 rend = g_utf8_offset_to_pointer (text, rend) - text;
3318 if (rstart < ostart) {
3319 if (addresses->str && *addresses->str)
3320 g_string_append (addresses, ", ");
3322 g_string_append_len (addresses, text + ostart, MIN (oend - ostart, rend - ostart));
3323 } else if (rend > oend) {
3324 if (addresses->str && *addresses->str)
3325 g_string_append (addresses, ", ");
3327 g_string_append_len (addresses, text + rstart, oend - rstart);
3328 } else {
3329 /* the contact is whole selected */
3330 dest = find_destination_by_index (name_selector_entry, index);
3331 if (dest && e_destination_get_textrep (dest, TRUE)) {
3332 if (addresses->str && *addresses->str)
3333 g_string_append (addresses, ", ");
3335 g_string_append (addresses, e_destination_get_textrep (dest, TRUE));
3336 } else
3337 g_string_append_len (addresses, text + rstart, rend - rstart);
3341 g_slist_free (sorted);
3343 if (is_cut)
3344 gtk_editable_delete_text (editable, start, end);
3346 g_hash_table_unref (hash);
3348 clipboard = gtk_widget_get_clipboard (
3349 GTK_WIDGET (name_selector_entry), GDK_SELECTION_CLIPBOARD);
3350 gtk_clipboard_set_text (clipboard, addresses->str, -1);
3352 g_string_free (addresses, TRUE);
3355 static void
3356 copy_clipboard (GtkEntry *entry,
3357 ENameSelectorEntry *name_selector_entry)
3359 copy_or_cut_clipboard (name_selector_entry, FALSE);
3360 g_signal_stop_emission_by_name (entry, "copy-clipboard");
3363 static void
3364 cut_clipboard (GtkEntry *entry,
3365 ENameSelectorEntry *name_selector_entry)
3367 copy_or_cut_clipboard (name_selector_entry, TRUE);
3368 g_signal_stop_emission_by_name (entry, "cut-clipboard");
3371 static void
3372 e_name_selector_entry_init (ENameSelectorEntry *name_selector_entry)
3374 GtkCellRenderer *renderer;
3376 name_selector_entry->priv =
3377 E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
3379 g_queue_init (&name_selector_entry->priv->cancellables);
3381 name_selector_entry->priv->minimum_query_length = 3;
3382 name_selector_entry->priv->show_address = FALSE;
3383 name_selector_entry->priv->known_contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
3385 /* Edit signals */
3387 g_signal_connect (
3388 name_selector_entry, "insert-text",
3389 G_CALLBACK (user_insert_text), name_selector_entry);
3390 g_signal_connect (
3391 name_selector_entry, "delete-text",
3392 G_CALLBACK (user_delete_text), name_selector_entry);
3393 g_signal_connect (
3394 name_selector_entry, "focus-out-event",
3395 G_CALLBACK (user_focus_out), name_selector_entry);
3396 g_signal_connect_after (
3397 name_selector_entry, "focus-in-event",
3398 G_CALLBACK (user_focus_in), name_selector_entry);
3399 g_signal_connect (
3400 name_selector_entry, "key-press-event",
3401 G_CALLBACK (user_key_press_event_cb), name_selector_entry);
3403 /* Drawing */
3405 g_signal_connect (
3406 name_selector_entry, "draw",
3407 G_CALLBACK (draw_event), name_selector_entry);
3409 /* Activation: Complete current entry if possible */
3411 g_signal_connect (
3412 name_selector_entry, "activate",
3413 G_CALLBACK (entry_activate), name_selector_entry);
3415 /* Pop-up menu */
3417 g_signal_connect (
3418 name_selector_entry, "button-press-event",
3419 G_CALLBACK (prepare_popup_destination), name_selector_entry);
3420 g_signal_connect (
3421 name_selector_entry, "populate-popup",
3422 G_CALLBACK (populate_popup), name_selector_entry);
3424 /* Clipboard signals */
3425 g_signal_connect (
3426 name_selector_entry, "copy-clipboard",
3427 G_CALLBACK (copy_clipboard), name_selector_entry);
3428 g_signal_connect (
3429 name_selector_entry, "cut-clipboard",
3430 G_CALLBACK (cut_clipboard), name_selector_entry);
3432 /* Completion */
3434 name_selector_entry->priv->email_generator = NULL;
3436 name_selector_entry->priv->entry_completion = gtk_entry_completion_new ();
3437 gtk_entry_completion_set_match_func (
3438 name_selector_entry->priv->entry_completion,
3439 (GtkEntryCompletionMatchFunc) completion_match_cb, NULL, NULL);
3440 g_signal_connect_swapped (
3441 name_selector_entry->priv->entry_completion, "match-selected",
3442 G_CALLBACK (completion_match_selected), name_selector_entry);
3444 gtk_entry_set_completion (
3445 GTK_ENTRY (name_selector_entry),
3446 name_selector_entry->priv->entry_completion);
3448 renderer = gtk_cell_renderer_pixbuf_new ();
3449 gtk_cell_layout_pack_start (
3450 GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3451 renderer, FALSE);
3452 gtk_cell_layout_set_cell_data_func (
3453 GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3454 GTK_CELL_RENDERER (renderer),
3455 (GtkCellLayoutDataFunc) contact_layout_pixbuffer,
3456 name_selector_entry, NULL);
3458 /* Completion list name renderer */
3459 renderer = gtk_cell_renderer_text_new ();
3460 gtk_cell_layout_pack_start (
3461 GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3462 renderer, TRUE);
3463 gtk_cell_layout_set_cell_data_func (
3464 GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
3465 GTK_CELL_RENDERER (renderer),
3466 (GtkCellLayoutDataFunc) contact_layout_formatter,
3467 name_selector_entry, NULL);
3469 /* Destination store */
3471 name_selector_entry->priv->destination_store = e_destination_store_new ();
3472 setup_destination_store (name_selector_entry);
3473 name_selector_entry->priv->is_completing = FALSE;
3477 * e_name_selector_entry_new:
3478 * @client_cache: an #EClientCache
3480 * Creates a new #ENameSelectorEntry.
3482 * Returns: A new #ENameSelectorEntry.
3484 GtkWidget *
3485 e_name_selector_entry_new (EClientCache *client_cache)
3487 g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL);
3489 return g_object_new (
3490 E_TYPE_NAME_SELECTOR_ENTRY,
3491 "client-cache", client_cache, NULL);
3495 * e_name_selector_entry_ref_client_cache:
3496 * @name_selector_entry: an #ENameSelectorEntry
3498 * Returns the #EClientCache passed to e_name_selector_entry_new().
3500 * The returned #EClientCache is referenced for thread-safety and must be
3501 * unreferenced with g_object_unref() when finished with it.
3503 * Returns: an #EClientCache
3505 * Since: 3.8
3507 EClientCache *
3508 e_name_selector_entry_ref_client_cache (ENameSelectorEntry *name_selector_entry)
3510 g_return_val_if_fail (
3511 E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3513 if (name_selector_entry->priv->client_cache == NULL)
3514 return NULL;
3516 return g_object_ref (name_selector_entry->priv->client_cache);
3520 * e_name_selector_entry_set_client_cache:
3521 * @name_selector_entry: an #ENameSelectorEntry
3522 * @client_cache: an #EClientCache
3524 * Sets the #EClientCache used to query address books.
3526 * This function is intended for cases where @name_selector_entry is
3527 * instantiated by a #GtkBuilder and has to be given an #EClientCache
3528 * after it is fully constructed.
3530 * Since: 3.6
3532 void
3533 e_name_selector_entry_set_client_cache (ENameSelectorEntry *name_selector_entry,
3534 EClientCache *client_cache)
3536 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3538 if (client_cache == name_selector_entry->priv->client_cache)
3539 return;
3541 if (client_cache != NULL) {
3542 g_return_if_fail (E_IS_CLIENT_CACHE (client_cache));
3543 g_object_ref (client_cache);
3546 if (name_selector_entry->priv->client_cache != NULL)
3547 g_object_unref (name_selector_entry->priv->client_cache);
3549 name_selector_entry->priv->client_cache = client_cache;
3551 g_object_notify (G_OBJECT (name_selector_entry), "client-cache");
3555 * e_name_selector_entry_get_minimum_query_length:
3556 * @name_selector_entry: an #ENameSelectorEntry
3558 * Returns: Minimum length of query before completion starts
3560 * Since: 3.6
3562 gint
3563 e_name_selector_entry_get_minimum_query_length (ENameSelectorEntry *name_selector_entry)
3565 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), -1);
3567 return name_selector_entry->priv->minimum_query_length;
3571 * e_name_selector_entry_set_minimum_query_length:
3572 * @name_selector_entry: an #ENameSelectorEntry
3573 * @length: minimum query length
3575 * Sets minimum length of query before completion starts.
3577 * Since: 3.6
3579 void
3580 e_name_selector_entry_set_minimum_query_length (ENameSelectorEntry *name_selector_entry,
3581 gint length)
3583 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3584 g_return_if_fail (length > 0);
3586 if (name_selector_entry->priv->minimum_query_length == length)
3587 return;
3589 name_selector_entry->priv->minimum_query_length = length;
3591 g_object_notify (G_OBJECT (name_selector_entry), "minimum-query-length");
3595 * e_name_selector_entry_get_show_address:
3596 * @name_selector_entry: an #ENameSelectorEntry
3598 * Returns: Whether always show email address for an auto-completed contact.
3600 * Since: 3.6
3602 gboolean
3603 e_name_selector_entry_get_show_address (ENameSelectorEntry *name_selector_entry)
3605 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), FALSE);
3607 return name_selector_entry->priv->show_address;
3611 * e_name_selector_entry_set_show_address:
3612 * @name_selector_entry: an #ENameSelectorEntry
3613 * @show: new value to set
3615 * Sets whether always show email address for an auto-completed contact.
3617 * Since: 3.6
3619 void
3620 e_name_selector_entry_set_show_address (ENameSelectorEntry *name_selector_entry,
3621 gboolean show)
3623 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3625 if ((name_selector_entry->priv->show_address ? 1 : 0) == (show ? 1 : 0))
3626 return;
3628 name_selector_entry->priv->show_address = show;
3630 sanitize_entry (name_selector_entry);
3632 g_object_notify (G_OBJECT (name_selector_entry), "show-address");
3636 * e_name_selector_entry_peek_contact_store:
3637 * @name_selector_entry: an #ENameSelectorEntry
3639 * Gets the #EContactStore being used by @name_selector_entry.
3641 * Returns: An #EContactStore.
3643 EContactStore *
3644 e_name_selector_entry_peek_contact_store (ENameSelectorEntry *name_selector_entry)
3646 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3648 return name_selector_entry->priv->contact_store;
3652 * e_name_selector_entry_set_contact_store:
3653 * @name_selector_entry: an #ENameSelectorEntry
3654 * @contact_store: an #EContactStore to use
3656 * Sets the #EContactStore being used by @name_selector_entry to @contact_store.
3658 void
3659 e_name_selector_entry_set_contact_store (ENameSelectorEntry *name_selector_entry,
3660 EContactStore *contact_store)
3662 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3663 g_return_if_fail (contact_store == NULL || E_IS_CONTACT_STORE (contact_store));
3665 if (contact_store == name_selector_entry->priv->contact_store)
3666 return;
3668 if (name_selector_entry->priv->contact_store)
3669 g_object_unref (name_selector_entry->priv->contact_store);
3670 name_selector_entry->priv->contact_store = contact_store;
3671 if (name_selector_entry->priv->contact_store)
3672 g_object_ref (name_selector_entry->priv->contact_store);
3674 setup_contact_store (name_selector_entry);
3678 * e_name_selector_entry_peek_destination_store:
3679 * @name_selector_entry: an #ENameSelectorEntry
3681 * Gets the #EDestinationStore being used to store @name_selector_entry's destinations.
3683 * Returns: An #EDestinationStore.
3685 EDestinationStore *
3686 e_name_selector_entry_peek_destination_store (ENameSelectorEntry *name_selector_entry)
3688 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3690 return name_selector_entry->priv->destination_store;
3694 * e_name_selector_entry_set_destination_store:
3695 * @name_selector_entry: an #ENameSelectorEntry
3696 * @destination_store: an #EDestinationStore to use
3698 * Sets @destination_store as the #EDestinationStore to be used to store
3699 * destinations for @name_selector_entry.
3701 void
3702 e_name_selector_entry_set_destination_store (ENameSelectorEntry *name_selector_entry,
3703 EDestinationStore *destination_store)
3705 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
3706 g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
3708 if (destination_store == name_selector_entry->priv->destination_store)
3709 return;
3711 g_object_unref (name_selector_entry->priv->destination_store);
3712 name_selector_entry->priv->destination_store = g_object_ref (destination_store);
3714 setup_destination_store (name_selector_entry);
3718 * e_name_selector_entry_get_popup_destination:
3720 * Since: 2.32
3722 EDestination *
3723 e_name_selector_entry_get_popup_destination (ENameSelectorEntry *name_selector_entry)
3725 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
3727 return name_selector_entry->priv->popup_destination;
3731 * e_name_selector_entry_set_contact_editor_func:
3733 * DO NOT USE.
3735 void
3736 e_name_selector_entry_set_contact_editor_func (ENameSelectorEntry *name_selector_entry,
3737 gpointer func)
3739 name_selector_entry->priv->contact_editor_func = func;
3743 * e_name_selector_entry_set_contact_list_editor_func:
3745 * DO NOT USE.
3747 void
3748 e_name_selector_entry_set_contact_list_editor_func (ENameSelectorEntry *name_selector_entry,
3749 gpointer func)
3751 name_selector_entry->priv->contact_list_editor_func = func;