/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* e-name-selector-model.c - Model for contact selection.
*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Hans Petter Jansson <hpj@novell.com>
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <glib/gi18n-lib.h>
#include "e-name-selector-model.h"
#define E_NAME_SELECTOR_MODEL_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_NAME_SELECTOR_MODEL, ENameSelectorModelPrivate))
typedef struct {
gchar *name;
gchar *pretty_name;
EDestinationStore *destination_store;
}
Section;
struct _ENameSelectorModelPrivate {
GArray *sections;
EContactStore *contact_store;
ETreeModelGenerator *contact_filter;
GHashTable *destination_uid_hash;
};
static gint generate_contact_rows (EContactStore *contact_store, GtkTreeIter *iter,
ENameSelectorModel *name_selector_model);
static void override_email_address (EContactStore *contact_store, GtkTreeIter *iter,
gint permutation_n, gint column, GValue *value,
ENameSelectorModel *name_selector_model);
static void free_section (ENameSelectorModel *name_selector_model, gint n);
/* ------------------ *
* Class/object setup *
* ------------------ */
/* Signals */
enum {
SECTION_ADDED,
SECTION_REMOVED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (ENameSelectorModel, e_name_selector_model, G_TYPE_OBJECT)
static void
e_name_selector_model_init (ENameSelectorModel *name_selector_model)
{
name_selector_model->priv =
E_NAME_SELECTOR_MODEL_GET_PRIVATE (name_selector_model);
name_selector_model->priv->sections = g_array_new (FALSE, FALSE, sizeof (Section));
name_selector_model->priv->contact_store = e_contact_store_new ();
name_selector_model->priv->contact_filter =
e_tree_model_generator_new (GTK_TREE_MODEL (name_selector_model->priv->contact_store));
e_tree_model_generator_set_generate_func (
name_selector_model->priv->contact_filter,
(ETreeModelGeneratorGenerateFunc) generate_contact_rows,
name_selector_model, NULL);
e_tree_model_generator_set_modify_func (name_selector_model->priv->contact_filter,
(ETreeModelGeneratorModifyFunc) override_email_address,
name_selector_model, NULL);
g_object_unref (name_selector_model->priv->contact_store);
name_selector_model->priv->destination_uid_hash = NULL;
}
static void
name_selector_model_finalize (GObject *object)
{
ENameSelectorModelPrivate *priv;
gint i;
priv = E_NAME_SELECTOR_MODEL_GET_PRIVATE (object);
for (i = 0; i < priv->sections->len; i++)
free_section (E_NAME_SELECTOR_MODEL (object), i);
g_array_free (priv->sections, TRUE);
g_object_unref (priv->contact_filter);
if (priv->destination_uid_hash)
g_hash_table_destroy (priv->destination_uid_hash);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_name_selector_model_parent_class)->finalize (object);
}
static void
e_name_selector_model_class_init (ENameSelectorModelClass *class)
{
GObjectClass *object_class;
g_type_class_add_private (class, sizeof (ENameSelectorModelPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->finalize = name_selector_model_finalize;
signals[SECTION_ADDED] = g_signal_new (
"section-added",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ENameSelectorModelClass, section_added),
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
signals[SECTION_REMOVED] = g_signal_new (
"section-removed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ENameSelectorModelClass, section_removed),
NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
}
/**
* e_name_selector_model_new:
*
* Creates a new #ENameSelectorModel.
*
* Returns: A new #ENameSelectorModel.
**/
ENameSelectorModel *
e_name_selector_model_new (void)
{
return E_NAME_SELECTOR_MODEL (g_object_new (E_TYPE_NAME_SELECTOR_MODEL, NULL));
}
/* ---------------------------- *
* GtkTreeModelFilter filtering *
* ---------------------------- */
static void
deep_free_list (GList *list)
{
GList *l;
for (l = list; l; l = g_list_next (l))
g_free (l->data);
g_list_free (list);
}
static gint
generate_contact_rows (EContactStore *contact_store,
GtkTreeIter *iter,
ENameSelectorModel *name_selector_model)
{
EContact *contact;
const gchar *contact_uid;
gint n_rows, used_rows = 0;
gint i;
contact = e_contact_store_get_contact (contact_store, iter);
g_assert (contact != NULL);
contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
if (!contact_uid)
return 0; /* Can happen with broken databases */
for (i = 0; i < name_selector_model->priv->sections->len; i++) {
Section *section;
GList *destinations;
GList *l;
section = &g_array_index (name_selector_model->priv->sections, Section, i);
destinations = e_destination_store_list_destinations (section->destination_store);
for (l = destinations; l; l = g_list_next (l)) {
EDestination *destination = l->data;
const gchar *destination_uid;
destination_uid = e_destination_get_contact_uid (destination);
if (destination_uid && !strcmp (contact_uid, destination_uid)) {
used_rows++;
}
}
g_list_free (destinations);
}
if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
n_rows = 1 - used_rows;
} else {
GList *email_list;
email_list = e_contact_get (contact, E_CONTACT_EMAIL);
n_rows = g_list_length (email_list) - used_rows;
deep_free_list (email_list);
}
g_return_val_if_fail (n_rows >= 0, 0);
return n_rows;
}
static void
override_email_address (EContactStore *contact_store,
GtkTreeIter *iter,
gint permutation_n,
gint column,
GValue *value,
ENameSelectorModel *name_selector_model)
{
if (column == E_CONTACT_EMAIL_1) {
EContact *contact;
GList *email_list;
gchar *email;
contact = e_contact_store_get_contact (contact_store, iter);
email_list = e_name_selector_model_get_contact_emails_without_used (name_selector_model, contact, TRUE);
g_return_if_fail (g_list_length (email_list) <= permutation_n);
email = g_strdup (g_list_nth_data (email_list, permutation_n));
g_value_set_string (value, email);
e_name_selector_model_free_emails_list (email_list);
} else {
gtk_tree_model_get_value (GTK_TREE_MODEL (contact_store), iter, column, value);
}
}
/* --------------- *
* Section helpers *
* --------------- */
typedef struct
{
ENameSelectorModel *name_selector_model;
GHashTable *other_hash;
}
HashCompare;
static void
emit_destination_uid_changes_cb (gchar *uid_num,
gpointer value,
HashCompare *hash_compare)
{
EContactStore *contact_store = hash_compare->name_selector_model->priv->contact_store;
if (!hash_compare->other_hash || !g_hash_table_lookup (hash_compare->other_hash, uid_num)) {
GtkTreeIter iter;
GtkTreePath *path;
gchar *sep;
sep = strrchr (uid_num, ':');
g_return_if_fail (sep != NULL);
*sep = '\0';
if (e_contact_store_find_contact (contact_store, uid_num, &iter)) {
*sep = ':';
path = gtk_tree_model_get_path (GTK_TREE_MODEL (contact_store), &iter);
gtk_tree_model_row_changed (GTK_TREE_MODEL (contact_store), path, &iter);
gtk_tree_path_free (path);
} else {
*sep = ':';
}
}
}
static void
destinations_changed (ENameSelectorModel *name_selector_model)
{
GHashTable *destination_uid_hash_new;
GHashTable *destination_uid_hash_old;
HashCompare hash_compare;
gint i;
destination_uid_hash_new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
for (i = 0; i < name_selector_model->priv->sections->len; i++) {
Section *section = &g_array_index (name_selector_model->priv->sections, Section, i);
GList *destinations;
GList *l;
destinations = e_destination_store_list_destinations (section->destination_store);
for (l = destinations; l; l = g_list_next (l)) {
EDestination *destination = l->data;
const gchar *destination_uid;
destination_uid = e_destination_get_contact_uid (destination);
if (destination_uid)
g_hash_table_insert (
destination_uid_hash_new,
g_strdup_printf (
"%s:%d", destination_uid,
e_destination_get_email_num (destination)),
GINT_TO_POINTER (TRUE));
}
g_list_free (destinations);
}
destination_uid_hash_old = name_selector_model->priv->destination_uid_hash;
name_selector_model->priv->destination_uid_hash = destination_uid_hash_new;
hash_compare.name_selector_model = name_selector_model;
hash_compare.other_hash = destination_uid_hash_old;
g_hash_table_foreach (
destination_uid_hash_new,
(GHFunc) emit_destination_uid_changes_cb,
&hash_compare);
if (destination_uid_hash_old) {
hash_compare.other_hash = destination_uid_hash_new;
g_hash_table_foreach (
destination_uid_hash_old,
(GHFunc) emit_destination_uid_changes_cb,
&hash_compare);
g_hash_table_destroy (destination_uid_hash_old);
}
}
static void
free_section (ENameSelectorModel *name_selector_model,
gint n)
{
Section *section;
g_assert (n >= 0);
g_assert (n < name_selector_model->priv->sections->len);
section = &g_array_index (name_selector_model->priv->sections, Section, n);
g_signal_handlers_disconnect_matched (
section->destination_store, G_SIGNAL_MATCH_DATA,
0, 0, NULL, NULL, name_selector_model);
g_free (section->name);
g_free (section->pretty_name);
g_object_unref (section->destination_store);
}
static gint
find_section_by_name (ENameSelectorModel *name_selector_model,
const gchar *name)
{
gint i;
g_assert (name != NULL);
for (i = 0; i < name_selector_model->priv->sections->len; i++) {
Section *section = &g_array_index (name_selector_model->priv->sections, Section, i);
if (!strcmp (name, section->name))
return i;
}
return -1;
}
/* ---------------------- *
* ENameSelectorModel API *
* ---------------------- */
/**
* e_name_selector_model_peek_contact_store:
* @name_selector_model: an #ENameSelectorModel
*
* Gets the #EContactStore associated with @name_selector_model.
*
* Returns: An #EContactStore.
**/
EContactStore *
e_name_selector_model_peek_contact_store (ENameSelectorModel *name_selector_model)
{
g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
return name_selector_model->priv->contact_store;
}
/**
* e_name_selector_model_peek_contact_filter:
* @name_selector_model: an #ENameSelectorModel
*
* Gets the #ETreeModelGenerator being used to filter and/or extend the
* list of contacts in @name_selector_model's #EContactStore.
*
* Returns: An #ETreeModelGenerator.
**/
ETreeModelGenerator *
e_name_selector_model_peek_contact_filter (ENameSelectorModel *name_selector_model)
{
g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
return name_selector_model->priv->contact_filter;
}
/**
* e_name_selector_model_list_sections:
* @name_selector_model: an #ENameSelectorModel
*
* Gets a list of the destination sections in @name_selector_model.
*
* Returns: A #GList of pointers to strings. The #GList and the
* strings belong to the caller, and must be freed when no longer needed.
**/
GList *
e_name_selector_model_list_sections (ENameSelectorModel *name_selector_model)
{
GList *section_names = NULL;
gint i;
g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
/* Do this backwards so we can use g_list_prepend () and get correct order */
for (i = name_selector_model->priv->sections->len - 1; i >= 0; i--) {
Section *section = &g_array_index (name_selector_model->priv->sections, Section, i);
gchar *name;
name = g_strdup (section->name);
section_names = g_list_prepend (section_names, name);
}
return section_names;
}
/**
* e_name_selector_model_add_section:
* @name_selector_model: an #ENameSelectorModel
* @name: internal name of this section
* @pretty_name: user-visible name of this section
* @destination_store: the #EDestinationStore to use to store the destinations for this
* section, or %NULL if @name_selector_model should create its own.
*
* Adds a destination section to @name_selector_model.
**/
void
e_name_selector_model_add_section (ENameSelectorModel *name_selector_model,
const gchar *name,
const gchar *pretty_name,
EDestinationStore *destination_store)
{
Section section;
g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model));
g_return_if_fail (name != NULL);
g_return_if_fail (pretty_name != NULL);
if (find_section_by_name (name_selector_model, name) >= 0) {
g_warning ("ENameSelectorModel already has a section called '%s'!", name);
return;
}
memset (§ion, 0, sizeof (Section));
section.name = g_strdup (name);
section.pretty_name = g_strdup (pretty_name);
if (destination_store)
section.destination_store = g_object_ref (destination_store);
else
section.destination_store = e_destination_store_new ();
g_signal_connect_swapped (
section.destination_store, "row-changed",
G_CALLBACK (destinations_changed), name_selector_model);
g_signal_connect_swapped (
section.destination_store, "row-deleted",
G_CALLBACK (destinations_changed), name_selector_model);
g_signal_connect_swapped (
section.destination_store, "row-inserted",
G_CALLBACK (destinations_changed), name_selector_model);
g_array_append_val (name_selector_model->priv->sections, section);
destinations_changed (name_selector_model);
g_signal_emit (name_selector_model, signals[SECTION_ADDED], 0, name);
}
/**
* e_name_selector_model_remove_section:
* @name_selector_model: an #ENameSelectorModel
* @name: internal name of the section to remove
*
* Removes a destination section from @name_selector_model.
**/
void
e_name_selector_model_remove_section (ENameSelectorModel *name_selector_model,
const gchar *name)
{
gint n;
g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model));
g_return_if_fail (name != NULL);
n = find_section_by_name (name_selector_model, name);
if (n < 0) {
g_warning ("ENameSelectorModel does not have a section called '%s'!", name);
return;
}
free_section (name_selector_model, n);
g_array_remove_index_fast (name_selector_model->priv->sections, n); /* Order doesn't matter */
destinations_changed (name_selector_model);
g_signal_emit (name_selector_model, signals[SECTION_REMOVED], 0, name);
}
/**
* e_name_selector_model_peek_section:
* @name_selector_model: an #ENameSelectorModel
* @name: internal name of the section to peek
* @pretty_name: location in which to store a pointer to the user-visible name of the section,
* or %NULL if undesired.
* @destination_store: location in which to store a pointer to the #EDestinationStore being used
* by the section, or %NULL if undesired
*
* Gets the parameters for a destination section.
**/
gboolean
e_name_selector_model_peek_section (ENameSelectorModel *name_selector_model,
const gchar *name,
gchar **pretty_name,
EDestinationStore **destination_store)
{
Section *section;
gint n;
g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
n = find_section_by_name (name_selector_model, name);
if (n < 0) {
g_warning ("ENameSelectorModel does not have a section called '%s'!", name);
return FALSE;
}
section = &g_array_index (name_selector_model->priv->sections, Section, n);
if (pretty_name)
*pretty_name = g_strdup (section->pretty_name);
if (destination_store)
*destination_store = section->destination_store;
return TRUE;
}
/**
* e_name_selector_model_get_contact_emails_without_used:
* @name_selector_model: an #ENameSelectorModel
* @contact: to get emails from
* @remove_used: set to %TRUE to remove used from a list; or set to %FALSE to
* set used indexes to %NULL and keep them in the returned list
*
* Returns list of all email from @contact, without all used
* in any section. Each item is a string, an email address.
* Returned list should be freed with @e_name_selector_model_free_emails_list.
*
* Since: 2.30
**/
GList *
e_name_selector_model_get_contact_emails_without_used (ENameSelectorModel *name_selector_model,
EContact *contact,
gboolean remove_used)
{
GList *email_list;
gint emails;
gint i;
const gchar *contact_uid;
g_return_val_if_fail (name_selector_model != NULL, NULL);
g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
g_return_val_if_fail (contact != NULL, NULL);
g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
g_return_val_if_fail (contact_uid != NULL, NULL);
email_list = e_contact_get (contact, E_CONTACT_EMAIL);
emails = g_list_length (email_list);
for (i = 0; i < name_selector_model->priv->sections->len; i++) {
Section *section;
GList *destinations;
GList *l;
section = &g_array_index (name_selector_model->priv->sections, Section, i);
destinations = e_destination_store_list_destinations (section->destination_store);
for (l = destinations; l; l = g_list_next (l)) {
EDestination *destination = l->data;
const gchar *destination_uid;
destination_uid = e_destination_get_contact_uid (destination);
if (destination_uid && !strcmp (contact_uid, destination_uid)) {
gint email_num = e_destination_get_email_num (destination);
if (email_num < 0 || email_num >= emails) {
g_warning ("%s: Destination's email_num %d out of bounds 0..%d", G_STRFUNC, email_num, emails - 1);
} else {
GList *nth = g_list_nth (email_list, email_num);
g_return_val_if_fail (nth != NULL, NULL);
g_free (nth->data);
nth->data = NULL;
}
}
}
g_list_free (destinations);
}
if (remove_used) {
/* remove all with data NULL, which are those used already */
do {
emails = g_list_length (email_list);
email_list = g_list_remove (email_list, NULL);
} while (g_list_length (email_list) != emails);
}
return email_list;
}
/**
* e_name_selector_model_free_emails_list:
* @email_list: list of emails returned from @e_name_selector_model_get_contact_emails_without_used
*
* Frees a list of emails returned from @e_name_selector_model_get_contact_emails_without_used.
*
* Since: 2.30
**/
void
e_name_selector_model_free_emails_list (GList *email_list)
{
deep_free_list (email_list);
}