/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* 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.
*/
/* We need this for strptime. */
#define _XOPEN_SOURCE
#include <config.h>
#include <ctype.h>
#include <math.h>
#undef _XOPEN_SOURCE
#include <sys/time.h>
#define _XOPEN_SOURCE
#include <time.h>
#include <gnome.h>
#include <cal-util/timeutil.h>
#include "calendar-model.h"
#include "calendar-commands.h"
/* Private part of the ECalendarModel structure */
struct _CalendarModelPrivate {
/* 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;
/* HACK: so that ETable can do its stupid append_row() thing */
guint appending_row : 1;
};
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_append_row (ETableModel *etm, ETableModel *source, gint 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);
#if 0
static char * calendar_model_value_to_string (ETableModel *etm, int col, const void *value);
#endif
static void load_objects (CalendarModel *model);
static int remove_object (CalendarModel *model, const char *uid);
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->append_row = calendar_model_append_row;
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;
#if 0
etm_class->value_to_string = calendar_model_value_to_string;
#endif
}
/* 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 (CalComponent *));
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++) {
CalComponent *comp;
comp = g_array_index (priv->objects, CalComponent *, i);
g_assert (comp != NULL);
gtk_object_unref (GTK_OBJECT (comp));
}
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 CAL_COMPONENT_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;
}
/* Creates a nice string representation of a time value */
static char*
get_time_t (time_t *t, gboolean skip_midnight)
{
static char buffer[32];
struct tm *tmp_tm;
if (*t <= 0) {
buffer[0] = '\0';
} else {
tmp_tm = localtime (t);
if (skip_midnight && tmp_tm->tm_hour == 0
&& tmp_tm->tm_min == 0 && tmp_tm->tm_sec == 0)
strftime (buffer, 32, "%a %x", tmp_tm);
else
strftime (buffer, 32, "%a %x %T", tmp_tm);
}
return buffer;
}
/* Builds a string based on the list of CATEGORIES properties of a calendar
* component.
*/
static char *
get_categories (CalComponent *comp)
{
GSList *categories;
GString *str;
char *s;
GSList *l;
cal_component_get_categories_list (comp, &categories);
str = g_string_new (NULL);
for (l = categories; l; l = l->next) {
const char *category;
category = l->data;
g_string_append (str, category);
if (l->next != NULL)
g_string_append (str, ", ");
}
s = str->str;
g_string_free (str, FALSE);
cal_component_free_categories_list (categories);
return s;
}
/* Returns a string based on the CLASSIFICATION property of a calendar component */
static char *
get_classification (CalComponent *comp)
{
CalComponentClassification classif;
cal_component_get_classification (comp, &classif);
switch (classif) {
case CAL_COMPONENT_CLASS_NONE:
return "";
case CAL_COMPONENT_CLASS_PUBLIC:
return _("Public");
case CAL_COMPONENT_CLASS_PRIVATE:
return _("Private");
case CAL_COMPONENT_CLASS_CONFIDENTIAL:
return _("Confidential");
case CAL_COMPONENT_CLASS_UNKNOWN:
return _("Unknown");
default:
g_assert_not_reached ();
return NULL;
}
}
/* Builds a string for the COMPLETED property of a calendar component */
static char *
get_completed (CalComponent *comp)
{
struct icaltimetype *completed;
time_t t;
cal_component_get_completed (comp, &completed);
if (!completed)
t = 0;
else {
t = icaltime_as_timet (*completed);
cal_component_free_icaltimetype (completed);
}
return get_time_t (&t, FALSE);
}
/* Builds a string for and frees a date/time value */
static char *
get_and_free_datetime (CalComponentDateTime dt)
{
time_t t;
if (!dt.value)
t = 0;
else
t = icaltime_as_timet (*dt.value);
cal_component_free_datetime (&dt);
return get_time_t (&t, FALSE);
}
/* Builds a string for the DTEND property of a calendar component */
static char *
get_dtend (CalComponent *comp)
{
CalComponentDateTime dt;
cal_component_get_dtend (comp, &dt);
return get_and_free_datetime (dt);
}
/* Builds a string for the DTSTART property of a calendar component */
static char *
get_dtstart (CalComponent *comp)
{
CalComponentDateTime dt;
cal_component_get_dtstart (comp, &dt);
return get_and_free_datetime (dt);
}
/* Builds a string for the DUE property of a calendar component */
static char *
get_due (CalComponent *comp)
{
CalComponentDateTime dt;
cal_component_get_due (comp, &dt);
return get_and_free_datetime (dt);
}
/* Builds a string for the GEO property of a calendar component */
static char*
get_geo (CalComponent *comp)
{
struct icalgeotype *geo;
static gchar buf[32];
cal_component_get_geo (comp, &geo);
if (!geo)
buf[0] = '\0';
else {
g_snprintf (buf, sizeof (buf), "%g %s, %g %s",
fabs (geo->lat),
geo->lat >= 0.0 ? _("N") : _("S"),
fabs (geo->lon),
geo->lon >= 0.0 ? _("E") : _("W"));
cal_component_free_geo (geo);
}
return buf;
}
/* Builds a string for the PERCENT property of a calendar component */
static char *
get_percent (CalComponent *comp)
{
int *percent;
static char buf[32];
cal_component_get_percent (comp, &percent);
if (!percent)
buf[0] = '\0';
else {
g_snprintf (buf, sizeof (buf), "%d%%", *percent);
cal_component_free_percent (percent);
}
return buf;
}
/* Builds a string for the PRIORITY property of a calendar component */
static char *
get_priority (CalComponent *comp)
{
int *priority;
static char buf[32];
cal_component_get_priority (comp, &priority);
if (!priority)
buf[0] = '\0';
else {
g_snprintf (buf, sizeof (buf), "%d", *priority);
cal_component_free_priority (priority);
}
return buf;
}
/* Builds a string for the SUMMARY property of a calendar component */
static char *
get_summary (CalComponent *comp)
{
CalComponentText summary;
cal_component_get_summary (comp, &summary);
if (summary.value)
return (char *) summary.value;
else
return "";
}
/* Builds a string for the TRANSPARENCY property of a calendar component */
static char *
get_transparency (CalComponent *comp)
{
CalComponentTransparency transp;
cal_component_get_transparency (comp, &transp);
switch (transp) {
case CAL_COMPONENT_TRANSP_NONE:
return "";
case CAL_COMPONENT_TRANSP_TRANSPARENT:
return _("Transparent");
case CAL_COMPONENT_TRANSP_OPAQUE:
return _("Opaque");
case CAL_COMPONENT_TRANSP_UNKNOWN:
return _("Unknown");
default:
g_assert_not_reached ();
return NULL;
}
}
/* Builds a string for the URL property of a calendar component */
static char *
get_url (CalComponent *comp)
{
const char *url;
cal_component_get_url (comp, &url);
if (url)
return (char *) url;
else
return "";
}
/* Returns whether the component has any alarms defined for it */
static gboolean
get_has_alarms (CalComponent *comp)
{
CalComponentAlarm *alarm;
gboolean retval;
alarm = cal_component_get_first_alarm (comp);
retval = (alarm != NULL);
cal_component_alarm_free (alarm);
return retval;
}
/* Returns whether the completion date has been set on a component */
static gboolean
get_is_complete (CalComponent *comp)
{
struct icaltimetype *t;
gboolean retval;
cal_component_get_completed (comp, &t);
retval = (t != NULL);
if (retval)
cal_component_free_icaltimetype (t);
return retval;
}
/* Returns whether a calendar component is overdue.
*
* FIXME: This will only get called when the component is scrolled into the
* ETable. There should be some sort of dynamic update thingy for if a component
* becomes overdue while it is being viewed.
*/
static gboolean
get_is_overdue (CalComponent *comp)
{
CalComponentDateTime dt;
gboolean retval;
cal_component_get_due (comp, &dt);
/* First, do we have a due date? */
if (!dt.value)
retval = FALSE;
else {
struct icaltimetype *completed;
time_t t;
/* Second, is it already completed? */
cal_component_get_completed (comp, &completed);
if (completed) {
retval = FALSE;
cal_component_free_icaltimetype (completed);
goto out;
}
/* Third, are we overdue as of right now?
*
* FIXME: should we check the PERCENT as well? If it is at 100%
* but the COMPLETED property is not set, is the component
* really overdue?
**/
t = icaltime_as_timet (*dt.value);
if (t < time (NULL))
retval = TRUE;
else
retval = FALSE;
}
out:
cal_component_free_datetime (&dt);
return retval;
}
/* value_at handler for the calendar table model */
static void *
calendar_model_value_at (ETableModel *etm, int col, int row)
{
CalendarModel *model;
CalendarModelPrivate *priv;
CalComponent *comp;
model = CALENDAR_MODEL (etm);
priv = model->priv;
g_return_val_if_fail (col >= 0 && col < CAL_COMPONENT_FIELD_NUM_FIELDS, NULL);
g_return_val_if_fail (row >= 0 && row < priv->objects->len, NULL);
comp = g_array_index (priv->objects, CalComponent *, row);
g_assert (comp != NULL);
switch (col) {
case CAL_COMPONENT_FIELD_CATEGORIES:
return get_categories (comp);
case CAL_COMPONENT_FIELD_CLASSIFICATION:
return get_classification (comp);
case CAL_COMPONENT_FIELD_COMPLETED:
return get_completed (comp);
case CAL_COMPONENT_FIELD_DTEND:
return get_dtend (comp);
case CAL_COMPONENT_FIELD_DTSTART:
return get_dtstart (comp);
case CAL_COMPONENT_FIELD_DUE:
return get_due (comp);
case CAL_COMPONENT_FIELD_GEO:
return get_geo (comp);
case CAL_COMPONENT_FIELD_PERCENT:
return get_percent (comp);
case CAL_COMPONENT_FIELD_PRIORITY:
return get_priority (comp);
case CAL_COMPONENT_FIELD_SUMMARY:
return get_summary (comp);
case CAL_COMPONENT_FIELD_TRANSPARENCY:
return get_transparency (comp);
case CAL_COMPONENT_FIELD_URL:
return get_url (comp);
case CAL_COMPONENT_FIELD_HAS_ALARMS:
return GINT_TO_POINTER (get_has_alarms (comp));
case CAL_COMPONENT_FIELD_ICON:
/* FIXME: Also support 'Assigned to me' & 'Assigned to someone
else'. */
if (cal_component_has_recurrences (comp))
return GINT_TO_POINTER (1);
else
return GINT_TO_POINTER (0);
case CAL_COMPONENT_FIELD_COMPLETE:
return GINT_TO_POINTER (get_is_complete (comp));
case CAL_COMPONENT_FIELD_RECURRING:
return GINT_TO_POINTER (cal_component_has_recurrences (comp));
case CAL_COMPONENT_FIELD_OVERDUE:
return GINT_TO_POINTER (get_is_overdue (comp));
case CAL_COMPONENT_FIELD_COLOR:
if (get_is_overdue (comp))
return "red";
else
return NULL;
default:
g_message ("calendar_model_value_at(): Requested invalid column %d", col);
return NULL;
}
}
/* Returns whether a string is NULL, empty, or full of whitespace */
static gboolean
string_is_empty (const char *value)
{
const char *p;
gboolean empty = TRUE;
if (value) {
p = value;
while (*p) {
if (!isspace (*p)) {
empty = FALSE;
break;
}
p++;
}
}
return empty;
}
/* FIXME: We need to set the "transient_for" property for the dialog, but
the model doesn't know anything about the windows. */
static void
show_date_warning (void)
{
GtkWidget *dialog;
char buffer[32], message[256];
time_t t;
struct tm *tmp_tm;
t = time (NULL);
tmp_tm = localtime (&t);
strftime (buffer, 32, "%a %x %T", tmp_tm);
g_snprintf (message, 256,
_("The date must be entered in the format: \n\n%s"),
buffer);
dialog = gnome_message_box_new (message,
GNOME_MESSAGE_BOX_ERROR,
GNOME_STOCK_BUTTON_OK, NULL);
gtk_widget_show (dialog);
}
/* Builds a list of categories from a comma-delimited string */
static GSList *
categories_from_string (const char *value)
{
GSList *list;
const char *categ_start;
const char *categ_end;
const char *p;
if (!value)
return NULL;
list = NULL;
categ_start = categ_end = NULL;
for (p = value; *p; p++) {
if (categ_start) {
if (*p == ',') {
char *c;
c = g_strndup (categ_start, categ_end - categ_start + 1);
list = g_slist_prepend (list, c);
categ_start = categ_end = NULL;
} else if (!isspace (*p))
categ_end = p;
} else if (!isspace (*p) && *p != ',')
categ_start = categ_end = p;
}
if (categ_start) {
char *c;
c = g_strndup (categ_start, categ_end - categ_start + 1);
list = g_slist_prepend (list, c);
}
return g_slist_reverse (list);
}
/* Sets the list of categories from a comma-delimited string */
static void
set_categories (CalComponent *comp, const char *value)
{
GSList *list;
GSList *l;
list = categories_from_string (value);
cal_component_set_categories_list (comp, list);
for (l = list; l; l = l->next) {
char *s;
s = l->data;
g_free (s);
}
g_slist_free (list);
}
/* Parses a time value entered by the user; returns -1 if it could not be
* parsed. Returns 0 for an empty time.
*/
static time_t
parse_time (const char *value)
{
struct tm tmp_tm;
struct tm *today_tm;
time_t t;
const char *p;
if (string_is_empty (value))
return 0;
/* Skip any weekday name. */
p = strptime (value, "%a", &tmp_tm);
if (!p)
p = value;
/* Try to match the full date & time, or without the seconds,
or just the date, or just the time with/without seconds.
The info pages say we should clear the tm before calling
strptime. It also means that if we don't match a time we
get 00:00:00 which is good. */
memset (&tmp_tm, 0, sizeof (tmp_tm));
if (!strptime (value, "%x %T", &tmp_tm)) {
memset (&tmp_tm, 0, sizeof (tmp_tm));
if (!strptime (value, "%x %H:%M", &tmp_tm)) {
memset (&tmp_tm, 0, sizeof (tmp_tm));
if (!strptime (value, "%x", &tmp_tm)) {
memset (&tmp_tm, 0, sizeof (tmp_tm));
if (!strptime (value, "%T", &tmp_tm)) {
memset (&tmp_tm, 0, sizeof (tmp_tm));
if (!strptime (value, "%H:%M", &tmp_tm))
return -1; /* Could not parse it */
}
/* We only got a time, so we use the
current day. */
t = time (NULL);
today_tm = localtime (&t);
tmp_tm.tm_mday = today_tm->tm_mday;
tmp_tm.tm_mon = today_tm->tm_mon;
tmp_tm.tm_year = today_tm->tm_year;
}
}
}
tmp_tm.tm_isdst = -1;
return mktime (&tmp_tm);
}
/* Sets the completion time of a component */
static void
set_completed (CalComponent *comp, const char *value)
{
time_t t;
struct icaltimetype itt;
t = parse_time (value);
if (t == -1) {
show_date_warning ();
return;
} else if (t == 0) {
cal_component_set_completed (comp, NULL);
return;
} else {
itt = icaltime_from_timet (t, FALSE, TRUE);
cal_component_set_completed (comp, &itt);
}
}
/* Sets a CalComponentDateTime value */
static void
set_datetime (CalComponent *comp, const char *value,
void (* set_func) (CalComponent *comp, CalComponentDateTime *dt))
{
time_t t;
t = parse_time (value);
if (t == -1) {
show_date_warning ();
return;
} else if (t == 0) {
(* set_func) (comp, NULL);
return;
} else {
CalComponentDateTime dt;
struct icaltimetype itt;
itt = icaltime_from_timet (t, FALSE, TRUE);
dt.value = &itt;
dt.tzid = NULL;
(* set_func) (comp, &dt);
}
}
/* FIXME: We need to set the "transient_for" property for the dialog, but the
* model doesn't know anything about the windows.
*/
static void
show_geo_warning (void)
{
GtkWidget *dialog;
dialog = gnome_message_box_new (_("The geographical position must be entered "
"in the format: \n\n45.436845,125.862501"),
GNOME_MESSAGE_BOX_ERROR,
GNOME_STOCK_BUTTON_OK, NULL);
gtk_widget_show (dialog);
}
/* Sets the geographical position value of a component */
static void
set_geo (CalComponent *comp, const char *value)
{
double latitude, longitude;
int matched;
struct icalgeotype geo;
if (string_is_empty (value)) {
cal_component_set_geo (comp, NULL);
return;
}
matched = sscanf (value, "%lg , %lg", &latitude, &longitude);
if (matched != 2) {
show_geo_warning ();
return;
}
geo.lat = latitude;
geo.lon = longitude;
cal_component_set_geo (comp, &geo);
}
/* FIXME: We need to set the "transient_for" property for the dialog, but the
* model doesn't know anything about the windows.
*/
static void
show_percent_warning (void)
{
GtkWidget *dialog;
dialog = gnome_message_box_new (_("The percent value must be between 0 and 100, inclusive"),
GNOME_MESSAGE_BOX_ERROR,
GNOME_STOCK_BUTTON_OK, NULL);
gtk_widget_show (dialog);
}
/* Sets the percent value of a calendar component */
static void
set_percent (CalComponent *comp, const char *value)
{
int matched, percent;
if (string_is_empty (value)) {
cal_component_set_percent (comp, NULL);
return;
}
matched = sscanf (value, "%i", &percent);
if (matched != 1 || percent < 0 || percent > 100) {
show_percent_warning ();
return;
}
cal_component_set_percent (comp, &percent);
}
/* FIXME: We need to set the "transient_for" property for the dialog, but the
* model doesn't know anything about the windows. */
static void
show_priority_warning (void)
{
GtkWidget *dialog;
dialog = gnome_message_box_new (_("The priority must be between 1 and 9, inclusive"),
GNOME_MESSAGE_BOX_ERROR,
GNOME_STOCK_BUTTON_OK, NULL);
gtk_widget_show (dialog);
}
/* Sets the priority of a calendar component */
static void
set_priority (CalComponent *comp, const char *value)
{
int matched, priority;
if (string_is_empty (value)) {
cal_component_set_priority (comp, NULL);
return;
}
matched = sscanf (value, "%i", &priority);
if (matched != 1 || priority < 1 || priority > 9) {
show_priority_warning ();
return;
}
cal_component_set_priority (comp, &priority);
}
/* Sets the summary of a calendar component */
static void
set_summary (CalComponent *comp, const char *value)
{
CalComponentText text;
if (string_is_empty (value)) {
cal_component_set_summary (comp, NULL);
return;
}
text.value = value;
text.altrep = NULL; /* FIXME: should we preserve the old ALTREP? */
cal_component_set_summary (comp, &text);
}
/* Sets the URI of a calendar component */
static void
set_url (CalComponent *comp, const char *value)
{
if (string_is_empty (value)) {
cal_component_set_url (comp, NULL);
return;
}
cal_component_set_url (comp, value);
}
/* Sets the completion time of a component was toggled */
static void
set_complete (CalComponent *comp, const void *value)
{
time_t t = time (NULL);
gint state = GPOINTER_TO_INT (value);
struct icaltimetype itt;
if (state) {
itt = icaltime_from_timet (t, FALSE, FALSE);
cal_component_set_completed (comp, &itt);
} else {
cal_component_set_completed (comp, NULL);
}
}
/* set_value_at handler for the calendar table model */
static void
calendar_model_set_value_at (ETableModel *etm, int col, int row, const void *value)
{
CalendarModel *model;
CalendarModelPrivate *priv;
CalComponent *comp;
model = CALENDAR_MODEL (etm);
priv = model->priv;
g_return_if_fail (col >= 0 && col < CAL_COMPONENT_FIELD_NUM_FIELDS);
g_return_if_fail (row >= 0 && row < priv->objects->len);
comp = g_array_index (priv->objects, CalComponent *, row);
g_assert (comp != NULL);
switch (col) {
case CAL_COMPONENT_FIELD_CATEGORIES:
set_categories (comp, value);
break;
/* FIXME: CLASSIFICATION requires an option menu cell renderer */
case CAL_COMPONENT_FIELD_COMPLETED:
set_completed (comp, value);
break;
case CAL_COMPONENT_FIELD_DTEND:
/* FIXME: Need to reset dtstart if dtend happens before it */
set_datetime (comp, value, cal_component_set_dtend);
break;
case CAL_COMPONENT_FIELD_DTSTART:
/* FIXME: Need to reset dtend if dtstart happens after it */
set_datetime (comp, value, cal_component_set_dtstart);
break;
case CAL_COMPONENT_FIELD_DUE:
set_datetime (comp, value, cal_component_set_due);
break;
case CAL_COMPONENT_FIELD_GEO:
set_geo (comp, value);
break;
case CAL_COMPONENT_FIELD_PERCENT:
set_percent (comp, value);
break;
case CAL_COMPONENT_FIELD_PRIORITY:
set_priority (comp, value);
break;
case CAL_COMPONENT_FIELD_SUMMARY:
set_summary (comp, value);
break;
/* FIXME: TRANSPARENCY requires an option menu cell renderer */
case CAL_COMPONENT_FIELD_URL:
set_url (comp, value);
break;
case CAL_COMPONENT_FIELD_COMPLETE:
set_complete (comp, value);
break;
default:
g_message ("calendar_model_set_value_at(): Requested invalid column %d", col);
break;
}
/* FIXME: this is an ugly HACK. ETable needs a better API for the
* "click here to add an element" thingy.
*/
if (priv->appending_row)
return;
if (!cal_client_update_object (priv->client, comp))
g_message ("calendar_model_set_value_at(): Could not update the object!");
}
/* is_cell_editable handler for the calendar table model */
static gboolean
calendar_model_is_cell_editable (ETableModel *etm, int col, int row)
{
CalendarModel *model;
CalendarModelPrivate *priv;
model = CALENDAR_MODEL (etm);
priv = model->priv;
g_return_val_if_fail (col >= 0 && col < CAL_COMPONENT_FIELD_NUM_FIELDS, FALSE);
/* FIXME: We can't check this as 'click-to-add' passes row 0. */
/*g_return_val_if_fail (row >= 0 && row < priv->objects->len, FALSE);*/
switch (col) {
case CAL_COMPONENT_FIELD_CATEGORIES:
case CAL_COMPONENT_FIELD_COMPLETED:
case CAL_COMPONENT_FIELD_DTEND:
case CAL_COMPONENT_FIELD_DTSTART:
case CAL_COMPONENT_FIELD_DUE:
case CAL_COMPONENT_FIELD_GEO:
case CAL_COMPONENT_FIELD_PERCENT:
case CAL_COMPONENT_FIELD_PRIORITY:
case CAL_COMPONENT_FIELD_SUMMARY:
case CAL_COMPONENT_FIELD_URL:
case CAL_COMPONENT_FIELD_ICON:
case CAL_COMPONENT_FIELD_COMPLETE:
return TRUE;
default:
return FALSE;
}
}
/* append_row handler for the calendar model */
static void
calendar_model_append_row (ETableModel *etm, ETableModel *source, gint row)
{
CalendarModel *model;
CalendarModelPrivate *priv;
CalComponent *comp;
int *new_idx, col;
const char *uid;
model = CALENDAR_MODEL (etm);
priv = model->priv;
/* This is a HACK */
priv->appending_row = TRUE;
/* FIXME: This should support other types of components, but for now it
* is only used for the task list.
*/
comp = cal_component_new ();
cal_component_set_new_vtype (comp, CAL_COMPONENT_TODO);
cal_component_get_uid (comp, &uid);
g_array_append_val (priv->objects, comp);
new_idx = g_new (int, 1);
*new_idx = priv->objects->len - 1;
g_hash_table_insert (priv->uid_index_hash, (char *) uid, new_idx);
/* Notify the views about the new row. I think we have to do that here,
or the views may become confused when they start getting
"row_changed" or "cell_changed" signals for this new row. */
e_table_model_row_inserted (etm, *new_idx);
for (col = 0; col < CAL_COMPONENT_FIELD_NUM_FIELDS; col++) {
const void *val;
if (!e_table_model_is_cell_editable (etm, col, *new_idx))
continue;
val = e_table_model_value_at(source, col, row);
e_table_model_set_value_at (etm, col, *new_idx, val);
}
/* This is the end of the HACK */
priv->appending_row = FALSE;
if (!cal_client_update_object (priv->client, comp)) {
/* FIXME: Show error dialog. */
g_message ("calendar_model_append_row(): Could not add new object!");
remove_object (model, uid);
e_table_model_row_deleted (etm, *new_idx);
}
}
/* Duplicates a string value */
static char *
dup_string (const char *value)
{
return g_strdup (value);
}
/* duplicate_value handler for the calendar table model */
static void *
calendar_model_duplicate_value (ETableModel *etm, int col, const void *value)
{
g_return_val_if_fail (col >= 0 && col < CAL_COMPONENT_FIELD_NUM_FIELDS, NULL);
/* They are almost all dup_string()s for now, but we'll have real fields
* later.
*/
switch (col) {
case CAL_COMPONENT_FIELD_CATEGORIES:
return dup_string (value);
case CAL_COMPONENT_FIELD_CLASSIFICATION:
return (void *) value;
case CAL_COMPONENT_FIELD_COMPLETED:
case CAL_COMPONENT_FIELD_DTEND:
case CAL_COMPONENT_FIELD_DTSTART:
case CAL_COMPONENT_FIELD_DUE:
case CAL_COMPONENT_FIELD_GEO:
case CAL_COMPONENT_FIELD_PERCENT:
case CAL_COMPONENT_FIELD_PRIORITY:
case CAL_COMPONENT_FIELD_SUMMARY:
return dup_string (value);
case CAL_COMPONENT_FIELD_TRANSPARENCY:
return (void *) value;
case CAL_COMPONENT_FIELD_URL:
return dup_string (value);
case CAL_COMPONENT_FIELD_HAS_ALARMS:
case CAL_COMPONENT_FIELD_ICON:
case CAL_COMPONENT_FIELD_COMPLETE:
case CAL_COMPONENT_FIELD_RECURRING:
case CAL_COMPONENT_FIELD_OVERDUE:
case CAL_COMPONENT_FIELD_COLOR:
return (void *) value;
default:
g_message ("calendar_model_duplicate_value(): Requested invalid column %d", col);
return NULL;
}
}
/* free_value handler for the calendar table model */
static void
calendar_model_free_value (ETableModel *etm, int col, void *value)
{
g_return_if_fail (col >= 0 && col < CAL_COMPONENT_FIELD_NUM_FIELDS);
switch (col) {
case CAL_COMPONENT_FIELD_CATEGORIES:
g_free (value);
case CAL_COMPONENT_FIELD_CLASSIFICATION:
return;
case CAL_COMPONENT_FIELD_COMPLETED:
case CAL_COMPONENT_FIELD_DTEND:
case CAL_COMPONENT_FIELD_DTSTART:
case CAL_COMPONENT_FIELD_DUE:
case CAL_COMPONENT_FIELD_GEO:
case CAL_COMPONENT_FIELD_PERCENT:
case CAL_COMPONENT_FIELD_PRIORITY:
case CAL_COMPONENT_FIELD_SUMMARY:
g_free (value);
case CAL_COMPONENT_FIELD_TRANSPARENCY:
return;
case CAL_COMPONENT_FIELD_URL:
g_free (value);
case CAL_COMPONENT_FIELD_HAS_ALARMS:
case CAL_COMPONENT_FIELD_ICON:
case CAL_COMPONENT_FIELD_COMPLETE:
case CAL_COMPONENT_FIELD_RECURRING:
case CAL_COMPONENT_FIELD_OVERDUE:
case CAL_COMPONENT_FIELD_COLOR:
return;
default:
g_message ("calendar_model_free_value(): Requested invalid column %d", col);
return;
}
}
/* Initializes a string value */
static char *
init_string (void)
{
return g_strdup ("");
}
/* initialize_value handler for the calendar table model */
static void *
calendar_model_initialize_value (ETableModel *etm, int col)
{
g_return_val_if_fail (col >= 0 && col < CAL_COMPONENT_FIELD_NUM_FIELDS, NULL);
switch (col) {
case CAL_COMPONENT_FIELD_CATEGORIES:
return init_string ();
case CAL_COMPONENT_FIELD_CLASSIFICATION:
return NULL;
case CAL_COMPONENT_FIELD_COMPLETED:
case CAL_COMPONENT_FIELD_DTEND:
case CAL_COMPONENT_FIELD_DTSTART:
case CAL_COMPONENT_FIELD_DUE:
case CAL_COMPONENT_FIELD_GEO:
case CAL_COMPONENT_FIELD_PERCENT:
case CAL_COMPONENT_FIELD_PRIORITY:
case CAL_COMPONENT_FIELD_SUMMARY:
return init_string ();
case CAL_COMPONENT_FIELD_TRANSPARENCY:
return NULL;
case CAL_COMPONENT_FIELD_URL:
return init_string ();
case CAL_COMPONENT_FIELD_HAS_ALARMS:
case CAL_COMPONENT_FIELD_ICON:
case CAL_COMPONENT_FIELD_COMPLETE:
case CAL_COMPONENT_FIELD_RECURRING:
case CAL_COMPONENT_FIELD_OVERDUE:
case CAL_COMPONENT_FIELD_COLOR:
return NULL;
default:
g_message ("calendar_model_initialize_value(): Requested invalid column %d", col);
return NULL;
}
}
/* value_is_empty handler for the calendar model. This should return TRUE
unless a significant value has been set. The 'click-to-add' feature
checks all fields to see if any are not empty and if so it adds a new
row, so we only want to return FALSE if we have a useful object. */
static gboolean
calendar_model_value_is_empty (ETableModel *etm, int col, const void *value)
{
g_return_val_if_fail (col >= 0 && col < CAL_COMPONENT_FIELD_NUM_FIELDS, TRUE);
switch (col) {
case CAL_COMPONENT_FIELD_CATEGORIES:
case CAL_COMPONENT_FIELD_CLASSIFICATION: /* actually goes here, not by itself */
case CAL_COMPONENT_FIELD_COMPLETED:
case CAL_COMPONENT_FIELD_DTEND:
case CAL_COMPONENT_FIELD_DTSTART:
case CAL_COMPONENT_FIELD_DUE:
case CAL_COMPONENT_FIELD_GEO:
case CAL_COMPONENT_FIELD_PERCENT:
case CAL_COMPONENT_FIELD_PRIORITY:
case CAL_COMPONENT_FIELD_SUMMARY:
case CAL_COMPONENT_FIELD_TRANSPARENCY:
case CAL_COMPONENT_FIELD_URL:
return string_is_empty (value);
case CAL_COMPONENT_FIELD_HAS_ALARMS:
case CAL_COMPONENT_FIELD_ICON:
case CAL_COMPONENT_FIELD_COMPLETE:
case CAL_COMPONENT_FIELD_RECURRING:
case CAL_COMPONENT_FIELD_OVERDUE:
case CAL_COMPONENT_FIELD_COLOR:
return TRUE;
default:
g_message ("calendar_model_value_is_empty(): Requested invalid column %d", col);
return TRUE;
}
}
/**
* 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));
}
/* Callback used when a calendar is loaded into the server */
static void
cal_loaded_cb (CalClient *client,
CalClientLoadStatus status,
CalendarModel *model)
{
g_return_if_fail (IS_CALENDAR_MODEL (model));
e_table_model_pre_change (E_TABLE_MODEL (model));
if (status == CAL_CLIENT_LOAD_SUCCESS)
load_objects (model);
e_table_model_changed (E_TABLE_MODEL (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;
CalComponent *orig_comp;
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_comp = g_array_index (priv->objects, CalComponent *, *idx);
g_assert (orig_comp != NULL);
/* Decrease the indices of all the objects that follow in the array */
for (i = *idx + 1; i < priv->objects->len; i++) {
CalComponent *comp;
int *comp_idx;
const char *comp_uid;
comp = g_array_index (priv->objects, CalComponent *, i);
g_assert (comp != NULL);
cal_component_get_uid (comp, &comp_uid);
comp_idx = g_hash_table_lookup (priv->uid_index_hash, comp_uid);
g_assert (comp_idx != NULL);
(*comp_idx)--;
g_assert (*comp_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);
gtk_object_unref (GTK_OBJECT (orig_comp));
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;
CalComponent *new_comp;
const char *new_comp_uid;
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_comp);
added = FALSE;
switch (status) {
case CAL_CLIENT_GET_SUCCESS:
cal_component_get_uid (new_comp, &new_comp_uid);
if (orig_idx == -1) {
/* The object not in the model originally, so we just append it */
g_array_append_val (priv->objects, new_comp);
new_idx = g_new (int, 1);
*new_idx = priv->objects->len - 1;
g_hash_table_insert (priv->uid_index_hash, (char *) new_comp_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_comp);
new_idx = g_new (int, 1);
*new_idx = orig_idx;
g_hash_table_insert (priv->uid_index_hash, (char *) new_comp_uid, new_idx);
/* Increase the indices of all subsequent objects */
for (i = orig_idx + 1; i < priv->objects->len; i++) {
CalComponent *comp;
int *comp_idx;
const char *comp_uid;
comp = g_array_index (priv->objects, CalComponent *, i);
g_assert (comp != NULL);
cal_component_get_uid (comp, &comp_uid);
comp_idx = g_hash_table_lookup (priv->uid_index_hash, comp_uid);
g_assert (comp_idx != NULL);
(*comp_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;
if (!cal_client_is_loaded (priv->client))
return;
uids = cal_client_get_uids (priv->client, priv->type);
for (l = uids; l; l = l->next) {
char *uid;
CalComponent *comp;
const char *comp_uid;
CalClientGetStatus status;
int *idx;
uid = l->data;
status = cal_client_get_object (priv->client, uid, &comp);
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 ();
}
idx = g_new (int, 1);
g_array_append_val (priv->objects, comp);
*idx = priv->objects->len - 1;
cal_component_get_uid (comp, &comp_uid);
g_hash_table_insert (priv->uid_index_hash, (char *) comp_uid, idx);
}
cal_obj_uid_list_free (uids);
}
/**
* calendar_model_get_cal_client:
* @model: A calendar model.
*
* Queries the calendar client interface object that a calendar model is using.
*
* Return value: A calendar client interface object.
**/
CalClient *
calendar_model_get_cal_client (CalendarModel *model)
{
CalendarModelPrivate *priv;
g_return_val_if_fail (model != NULL, NULL);
g_return_val_if_fail (IS_CALENDAR_MODEL (model), NULL);
priv = model->priv;
return priv->client;
}
/**
* 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;
e_table_model_pre_change (E_TABLE_MODEL(model));
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), "cal_loaded",
GTK_SIGNAL_FUNC (cal_loaded_cb), model);
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));
}
void
calendar_model_delete_task (CalendarModel *model,
gint row)
{
CalendarModelPrivate *priv;
CalComponent *comp;
const char *uid;
g_return_if_fail (model != NULL);
g_return_if_fail (IS_CALENDAR_MODEL (model));
priv = model->priv;
g_return_if_fail (row >= 0 && row < priv->objects->len);
comp = g_array_index (priv->objects, CalComponent *, row);
g_assert (comp != NULL);
cal_component_get_uid (comp, &uid);
/* We don't check the return value; FALSE can mean the object was not in
* the server anyways.
*/
cal_client_remove_object (priv->client, uid);
}
void
calendar_model_mark_task_complete (CalendarModel *model,
gint row)
{
CalendarModelPrivate *priv;
CalComponent *comp;
int percent;
struct icaltimetype itt;
g_return_if_fail (model != NULL);
g_return_if_fail (IS_CALENDAR_MODEL (model));
priv = model->priv;
g_return_if_fail (row >= 0 && row < priv->objects->len);
comp = g_array_index (priv->objects, CalComponent *, row);
g_assert (comp != NULL);
percent = 100;
cal_component_set_percent (comp, &percent);
itt = icaltime_from_timet (time (NULL), FALSE, TRUE);
cal_component_set_completed (comp, &itt);
if (!cal_client_update_object (priv->client, comp))
g_message ("calendar_model_mark_task_complete(): Could not update the object!");
}
/* Frees the objects stored in the calendar model */
CalComponent *
calendar_model_get_cal_object (CalendarModel *model,
gint row)
{
CalendarModelPrivate *priv;
g_return_val_if_fail (model != NULL, NULL);
g_return_val_if_fail (IS_CALENDAR_MODEL (model), NULL);
priv = model->priv;
g_return_val_if_fail (row >= 0 && row < priv->objects->len, NULL);
return g_array_index (priv->objects, CalComponent *, row);
}