/* Evolution calendar - Data model for ETable
*
* Copyright (C) 2000 Helix Code, Inc.
*
* Authors: Federico Mena-Quintero <federico@helixcode.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include <config.h>
#include <gtk/gtksignal.h>
#include "calendar-model.h"
/* Private part of the ECalendarModel structure */
typedef struct {
/* Calendar client we are using */
CalClient *client;
/* Types of objects we are dealing with */
CalObjType type;
/* Array of pointers to calendar objects */
GArray *objects;
/* UID -> array index hash */
GHashTable *uid_index_hash;
} CalendarModelPrivate;
static void calendar_model_class_init (CalendarModelClass *class);
static void calendar_model_init (CalendarModel *model);
static void calendar_model_destroy (GtkObject *object);
static int calendar_model_column_count (ETableModel *etm);
static int calendar_model_row_count (ETableModel *etm);
static void *calendar_model_value_at (ETableModel *etm, int col, int row);
static void calendar_model_set_value_at (ETableModel *etm, int col, int row, const void *value);
static gboolean calendar_model_is_cell_editable (ETableModel *etm, int col, int row);
static void *calendar_model_duplicate_value (ETableModel *etm, int col, const void *value);
static void calendar_model_free_value (ETableModel *etm, int col, void *value);
static void *calendar_model_initialize_value (ETableModel *etm, int col);
static gboolean calendar_model_value_is_empty (ETableModel *etm, int col, const void *value);
static void calendar_model_freeze (ETableModel *etm);
static void calendar_model_thaw (ETableModel *etm);
static ETableModelClass *parent_class;
/**
* calendar_model_get_type:
* @void:
*
* Registers the #CalendarModel class if necessary, and returns the type ID
* associated to it.
*
* Return value: The type ID of the #CalendarModel class.
**/
GtkType
calendar_model_get_type (void)
{
static GtkType calendar_model_type = 0;
if (!calendar_model_type) {
static GtkTypeInfo calendar_model_info = {
"CalendarModel",
sizeof (CalendarModel),
sizeof (CalendarModelClass),
(GtkClassInitFunc) calendar_model_class_init,
(GtkObjectInitFunc) calendar_model_init,
NULL, /* reserved_1 */
NULL, /* reserved_2 */
(GtkClassInitFunc) NULL
};
calendar_model_type = gtk_type_unique (E_TABLE_MODEL_TYPE, &calendar_model_info);
}
return calendar_model_type;
}
/* Class initialization function for the calendar table model */
static void
calendar_model_class_init (CalendarModelClass *class)
{
GtkObjectClass *object_class;
ETableModelClass *etm_class;
object_class = (GtkObjectClass *) class;
etm_class = (ETableModelClass *) class;
parent_class = gtk_type_class (E_TABLE_MODEL_TYPE);
object_class->destroy = calendar_model_destroy;
etm_class->column_count = calendar_model_column_count;
etm_class->row_count = calendar_model_row_count;
etm_class->value_at = calendar_model_value_at;
etm_class->set_value_at = calendar_model_set_value_at;
etm_class->is_cell_editable = calendar_model_is_cell_editable;
etm_class->duplicate_value = calendar_model_duplicate_value;
etm_class->free_value = calendar_model_free_value;
etm_class->initialize_value = calendar_model_initialize_value;
etm_class->value_is_empty = calendar_model_value_is_empty;
}
/* Object initialization function for the calendar table model */
static void
calendar_model_init (CalendarModel *model)
{
CalendarModelPrivate *priv;
priv = g_new0 (CalendarModelPrivate, 1);
model->priv = priv;
priv->objects = g_array_new (FALSE, TRUE, sizeof (iCalObject *));
priv->uid_index_hash = g_hash_table_new (g_str_hash, g_str_equal);
}
/* Called from g_hash_table_foreach_remove(), frees a stored UID->index
* mapping.
*/
static gboolean
free_uid_index (gpointer key, gpointer value, gpointer data)
{
int *idx;
idx = value;
g_free (idx);
return TRUE;
}
/* Frees the objects stored in the calendar model */
static void
free_objects (CalendarModel *model)
{
CalendarModelPrivate *priv;
int i;
priv = model->priv;
g_hash_table_foreach_remove (priv->uid_index_hash, free_uid_index, NULL);
for (i = 0; i < priv->objects->len; i++) {
iCalObject *ico;
ico = g_array_index (priv->objects, iCalObject *, i);
g_assert (ico != NULL);
ical_object_unref (ico);
}
g_array_set_size (priv->objects, 0);
}
/* Destroy handler for the calendar table model */
static void
calendar_model_destroy (GtkObject *object)
{
CalendarModel *model;
CalendarModelPrivate *priv;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_CALENDAR_MODEL (object));
model = CALENDAR_MODEL (object);
priv = model->priv;
/* Free the calendar client interface object */
if (priv->client) {
gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), model);
gtk_object_unref (GTK_OBJECT (priv->client));
priv->client = NULL;
}
/* Free the uid->index hash data and the array of UIDs */
free_objects (model);
g_hash_table_destroy (priv->uid_index_hash);
priv->uid_index_hash = NULL;
g_array_free (priv->objects, TRUE);
priv->objects = NULL;
/* Free the private structure */
g_free (priv);
model->priv = NULL;
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
/* ETableModel methods */
/* column_count handler for the calendar table model */
static int
calendar_model_column_count (ETableModel *etm)
{
return ICAL_OBJECT_FIELD_NUM_FIELDS;
}
/* row_count handler for the calendar table model */
static int
calendar_model_row_count (ETableModel *etm)
{
CalendarModel *model;
CalendarModelPrivate *priv;
model = CALENDAR_MODEL (etm);
priv = model->priv;
return priv->objects->len;
}
/* value_at handler for the calendar table model */
static void *
calendar_model_value_at (ETableModel *etm, int col, int row)
{
CalendarModel *model;
CalendarModelPrivate *priv;
iCalObject *ico;
model = CALENDAR_MODEL (etm);
priv = model->priv;
if (row >= priv->objects->len) {
g_message ("calendar_model_value_at(): Requested invalid row index %d", row);
return NULL;
}
ico = g_array_index (priv->objects, iCalObject *, row);
g_assert (ico != NULL);
switch (col) {
case ICAL_OBJECT_FIELD_COMMENT:
return ico->comment ? ico->comment : "";
case ICAL_OBJECT_FIELD_COMPLETED:
return &ico->completed;
case ICAL_OBJECT_FIELD_CREATED:
return &ico->created;
case ICAL_OBJECT_FIELD_DESCRIPTION:
return ico->desc ? ico->desc : "";
case ICAL_OBJECT_FIELD_DTSTAMP:
return &ico->dtstamp;
case ICAL_OBJECT_FIELD_DTSTART:
return &ico->dtstart;
case ICAL_OBJECT_FIELD_DTEND:
return &ico->dtend;
case ICAL_OBJECT_FIELD_GEO:
return &ico->geo;
case ICAL_OBJECT_FIELD_LAST_MOD:
return &ico->last_mod;
case ICAL_OBJECT_FIELD_LOCATION:
return ico->location ? ico->location : "";
case ICAL_OBJECT_FIELD_ORGANIZER:
return ico->organizer;
case ICAL_OBJECT_FIELD_PERCENT:
return &ico->percent;
case ICAL_OBJECT_FIELD_PRIORITY:
return &ico->priority;
case ICAL_OBJECT_FIELD_SUMMARY:
return ico->summary ? ico->summary : "";
case ICAL_OBJECT_FIELD_URL:
return ico->url ? ico->url : "";
case ICAL_OBJECT_FIELD_HAS_ALARMS:
return (gpointer) (ico->dalarm.enabled || ico->aalarm.enabled
|| ico->palarm.enabled || ico->malarm.enabled);
default:
g_message ("calendar_model_value_at(): Requested invalid column %d", col);
return NULL;
}
}
/**
* calendar_model_new:
*
* Creates a new calendar model. It must be told about the calendar client
* interface object it will monitor with calendar_model_set_cal_client().
*
* Return value: A newly-created calendar model.
**/
CalendarModel *
calendar_model_new (void)
{
return CALENDAR_MODEL (gtk_type_new (TYPE_CALENDAR_MODEL));
}
/* Removes an object from the model and updates all the indices that follow.
* Returns the index of the object that was removed, or -1 if no object with
* such UID was found.
*/
static int
remove_object (CalendarModel *model, const char *uid)
{
CalendarModelPrivate *priv;
int *idx;
iCalObject *orig_ico;
int i;
int n;
priv = model->priv;
/* Find the index of the object to be removed */
idx = g_hash_table_lookup (priv->uid_index_hash, uid);
if (!idx)
return -1;
orig_ico = g_array_index (priv->objects, iCalObject *, *idx);
g_assert (orig_ico != NULL);
/* Decrease the indices of all the objects that follow in the array */
for (i = *idx + 1; i < priv->objects->len; i++) {
iCalObject *ico;
int *ico_idx;
ico = g_array_index (priv->objects, iCalObject *, i);
g_assert (ico != NULL);
ico_idx = g_hash_table_lookup (priv->uid_index_hash, ico->uid);
g_assert (ico_idx != NULL);
(*ico_idx)--;
g_assert (*ico_idx >= 0);
}
/* Remove this object from the array and hash */
g_hash_table_remove (priv->uid_index_hash, uid);
g_array_remove_index (priv->objects, *idx);
ical_object_unref (orig_ico);
n = *idx;
g_free (idx);
return n;
}
/* Callback used when an object is updated in the server */
static void
obj_updated_cb (CalClient *client, const char *uid, gpointer data)
{
CalendarModel *model;
CalendarModelPrivate *priv;
int orig_idx;
iCalObject *new_ico;
int *new_idx;
CalClientGetStatus status;
gboolean added;
model = CALENDAR_MODEL (data);
priv = model->priv;
orig_idx = remove_object (model, uid);
status = cal_client_get_object (priv->client, uid, &new_ico);
added = FALSE;
switch (status) {
case CAL_CLIENT_GET_SUCCESS:
if (orig_idx == -1) {
/* The object not in the model originally, so we just append it */
g_array_append_val (priv->objects, new_ico);
new_idx = g_new (int, 1);
*new_idx = priv->objects->len - 1;
g_hash_table_insert (priv->uid_index_hash, new_ico->uid, new_idx);
} else {
int i;
/* Insert the new version of the object in its old position */
g_array_insert_val (priv->objects, orig_idx, new_ico);
new_idx = g_new (int, 1);
*new_idx = orig_idx;
g_hash_table_insert (priv->uid_index_hash, new_ico->uid, new_idx);
/* Increase the indices of all subsequent objects */
for (i = orig_idx + 1; i < priv->objects->len; i++) {
iCalObject *ico;
int *ico_idx;
ico = g_array_index (priv->objects, iCalObject *, i);
g_assert (ico != NULL);
ico_idx = g_hash_table_lookup (priv->uid_index_hash, ico->uid);
g_assert (ico_idx != NULL);
(*ico_idx)++;
}
}
e_table_model_row_changed (E_TABLE_MODEL (model), *new_idx);
break;
case CAL_CLIENT_GET_NOT_FOUND:
/* Nothing; the object may have been removed from the server. We just
* notify that the old object was deleted.
*/
if (orig_idx != -1)
e_table_model_row_deleted (E_TABLE_MODEL (model), orig_idx);
break;
case CAL_CLIENT_GET_SYNTAX_ERROR:
g_message ("obj_updated_cb(): Syntax error when getting object `%s'", uid);
/* Same notification as above */
if (orig_idx != -1)
e_table_model_row_deleted (E_TABLE_MODEL (model), orig_idx);
break;
default:
g_assert_not_reached ();
}
}
/* Callback used when an object is removed in the server */
static void
obj_removed_cb (CalClient *client, const char *uid, gpointer data)
{
CalendarModel *model;
int idx;
model = CALENDAR_MODEL (data);
idx = remove_object (model, uid);
if (idx != -1)
e_table_model_row_deleted (E_TABLE_MODEL (model), idx);
}
/* Loads the required objects from the calendar client */
static void
load_objects (CalendarModel *model)
{
CalendarModelPrivate *priv;
GList *uids;
GList *l;
priv = model->priv;
uids = cal_client_get_uids (priv->client, priv->type);
for (l = uids; l; l = l->next) {
char *uid;
iCalObject *ico;
CalClientGetStatus status;
int *idx;
uid = l->data;
status = cal_client_get_object (priv->client, uid, &ico);
switch (status) {
case CAL_CLIENT_GET_SUCCESS:
break;
case CAL_CLIENT_GET_NOT_FOUND:
/* Nothing; the object may have been removed from the server */
continue;
case CAL_CLIENT_GET_SYNTAX_ERROR:
g_message ("load_objects(): Syntax error when getting object `%s'", uid);
continue;
default:
g_assert_not_reached ();
}
g_assert (ico->uid != NULL);
idx = g_new (int, 1);
g_array_append_val (priv->objects, ico);
*idx = priv->objects->len - 1;
g_hash_table_insert (priv->uid_index_hash, ico->uid, idx);
}
cal_obj_uid_list_free (uids);
}
/**
* calendar_model_set_cal_client:
* @model: A calendar model.
* @client: A calendar client interface object.
* @type: Type of objects to present.
*
* Sets the calendar client interface object that a calendar model will monitor.
* It also sets the types of objects this model will present to an #ETable.
**/
void
calendar_model_set_cal_client (CalendarModel *model, CalClient *client, CalObjType type)
{
CalendarModelPrivate *priv;
g_return_if_fail (model != NULL);
g_return_if_fail (IS_CALENDAR_MODEL (model));
if (client)
g_return_if_fail (IS_CAL_CLIENT (client));
priv = model->priv;
if (priv->client == client && priv->type == type)
return;
if (client)
gtk_object_ref (GTK_OBJECT (client));
if (priv->client) {
gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), model);
gtk_object_unref (GTK_OBJECT (priv->client));
}
free_objects (model);
priv->client = client;
priv->type = type;
if (priv->client) {
gtk_signal_connect (GTK_OBJECT (priv->client), "obj_updated",
GTK_SIGNAL_FUNC (obj_updated_cb), model);
gtk_signal_connect (GTK_OBJECT (priv->client), "obj_removed",
GTK_SIGNAL_FUNC (obj_removed_cb), model);
load_objects (model);
}
e_table_model_changed (E_TABLE_MODEL (model));
}