From 04bda8ad1e0cb676d711dbf0f22ff9f4fb615891 Mon Sep 17 00:00:00 2001 From: Damon Chaplin Date: Tue, 23 Oct 2001 00:23:42 +0000 Subject: added setDefaultTimezone() method. 2001-10-22 Damon Chaplin * idl/evolution-calendar.idl: added setDefaultTimezone() method. * pcs/cal-backend.c (cal_backend_get_default_timezone): (cal_backend_set_default_timezone): new functions to call class methods. * pcs/cal-backend-file.c: lots of changes to handle the default timezone and use it. * pcs/query.c: use the default timezone. * gui/dialogs/task-details-page.c (date_changed_cb): initialized completed_tt. * gui/dialogs/event-page.c: changed it to handle DATE values. The 'All Day Event' checkbox is only set now when the DTSTART and DTEND are DATE values. * gui/dialogs/comp-editor-util.c (comp_editor_free_dates): free the CalComponentDateTime structs as well. * gui/e-tasks.c: set the default timezone on the server. * gui/tag-calendar.c: * gui/gnome-cal.c: * gui/e-week-view.c: * gui/e-day-view.c: updates to handle DATE values. * gui/e-calendar-table.c (date_compare_cb): updated to use the new ECellDateEditValue values, so it now works. (percent_compare_cb): updated to use GPOINTER_TO_INT values. (e_calendar_table_init): use an ECellPercent for the percent field and an ECellDateEditText for the date fields. * gui/comp-util.c (cal_comp_util_compare_event_timezones): return TRUE if the DTSTART or DTEND is a DATE value. We don't want to show the timezone icons for DATE values. * gui/comp-editor-factory.c (resolve_pending_requests): set the default timezone on the server. * gui/calendar-model.c: major changes to support sorting properly. For date and percent fields we now use subclasses of ECellText, so we don't use a char* as the model value. For the percent field we now use a GINT_TO_POINTER. For the date fields we now use a ECellDateEditValue* as the value. * gui/calendar-config.c (calendar_config_configure_e_cell_date_edit): set the timezone and use_24_hour flags of the new ECellDateEditText. * conduits/todo/todo-conduit.c (pre_sync): * conduits/calendar/calendar-conduit.c (pre_sync): set the default timezone on the server. * cal-util/timeutil.c (time_days_in_month): removed debug message. * cal-util/test-recur.c: try to handle timezones in the iCalendar file properly, and updated to pass default timezone. * cal-util/cal-util.c (cal_util_generate_alarms_for_comp): (cal_util_generate_alarms_for_list): added default timezone argument. * cal-util/cal-recur.c: changed many of the functions to take a default timezone, to use to resolve DATE and floating DATE-TIME values. * cal-client/cal-client.c (cal_client_set_default_timezone): new function to set the default timezone. (cal_client_ensure_timezone_on_server): new function to ensure that a given timezone is on the server. * gui/e-cell-date-edit-text.c: new subclass of ECellText to display and edit a date value. * cal-util/cal-recur.c (cal_obj_byday_expand_monthly): changed week_num to -week_num when calculating the weeks to go back from the end of the month for things like BYDAY=-2WE. Fixes bug #11525. (cal_recur_generate_instances_of_rule): only go up to MAX_YEAR (2037). We can't really handle anything past that anyway. (cal_recur_ensure_rule_end_date): initialize cb_date.end_date to 0, so if the RULE doesn't generate COUNT instances we save 0 as the time_t. svn path=/trunk/; revision=13920 --- calendar/ChangeLog | 84 +++++ calendar/cal-client/cal-client.c | 131 ++++++- calendar/cal-client/cal-client.h | 6 + calendar/cal-util/cal-component.c | 12 +- calendar/cal-util/cal-recur.c | 123 +++++-- calendar/cal-util/cal-recur.h | 6 +- calendar/cal-util/cal-util.c | 15 +- calendar/cal-util/cal-util.h | 6 +- calendar/cal-util/test-recur.c | 27 +- calendar/cal-util/timeutil.c | 2 - calendar/conduits/calendar/calendar-conduit.c | 4 + calendar/conduits/todo/todo-conduit.c | 4 + calendar/gui/Makefile.am | 2 + calendar/gui/calendar-config.c | 14 + calendar/gui/calendar-model.c | 480 +++++++++++++------------- calendar/gui/comp-editor-factory.c | 8 + calendar/gui/comp-util.c | 8 + calendar/gui/dialogs/comp-editor-util.c | 17 +- calendar/gui/dialogs/event-page.c | 408 +++++++++++----------- calendar/gui/dialogs/task-details-page.c | 2 +- calendar/gui/e-calendar-table.c | 48 ++- calendar/gui/e-cell-date-edit-text.c | 234 +++++++++++++ calendar/gui/e-cell-date-edit-text.h | 75 ++++ calendar/gui/e-day-view.c | 76 ++-- calendar/gui/e-tasks.c | 21 +- calendar/gui/e-week-view.c | 15 +- calendar/gui/gnome-cal.c | 38 +- calendar/gui/tag-calendar.c | 4 +- calendar/idl/evolution-calendar.idl | 22 +- calendar/pcs/cal-backend-file.c | 259 +++++++++----- calendar/pcs/cal-backend.c | 49 ++- calendar/pcs/cal-backend.h | 5 + calendar/pcs/cal.c | 21 ++ calendar/pcs/query.c | 8 +- 34 files changed, 1592 insertions(+), 642 deletions(-) create mode 100644 calendar/gui/e-cell-date-edit-text.c create mode 100644 calendar/gui/e-cell-date-edit-text.h diff --git a/calendar/ChangeLog b/calendar/ChangeLog index abd0b9cff0..0d3cafaabe 100644 --- a/calendar/ChangeLog +++ b/calendar/ChangeLog @@ -1,3 +1,87 @@ +2001-10-22 Damon Chaplin + + * idl/evolution-calendar.idl: added setDefaultTimezone() method. + + * pcs/cal-backend.c (cal_backend_get_default_timezone): + (cal_backend_set_default_timezone): new functions to call class + methods. + + * pcs/cal-backend-file.c: lots of changes to handle the default + timezone and use it. + + * pcs/query.c: use the default timezone. + + * gui/dialogs/task-details-page.c (date_changed_cb): initialized + completed_tt. + + * gui/dialogs/event-page.c: changed it to handle DATE values. The + 'All Day Event' checkbox is only set now when the DTSTART and DTEND + are DATE values. + + * gui/dialogs/comp-editor-util.c (comp_editor_free_dates): free the + CalComponentDateTime structs as well. + + * gui/e-tasks.c: set the default timezone on the server. + + * gui/tag-calendar.c: + * gui/gnome-cal.c: + * gui/e-week-view.c: + * gui/e-day-view.c: updates to handle DATE values. + + * gui/e-calendar-table.c (date_compare_cb): updated to use the new + ECellDateEditValue values, so it now works. + (percent_compare_cb): updated to use GPOINTER_TO_INT values. + (e_calendar_table_init): use an ECellPercent for the percent field + and an ECellDateEditText for the date fields. + + * gui/comp-util.c (cal_comp_util_compare_event_timezones): return TRUE + if the DTSTART or DTEND is a DATE value. We don't want to show the + timezone icons for DATE values. + + * gui/comp-editor-factory.c (resolve_pending_requests): set the default + timezone on the server. + + * gui/calendar-model.c: major changes to support sorting properly. + For date and percent fields we now use subclasses of ECellText, so + we don't use a char* as the model value. For the percent field we now + use a GINT_TO_POINTER. For the date fields we now use a + ECellDateEditValue* as the value. + + * gui/calendar-config.c (calendar_config_configure_e_cell_date_edit): + set the timezone and use_24_hour flags of the new ECellDateEditText. + + * conduits/todo/todo-conduit.c (pre_sync): + * conduits/calendar/calendar-conduit.c (pre_sync): set the default + timezone on the server. + + * cal-util/timeutil.c (time_days_in_month): removed debug message. + + * cal-util/test-recur.c: try to handle timezones in the iCalendar + file properly, and updated to pass default timezone. + + * cal-util/cal-util.c (cal_util_generate_alarms_for_comp): + (cal_util_generate_alarms_for_list): added default timezone argument. + + * cal-util/cal-recur.c: changed many of the functions to take a default + timezone, to use to resolve DATE and floating DATE-TIME values. + + * cal-client/cal-client.c (cal_client_set_default_timezone): new + function to set the default timezone. + (cal_client_ensure_timezone_on_server): new function to ensure that + a given timezone is on the server. + + * gui/e-cell-date-edit-text.c: new subclass of ECellText to display + and edit a date value. + + * cal-util/cal-recur.c (cal_obj_byday_expand_monthly): changed week_num + to -week_num when calculating the weeks to go back from the end of the + month for things like BYDAY=-2WE. Fixes bug #11525. + (cal_recur_generate_instances_of_rule): only go up to MAX_YEAR (2037). + We can't really handle anything past that anyway. + (cal_recur_ensure_rule_end_date): initialize cb_date.end_date to 0, + so if the RULE doesn't generate COUNT instances we save 0 as the + time_t. + 2001-10-22 Federico Mena Quintero * gui/tasks-control-factory.c (tasks_control_factory_fn): Put up a diff --git a/calendar/cal-client/cal-client.c b/calendar/cal-client/cal-client.c index f8ce2ff71b..79bfae2b1a 100644 --- a/calendar/cal-client/cal-client.c +++ b/calendar/cal-client/cal-client.c @@ -63,6 +63,10 @@ struct _CalClientPrivate { /* A cache of timezones retrieved from the server, to avoid getting them repeatedly for each get_object() call. */ GHashTable *timezones; + + /* The default timezone to use to resolve DATE and floating DATE-TIME + values. */ + icaltimezone *default_zone; }; @@ -215,6 +219,7 @@ cal_client_init (CalClient *client) priv->uri = NULL; priv->factories = NULL; priv->timezones = g_hash_table_new (g_str_hash, g_str_equal); + priv->default_zone = icaltimezone_get_utc_timezone (); /* create the WombatClient */ priv->w_client = wombat_client_new ( @@ -1714,7 +1719,8 @@ cal_client_generate_instances (CalClient *client, CalObjType type, comp = l->data; cal_recur_generate_instances (comp, start, end, add_instance, &instances, - cal_client_resolve_tzid_cb, client); + cal_client_resolve_tzid_cb, client, + priv->default_zone); gtk_object_unref (GTK_OBJECT (comp)); } @@ -2285,3 +2291,126 @@ cal_client_get_query (CalClient *client, const char *sexp) return cal_query_new (priv->cal, sexp); } + + +/* This ensures that the given timezone is on the server. We use this to pass + the default timezone to the server, so it can resolve DATE and floating + DATE-TIME values into specific times. (Most of our IDL interface uses + time_t values to pass specific times from the server to the client.) */ +static gboolean +cal_client_ensure_timezone_on_server (CalClient *client, icaltimezone *zone) +{ + CalClientPrivate *priv; + char *tzid, *obj_string; + icaltimezone *tmp_zone; + GString *vcal_string; + gboolean retval = FALSE; + icalcomponent *vtimezone_comp; + char *vtimezone_as_string; + CORBA_Environment ev; + + priv = client->priv; + + /* If the zone is NULL or UTC we don't need to do anything. */ + if (!zone) + return TRUE; + + tzid = icaltimezone_get_tzid (zone); + + if (!strcmp (tzid, "UTC")) + return TRUE; + + /* See if we already have it in the cache. If we do, it must be on + the server already. */ + tmp_zone = g_hash_table_lookup (priv->timezones, tzid); + if (tmp_zone) + return TRUE; + + /* Now we have to send it to the server, in case it doesn't already + have it. */ + + vcal_string = g_string_new (NULL); + g_string_append (vcal_string, + "BEGIN:VCALENDAR\n" + "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n" + "VERSION:2.0\n"); + + /* Convert the timezone to a string and add it. */ + vtimezone_comp = icaltimezone_get_component (zone); + if (!vtimezone_comp) { + g_string_free (vcal_string, TRUE); + return FALSE; + } + + /* We don't need to free this string as libical owns it. */ + vtimezone_as_string = icalcomponent_as_ical_string (vtimezone_comp); + g_string_append (vcal_string, vtimezone_as_string); + + g_string_append (vcal_string, "END:VCALENDAR\n"); + + obj_string = vcal_string->str; + g_string_free (vcal_string, FALSE); + + CORBA_exception_init (&ev); + GNOME_Evolution_Calendar_Cal_updateObjects (priv->cal, obj_string, &ev); + g_free (obj_string); + + if (BONOBO_USER_EX (&ev, ex_GNOME_Evolution_Calendar_Cal_InvalidObject)) + goto out; + else if (BONOBO_EX (&ev)) { + g_message ("cal_client_ensure_timezone_on_server(): could not add the timezone to the server"); + goto out; + } + + retval = TRUE; + + out: + CORBA_exception_free (&ev); + return retval; +} + + +gboolean +cal_client_set_default_timezone (CalClient *client, icaltimezone *zone) +{ + CalClientPrivate *priv; + gboolean retval = FALSE; + CORBA_Environment ev; + const char *tzid; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (IS_CAL_CLIENT (client), FALSE); + g_return_val_if_fail (zone != NULL, FALSE); + + priv = client->priv; + + g_return_val_if_fail (priv->load_state == CAL_CLIENT_LOAD_LOADED, + FALSE); + + /* Make sure the server has the VTIMEZONE data. */ + if (!cal_client_ensure_timezone_on_server (client, zone)) + return FALSE; + + /* Now set the default timezone on the server. */ + CORBA_exception_init (&ev); + tzid = icaltimezone_get_tzid (zone); + GNOME_Evolution_Calendar_Cal_setDefaultTimezone (priv->cal, + (char *) tzid, &ev); + + if (BONOBO_USER_EX (&ev, ex_GNOME_Evolution_Calendar_Cal_NotFound)) + goto out; + else if (BONOBO_EX (&ev)) { + g_message ("cal_client_set_default_timezone(): could not set the default timezone"); + goto out; + } + + retval = TRUE; + + priv->default_zone = zone; + + out: + + CORBA_exception_free (&ev); + return retval; +} + diff --git a/calendar/cal-client/cal-client.h b/calendar/cal-client/cal-client.h index 10614ef83f..36ff410fe1 100644 --- a/calendar/cal-client/cal-client.h +++ b/calendar/cal-client/cal-client.h @@ -108,6 +108,12 @@ CalClient *cal_client_new (void); void cal_client_set_auth_func (CalClient *client, CalClientAuthFunc func, gpointer data); +/* Sets the default timezone to use to resolve DATE and floating DATE-TIME + values. This will typically be from the user's timezone setting. Call this + before using any other functions. It will pass the default timezone on to + the server. Returns TRUE on success. */ +gboolean cal_client_set_default_timezone (CalClient *client, icaltimezone *zone); + gboolean cal_client_open_calendar (CalClient *client, const char *str_uri, gboolean only_if_exists); GList *cal_client_uri_list (CalClient *client, CalMode mode); diff --git a/calendar/cal-util/cal-component.c b/calendar/cal-util/cal-component.c index 312556fd54..fb7c30f95b 100644 --- a/calendar/cal-util/cal-component.c +++ b/calendar/cal-util/cal-component.c @@ -1925,6 +1925,8 @@ set_datetime (CalComponent *comp, struct datetime *datetime, priv = comp->priv; + /* If we are setting the property to NULL (i.e. removing it), then + we remove it if it exists. */ if (!dt) { if (datetime->prop) { icalcomponent_remove_property (priv->icalcomp, datetime->prop); @@ -1942,10 +1944,12 @@ set_datetime (CalComponent *comp, struct datetime *datetime, /* If the TZID is set to "UTC", we set the is_utc flag. */ if (dt->tzid && !strcmp (dt->tzid, "UTC")) dt->value->is_utc = 1; + else + dt->value->is_utc = 0; - if (datetime->prop) + if (datetime->prop) { (* prop_set_func) (datetime->prop, *dt->value); - else { + } else { datetime->prop = (* prop_new_func) (*dt->value); icalcomponent_add_property (priv->icalcomp, datetime->prop); } @@ -1954,9 +1958,9 @@ set_datetime (CalComponent *comp, struct datetime *datetime, if (dt->tzid && strcmp (dt->tzid, "UTC")) { g_assert (datetime->prop != NULL); - if (datetime->tzid_param) + if (datetime->tzid_param) { icalparameter_set_tzid (datetime->tzid_param, (char *) dt->tzid); - else { + } else { datetime->tzid_param = icalparameter_new_tzid ((char *) dt->tzid); icalproperty_add_parameter (datetime->prop, datetime->tzid_param); } diff --git a/calendar/cal-util/cal-recur.c b/calendar/cal-util/cal-recur.c index 83a0bccae9..28b1be9aec 100644 --- a/calendar/cal-util/cal-recur.c +++ b/calendar/cal-util/cal-recur.c @@ -104,8 +104,13 @@ * spec. */ +/* This is the maximum year we will go up to (inclusive). Since we use time_t + values we can't go past 2037 anyway, and some of our VTIMEZONEs may stop + at 2037 as well. */ +#define MAX_YEAR 2037 + /* Define this for some debugging output. */ -#if 0 +#if 1 #define CAL_OBJ_DEBUG 1 #endif @@ -264,11 +269,13 @@ static void cal_recur_generate_instances_of_rule (CalComponent *comp, CalRecurInstanceFn cb, gpointer cb_data, CalRecurResolveTimezoneFn tz_cb, - gpointer tz_cb_data); + gpointer tz_cb_data, + icaltimezone *default_timezone); static CalRecurrence * cal_recur_from_icalproperty (icalproperty *prop, gboolean exception, - icaltimezone *zone); + icaltimezone *zone, + gboolean convert_end_date); static gint cal_recur_ical_weekday_to_weekday (enum icalrecurrencetype_weekday day); static void cal_recur_free (CalRecurrence *r); @@ -294,6 +301,7 @@ static gboolean generate_instances_for_chunk (CalComponent *comp, CalObjTime *chunk_end, gint duration_days, gint duration_seconds, + gboolean convert_end_date, CalRecurInstanceFn cb, gpointer cb_data); @@ -487,7 +495,8 @@ static gboolean cal_recur_ensure_rule_end_date_cb (CalComponent *comp, time_t instance_start, time_t instance_end, gpointer data); -static time_t cal_recur_get_rule_end_date (icalproperty *prop); +static time_t cal_recur_get_rule_end_date (icalproperty *prop, + icaltimezone *default_timezone); static void cal_recur_set_rule_end_date (icalproperty *prop, time_t end_date); @@ -613,7 +622,8 @@ cal_recur_generate_instances (CalComponent *comp, CalRecurInstanceFn cb, gpointer cb_data, CalRecurResolveTimezoneFn tz_cb, - gpointer tz_cb_data) + gpointer tz_cb_data, + icaltimezone *default_timezone) { #if 0 g_print ("In cal_recur_generate_instances comp: %p\n", comp); @@ -621,7 +631,8 @@ cal_recur_generate_instances (CalComponent *comp, g_print (" end : %li - %s", end, ctime (&end)); #endif cal_recur_generate_instances_of_rule (comp, NULL, start, end, - cb, cb_data, tz_cb, tz_cb_data); + cb, cb_data, tz_cb, tz_cb_data, + default_timezone); } @@ -647,7 +658,8 @@ cal_recur_generate_instances_of_rule (CalComponent *comp, CalRecurInstanceFn cb, gpointer cb_data, CalRecurResolveTimezoneFn tz_cb, - gpointer tz_cb_data) + gpointer tz_cb_data, + icaltimezone *default_timezone) { CalComponentDateTime dtstart, dtend; time_t dtstart_time, dtend_time; @@ -656,11 +668,12 @@ cal_recur_generate_instances_of_rule (CalComponent *comp, CalObjTime interval_start, interval_end, event_start, event_end; CalObjTime chunk_start, chunk_end; gint days, seconds, year; - gboolean single_rule; + gboolean single_rule, convert_end_date = FALSE; icaltimezone *start_zone = NULL, *end_zone = NULL; g_return_if_fail (comp != NULL); g_return_if_fail (cb != NULL); + g_return_if_fail (tz_cb != NULL); g_return_if_fail (start >= -1); g_return_if_fail (end >= -1); @@ -675,15 +688,18 @@ cal_recur_generate_instances_of_rule (CalComponent *comp, goto out; } - /* FIXME: All floating times, including those from recurrence rules, - should be converted to the current timezone, otherwise the time_t - values may be outside the given range. This may also mean that - we need to add a timezone argument to the IDL method. */ - - if (dtstart.tzid && tz_cb) + /* For DATE-TIME values with a TZID, we use the supplied callback to + resolve the TZID. For DATE values and DATE-TIME values without a + TZID (i.e. floating times) we use the default timezone. */ + if (dtstart.tzid && !dtstart.value->is_date) { start_zone = (*tz_cb) (dtstart.tzid, tz_cb_data); - if (dtend.tzid && tz_cb) - end_zone = (*tz_cb) (dtend.tzid, tz_cb_data); + } else { + start_zone = default_timezone; + + /* Flag that we need to convert the saved ENDDATE property + to the default timezone. */ + convert_end_date = TRUE; + } dtstart_time = icaltime_as_timet_with_zone (*dtstart.value, start_zone); @@ -691,8 +707,22 @@ cal_recur_generate_instances_of_rule (CalComponent *comp, start = dtstart_time; /* FIXME: DURATION could be used instead, couldn't it? - Damon */ - if (!dtend.value) { + + /* If there is no DTEND, then use the same as the DTSTART. For + DATE-TIME values that means we will just have a single point in + time. For DATE values it means we end up with the entire day. */ + if (!dtend.value) *dtend.value = *dtstart.value; + + if (dtend.tzid && !dtend.value->is_date) { + end_zone = (*tz_cb) (dtend.tzid, tz_cb_data); + } else { + end_zone = default_timezone; + } + + /* If DTEND is a DATE value, we add 1 day to it so that it includes + the entire day. */ + if (dtend.value->is_date) { dtend.value->hour = 0; dtend.value->minute = 0; dtend.value->second = 0; @@ -768,7 +798,7 @@ cal_recur_generate_instances_of_rule (CalComponent *comp, Though this does mean that we sometimes do a lot more work than is necessary, e.g. if COUNT is set to something quite low. */ for (year = interval_start.year; - end == -1 || year <= interval_end.year; + (end == -1 || year <= interval_end.year) && year <= MAX_YEAR; year++) { chunk_start = interval_start; chunk_start.year = year; @@ -801,6 +831,7 @@ cal_recur_generate_instances_of_rule (CalComponent *comp, start, &chunk_start, &chunk_end, days, seconds, + convert_end_date, cb, cb_data)) break; } @@ -833,9 +864,13 @@ array_to_list (short *array, int max_elements) /** * cal_recur_from_icalproperty: - * @ir: An RRULE or EXRULE #icalproperty. + * @prop: An RRULE or EXRULE #icalproperty. + * @exception: TRUE if this is an EXRULE rather than an RRULE. * @zone: The DTSTART timezone, used for converting the UNTIL property if it * is given as a DATE value. + * @convert_end_date: TRUE if the saved end date needs to be converted to the + * given @zone timezone. This is needed if the DTSTART is a DATE or floating + * time. * * Converts an #icalproperty to a #CalRecurrence. This should be * freed using the cal_recur_free() function. @@ -844,7 +879,7 @@ array_to_list (short *array, int max_elements) **/ static CalRecurrence * cal_recur_from_icalproperty (icalproperty *prop, gboolean exception, - icaltimezone *zone) + icaltimezone *zone, gboolean convert_end_date) { struct icalrecurrencetype ir; CalRecurrence *r; @@ -863,8 +898,10 @@ cal_recur_from_icalproperty (icalproperty *prop, gboolean exception, r->interval = ir.interval; if (ir.count != 0) { - /* If COUNT is set, we use the pre-calculated enddate. */ - r->enddate = cal_recur_get_rule_end_date (prop); + /* If COUNT is set, we use the pre-calculated enddate. + Note that this can be 0 if the RULE doesn't actually + generate COUNT instances. */ + r->enddate = cal_recur_get_rule_end_date (prop, convert_end_date ? zone : NULL); } else { if (icaltime_is_null_time (ir.until)) { /* If neither COUNT or UNTIL is set, the event @@ -1033,6 +1070,7 @@ generate_instances_for_chunk (CalComponent *comp, CalObjTime *chunk_end, gint duration_days, gint duration_seconds, + gboolean convert_end_date, CalRecurInstanceFn cb, gpointer cb_data) { @@ -1079,7 +1117,8 @@ generate_instances_for_chunk (CalComponent *comp, CalRecurrence *r; prop = elem->data; - r = cal_recur_from_icalproperty (prop, FALSE, zone); + r = cal_recur_from_icalproperty (prop, FALSE, zone, + convert_end_date); tmp_occs = cal_obj_expand_recurrence (event_start, zone, r, chunk_start, @@ -1146,7 +1185,8 @@ generate_instances_for_chunk (CalComponent *comp, CalRecurrence *r; prop = elem->data; - r = cal_recur_from_icalproperty (prop, FALSE, zone); + r = cal_recur_from_icalproperty (prop, FALSE, zone, + convert_end_date); tmp_occs = cal_obj_expand_recurrence (event_start, zone, r, chunk_start, @@ -2986,8 +3026,15 @@ cal_obj_byday_expand_monthly (RecurData *recur_data, occ->day = time_days_in_month (occ->year, occ->month); last_weekday = cal_obj_time_weekday (occ); + + /* This calculates the number of days to step + backwards from the last day of the month + to the weekday we want. */ offset = (last_weekday + 7 - weekday) % 7; - offset += (week_num - 1) * 7; + + /* This adds on the weeks. */ + offset += (-week_num - 1) * 7; + cal_obj_time_add_days (occ, -offset); if (occ->year == year && occ->month == month) g_array_append_vals (new_occs, occ, 1); @@ -3780,16 +3827,22 @@ cal_recur_ensure_rule_end_date (CalComponent *comp, /* If refresh is FALSE, we check if the enddate is already set, and if it is we just return. */ if (!refresh) { - if (cal_recur_get_rule_end_date (prop) != -1) + if (cal_recur_get_rule_end_date (prop, NULL) != -1) return FALSE; } - /* Calculate the end date. */ + /* Calculate the end date. Note that we initialize end_date to 0, so + if the RULE doesn't generate COUNT instances we save a time_t of 0. + Also note that we use the UTC timezone as the default timezone. + In get_end_date() if the DTSTART is a DATE or floating time, we will + convert the ENDDATE to the current timezone. */ cb_data.count = rule.count; cb_data.instances = 0; + cb_data.end_date = 0; cal_recur_generate_instances_of_rule (comp, prop, -1, -1, cal_recur_ensure_rule_end_date_cb, - &cb_data, tz_cb, tz_cb_data); + &cb_data, tz_cb, tz_cb_data, + icaltimezone_get_utc_timezone ()); /* Store the end date in the "X-EVOLUTION-ENDDATE" parameter of the rule. */ @@ -3820,14 +3873,19 @@ cal_recur_ensure_rule_end_date_cb (CalComponent *comp, } +/* If default_timezone is set, the saved ENDDATE parameter is assumed to be + in that timezone. This is used when the DTSTART is a DATE or floating + value, since the RRULE end date will change depending on the timezone that + it is evaluated in. */ static time_t -cal_recur_get_rule_end_date (icalproperty *prop) +cal_recur_get_rule_end_date (icalproperty *prop, + icaltimezone *default_timezone) { icalparameter *param; const char *xname, *xvalue; icalvalue *value; struct icaltimetype icaltime; - icaltimezone *utc_zone; + icaltimezone *zone; param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER); while (param) { @@ -3840,9 +3898,10 @@ cal_recur_get_rule_end_date (icalproperty *prop) icaltime = icalvalue_get_datetime (value); icalvalue_free (value); - utc_zone = icaltimezone_get_utc_timezone (); + zone = default_timezone ? default_timezone : + icaltimezone_get_utc_timezone (); return icaltime_as_timet_with_zone (icaltime, - utc_zone); + zone); } } diff --git a/calendar/cal-util/cal-recur.h b/calendar/cal-util/cal-recur.h index 921a230de4..3527368cba 100644 --- a/calendar/cal-util/cal-recur.h +++ b/calendar/cal-util/cal-recur.h @@ -52,6 +52,9 @@ typedef icaltimezone* (* CalRecurResolveTimezoneFn) (const char *tzid, * The tz_cb is used to resolve references to timezones. It is passed a TZID * and should return the icaltimezone* corresponding to that TZID. We need to * do this as we access timezones in different ways on the client & server. + * + * The default_timezone argument is used for DTSTART or DTEND properties that + * are DATE values or do not have a TZID (i.e. floating times). */ void cal_recur_generate_instances (CalComponent *comp, time_t start, @@ -59,7 +62,8 @@ void cal_recur_generate_instances (CalComponent *comp, CalRecurInstanceFn cb, gpointer cb_data, CalRecurResolveTimezoneFn tz_cb, - gpointer tz_cb_data); + gpointer tz_cb_data, + icaltimezone *default_timezone); END_GNOME_DECLS diff --git a/calendar/cal-util/cal-util.c b/calendar/cal-util/cal-util.c index 774b9aca11..0819a56481 100644 --- a/calendar/cal-util/cal-util.c +++ b/calendar/cal-util/cal-util.c @@ -312,6 +312,8 @@ compare_alarm_instance (gconstpointer a, gconstpointer b) * @end: end time * @resolve_tzid: callback for resolving timezones * @user_data: data to be passed to the resolve_tzid callback + * @default_timezone: the timezone used to resolve DATE and floating DATE-TIME + * values. * * Generates alarm instances for a calendar component. Returns the instances * structure, or NULL if no alarm instances occurred in the specified time @@ -322,7 +324,8 @@ cal_util_generate_alarms_for_comp (CalComponent *comp, time_t start, time_t end, CalRecurResolveTimezoneFn resolve_tzid, - gpointer user_data) + gpointer user_data, + icaltimezone *default_timezone) { GList *alarm_uids; time_t alarm_start, alarm_end; @@ -343,7 +346,8 @@ cal_util_generate_alarms_for_comp (CalComponent *comp, cal_recur_generate_instances (comp, alarm_start, alarm_end, add_alarm_occurrences_cb, &aod, - resolve_tzid, user_data); + resolve_tzid, user_data, + default_timezone); /* We add the ABSOLUTE triggers separately */ generate_absolute_triggers (comp, &aod); @@ -369,6 +373,8 @@ cal_util_generate_alarms_for_comp (CalComponent *comp, * @comp_alarms: list to be returned * @resolve_tzid: callback for resolving timezones * @user_data: data to be passed to the resolve_tzid callback + * @default_timezone: the timezone used to resolve DATE and floating DATE-TIME + * values. * * Iterates through all the components in the comps list and generates alarm * instances for them; putting them in the comp_alarms list. @@ -381,7 +387,8 @@ cal_util_generate_alarms_for_list (GList *comps, time_t end, GSList **comp_alarms, CalRecurResolveTimezoneFn resolve_tzid, - gpointer user_data) + gpointer user_data, + icaltimezone *default_timezone) { GList *l; int n; @@ -393,7 +400,7 @@ cal_util_generate_alarms_for_list (GList *comps, CalComponentAlarms *alarms; comp = CAL_COMPONENT (l->data); - alarms = cal_util_generate_alarms_for_comp (comp, start, end, resolve_tzid, user_data); + alarms = cal_util_generate_alarms_for_comp (comp, start, end, resolve_tzid, user_data, default_timezone); if (alarms) { *comp_alarms = g_slist_prepend (*comp_alarms, alarms); diff --git a/calendar/cal-util/cal-util.h b/calendar/cal-util/cal-util.h index 9e7c7ce5e4..da16a61822 100644 --- a/calendar/cal-util/cal-util.h +++ b/calendar/cal-util/cal-util.h @@ -69,13 +69,15 @@ CalComponentAlarms *cal_util_generate_alarms_for_comp (CalComponent *comp, time_t start, time_t end, CalRecurResolveTimezoneFn resolve_tzid, - gpointer user_data); + gpointer user_data, + icaltimezone *default_timezone); int cal_util_generate_alarms_for_list (GList *comps, time_t start, time_t end, GSList **comp_alarms, CalRecurResolveTimezoneFn resolve_tzid, - gpointer user_data); + gpointer user_data, + icaltimezone *default_timezone); icaltimezone *cal_util_resolve_tzid (const char *tzid, gpointer data); diff --git a/calendar/cal-util/test-recur.c b/calendar/cal-util/test-recur.c index 213017ea0e..de8cf23dc2 100644 --- a/calendar/cal-util/test-recur.c +++ b/calendar/cal-util/test-recur.c @@ -121,10 +121,31 @@ get_line (char *s, } +/* This resolves any TZIDs in the components. The VTIMEZONEs must be in the + file we are reading. */ +static icaltimezone* +resolve_tzid_cb (const char *tzid, + gpointer user_data) +{ + icalcomponent *vcalendar_comp = user_data; + + if (!tzid || !tzid[0]) + return NULL; + else if (!strcmp (tzid, "UTC")) + return icaltimezone_get_utc_timezone (); + + return icalcomponent_get_timezone (vcalendar_comp, tzid); +} + + static void generate_occurrences (icalcomponent *icalcomp) { icalcompiter iter; + icaltimezone *default_timezone; + + /* This is the timezone we will use for DATE and floating values. */ + default_timezone = icaltimezone_get_utc_timezone (); for (iter = icalcomponent_begin_component (icalcomp, ICAL_ANY_COMPONENT); icalcompiter_deref (&iter) != NULL; @@ -157,11 +178,13 @@ generate_occurrences (icalcomponent *icalcomp) #if 0 cal_recur_generate_instances (comp, 982022400, 982108800, occurrence_cb, &occurrences, - NULL, NULL); + resolve_tzid_cb, icalcomp, + default_timezone); #else cal_recur_generate_instances (comp, -1, -1, occurrence_cb, &occurrences, - NULL, NULL); + resolve_tzid_cb, icalcomp, + default_timezone); #endif /* Print the component again so we can see the diff --git a/calendar/cal-util/timeutil.c b/calendar/cal-util/timeutil.c index aff465b8df..f960346477 100644 --- a/calendar/cal-util/timeutil.c +++ b/calendar/cal-util/timeutil.c @@ -367,8 +367,6 @@ time_days_in_month (int year, int month) { int days; - g_print ("Year: %i Month: %i\n", year, month); - g_return_val_if_fail (year >= 1900, 0); g_return_val_if_fail ((month >= 0) && (month < 12), 0); diff --git a/calendar/conduits/calendar/calendar-conduit.c b/calendar/conduits/calendar/calendar-conduit.c index 81c66041b6..b9f19489e7 100644 --- a/calendar/conduits/calendar/calendar-conduit.c +++ b/calendar/conduits/calendar/calendar-conduit.c @@ -948,6 +948,10 @@ pre_sync (GnomePilotConduit *conduit, return -1; LOG (" Using timezone: %s", icaltimezone_get_tzid (ctxt->timezone)); + /* Set the default timezone on the backend. */ + if (ctxt->timezone) + cal_client_set_default_timezone (ctxt->client, ctxt->timezone); + /* Load the uid <--> pilot id mapping */ filename = map_name (ctxt); e_pilot_map_read (filename, &ctxt->map); diff --git a/calendar/conduits/todo/todo-conduit.c b/calendar/conduits/todo/todo-conduit.c index e2256904c6..ca59b04bd6 100644 --- a/calendar/conduits/todo/todo-conduit.c +++ b/calendar/conduits/todo/todo-conduit.c @@ -647,6 +647,10 @@ pre_sync (GnomePilotConduit *conduit, return -1; LOG (" Using timezone: %s", icaltimezone_get_tzid (ctxt->timezone)); + /* Set the default timezone on the backend. */ + if (ctxt->timezone) + cal_client_set_default_timezone (ctxt->client, ctxt->timezone); + /* Load the uid <--> pilot id map */ filename = map_name (ctxt); e_pilot_map_read (filename, &ctxt->map); diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am index 8b70da7f10..1a155efcd7 100644 --- a/calendar/gui/Makefile.am +++ b/calendar/gui/Makefile.am @@ -96,6 +96,8 @@ evolution_calendar_SOURCES = \ component-factory.h \ e-calendar-table.h \ e-calendar-table.c \ + e-cell-date-edit-text.h \ + e-cell-date-edit-text.c \ e-day-view-layout.c \ e-day-view-layout.h \ e-day-view-main-item.c \ diff --git a/calendar/gui/calendar-config.c b/calendar/gui/calendar-config.c index 995eac2fb3..c4a3e12510 100644 --- a/calendar/gui/calendar-config.c +++ b/calendar/gui/calendar-config.c @@ -36,6 +36,7 @@ #include "component-factory.h" #include "calendar-commands.h" #include "e-tasks.h" +#include "e-cell-date-edit-text.h" #include "calendar-config.h" #include #include @@ -719,9 +720,19 @@ calendar_config_configure_e_cell_date_edit (ECellDateEdit *ecde) { gboolean use_24_hour; gint start_hour, end_hour; + ECellPopup *ecp; + ECellDateEditText *ecd; + char *location; + icaltimezone *zone; g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde)); + ecp = E_CELL_POPUP (ecde); + ecd = E_CELL_DATE_EDIT_TEXT (ecp->child); + + location = calendar_config_get_timezone (); + zone = icaltimezone_get_builtin_timezone (location); + calendar_config_configure_e_calendar (E_CALENDAR (ecde->calendar)); use_24_hour = calendar_config_get_24_hour_format (); @@ -743,6 +754,9 @@ calendar_config_configure_e_cell_date_edit (ECellDateEdit *ecde) #endif NULL); e_cell_date_edit_thaw (ecde); + + e_cell_date_edit_text_set_timezone (ecd, zone); + e_cell_date_edit_text_set_use_24_hour_format (ecd, use_24_hour); } diff --git a/calendar/gui/calendar-model.c b/calendar/gui/calendar-model.c index 6b0dd31540..bb57586767 100644 --- a/calendar/gui/calendar-model.c +++ b/calendar/gui/calendar-model.c @@ -40,6 +40,7 @@ #include "itip-utils.h" #include "calendar-model.h" #include "evolution-activity-client.h" +#include "e-cell-date-edit-text.h" /* This specifies how often we refresh the list, so that completed tasks are hidden according to the config setting, and overdue tasks change color etc. @@ -48,6 +49,19 @@ lose their edit, so this isn't ideal. */ #define CALENDAR_MODEL_REFRESH_TIMEOUT 1000 * 60 * 10 +/* These hold the date values of the objects, so we can free the values when + we no longer need them. */ +typedef struct _CalendarModelObjectData CalendarModelObjectData; +struct _CalendarModelObjectData { + ECellDateEditValue *dtstart; + ECellDateEditValue *dtend; + ECellDateEditValue *due; + ECellDateEditValue *completed; +}; + +/* We use a pointer to this value to indicate that the property is not set. */ +static ECellDateEditValue unset_date_edit_value; + /* Private part of the ECalendarModel structure */ struct _CalendarModelPrivate { /* Calendar client we are using */ @@ -63,6 +77,10 @@ struct _CalendarModelPrivate { /* Array of pointers to calendar objects */ GArray *objects; + /* Array of CalendarModelObjectData* holding data for each of the + objects in the objects array above. */ + GArray *objects_data; + /* UID -> array index hash */ GHashTable *uid_index_hash; @@ -207,6 +225,7 @@ calendar_model_init (CalendarModel *model) priv->query = NULL; priv->objects = g_array_new (FALSE, TRUE, sizeof (CalComponent *)); + priv->objects_data = g_array_new (FALSE, FALSE, sizeof (CalendarModelObjectData)); priv->uid_index_hash = g_hash_table_new (g_str_hash, g_str_equal); priv->new_comp_vtype = CAL_COMPONENT_EVENT; priv->use_24_hour_format = TRUE; @@ -221,6 +240,23 @@ calendar_model_init (CalendarModel *model) priv->activity = NULL; } +static void +calendar_model_free_object_data (CalendarModel *model, + CalendarModelObjectData *object_data) +{ + if (object_data->dtstart != &unset_date_edit_value) + g_free (object_data->dtstart); + + if (object_data->dtend != &unset_date_edit_value) + g_free (object_data->dtend); + + if (object_data->due != &unset_date_edit_value) + g_free (object_data->due); + + if (object_data->completed != &unset_date_edit_value) + g_free (object_data->completed); +} + /* Called from g_hash_table_foreach_remove(), frees a stored UID->index * mapping. */ @@ -248,13 +284,19 @@ free_objects (CalendarModel *model) for (i = 0; i < priv->objects->len; i++) { CalComponent *comp; + CalendarModelObjectData *object_data; comp = g_array_index (priv->objects, CalComponent *, i); g_assert (comp != NULL); gtk_object_unref (GTK_OBJECT (comp)); + + object_data = &g_array_index (priv->objects_data, + CalendarModelObjectData, i); + calendar_model_free_object_data (model, object_data); } g_array_set_size (priv->objects, 0); + g_array_set_size (priv->objects_data, 0); } /* Destroy handler for the calendar table model */ @@ -304,6 +346,9 @@ calendar_model_destroy (GtkObject *object) g_array_free (priv->objects, TRUE); priv->objects = NULL; + g_array_free (priv->objects_data, TRUE); + priv->objects_data = NULL; + g_free (priv->default_category); itip_addresses_free (priv->addresses); @@ -346,44 +391,6 @@ calendar_model_row_count (ETableModel *etm) return priv->objects->len; } -/* Creates a nice string representation of a time value. If show_midnight is - FALSE, and the time is midnight, then we just show the date. */ -static char* -get_time_t (CalendarModel *model, time_t *t, gboolean show_midnight) -{ - static char buffer[64]; - struct tm tmp_tm; - struct icaltimetype tt; - - if (*t <= 0) { - buffer[0] = '\0'; - } else { - /* Note that although the property may be in a different - timezone, we convert it to the current timezone to display - it in the table. If the user actually edits the value, - it will be set to the current timezone. See set_datetime. */ - tt = icaltime_from_timet_with_zone (*t, FALSE, - model->priv->zone); - tmp_tm.tm_year = tt.year - 1900; - tmp_tm.tm_mon = tt.month - 1; - tmp_tm.tm_mday = tt.day; - tmp_tm.tm_hour = tt.hour; - tmp_tm.tm_min = tt.minute; - tmp_tm.tm_sec = tt.second; - tmp_tm.tm_isdst = -1; - - tmp_tm.tm_wday = time_day_of_week (tt.day, tt.month - 1, - tt.year); - - e_time_format_date_and_time (&tmp_tm, - model->priv->use_24_hour_format, - show_midnight, FALSE, - buffer, sizeof (buffer)); - } - - return buffer; -} - /* Builds a string based on the list of CATEGORIES properties of a calendar * component. */ @@ -417,79 +424,92 @@ get_classification (CalComponent *comp) } } -/* Builds a string for the COMPLETED property of a calendar component */ -static char * +/* Returns an ECellDateEditValue* for a COMPLETED property of a + calendar component. Note that we cache these in the objects_data array so + we can free them eventually. */ +static ECellDateEditValue* get_completed (CalendarModel *model, - CalComponent *comp) + CalComponent *comp, + int row) { + CalendarModelPrivate *priv; + CalComponentDateTime dt; + CalendarModelObjectData *object_data; struct icaltimetype *completed; - time_t t; - cal_component_get_completed (comp, &completed); + priv = model->priv; + + object_data = &g_array_index (priv->objects_data, + CalendarModelObjectData, row); - if (!completed) - t = 0; - else { - /* Note that COMPLETED is stored in UTC, though we show it in - the current timezone. */ - t = icaltime_as_timet_with_zone (*completed, icaltimezone_get_utc_timezone ()); - cal_component_free_icaltimetype (completed); + if (!object_data->completed) { + cal_component_get_completed (comp, &completed); + + if (completed) { + object_data->completed = g_new (ECellDateEditValue, 1); + object_data->completed->tt = *dt.value; + object_data->completed->zone = icaltimezone_get_utc_timezone (); + cal_component_free_icaltimetype (completed); + } else { + object_data->completed = &unset_date_edit_value; + } } - return get_time_t (model, &t, TRUE); + return (object_data->completed == &unset_date_edit_value) + ? NULL : object_data->completed; } -/* Builds a string for and frees a date/time value */ -static char * -get_and_free_datetime (CalendarModel *model, CalComponentDateTime dt) +/* Returns an ECellDateEditValue* for a DTSTART, DTEND or DUE property of a + calendar component. Note that we cache these in the objects_data array so + we can free them eventually. */ +static ECellDateEditValue* +get_date_edit_value (CalendarModel *model, CalComponent *comp, + int col, int row) { - time_t t; - - if (!dt.value) - t = 0; - else { - CalClientGetStatus status; - icaltimezone *zone; + CalendarModelPrivate *priv; + CalComponentDateTime dt; + CalendarModelObjectData *object_data; + ECellDateEditValue **value; - /* FIXME: TIMEZONES: Handle error. */ - status = cal_client_get_timezone (model->priv->client, dt.tzid, - &zone); - t = icaltime_as_timet_with_zone (*dt.value, zone); - } + priv = model->priv; - cal_component_free_datetime (&dt); + object_data = &g_array_index (priv->objects_data, + CalendarModelObjectData, row); - return get_time_t (model, &t, TRUE); -} + if (col == CAL_COMPONENT_FIELD_DTSTART) + value = &object_data->dtstart; + else if (col == CAL_COMPONENT_FIELD_DTEND) + value = &object_data->dtend; + else + value = &object_data->due; -/* Builds a string for the DTEND property of a calendar component */ -static char * -get_dtend (CalendarModel *model, CalComponent *comp) -{ - CalComponentDateTime dt; + if (!(*value)) { + if (col == CAL_COMPONENT_FIELD_DTSTART) + cal_component_get_dtstart (comp, &dt); + else if (col == CAL_COMPONENT_FIELD_DTEND) + cal_component_get_dtend (comp, &dt); + else + cal_component_get_due (comp, &dt); - cal_component_get_dtend (comp, &dt); - return get_and_free_datetime (model, dt); -} + if (dt.value) { + CalClientGetStatus status; + icaltimezone *zone; -/* Builds a string for the DTSTART property of a calendar component */ -static char * -get_dtstart (CalendarModel *model, CalComponent *comp) -{ - CalComponentDateTime dt; + *value = g_new (ECellDateEditValue, 1); + (*value)->tt = *dt.value; - cal_component_get_dtstart (comp, &dt); - return get_and_free_datetime (model, dt); -} + /* FIXME: TIMEZONES: Handle error. */ + status = cal_client_get_timezone (model->priv->client, + dt.tzid, &zone); + (*value)->zone = zone; + } else { + *value = &unset_date_edit_value; + } -/* Builds a string for the DUE property of a calendar component */ -static char * -get_due (CalendarModel *model, CalComponent *comp) -{ - CalComponentDateTime dt; + cal_component_free_datetime (&dt); + } - cal_component_get_due (comp, &dt); - return get_and_free_datetime (model, dt); + return (*value == &unset_date_edit_value) ? NULL : *value; } /* Builds a string for the GEO property of a calendar component */ @@ -516,22 +536,21 @@ get_geo (CalComponent *comp) } /* Builds a string for the PERCENT property of a calendar component */ -static char * +static int get_percent (CalComponent *comp) { - int *percent; - static char buf[32]; + int *percent, retval; cal_component_get_percent (comp, &percent); - if (!percent) - buf[0] = '\0'; - else { - g_snprintf (buf, sizeof (buf), "%d%%", *percent); + if (percent) { + retval = *percent; cal_component_free_percent (percent); + } else { + retval = -1; } - return buf; + return retval; } /* Builds a string for the PRIORITY property of a calendar component */ @@ -748,7 +767,7 @@ get_location (CalComponent *comp) const char *location; cal_component_get_location (comp, &location); - return location; + return (void*) location; } /* value_at handler for the calendar table model */ @@ -780,22 +799,18 @@ calendar_model_value_at (ETableModel *etm, int col, int row) return get_classification (comp); case CAL_COMPONENT_FIELD_COMPLETED: - return get_completed (model, comp); + return get_completed (model, comp, row); case CAL_COMPONENT_FIELD_DTEND: - return get_dtend (model, comp); - case CAL_COMPONENT_FIELD_DTSTART: - return get_dtstart (model, comp); - case CAL_COMPONENT_FIELD_DUE: - return get_due (model, comp); + return get_date_edit_value (model, comp, col, row); case CAL_COMPONENT_FIELD_GEO: return get_geo (comp); case CAL_COMPONENT_FIELD_PERCENT: - return get_percent (comp); + return GINT_TO_POINTER (get_percent (comp)); case CAL_COMPONENT_FIELD_PRIORITY: return get_priority (comp); @@ -900,7 +915,7 @@ string_is_empty (const char *value) if (value) { p = value; while (*p) { - if (!isspace (*p)) { + if (!isspace ((unsigned char) *p)) { empty = FALSE; break; } @@ -911,40 +926,6 @@ string_is_empty (const char *value) } -/* 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 (CalendarModel *model) -{ - GtkWidget *dialog; - char buffer[64], message[256], *format; - time_t t; - struct tm *tmp_tm; - - t = time (NULL); - /* We are only using this as an example, so the timezone doesn't - matter. */ - tmp_tm = localtime (&t); - - if (model->priv->use_24_hour_format) - /* strftime format of a weekday, a date and a time, 24-hour. */ - format = _("%a %m/%d/%Y %H:%M:%S"); - else - /* strftime format of a weekday, a date and a time, 12-hour. */ - format = _("%a %m/%d/%Y %I:%M:%S %p"); - - strftime (buffer, sizeof (buffer), format, 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) @@ -1028,31 +1009,18 @@ set_classification (CalComponent *comp, /* Called to set the "Date Completed" field. We also need to update the Status and Percent fields to make sure they match. */ static void -set_completed (CalendarModel *model, CalComponent *comp, const char *value) +set_completed (CalendarModel *model, CalComponent *comp, const void *value) { - ETimeParseStatus status; - struct tm tmp_tm; - time_t t; + ECellDateEditValue *dv = (ECellDateEditValue*) value; - status = e_time_parse_date_and_time (value, &tmp_tm); - - if (status == E_TIME_PARSE_INVALID) { - show_date_warning (model); - } else if (status == E_TIME_PARSE_NONE) { + if (!dv) { ensure_task_not_complete (comp); } else { - struct icaltimetype itt = icaltime_null_time (); - - itt.year = tmp_tm.tm_year + 1900; - itt.month = tmp_tm.tm_mon + 1; - itt.day = tmp_tm.tm_mday; - itt.hour = tmp_tm.tm_hour; - itt.minute = tmp_tm.tm_min; - itt.second = tmp_tm.tm_sec; + time_t t; /* We assume that COMPLETED is entered in the current timezone, even though it gets stored in UTC. */ - t = icaltime_as_timet_with_zone (itt, model->priv->zone); + t = icaltime_as_timet_with_zone (dv->tt, dv->zone); ensure_task_complete (comp, t); } @@ -1060,33 +1028,18 @@ set_completed (CalendarModel *model, CalComponent *comp, const char *value) /* Sets a CalComponentDateTime value */ static void -set_datetime (CalendarModel *model, CalComponent *comp, const char *value, +set_datetime (CalendarModel *model, CalComponent *comp, const void *value, void (* set_func) (CalComponent *comp, CalComponentDateTime *dt)) { - ETimeParseStatus status; - struct tm tmp_tm; + ECellDateEditValue *dv = (ECellDateEditValue*) value; - status = e_time_parse_date_and_time (value, &tmp_tm); - - if (status == E_TIME_PARSE_INVALID) { - show_date_warning (model); - } else if (status == E_TIME_PARSE_NONE) { + if (!dv) { (* set_func) (comp, NULL); } else { CalComponentDateTime dt; - struct icaltimetype itt = icaltime_null_time (); - - itt.year = tmp_tm.tm_year + 1900; - itt.month = tmp_tm.tm_mon + 1; - itt.day = tmp_tm.tm_mday; - itt.hour = tmp_tm.tm_hour; - itt.minute = tmp_tm.tm_min; - itt.second = tmp_tm.tm_sec; - dt.value = &itt; - /* FIXME: We assume it is being set to the current timezone. - Is that OK? */ - dt.tzid = icaltimezone_get_tzid (model->priv->zone); + dt.value = &dv->tt; + dt.tzid = icaltimezone_get_tzid (dv->zone); (* set_func) (comp, &dt); } @@ -1132,45 +1085,27 @@ set_geo (CalComponent *comp, const char *value) 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) +set_percent (CalComponent *comp, const void *value) { - int matched, percent; + gint percent = GPOINTER_TO_INT (value); - if (string_is_empty (value)) { + g_return_if_fail (percent >= -1); + g_return_if_fail (percent <= 100); + + /* A value of -1 means it isn't set. */ + if (percent == -1) { cal_component_set_percent (comp, NULL); ensure_task_not_complete (comp); - return; - } - - matched = sscanf (value, "%i", &percent); + } else { + cal_component_set_percent (comp, &percent); - if (matched != 1 || percent < 0 || percent > 100) { - show_percent_warning (); - return; + if (percent == 100) + ensure_task_complete (comp, -1); + else + ensure_task_not_complete (comp); } - - cal_component_set_percent (comp, &percent); - - if (percent == 100) - ensure_task_complete (comp, -1); - else - ensure_task_not_complete (comp); } /* Sets the priority of a calendar component */ @@ -1476,6 +1411,19 @@ dup_string (const char *value) return g_strdup (value); } +static void* +dup_date_edit_value (const void *value) +{ + ECellDateEditValue *dv, *orig_dv; + + orig_dv = (ECellDateEditValue*) value; + + dv = g_new (ECellDateEditValue, 1); + *dv = *orig_dv; + + return dv; +} + /* duplicate_value handler for the calendar table model */ static void * calendar_model_duplicate_value (ETableModel *etm, int col, const void *value) @@ -1489,12 +1437,7 @@ calendar_model_duplicate_value (ETableModel *etm, int col, const void *value) switch (col) { case CAL_COMPONENT_FIELD_CATEGORIES: case CAL_COMPONENT_FIELD_CLASSIFICATION: - 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: @@ -1502,9 +1445,16 @@ calendar_model_duplicate_value (ETableModel *etm, int col, const void *value) case CAL_COMPONENT_FIELD_STATUS: return dup_string (value); + case CAL_COMPONENT_FIELD_COMPLETED: + case CAL_COMPONENT_FIELD_DTEND: + case CAL_COMPONENT_FIELD_DTSTART: + case CAL_COMPONENT_FIELD_DUE: + return dup_date_edit_value (value); + case CAL_COMPONENT_FIELD_HAS_ALARMS: case CAL_COMPONENT_FIELD_ICON: case CAL_COMPONENT_FIELD_COMPLETE: + case CAL_COMPONENT_FIELD_PERCENT: case CAL_COMPONENT_FIELD_RECURRING: case CAL_COMPONENT_FIELD_OVERDUE: case CAL_COMPONENT_FIELD_COLOR: @@ -1544,7 +1494,6 @@ calendar_model_free_value (ETableModel *etm, int col, void *value) 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_STATUS: @@ -1560,6 +1509,7 @@ calendar_model_free_value (ETableModel *etm, int col, void *value) g_free (value); break; + case CAL_COMPONENT_FIELD_PERCENT: case CAL_COMPONENT_FIELD_HAS_ALARMS: case CAL_COMPONENT_FIELD_ICON: case CAL_COMPONENT_FIELD_COMPLETE: @@ -1600,12 +1550,7 @@ calendar_model_initialize_value (ETableModel *etm, int col) return g_strdup (model->priv->default_category ? model->priv->default_category : ""); case CAL_COMPONENT_FIELD_CLASSIFICATION: - 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: @@ -1613,6 +1558,10 @@ calendar_model_initialize_value (ETableModel *etm, int col) case CAL_COMPONENT_FIELD_STATUS: return init_string (); + case CAL_COMPONENT_FIELD_COMPLETED: + case CAL_COMPONENT_FIELD_DTEND: + case CAL_COMPONENT_FIELD_DTSTART: + case CAL_COMPONENT_FIELD_DUE: case CAL_COMPONENT_FIELD_HAS_ALARMS: case CAL_COMPONENT_FIELD_ICON: case CAL_COMPONENT_FIELD_COMPLETE: @@ -1622,6 +1571,9 @@ calendar_model_initialize_value (ETableModel *etm, int col) case CAL_COMPONENT_FIELD_COMPONENT: return NULL; + case CAL_COMPONENT_FIELD_PERCENT: + return GINT_TO_POINTER (-1); + default: g_message ("calendar_model_initialize_value(): Requested invalid column %d", col); return NULL; @@ -1657,12 +1609,7 @@ calendar_model_value_is_empty (ETableModel *etm, int col, const void *value) return string_is_empty (value); 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: @@ -1670,6 +1617,15 @@ calendar_model_value_is_empty (ETableModel *etm, int col, const void *value) case CAL_COMPONENT_FIELD_STATUS: return string_is_empty (value); + case CAL_COMPONENT_FIELD_COMPLETED: + case CAL_COMPONENT_FIELD_DTEND: + case CAL_COMPONENT_FIELD_DTSTART: + case CAL_COMPONENT_FIELD_DUE: + return value ? FALSE : TRUE; + + case CAL_COMPONENT_FIELD_PERCENT: + return (GPOINTER_TO_INT (value) < 0) ? TRUE : FALSE; + case CAL_COMPONENT_FIELD_HAS_ALARMS: case CAL_COMPONENT_FIELD_ICON: case CAL_COMPONENT_FIELD_COMPLETE: @@ -1685,6 +1641,43 @@ calendar_model_value_is_empty (ETableModel *etm, int col, const void *value) } } +static char* +date_value_to_string (ETableModel *etm, const void *value) +{ + CalendarModel *model; + CalendarModelPrivate *priv; + ECellDateEditValue *dv = (ECellDateEditValue *) value; + struct icaltimetype tt; + struct tm tmp_tm; + char buffer[64]; + + model = CALENDAR_MODEL (etm); + priv = model->priv; + + if (!dv) + return g_strdup (""); + + /* We currently convert all the dates to the current timezone. */ + tt = dv->tt; + icaltimezone_convert_time (&tt, dv->zone, priv->zone); + + tmp_tm.tm_year = tt.year - 1900; + tmp_tm.tm_mon = tt.month - 1; + tmp_tm.tm_mday = tt.day; + tmp_tm.tm_hour = tt.hour; + tmp_tm.tm_min = tt.minute; + tmp_tm.tm_sec = tt.second; + tmp_tm.tm_isdst = -1; + + tmp_tm.tm_wday = time_day_of_week (tt.day, tt.month - 1, tt.year); + + e_time_format_date_and_time (&tmp_tm, priv->use_24_hour_format, + TRUE, FALSE, + buffer, sizeof (buffer)); + return g_strdup (buffer); +} + + static char * calendar_model_value_to_string (ETableModel *etm, int col, const void *value) { @@ -1693,12 +1686,7 @@ calendar_model_value_to_string (ETableModel *etm, int col, const void *value) switch (col) { case CAL_COMPONENT_FIELD_CATEGORIES: case CAL_COMPONENT_FIELD_CLASSIFICATION: - 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: @@ -1706,10 +1694,16 @@ calendar_model_value_to_string (ETableModel *etm, int col, const void *value) case CAL_COMPONENT_FIELD_STATUS: return e_utf8_from_locale_string (value); + case CAL_COMPONENT_FIELD_COMPLETED: + case CAL_COMPONENT_FIELD_DTEND: + case CAL_COMPONENT_FIELD_DTSTART: + case CAL_COMPONENT_FIELD_DUE: + return date_value_to_string (etm, value); + case CAL_COMPONENT_FIELD_ICON: - if ((int)value == 0) + if (GPOINTER_TO_INT (value) == 0) return e_utf8_from_locale_string (_("Normal")); - else if ((int)value == 1) + else if (GPOINTER_TO_INT (value) == 1) return e_utf8_from_locale_string (_("Recurring")); else return e_utf8_from_locale_string (_("Assigned")); @@ -1726,6 +1720,12 @@ calendar_model_value_to_string (ETableModel *etm, int col, const void *value) case CAL_COMPONENT_FIELD_COMPONENT: return NULL; + case CAL_COMPONENT_FIELD_PERCENT: + if (GPOINTER_TO_INT (value) < 0) + return NULL; + else + return g_strdup_printf ("%i%%", GPOINTER_TO_INT (value)); + default: g_message ("calendar_model_value_as_string(): Requested invalid column %d", col); return NULL; @@ -1762,6 +1762,7 @@ query_obj_updated_cb (CalQuery *query, const char *uid, const char *new_comp_uid; int *new_idx; CalClientGetStatus status; + CalendarModelObjectData new_object_data = { NULL, NULL, NULL, NULL }; model = CALENDAR_MODEL (data); priv = model->priv; @@ -1780,6 +1781,7 @@ query_obj_updated_cb (CalQuery *query, const char *uid, /* The object not in the model originally, so we just append it */ g_array_append_val (priv->objects, new_comp); + g_array_append_val (priv->objects_data, new_object_data); new_idx = g_new (int, 1); *new_idx = priv->objects->len - 1; @@ -1792,6 +1794,8 @@ query_obj_updated_cb (CalQuery *query, const char *uid, /* Insert the new version of the object in its old position */ g_array_insert_val (priv->objects, orig_idx, new_comp); + g_array_insert_val (priv->objects_data, orig_idx, + new_object_data); new_idx = g_new (int, 1); *new_idx = orig_idx; @@ -2053,6 +2057,7 @@ remove_object (CalendarModel *model, const char *uid) CalComponent *orig_comp; int i; int n; + CalendarModelObjectData *object_data; priv = model->priv; @@ -2089,6 +2094,11 @@ remove_object (CalendarModel *model, const char *uid) g_hash_table_remove (priv->uid_index_hash, uid); g_array_remove_index (priv->objects, *idx); + object_data = &g_array_index (priv->objects_data, + CalendarModelObjectData, *idx); + calendar_model_free_object_data (model, object_data); + g_array_remove_index (priv->objects_data, *idx); + gtk_object_unref (GTK_OBJECT (orig_comp)); n = *idx; diff --git a/calendar/gui/comp-editor-factory.c b/calendar/gui/comp-editor-factory.c index 2c74649647..23920c1592 100644 --- a/calendar/gui/comp-editor-factory.c +++ b/calendar/gui/comp-editor-factory.c @@ -419,12 +419,20 @@ resolve_pending_requests (OpenClient *oc) CompEditorFactory *factory; CompEditorFactoryPrivate *priv; GSList *l; + char *location; + icaltimezone *zone; factory = oc->factory; priv = factory->priv; g_assert (oc->pending != NULL); + /* Set the default timezone in the backend. */ + location = calendar_config_get_timezone (); + zone = icaltimezone_get_builtin_timezone (location); + if (zone) + cal_client_set_default_timezone (oc->client, zone); + for (l = oc->pending; l; l = l->next) { Request *request; diff --git a/calendar/gui/comp-util.c b/calendar/gui/comp-util.c index 995e69a270..f9a6c0efec 100644 --- a/calendar/gui/comp-util.c +++ b/calendar/gui/comp-util.c @@ -105,6 +105,14 @@ cal_comp_util_compare_event_timezones (CalComponent *comp, cal_component_get_dtstart (comp, &start_datetime); cal_component_get_dtend (comp, &end_datetime); + /* If either the DTSTART or the DTEND is a DATE value, we return TRUE. + Maybe if one was a DATE-TIME we should check that, but that should + not happen often. */ + if (start_datetime.value->is_date || end_datetime.value->is_date) { + retval = TRUE; + goto out; + } + /* FIXME: DURATION may be used instead. */ if (cal_component_compare_tzid (tzid, start_datetime.tzid) && cal_component_compare_tzid (tzid, end_datetime.tzid)) { diff --git a/calendar/gui/dialogs/comp-editor-util.c b/calendar/gui/dialogs/comp-editor-util.c index 5d10942a5e..77939beee8 100644 --- a/calendar/gui/dialogs/comp-editor-util.c +++ b/calendar/gui/dialogs/comp-editor-util.c @@ -59,6 +59,9 @@ comp_editor_dates (CompEditorPageDates *dates, CalComponent *comp) dates->due = NULL; dates->complete = NULL; + /* Note that the CalComponentDateTime's returned contain allocated + icaltimetype and tzid values, so we just take over ownership of + those. */ cal_component_get_dtstart (comp, &dt); if (dt.value) { dates->start = g_new (CalComponentDateTime, 1); @@ -87,14 +90,22 @@ comp_editor_dates (CompEditorPageDates *dates, CalComponent *comp) void comp_editor_free_dates (CompEditorPageDates *dates) { - if (dates->start) + /* Note that cal_component_free_datetime() only frees the fields in + the struct. It doesn't free the struct itself, so we do that. */ + if (dates->start) { cal_component_free_datetime (dates->start); + g_free (dates->start); + } - if (dates->end) + if (dates->end) { cal_component_free_datetime (dates->end); + g_free (dates->end); + } - if (dates->due) + if (dates->due) { cal_component_free_datetime (dates->due); + g_free (dates->due); + } if (dates->complete) cal_component_free_icaltimetype (dates->complete); diff --git a/calendar/gui/dialogs/event-page.c b/calendar/gui/dialogs/event-page.c index a3c8041933..f4a849db11 100644 --- a/calendar/gui/dialogs/event-page.c +++ b/calendar/gui/dialogs/event-page.c @@ -268,29 +268,15 @@ event_page_focus_main_widget (CompEditorPage *page) gtk_widget_grab_focus (priv->summary); } -/* Checks if the event's time starts and ends at midnight, and sets the - *"all day event" box accordingly. - */ +/* Sets the 'All Day Event' flag to the given value (without emitting signals), + * and shows or hides the widgets as appropriate. */ static void -check_all_day (EventPage *epage) +set_all_day (EventPage *epage, gboolean all_day) { EventPagePrivate *priv; - gboolean all_day = FALSE, start_set, end_set; - gint start_hour, start_minute, end_hour, end_minute; priv = epage->priv; - start_set = e_date_edit_get_time_of_day (E_DATE_EDIT (priv->start_time), - &start_hour, &start_minute); - - end_set = e_date_edit_get_time_of_day (E_DATE_EDIT (priv->end_time), - &end_hour, &end_minute); - - /* all day event checkbox */ - if ((!start_set || (start_hour == 0 && start_minute == 0)) - && (!end_set || (end_hour == 0 && end_minute == 0))) - all_day = TRUE; - gtk_signal_handler_block_by_data (GTK_OBJECT (priv->all_day_event), epage); e_dialog_toggle_set (priv->all_day_event, all_day); @@ -300,8 +286,7 @@ check_all_day (EventPage *epage) e_date_edit_set_show_time (E_DATE_EDIT (priv->start_time), !all_day); e_date_edit_set_show_time (E_DATE_EDIT (priv->end_time), !all_day); - /* We will use DATE values for all-day events eventually, in which - case timezones can't be used. */ + /* DATE values do not have timezones, so we hide the fields. */ if (all_day) { gtk_widget_hide (priv->start_timezone); gtk_widget_hide (priv->end_timezone); @@ -318,6 +303,7 @@ update_time (EventPage *epage, CalComponentDateTime *start_date, CalComponentDat struct icaltimetype *start_tt, *end_tt; icaltimezone *start_zone = NULL, *end_zone = NULL; CalClientGetStatus status; + gboolean all_day_event; priv = epage->priv; @@ -346,15 +332,28 @@ update_time (EventPage *epage, CalComponentDateTime *start_date, CalComponentDat end_date->tzid ? end_date->tzid : ""); } - /* All-day events are inclusive, i.e. if the end date shown is 2nd Feb - then the event includes all of the 2nd Feb. We would normally show - 3rd Feb as the end date, since it really ends at midnight on 3rd, - so we have to subtract a day so we only show the 2nd. */ + /* If both times are DATE values, we set the 'All Day Event' checkbox. + If not, if the end time is a DATE we convert it to the end of the + day. */ + all_day_event = FALSE; start_tt = start_date->value; end_tt = end_date->value; - if (start_tt->hour == 0 && start_tt->minute == 0 && start_tt->second == 0 - && end_tt->hour == 0 && end_tt->minute == 0 && end_tt->second == 0) - icaltime_adjust (end_tt, -1, 0, 0, 0); + if (start_tt->is_date && end_tt->is_date) { + all_day_event = TRUE; + } else if (end_tt->is_date) { + icaltime_adjust (end_tt, 1, 0, 0, 0); + } + + set_all_day (epage, all_day_event); + + /* If it is an all day event, we set both timezones to the current + timezone, so that if the user toggles the 'All Day Event' checkbox + the event uses the current timezone rather than none at all. */ + if (all_day_event) { + char *location = calendar_config_get_timezone (); + start_zone = end_zone = icaltimezone_get_builtin_timezone (location); + } + gtk_signal_handler_block_by_data (GTK_OBJECT (priv->start_time), epage); @@ -377,13 +376,20 @@ update_time (EventPage *epage, CalComponentDateTime *start_date, CalComponentDat /* Set the timezones, and set sync_timezones to TRUE if both timezones are the same. */ + /* FIXME: JPR - why did you add the if check here? It looks like it + won't work for floating times, where start_zone or end_zone may be + NULL. */ +#if 0 if (start_zone && end_zone) { +#endif e_timezone_entry_set_timezone (E_TIMEZONE_ENTRY (priv->start_timezone), start_zone); e_timezone_entry_set_timezone (E_TIMEZONE_ENTRY (priv->end_timezone), end_zone); priv->sync_timezones = (start_zone == end_zone) ? TRUE : FALSE; +#if 0 } +#endif } /* Fills the widgets with default values */ @@ -411,7 +417,7 @@ clear_widgets (EventPage *epage) gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->end_time), epage); - check_all_day (epage); + set_all_day (epage, FALSE); /* Classification */ e_dialog_radio_set (priv->classification_public, @@ -486,13 +492,12 @@ event_page_fill_widgets (CompEditorPage *page, CalComponent *comp) cal_component_get_dtstart (comp, &start_date); cal_component_get_dtend (comp, &end_date); + update_time (epage, &start_date, &end_date); cal_component_free_datetime (&start_date); cal_component_free_datetime (&end_date); - check_all_day (epage); - /* Classification */ cal_component_get_classification (comp, &cl); @@ -549,7 +554,7 @@ event_page_fill_widgets (CompEditorPage *page, CalComponent *comp) comp_editor_contacts_to_widget (priv->contacts_entry, comp); /* We connect the contacts changed signal here, as we have to be a bit - more careful with it due to the use or Corba. The priv->updating + more careful with it due to the use of Corba. The priv->updating flag won't work as we won't get the changed event immediately. FIXME: Unfortunately this doesn't work either. We never get the changed event now. */ @@ -565,13 +570,12 @@ event_page_fill_component (CompEditorPage *page, CalComponent *comp) { EventPage *epage; EventPagePrivate *priv; - CalComponentDateTime date; - struct icaltimetype icaltime; - gboolean all_day_event, date_set; + CalComponentDateTime start_date, end_date; + struct icaltimetype start_tt, end_tt; + gboolean all_day_event, start_date_set, end_date_set; char *cat, *str; CalComponentClassification classif; CalComponentTransparency transparency; - icaltimezone *start_zone, *end_zone; epage = EVENT_PAGE (page); priv = epage->priv; @@ -615,56 +619,50 @@ event_page_fill_component (CompEditorPage *page, CalComponent *comp) /* Dates */ - icaltime = icaltime_null_time (); + start_tt = icaltime_null_time (); + start_date.value = &start_tt; + start_date.tzid = NULL; + + end_tt = icaltime_null_time (); + end_date.value = &end_tt; + end_date.tzid = NULL; - date.value = &icaltime; - date.tzid = NULL; + start_date_set = e_date_edit_get_date (E_DATE_EDIT (priv->start_time), + &start_tt.year, + &start_tt.month, + &start_tt.day); + g_assert (start_date_set); - /* FIXME: We should use is_date at some point. */ + end_date_set = e_date_edit_get_date (E_DATE_EDIT (priv->end_time), + &end_tt.year, + &end_tt.month, + &end_tt.day); + g_assert (end_date_set); - /* If the all_day toggle is set, the end date is inclusive of the - entire day on which it points to. Also, we will use DATE values - eventually, which can't have timezones. So for now we just use - the default timezone. */ + /* If the all_day toggle is set, we use DATE values for DTSTART and + DTEND. If not, we fetch the hour & minute from the widgets. */ all_day_event = e_dialog_toggle_get (priv->all_day_event); if (all_day_event) { - char *location = calendar_config_get_timezone (); - start_zone = end_zone = icaltimezone_get_builtin_timezone (location); + start_tt.is_date = TRUE; + end_tt.is_date = TRUE; } else { + icaltimezone *start_zone, *end_zone; + + e_date_edit_get_time_of_day (E_DATE_EDIT (priv->start_time), + &start_tt.hour, + &start_tt.minute); + e_date_edit_get_time_of_day (E_DATE_EDIT (priv->end_time), + &end_tt.hour, + &end_tt.minute); start_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->start_timezone)); + start_date.tzid = icaltimezone_get_tzid (start_zone); end_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->end_timezone)); + end_date.tzid = icaltimezone_get_tzid (end_zone); } - date_set = e_date_edit_get_date (E_DATE_EDIT (priv->start_time), - &icaltime.year, - &icaltime.month, - &icaltime.day); - e_date_edit_get_time_of_day (E_DATE_EDIT (priv->start_time), - &icaltime.hour, - &icaltime.minute); - g_assert (date_set); - date.tzid = icaltimezone_get_tzid (start_zone); - cal_component_set_dtstart (comp, &date); - - date_set = e_date_edit_get_date (E_DATE_EDIT (priv->end_time), - &icaltime.year, - &icaltime.month, - &icaltime.day); - e_date_edit_get_time_of_day (E_DATE_EDIT (priv->end_time), - &icaltime.hour, - &icaltime.minute); - g_assert (date_set); - - if (all_day_event) { - icaltime.hour = 0; - icaltime.minute = 0; - icaltime.second = 0; - icaltime_adjust (&icaltime, 1, 0, 0, 0); - } - - date.tzid = icaltimezone_get_tzid (end_zone); - cal_component_set_dtend (comp, &date); + cal_component_set_dtstart (comp, &start_date); + cal_component_set_dtend (comp, &end_date); /* Categories */ @@ -802,7 +800,7 @@ notify_dates_changed (EventPage *epage, struct icaltimetype *start_tt, CompEditorPageDates dates; CalComponentDateTime start_dt, end_dt; gboolean all_day_event; - icaltimezone *start_zone, *end_zone; + icaltimezone *start_zone = NULL, *end_zone = NULL; priv = epage->priv; @@ -811,14 +809,7 @@ notify_dates_changed (EventPage *epage, struct icaltimetype *start_tt, start_dt.value = start_tt; end_dt.value = end_tt; - if (all_day_event) { - /* FIXME: When we switch to using DATE values we'll set the - TZIDs to NULL. */ - char *location; - - location = calendar_config_get_timezone (); - start_zone = end_zone = icaltimezone_get_builtin_timezone (location); - } else { + if (!all_day_event) { start_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->start_timezone)); end_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->end_timezone)); } @@ -836,12 +827,48 @@ notify_dates_changed (EventPage *epage, struct icaltimetype *start_tt, } +static gboolean +check_start_before_end (struct icaltimetype *start_tt, + icaltimezone *start_zone, + struct icaltimetype *end_tt, + icaltimezone *end_zone, + gboolean adjust_end_time) +{ + struct icaltimetype end_tt_copy; + int cmp; + + /* Convert the end time to the same timezone as the start time. */ + end_tt_copy = *end_tt; + icaltimezone_convert_time (&end_tt_copy, end_zone, start_zone); + + /* Now check if the start time is after the end time. If it is, + we need to modify one of the times. */ + cmp = icaltime_compare (*start_tt, end_tt_copy); + if (cmp > 0) { + if (adjust_end_time) { + /* Modify the end time, to be the start + 1 hour. */ + *end_tt = *start_tt; + icaltime_adjust (end_tt, 0, 1, 0, 0); + icaltimezone_convert_time (end_tt, start_zone, + end_zone); + } else { + /* Modify the start time, to be the end - 1 hour. */ + *start_tt = *end_tt; + icaltime_adjust (start_tt, 0, -1, 0, 0); + icaltimezone_convert_time (start_tt, end_zone, + start_zone); + } + return TRUE; + } + + return FALSE; +} + + /* * This is called whenever the start or end dates or timezones is changed. - * It makes sure that the start date < end date, and currently sets the - * "all day event" checkbox as appropriate (but won't when we use DATE values). - * It also emits the notification signals so the other event editor pages - * update their labels etc. + * It makes sure that the start date < end date. It also emits the notification + * signals so the other event editor pages update their labels etc. * * If adjust_end_time is TRUE, if the start time < end time it will adjust * the end time. If FALSE it will adjust the start time. If the user sets the @@ -853,9 +880,8 @@ times_updated (EventPage *epage, gboolean adjust_end_time) EventPagePrivate *priv; struct icaltimetype start_tt = icaltime_null_time(); struct icaltimetype end_tt = icaltime_null_time(); - struct icaltimetype end_tt_copy; - int cmp; gboolean date_set, all_day_event; + gboolean set_start_date = FALSE, set_end_date = FALSE; icaltimezone *start_zone, *end_zone; priv = epage->priv; @@ -870,88 +896,69 @@ times_updated (EventPage *epage, gboolean adjust_end_time) &start_tt.year, &start_tt.month, &start_tt.day); - e_date_edit_get_time_of_day (E_DATE_EDIT (priv->start_time), - &start_tt.hour, - &start_tt.minute); g_assert (date_set); date_set = e_date_edit_get_date (E_DATE_EDIT (priv->end_time), &end_tt.year, &end_tt.month, &end_tt.day); - e_date_edit_get_time_of_day (E_DATE_EDIT (priv->end_time), - &end_tt.hour, - &end_tt.minute); g_assert (date_set); if (all_day_event) { - char *location = calendar_config_get_timezone (); - start_zone = end_zone = icaltimezone_get_builtin_timezone (location); + /* All Day Events are simple. We just compare the dates and if + start > end we copy one of them to the other. */ + int cmp = icaltime_compare_date_only (start_tt, end_tt); + if (cmp > 0) { + if (adjust_end_time) { + end_tt = start_tt; + set_end_date = TRUE; + } else { + start_tt = end_tt; + set_start_date = TRUE; + } + } } else { + /* For DATE-TIME events, we have to convert to the same + timezone before comparing. */ + e_date_edit_get_time_of_day (E_DATE_EDIT (priv->start_time), + &start_tt.hour, + &start_tt.minute); + e_date_edit_get_time_of_day (E_DATE_EDIT (priv->end_time), + &end_tt.hour, + &end_tt.minute); + start_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->start_timezone)); end_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->end_timezone)); - } - - - /* Convert the end time to the same timezone as the start time. */ - end_tt_copy = end_tt; - icaltimezone_convert_time (&end_tt_copy, end_zone, start_zone); - /* Now check if the start time is after the end time. If it is, we need - to modify one of the times. */ - cmp = icaltime_compare (start_tt, end_tt_copy); - if (cmp > 0) { - if (adjust_end_time) { - /* Modify the end time, to be the start + 1 hour, - or the same as the start time for all-day events. - We copy the start time, add on one hour, then - convert it to the original end timezone. */ - end_tt = start_tt; - if (!all_day_event) { - icaltime_adjust (&end_tt, 0, 1, 0, 0); - icaltimezone_convert_time (&end_tt, start_zone, - end_zone); - } - - gtk_signal_handler_block_by_data (GTK_OBJECT (priv->end_time), epage); - - e_date_edit_set_date (E_DATE_EDIT (priv->end_time), - end_tt.year, - end_tt.month, - end_tt.day); - e_date_edit_set_time_of_day (E_DATE_EDIT (priv->end_time), - end_tt.hour, - end_tt.minute); - - gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->end_time), epage); - } else { - /* Modify the start time, to be the end - 1 hour, - or the same as the start time for all-day events. - We copy the end time, subtract one hour, then - convert it to the original start timezone. */ - start_tt = end_tt; - if (!all_day_event) { - icaltime_adjust (&start_tt, 0, -1, 0, 0); - icaltimezone_convert_time (&start_tt, end_zone, - start_zone); - } - - gtk_signal_handler_block_by_data (GTK_OBJECT (priv->start_time), epage); + if (check_start_before_end (&start_tt, start_zone, + &end_tt, end_zone, + adjust_end_time)) { + if (adjust_end_time) + set_end_date = TRUE; + else + set_start_date = TRUE; + } + } - e_date_edit_set_date (E_DATE_EDIT (priv->start_time), - start_tt.year, - start_tt.month, - start_tt.day); - e_date_edit_set_time_of_day (E_DATE_EDIT (priv->start_time), - start_tt.hour, - start_tt.minute); - gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->start_time), epage); - } + if (set_start_date) { + gtk_signal_handler_block_by_data (GTK_OBJECT (priv->start_time), epage); + e_date_edit_set_date (E_DATE_EDIT (priv->start_time), + start_tt.year, start_tt.month, + start_tt.day); + e_date_edit_set_time_of_day (E_DATE_EDIT (priv->start_time), + start_tt.hour, start_tt.minute); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->start_time), epage); } - /* Set the "all day event" button as appropriate */ - check_all_day (epage); + if (set_end_date) { + gtk_signal_handler_block_by_data (GTK_OBJECT (priv->end_time), epage); + e_date_edit_set_date (E_DATE_EDIT (priv->end_time), + end_tt.year, end_tt.month, end_tt.day); + e_date_edit_set_time_of_day (E_DATE_EDIT (priv->end_time), + end_tt.hour, end_tt.minute); + gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->end_time), epage); + } /* Notify upstream */ notify_dates_changed (epage, &start_tt, &end_tt); @@ -1040,21 +1047,22 @@ all_day_event_toggled_cb (GtkWidget *toggle, gpointer data) * rounded down to the start of the day on which the event * ends. The event is then taken to be inclusive of the days * between the start and end days. Note that if the event end - * is at midnight, we do not round it down to the previous - * day, since if we do that and the user repeatedly turns the - * all_day toggle on and off, the event keeps shrinking. - * (We'd also need to make sure we didn't adjust the time when - * the radio button is initially set.) + * is at midnight, we round it down to the previous day, so the + * event times stay the same. + * + * When the all_day_toggle is turned off, then if the event is within + * one day, we set the event start to the start of the working day, + * and set the event end to one hour after it. If the event is longer + * than one day, we set the event end to the end of the day it is on, + * so that the actual event times remain the same. * - * When the all_day_toggle is turned off, we set the event start to the - * start of the working day, and if the event end is on or before the - * day of the event start we set it to one hour after the event start. + * This may need tweaking to work well with different timezones used + * in the event start & end. */ all_day = GTK_TOGGLE_BUTTON (toggle)->active; - /* - * Start time. - */ + set_all_day (epage, all_day); + date_set = e_date_edit_get_date (E_DATE_EDIT (priv->start_time), &start_tt.year, &start_tt.month, @@ -1064,21 +1072,6 @@ all_day_event_toggled_cb (GtkWidget *toggle, gpointer data) &start_tt.minute); g_assert (date_set); - if (all_day) { - /* Round down to the start of the day. */ - start_tt.hour = 0; - start_tt.minute = 0; - start_tt.second = 0; - } else { - /* Set to the start of the working day. */ - start_tt.hour = calendar_config_get_day_start_hour (); - start_tt.minute = calendar_config_get_day_start_minute (); - start_tt.second = 0; - } - - /* - * End time. - */ date_set = e_date_edit_get_date (E_DATE_EDIT (priv->end_time), &end_tt.year, &end_tt.month, @@ -1090,24 +1083,44 @@ all_day_event_toggled_cb (GtkWidget *toggle, gpointer data) if (all_day) { /* Round down to the start of the day. */ + start_tt.hour = 0; + start_tt.minute = 0; + start_tt.second = 0; + + /* Round down to the start of the day, or the start of the + previous day if it is midnight. */ + icaltime_adjust (&end_tt, 0, 0, 0, -1); end_tt.hour = 0; end_tt.minute = 0; end_tt.second = 0; } else { - /* If the event end is now on or before the event start day, - * make it end one hour after the start. */ - if (end_tt.year < start_tt.year - || (end_tt.year == start_tt.year - && end_tt.month < start_tt.month) - || (end_tt.year == start_tt.year - && end_tt.month == start_tt.month - && end_tt.day <= start_tt.day)) { - end_tt.year = start_tt.year; - end_tt.month = start_tt.month; - end_tt.day = start_tt.day; - end_tt.hour = start_tt.hour; + icaltimezone *start_zone, *end_zone; + + if (end_tt.year == start_tt.year + && end_tt.month == start_tt.month + && end_tt.day == start_tt.day) { + /* The event is within one day, so we set the event + start to the start of the working day, and the end + to one hour later. */ + start_tt.hour = calendar_config_get_day_start_hour (); + start_tt.minute = calendar_config_get_day_start_minute (); + start_tt.second = 0; + + end_tt = start_tt; icaltime_adjust (&end_tt, 0, 1, 0, 0); + } else { + /* The event is longer than 1 day, so we keep exactly + the same times, just using DATE-TIME rather than + DATE. */ + icaltime_adjust (&end_tt, 1, 0, 0, 0); } + + /* Make sure that end > start using the timezones. */ + start_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->start_timezone)); + end_zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (priv->end_timezone)); + check_start_before_end (&start_tt, start_zone, + &end_tt, end_zone, + TRUE); } gtk_signal_handler_block_by_data (GTK_OBJECT (priv->start_time), @@ -1130,19 +1143,6 @@ all_day_event_toggled_cb (GtkWidget *toggle, gpointer data) gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->end_time), epage); - e_date_edit_set_show_time (E_DATE_EDIT (priv->start_time), !all_day); - e_date_edit_set_show_time (E_DATE_EDIT (priv->end_time), !all_day); - - /* We will use DATE values for all-day events eventually, in which - case timezones can't be used. */ - if (all_day) { - gtk_widget_hide (priv->start_timezone); - gtk_widget_hide (priv->end_timezone); - } else { - gtk_widget_show (priv->start_timezone); - gtk_widget_show (priv->end_timezone); - } - /* Notify upstream */ notify_dates_changed (epage, &start_tt, &end_tt); } diff --git a/calendar/gui/dialogs/task-details-page.c b/calendar/gui/dialogs/task-details-page.c index 1128d54deb..474b80da12 100644 --- a/calendar/gui/dialogs/task-details-page.c +++ b/calendar/gui/dialogs/task-details-page.c @@ -509,7 +509,7 @@ date_changed_cb (EDateEdit *dedit, gpointer data) TaskDetailsPage *tdpage; TaskDetailsPagePrivate *priv; CompEditorPageDates dates = {NULL, NULL, NULL, NULL}; - struct icaltimetype completed_tt; + struct icaltimetype completed_tt = icaltime_null_time (); icalproperty_status status; gboolean date_set; diff --git a/calendar/gui/e-calendar-table.c b/calendar/gui/e-calendar-table.c index 8d8856ed2a..9002811f09 100644 --- a/calendar/gui/e-calendar-table.c +++ b/calendar/gui/e-calendar-table.c @@ -40,7 +40,9 @@ #include #include #include +#include #include "e-calendar-table.h" +#include "e-cell-date-edit-text.h" #include "calendar-config.h" #include "calendar-model.h" #include "dialogs/delete-comp.h" @@ -244,26 +246,46 @@ task_compare_cb (gconstpointer a, gconstpointer b) static gint date_compare_cb (gconstpointer a, gconstpointer b) { - const char *value1 = a, *value2 = b; + ECellDateEditValue *dv1 = (ECellDateEditValue *) a; + ECellDateEditValue *dv2 = (ECellDateEditValue *) b; + struct icaltimetype tt; + + /* First check if either is NULL. NULL dates sort last. */ + if (!dv1 || !dv2) { + if (dv1 == dv2) + return 0; + else if (dv1) + return -1; + else + return 1; + } + + /* Copy the 2nd value and convert it to the same timezone as the + first. */ + tt = dv2->tt; + + icaltimezone_convert_time (&tt, dv2->zone, dv1->zone); - g_print ("In date_compare_cb '%s' '%s'\n", value1, value2); + /* Now we can compare them. */ - return 0; + return icaltime_compare (dv1->tt, tt); } static gint percent_compare_cb (gconstpointer a, gconstpointer b) { - const char *value1 = a, *value2 = b; - - /* FIXME: Currently this isn't working as the ETableSorter caches - all the values in the table before sorting, but our get_value() - function returns a pointer to a static buffer. So all the cached - pointers point to the same buffer. */ + int percent1 = GPOINTER_TO_INT (a); + int percent2 = GPOINTER_TO_INT (b); + int retval; - g_print ("In percent_compare_cb '%s' '%s'\n", value1, value2); + if (percent1 > percent2) + retval = 1; + else if (percent1 < percent2) + retval = -1; + else + retval = 0; - return 0; + return retval; } static gint @@ -324,7 +346,7 @@ e_calendar_table_init (ECalendarTable *cal_table) /* * Date fields. */ - cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); + cell = e_cell_date_edit_text_new (NULL, GTK_JUSTIFY_LEFT); gtk_object_set (GTK_OBJECT (cell), "strikeout_column", CAL_COMPONENT_FIELD_COMPLETE, "bold_column", CAL_COMPONENT_FIELD_OVERDUE, @@ -392,7 +414,7 @@ e_calendar_table_init (ECalendarTable *cal_table) e_table_extras_add_cell (extras, "priority", popup_cell); /* Percent field. */ - cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); + cell = e_cell_percent_new (NULL, GTK_JUSTIFY_LEFT); gtk_object_set (GTK_OBJECT (cell), "strikeout_column", CAL_COMPONENT_FIELD_COMPLETE, "bold_column", CAL_COMPONENT_FIELD_OVERDUE, diff --git a/calendar/gui/e-cell-date-edit-text.c b/calendar/gui/e-cell-date-edit-text.c new file mode 100644 index 0000000000..d0b9d2bf7a --- /dev/null +++ b/calendar/gui/e-cell-date-edit-text.c @@ -0,0 +1,234 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin + * + * Copyright 2001, Ximian, Inc. + * + * 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 + */ + +/* + * ECellDateEditText - a subclass of ECellText used to show and edit the text + * representation of the date, from a CalComponentDateTime* model value. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "e-cell-date-edit-text.h" + + +#define PARENT_TYPE e_cell_text_get_type () + +static ECellTextClass *parent_class; + + +void +e_cell_date_edit_text_set_timezone (ECellDateEditText *ecd, + icaltimezone *zone) +{ + g_return_if_fail (E_IS_CELL_DATE_EDIT_TEXT (ecd)); + + ecd->zone = zone; +} + + +void +e_cell_date_edit_text_set_use_24_hour_format (ECellDateEditText *ecd, + gboolean use_24_hour) +{ + g_return_if_fail (E_IS_CELL_DATE_EDIT_TEXT (ecd)); + + ecd->use_24_hour_format = use_24_hour; +} + + +static char * +ecd_get_text (ECellText *cell, ETableModel *model, int col, int row) +{ + ECellDateEditText *ecd = E_CELL_DATE_EDIT_TEXT (cell); + ECellDateEditValue *dv = e_table_model_value_at (model, col, row); + struct icaltimetype tt; + struct tm tmp_tm; + char buffer[64]; + + if (!dv) + return g_strdup (""); + + /* Note that although the property may be in a different + timezone, we convert it to the current timezone to display + it in the table. If the user actually edits the value, + it will be set to the current timezone. See set_value(). */ + tt = dv->tt; + icaltimezone_convert_time (&tt, dv->zone, ecd->zone); + + tmp_tm.tm_year = tt.year - 1900; + tmp_tm.tm_mon = tt.month - 1; + tmp_tm.tm_mday = tt.day; + tmp_tm.tm_hour = tt.hour; + tmp_tm.tm_min = tt.minute; + tmp_tm.tm_sec = tt.second; + tmp_tm.tm_isdst = -1; + + tmp_tm.tm_wday = time_day_of_week (tt.day, tt.month - 1, tt.year); + + e_time_format_date_and_time (&tmp_tm, ecd->use_24_hour_format, + TRUE, FALSE, + buffer, sizeof (buffer)); + return g_strdup (buffer); +} + + +static void +ecd_free_text (ECellText *cell, char *text) +{ + g_free (text); +} + + +/* FIXME: We need to set the "transient_for" property for the dialog. */ +static void +show_date_warning (ECellDateEditText *ecd) +{ + GtkWidget *dialog; + char buffer[64], message[256], *format; + time_t t; + struct tm *tmp_tm; + + t = time (NULL); + /* We are only using this as an example, so the timezone doesn't + matter. */ + tmp_tm = localtime (&t); + + if (ecd->use_24_hour_format) + /* strftime format of a weekday, a date and a time, 24-hour. */ + format = _("%a %m/%d/%Y %H:%M:%S"); + else + /* strftime format of a weekday, a date and a time, 12-hour. */ + format = _("%a %m/%d/%Y %I:%M:%S %p"); + + strftime (buffer, sizeof (buffer), format, 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); +} + + +static void +ecd_set_value (ECellText *cell, ETableModel *model, int col, int row, + const char *text) +{ + ECellDateEditText *ecd = E_CELL_DATE_EDIT_TEXT (cell); + ETimeParseStatus status; + struct tm tmp_tm; + ECellDateEditValue *value; + + status = e_time_parse_date_and_time (text, &tmp_tm); + + if (status == E_TIME_PARSE_INVALID) { + show_date_warning (ecd); + return; + } else if (status == E_TIME_PARSE_NONE) { + value = NULL; + } else { + ECellDateEditValue dv; + + dv.tt = icaltime_null_time (); + + dv.tt.year = tmp_tm.tm_year + 1900; + dv.tt.month = tmp_tm.tm_mon + 1; + dv.tt.day = tmp_tm.tm_mday; + dv.tt.hour = tmp_tm.tm_hour; + dv.tt.minute = tmp_tm.tm_min; + dv.tt.second = tmp_tm.tm_sec; + + /* FIXME: We assume it is being set to the current timezone. + Is that OK? */ + dv.zone = ecd->zone; + + value = &dv; + } + + e_table_model_set_value_at (model, col, row, value); +} + + +static void +e_cell_date_edit_text_class_init (GtkObjectClass *object_class) +{ + ECellTextClass *ectc = (ECellTextClass *) object_class; + + parent_class = gtk_type_class (PARENT_TYPE); + + ectc->get_text = ecd_get_text; + ectc->free_text = ecd_free_text; + ectc->set_value = ecd_set_value; +} + + +static void +e_cell_date_edit_text_init (GtkObject *object) +{ + ECellDateEditText *ecd = E_CELL_DATE_EDIT_TEXT (object); + + ecd->zone = icaltimezone_get_utc_timezone (); + ecd->use_24_hour_format = TRUE; +} + + +/** + * e_cell_date_edit_text_new: + * + * Creates a new ECell renderer that can be used to render and edit dates that + * that come from the model. The value returned from the model is + * interpreted as being a CalComponentDateTime*. + * + * Returns: an ECell object that can be used to render dates. + */ +ECell * +e_cell_date_edit_text_new (const char *fontname, + GtkJustification justify) +{ + ECellDateEditText *ecd = gtk_type_new (e_cell_date_edit_text_get_type ()); + + e_cell_text_construct (E_CELL_TEXT (ecd), fontname, justify); + + return (ECell *) ecd; +} + + +E_MAKE_TYPE (e_cell_date_edit_text, "ECellDateEditText", ECellDateEditText, + e_cell_date_edit_text_class_init, e_cell_date_edit_text_init, + PARENT_TYPE); diff --git a/calendar/gui/e-cell-date-edit-text.h b/calendar/gui/e-cell-date-edit-text.h new file mode 100644 index 0000000000..527f75b16e --- /dev/null +++ b/calendar/gui/e-cell-date-edit-text.h @@ -0,0 +1,75 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin + * + * Copyright 2001, Ximian, Inc. + * + * 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 + */ + +/* + * ECellDateEditText - a subclass of ECellText used to show and edit the text + * representation of the date, from a CalComponentDateTime* model value. + */ + +#ifndef _E_CELL_DATE_EDIT_TEXT_H_ +#define _E_CELL_DATE_EDIT_TEXT_H_ + +#include +#include + +BEGIN_GNOME_DECLS + +#define E_CELL_DATE_EDIT_TEXT_TYPE (e_cell_date_edit_text_get_type ()) +#define E_CELL_DATE_EDIT_TEXT(o) (GTK_CHECK_CAST ((o), E_CELL_DATE_EDIT_TEXT_TYPE, ECellDateEditText)) +#define E_CELL_DATE_EDIT_TEXT_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_CELL_DATE_EDIT_TEXT_TYPE, ECellDateEditTextClass)) +#define E_IS_CELL_DATE_EDIT_TEXT(o) (GTK_CHECK_TYPE ((o), E_CELL_DATE_EDIT_TEXT_TYPE)) +#define E_IS_CELL_DATE_EDIT_TEXT_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_CELL_DATE_EDIT_TEXT_TYPE)) + +typedef struct _ECellDateEditValue ECellDateEditValue; +struct _ECellDateEditValue { + struct icaltimetype tt; + icaltimezone *zone; +}; + +typedef struct { + ECellText base; + + /* The timezone to display the date in. */ + icaltimezone *zone; + + /* Whether to display in 24-hour format. */ + gboolean use_24_hour_format; +} ECellDateEditText; + +typedef struct { + ECellTextClass parent_class; +} ECellDateEditTextClass; + +GtkType e_cell_date_edit_text_get_type (void); +ECell *e_cell_date_edit_text_new (const char *fontname, + GtkJustification justify); + + +void e_cell_date_edit_text_set_timezone (ECellDateEditText *ecd, + icaltimezone *zone); +void e_cell_date_edit_text_set_use_24_hour_format (ECellDateEditText *ecd, + gboolean use_24_hour); +END_GNOME_DECLS + +#endif /* _E_CELL_DATE_EDIT_TEXT_H_ */ diff --git a/calendar/gui/e-day-view.c b/calendar/gui/e-day-view.c index 7aaee07062..a6bd101d46 100644 --- a/calendar/gui/e-day-view.c +++ b/calendar/gui/e-day-view.c @@ -1555,7 +1555,8 @@ query_obj_updated_cb (CalQuery *query, const char *uid, cal_recur_generate_instances (comp, day_view->lower, day_view->upper, e_day_view_add_event, day_view, - cal_client_resolve_tzid_cb, day_view->client); + cal_client_resolve_tzid_cb, day_view->client, + day_view->zone); gtk_object_unref (GTK_OBJECT (comp)); e_day_view_queue_layout (day_view); @@ -5012,8 +5013,8 @@ e_day_view_key_press (GtkWidget *widget, GdkEventKey *event) guint keyval; gboolean stop_emission; time_t dtstart, dtend; - CalComponentDateTime dt; - struct icaltimetype itt; + CalComponentDateTime start_dt, end_dt; + struct icaltimetype start_tt, end_tt; const char *uid; g_return_val_if_fail (widget != NULL, FALSE); @@ -5108,16 +5109,27 @@ e_day_view_key_press (GtkWidget *widget, GdkEventKey *event) e_day_view_get_selected_time_range (day_view, &dtstart, &dtend); - dt.value = &itt; - dt.tzid = icaltimezone_get_tzid (day_view->zone); + start_tt = icaltime_from_timet_with_zone (dtstart, FALSE, + day_view->zone); + + end_tt = icaltime_from_timet_with_zone (dtend, FALSE, + day_view->zone); - *dt.value = icaltime_from_timet_with_zone (dtstart, FALSE, - day_view->zone); - cal_component_set_dtstart (comp, &dt); + if (day_view->selection_in_top_canvas) { + start_dt.tzid = NULL; + start_tt.is_date = 1; + end_tt.is_date = 1; + /* We have to take a day off the end time as it is a DATE. */ + icaltime_adjust (&end_tt, -1, 0, 0, 0); + } else { + start_dt.tzid = icaltimezone_get_tzid (day_view->zone); + } - *dt.value = icaltime_from_timet_with_zone (dtend, FALSE, - day_view->zone); - cal_component_set_dtend (comp, &dt); + start_dt.value = &start_tt; + end_dt.value = &end_tt; + end_dt.tzid = start_dt.tzid; + cal_component_set_dtstart (comp, &start_dt); + cal_component_set_dtend (comp, &end_dt); cal_component_set_categories (comp, day_view->default_category); @@ -6595,6 +6607,7 @@ e_day_view_on_top_canvas_drag_data_received (GtkWidget *widget, CalComponentDateTime date; struct icaltimetype itt; time_t dt; + gboolean all_day_event; /* Note that we only support DnD within the EDayView at present. */ if ((data->length >= 0) && (data->format == 8) @@ -6606,7 +6619,7 @@ e_day_view_on_top_canvas_drag_data_received (GtkWidget *widget, const char *uid; num_days = 1; start_offset = 0; - end_offset = -1; + end_offset = 0; if (day_view->drag_event_day == E_DAY_VIEW_LONG_EVENT) { event = &g_array_index (day_view->long_events, EDayViewEvent, @@ -6638,28 +6651,47 @@ e_day_view_on_top_canvas_drag_data_received (GtkWidget *widget, if (!event_uid || !uid || strcmp (event_uid, uid)) g_warning ("Unexpected event UID"); - /* We use a temporary shallow of the comp since we - don't want to change the original comp here. + /* We clone the event since we don't want to change + the original comp here. Otherwise we would not detect that the event's time had changed in the "update_event" callback. */ comp = cal_component_clone (event->comp); + if (start_offset == 0 && end_offset == 0) + all_day_event = TRUE; + else + all_day_event = FALSE; + date.value = &itt; - /* FIXME: Should probably keep the timezone of the - original start and end times. */ - date.tzid = icaltimezone_get_tzid (day_view->zone); dt = day_view->day_starts[day] + start_offset * 60; - *date.value = icaltime_from_timet_with_zone (dt, FALSE, - day_view->zone); + itt = icaltime_from_timet_with_zone (dt, FALSE, + day_view->zone); + if (all_day_event) { + itt.is_date = TRUE; + date.tzid = NULL; + } else { + /* FIXME: Should probably keep the timezone of + the original start and end times. */ + date.tzid = icaltimezone_get_tzid (day_view->zone); + } cal_component_set_dtstart (comp, &date); - if (end_offset == -1 || end_offset == 0) + + if (end_offset == 0) dt = day_view->day_starts[day + num_days]; else dt = day_view->day_starts[day + num_days - 1] + end_offset * 60; - *date.value = icaltime_from_timet_with_zone (dt, FALSE, - day_view->zone); + itt = icaltime_from_timet_with_zone (dt, FALSE, + day_view->zone); + if (all_day_event) { + itt.is_date = TRUE; + date.tzid = NULL; + } else { + /* FIXME: Should probably keep the timezone of + the original start and end times. */ + date.tzid = icaltimezone_get_tzid (day_view->zone); + } cal_component_set_dtend (comp, &date); gtk_drag_finish (context, TRUE, TRUE, time); diff --git a/calendar/gui/e-tasks.c b/calendar/gui/e-tasks.c index 02cf6b3f47..d6c0f4a64e 100644 --- a/calendar/gui/e-tasks.c +++ b/calendar/gui/e-tasks.c @@ -328,7 +328,7 @@ e_tasks_open (ETasks *tasks, message = g_strdup_printf (_("Opening tasks at %s"), file); calendar_model_set_status_message ( - e_calendar_table_get_model (priv->tasks_view), + e_calendar_table_get_model (E_CALENDAR_TABLE (priv->tasks_view)), message); g_free (message); @@ -378,16 +378,24 @@ cal_opened_cb (CalClient *client, { ETasks *tasks; ETasksPrivate *priv; + char *location; + icaltimezone *zone; tasks = E_TASKS (data); priv = tasks->priv; calendar_model_set_status_message ( - e_calendar_table_get_model (priv->tasks_view), NULL); + e_calendar_table_get_model (E_CALENDAR_TABLE (priv->tasks_view)), NULL); switch (status) { case CAL_CLIENT_OPEN_SUCCESS: /* Everything is OK */ + + /* Set the client's default timezone, if we have one. */ + location = calendar_config_get_timezone (); + zone = icaltimezone_get_builtin_timezone (location); + if (zone) + cal_client_set_default_timezone (client, zone); return; case CAL_CLIENT_OPEN_ERROR: @@ -632,10 +640,19 @@ e_tasks_update_all_config_settings (void) ETasks *tasks; ETasksPrivate *priv; GList *elem; + char *location; + icaltimezone *zone; + + location = calendar_config_get_timezone (); + zone = icaltimezone_get_builtin_timezone (location); for (elem = all_tasks; elem; elem = elem->next) { tasks = E_TASKS (elem->data); priv = tasks->priv; + calendar_config_configure_e_calendar_table (E_CALENDAR_TABLE (priv->tasks_view)); + + if (zone) + cal_client_set_default_timezone (priv->client, zone); } } diff --git a/calendar/gui/e-week-view.c b/calendar/gui/e-week-view.c index 3fb334905a..0af180da9a 100644 --- a/calendar/gui/e-week-view.c +++ b/calendar/gui/e-week-view.c @@ -1059,7 +1059,8 @@ query_obj_updated_cb (CalQuery *query, const char *uid, week_view->day_starts[0], week_view->day_starts[num_days], e_week_view_add_event, week_view, - cal_client_resolve_tzid_cb, week_view->client); + cal_client_resolve_tzid_cb, week_view->client, + week_view->zone); gtk_object_unref (GTK_OBJECT (comp)); @@ -3232,13 +3233,19 @@ e_week_view_key_press (GtkWidget *widget, GdkEventKey *event) dtend = week_view->day_starts[week_view->selection_end_day + 1]; date.value = &itt; - date.tzid = icaltimezone_get_tzid (week_view->zone); + date.tzid = NULL; + + /* We use DATE values now, so we don't need the timezone. */ + /*date.tzid = icaltimezone_get_tzid (week_view->zone);*/ - *date.value = icaltime_from_timet_with_zone (dtstart, FALSE, + *date.value = icaltime_from_timet_with_zone (dtstart, TRUE, week_view->zone); cal_component_set_dtstart (comp, &date); - *date.value = icaltime_from_timet_with_zone (dtend, FALSE, + + /* We have to take a day off the end time as it is a DATE value. */ + *date.value = icaltime_from_timet_with_zone (dtend, TRUE, week_view->zone); + icaltime_adjust (date.value, -1, 0, 0, 0); cal_component_set_dtend (comp, &date); cal_component_set_categories (comp, week_view->default_category); diff --git a/calendar/gui/gnome-cal.c b/calendar/gui/gnome-cal.c index b1f7c9470c..1790739853 100644 --- a/calendar/gui/gnome-cal.c +++ b/calendar/gui/gnome-cal.c @@ -1345,9 +1345,15 @@ client_cal_opened_cb (CalClient *client, CalClientOpenStatus status, gpointer da switch (status) { case CAL_CLIENT_OPEN_SUCCESS: - if (client == priv->client) + /* If this is the main CalClient, update the Date Navigator. */ + if (client == priv->client) { update_query (gcal); + } + /* Set the client's default timezone, if we have one. */ + if (priv->zone) { + cal_client_set_default_timezone (client, priv->zone); + } break; case CAL_CLIENT_OPEN_ERROR: @@ -1826,6 +1832,16 @@ gnome_calendar_update_config_settings (GnomeCalendar *gcal, location = calendar_config_get_timezone (); priv->zone = icaltimezone_get_builtin_timezone (location); + if (priv->client + && cal_client_get_load_state (priv->client) == CAL_CLIENT_LOAD_LOADED) { + cal_client_set_default_timezone (priv->client, priv->zone); + } + if (priv->task_pad_client + && cal_client_get_load_state (priv->task_pad_client) == CAL_CLIENT_LOAD_LOADED) { + cal_client_set_default_timezone (priv->task_pad_client, + priv->zone); + } + e_day_view_set_timezone (E_DAY_VIEW (priv->day_view), priv->zone); e_day_view_set_timezone (E_DAY_VIEW (priv->work_week_view), priv->zone); e_week_view_set_timezone (E_WEEK_VIEW (priv->week_view), priv->zone); @@ -1996,7 +2012,10 @@ gnome_calendar_new_appointment_for (GnomeCalendar *cal, priv = cal->priv; dt.value = &itt; - dt.tzid = icaltimezone_get_tzid (priv->zone); + if (all_day) + dt.tzid = NULL; + else + dt.tzid = icaltimezone_get_tzid (priv->zone); /* Component type */ @@ -2006,18 +2025,21 @@ gnome_calendar_new_appointment_for (GnomeCalendar *cal, /* DTSTART, DTEND */ itt = icaltime_from_timet_with_zone (dtstart, FALSE, priv->zone); - if (all_day) + if (all_day) { itt.hour = itt.minute = itt.second = 0; + itt.is_date = TRUE; + } cal_component_set_dtstart (comp, &dt); itt = icaltime_from_timet_with_zone (dtend, FALSE, priv->zone); if (all_day) { - /* If we want an all-day event and the end time isn't on a - day boundary, we move it to the end of the day it is in. */ - if (itt.hour != 0 || itt.minute != 0 || itt.second != 0) { - itt.hour = itt.minute = itt.second = 0; - icaltime_adjust (&itt, 1, 0, 0, 0); + /* We round it down to the start of the day, or the start of + the previous day if it is midnight. */ + if (itt.hour == 0 && itt.minute == 0 && itt.second == 0) { + icaltime_adjust (&itt, -1, 0, 0, 0); } + itt.hour = itt.minute = itt.second = 0; + itt.is_date = TRUE; } cal_component_set_dtend (comp, &dt); diff --git a/calendar/gui/tag-calendar.c b/calendar/gui/tag-calendar.c index 08a2636ec0..747e1e1cb1 100644 --- a/calendar/gui/tag-calendar.c +++ b/calendar/gui/tag-calendar.c @@ -218,11 +218,11 @@ tag_calendar_by_comp (ECalendar *ecal, CalComponent *comp, CalClient *client, gb cal_recur_generate_instances (comp, c.start_time, c.end_time, tag_calendar_cb, &c, cal_client_resolve_tzid_cb, - client); + client, c.zone); } else { cal_recur_generate_instances (comp, c.start_time, c.end_time, tag_calendar_cb, &c, resolve_tzid_cb, - client); + client, c.zone); } } diff --git a/calendar/idl/evolution-calendar.idl b/calendar/idl/evolution-calendar.idl index ca63d3c52c..43bb818eff 100644 --- a/calendar/idl/evolution-calendar.idl +++ b/calendar/idl/evolution-calendar.idl @@ -145,6 +145,11 @@ module Calendar { CalObj getObject (in CalObjUID uid) raises (NotFound); + /* Sets the default timezone to be used for resolving DATE + and floating DATE-TIME values. */ + void setDefaultTimezone (in CalTimezoneObjUID tzid) + raises (NotFound); + /* Gets a VTIMEZONE component based on its TZID */ CalTimezoneObj getTimezoneObject (in CalTimezoneObjUID tzid) raises (NotFound); @@ -179,15 +184,16 @@ module Calendar { raises (NotFound, InvalidRange); - /* Updates a component by adding it if it does not exist or by - * changing an existing one. This can be a simple VEVENT/VTODO - * object if no timezone data is needed for the component or - * it hasn't changed. To add or change the associated VTIMEZONE - * data the calobj should be a VCALENDAR component with - * VTIMEZONE and VEVENT/VTODO subcomponents. The VTIMEZONE data - * will be merged into the calendar, possibly by renaming TZIDs - * (though not for builtin VTIMEZONEs), so don't rely on the + /* Adds or updates one or more VEVENT/VTODO/VTIMEZONE + * components. The calobj should be a string representation of + * a complete VCALENDAR object (we also support single + * VEVENT/VTODO strings, but that is deprecated). + * + * The VTIMEZONE data will be merged into the calendar, + * possibly by renaming TZIDs (though not for builtin + * VTIMEZONEs, which have unique TZIDs), so don't rely on the * TZIDs being the same in the new object on the server. + * * The client should probably immediately free its copy of the * object after this call, and call getObject to get the * updated version. diff --git a/calendar/pcs/cal-backend-file.c b/calendar/pcs/cal-backend-file.c index 471dba3fa2..adf323023a 100644 --- a/calendar/pcs/cal-backend-file.c +++ b/calendar/pcs/cal-backend-file.c @@ -79,6 +79,10 @@ struct _CalBackendFilePrivate { /* Idle handler for saving the calendar when it is dirty */ guint idle_id; + + /* The calendar's default timezone, used for resolving DATE and + floating DATE-TIME values. */ + icaltimezone *default_zone; }; @@ -118,6 +122,9 @@ static gboolean cal_backend_file_update_objects (CalBackend *backend, const char static gboolean cal_backend_file_remove_object (CalBackend *backend, const char *uid); static icaltimezone* cal_backend_file_get_timezone (CalBackend *backend, const char *tzid); +static icaltimezone* cal_backend_file_get_default_timezone (CalBackend *backend); +static gboolean cal_backend_file_set_default_timezone (CalBackend *backend, + const char *tzid); static void notify_categories_changed (CalBackendFile *cbfile); @@ -190,6 +197,8 @@ cal_backend_file_class_init (CalBackendFileClass *class) backend_class->remove_object = cal_backend_file_remove_object; backend_class->get_timezone = cal_backend_file_get_timezone; + backend_class->get_default_timezone = cal_backend_file_get_default_timezone; + backend_class->set_default_timezone = cal_backend_file_set_default_timezone; } static Bonobo_ConfigDatabase @@ -235,6 +244,9 @@ cal_backend_file_init (CalBackendFile *cbfile) priv->categories = g_hash_table_new (g_str_hash, g_str_equal); priv->removed_categories = g_hash_table_new (g_str_hash, g_str_equal); + /* The timezone defaults to UTC. */ + priv->default_zone = icaltimezone_get_utc_timezone (); + priv->db = load_db (); gtk_signal_connect (GTK_OBJECT (cbfile), "cal_added", @@ -1015,7 +1027,7 @@ cal_backend_file_get_timezone_object (CalBackend *backend, const char *tzid) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; - icaltimezone *icaltz; + icaltimezone *zone; icalcomponent *icalcomp; char *ical_string; @@ -1027,11 +1039,11 @@ cal_backend_file_get_timezone_object (CalBackend *backend, const char *tzid) g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_assert (priv->comp_uid_hash != NULL); - icaltz = icalcomponent_get_timezone (priv->icalcomp, tzid); - if (!icaltz) + zone = icalcomponent_get_timezone (priv->icalcomp, tzid); + if (!zone) return NULL; - icalcomp = icaltimezone_get_component (icaltz); + icalcomp = icaltimezone_get_component (zone); if (!icalcomp) return NULL; @@ -1131,7 +1143,7 @@ add_instance (CalComponent *comp, time_t start, time_t end, gpointer data) * within a specific time range. */ static void -get_instances_in_range (GHashTable *uid_hash, GList *components, time_t start, time_t end) +get_instances_in_range (GHashTable *uid_hash, GList *components, time_t start, time_t end, icaltimezone *default_zone) { GList *l; @@ -1147,7 +1159,7 @@ get_instances_in_range (GHashTable *uid_hash, GList *components, time_t start, t vcalendar_comp = icalcomponent_get_parent (icalcomp); g_assert (vcalendar_comp != NULL); - cal_recur_generate_instances (comp, start, end, add_instance, uid_hash, resolve_tzid, vcalendar_comp); + cal_recur_generate_instances (comp, start, end, add_instance, uid_hash, resolve_tzid, vcalendar_comp, default_zone); } } @@ -1188,13 +1200,16 @@ cal_backend_file_get_objects_in_range (CalBackend *backend, CalObjType type, uid_hash = g_hash_table_new (g_str_hash, g_str_equal); if (type & CALOBJ_TYPE_EVENT) - get_instances_in_range (uid_hash, priv->events, start, end); + get_instances_in_range (uid_hash, priv->events, start, end, + priv->default_zone); if (type & CALOBJ_TYPE_TODO) - get_instances_in_range (uid_hash, priv->todos, start, end); + get_instances_in_range (uid_hash, priv->todos, start, end, + priv->default_zone); if (type & CALOBJ_TYPE_JOURNAL) - get_instances_in_range (uid_hash, priv->journals, start, end); + get_instances_in_range (uid_hash, priv->journals, start, end, + priv->default_zone); event_list = NULL; g_hash_table_foreach (uid_hash, add_uid_to_list, &event_list); @@ -1232,6 +1247,7 @@ create_user_free_busy (CalBackendFile *cbfile, const char *address, const char * icalcomponent_set_dtend (vfb, icaltime_from_timet (end, 1)); /* add all objects in the given interval */ + uids = cal_backend_get_objects_in_range (CAL_BACKEND (cbfile), CALOBJ_TYPE_ANY, start, end); for (l = uids; l != NULL; l = l->next) { @@ -1466,7 +1482,8 @@ cal_backend_file_get_changes (CalBackend *backend, CalObjType type, const char * /* Get_alarms_in_range handler for the file backend */ static GNOME_Evolution_Calendar_CalComponentAlarmsSeq * -cal_backend_file_get_alarms_in_range (CalBackend *backend, time_t start, time_t end) +cal_backend_file_get_alarms_in_range (CalBackend *backend, + time_t start, time_t end) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; @@ -1491,10 +1508,12 @@ cal_backend_file_get_alarms_in_range (CalBackend *backend, time_t start, time_t n_comp_alarms += cal_util_generate_alarms_for_list (priv->events, start, end, &comp_alarms, resolve_tzid, - priv->icalcomp); + priv->icalcomp, + priv->default_zone); n_comp_alarms += cal_util_generate_alarms_for_list (priv->todos, start, end, &comp_alarms, resolve_tzid, - priv->icalcomp); + priv->icalcomp, + priv->default_zone); seq = GNOME_Evolution_Calendar_CalComponentAlarmsSeq__alloc (); CORBA_sequence_set_release (seq, TRUE); @@ -1525,7 +1544,8 @@ cal_backend_file_get_alarms_in_range (CalBackend *backend, time_t start, time_t /* Get_alarms_for_object handler for the file backend */ static GNOME_Evolution_Calendar_CalComponentAlarms * cal_backend_file_get_alarms_for_object (CalBackend *backend, const char *uid, - time_t start, time_t end, gboolean *object_found) + time_t start, time_t end, + gboolean *object_found) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; @@ -1558,7 +1578,7 @@ cal_backend_file_get_alarms_for_object (CalBackend *backend, const char *uid, corba_alarms->calobj = CORBA_string_dup (comp_str); g_free (comp_str); - alarms = cal_util_generate_alarms_for_comp (comp, start, end, resolve_tzid, priv->icalcomp); + alarms = cal_util_generate_alarms_for_comp (comp, start, end, resolve_tzid, priv->icalcomp, priv->default_zone); if (alarms) { cal_backend_util_fill_alarm_instances_seq (&corba_alarms->alarms, alarms->alarms); cal_component_alarms_free (alarms); @@ -1632,6 +1652,48 @@ clean_removed_categories (CalBackendFile *cbfile) NULL); } + +/* Creates a CalComponent for the given icalcomponent and adds it to our + cache. Note that the icalcomponent is not added to the toplevel + icalcomponent here. That needs to be done elsewhere. It returns the uid + of the added component, or NULL if it failed. */ +static const char* +cal_backend_file_update_object (CalBackendFile *cbfile, + icalcomponent *icalcomp) +{ + CalComponent *old_comp; + CalComponent *comp; + const char *comp_uid; + + /* Create a CalComponent wrapper for the icalcomponent. */ + comp = cal_component_new (); + if (!cal_component_set_icalcomponent (comp, icalcomp)) { + gtk_object_unref (GTK_OBJECT (comp)); + return NULL; + } + + /* Get the UID, and check it isn't empty. */ + cal_component_get_uid (comp, &comp_uid); + if (!comp_uid || !comp_uid[0]) { + gtk_object_unref (GTK_OBJECT (comp)); + return NULL; + } + + /* Remove any old version of the component. */ + old_comp = lookup_component (cbfile, comp_uid); + if (old_comp) + remove_component (cbfile, old_comp); + + /* Now add the component to our local cache, but we pass FALSE as + the last argument, since the libical component is assumed to have + been added already. */ + add_component (cbfile, comp, FALSE); + + return comp_uid; +} + + + /* Update_objects handler for the file backend. */ static gboolean cal_backend_file_update_objects (CalBackend *backend, const char *calobj) @@ -1640,10 +1702,10 @@ cal_backend_file_update_objects (CalBackend *backend, const char *calobj) CalBackendFilePrivate *priv; icalcomponent *toplevel_comp, *icalcomp = NULL; icalcomponent_kind kind; - CalComponent *old_comp; - CalComponent *comp; - const char *comp_uid; int old_n_categories, new_n_categories; + icalcomponent *subcomp; + gboolean retval = TRUE; + GList *comp_uid_list = NULL, *elem; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; @@ -1661,62 +1723,20 @@ cal_backend_file_update_objects (CalBackend *backend, const char *calobj) kind = icalcomponent_isa (toplevel_comp); - if (kind == ICAL_VCALENDAR_COMPONENT) { - int num_found = 0; - icalcomponent_kind child_kind; - icalcomponent *subcomp; - - /* We have a VCALENDAR containing the VEVENT/VTODO and the - related timezone data, so we have to step through it to - find the actual VEVENT/VTODO component. */ - subcomp = icalcomponent_get_first_component (toplevel_comp, - ICAL_ANY_COMPONENT); - while (subcomp) { - child_kind = icalcomponent_isa (subcomp); - if (child_kind == ICAL_VEVENT_COMPONENT - || child_kind == ICAL_VTODO_COMPONENT - || child_kind == ICAL_VJOURNAL_COMPONENT) { - icalcomp = subcomp; - num_found++; - } - subcomp = icalcomponent_get_next_component (toplevel_comp, - ICAL_ANY_COMPONENT); - } - - /* If we didn't find exactly 1 VEVENT/VTODO it is an error. */ - if (num_found != 1) { - icalcomponent_free (toplevel_comp); - return FALSE; - } - - } else if (kind == ICAL_VEVENT_COMPONENT - || kind == ICAL_VTODO_COMPONENT - || kind == ICAL_VJOURNAL_COMPONENT) { + if (kind == ICAL_VEVENT_COMPONENT + || kind == ICAL_VTODO_COMPONENT + || kind == ICAL_VJOURNAL_COMPONENT) { + /* Create a temporary toplevel component and put the VEVENT + or VTODO in it, to simplify the code below. */ icalcomp = toplevel_comp; - } else { + toplevel_comp = cal_util_new_top_level (); + icalcomponent_add_component (toplevel_comp, icalcomp); + } else if (kind != ICAL_VCALENDAR_COMPONENT) { /* We don't support this type of component */ icalcomponent_free (toplevel_comp); return FALSE; } - comp = cal_component_new (); - if (!cal_component_set_icalcomponent (comp, icalcomp)) { - gtk_object_unref (GTK_OBJECT (comp)); - icalcomponent_free (toplevel_comp); - return FALSE; - } - - /* Get the UID, and check it isn't empty. */ - - cal_component_get_uid (comp, &comp_uid); - - if (!comp_uid || !comp_uid[0]) { - gtk_object_unref (GTK_OBJECT (comp)); - if (kind == ICAL_VCALENDAR_COMPONENT) - icalcomponent_free (toplevel_comp); - return FALSE; - } - /* The list of removed categories must be empty because we are about to * start a new scanning process. */ @@ -1724,32 +1744,54 @@ cal_backend_file_update_objects (CalBackend *backend, const char *calobj) old_n_categories = g_hash_table_size (priv->categories); - /* Update the component */ - - old_comp = lookup_component (cbfile, comp_uid); - - if (old_comp) - remove_component (cbfile, old_comp); - - if (kind == ICAL_VCALENDAR_COMPONENT) { - /* If we have a VCALENDAR component with child VTIMEZONEs and - the VEVENT/VTODO, we have to merge it into the existing - VCALENDAR, resolving any conflicting TZIDs. */ - icalcomponent_merge_component (priv->icalcomp, toplevel_comp); - - /* Now we add the component to our local cache, but we pass - FALSE as the last argument, since we have already added - the libical component when merging above.*/ - add_component (cbfile, comp, FALSE); - } else { - add_component (cbfile, comp, TRUE); + /* Step throught the VEVENT/VTODOs being added, create CalComponents + for them, and add them to our cache. */ + subcomp = icalcomponent_get_first_component (toplevel_comp, + ICAL_ANY_COMPONENT); + while (subcomp) { + /* We ignore anything except VEVENT, VTODO and VJOURNAL + components. */ + icalcomponent_kind child_kind = icalcomponent_isa (icalcomp); + if (child_kind == ICAL_VEVENT_COMPONENT + || child_kind == ICAL_VTODO_COMPONENT + || child_kind == ICAL_VJOURNAL_COMPONENT) { + const char *comp_uid; + + comp_uid = cal_backend_file_update_object (cbfile, + icalcomp); + if (comp_uid) { + /* We add a copy of the UID to a list, so we + can emit notification signals later. We do + a g_strdup() in case any of the components + get removed while we are emitting + notification signals. */ + comp_uid_list = g_list_prepend (comp_uid_list, + g_strdup (comp_uid)); + } else { + retval = FALSE; + } + } + subcomp = icalcomponent_get_next_component (toplevel_comp, + ICAL_ANY_COMPONENT); } + /* Merge the iCalendar components with our existing VCALENDAR, + resolving any conflicting TZIDs. */ + icalcomponent_merge_component (priv->icalcomp, toplevel_comp); + new_n_categories = g_hash_table_size (priv->categories); mark_dirty (cbfile); - notify_update (cbfile, comp_uid); + /* Now emit notification signals for all of the added components. + We do this after adding them all to make sure the calendar is in a + stable state before emitting signals. */ + for (elem = comp_uid_list; elem; elem = elem->next) { + char *comp_uid = elem->data; + notify_update (cbfile, comp_uid); + g_free (comp_uid); + } + g_list_free (comp_uid_list); if (old_n_categories != new_n_categories || g_hash_table_size (priv->removed_categories) != 0) { @@ -1757,9 +1799,10 @@ cal_backend_file_update_objects (CalBackend *backend, const char *calobj) notify_categories_changed (cbfile); } - return TRUE; + return retval; } + /* Remove_object handler for the file backend */ static gboolean cal_backend_file_remove_object (CalBackend *backend, const char *uid) @@ -1816,3 +1859,43 @@ cal_backend_file_get_timezone (CalBackend *backend, const char *tzid) return icalcomponent_get_timezone (priv->icalcomp, tzid); } + +static icaltimezone* +cal_backend_file_get_default_timezone (CalBackend *backend) +{ + CalBackendFile *cbfile; + CalBackendFilePrivate *priv; + + cbfile = CAL_BACKEND_FILE (backend); + priv = cbfile->priv; + + g_return_val_if_fail (priv->icalcomp != NULL, NULL); + + return priv->default_zone; +} + + +static gboolean +cal_backend_file_set_default_timezone (CalBackend *backend, + const char *tzid) +{ + CalBackendFile *cbfile; + CalBackendFilePrivate *priv; + icaltimezone *zone; + + cbfile = CAL_BACKEND_FILE (backend); + priv = cbfile->priv; + + g_return_val_if_fail (priv->icalcomp != NULL, FALSE); + + /* Look up the VTIMEZONE in our icalcomponent. */ + zone = icalcomponent_get_timezone (priv->icalcomp, tzid); + if (!zone) + return FALSE; + + /* Set the default timezone to it. */ + priv->default_zone = zone; + + return TRUE; +} + diff --git a/calendar/pcs/cal-backend.c b/calendar/pcs/cal-backend.c index d123f17ec6..149467ecd4 100644 --- a/calendar/pcs/cal-backend.c +++ b/calendar/pcs/cal-backend.c @@ -575,7 +575,8 @@ cal_backend_get_changes (CalBackend *backend, CalObjType type, const char *chang * if @valid_range returns FALSE. **/ GNOME_Evolution_Calendar_CalComponentAlarmsSeq * -cal_backend_get_alarms_in_range (CalBackend *backend, time_t start, time_t end, gboolean *valid_range) +cal_backend_get_alarms_in_range (CalBackend *backend, time_t start, time_t end, + gboolean *valid_range) { g_return_val_if_fail (backend != NULL, NULL); g_return_val_if_fail (IS_CAL_BACKEND (backend), NULL); @@ -764,8 +765,7 @@ cal_backend_obj_removed (CalBackend *backend, const char *uid) * Returns the icaltimezone* corresponding to the TZID, or NULL if the TZID * can't be found. * - * Return value: TRUE on success, FALSE on being passed an UID for an object - * that does not exist in the backend. + * Returns: The icaltimezone* corresponding to the given TZID, or NULL. **/ icaltimezone* cal_backend_get_timezone (CalBackend *backend, const char *tzid) @@ -778,3 +778,46 @@ cal_backend_get_timezone (CalBackend *backend, const char *tzid) return (* CLASS (backend)->get_timezone) (backend, tzid); } + +/** + * cal_backend_get_default_timezone: + * @backend: A calendar backend. + * + * Returns the default timezone for the calendar, which is used to resolve + * DATE and floating DATE-TIME values. + * + * Returns: The default icaltimezone* for the calendar. + **/ +icaltimezone* +cal_backend_get_default_timezone (CalBackend *backend) +{ + g_return_val_if_fail (backend != NULL, NULL); + g_return_val_if_fail (IS_CAL_BACKEND (backend), NULL); + + g_assert (CLASS (backend)->get_default_timezone != NULL); + return (* CLASS (backend)->get_default_timezone) (backend); +} + + +/** + * cal_backend_set_default_timezone: + * @backend: A calendar backend. + * @tzid: The TZID identifying the timezone. + * + * Sets the default timezone for the calendar, which is used to resolve + * DATE and floating DATE-TIME values. + * + * Returns: TRUE if the VTIMEZONE data for the timezone was found, or FALSE if + * not. + **/ +gboolean +cal_backend_set_default_timezone (CalBackend *backend, const char *tzid) +{ + g_return_val_if_fail (backend != NULL, FALSE); + g_return_val_if_fail (IS_CAL_BACKEND (backend), FALSE); + g_return_val_if_fail (tzid != NULL, FALSE); + + g_assert (CLASS (backend)->set_default_timezone != NULL); + return (* CLASS (backend)->set_default_timezone) (backend, tzid); +} + diff --git a/calendar/pcs/cal-backend.h b/calendar/pcs/cal-backend.h index 669de43f8e..e3aa6d55e0 100644 --- a/calendar/pcs/cal-backend.h +++ b/calendar/pcs/cal-backend.h @@ -113,6 +113,8 @@ struct _CalBackendClass { /* Timezone related virtual methods */ icaltimezone *(* get_timezone) (CalBackend *backend, const char *tzid); + icaltimezone *(* get_default_timezone) (CalBackend *backend); + gboolean (* set_default_timezone) (CalBackend *backend, const char *tzid); }; GtkType cal_backend_get_type (void); @@ -135,6 +137,8 @@ char *cal_backend_get_object (CalBackend *backend, const char *uid); CalComponent *cal_backend_get_object_component (CalBackend *backend, const char *uid); +gboolean cal_backend_set_default_timezone (CalBackend *backend, const char *tzid); + char *cal_backend_get_timezone_object (CalBackend *backend, const char *tzid); CalObjType cal_backend_get_type_by_uid (CalBackend *backend, const char *uid); @@ -163,6 +167,7 @@ gboolean cal_backend_update_objects (CalBackend *backend, const char *calobj); gboolean cal_backend_remove_object (CalBackend *backend, const char *uid); icaltimezone* cal_backend_get_timezone (CalBackend *backend, const char *tzid); +icaltimezone* cal_backend_get_default_timezone (CalBackend *backend); void cal_backend_last_client_gone (CalBackend *backend); void cal_backend_opened (CalBackend *backend, CalBackendOpenStatus status); diff --git a/calendar/pcs/cal.c b/calendar/pcs/cal.c index 0d0e8e4a8b..cee41fd6d9 100644 --- a/calendar/pcs/cal.c +++ b/calendar/pcs/cal.c @@ -448,6 +448,26 @@ impl_Cal_get_query (PortableServer_Servant servant, return query_copy; } +/* Cal::set_default_timezone method */ +static void +impl_Cal_set_default_timezone (PortableServer_Servant servant, + const GNOME_Evolution_Calendar_CalTimezoneObjUID tzid, + CORBA_Environment *ev) +{ + Cal *cal; + CalPrivate *priv; + gboolean zone_set; + + cal = CAL (bonobo_object_from_servant (servant)); + priv = cal->priv; + + zone_set = cal_backend_set_default_timezone (priv->backend, tzid); + + if (!zone_set) { + bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_Cal_NotFound); + } +} + /* Cal::get_timezone_object method */ static GNOME_Evolution_Calendar_CalObj impl_Cal_get_timezone_object (PortableServer_Servant servant, @@ -609,6 +629,7 @@ cal_class_init (CalClass *klass) epv->setMode = impl_Cal_set_mode; epv->countObjects = impl_Cal_get_n_objects; epv->getObject = impl_Cal_get_object; + epv->setDefaultTimezone = impl_Cal_set_default_timezone; epv->getTimezoneObject = impl_Cal_get_timezone_object; epv->getUIDs = impl_Cal_get_uids; epv->getChanges = impl_Cal_get_changes; diff --git a/calendar/pcs/query.c b/calendar/pcs/query.c index 0fd8a8a847..e3a77d0dc4 100644 --- a/calendar/pcs/query.c +++ b/calendar/pcs/query.c @@ -50,6 +50,9 @@ struct _QueryPrivate { /* The backend we are monitoring */ CalBackend *backend; + /* The default timezone for the calendar. */ + icaltimezone *default_zone; + /* Listener to which we report changes in the live query */ GNOME_Evolution_Calendar_QueryListener ql; @@ -117,6 +120,7 @@ query_init (Query *query) query->priv = priv; priv->backend = NULL; + priv->default_zone = NULL; priv->ql = CORBA_OBJECT_NIL; priv->sexp = NULL; @@ -526,7 +530,7 @@ func_occur_in_time_range (ESExp *esexp, int argc, ESExpResult **argv, void *data cal_recur_generate_instances (comp, start, end, instance_occur_cb, &occurs, - resolve_tzid, query); + resolve_tzid, query, priv->default_zone); result = e_sexp_result_new (esexp, ESEXP_RES_BOOL); result->value.bool = occurs; @@ -1383,6 +1387,8 @@ query_construct (Query *query, priv->backend = backend; gtk_object_ref (GTK_OBJECT (priv->backend)); + priv->default_zone = cal_backend_get_default_timezone (backend); + gtk_signal_connect (GTK_OBJECT (priv->backend), "obj_updated", GTK_SIGNAL_FUNC (backend_obj_updated_cb), query); -- cgit v1.2.3