aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-name-selector-dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-name-selector-dialog.c')
-rw-r--r--e-util/e-name-selector-dialog.c1863
1 files changed, 1863 insertions, 0 deletions
diff --git a/e-util/e-name-selector-dialog.c b/e-util/e-name-selector-dialog.c
new file mode 100644
index 0000000000..ece556b0a9
--- /dev/null
+++ b/e-util/e-name-selector-dialog.c
@@ -0,0 +1,1863 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-dialog.c - Dialog that lets user pick EDestinations.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Hans Petter Jansson <hpj@novell.com>
+ */
+
+#ifdef GTK_DISABLE_DEPRECATED
+#undef GTK_DISABLE_DEPRECATED
+#endif
+
+#include <config.h>
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n-lib.h>
+
+#include <libebook/libebook.h>
+#include <libebackend/libebackend.h>
+
+#include "e-source-combo-box.h"
+#include "e-destination-store.h"
+#include "e-contact-store.h"
+#include "e-client-utils.h"
+#include "e-name-selector-dialog.h"
+#include "e-name-selector-entry.h"
+
+#define E_NAME_SELECTOR_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_NAME_SELECTOR_DIALOG, ENameSelectorDialogPrivate))
+
+typedef struct {
+ gchar *name;
+
+ GtkGrid *section_grid;
+ GtkLabel *label;
+ GtkButton *transfer_button;
+ GtkButton *remove_button;
+ GtkTreeView *destination_view;
+}
+Section;
+
+typedef struct {
+ GtkTreeView *view;
+ GtkButton *button;
+ ENameSelectorDialog *dlg_ptr;
+} SelData;
+
+struct _ENameSelectorDialogPrivate {
+ ESourceRegistry *registry;
+ ENameSelectorModel *name_selector_model;
+ GtkTreeModelSort *contact_sort;
+ GCancellable *cancellable;
+
+ GtkTreeView *contact_view;
+ GtkLabel *status_label;
+ GtkGrid *destination_vgrid;
+ GtkEntry *search_entry;
+ GtkSizeGroup *button_size_group;
+ GtkWidget *category_combobox;
+ GtkWidget *contact_window;
+
+ GArray *sections;
+
+ guint destination_index;
+ GSList *user_query_fields;
+ GtkSizeGroup *dest_label_size_group;
+};
+
+enum {
+ PROP_0,
+ PROP_REGISTRY
+};
+
+static void search_changed (ENameSelectorDialog *name_selector_dialog);
+static void source_changed (ENameSelectorDialog *name_selector_dialog, ESourceComboBox *source_combo_box);
+static void transfer_button_clicked (ENameSelectorDialog *name_selector_dialog, GtkButton *transfer_button);
+static void contact_selection_changed (ENameSelectorDialog *name_selector_dialog);
+static void setup_name_selector_model (ENameSelectorDialog *name_selector_dialog);
+static void shutdown_name_selector_model (ENameSelectorDialog *name_selector_dialog);
+static void contact_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path);
+static void destination_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path,
+ GtkTreeViewColumn *column, GtkTreeView *tree_view);
+static gboolean destination_key_press (ENameSelectorDialog *name_selector_dialog, GdkEventKey *event, GtkTreeView *tree_view);
+static void remove_button_clicked (GtkButton *button, SelData *data);
+static void remove_books (ENameSelectorDialog *name_selector_dialog);
+static void contact_column_formatter (GtkTreeViewColumn *column, GtkCellRenderer *cell,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ ENameSelectorDialog *name_selector_dialog);
+static void destination_column_formatter (GtkTreeViewColumn *column, GtkCellRenderer *cell,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ ENameSelectorDialog *name_selector_dialog);
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+G_DEFINE_TYPE_WITH_CODE (
+ ENameSelectorDialog,
+ e_name_selector_dialog,
+ GTK_TYPE_DIALOG,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL))
+
+static void
+name_selector_dialog_populate_categories (ENameSelectorDialog *name_selector_dialog)
+{
+ GtkWidget *combo_box;
+ GList *category_list, *iter;
+
+ /* "Any Category" is preloaded. */
+ combo_box = name_selector_dialog->priv->category_combobox;
+ if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) == -1)
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
+
+ /* Categories are already sorted. */
+ category_list = e_categories_get_list ();
+ for (iter = category_list; iter != NULL; iter = iter->next) {
+ /* Only add user-visible categories. */
+ if (!e_categories_is_searchable (iter->data))
+ continue;
+
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (combo_box), iter->data);
+ }
+
+ g_list_free (category_list);
+
+ g_signal_connect_swapped (
+ combo_box, "changed",
+ G_CALLBACK (search_changed), name_selector_dialog);
+}
+
+static void
+name_selector_dialog_set_registry (ENameSelectorDialog *name_selector_dialog,
+ ESourceRegistry *registry)
+{
+ g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+ g_return_if_fail (name_selector_dialog->priv->registry == NULL);
+
+ name_selector_dialog->priv->registry = g_object_ref (registry);
+}
+
+static void
+name_selector_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ name_selector_dialog_set_registry (
+ E_NAME_SELECTOR_DIALOG (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_dialog_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ g_value_set_object (
+ value,
+ e_name_selector_dialog_get_registry (
+ E_NAME_SELECTOR_DIALOG (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_dialog_dispose (GObject *object)
+{
+ ENameSelectorDialogPrivate *priv;
+
+ priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+ remove_books (E_NAME_SELECTOR_DIALOG (object));
+ shutdown_name_selector_model (E_NAME_SELECTOR_DIALOG (object));
+
+ if (priv->registry != NULL) {
+ g_object_unref (priv->registry);
+ priv->registry = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->dispose (object);
+}
+
+static void
+name_selector_dialog_finalize (GObject *object)
+{
+ ENameSelectorDialogPrivate *priv;
+
+ priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+ g_slist_foreach (priv->user_query_fields, (GFunc) g_free, NULL);
+ g_slist_free (priv->user_query_fields);
+
+ g_array_free (priv->sections, TRUE);
+ g_object_unref (priv->button_size_group);
+ g_object_unref (priv->dest_label_size_group);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->finalize (object);
+}
+
+static void
+name_selector_dialog_constructed (GObject *object)
+{
+ ENameSelectorDialogPrivate *priv;
+ GtkTreeSelection *contact_selection;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_renderer;
+ GtkTreeSelection *selection;
+ ESource *source;
+ gchar *tmp_str;
+ GtkWidget *name_selector_grid;
+ GtkWidget *show_contacts_label;
+ GtkWidget *hgrid;
+ GtkWidget *label;
+ GtkWidget *show_contacts_grid;
+ GtkWidget *AddressBookLabel;
+ GtkWidget *label_category;
+ GtkWidget *search;
+ AtkObject *atko;
+ GtkWidget *label_search;
+ GtkWidget *source_menu_hgrid;
+ GtkWidget *combobox_category;
+ GtkWidget *label_contacts;
+ GtkWidget *scrolledwindow0;
+ GtkWidget *scrolledwindow1;
+ AtkRelationSet *tmp_relation_set;
+ AtkRelationType tmp_relationship;
+ AtkRelation *tmp_relation;
+ AtkObject *scrolledwindow1_relation_targets[1];
+ GtkWidget *source_tree_view;
+ GtkWidget *destination_vgrid;
+ GtkWidget *status_message;
+ GtkWidget *source_combo;
+ const gchar *extension_name;
+
+ priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->constructed (object);
+
+ name_selector_grid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "column-homogeneous", FALSE,
+ "row-spacing", 6,
+ NULL);
+ gtk_widget_show (name_selector_grid);
+ gtk_container_set_border_width (GTK_CONTAINER (name_selector_grid), 0);
+
+ tmp_str = g_strconcat ("<b>", _("Show Contacts"), "</b>", NULL);
+ show_contacts_label = gtk_label_new (tmp_str);
+ gtk_widget_show (show_contacts_label);
+ gtk_container_add (GTK_CONTAINER (name_selector_grid), show_contacts_label);
+ gtk_label_set_use_markup (GTK_LABEL (show_contacts_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (show_contacts_label), 0, 0.5);
+ g_free (tmp_str);
+
+ hgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "row-homogeneous", FALSE,
+ "column-spacing", 12,
+ NULL);
+ gtk_widget_show (hgrid);
+ gtk_container_add (GTK_CONTAINER (name_selector_grid), hgrid);
+
+ label = gtk_label_new ("");
+ gtk_widget_show (label);
+ gtk_container_add (GTK_CONTAINER (hgrid), label);
+
+ show_contacts_grid = gtk_grid_new ();
+ gtk_widget_show (show_contacts_grid);
+ gtk_container_add (GTK_CONTAINER (hgrid), show_contacts_grid);
+ g_object_set (G_OBJECT (show_contacts_grid),
+ "column-spacing", 12,
+ "row-spacing", 6,
+ "hexpand", TRUE,
+ "halign", GTK_ALIGN_FILL,
+ NULL);
+
+ AddressBookLabel = gtk_label_new_with_mnemonic (_("Address B_ook:"));
+ gtk_widget_show (AddressBookLabel);
+ gtk_grid_attach (GTK_GRID (show_contacts_grid), AddressBookLabel, 0, 0, 1, 1);
+ gtk_widget_set_halign (AddressBookLabel, GTK_ALIGN_FILL);
+ gtk_label_set_justify (GTK_LABEL (AddressBookLabel), GTK_JUSTIFY_CENTER);
+ gtk_misc_set_alignment (GTK_MISC (AddressBookLabel), 0, 0.5);
+
+ label_category = gtk_label_new_with_mnemonic (_("Cat_egory:"));
+ gtk_widget_show (label_category);
+ gtk_grid_attach (GTK_GRID (show_contacts_grid), label_category, 0, 1, 1, 1);
+ gtk_widget_set_halign (label_category, GTK_ALIGN_FILL);
+ gtk_label_set_justify (GTK_LABEL (label_category), GTK_JUSTIFY_CENTER);
+ gtk_misc_set_alignment (GTK_MISC (label_category), 0, 0.5);
+
+ hgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "row-homogeneous", FALSE,
+ "column-spacing", 12,
+ "hexpand", TRUE,
+ "halign", GTK_ALIGN_FILL,
+ NULL);
+ gtk_widget_show (hgrid);
+ gtk_grid_attach (GTK_GRID (show_contacts_grid), hgrid, 1, 2, 1, 1);
+
+ search = gtk_entry_new ();
+ gtk_widget_show (search);
+ gtk_widget_set_hexpand (search, TRUE);
+ gtk_widget_set_halign (search, GTK_ALIGN_FILL);
+ gtk_container_add (GTK_CONTAINER (hgrid), search);
+
+ label_search = gtk_label_new_with_mnemonic (_("_Search:"));
+ gtk_widget_show (label_search);
+ gtk_grid_attach (GTK_GRID (show_contacts_grid), label_search, 0, 2, 1, 1);
+ gtk_widget_set_halign (label_search, GTK_ALIGN_FILL);
+ gtk_misc_set_alignment (GTK_MISC (label_search), 0, 0.5);
+
+ source_menu_hgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "row-homogeneous", FALSE,
+ "column-spacing", 0,
+ "halign", GTK_ALIGN_FILL,
+ "valign", GTK_ALIGN_FILL,
+ NULL);
+ gtk_widget_show (source_menu_hgrid);
+ gtk_grid_attach (GTK_GRID (show_contacts_grid), source_menu_hgrid, 1, 0, 1, 1);
+
+ combobox_category = gtk_combo_box_text_new ();
+ gtk_widget_show (combobox_category);
+ g_object_set (G_OBJECT (combobox_category),
+ "halign", GTK_ALIGN_FILL,
+ "valign", GTK_ALIGN_FILL,
+ NULL);
+ gtk_grid_attach (GTK_GRID (show_contacts_grid), combobox_category, 1, 1, 1, 1);
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (combobox_category), _("Any Category"));
+
+ tmp_str = g_strconcat ("<b>", _("Co_ntacts"), "</b>", NULL);
+ label_contacts = gtk_label_new_with_mnemonic (tmp_str);
+ gtk_widget_show (label_contacts);
+ gtk_container_add (GTK_CONTAINER (name_selector_grid), label_contacts);
+ gtk_label_set_use_markup (GTK_LABEL (label_contacts), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label_contacts), 0, 0.5);
+ g_free (tmp_str);
+
+ scrolledwindow0 = gtk_scrolled_window_new (NULL, NULL);
+ priv->contact_window = scrolledwindow0;
+ gtk_widget_show (scrolledwindow0);
+ gtk_widget_set_vexpand (scrolledwindow0, TRUE);
+ gtk_widget_set_valign (scrolledwindow0, GTK_ALIGN_FILL);
+ gtk_container_add (GTK_CONTAINER (name_selector_grid), scrolledwindow0);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (scrolledwindow0),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+ hgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "row-homogeneous", FALSE,
+ "column-spacing", 12,
+ NULL);
+ gtk_widget_show (hgrid);
+ gtk_scrolled_window_add_with_viewport (
+ GTK_SCROLLED_WINDOW (scrolledwindow0), hgrid);
+
+ label = gtk_label_new ("");
+ gtk_widget_show (label);
+ gtk_container_add (GTK_CONTAINER (hgrid), label);
+
+ scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_show (scrolledwindow1);
+ gtk_container_add (GTK_CONTAINER (hgrid), scrolledwindow1);
+ gtk_widget_set_hexpand (scrolledwindow1, TRUE);
+ gtk_widget_set_halign (scrolledwindow1, GTK_ALIGN_FILL);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (scrolledwindow1),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (
+ GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_SHADOW_IN);
+
+ source_tree_view = gtk_tree_view_new ();
+ gtk_widget_show (source_tree_view);
+ gtk_container_add (GTK_CONTAINER (scrolledwindow1), source_tree_view);
+ gtk_tree_view_set_headers_visible (
+ GTK_TREE_VIEW (source_tree_view), FALSE);
+ gtk_tree_view_set_enable_search (
+ GTK_TREE_VIEW (source_tree_view), FALSE);
+
+ destination_vgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "column-homogeneous", TRUE,
+ "row-spacing", 6,
+ "hexpand", TRUE,
+ "halign", GTK_ALIGN_FILL,
+ "vexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ NULL);
+ gtk_widget_show (destination_vgrid);
+ gtk_container_add (GTK_CONTAINER (hgrid), destination_vgrid);
+
+ status_message = gtk_label_new ("");
+ gtk_widget_show (status_message);
+ gtk_container_add (GTK_CONTAINER (name_selector_grid), status_message);
+ gtk_label_set_use_markup (GTK_LABEL (status_message), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (status_message), 0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (status_message), 0, 3);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (AddressBookLabel), source_menu_hgrid);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label_category), combobox_category);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label_search), search);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label_contacts), source_tree_view);
+
+ atko = gtk_widget_get_accessible (search);
+ atk_object_set_name (atko, _("Search"));
+
+ atko = gtk_widget_get_accessible (source_menu_hgrid);
+ atk_object_set_name (atko, _("Address Book"));
+
+ atko = gtk_widget_get_accessible (scrolledwindow1);
+ atk_object_set_name (atko, _("Contacts"));
+ tmp_relation_set = atk_object_ref_relation_set (atko);
+ scrolledwindow1_relation_targets[0] = gtk_widget_get_accessible (label_contacts);
+ tmp_relationship = atk_relation_type_for_name ("labelled-by");
+ tmp_relation = atk_relation_new (scrolledwindow1_relation_targets, 1, tmp_relationship);
+ atk_relation_set_add (tmp_relation_set, tmp_relation);
+ g_object_unref (G_OBJECT (tmp_relation));
+ g_object_unref (G_OBJECT (tmp_relation_set));
+
+ gtk_box_pack_start (
+ GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (object))),
+ name_selector_grid, TRUE, TRUE, 0);
+
+ /* Store pointers to relevant widgets */
+
+ priv->contact_view = GTK_TREE_VIEW (source_tree_view);
+ priv->status_label = GTK_LABEL (status_message);
+ priv->destination_vgrid = GTK_GRID (destination_vgrid);
+ priv->search_entry = GTK_ENTRY (search);
+ priv->category_combobox = combobox_category;
+
+ /* Create size group for transfer buttons */
+
+ priv->button_size_group =
+ gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ /* Create size group for destination labels */
+
+ priv->dest_label_size_group =
+ gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ /* Set up contacts view */
+
+ column = gtk_tree_view_column_new ();
+ cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ());
+ gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+ gtk_tree_view_column_set_cell_data_func (
+ column, cell_renderer, (GtkTreeCellDataFunc)
+ contact_column_formatter, object, NULL);
+
+ selection = gtk_tree_view_get_selection (priv->contact_view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+ gtk_tree_view_append_column (priv->contact_view, column);
+ g_signal_connect_swapped (
+ priv->contact_view, "row-activated",
+ G_CALLBACK (contact_activated), object);
+
+ /* Listen for changes to the contact selection */
+
+ contact_selection = gtk_tree_view_get_selection (priv->contact_view);
+ g_signal_connect_swapped (
+ contact_selection, "changed",
+ G_CALLBACK (contact_selection_changed), object);
+
+ /* Set up our data structures */
+
+ priv->name_selector_model = e_name_selector_model_new ();
+ priv->sections = g_array_new (FALSE, FALSE, sizeof (Section));
+
+ setup_name_selector_model (E_NAME_SELECTOR_DIALOG (object));
+
+ /* Create source menu */
+
+ extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+ source_combo = e_source_combo_box_new (priv->registry, extension_name);
+ g_signal_connect_swapped (
+ source_combo, "changed",
+ G_CALLBACK (source_changed), object);
+
+ source_changed (E_NAME_SELECTOR_DIALOG (object), E_SOURCE_COMBO_BOX (source_combo));
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (AddressBookLabel), source_combo);
+ gtk_widget_show (source_combo);
+ gtk_widget_set_hexpand (source_combo, TRUE);
+ gtk_widget_set_halign (source_combo, GTK_ALIGN_FILL);
+ gtk_container_add (GTK_CONTAINER (source_menu_hgrid), source_combo);
+
+ name_selector_dialog_populate_categories (
+ E_NAME_SELECTOR_DIALOG (object));
+
+ /* Set up search-as-you-type signal */
+
+ g_signal_connect_swapped (
+ search, "changed",
+ G_CALLBACK (search_changed), object);
+
+ /* Display initial source */
+
+ source = e_source_registry_ref_default_address_book (priv->registry);
+ e_source_combo_box_set_active (
+ E_SOURCE_COMBO_BOX (source_combo), source);
+ g_object_unref (source);
+
+ /* Set up dialog defaults */
+
+ gtk_dialog_add_buttons (
+ GTK_DIALOG (object),
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+ NULL);
+
+ /* Try to figure out a sane default size for the dialog. We used to hard
+ * code this to 512 so keep using 512 if the screen is big enough,
+ * otherwise use -1 (use as little as possible, use the
+ * GtkScrolledWindow's scrollbars).
+ *
+ * This should allow scrolling on tiny netbook resolutions and let
+ * others see as much of the dialog as possible.
+ *
+ * 600 pixels seems to be a good lower bound resolution to allow room
+ * above or below for other UI (window manager's?)
+ */
+ gtk_window_set_default_size (
+ GTK_WINDOW (object), 700,
+ gdk_screen_height () >= 600 ? 512 : -1);
+
+ gtk_dialog_set_default_response (
+ GTK_DIALOG (object), GTK_RESPONSE_CLOSE);
+ gtk_window_set_modal (GTK_WINDOW (object), TRUE);
+ gtk_window_set_resizable (GTK_WINDOW (object), TRUE);
+ gtk_container_set_border_width (GTK_CONTAINER (object), 4);
+ gtk_window_set_title (
+ GTK_WINDOW (object),
+ _("Select Contacts from Address Book"));
+ gtk_widget_grab_focus (search);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+e_name_selector_dialog_class_init (ENameSelectorDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (ENameSelectorDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = name_selector_dialog_set_property;
+ object_class->get_property = name_selector_dialog_get_property;
+ object_class->dispose = name_selector_dialog_dispose;
+ object_class->finalize = name_selector_dialog_finalize;
+ object_class->constructed = name_selector_dialog_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_REGISTRY,
+ g_param_spec_object (
+ "registry",
+ "Registry",
+ "Data source registry",
+ E_TYPE_SOURCE_REGISTRY,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_name_selector_dialog_init (ENameSelectorDialog *name_selector_dialog)
+{
+ name_selector_dialog->priv =
+ E_NAME_SELECTOR_DIALOG_GET_PRIVATE (name_selector_dialog);
+}
+
+/**
+ * e_name_selector_dialog_new:
+ * @registry: an #ESourceRegistry
+ *
+ * Creates a new #ENameSelectorDialog.
+ *
+ * Returns: A new #ENameSelectorDialog.
+ **/
+ENameSelectorDialog *
+e_name_selector_dialog_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_NAME_SELECTOR_DIALOG,
+ "registry", registry, NULL);
+}
+
+/**
+ * e_name_selector_dialog_get_registry:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ *
+ * Returns the #ESourceRegistry that was passed to
+ * e_name_selector_dialog_new().
+ *
+ * Returns: the #ESourceRegistry
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_name_selector_dialog_get_registry (ENameSelectorDialog *name_selector_dialog)
+{
+ g_return_val_if_fail (
+ E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog), NULL);
+
+ return name_selector_dialog->priv->registry;
+}
+
+/* --------- *
+ * Utilities *
+ * --------- */
+
+static gchar *
+escape_sexp_string (const gchar *string)
+{
+ GString *gstring;
+ gchar *encoded_string;
+
+ gstring = g_string_new ("");
+ e_sexp_encode_string (gstring, string);
+
+ encoded_string = gstring->str;
+ g_string_free (gstring, FALSE);
+
+ return encoded_string;
+}
+
+static void
+sort_iter_to_contact_store_iter (ENameSelectorDialog *name_selector_dialog,
+ GtkTreeIter *iter,
+ gint *email_n)
+{
+ ETreeModelGenerator *contact_filter;
+ GtkTreeIter child_iter;
+ gint email_n_local;
+
+ contact_filter = e_name_selector_model_peek_contact_filter (
+ name_selector_dialog->priv->name_selector_model);
+
+ gtk_tree_model_sort_convert_iter_to_child_iter (
+ name_selector_dialog->priv->contact_sort, &child_iter, iter);
+ e_tree_model_generator_convert_iter_to_child_iter (
+ contact_filter, iter, &email_n_local, &child_iter);
+
+ if (email_n)
+ *email_n = email_n_local;
+}
+
+static void
+add_destination (ENameSelectorModel *name_selector_model,
+ EDestinationStore *destination_store,
+ EContact *contact,
+ gint email_n,
+ EBookClient *client)
+{
+ EDestination *destination;
+ GList *email_list, *nth;
+
+ /* get the correct index of an email in the contact */
+ email_list = e_name_selector_model_get_contact_emails_without_used (name_selector_model, contact, FALSE);
+ while (nth = g_list_nth (email_list, email_n), nth && nth->data == NULL) {
+ email_n++;
+ }
+ e_name_selector_model_free_emails_list (email_list);
+
+ /* Transfer (actually, copy into a destination and let the model filter out the
+ * source automatically) */
+
+ destination = e_destination_new ();
+ e_destination_set_contact (destination, contact, email_n);
+ if (client)
+ e_destination_set_client (destination, client);
+ e_destination_store_append_destination (destination_store, destination);
+ g_object_unref (destination);
+}
+
+static void
+remove_books (ENameSelectorDialog *name_selector_dialog)
+{
+ EContactStore *contact_store;
+ GSList *clients, *l;
+
+ if (!name_selector_dialog->priv->name_selector_model)
+ return;
+
+ contact_store = e_name_selector_model_peek_contact_store (
+ name_selector_dialog->priv->name_selector_model);
+
+ /* Remove books (should be just one) being viewed */
+ clients = e_contact_store_get_clients (contact_store);
+ for (l = clients; l; l = g_slist_next (l)) {
+ EBookClient *client = l->data;
+ e_contact_store_remove_client (contact_store, client);
+ }
+ g_slist_free (clients);
+
+ /* See if we have a book pending; stop loading it if so */
+ if (name_selector_dialog->priv->cancellable != NULL) {
+ g_cancellable_cancel (name_selector_dialog->priv->cancellable);
+ g_object_unref (name_selector_dialog->priv->cancellable);
+ name_selector_dialog->priv->cancellable = NULL;
+ }
+}
+
+/* ------------------ *
+ * Section management *
+ * ------------------ */
+
+static gint
+find_section_by_transfer_button (ENameSelectorDialog *name_selector_dialog,
+ GtkButton *transfer_button)
+{
+ gint i;
+
+ for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+ Section *section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, i);
+
+ if (section->transfer_button == transfer_button)
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+find_section_by_tree_view (ENameSelectorDialog *name_selector_dialog,
+ GtkTreeView *tree_view)
+{
+ gint i;
+
+ for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+ Section *section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, i);
+
+ if (section->destination_view == tree_view)
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+find_section_by_name (ENameSelectorDialog *name_selector_dialog,
+ const gchar *name)
+{
+ gint i;
+
+ for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+ Section *section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, i);
+
+ if (!strcmp (name, section->name))
+ return i;
+ }
+
+ return -1;
+}
+
+static void
+selection_changed (GtkTreeSelection *selection,
+ SelData *data)
+{
+ GtkTreeSelection *contact_selection;
+ gboolean have_selection = FALSE;
+
+ contact_selection = gtk_tree_view_get_selection (data->view);
+ if (gtk_tree_selection_count_selected_rows (contact_selection) > 0)
+ have_selection = TRUE;
+ gtk_widget_set_sensitive (GTK_WIDGET (data->button), have_selection);
+}
+
+static GtkTreeView *
+make_tree_view_for_section (ENameSelectorDialog *name_selector_dialog,
+ EDestinationStore *destination_store)
+{
+ GtkTreeView *tree_view;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_renderer;
+
+ tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+
+ column = gtk_tree_view_column_new ();
+ cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ());
+ gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+ gtk_tree_view_column_set_cell_data_func (
+ column, cell_renderer,
+ (GtkTreeCellDataFunc) destination_column_formatter,
+ name_selector_dialog, NULL);
+ gtk_tree_view_append_column (tree_view, column);
+ gtk_tree_view_set_headers_visible (tree_view, FALSE);
+ gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (destination_store));
+
+ return tree_view;
+}
+
+static void
+setup_section_button (ENameSelectorDialog *name_selector_dialog,
+ GtkButton *button,
+ double halign,
+ const gchar *label_text,
+ const gchar *icon_name,
+ gboolean icon_before_label)
+{
+ GtkWidget *alignment;
+ GtkWidget *hgrid;
+ GtkWidget *label;
+ GtkWidget *image;
+
+ gtk_size_group_add_widget (
+ name_selector_dialog->priv->button_size_group,
+ GTK_WIDGET (button));
+
+ alignment = gtk_alignment_new (halign, 0.5, 0.0, 0.0);
+ gtk_container_add (GTK_CONTAINER (button), GTK_WIDGET (alignment));
+
+ hgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "row-homogeneous", FALSE,
+ "column-spacing", 2,
+ NULL);
+ gtk_widget_show (hgrid);
+ gtk_container_add (GTK_CONTAINER (alignment), hgrid);
+
+ label = gtk_label_new_with_mnemonic (label_text);
+ gtk_widget_show (label);
+
+ image = gtk_image_new_from_stock (icon_name, GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show (image);
+
+ if (icon_before_label) {
+ gtk_container_add (GTK_CONTAINER (hgrid), image);
+ gtk_container_add (GTK_CONTAINER (hgrid), label);
+ } else {
+ gtk_container_add (GTK_CONTAINER (hgrid), label);
+ gtk_container_add (GTK_CONTAINER (hgrid), image);
+ }
+}
+
+static gint
+add_section (ENameSelectorDialog *name_selector_dialog,
+ const gchar *name,
+ const gchar *pretty_name,
+ EDestinationStore *destination_store)
+{
+ ENameSelectorDialogPrivate *priv;
+ Section section;
+ GtkWidget *vgrid;
+ GtkWidget *alignment;
+ GtkWidget *scrollwin;
+ SelData *data;
+ GtkTreeSelection *selection;
+ gchar *text;
+ GtkWidget *hgrid;
+
+ g_assert (name != NULL);
+ g_assert (pretty_name != NULL);
+ g_assert (E_IS_DESTINATION_STORE (destination_store));
+
+ priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (name_selector_dialog);
+
+ memset (&section, 0, sizeof (Section));
+
+ section.name = g_strdup (name);
+ section.section_grid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "row-homogeneous", FALSE,
+ "column-spacing", 12,
+ "vexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ NULL);
+ section.label = GTK_LABEL (gtk_label_new_with_mnemonic (pretty_name));
+ section.transfer_button = GTK_BUTTON (gtk_button_new ());
+ section.remove_button = GTK_BUTTON (gtk_button_new ());
+ section.destination_view = make_tree_view_for_section (name_selector_dialog, destination_store);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (section.label), GTK_WIDGET (section.destination_view));
+
+ if (pango_parse_markup (pretty_name, -1, '_', NULL,
+ &text, NULL, NULL)) {
+ atk_object_set_name (gtk_widget_get_accessible (
+ GTK_WIDGET (section.destination_view)), text);
+ g_free (text);
+ }
+
+ /* Set up transfer button */
+ g_signal_connect_swapped (
+ section.transfer_button, "clicked",
+ G_CALLBACK (transfer_button_clicked), name_selector_dialog);
+
+ /*data for the remove callback*/
+ data = g_malloc0 (sizeof (SelData));
+ data->view = section.destination_view;
+ data->dlg_ptr = name_selector_dialog;
+
+ /*Associate to an object destroy so that it gets freed*/
+ g_object_set_data_full ((GObject *) section.destination_view, "sel-remove-data", data, g_free);
+
+ g_signal_connect (
+ section.remove_button, "clicked",
+ G_CALLBACK (remove_button_clicked), data);
+
+ /* Alignment and vgrid for the add/remove buttons */
+
+ alignment = gtk_alignment_new (0.5, 0.0, 0.0, 0.0);
+ gtk_container_add (GTK_CONTAINER (section.section_grid), alignment);
+
+ vgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "column-homogeneous", TRUE,
+ "row-spacing", 6,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (alignment), vgrid);
+
+ /* "Add" button */
+ gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (section.transfer_button));
+ setup_section_button (name_selector_dialog, section.transfer_button, 0.7, _("_Add"), "gtk-go-forward", FALSE);
+
+ /* "Remove" button */
+ gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (section.remove_button));
+ setup_section_button (name_selector_dialog, section.remove_button, 0.5, _("_Remove"), "gtk-go-back", TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (section.remove_button), FALSE);
+
+ /* hgrid for label and scrolled window. This is a separate hgrid, instead
+ * of just using the section.section_grid directly, as it has a different
+ * spacing.
+ */
+
+ hgrid = g_object_new (GTK_TYPE_GRID,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "row-homogeneous", FALSE,
+ "column-spacing", 6,
+ "vexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (section.section_grid), hgrid);
+
+ /* Title label */
+
+ gtk_size_group_add_widget (priv->dest_label_size_group, GTK_WIDGET (section.label));
+
+ gtk_misc_set_alignment (GTK_MISC (section.label), 0.0, 0.0);
+ gtk_container_add (GTK_CONTAINER (hgrid), GTK_WIDGET (section.label));
+
+ /* Treeview in a scrolled window */
+ scrollwin = gtk_scrolled_window_new (NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (hgrid), scrollwin);
+ gtk_widget_set_hexpand (scrollwin, TRUE);
+ gtk_widget_set_halign (scrollwin, GTK_ALIGN_FILL);
+ gtk_widget_set_vexpand (scrollwin, TRUE);
+ gtk_widget_set_valign (scrollwin, GTK_ALIGN_FILL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (scrollwin), GTK_WIDGET (section.destination_view));
+
+ /*data for 'changed' callback*/
+ data = g_malloc0 (sizeof (SelData));
+ data->view = section.destination_view;
+ data->button = section.remove_button;
+ g_object_set_data_full ((GObject *) section.destination_view, "sel-change-data", data, g_free);
+ selection = gtk_tree_view_get_selection (section.destination_view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+
+ g_signal_connect (
+ selection, "changed",
+ G_CALLBACK (selection_changed), data);
+
+ g_signal_connect_swapped (
+ section.destination_view, "row-activated",
+ G_CALLBACK (destination_activated), name_selector_dialog);
+ g_signal_connect_swapped (
+ section.destination_view, "key-press-event",
+ G_CALLBACK (destination_key_press), name_selector_dialog);
+
+ /* Done! */
+
+ gtk_widget_show_all (GTK_WIDGET (section.section_grid));
+
+ /* Pack this section's box into the dialog */
+ gtk_container_add (GTK_CONTAINER (name_selector_dialog->priv->destination_vgrid), GTK_WIDGET (section.section_grid));
+ g_object_set (G_OBJECT (section.section_grid),
+ "vexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ NULL);
+
+ g_array_append_val (name_selector_dialog->priv->sections, section);
+
+ /* Make sure UI is consistent */
+ contact_selection_changed (name_selector_dialog);
+
+ return name_selector_dialog->priv->sections->len - 1;
+}
+
+static void
+free_section (ENameSelectorDialog *name_selector_dialog,
+ gint n)
+{
+ Section *section;
+
+ g_assert (n >= 0);
+ g_assert (n < name_selector_dialog->priv->sections->len);
+
+ section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, n);
+
+ g_free (section->name);
+ gtk_widget_destroy (GTK_WIDGET (section->section_grid));
+}
+
+static void
+model_section_added (ENameSelectorDialog *name_selector_dialog,
+ const gchar *name)
+{
+ gchar *pretty_name;
+ EDestinationStore *destination_store;
+
+ e_name_selector_model_peek_section (
+ name_selector_dialog->priv->name_selector_model,
+ name, &pretty_name, &destination_store);
+ add_section (name_selector_dialog, name, pretty_name, destination_store);
+ g_free (pretty_name);
+}
+
+static void
+model_section_removed (ENameSelectorDialog *name_selector_dialog,
+ const gchar *name)
+{
+ gint section_index;
+
+ section_index = find_section_by_name (name_selector_dialog, name);
+ g_assert (section_index >= 0);
+
+ free_section (name_selector_dialog, section_index);
+ g_array_remove_index (
+ name_selector_dialog->priv->sections, section_index);
+}
+
+/* -------------------- *
+ * Addressbook selector *
+ * -------------------- */
+
+static void
+view_progress (EBookClientView *view,
+ guint percent,
+ const gchar *message,
+ ENameSelectorDialog *dialog)
+{
+ if (message == NULL)
+ gtk_label_set_text (dialog->priv->status_label, "");
+ else
+ gtk_label_set_text (dialog->priv->status_label, message);
+}
+
+static void
+view_complete (EBookClientView *view,
+ const GError *error,
+ ENameSelectorDialog *dialog)
+{
+ view_progress (view, -1, NULL, dialog);
+}
+
+static void
+start_client_view_cb (EContactStore *store,
+ EBookClientView *client_view,
+ ENameSelectorDialog *name_selector_dialog)
+{
+ g_signal_connect (
+ client_view, "progress",
+ G_CALLBACK (view_progress), name_selector_dialog);
+
+ g_signal_connect (
+ client_view, "complete",
+ G_CALLBACK (view_complete), name_selector_dialog);
+}
+
+static void
+stop_client_view_cb (EContactStore *store,
+ EBookClientView *client_view,
+ ENameSelectorDialog *name_selector_dialog)
+{
+ g_signal_handlers_disconnect_by_func (client_view, view_progress, name_selector_dialog);
+ g_signal_handlers_disconnect_by_func (client_view, view_complete, name_selector_dialog);
+}
+
+static void
+book_loaded_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ENameSelectorDialog *name_selector_dialog = user_data;
+ EClient *client = NULL;
+ EBookClient *book_client;
+ EContactStore *store;
+ ENameSelectorModel *model;
+ GError *error = NULL;
+
+ e_client_utils_open_new_finish (E_SOURCE (source_object), result, &client, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_warn_if_fail (client == NULL);
+ g_error_free (error);
+ goto exit;
+ }
+
+ if (error != NULL) {
+ gchar *message;
+
+ message = g_strdup_printf (
+ _("Error loading address book: %s"), error->message);
+ gtk_label_set_text (
+ name_selector_dialog->priv->status_label, message);
+ g_free (message);
+
+ g_warn_if_fail (client == NULL);
+ g_error_free (error);
+ goto exit;
+ }
+
+ book_client = E_BOOK_CLIENT (client);
+ if (!book_client) {
+ g_warn_if_fail (book_client != NULL);
+ goto exit;
+ }
+
+ model = name_selector_dialog->priv->name_selector_model;
+ store = e_name_selector_model_peek_contact_store (model);
+ e_contact_store_add_client (store, book_client);
+ g_object_unref (book_client);
+
+ exit:
+ g_object_unref (name_selector_dialog);
+}
+
+static void
+source_changed (ENameSelectorDialog *name_selector_dialog,
+ ESourceComboBox *source_combo_box)
+{
+ GCancellable *cancellable;
+ ESource *source;
+ gpointer parent;
+
+ source = e_source_combo_box_ref_active (source_combo_box);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (name_selector_dialog));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ /* Remove any previous books being shown or loaded */
+ remove_books (name_selector_dialog);
+
+ if (source == NULL)
+ return;
+
+ cancellable = g_cancellable_new ();
+ name_selector_dialog->priv->cancellable = cancellable;
+
+ /* Start loading selected book */
+ e_client_utils_open_new (
+ source, E_CLIENT_SOURCE_TYPE_CONTACTS, TRUE, cancellable,
+ book_loaded_cb, g_object_ref (name_selector_dialog));
+
+ g_object_unref (source);
+}
+
+/* --------------- *
+ * Other UI events *
+ * --------------- */
+
+static void
+search_changed (ENameSelectorDialog *name_selector_dialog)
+{
+ ENameSelectorDialogPrivate *priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (name_selector_dialog);
+ EContactStore *contact_store;
+ EBookQuery *book_query;
+ GtkWidget *combo_box;
+ const gchar *text;
+ gchar *text_escaped;
+ gchar *query_string;
+ gchar *category;
+ gchar *category_escaped;
+ gchar *user_fields_str;
+
+ combo_box = priv->category_combobox;
+ if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) == -1)
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
+
+ category = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box));
+ category_escaped = escape_sexp_string (category);
+
+ text = gtk_entry_get_text (name_selector_dialog->priv->search_entry);
+ text_escaped = escape_sexp_string (text);
+
+ user_fields_str = ens_util_populate_user_query_fields (priv->user_query_fields, text, text_escaped);
+
+ if (g_strcmp0 (category, _("Any Category")) == 0)
+ query_string = g_strdup_printf (
+ "(or (beginswith \"file_as\" %s) "
+ " (beginswith \"full_name\" %s) "
+ " (beginswith \"email\" %s) "
+ " (beginswith \"nickname\" %s)%s))",
+ text_escaped, text_escaped,
+ text_escaped, text_escaped,
+ user_fields_str ? user_fields_str : "");
+ else
+ query_string = g_strdup_printf (
+ "(and (is \"category_list\" %s) "
+ "(or (beginswith \"file_as\" %s) "
+ " (beginswith \"full_name\" %s) "
+ " (beginswith \"email\" %s) "
+ " (beginswith \"nickname\" %s)%s))",
+ category_escaped, text_escaped, text_escaped,
+ text_escaped, text_escaped,
+ user_fields_str ? user_fields_str : "");
+
+ book_query = e_book_query_from_string (query_string);
+
+ contact_store = e_name_selector_model_peek_contact_store (
+ name_selector_dialog->priv->name_selector_model);
+ e_contact_store_set_query (contact_store, book_query);
+ e_book_query_unref (book_query);
+
+ g_free (query_string);
+ g_free (text_escaped);
+ g_free (category_escaped);
+ g_free (category);
+ g_free (user_fields_str);
+}
+
+static void
+contact_selection_changed (ENameSelectorDialog *name_selector_dialog)
+{
+ GtkTreeSelection *contact_selection;
+ gboolean have_selection = FALSE;
+ gint i;
+
+ contact_selection = gtk_tree_view_get_selection (
+ name_selector_dialog->priv->contact_view);
+ if (gtk_tree_selection_count_selected_rows (contact_selection))
+ have_selection = TRUE;
+
+ for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+ Section *section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, i);
+ gtk_widget_set_sensitive (GTK_WIDGET (section->transfer_button), have_selection);
+ }
+}
+
+static void
+contact_activated (ENameSelectorDialog *name_selector_dialog,
+ GtkTreePath *path)
+{
+ EContactStore *contact_store;
+ EDestinationStore *destination_store;
+ EContact *contact;
+ GtkTreeIter iter;
+ Section *section;
+ gint email_n;
+
+ /* When a contact is activated, we transfer it to the first destination on our list */
+
+ contact_store = e_name_selector_model_peek_contact_store (
+ name_selector_dialog->priv->name_selector_model);
+
+ /* If we have no sections, we can't transfer */
+ if (name_selector_dialog->priv->sections->len == 0)
+ return;
+
+ /* Get the contact to be transferred */
+
+ if (!gtk_tree_model_get_iter (
+ GTK_TREE_MODEL (name_selector_dialog->priv->contact_sort),
+ &iter, path))
+ g_assert_not_reached ();
+
+ sort_iter_to_contact_store_iter (name_selector_dialog, &iter, &email_n);
+
+ contact = e_contact_store_get_contact (contact_store, &iter);
+ if (!contact) {
+ g_warning ("ENameSelectorDialog could not get selected contact!");
+ return;
+ }
+
+ section = &g_array_index (
+ name_selector_dialog->priv->sections,
+ Section, name_selector_dialog->priv->destination_index);
+ if (!e_name_selector_model_peek_section (
+ name_selector_dialog->priv->name_selector_model,
+ section->name, NULL, &destination_store)) {
+ g_warning ("ENameSelectorDialog has a section unknown to the model!");
+ return;
+ }
+
+ add_destination (
+ name_selector_dialog->priv->name_selector_model,
+ destination_store, contact, email_n,
+ e_contact_store_get_client (contact_store, &iter));
+}
+
+static void
+destination_activated (ENameSelectorDialog *name_selector_dialog,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GtkTreeView *tree_view)
+{
+ gint section_index;
+ EDestinationStore *destination_store;
+ EDestination *destination;
+ Section *section;
+ GtkTreeIter iter;
+
+ /* When a destination is activated, we remove it from the section */
+
+ section_index = find_section_by_tree_view (
+ name_selector_dialog, tree_view);
+ if (section_index < 0) {
+ g_warning ("ENameSelectorDialog got activation from unknown view!");
+ return;
+ }
+
+ section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, section_index);
+ if (!e_name_selector_model_peek_section (
+ name_selector_dialog->priv->name_selector_model,
+ section->name, NULL, &destination_store)) {
+ g_warning ("ENameSelectorDialog has a section unknown to the model!");
+ return;
+ }
+
+ if (!gtk_tree_model_get_iter (
+ GTK_TREE_MODEL (destination_store), &iter, path))
+ g_assert_not_reached ();
+
+ destination = e_destination_store_get_destination (
+ destination_store, &iter);
+ g_assert (destination);
+
+ e_destination_store_remove_destination (
+ destination_store, destination);
+}
+
+static gboolean
+remove_selection (ENameSelectorDialog *name_selector_dialog,
+ GtkTreeView *tree_view)
+{
+ gint section_index;
+ EDestinationStore *destination_store;
+ EDestination *destination;
+ Section *section;
+ GtkTreeSelection *selection;
+ GList *rows, *l;
+
+ section_index = find_section_by_tree_view (
+ name_selector_dialog, tree_view);
+ if (section_index < 0) {
+ g_warning ("ENameSelectorDialog got key press from unknown view!");
+ return FALSE;
+ }
+
+ section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, section_index);
+ if (!e_name_selector_model_peek_section (
+ name_selector_dialog->priv->name_selector_model,
+ section->name, NULL, &destination_store)) {
+ g_warning ("ENameSelectorDialog has a section unknown to the model!");
+ return FALSE;
+ }
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ if (!gtk_tree_selection_count_selected_rows (selection)) {
+ g_warning ("ENameSelectorDialog remove button clicked, but no selection!");
+ return FALSE;
+ }
+
+ rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+ rows = g_list_reverse (rows);
+
+ for (l = rows; l; l = g_list_next (l)) {
+ GtkTreeIter iter;
+ GtkTreePath *path = l->data;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (destination_store),
+ &iter, path))
+ g_assert_not_reached ();
+
+ gtk_tree_path_free (path);
+
+ destination = e_destination_store_get_destination (
+ destination_store, &iter);
+ g_assert (destination);
+
+ e_destination_store_remove_destination (
+ destination_store, destination);
+ }
+ g_list_free (rows);
+
+ return TRUE;
+}
+
+static void
+remove_button_clicked (GtkButton *button,
+ SelData *data)
+{
+ GtkTreeView *view;
+ ENameSelectorDialog *name_selector_dialog;
+
+ view = data->view;
+ name_selector_dialog = data->dlg_ptr;
+ remove_selection (name_selector_dialog, view);
+}
+
+static gboolean
+destination_key_press (ENameSelectorDialog *name_selector_dialog,
+ GdkEventKey *event,
+ GtkTreeView *tree_view)
+{
+
+ /* we only care about DEL key */
+ if (event->keyval != GDK_KEY_Delete)
+ return FALSE;
+ return remove_selection (name_selector_dialog, tree_view);
+
+}
+
+static void
+transfer_button_clicked (ENameSelectorDialog *name_selector_dialog,
+ GtkButton *transfer_button)
+{
+ EContactStore *contact_store;
+ EDestinationStore *destination_store;
+ GtkTreeSelection *selection;
+ EContact *contact;
+ gint section_index;
+ Section *section;
+ gint email_n;
+ GList *rows, *l;
+
+ /* Get the contact to be transferred */
+
+ contact_store = e_name_selector_model_peek_contact_store (
+ name_selector_dialog->priv->name_selector_model);
+ selection = gtk_tree_view_get_selection (
+ name_selector_dialog->priv->contact_view);
+
+ if (!gtk_tree_selection_count_selected_rows (selection)) {
+ g_warning ("ENameSelectorDialog transfer button clicked, but no selection!");
+ return;
+ }
+
+ /* Get the target section */
+ section_index = find_section_by_transfer_button (
+ name_selector_dialog, transfer_button);
+ if (section_index < 0) {
+ g_warning ("ENameSelectorDialog got click from unknown button!");
+ return;
+ }
+
+ section = &g_array_index (
+ name_selector_dialog->priv->sections, Section, section_index);
+ if (!e_name_selector_model_peek_section (
+ name_selector_dialog->priv->name_selector_model,
+ section->name, NULL, &destination_store)) {
+ g_warning ("ENameSelectorDialog has a section unknown to the model!");
+ return;
+ }
+
+ rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+ rows = g_list_reverse (rows);
+
+ for (l = rows; l; l = g_list_next (l)) {
+ GtkTreeIter iter;
+ GtkTreePath *path = l->data;
+
+ if (!gtk_tree_model_get_iter (
+ GTK_TREE_MODEL (name_selector_dialog->priv->contact_sort),
+ &iter, path)) {
+ gtk_tree_path_free (path);
+ return;
+ }
+
+ gtk_tree_path_free (path);
+ sort_iter_to_contact_store_iter (name_selector_dialog, &iter, &email_n);
+
+ contact = e_contact_store_get_contact (contact_store, &iter);
+ if (!contact) {
+ g_warning ("ENameSelectorDialog could not get selected contact!");
+ g_list_free (rows);
+ return;
+ }
+
+ add_destination (
+ name_selector_dialog->priv->name_selector_model,
+ destination_store, contact, email_n,
+ e_contact_store_get_client (contact_store, &iter));
+ }
+ g_list_free (rows);
+}
+
+/* --------------------- *
+ * Main model management *
+ * --------------------- */
+
+static void
+setup_name_selector_model (ENameSelectorDialog *name_selector_dialog)
+{
+ ETreeModelGenerator *contact_filter;
+ EContactStore *contact_store;
+ GList *new_sections;
+ GList *l;
+
+ /* Create new destination sections in UI */
+
+ new_sections = e_name_selector_model_list_sections (
+ name_selector_dialog->priv->name_selector_model);
+
+ for (l = new_sections; l; l = g_list_next (l)) {
+ gchar *name = l->data;
+ gchar *pretty_name;
+ EDestinationStore *destination_store;
+
+ e_name_selector_model_peek_section (
+ name_selector_dialog->priv->name_selector_model,
+ name, &pretty_name, &destination_store);
+
+ add_section (name_selector_dialog, name, pretty_name, destination_store);
+
+ g_free (pretty_name);
+ g_free (name);
+ }
+
+ g_list_free (new_sections);
+
+ /* Connect to section add/remove signals */
+
+ g_signal_connect_swapped (
+ name_selector_dialog->priv->name_selector_model, "section-added",
+ G_CALLBACK (model_section_added), name_selector_dialog);
+ g_signal_connect_swapped (
+ name_selector_dialog->priv->name_selector_model, "section-removed",
+ G_CALLBACK (model_section_removed), name_selector_dialog);
+
+ /* Get contact store and its filter wrapper */
+
+ contact_filter = e_name_selector_model_peek_contact_filter (
+ name_selector_dialog->priv->name_selector_model);
+
+ /* Create sorting model on top of filter, assign it to view */
+
+ name_selector_dialog->priv->contact_sort = GTK_TREE_MODEL_SORT (
+ gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (contact_filter)));
+
+ /* sort on full name as we display full name in name selector dialog */
+ gtk_tree_sortable_set_sort_column_id (
+ GTK_TREE_SORTABLE (name_selector_dialog->priv->contact_sort),
+ E_CONTACT_FULL_NAME, GTK_SORT_ASCENDING);
+
+ gtk_tree_view_set_model (
+ name_selector_dialog->priv->contact_view,
+ GTK_TREE_MODEL (name_selector_dialog->priv->contact_sort));
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->priv->name_selector_model);
+ if (contact_store) {
+ g_signal_connect (contact_store, "start-client-view", G_CALLBACK (start_client_view_cb), name_selector_dialog);
+ g_signal_connect (contact_store, "stop-client-view", G_CALLBACK (stop_client_view_cb), name_selector_dialog);
+ }
+
+ /* Make sure UI is consistent */
+
+ search_changed (name_selector_dialog);
+ contact_selection_changed (name_selector_dialog);
+}
+
+static void
+shutdown_name_selector_model (ENameSelectorDialog *name_selector_dialog)
+{
+ gint i;
+
+ /* Rid UI of previous destination sections */
+
+ for (i = 0; i < name_selector_dialog->priv->sections->len; i++)
+ free_section (name_selector_dialog, i);
+
+ g_array_set_size (name_selector_dialog->priv->sections, 0);
+
+ /* Free sorting model */
+
+ if (name_selector_dialog->priv->contact_sort) {
+ g_object_unref (name_selector_dialog->priv->contact_sort);
+ name_selector_dialog->priv->contact_sort = NULL;
+ }
+
+ /* Free backend model */
+
+ if (name_selector_dialog->priv->name_selector_model) {
+ EContactStore *contact_store;
+
+ contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->priv->name_selector_model);
+ if (contact_store) {
+ g_signal_handlers_disconnect_by_func (contact_store, start_client_view_cb, name_selector_dialog);
+ g_signal_handlers_disconnect_by_func (contact_store, stop_client_view_cb, name_selector_dialog);
+ }
+
+ g_signal_handlers_disconnect_matched (
+ name_selector_dialog->priv->name_selector_model,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_dialog);
+
+ g_object_unref (name_selector_dialog->priv->name_selector_model);
+ name_selector_dialog->priv->name_selector_model = NULL;
+ }
+}
+
+static void
+contact_column_formatter (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ ENameSelectorDialog *name_selector_dialog)
+{
+ EContactStore *contact_store;
+ EContact *contact;
+ GtkTreeIter contact_store_iter;
+ GList *email_list;
+ gchar *string;
+ gchar *full_name_str;
+ gchar *email_str;
+ gint email_n;
+
+ contact_store_iter = *iter;
+ sort_iter_to_contact_store_iter (
+ name_selector_dialog, &contact_store_iter, &email_n);
+
+ contact_store = e_name_selector_model_peek_contact_store (
+ name_selector_dialog->priv->name_selector_model);
+ contact = e_contact_store_get_contact (
+ contact_store, &contact_store_iter);
+ email_list = e_name_selector_model_get_contact_emails_without_used (
+ name_selector_dialog->priv->name_selector_model, contact, TRUE);
+ email_str = g_list_nth_data (email_list, email_n);
+ full_name_str = e_contact_get (contact, E_CONTACT_FULL_NAME);
+
+ if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
+ if (!full_name_str)
+ full_name_str = e_contact_get (contact, E_CONTACT_FILE_AS);
+ string = g_strdup_printf ("%s", full_name_str ? full_name_str : "?");
+ } else {
+ string = g_strdup_printf (
+ "%s%s<%s>", full_name_str ? full_name_str : "",
+ full_name_str ? " " : "",
+ email_str ? email_str : "");
+ }
+
+ g_free (full_name_str);
+ e_name_selector_model_free_emails_list (email_list);
+
+ g_object_set (cell, "text", string, NULL);
+ g_free (string);
+}
+
+static void
+destination_column_formatter (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ ENameSelectorDialog *name_selector_dialog)
+{
+ EDestinationStore *destination_store = E_DESTINATION_STORE (model);
+ EDestination *destination;
+ GString *buffer;
+
+ destination = e_destination_store_get_destination (destination_store, iter);
+ g_assert (destination);
+
+ buffer = g_string_new (e_destination_get_name (destination));
+
+ if (!e_destination_is_evolution_list (destination)) {
+ const gchar *email;
+
+ email = e_destination_get_email (destination);
+ if (email == NULL || *email == '\0')
+ email = "?";
+ g_string_append_printf (buffer, " <%s>", email);
+ }
+
+ g_object_set (cell, "text", buffer->str, NULL);
+ g_string_free (buffer, TRUE);
+}
+
+/* ----------------------- *
+ * ENameSelectorDialog API *
+ * ----------------------- */
+
+/**
+ * e_name_selector_dialog_peek_model:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ *
+ * Gets the #ENameSelectorModel used by @name_selector_model.
+ *
+ * Returns: The #ENameSelectorModel being used.
+ **/
+ENameSelectorModel *
+e_name_selector_dialog_peek_model (ENameSelectorDialog *name_selector_dialog)
+{
+ g_return_val_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog), NULL);
+
+ return name_selector_dialog->priv->name_selector_model;
+}
+
+/**
+ * e_name_selector_dialog_set_model:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @model: an #ENameSelectorModel
+ *
+ * Sets the model being used by @name_selector_dialog to @model.
+ **/
+void
+e_name_selector_dialog_set_model (ENameSelectorDialog *name_selector_dialog,
+ ENameSelectorModel *model)
+{
+ g_return_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog));
+ g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (model));
+
+ if (model == name_selector_dialog->priv->name_selector_model)
+ return;
+
+ shutdown_name_selector_model (name_selector_dialog);
+ name_selector_dialog->priv->name_selector_model = g_object_ref (model);
+
+ setup_name_selector_model (name_selector_dialog);
+}
+
+/**
+ * e_name_selector_dialog_set_destination_index:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @index: index of the destination section, starting from 0.
+ *
+ * Sets the index number of the destination section.
+ **/
+void
+e_name_selector_dialog_set_destination_index (ENameSelectorDialog *name_selector_dialog,
+ guint index)
+{
+ g_return_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog));
+
+ if (index >= name_selector_dialog->priv->sections->len)
+ return;
+
+ name_selector_dialog->priv->destination_index = index;
+}
+
+/**
+ * e_name_selector_dialog_set_scrolling_policy:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @hscrollbar_policy: scrolling policy for horizontal bar of the contacts window.
+ * @vscrollbar_policy: scrolling policy for vertical bar of the contacts window.
+ *
+ * Sets the scrolling policy for the contacts section.
+ *
+ * Since: 3.2
+ **/
+void
+e_name_selector_dialog_set_scrolling_policy (ENameSelectorDialog *name_selector_dialog,
+ GtkPolicyType hscrollbar_policy,
+ GtkPolicyType vscrollbar_policy)
+{
+ GtkScrolledWindow *win = GTK_SCROLLED_WINDOW (name_selector_dialog->priv->contact_window);
+
+ gtk_scrolled_window_set_policy (win, hscrollbar_policy, vscrollbar_policy);
+}
+
+/**
+ * e_name_selector_dialog_get_section_visible:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @name: name of the section
+ *
+ * Returns: whether section named @name is visible in the dialog.
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_name_selector_dialog_get_section_visible (ENameSelectorDialog *name_selector_dialog,
+ const gchar *name)
+{
+ Section *section;
+ gint index;
+
+ g_return_val_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ index = find_section_by_name (name_selector_dialog, name);
+ g_return_val_if_fail (index != -1, FALSE);
+
+ section = &g_array_index (name_selector_dialog->priv->sections, Section, index);
+ return gtk_widget_get_visible (GTK_WIDGET (section->section_grid));
+}
+
+/**
+ * e_name_selector_dialog_set_section_visible:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @name: name of the section
+ * @visible: whether to show or hide the section
+ *
+ * Shows or hides section named @name in the dialog.
+ *
+ * Since: 3.8
+ **/
+void
+e_name_selector_dialog_set_section_visible (ENameSelectorDialog *name_selector_dialog,
+ const gchar *name,
+ gboolean visible)
+{
+ Section *section;
+ gint index;
+
+ g_return_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog));
+ g_return_if_fail (name != NULL);
+
+ index = find_section_by_name (name_selector_dialog, name);
+ g_return_if_fail (index != -1);
+
+ section = &g_array_index (name_selector_dialog->priv->sections, Section, index);
+
+ if (visible)
+ gtk_widget_show (GTK_WIDGET (section->section_grid));
+ else
+ gtk_widget_hide (GTK_WIDGET (section->section_grid));
+}
+