aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-name-selector.c
blob: 9f2f48cea8877f8ad715edbd7515a97991ce9e10 (plain) (tree)

































                                                                                 

















                                                            
                                   











                                    
                         

























                                                                       


                                                    

                                                 
                                 
                        




                               

                                                                




                                                        





                                                                                                   
                                                                        












































                                                                            
                                   






                                                              


                                                                        
                                                                         








                                                                            




                                                                        







                                                                



                                                                 


                                      




















                                                                         

                                                             
 

                                                                     
 
                                                                        








                                                

                                                        














                                                                       

                                             
                                      
                                                                  











































                                                                       









                                                               






























                                                                         




                                              

                                         
                                  
                                     



                                                            























                                                                          
                                  





                                 
                                                
 
                                                                      


                                     
                                                    


   
                                    

                                    
                                                             
  

                                                                         
  


                            
    

                                                               


                                                                        
                                                                













































































                                                                        
                                           


                                            

                                                                                
                                                     
                                              












































































                                                                                  
                                           
                                             
                                  


                                    




                                                                                
































































                                                                                                            

                                           


                                    




                                                                                























                                                                                                            
/* -*- 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 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 Lesser 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Authors: Hans Petter Jansson <hpj@novell.com>
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <string.h>
#include <gtk/gtk.h>
#include <glib/gi18n-lib.h>

#include <libebook/libebook.h>

#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;
    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)));

    if (error != NULL) {
        if (!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, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED)
            && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
            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,
            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_assert (name != NULL);

    memset (&section, 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_assert (name != NULL);

    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;
}