/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* e-name-selector.c - Unified context for contact/destination selection UI. * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This program is free software; you can redistribute it and/or modify it * under the terms 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 program; if not, see . * * Authors: Hans Petter Jansson */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include "e-name-selector.h" #include "e-contact-store.h" #include "e-destination-store.h" #define E_NAME_SELECTOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_NAME_SELECTOR, ENameSelectorPrivate)) typedef struct { gchar *name; ENameSelectorEntry *entry; } Section; typedef struct { EBookClient *client; guint is_completion_book : 1; } SourceBook; struct _ENameSelectorPrivate { EClientCache *client_cache; ENameSelectorModel *model; ENameSelectorDialog *dialog; GArray *sections; gboolean books_loaded; GCancellable *cancellable; GArray *source_books; }; enum { PROP_0, PROP_CLIENT_CACHE }; G_DEFINE_TYPE (ENameSelector, e_name_selector, G_TYPE_OBJECT) static void reset_pointer_cb (gpointer data, GObject *where_was) { ENameSelector *name_selector = data; ENameSelectorPrivate *priv; guint ii; g_return_if_fail (E_IS_NAME_SELECTOR (name_selector)); priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector); for (ii = 0; ii < priv->sections->len; ii++) { Section *section; section = &g_array_index (priv->sections, Section, ii); if (section->entry == (ENameSelectorEntry *) where_was) section->entry = NULL; } } static void name_selector_get_client_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { ENameSelector *name_selector = user_data; EBookClient *book_client; EClient *client; GArray *sections; SourceBook source_book; guint ii; gboolean ignore_error; GError *error = NULL; client = e_client_cache_get_client_finish ( E_CLIENT_CACHE (source_object), result, &error); /* Sanity check. */ g_return_if_fail ( ((client != NULL) && (error == NULL)) || ((client == NULL) && (error != NULL))); ignore_error = g_error_matches ( error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE) || g_error_matches ( error, E_CLIENT_ERROR, E_CLIENT_ERROR_OFFLINE_UNAVAILABLE) || g_error_matches ( error, G_IO_ERROR, G_IO_ERROR_CANCELLED); if (error != NULL) { if (!ignore_error) g_warning ("%s: %s", G_STRFUNC, error->message); g_error_free (error); goto exit; } book_client = E_BOOK_CLIENT (client); g_return_if_fail (E_IS_BOOK_CLIENT (book_client)); source_book.client = book_client; source_book.is_completion_book = TRUE; g_array_append_val (name_selector->priv->source_books, source_book); sections = name_selector->priv->sections; for (ii = 0; ii < sections->len; ii++) { EContactStore *store; Section *section; section = &g_array_index (sections, Section, ii); if (section->entry == NULL) continue; store = e_name_selector_entry_peek_contact_store ( section->entry); if (store != NULL) e_contact_store_add_client (store, book_client); } exit: g_object_unref (name_selector); } /** * e_name_selector_load_books: * @name_selector: an #ENameSelector * * Loads address books available for the @name_selector. * This can be called only once and it can be cancelled * by e_name_selector_cancel_loading(). * * Since: 3.2 **/ void e_name_selector_load_books (ENameSelector *name_selector) { EClientCache *client_cache; ESourceRegistry *registry; GList *list, *iter; const gchar *extension_name; g_return_if_fail (E_IS_NAME_SELECTOR (name_selector)); extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK; client_cache = e_name_selector_ref_client_cache (name_selector); registry = e_client_cache_ref_registry (client_cache); list = e_source_registry_list_enabled (registry, extension_name); for (iter = list; iter != NULL; iter = g_list_next (iter)) { ESource *source = E_SOURCE (iter->data); ESourceAutocomplete *extension; const gchar *extension_name; extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE; extension = e_source_get_extension (source, extension_name); /* Only load address books with autocomplete enabled, * so as to avoid unnecessary authentication prompts. */ if (!e_source_autocomplete_get_include_me (extension)) continue; /* FIXME GCancellable is only to be used for one * operation at a time, not for multiple * concurrent operations like this. */ e_client_cache_get_client ( client_cache, source, E_SOURCE_EXTENSION_ADDRESS_BOOK, (guint32) -1, name_selector->priv->cancellable, name_selector_get_client_cb, g_object_ref (name_selector)); } g_list_free_full (list, (GDestroyNotify) g_object_unref); g_object_unref (registry); g_object_unref (client_cache); } /** * e_name_selector_cancel_loading: * @name_selector: an #ENameSelector * * Cancels any pending address book load operations. This might be called * before an owner unrefs this @name_selector. * * Since: 3.2 **/ void e_name_selector_cancel_loading (ENameSelector *name_selector) { g_return_if_fail (E_IS_NAME_SELECTOR (name_selector)); g_return_if_fail (name_selector->priv->cancellable != NULL); g_cancellable_cancel (name_selector->priv->cancellable); } static void name_selector_set_client_cache (ENameSelector *name_selector, EClientCache *client_cache) { g_return_if_fail (E_IS_CLIENT_CACHE (client_cache)); g_return_if_fail (name_selector->priv->client_cache == NULL); name_selector->priv->client_cache = g_object_ref (client_cache); } static void name_selector_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CLIENT_CACHE: name_selector_set_client_cache ( E_NAME_SELECTOR (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void name_selector_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CLIENT_CACHE: g_value_take_object ( value, e_name_selector_ref_client_cache ( E_NAME_SELECTOR (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void name_selector_dispose (GObject *object) { ENameSelectorPrivate *priv; guint ii; priv = E_NAME_SELECTOR_GET_PRIVATE (object); if (priv->cancellable) { g_cancellable_cancel (priv->cancellable); g_object_unref (priv->cancellable); priv->cancellable = NULL; } for (ii = 0; ii < priv->source_books->len; ii++) { SourceBook *source_book; source_book = &g_array_index ( priv->source_books, SourceBook, ii); if (source_book->client != NULL) g_object_unref (source_book->client); } for (ii = 0; ii < priv->sections->len; ii++) { Section *section; section = &g_array_index (priv->sections, Section, ii); if (section->entry) g_object_weak_unref ( G_OBJECT (section->entry), reset_pointer_cb, object); g_free (section->name); } g_array_set_size (priv->source_books, 0); g_array_set_size (priv->sections, 0); if (priv->dialog) { gtk_widget_destroy (GTK_WIDGET (priv->dialog)); priv->dialog = NULL; } if (priv->model) { g_object_unref (priv->model); priv->model = NULL; } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_name_selector_parent_class)->dispose (object); } static void name_selector_finalize (GObject *object) { ENameSelectorPrivate *priv; priv = E_NAME_SELECTOR_GET_PRIVATE (object); g_array_free (priv->source_books, TRUE); g_array_free (priv->sections, TRUE); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_name_selector_parent_class)->finalize (object); } static void e_name_selector_class_init (ENameSelectorClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (ENameSelectorPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = name_selector_set_property; object_class->get_property = name_selector_get_property; object_class->dispose = name_selector_dispose; object_class->finalize = name_selector_finalize; /** * ENameSelector:client-cache: * * Cache of shared #EClient instances. **/ g_object_class_install_property ( object_class, PROP_CLIENT_CACHE, g_param_spec_object ( "client-cache", "Client Cache", "Cache of shared EClient instances", E_TYPE_CLIENT_CACHE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } static void e_name_selector_init (ENameSelector *name_selector) { GArray *sections; GArray *source_books; sections = g_array_new (FALSE, FALSE, sizeof (Section)); source_books = g_array_new (FALSE, FALSE, sizeof (SourceBook)); name_selector->priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector); name_selector->priv->sections = sections; name_selector->priv->model = e_name_selector_model_new (); name_selector->priv->source_books = source_books; name_selector->priv->cancellable = g_cancellable_new (); name_selector->priv->books_loaded = FALSE; } /** * e_name_selector_new: * @client_cache: an #EClientCache * * Creates a new #ENameSelector. * * Returns: A new #ENameSelector. **/ ENameSelector * e_name_selector_new (EClientCache *client_cache) { g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL); return g_object_new ( E_TYPE_NAME_SELECTOR, "client-cache", client_cache, NULL); } /** * e_name_selector_ref_client_cache: * @name_selector: an #ENameSelector * * Returns the #EClientCache passed to e_name_selector_new(). * * The returned #EClientCache is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: an #EClientCache * * Since: 3.8 **/ EClientCache * e_name_selector_ref_client_cache (ENameSelector *name_selector) { g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL); return g_object_ref (name_selector->priv->client_cache); } /* ------- * * Helpers * * ------- */ static gint add_section (ENameSelector *name_selector, const gchar *name) { GArray *array; Section section; g_return_val_if_fail (name != NULL, -1); memset (§ion, 0, sizeof (Section)); section.name = g_strdup (name); array = name_selector->priv->sections; g_array_append_val (array, section); return array->len - 1; } static gint find_section_by_name (ENameSelector *name_selector, const gchar *name) { GArray *array; gint i; g_return_val_if_fail (name != NULL, -1); array = name_selector->priv->sections; for (i = 0; i < array->len; i++) { Section *section = &g_array_index (array, Section, i); if (!strcmp (name, section->name)) return i; } return -1; } /* ----------------- * * ENameSelector API * * ----------------- */ /** * e_name_selector_peek_model: * @name_selector: an #ENameSelector * * Gets the #ENameSelectorModel used by @name_selector. * * Returns: The #ENameSelectorModel used by @name_selector. **/ ENameSelectorModel * e_name_selector_peek_model (ENameSelector *name_selector) { g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL); return name_selector->priv->model; } /** * e_name_selector_peek_dialog: * @name_selector: an #ENameSelector * * Gets the #ENameSelectorDialog used by @name_selector. * * Returns: The #ENameSelectorDialog used by @name_selector. **/ ENameSelectorDialog * e_name_selector_peek_dialog (ENameSelector *name_selector) { g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL); if (name_selector->priv->dialog == NULL) { EClientCache *client_cache; ENameSelectorDialog *dialog; ENameSelectorModel *model; client_cache = e_name_selector_ref_client_cache (name_selector); dialog = e_name_selector_dialog_new (client_cache); name_selector->priv->dialog = dialog; g_object_unref (client_cache); model = e_name_selector_peek_model (name_selector); e_name_selector_dialog_set_model (dialog, model); g_signal_connect ( dialog, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), name_selector); } return name_selector->priv->dialog; } /** * e_name_selector_show_dialog: * @name_selector: an #ENameSelector * @for_transient_widget: a widget parent or %NULL * * Shows the associated dialog, and sets the transient parent to the * GtkWindow top-level of "for_transient_widget if set (it should be) * * Since: 2.32 **/ void e_name_selector_show_dialog (ENameSelector *name_selector, GtkWidget *for_transient_widget) { GtkWindow *top = NULL; ENameSelectorDialog *dialog; g_return_if_fail (E_IS_NAME_SELECTOR (name_selector)); dialog = e_name_selector_peek_dialog (name_selector); if (for_transient_widget) top = GTK_WINDOW (gtk_widget_get_toplevel (for_transient_widget)); if (top) gtk_window_set_transient_for (GTK_WINDOW (dialog), top); gtk_widget_show (GTK_WIDGET (dialog)); } /** * e_name_selector_peek_section_entry: * @name_selector: an #ENameSelector * @name: the name of the section to peek * * Gets the #ENameSelectorEntry for the section specified by @name. * * Returns: The #ENameSelectorEntry for the named section, or %NULL if it * doesn't exist in the #ENameSelectorModel. **/ ENameSelectorEntry * e_name_selector_peek_section_entry (ENameSelector *name_selector, const gchar *name) { ENameSelectorPrivate *priv; ENameSelectorModel *model; EDestinationStore *destination_store; Section *section; gint n; g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL); g_return_val_if_fail (name != NULL, NULL); priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector); model = e_name_selector_peek_model (name_selector); if (!e_name_selector_model_peek_section ( model, name, NULL, &destination_store)) return NULL; n = find_section_by_name (name_selector, name); if (n < 0) n = add_section (name_selector, name); section = &g_array_index (name_selector->priv->sections, Section, n); if (!section->entry) { EClientCache *client_cache; EContactStore *contact_store; GtkWidget *widget; gchar *text; gint i; client_cache = e_name_selector_ref_client_cache (name_selector); widget = e_name_selector_entry_new (client_cache); section->entry = E_NAME_SELECTOR_ENTRY (widget); g_object_unref (client_cache); g_object_weak_ref (G_OBJECT (section->entry), reset_pointer_cb, name_selector); if (pango_parse_markup (name, -1, '_', NULL, &text, NULL, NULL)) { atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (section->entry)), text); g_free (text); } e_name_selector_entry_set_destination_store (section->entry, destination_store); /* Create a contact store for the entry and assign our already-open books to it */ contact_store = e_contact_store_new (); for (i = 0; i < priv->source_books->len; i++) { SourceBook *source_book = &g_array_index (priv->source_books, SourceBook, i); if (source_book->is_completion_book && source_book->client) e_contact_store_add_client (contact_store, source_book->client); } e_name_selector_entry_set_contact_store (section->entry, contact_store); g_object_unref (contact_store); } return section->entry; } /** * e_name_selector_peek_section_list: * @name_selector: an #ENameSelector * @name: the name of the section to peek * * Gets the #ENameSelectorList for the section specified by @name. * * Returns: The #ENameSelectorList for the named section, or %NULL if it * doesn't exist in the #ENameSelectorModel. **/ ENameSelectorList * e_name_selector_peek_section_list (ENameSelector *name_selector, const gchar *name) { ENameSelectorPrivate *priv; ENameSelectorModel *model; EDestinationStore *destination_store; Section *section; gint n; g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL); g_return_val_if_fail (name != NULL, NULL); priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector); model = e_name_selector_peek_model (name_selector); if (!e_name_selector_model_peek_section ( model, name, NULL, &destination_store)) return NULL; n = find_section_by_name (name_selector, name); if (n < 0) n = add_section (name_selector, name); section = &g_array_index (name_selector->priv->sections, Section, n); if (!section->entry) { EContactStore *contact_store; EClientCache *client_cache; GtkWidget *widget; gchar *text; gint i; client_cache = e_name_selector_ref_client_cache (name_selector); widget = e_name_selector_list_new (client_cache); section->entry = E_NAME_SELECTOR_ENTRY (widget); g_object_unref (client_cache); g_object_weak_ref (G_OBJECT (section->entry), reset_pointer_cb, name_selector); if (pango_parse_markup (name, -1, '_', NULL, &text, NULL, NULL)) { atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (section->entry)), text); g_free (text); } e_name_selector_entry_set_destination_store (section->entry, destination_store); /* Create a contact store for the entry and assign our already-open books to it */ contact_store = e_contact_store_new (); for (i = 0; i < priv->source_books->len; i++) { SourceBook *source_book = &g_array_index (priv->source_books, SourceBook, i); if (source_book->is_completion_book && source_book->client) e_contact_store_add_client (contact_store, source_book->client); } e_name_selector_entry_set_contact_store (section->entry, contact_store); g_object_unref (contact_store); } return (ENameSelectorList *) section->entry; }