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) 2004 Novell, Inc.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
21 * Authors: Hans Petter Jansson <hpj@novell.com>
26 #include <gtk/gtkentry.h>
27 #include <gtk/gtkentrycompletion.h>
28 #include <gtk/gtkcelllayout.h>
29 #include <gtk/gtkcellrenderertext.h>
30 #include <gtk/gtkmenuitem.h>
31 #include <gtk/gtkradiomenuitem.h>
32 #include <gtk/gtkseparatormenuitem.h>
33 #include <glib/gi18n-lib.h>
34 #include <gtk/gtkclipboard.h>
36 #include <libebook/e-book.h>
37 #include <libebook/e-contact.h>
38 #include <libebook/e-destination.h>
39 #include <libedataserverui/e-book-auth-util.h>
40 #include "libedataserver/e-sexp.h"
42 #include "e-name-selector-entry.h"
48 static guint signals[LAST_SIGNAL] = { 0 };
49 static guint COMPLETION_CUE_MIN_LEN = 0;
52 G_DEFINE_TYPE (ENameSelectorEntry, e_name_selector_entry, GTK_TYPE_ENTRY);
54 typedef struct _ENameSelectorEntryPrivate ENameSelectorEntryPrivate;
55 struct _ENameSelectorEntryPrivate
57 gboolean is_completing;
60 #define E_NAME_SELECTOR_ENTRY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), E_TYPE_NAME_SELECTOR_ENTRY, ENameSelectorEntryPrivate))
62 static void e_name_selector_entry_class_init (ENameSelectorEntryClass *name_selector_entry_class);
63 static void e_name_selector_entry_init (ENameSelectorEntry *name_selector_entry);
64 static void e_name_selector_entry_dispose (GObject *object);
65 static void e_name_selector_entry_finalize (GObject *object);
67 static void destination_row_inserted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
68 static void destination_row_changed (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
69 static void destination_row_deleted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path);
71 static void user_insert_text (ENameSelectorEntry *name_selector_entry, gchar *new_text, gint new_text_length, gint *position, gpointer user_data);
72 static void user_delete_text (ENameSelectorEntry *name_selector_entry, gint start_pos, gint end_pos, gpointer user_data);
74 static void setup_default_contact_store (ENameSelectorEntry *name_selector_entry);
77 e_name_selector_entry_get_property (GObject *object, guint prop_id,
78 GValue *value, GParamSpec *pspec)
83 e_name_selector_entry_set_property (GObject *object, guint prop_id,
84 const GValue *value, GParamSpec *pspec)
89 e_name_selector_entry_realize (GtkWidget *widget)
91 ENameSelectorEntry *name_selector_entry = E_NAME_SELECTOR_ENTRY (widget);
93 GTK_WIDGET_CLASS (e_name_selector_entry_parent_class)->realize (widget);
95 if (!name_selector_entry->contact_store) {
96 setup_default_contact_store (name_selector_entry);
100 /* Partial, repeatable destruction. Release references. */
102 e_name_selector_entry_dispose (GObject *object)
104 ENameSelectorEntry *name_selector_entry = E_NAME_SELECTOR_ENTRY (object);
106 if (name_selector_entry->entry_completion) {
107 g_object_unref (name_selector_entry->entry_completion);
108 name_selector_entry->entry_completion = NULL;
111 if (name_selector_entry->destination_store) {
112 g_object_unref (name_selector_entry->destination_store);
113 name_selector_entry->destination_store = NULL;
116 if (G_OBJECT_CLASS (e_name_selector_entry_parent_class)->dispose)
117 G_OBJECT_CLASS (e_name_selector_entry_parent_class)->dispose (object);
120 /* Final, one-time destruction. Free all. */
122 e_name_selector_entry_finalize (GObject *object)
124 if (G_OBJECT_CLASS (e_name_selector_entry_parent_class)->finalize)
125 G_OBJECT_CLASS (e_name_selector_entry_parent_class)->finalize (object);
129 e_name_selector_entry_updated (ENameSelectorEntry *entry, char *email)
131 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (entry));
135 e_name_selector_entry_class_init (ENameSelectorEntryClass *name_selector_entry_class)
137 GObjectClass *object_class = G_OBJECT_CLASS (name_selector_entry_class);
138 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (name_selector_entry_class);
140 object_class->get_property = e_name_selector_entry_get_property;
141 object_class->set_property = e_name_selector_entry_set_property;
142 object_class->dispose = e_name_selector_entry_dispose;
143 object_class->finalize = e_name_selector_entry_finalize;
144 name_selector_entry_class->updated = e_name_selector_entry_updated;
146 widget_class->realize = e_name_selector_entry_realize;
148 /* Install properties */
150 /* Install signals */
152 signals[UPDATED] = g_signal_new ("updated",
153 E_TYPE_NAME_SELECTOR_ENTRY,
155 G_STRUCT_OFFSET (ENameSelectorEntryClass, updated),
158 g_cclosure_marshal_VOID__POINTER,
159 G_TYPE_NONE, 1, G_TYPE_POINTER);
161 g_type_class_add_private (object_class, sizeof(ENameSelectorEntryPrivate));
164 /* Remove unquoted commas from string */
166 sanitize_string (const gchar *string)
169 gboolean quoted = FALSE;
172 gstring = g_string_new ("");
175 return g_string_free (gstring, FALSE);
177 for (p = string; *p; p = g_utf8_next_char (p)) {
178 gunichar c = g_utf8_get_char (p);
182 else if (c == ',' && !quoted)
185 g_string_append_unichar (gstring, c);
188 return g_string_free (gstring, FALSE);
191 /* Called for each list store entry whenever the user types (but not on cut/paste) */
193 completion_match_cb (GtkEntryCompletion *completion, const gchar *key,
194 GtkTreeIter *iter, gpointer user_data)
196 ENS_DEBUG (g_print ("completion_match_cb, key=%s\n", key));
201 /* Gets context of n_unichars total (n_unicars / 2, before and after position)
202 * and places them in array. If any positions would be outside the string, the
203 * corresponding unichars are set to zero. */
205 get_utf8_string_context (const gchar *string, gint position, gunichar *unichars, gint n_unichars)
212 /* n_unichars must be even */
213 g_assert (n_unichars % 2 == 0);
215 len = g_utf8_strlen (string, -1);
216 gap = n_unichars / 2;
218 for (i = 0; i < n_unichars; i++) {
219 gint char_pos = position - gap + i;
221 if (char_pos < 0 || char_pos >= len) {
227 p = g_utf8_next_char (p);
229 p = g_utf8_offset_to_pointer (string, char_pos);
231 unichars [i] = g_utf8_get_char (p);
236 get_range_at_position (const gchar *string, gint pos, gint *start_pos, gint *end_pos)
239 gboolean quoted = FALSE;
240 gint local_start_pos = 0;
241 gint local_end_pos = 0;
244 if (!string || !*string)
247 for (p = string, i = 0; *p; p = g_utf8_next_char (p), i++) {
248 gunichar c = g_utf8_get_char (p);
252 } else if (c == ',' && !quoted) {
254 /* Start right after comma */
255 local_start_pos = i + 1;
257 /* Stop right before comma */
261 } else if (c == ' ' && local_start_pos == i) {
262 /* Adjust start to skip space after first comma */
267 /* If we didn't hit a comma, we must've hit NULL, and ours was the last element. */
272 *start_pos = local_start_pos;
274 *end_pos = local_end_pos;
280 is_quoted_at (const gchar *string, gint pos)
283 gboolean quoted = FALSE;
286 for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
287 gunichar c = g_utf8_get_char (p);
293 return quoted ? TRUE : FALSE;
297 get_index_at_position (const gchar *string, gint pos)
300 gboolean quoted = FALSE;
304 for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
305 gunichar c = g_utf8_get_char (p);
309 else if (c == ',' && !quoted)
317 get_range_by_index (const gchar *string, gint index, gint *start_pos, gint *end_pos)
320 gboolean quoted = FALSE;
324 for (p = string, i = 0; *p && n < index; p = g_utf8_next_char (p), i++) {
325 gunichar c = g_utf8_get_char (p);
329 if (c == ',' && !quoted)
336 return get_range_at_position (string, i, start_pos, end_pos);
340 get_address_at_position (const gchar *string, gint pos)
344 const gchar *start_p;
347 if (!get_range_at_position (string, pos, &start_pos, &end_pos))
350 start_p = g_utf8_offset_to_pointer (string, start_pos);
351 end_p = g_utf8_offset_to_pointer (string, end_pos);
353 return g_strndup (start_p, end_p - start_p);
356 /* Finds the destination in model */
357 static EDestination *
358 find_destination_by_index (ENameSelectorEntry *name_selector_entry, gint index)
363 path = gtk_tree_path_new_from_indices (index, -1);
364 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (name_selector_entry->destination_store),
366 /* If we have zero destinations, getting a NULL destination at index 0
369 g_warning ("ENameSelectorEntry is out of sync with model!");
370 gtk_tree_path_free (path);
373 gtk_tree_path_free (path);
375 return e_destination_store_get_destination (name_selector_entry->destination_store, &iter);
378 /* Finds the destination in model */
379 static EDestination *
380 find_destination_at_position (ENameSelectorEntry *name_selector_entry, gint pos)
385 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
386 index = get_index_at_position (text, pos);
388 return find_destination_by_index (name_selector_entry, index);
391 /* Builds destination from our text */
392 static EDestination *
393 build_destination_at_position (const gchar *string, gint pos)
395 EDestination *destination;
398 address = get_address_at_position (string, pos);
402 destination = e_destination_new ();
403 e_destination_set_raw (destination, address);
410 name_style_query (const gchar *field, const gchar *value)
414 GString *out = g_string_new ("");
418 spaced_str = sanitize_string (value);
419 g_strstrip (spaced_str);
421 strv = g_strsplit (spaced_str, " ", 0);
423 if (strv [0] && strv [1]) {
424 g_string_append (out, "(or ");
425 comma_str = g_strjoinv (", ", strv);
430 g_string_append (out, " (beginswith ");
431 e_sexp_encode_string (out, field);
432 e_sexp_encode_string (out, spaced_str);
433 g_string_append (out, ")");
436 g_string_append (out, " (beginswith ");
438 e_sexp_encode_string (out, field);
439 g_strstrip (comma_str);
440 e_sexp_encode_string (out, comma_str);
441 g_string_append (out, "))");
444 query = g_string_free (out, FALSE);
454 escape_sexp_string (const gchar *string)
457 gchar *encoded_string;
459 gstring = g_string_new ("");
460 e_sexp_encode_string (gstring, string);
462 encoded_string = gstring->str;
463 g_string_free (gstring, FALSE);
465 return encoded_string;
469 set_completion_query (ENameSelectorEntry *name_selector_entry, const gchar *cue_str)
471 EBookQuery *book_query;
473 gchar *encoded_cue_str;
474 gchar *full_name_query_str;
475 gchar *file_as_query_str;
477 if (!name_selector_entry->contact_store)
481 /* Clear the store */
482 e_contact_store_set_query (name_selector_entry->contact_store, NULL);
486 encoded_cue_str = escape_sexp_string (cue_str);
487 full_name_query_str = name_style_query ("full_name", cue_str);
488 file_as_query_str = name_style_query ("file_as", cue_str);
490 query_str = g_strdup_printf ("(or "
491 " (beginswith \"nickname\" %s) "
492 " (beginswith \"email\" %s) "
496 encoded_cue_str, encoded_cue_str,
497 full_name_query_str, file_as_query_str);
499 g_free (file_as_query_str);
500 g_free (full_name_query_str);
501 g_free (encoded_cue_str);
503 ENS_DEBUG (g_print ("%s\n", query_str));
505 book_query = e_book_query_from_string (query_str);
506 e_contact_store_set_query (name_selector_entry->contact_store, book_query);
507 e_book_query_unref (book_query);
513 get_entry_substring (ENameSelectorEntry *name_selector_entry, gint range_start, gint range_end)
515 const gchar *entry_text;
518 entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
520 p0 = g_utf8_offset_to_pointer (entry_text, range_start);
521 p1 = g_utf8_offset_to_pointer (entry_text, range_end);
523 return g_strndup (p0, p1 - p0);
527 utf8_casefold_collate_len (const gchar *str1, const gchar *str2, gint len)
529 gchar *s1 = g_utf8_casefold(str1, len);
530 gchar *s2 = g_utf8_casefold(str2, len);
533 rv = g_utf8_collate (s1, s2);
542 build_textrep_for_contact (EContact *contact, EContactField cue_field)
549 case E_CONTACT_FULL_NAME:
550 case E_CONTACT_NICKNAME:
551 case E_CONTACT_FILE_AS:
552 name = e_contact_get (contact, cue_field);
553 email = e_contact_get (contact, E_CONTACT_EMAIL_1);
556 case E_CONTACT_EMAIL_1:
557 case E_CONTACT_EMAIL_2:
558 case E_CONTACT_EMAIL_3:
559 case E_CONTACT_EMAIL_4:
561 email = e_contact_get (contact, cue_field);
565 g_assert_not_reached ();
570 g_assert (strlen (email) > 0);
573 textrep = g_strdup_printf ("%s <%s>", name, email);
575 textrep = g_strdup_printf ("%s", email);
583 contact_match_cue (EContact *contact, const gchar *cue_str,
584 EContactField *matched_field, gint *matched_field_rank)
586 EContactField fields [] = { E_CONTACT_FULL_NAME, E_CONTACT_NICKNAME, E_CONTACT_FILE_AS,
587 E_CONTACT_EMAIL_1, E_CONTACT_EMAIL_2, E_CONTACT_EMAIL_3,
590 gboolean result = FALSE;
597 if (g_utf8_strlen (cue_str, -1) < COMPLETION_CUE_MIN_LEN)
600 cue_len = strlen (cue_str);
602 /* Make sure contact has an email address */
603 email = e_contact_get (contact, E_CONTACT_EMAIL_1);
604 if (!email || !*email) {
610 for (i = 0; i < G_N_ELEMENTS (fields); i++) {
614 /* Don't match e-mail addresses in contact lists */
615 if (e_contact_get (contact, E_CONTACT_IS_LIST) &&
616 fields [i] >= E_CONTACT_FIRST_EMAIL_ID &&
617 fields [i] <= E_CONTACT_LAST_EMAIL_ID)
620 value = e_contact_get (contact, fields [i]);
624 value_sane = sanitize_string (value);
627 ENS_DEBUG (g_print ("Comparing '%s' to '%s'\n", value, cue_str));
629 if (!utf8_casefold_collate_len (value_sane, cue_str, cue_len)) {
631 *matched_field = fields [i];
632 if (matched_field_rank)
633 *matched_field_rank = i;
646 find_existing_completion (ENameSelectorEntry *name_selector_entry, const gchar *cue_str,
647 EContact **contact, gchar **text, EContactField *matched_field)
650 EContact *best_contact = NULL;
651 gint best_field_rank = G_MAXINT;
652 EContactField best_field = 0;
657 if (!name_selector_entry->contact_store)
660 cue_len = strlen (cue_str);
662 ENS_DEBUG (g_print ("Completing '%s'\n", cue_str));
664 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->contact_store), &iter))
668 EContact *current_contact;
669 gint current_field_rank;
670 EContactField current_field;
673 current_contact = e_contact_store_get_contact (name_selector_entry->contact_store, &iter);
674 if (!current_contact)
677 matches = contact_match_cue (current_contact, cue_str, ¤t_field, ¤t_field_rank);
678 if (matches && current_field_rank < best_field_rank) {
679 best_contact = current_contact;
680 best_field_rank = current_field_rank;
681 best_field = current_field;
683 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->contact_store), &iter));
689 *contact = best_contact;
691 *text = build_textrep_for_contact (best_contact, best_field);
693 *matched_field = best_field;
699 generate_attribute_list (ENameSelectorEntry *name_selector_entry)
702 PangoAttrList *attr_list;
706 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
707 layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
709 /* Set up the attribute list */
711 attr_list = pango_attr_list_new ();
713 if (name_selector_entry->attr_list)
714 pango_attr_list_unref (name_selector_entry->attr_list);
716 name_selector_entry->attr_list = attr_list;
718 /* Parse the entry's text and apply attributes to real contacts */
721 EDestination *destination;
722 PangoAttribute *attr;
726 if (!get_range_by_index (text, i, &start_pos, &end_pos))
729 destination = find_destination_at_position (name_selector_entry, start_pos);
731 /* Destination will be NULL if we have no entries */
732 if (!destination || !e_destination_get_contact (destination))
735 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
736 attr->start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
737 attr->end_index = g_utf8_offset_to_pointer (text, end_pos) - text;
738 pango_attr_list_insert (attr_list, attr);
741 pango_layout_set_attributes (layout, attr_list);
745 expose_event (ENameSelectorEntry *name_selector_entry)
749 layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
750 pango_layout_set_attributes (layout, name_selector_entry->attr_list);
756 type_ahead_complete (ENameSelectorEntry *name_selector_entry)
759 EContactField matched_field;
760 EDestination *destination;
762 gint range_start = 0;
771 ENameSelectorEntryPrivate *priv;
773 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
775 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
779 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
780 get_range_at_position (text, cursor_pos, &range_start, &range_end);
781 range_len = range_end - range_start;
782 if (range_len < COMPLETION_CUE_MIN_LEN)
785 destination = find_destination_at_position (name_selector_entry, cursor_pos);
787 cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
788 if (!find_existing_completion (name_selector_entry, cue_str, &contact,
789 &textrep, &matched_field)) {
795 temp_str = sanitize_string (textrep);
799 textrep_len = g_utf8_strlen (textrep, -1);
802 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
803 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
804 g_signal_handlers_block_by_func (name_selector_entry->destination_store,
805 destination_row_changed, name_selector_entry);
807 if (textrep_len > range_len) {
808 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
809 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), textrep, -1, &pos);
810 gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), range_end,
811 range_start + textrep_len);
812 priv->is_completing = TRUE;
815 if (contact && destination) {
818 if (matched_field >= E_CONTACT_FIRST_EMAIL_ID && matched_field <= E_CONTACT_LAST_EMAIL_ID)
819 email_n = matched_field - E_CONTACT_FIRST_EMAIL_ID;
821 e_destination_set_contact (destination, contact, email_n);
822 generate_attribute_list (name_selector_entry);
825 g_signal_handlers_unblock_by_func (name_selector_entry->destination_store,
826 destination_row_changed, name_selector_entry);
827 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
828 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
834 clear_completion_model (ENameSelectorEntry *name_selector_entry)
836 ENameSelectorEntryPrivate *priv;
838 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
840 if (!name_selector_entry->contact_store)
843 e_contact_store_set_query (name_selector_entry->contact_store, NULL);
844 priv->is_completing = FALSE;
848 update_completion_model (ENameSelectorEntry *name_selector_entry)
852 gint range_start = 0;
855 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
856 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
859 get_range_at_position (text, cursor_pos, &range_start, &range_end);
861 if (range_end - range_start >= COMPLETION_CUE_MIN_LEN && cursor_pos == range_end) {
864 cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
865 set_completion_query (name_selector_entry, cue_str);
868 /* N/A; Clear completion model */
869 clear_completion_model (name_selector_entry);
874 type_ahead_complete_on_idle_cb (ENameSelectorEntry *name_selector_entry)
876 type_ahead_complete (name_selector_entry);
877 name_selector_entry->type_ahead_complete_cb_id = 0;
882 update_completions_on_idle_cb (ENameSelectorEntry *name_selector_entry)
884 update_completion_model (name_selector_entry);
885 name_selector_entry->update_completions_cb_id = 0;
890 insert_destination_at_position (ENameSelectorEntry *name_selector_entry, gint pos)
892 EDestination *destination;
896 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
897 index = get_index_at_position (text, pos);
899 destination = build_destination_at_position (text, pos);
900 g_assert (destination);
902 g_signal_handlers_block_by_func (name_selector_entry->destination_store,
903 destination_row_inserted, name_selector_entry);
904 e_destination_store_insert_destination (name_selector_entry->destination_store,
906 g_signal_handlers_unblock_by_func (name_selector_entry->destination_store,
907 destination_row_inserted, name_selector_entry);
908 g_object_unref (destination);
912 modify_destination_at_position (ENameSelectorEntry *name_selector_entry, gint pos)
914 EDestination *destination;
917 gboolean rebuild_attributes = FALSE;
919 destination = find_destination_at_position (name_selector_entry, pos);
923 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
924 raw_address = get_address_at_position (text, pos);
925 g_assert (raw_address);
927 if (e_destination_get_contact (destination))
928 rebuild_attributes = TRUE;
930 g_signal_handlers_block_by_func (name_selector_entry->destination_store,
931 destination_row_changed, name_selector_entry);
932 e_destination_set_raw (destination, raw_address);
933 g_signal_handlers_unblock_by_func (name_selector_entry->destination_store,
934 destination_row_changed, name_selector_entry);
936 g_free (raw_address);
938 if (rebuild_attributes)
939 generate_attribute_list (name_selector_entry);
943 sync_destination_at_position (ENameSelectorEntry *name_selector_entry, gint range_pos, gint *cursor_pos)
945 EDestination *destination;
949 gint range_start, range_end;
951 /* Get the destination we're looking at. Note that the entry may be empty, and so
952 * there may not be one. */
953 destination = find_destination_at_position (name_selector_entry, range_pos);
957 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
958 if (!get_range_at_position (text, range_pos, &range_start, &range_end)) {
959 g_warning ("ENameSelectorEntry is out of sync with model!");
963 address = sanitize_string (e_destination_get_textrep (destination, FALSE));
964 address_len = g_utf8_strlen (address, -1);
967 /* Update cursor placement */
968 if (*cursor_pos >= range_end)
969 *cursor_pos += address_len - (range_end - range_start);
970 else if (*cursor_pos > range_start)
971 *cursor_pos = range_start + address_len;
974 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
975 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
977 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
978 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), address, -1, &range_start);
980 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
981 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
983 generate_attribute_list (name_selector_entry);
988 remove_destination_by_index (ENameSelectorEntry *name_selector_entry, gint index)
990 EDestination *destination;
992 destination = find_destination_by_index (name_selector_entry, index);
994 g_signal_handlers_block_by_func (name_selector_entry->destination_store,
995 destination_row_deleted, name_selector_entry);
996 e_destination_store_remove_destination (name_selector_entry->destination_store,
998 g_signal_handlers_unblock_by_func (name_selector_entry->destination_store,
999 destination_row_deleted, name_selector_entry);
1003 /* Returns the number of characters inserted */
1005 insert_unichar (ENameSelectorEntry *name_selector_entry, gint *pos, gunichar c)
1008 gunichar str_context [4];
1012 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1013 get_utf8_string_context (text, *pos, str_context, 4);
1015 /* Space is not allowed:
1016 * - Before or after another space.
1017 * - At start of string. */
1019 if (c == ' ' && (str_context [1] == ' ' || str_context [1] == '\0' || str_context [2] == ' '))
1022 /* Comma is not allowed:
1023 * - After another comma.
1024 * - At start of string. */
1026 if (c == ',' && !is_quoted_at (text, *pos)) {
1029 gboolean at_start = FALSE;
1030 gboolean at_end = FALSE;
1032 if (str_context [1] == ',' || str_context [1] == '\0')
1035 /* We do this so we can avoid disturbing destinations with completed contacts
1036 * either before or after the destination being inserted. */
1037 get_range_at_position (text, *pos, &start_pos, &end_pos);
1038 if (*pos <= start_pos)
1040 if (*pos >= end_pos)
1043 /* Must insert comma first, so modify_destination_at_position can do its job
1044 * correctly, splitting up the contact if necessary. */
1045 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, pos);
1048 g_assert (*pos >= 2);
1050 /* If we inserted the comma at the end of, or in the middle of, an existing
1051 * address, add a new destination for what appears after comma. Else, we
1052 * have to add a destination for what appears before comma (a blank one). */
1054 /* End: Add last, sync first */
1055 insert_destination_at_position (name_selector_entry, *pos);
1056 sync_destination_at_position (name_selector_entry, *pos - 2, pos);
1057 /* Sync generates the attributes list */
1058 } else if (at_start) {
1059 /* Start: Add first */
1060 insert_destination_at_position (name_selector_entry, *pos - 2);
1061 generate_attribute_list (name_selector_entry);
1064 insert_destination_at_position (name_selector_entry, *pos);
1065 modify_destination_at_position (name_selector_entry, *pos - 2);
1066 generate_attribute_list (name_selector_entry);
1072 /* Generic case. Allowed spaces also end up here. */
1074 len = g_unichar_to_utf8 (c, buf);
1077 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), buf, -1, pos);
1079 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1080 len = g_utf8_strlen (text, -1);
1081 text = g_utf8_next_char (text);
1084 /* First and only character so far, create initial destination */
1085 insert_destination_at_position (name_selector_entry, 0);
1087 /* Modified existing destination */
1088 modify_destination_at_position (name_selector_entry, *pos);
1091 /* If editing within the string, we need to regenerate attributes */
1093 generate_attribute_list (name_selector_entry);
1099 user_insert_text (ENameSelectorEntry *name_selector_entry, gchar *new_text,
1100 gint new_text_length, gint *position, gpointer user_data)
1103 gint chars_inserted = 0;
1105 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1106 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1108 /* Apply some rules as to where spaces and commas can be inserted, and insert
1109 * a trailing space after comma. */
1111 for (p = new_text; *p; p = g_utf8_next_char (p)) {
1112 gunichar c = g_utf8_get_char (p);
1113 insert_unichar (name_selector_entry, position, c);
1117 if (chars_inserted >= 1) {
1118 /* If the user inserted one character, kick off completion */
1119 if (!name_selector_entry->update_completions_cb_id) {
1120 name_selector_entry->update_completions_cb_id =
1121 g_idle_add ((GSourceFunc) update_completions_on_idle_cb,
1122 name_selector_entry);
1125 if (!name_selector_entry->type_ahead_complete_cb_id) {
1126 name_selector_entry->type_ahead_complete_cb_id =
1127 g_idle_add ((GSourceFunc) type_ahead_complete_on_idle_cb,
1128 name_selector_entry);
1132 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1133 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1135 g_signal_stop_emission_by_name (name_selector_entry, "insert_text");
1139 user_delete_text (ENameSelectorEntry *name_selector_entry, gint start_pos, gint end_pos,
1143 gint index_start, index_end;
1144 gint selection_start, selection_end;
1145 gunichar str_context [2], str_b_context [2];
1148 gboolean already_selected = FALSE, del_space = FALSE, del_comma = FALSE;
1150 if (start_pos == end_pos)
1153 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1154 len = g_utf8_strlen (text, -1);
1156 if (gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry),
1159 if ((g_utf8_get_char (g_utf8_offset_to_pointer (text, selection_end)) == 0) ||
1160 (g_utf8_get_char (g_utf8_offset_to_pointer (text, selection_end)) == ','))
1161 already_selected = TRUE;
1163 get_utf8_string_context (text, start_pos, str_context, 2);
1164 get_utf8_string_context (text, end_pos, str_b_context, 2);
1166 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1168 if (end_pos - start_pos == 1) {
1169 /* Might be backspace; update completion model so dropdown is accurate */
1170 if (!name_selector_entry->update_completions_cb_id) {
1171 name_selector_entry->update_completions_cb_id =
1172 g_idle_add ((GSourceFunc) update_completions_on_idle_cb,
1173 name_selector_entry);
1177 index_start = get_index_at_position (text, start_pos);
1178 index_end = get_index_at_position (text, end_pos);
1180 g_signal_stop_emission_by_name (name_selector_entry, "delete_text");
1182 /* If the deletion touches more than one destination, the first one is changed
1183 * and the rest are removed. If the last destination wasn't completely deleted,
1184 * it becomes part of the first one, since the separator between them was
1187 * Here, we let the model know about removals. */
1188 for (i = index_end; i > index_start; i--) {
1189 EDestination *destination = find_destination_by_index (name_selector_entry, i);
1190 int range_start, range_end;
1192 const char *email=NULL;
1196 email = e_destination_get_address (destination);
1198 if (!email || !*email)
1201 if (!get_range_by_index (text, i, &range_start, &range_end)) {
1202 g_warning ("ENameSelectorEntry is out of sync with model!");
1206 if ((selection_start < range_start && selection_end > range_start) ||
1207 (selection_end > range_start && selection_end < range_end))
1211 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1212 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1214 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1216 ttext = sanitize_string (email);
1217 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
1220 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1221 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1225 remove_destination_by_index (name_selector_entry, i);
1228 /* Do the actual deletion */
1230 if (end_pos == start_pos +1 && index_end == index_start) {
1231 /* We could be just deleting the empty text */
1234 /* Get the actual deleted text */
1235 c = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), start_pos, start_pos+1);
1238 /* If we are at the beginning or removing junk space, let us ignore it */
1241 } else if (end_pos == start_pos +1 && index_end == index_start+1) {
1242 /* We could be just deleting the empty text */
1245 /* Get the actual deleted text */
1246 c = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), start_pos, start_pos+1);
1248 if ( c[0] == ',' && !is_quoted_at (text, start_pos)) {
1249 /* If we are at the beginning or removing junk space, let us ignore it */
1255 int range_start=-1, range_end;
1256 EDestination *dest = find_destination_by_index (name_selector_entry, index_end);
1257 /* If we have deleted the last comma, let us autocomplete normally
1260 if (dest && len - end_pos != 0) {
1262 EDestination *destination1 = find_destination_by_index (name_selector_entry, index_start);
1264 const char *email=NULL;
1267 email = e_destination_get_address (destination1);
1269 if (email && *email) {
1271 if (!get_range_by_index (text, i, &range_start, &range_end)) {
1272 g_warning ("ENameSelectorEntry is out of sync with model!");
1276 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1277 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1279 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1281 ttext = sanitize_string (email);
1282 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
1285 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1286 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1289 if (range_start != -1) {
1290 start_pos = range_start;
1291 end_pos = start_pos+1;
1292 gtk_editable_set_position (GTK_EDITABLE (name_selector_entry),start_pos);
1296 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry),
1297 start_pos, end_pos);
1299 /*If the user is deleting a '"' new destinations have to be created for ',' between the quoted text
1300 Like "fd,ty,uy" is a one entity, but if you remove the quotes it has to be broken doan into 3 seperate
1304 if (str_b_context [1] == '"') {
1308 for (p = text + (end_pos-1), j = end_pos - 1; *p && *p != '"' ; p = g_utf8_next_char (p), j++) {
1309 gunichar c = g_utf8_get_char (p);
1311 insert_destination_at_position (name_selector_entry, j+1);
1317 /* Let model know about changes */
1318 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1319 if (!*text || strlen(text) <= 0) {
1320 /* If the entry was completely cleared, remove the initial destination too */
1321 remove_destination_by_index (name_selector_entry, 0);
1322 generate_attribute_list (name_selector_entry);
1323 } else if (!del_space){
1324 modify_destination_at_position (name_selector_entry, start_pos);
1327 /* If editing within the string, we need to regenerate attributes */
1329 generate_attribute_list (name_selector_entry);
1331 /* Prevent type-ahead completion */
1332 if (name_selector_entry->type_ahead_complete_cb_id) {
1333 g_source_remove (name_selector_entry->type_ahead_complete_cb_id);
1334 name_selector_entry->type_ahead_complete_cb_id = 0;
1337 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1341 completion_match_selected (ENameSelectorEntry *name_selector_entry, GtkTreeModel *model,
1345 EDestination *destination;
1347 GtkTreeIter generator_iter;
1348 GtkTreeIter contact_iter;
1351 if (!name_selector_entry->contact_store)
1354 gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model),
1355 &generator_iter, iter);
1356 e_tree_model_generator_convert_iter_to_child_iter (name_selector_entry->email_generator,
1357 &contact_iter, &email_n,
1360 contact = e_contact_store_get_contact (name_selector_entry->contact_store, &contact_iter);
1361 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1363 /* Set the contact in the model's destination */
1365 destination = find_destination_at_position (name_selector_entry, cursor_pos);
1366 e_destination_set_contact (destination, contact, email_n);
1367 sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
1368 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ",", -1, &cursor_pos);
1370 /* Place cursor at end of address */
1372 gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), cursor_pos);
1373 g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
1378 entry_activate (ENameSelectorEntry *name_selector_entry)
1381 gint range_start, range_end;
1382 ENameSelectorEntryPrivate *priv;
1383 EDestination *destination;
1388 cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
1392 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1394 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1395 if (!get_range_at_position (text, cursor_pos, &range_start, &range_end))
1398 range_len = range_end - range_start;
1399 if (range_len < COMPLETION_CUE_MIN_LEN)
1402 destination = find_destination_at_position (name_selector_entry, cursor_pos);
1406 cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
1408 if (!find_existing_completion (name_selector_entry, cue_str, &contact,
1409 &textrep, &matched_field)) {
1415 sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
1417 /* Place cursor at end of address */
1418 get_range_at_position (text, cursor_pos, &range_start, &range_end);
1420 if (priv->is_completing) {
1421 char *str_context=NULL;
1423 str_context = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), range_end, range_end+1);
1425 if (str_context[0] != ',') {
1427 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &range_end);
1430 int newpos = strlen (text);
1432 /* Doing this we can make sure that It wont ask for completion again. */
1433 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &newpos);
1434 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1435 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), newpos-2, newpos);
1436 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1438 /* Move it close to next destination*/
1439 range_end = range_end+2;
1444 gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), range_end);
1445 g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
1447 priv->is_completing = FALSE;
1451 user_focus_in (ENameSelectorEntry *name_selector_entry, GdkEventFocus *event_focus)
1454 * To preserve selected text, do not propagate the event any more.
1461 user_focus_out (ENameSelectorEntry *name_selector_entry, GdkEventFocus *event_focus)
1463 ENameSelectorEntryPrivate *priv;
1465 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
1467 if (!event_focus->in && priv->is_completing) {
1468 entry_activate (name_selector_entry);
1471 if (name_selector_entry->type_ahead_complete_cb_id) {
1472 g_source_remove (name_selector_entry->type_ahead_complete_cb_id);
1475 if (name_selector_entry->update_completions_cb_id) {
1476 g_source_remove (name_selector_entry->update_completions_cb_id);
1479 clear_completion_model (name_selector_entry);
1485 deep_free_list (GList *list)
1489 for (l = list; l; l = g_list_next (l))
1496 contact_layout_formatter (GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *model,
1497 GtkTreeIter *iter, ENameSelectorEntry *name_selector_entry)
1500 GtkTreeIter generator_iter;
1501 GtkTreeIter contact_store_iter;
1508 if (!name_selector_entry->contact_store)
1511 gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model),
1512 &generator_iter, iter);
1513 e_tree_model_generator_convert_iter_to_child_iter (name_selector_entry->email_generator,
1514 &contact_store_iter, &email_n,
1517 contact = e_contact_store_get_contact (name_selector_entry->contact_store, &contact_store_iter);
1518 email_list = e_contact_get (contact, E_CONTACT_EMAIL);
1519 email_str = g_list_nth_data (email_list, email_n);
1520 file_as_str = e_contact_get (contact, E_CONTACT_FILE_AS);
1522 if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
1523 string = g_strdup_printf ("%s", file_as_str ? file_as_str : "?");
1525 string = g_strdup_printf ("%s%s<%s>", file_as_str ? file_as_str : "",
1526 file_as_str ? " " : "",
1527 email_str ? email_str : "");
1530 g_free (file_as_str);
1531 deep_free_list (email_list);
1533 g_object_set (cell, "text", string, NULL);
1538 generate_contact_rows (EContactStore *contact_store, GtkTreeIter *iter,
1539 ENameSelectorEntry *name_selector_entry)
1542 const gchar *contact_uid;
1546 contact = e_contact_store_get_contact (contact_store, iter);
1547 g_assert (contact != NULL);
1549 contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
1551 return 0; /* Can happen with broken databases */
1553 if (e_contact_get (contact, E_CONTACT_IS_LIST))
1556 email_list = e_contact_get (contact, E_CONTACT_EMAIL);
1557 n_rows = g_list_length (email_list);
1558 deep_free_list (email_list);
1564 ensure_type_ahead_complete_on_idle (ENameSelectorEntry *name_selector_entry)
1566 if (!name_selector_entry->type_ahead_complete_cb_id) {
1567 name_selector_entry->type_ahead_complete_cb_id =
1568 g_idle_add ((GSourceFunc) type_ahead_complete_on_idle_cb,
1569 name_selector_entry);
1574 setup_contact_store (ENameSelectorEntry *name_selector_entry)
1576 if (name_selector_entry->email_generator) {
1577 g_object_unref (name_selector_entry->email_generator);
1578 name_selector_entry->email_generator = NULL;
1581 if (name_selector_entry->contact_store) {
1582 name_selector_entry->email_generator =
1583 e_tree_model_generator_new (GTK_TREE_MODEL (name_selector_entry->contact_store));
1585 e_tree_model_generator_set_generate_func (name_selector_entry->email_generator,
1586 (ETreeModelGeneratorGenerateFunc) generate_contact_rows,
1587 name_selector_entry, NULL);
1589 /* Assign the store to the entry completion */
1591 gtk_entry_completion_set_model (name_selector_entry->entry_completion,
1592 GTK_TREE_MODEL (name_selector_entry->email_generator));
1594 /* Set up callback for incoming matches */
1595 g_signal_connect_swapped (name_selector_entry->contact_store, "row-inserted",
1596 G_CALLBACK (ensure_type_ahead_complete_on_idle), name_selector_entry);
1598 /* Remove the store from the entry completion */
1600 gtk_entry_completion_set_model (name_selector_entry->entry_completion, NULL);
1605 setup_default_contact_store (ENameSelectorEntry *name_selector_entry)
1610 g_return_if_fail (name_selector_entry->contact_store == NULL);
1612 /* Create a book for each completion source, and assign them to the contact store */
1614 name_selector_entry->contact_store = e_contact_store_new ();
1615 groups = e_source_list_peek_groups (name_selector_entry->source_list);
1617 for (l = groups; l; l = g_slist_next (l)) {
1618 ESourceGroup *group = l->data;
1619 GSList *sources = e_source_group_peek_sources (group);
1622 for (m = sources; m; m = g_slist_next (m)) {
1623 ESource *source = m->data;
1625 const gchar *completion;
1627 /* Skip non-completion sources */
1628 completion = e_source_get_property (source, "completion");
1629 if (!completion || g_ascii_strcasecmp (completion, "true"))
1632 book = e_load_book_source (source, NULL, NULL);
1636 e_contact_store_add_book (name_selector_entry->contact_store, book);
1637 g_object_unref (book);
1641 setup_contact_store (name_selector_entry);
1645 destination_row_changed (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter)
1647 EDestination *destination;
1648 const gchar *entry_text;
1650 gint range_start, range_end;
1653 n = gtk_tree_path_get_indices (path) [0];
1654 destination = e_destination_store_get_destination (name_selector_entry->destination_store, iter);
1661 entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1662 if (!get_range_by_index (entry_text, n, &range_start, &range_end)) {
1663 g_warning ("ENameSelectorEntry is out of sync with model!");
1667 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1668 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1670 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1672 text = sanitize_string (e_destination_get_textrep (destination, FALSE));
1673 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &range_start);
1676 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1677 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1679 clear_completion_model (name_selector_entry);
1680 generate_attribute_list (name_selector_entry);
1684 destination_row_inserted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter)
1686 EDestination *destination;
1687 const gchar *entry_text;
1689 gboolean comma_before = FALSE;
1690 gboolean comma_after = FALSE;
1691 gint range_start, range_end;
1695 n = gtk_tree_path_get_indices (path) [0];
1696 destination = e_destination_store_get_destination (name_selector_entry->destination_store, iter);
1699 g_assert (destination != NULL);
1701 entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1703 if (get_range_by_index (entry_text, n, &range_start, &range_end) && range_start != range_end) {
1704 /* Another destination comes after us */
1705 insert_pos = range_start;
1707 } else if (n > 0 && get_range_by_index (entry_text, n - 1, &range_start, &range_end)) {
1708 /* Another destination comes before us */
1709 insert_pos = range_end;
1710 comma_before = TRUE;
1711 } else if (n == 0) {
1712 /* We're the sole destination */
1715 g_warning ("ENameSelectorEntry is out of sync with model!");
1719 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1722 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
1724 text = sanitize_string (e_destination_get_textrep (destination, FALSE));
1725 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &insert_pos);
1729 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
1731 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
1733 clear_completion_model (name_selector_entry);
1734 generate_attribute_list (name_selector_entry);
1738 destination_row_deleted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path)
1741 gboolean deleted_comma = FALSE;
1742 gint range_start, range_end;
1746 n = gtk_tree_path_get_indices (path) [0];
1749 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1751 if (!get_range_by_index (text, n, &range_start, &range_end)) {
1752 g_warning ("ENameSelectorEntry is out of sync with model!");
1756 /* Expand range for deletion forwards */
1757 for (p0 = g_utf8_offset_to_pointer (text, range_end); *p0;
1758 p0 = g_utf8_next_char (p0), range_end++) {
1759 gunichar c = g_utf8_get_char (p0);
1761 /* Gobble spaces directly after comma */
1762 if (c != ' ' && deleted_comma) {
1768 deleted_comma = TRUE;
1773 /* Expand range for deletion backwards */
1774 for (p0 = g_utf8_offset_to_pointer (text, range_start); range_start > 0;
1775 p0 = g_utf8_prev_char (p0), range_start--) {
1776 gunichar c = g_utf8_get_char (p0);
1779 if (!deleted_comma) {
1780 deleted_comma = TRUE;
1786 /* Leave a space in front; we deleted the comma and spaces before the
1787 * following destination */
1788 p0 = g_utf8_next_char (p0);
1789 c = g_utf8_get_char (p0);
1797 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1798 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
1799 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1801 clear_completion_model (name_selector_entry);
1802 generate_attribute_list (name_selector_entry);
1806 setup_destination_store (ENameSelectorEntry *name_selector_entry)
1810 g_signal_connect_swapped (name_selector_entry->destination_store, "row-changed",
1811 G_CALLBACK (destination_row_changed), name_selector_entry);
1812 g_signal_connect_swapped (name_selector_entry->destination_store, "row-deleted",
1813 G_CALLBACK (destination_row_deleted), name_selector_entry);
1814 g_signal_connect_swapped (name_selector_entry->destination_store, "row-inserted",
1815 G_CALLBACK (destination_row_inserted), name_selector_entry);
1817 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->destination_store), &iter))
1823 path = gtk_tree_model_get_path (GTK_TREE_MODEL (name_selector_entry->destination_store), &iter);
1826 destination_row_inserted (name_selector_entry, path, &iter);
1827 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->destination_store), &iter));
1831 prepare_popup_destination (ENameSelectorEntry *name_selector_entry, GdkEventButton *event_button)
1833 EDestination *destination;
1834 PangoLayout *layout;
1835 gint layout_offset_x;
1836 gint layout_offset_y;
1840 if (event_button->type != GDK_BUTTON_PRESS)
1843 if (event_button->button != 3)
1846 if (name_selector_entry->popup_destination) {
1847 g_object_unref (name_selector_entry->popup_destination);
1848 name_selector_entry->popup_destination = NULL;
1851 gtk_entry_get_layout_offsets (GTK_ENTRY (name_selector_entry),
1852 &layout_offset_x, &layout_offset_y);
1853 x = (event_button->x + 0.5) - layout_offset_x;
1854 y = (event_button->y + 0.5) - layout_offset_y;
1859 layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
1860 if (!pango_layout_xy_to_index (layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, NULL))
1863 index = gtk_entry_layout_index_to_text_index (GTK_ENTRY (name_selector_entry), index);
1864 destination = find_destination_at_position (name_selector_entry, index);
1865 /* FIXME: Add this to a private variable, in ENameSelectorEntry Class*/
1866 g_object_set_data ((GObject *)name_selector_entry, "index", GINT_TO_POINTER(index));
1868 if (!destination || !e_destination_get_contact (destination))
1871 /* TODO: Unref destination when we finalize */
1872 name_selector_entry->popup_destination = g_object_ref (destination);
1877 find_book_by_contact (GList *books, const gchar *contact_uid)
1881 for (l = books; l; l = g_list_next (l)) {
1882 EBook *book = l->data;
1886 result = e_book_get_contact (book, contact_uid, &contact, NULL);
1888 g_object_unref (contact);
1898 editor_closed_cb (GtkObject *editor, gpointer data)
1902 EDestination *destination;
1907 ENameSelectorEntry *name_selector_entry = E_NAME_SELECTOR_ENTRY (data);
1909 destination = name_selector_entry->popup_destination;
1910 contact = e_destination_get_contact (destination);
1913 contact_uid = e_contact_get (contact, E_CONTACT_UID);
1917 if (name_selector_entry->contact_store) {
1918 books = e_contact_store_get_books (name_selector_entry->contact_store);
1919 book = find_book_by_contact (books, contact_uid);
1920 g_list_free (books);
1927 result = e_book_get_contact(book, contact_uid, &contact, NULL);
1928 email_num = e_destination_get_email_num(destination);
1929 e_destination_set_contact (destination, contact, email_num);
1931 g_free (contact_uid);
1932 g_object_unref (contact);
1933 g_object_unref (editor);
1934 g_object_unref (name_selector_entry);
1938 popup_activate_inline_expand (ENameSelectorEntry *name_selector_entry, GtkWidget *menu_item)
1940 const char *email_list, *text;
1941 gchar *sanitized_text;
1942 EDestination *destination = name_selector_entry->popup_destination;
1943 int position, start, end;
1945 position = GPOINTER_TO_INT(g_object_get_data ((GObject *)name_selector_entry, "index"));
1947 email_list = e_destination_get_address(destination);
1948 text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
1949 get_range_at_position (text, position, &start, &end);
1951 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1953 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), start, end);
1955 sanitized_text = sanitize_string (email_list);
1956 gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), sanitized_text, -1, &start);
1957 g_free (sanitized_text);
1959 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
1961 clear_completion_model (name_selector_entry);
1962 generate_attribute_list (name_selector_entry);
1967 popup_activate_contact (ENameSelectorEntry *name_selector_entry, GtkWidget *menu_item)
1971 EDestination *destination;
1975 destination = name_selector_entry->popup_destination;
1979 contact = e_destination_get_contact (destination);
1983 contact_uid = e_contact_get (contact, E_CONTACT_UID);
1986 if (name_selector_entry->contact_store) {
1987 books = e_contact_store_get_books (name_selector_entry->contact_store);
1988 /*FIXME: read URI from contact and get the book ?*/
1989 book = find_book_by_contact (books, contact_uid);
1990 g_list_free (books);
1991 g_free (contact_uid);
1999 if (e_destination_is_evolution_list (destination)) {
2000 GtkWidget *contact_list_editor;
2002 if (!name_selector_entry->contact_list_editor_func)
2005 contact_list_editor = (*name_selector_entry->contact_list_editor_func) (book, contact, FALSE, TRUE);
2006 g_object_ref (name_selector_entry);
2007 g_signal_connect (contact_list_editor, "editor_closed",
2008 G_CALLBACK (editor_closed_cb), name_selector_entry);
2010 GtkWidget *contact_editor;
2012 if (!name_selector_entry->contact_editor_func)
2015 contact_editor = (*name_selector_entry->contact_editor_func) (book, contact, FALSE, TRUE);
2016 g_object_ref (name_selector_entry);
2017 g_signal_connect (contact_editor, "editor_closed",
2018 G_CALLBACK (editor_closed_cb), name_selector_entry);
2023 popup_activate_email (ENameSelectorEntry *name_selector_entry, GtkWidget *menu_item)
2025 EDestination *destination;
2029 destination = name_selector_entry->popup_destination;
2033 contact = e_destination_get_contact (destination);
2037 email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "order"));
2038 e_destination_set_contact (destination, contact, email_num);
2042 popup_activate_list (EDestination *destination, GtkWidget *item)
2044 gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
2046 e_destination_set_ignored (destination, !status);
2050 popup_activate_cut (ENameSelectorEntry *name_selector_entry, GtkWidget *menu_item)
2052 EDestination *destination;
2053 const char *contact_email;
2054 char *pemail = NULL;
2055 GtkClipboard *clipboard;
2057 destination = name_selector_entry->popup_destination;
2058 contact_email =e_destination_get_address(destination);
2060 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2061 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2063 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
2064 pemail = g_strconcat (contact_email, ",", NULL);
2065 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
2067 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2068 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
2070 gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), 0, 0);
2071 e_destination_store_remove_destination (name_selector_entry->destination_store, destination);
2074 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2075 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2079 popup_activate_copy (ENameSelectorEntry *name_selector_entry, GtkWidget *menu_item)
2081 EDestination *destination;
2082 const char *contact_email;
2084 GtkClipboard *clipboard;
2086 destination = name_selector_entry->popup_destination;
2087 contact_email = e_destination_get_address(destination);
2089 g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2090 g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2092 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
2093 pemail = g_strconcat (contact_email, ",", NULL);
2094 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
2096 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2097 gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
2099 g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
2100 g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
2105 destination_set_list (GtkWidget *item, EDestination *destination)
2108 gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
2110 contact = e_destination_get_contact (destination);
2114 e_destination_set_ignored (destination, !status);
2118 destination_set_email (GtkWidget *item, EDestination *destination)
2123 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
2125 contact = e_destination_get_contact (destination);
2129 email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "order"));
2130 e_destination_set_contact (destination, contact, email_num);
2134 populate_popup (ENameSelectorEntry *name_selector_entry, GtkMenu *menu)
2136 EDestination *destination;
2138 GtkWidget *menu_item;
2139 GList *email_list=NULL;
2146 GSList *group = NULL;
2148 gboolean show_menu = FALSE;
2150 destination = name_selector_entry->popup_destination;
2154 contact = e_destination_get_contact (destination);
2158 /* Prepend the menu items, backwards */
2162 menu_item = gtk_separator_menu_item_new ();
2163 gtk_widget_show (menu_item);
2164 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2165 email_num = e_destination_get_email_num (destination);
2168 is_list = e_contact_get (contact, E_CONTACT_IS_LIST) ? TRUE : FALSE;
2170 const GList *dests = e_destination_list_get_dests (destination);
2172 int length = g_list_length ((GList *)dests);
2174 for (iter = (GList *)dests; iter; iter = iter->next) {
2175 EDestination *dest = (EDestination *) iter->data;
2176 const char *email = e_destination_get_email (dest);
2178 if (!email || *email == '\0')
2182 menu_item = gtk_check_menu_item_new_with_label (email);
2183 g_signal_connect (menu_item, "toggled", G_CALLBACK (destination_set_list), dest);
2185 menu_item = gtk_menu_item_new_with_label (email);
2188 gtk_widget_show (menu_item);
2189 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2193 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), !e_destination_is_ignored(dest));
2194 g_signal_connect_swapped (menu_item, "activate", G_CALLBACK (popup_activate_list),
2200 email_list = e_contact_get (contact, E_CONTACT_EMAIL);
2201 len = g_list_length (email_list);
2203 for (l = email_list, i = 0; l; l = g_list_next (l), i++) {
2204 gchar *email = l->data;
2206 if (!email || *email == '\0')
2210 menu_item = gtk_radio_menu_item_new_with_label (group, email);
2211 group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menu_item));
2212 g_signal_connect (menu_item, "toggled", G_CALLBACK (destination_set_email), destination);
2214 menu_item = gtk_menu_item_new_with_label (email);
2217 gtk_widget_show (menu_item);
2218 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2220 g_object_set_data (G_OBJECT (menu_item), "order", GINT_TO_POINTER (i));
2222 if ( i == email_num && len > 1 ) {
2223 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), TRUE);
2224 g_signal_connect_swapped (menu_item, "activate", G_CALLBACK (popup_activate_email),
2225 name_selector_entry);
2233 menu_item = gtk_separator_menu_item_new ();
2234 gtk_widget_show (menu_item);
2235 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2238 /* Expand a list inline */
2240 /* To Translators: This would be similiar to "Expand MyList Inline" where MyList is a Contact List*/
2241 edit_label = g_strdup_printf (_("E_xpand %s Inline"), (char *)e_contact_get_const (contact, E_CONTACT_FILE_AS));
2242 menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
2243 g_free (edit_label);
2244 gtk_widget_show (menu_item);
2245 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2246 g_signal_connect_swapped (menu_item, "activate", G_CALLBACK (popup_activate_inline_expand),
2247 name_selector_entry);
2250 menu_item = gtk_separator_menu_item_new ();
2251 gtk_widget_show (menu_item);
2252 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2255 /* Copy Contact Item */
2256 copy_label = g_strdup_printf (_("Cop_y %s"), (char *)e_contact_get_const (contact, E_CONTACT_FILE_AS));
2257 menu_item = gtk_menu_item_new_with_mnemonic (copy_label);
2258 g_free (copy_label);
2259 gtk_widget_show (menu_item);
2260 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2262 g_signal_connect_swapped (menu_item, "activate", G_CALLBACK (popup_activate_copy),
2263 name_selector_entry);
2265 /* Cut Contact Item */
2266 cut_label = g_strdup_printf (_("C_ut %s"), (char *)e_contact_get_const (contact, E_CONTACT_FILE_AS));
2267 menu_item = gtk_menu_item_new_with_mnemonic (cut_label);
2269 gtk_widget_show (menu_item);
2270 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2272 g_signal_connect_swapped (menu_item, "activate", G_CALLBACK (popup_activate_cut),
2273 name_selector_entry);
2276 menu_item = gtk_separator_menu_item_new ();
2277 gtk_widget_show (menu_item);
2278 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2281 /* Edit Contact item */
2283 edit_label = g_strdup_printf (_("_Edit %s"), (char *)e_contact_get_const (contact, E_CONTACT_FILE_AS));
2284 menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
2285 g_free (edit_label);
2286 gtk_widget_show (menu_item);
2287 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
2289 g_signal_connect_swapped (menu_item, "activate", G_CALLBACK (popup_activate_contact),
2290 name_selector_entry);
2292 deep_free_list (email_list);
2296 e_name_selector_entry_init (ENameSelectorEntry *name_selector_entry)
2298 GtkCellRenderer *renderer;
2299 ENameSelectorEntryPrivate *priv;
2302 priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
2306 if (!e_book_get_addressbooks (&name_selector_entry->source_list, NULL)) {
2307 g_warning ("ENameSelectorEntry can't find any addressbooks!");
2311 /* read minimum_query_length from gconf*/
2312 gconf = gconf_client_get_default();
2313 if (COMPLETION_CUE_MIN_LEN == 0) {
2314 if ((COMPLETION_CUE_MIN_LEN = gconf_client_get_int (gconf, MINIMUM_QUERY_LENGTH, NULL)))
2316 else COMPLETION_CUE_MIN_LEN = 3;
2318 g_object_unref (G_OBJECT (gconf));
2322 g_signal_connect (name_selector_entry, "insert-text", G_CALLBACK (user_insert_text), name_selector_entry);
2323 g_signal_connect (name_selector_entry, "delete-text", G_CALLBACK (user_delete_text), name_selector_entry);
2324 g_signal_connect (name_selector_entry, "focus-out-event", G_CALLBACK (user_focus_out), name_selector_entry);
2325 g_signal_connect_after (name_selector_entry, "focus-in-event", G_CALLBACK (user_focus_in), name_selector_entry);
2329 g_signal_connect (name_selector_entry, "expose-event", G_CALLBACK (expose_event), name_selector_entry);
2331 /* Activation: Complete current entry if possible */
2333 g_signal_connect (name_selector_entry, "activate", G_CALLBACK (entry_activate), name_selector_entry);
2337 g_signal_connect (name_selector_entry, "button-press-event", G_CALLBACK (prepare_popup_destination), name_selector_entry);
2338 g_signal_connect (name_selector_entry, "populate-popup", G_CALLBACK (populate_popup), name_selector_entry);
2342 name_selector_entry->email_generator = NULL;
2344 name_selector_entry->entry_completion = gtk_entry_completion_new ();
2345 gtk_entry_completion_set_match_func (name_selector_entry->entry_completion,
2346 (GtkEntryCompletionMatchFunc) completion_match_cb, NULL, NULL);
2347 g_signal_connect_swapped (name_selector_entry->entry_completion, "match-selected",
2348 G_CALLBACK (completion_match_selected), name_selector_entry);
2350 gtk_entry_set_completion (GTK_ENTRY (name_selector_entry), name_selector_entry->entry_completion);
2352 /* Completion list name renderer */
2354 renderer = gtk_cell_renderer_text_new ();
2355 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (name_selector_entry->entry_completion),
2357 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (name_selector_entry->entry_completion),
2358 GTK_CELL_RENDERER (renderer),
2359 (GtkCellLayoutDataFunc) contact_layout_formatter,
2360 name_selector_entry, NULL);
2362 /* Destination store */
2364 name_selector_entry->destination_store = e_destination_store_new ();
2365 setup_destination_store (name_selector_entry);
2366 priv->is_completing = FALSE;
2370 * e_name_selector_entry_new:
2372 * Creates a new #ENameSelectorEntry.
2374 * Return value: A new #ENameSelectorEntry.
2376 ENameSelectorEntry *
2377 e_name_selector_entry_new (void)
2379 return g_object_new (e_name_selector_entry_get_type (), NULL);
2383 * e_name_selector_entry_peek_contact_store:
2384 * @name_selector_entry: an #ENameSelectorEntry
2386 * Gets the #EContactStore being used by @name_selector_entry.
2388 * Return value: An #EContactStore.
2391 e_name_selector_entry_peek_contact_store (ENameSelectorEntry *name_selector_entry)
2393 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
2395 return name_selector_entry->contact_store;
2399 * e_name_selector_entry_set_contact_store:
2400 * @name_selector_entry: an #ENameSelectorEntry
2401 * @contact_store: an #EContactStore to use
2403 * Sets the #EContactStore being used by @name_selector_entry to @contact_store.
2406 e_name_selector_entry_set_contact_store (ENameSelectorEntry *name_selector_entry,
2407 EContactStore *contact_store)
2409 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
2410 g_return_if_fail (contact_store == NULL || E_IS_CONTACT_STORE (contact_store));
2412 if (contact_store == name_selector_entry->contact_store)
2415 if (name_selector_entry->contact_store)
2416 g_object_unref (name_selector_entry->contact_store);
2417 name_selector_entry->contact_store = contact_store;
2418 if (name_selector_entry->contact_store)
2419 g_object_ref (name_selector_entry->contact_store);
2421 setup_contact_store (name_selector_entry);
2425 * e_name_selector_entry_peek_destination_store:
2426 * @name_selector_entry: an #ENameSelectorEntry
2428 * Gets the #EDestinationStore being used to store @name_selector_entry's destinations.
2430 * Return value: An #EDestinationStore.
2433 e_name_selector_entry_peek_destination_store (ENameSelectorEntry *name_selector_entry)
2435 g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
2437 return name_selector_entry->destination_store;
2441 * e_name_selector_entry_set_destination_store:
2442 * @name_selector_entry: an #ENameSelectorEntry
2443 * @destination_store: an #EDestinationStore to use
2445 * Sets @destination_store as the #EDestinationStore to be used to store
2446 * destinations for @name_selector_entry.
2449 e_name_selector_entry_set_destination_store (ENameSelectorEntry *name_selector_entry,
2450 EDestinationStore *destination_store)
2452 g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
2453 g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
2455 if (destination_store == name_selector_entry->destination_store)
2458 g_object_unref (name_selector_entry->destination_store);
2459 name_selector_entry->destination_store = g_object_ref (destination_store);
2461 setup_destination_store (name_selector_entry);
2465 * e_name_selector_entry_set_contact_editor_func:
2470 e_name_selector_entry_set_contact_editor_func (ENameSelectorEntry *name_selector_entry, gpointer func)
2472 name_selector_entry->contact_editor_func = func;
2476 * e_name_selector_entry_set_contact_list_editor_func:
2481 e_name_selector_entry_set_contact_list_editor_func (ENameSelectorEntry *name_selector_entry, gpointer func)
2483 name_selector_entry->contact_list_editor_func = func;