From 0ff98664ba2f06f71508288247b3b25ffb7f8f47 Mon Sep 17 00:00:00 2001 From: Damon Chaplin Date: Sat, 30 Sep 2000 15:29:22 +0000 Subject: updated to support RDATE end times or durations. Note that if you have two 2000-09-29 Damon Chaplin * cal-util/cal-recur.c: updated to support RDATE end times or durations. Note that if you have two RDATEs with the same start times, but with different end dates/durations set, the results are unpredictable. So the event editor dialog should check for this. * gui/e-week-view-main-item.c (e_week_view_main_item_draw_day): make strftime() strings translatable, and changed the formats a bit. * NOTE: someone needs to check print.c to make sure strftime strings are OK for i18n. * gui/e-day-view.h: Changed EDayViewDateFormat enum. We now try to include the weekday if possible. Also changed EDayView struct so we store the month & weekdays with the longest names rather than the actual widths. This helps i18n. * gui/e-day-view.c (e_day_view_recalc_cell_sizes): used _() for strftime strings, tried to see if weekday fits, and rearranged a bit to make i18n easier. * gui/e-day-view-top-item.c (e_day_view_top_item_draw): used _() for strftime strings, and updated to use new formats. * gui/calendar-model.c: added use_24_hour_format boolean to CalendarModelPrivate so we can display dates in 12-hour format if requested. This meant adding a CalendarModel argument to a few functions. Also added get/set functions to set use_24_hour_format. I suppose ideally we should have an ECellDate renderer and this option should go there. svn path=/trunk/; revision=5646 --- calendar/ChangeLog | 32 ++++ calendar/cal-util/cal-recur.c | 294 ++++++++++++++++++++++++++++------- calendar/cal-util/cal-recur.h | 60 +------ calendar/cal-util/test-recur.c | 1 + calendar/gui/calendar-model.c | 249 +++++++++++++++++++++-------- calendar/gui/calendar-model.h | 5 + calendar/gui/e-day-view-top-item.c | 18 ++- calendar/gui/e-day-view.c | 138 ++++++++++++---- calendar/gui/e-day-view.h | 17 +- calendar/gui/e-week-view-main-item.c | 33 +++- 10 files changed, 621 insertions(+), 226 deletions(-) diff --git a/calendar/ChangeLog b/calendar/ChangeLog index f8d5f360b7..ef896182e1 100644 --- a/calendar/ChangeLog +++ b/calendar/ChangeLog @@ -1,3 +1,35 @@ +2000-09-29 Damon Chaplin + + * cal-util/cal-recur.c: updated to support RDATE end times or + durations. Note that if you have two RDATEs with the same start times, + but with different end dates/durations set, the results are + unpredictable. So the event editor dialog should check for this. + + * gui/e-week-view-main-item.c (e_week_view_main_item_draw_day): + make strftime() strings translatable, and changed the formats a bit. + + * NOTE: someone needs to check print.c to make sure strftime strings + are OK for i18n. + + * gui/e-day-view.h: Changed EDayViewDateFormat enum. We now try to + include the weekday if possible. Also changed EDayView struct so we + store the month & weekdays with the longest names rather than the + actual widths. This helps i18n. + + * gui/e-day-view.c (e_day_view_recalc_cell_sizes): used _() for + strftime strings, tried to see if weekday fits, and rearranged a + bit to make i18n easier. + + * gui/e-day-view-top-item.c (e_day_view_top_item_draw): used _() for + strftime strings, and updated to use new formats. + + * gui/calendar-model.c: added use_24_hour_format boolean to + CalendarModelPrivate so we can display dates in 12-hour format if + requested. This meant adding a CalendarModel argument to a few + functions. Also added get/set functions to set use_24_hour_format. + I suppose ideally we should have an ECellDate renderer and this option + should go there. + 2000-09-27 Jesse Pavel * gui/event-editor.c: changed a menu entry so that it will invoke diff --git a/calendar/cal-util/cal-recur.c b/calendar/cal-util/cal-recur.c index 38394d2bfb..a9dfc2b250 100644 --- a/calendar/cal-util/cal-recur.c +++ b/calendar/cal-util/cal-recur.c @@ -87,6 +87,54 @@ */ +/* We will use icalrecurrencetype instead of this eventually. */ +typedef struct { + icalrecurrencetype_frequency freq; + + int interval; + + /* Specifies the end of the recurrence. No occurrences are generated + after this date. If it is 0, the event recurs forever. */ + time_t enddate; + + /* WKST property - the week start day: 0 = Monday to 6 = Sunday. */ + gint week_start_day; + + + /* NOTE: I've used GList's here, but it doesn't matter if we use + other data structures like arrays. The code should be easy to + change. So long as it is easy to see if the modifier is set. */ + + /* For BYMONTH modifier. A list of GINT_TO_POINTERs, 0-11. */ + GList *bymonth; + + /* For BYWEEKNO modifier. A list of GINT_TO_POINTERs, [+-]1-53. */ + GList *byweekno; + + /* For BYYEARDAY modifier. A list of GINT_TO_POINTERs, [+-]1-366. */ + GList *byyearday; + + /* For BYMONTHDAY modifier. A list of GINT_TO_POINTERs, [+-]1-31. */ + GList *bymonthday; + + /* For BYDAY modifier. A list of GINT_TO_POINTERs, in pairs. + The first of each pair is the weekday, 0 = Monday to 6 = Sunday. + The second of each pair is the week number [+-]0-53. */ + GList *byday; + + /* For BYHOUR modifier. A list of GINT_TO_POINTERs, 0-23. */ + GList *byhour; + + /* For BYMINUTE modifier. A list of GINT_TO_POINTERs, 0-59. */ + GList *byminute; + + /* For BYSECOND modifier. A list of GINT_TO_POINTERs, 0-60. */ + GList *bysecond; + + /* For BYSETPOS modifier. A list of GINT_TO_POINTERs, +ve or -ve. */ + GList *bysetpos; +} CalRecurrence; + /* This is what we use to pass to all the filter functions. */ typedef struct _RecurData RecurData; struct _RecurData { @@ -117,6 +165,27 @@ struct _RecurData { guint8 seconds[61]; }; +/* This is what we use to represent a date & time. */ +typedef struct _CalObjTime CalObjTime; +struct _CalObjTime { + guint16 year; + guint8 month; /* 0 - 11 */ + guint8 day; /* 1 - 31 */ + guint8 hour; /* 0 - 23 */ + guint8 minute; /* 0 - 59 */ + guint8 second; /* 0 - 59 (maybe 60 for leap second) */ + guint8 is_rdate; /* TRUE if this is an RDATE, which may have an + end or duration set. */ +}; + +/* This is what we use to represent specific recurrence dates. + Note that we assume it starts with a CalObjTime when sorting. */ +typedef struct _CalObjRecurrenceDate CalObjRecurrenceDate; +struct _CalObjRecurrenceDate { + CalObjTime start; + CalComponentPeriod *period; +}; + /* The paramter we use to store the enddate in RRULE and EXRULE properties. */ #define EVOLUTION_END_DATE_PARAMETER "X-EVOLUTION-ENDDATE" @@ -173,6 +242,8 @@ static gint cal_recur_ical_weekday_to_weekday (enum icalrecurrencetype_weekday d static void cal_recur_free (CalRecurrence *r); +static gboolean cal_object_get_rdate_end (CalObjTime *occ, + GArray *rdate_periods); static void cal_object_compute_duration (CalObjTime *start, CalObjTime *end, gint *days, @@ -625,6 +696,7 @@ cal_recur_generate_instances_of_rule (CalComponent *comp, chunk_end.hour = 0; chunk_end.minute = 0; chunk_end.second = 0; + chunk_end.is_rdate = FALSE; } if (!generate_instances_for_year (comp, dtstart_time, @@ -832,15 +904,18 @@ generate_instances_for_year (CalComponent *comp, CalRecurInstanceFn cb, gpointer cb_data) { - GArray *occs, *ex_occs, *tmp_occs; + GArray *occs, *ex_occs, *tmp_occs, *rdate_periods; CalObjTime cotime, *occ; GSList *elem; - gint i, status; + gint i; time_t start_time, end_time; struct tm start_tm, end_tm; + gboolean retval = TRUE; occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); ex_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + rdate_periods = g_array_new (FALSE, FALSE, + sizeof (CalObjRecurrenceDate)); /* Expand each of the recurrence rules. */ for (elem = rrules; elem; elem = elem->next) { @@ -859,20 +934,29 @@ generate_instances_for_year (CalComponent *comp, g_array_free (tmp_occs, TRUE); } - /* Add on specific occurrence dates. */ + /* Add on specific occurrence dates, flag them as RDATEs, and store + a pointer to the period in the rdate_periods array. */ for (elem = rdates; elem; elem = elem->next) { - struct icaltimetype *it; + CalComponentPeriod *p; + CalObjRecurrenceDate rdate; time_t t; - /* FIXME we should only be dealing with dates, not times too. - - No, I think it is supposed to be dates & times - Damon. - I'm not sure what the semantics of just a date would be, - since the event could recur several times each day. */ - it = elem->data; - t = icaltime_as_timet (*it); + p = elem->data; + t = icaltime_as_timet (p->start); cal_object_time_from_time (&cotime, t); + /* Check if the end date or duration is set. If it is we need + to store it so we can get it later. (libical seems to set + second to -1 to denote an unset time. See icalvalue.c) */ + if (p->type != CAL_COMPONENT_PERIOD_DATETIME + || p->u.end.second != -1) { + cotime.is_rdate = TRUE; + + rdate.start = cotime; + rdate.period = p; + g_array_append_val (rdate_periods, rdate); + } + g_array_append_val (occs, cotime); } @@ -923,10 +1007,13 @@ generate_instances_for_year (CalComponent *comp, } - /* Sort both arrays. */ + /* Sort all the arrays. */ cal_obj_sort_occurrences (occs); cal_obj_sort_occurrences (ex_occs); + qsort (rdate_periods->data, rdate_periods->len, + sizeof (CalObjRecurrenceDate), cal_obj_time_compare_func); + /* Create the final array, by removing the exceptions from the occurrences, and removing any duplicates. */ cal_obj_remove_exceptions (occs, ex_occs); @@ -939,35 +1026,102 @@ generate_instances_for_year (CalComponent *comp, check it is within the bounds of the event & interval. */ occ = &g_array_index (occs, CalObjTime, i); - start_tm.tm_year = occ->year - 1900; - start_tm.tm_mon = occ->month; - start_tm.tm_mday = occ->day; - start_tm.tm_hour = occ->hour; - start_tm.tm_min = occ->minute; - start_tm.tm_sec = occ->second; + start_tm.tm_year = occ->year - 1900; + start_tm.tm_mon = occ->month; + start_tm.tm_mday = occ->day; + start_tm.tm_hour = occ->hour; + start_tm.tm_min = occ->minute; + start_tm.tm_sec = occ->second; + start_tm.tm_isdst = -1; start_time = mktime (&start_tm); if (start_time < comp_dtstart || start_time >= interval_end_time) continue; - cal_obj_time_add_days (occ, duration_days); - cal_obj_time_add_seconds (occ, duration_seconds); + if (occ->is_rdate) { + if (!cal_object_get_rdate_end (occ, rdate_periods)) { + cal_obj_time_add_days (occ, duration_days); + cal_obj_time_add_seconds (occ, + duration_seconds); + } + } else { + cal_obj_time_add_days (occ, duration_days); + cal_obj_time_add_seconds (occ, duration_seconds); + } - end_tm.tm_year = occ->year - 1900; - end_tm.tm_mon = occ->month; - end_tm.tm_mday = occ->day; - end_tm.tm_hour = occ->hour; - end_tm.tm_min = occ->minute; - end_tm.tm_sec = occ->second; + end_tm.tm_year = occ->year - 1900; + end_tm.tm_mon = occ->month; + end_tm.tm_mday = occ->day; + end_tm.tm_hour = occ->hour; + end_tm.tm_min = occ->minute; + end_tm.tm_sec = occ->second; + end_tm.tm_isdst = -1; end_time = mktime (&end_tm); if (end_time < interval_start_time) continue; - status = (*cb) (comp, start_time, end_time, cb_data); - if (!status) - return FALSE; + retval = (*cb) (comp, start_time, end_time, cb_data); + if (!retval) + break; + } + + g_array_free (occs, TRUE); + g_array_free (ex_occs, TRUE); + g_array_free (rdate_periods, TRUE); + + return retval; +} + + +/* This looks up the occurrence time in the sorted rdate_periods array, and + tries to compute the end time of the occurrence. If no end time or duration + is set it returns FALSE and the default duration will be used. */ +static gboolean +cal_object_get_rdate_end (CalObjTime *occ, + GArray *rdate_periods) +{ + CalObjRecurrenceDate *rdate; + CalComponentPeriod *p; + gint lower, upper, middle, cmp = 0; + time_t t; + + lower = 0; + upper = rdate_periods->len; + + while (lower < upper) { + middle = (lower + upper) >> 1; + + rdate = &g_array_index (rdate_periods, CalObjRecurrenceDate, + middle); + + cmp = cal_obj_time_compare_func (occ, &rdate->start); + + if (cmp == 0) + break; + else if (cmp < 0) + upper = middle; + else + lower = middle + 1; + } + + /* This should never happen. */ + if (cmp == 0) { + g_warning ("Recurrence date not found"); + return FALSE; + } + + p = rdate->period; + if (p->type == CAL_COMPONENT_PERIOD_DATETIME) { + t = icaltime_as_timet (p->u.end); + cal_object_time_from_time (occ, t); + } else { + cal_obj_time_add_days (occ, p->u.duration.weeks * 7 + + p->u.duration.days); + cal_obj_time_add_hours (occ, p->u.duration.hours); + cal_obj_time_add_minutes (occ, p->u.duration.minutes); + cal_obj_time_add_seconds (occ, p->u.duration.seconds); } return TRUE; @@ -1481,9 +1635,9 @@ static void cal_obj_remove_exceptions (GArray *occs, GArray *ex_occs) { - CalObjTime *occ, *prev_occ = NULL, *ex_occ; + CalObjTime *occ, *prev_occ = NULL, *ex_occ, *last_occ_kept; gint i, j = 0, cmp, ex_index, occs_len, ex_occs_len; - gboolean keep_occ; + gboolean keep_occ, current_time_is_exception = FALSE; if (occs->len == 0 || ex_occs->len == 0) return; @@ -1502,29 +1656,50 @@ cal_obj_remove_exceptions (GArray *occs, if (prev_occ && cal_obj_time_compare_func (occ, prev_occ) == 0) { keep_occ = FALSE; - } else if (ex_occ) { - /* Step through the exceptions until we come to one - that matches or follows this occurrence. */ - while (ex_occ) { - cmp = cal_obj_date_only_compare_func (ex_occ, occ); - if (cmp > 0) - break; - /* Move to the next exception, or set ex_occ - to NULL when we reach the end of array. */ - ex_index++; - if (ex_index < ex_occs_len) - ex_occ = &g_array_index (ex_occs, - CalObjTime, - ex_index); - else - ex_occ = NULL; - - /* If the current exception matches this - occurrence we remove it. */ - if (cmp == 0) { - keep_occ = FALSE; - break; + /* If this occurrence is an RDATE, and the previous + occurrence in the array was kept, set the RDATE flag + of the last one, so we still use the end date + or duration. */ + if (occ->is_rdate && !current_time_is_exception) { + last_occ_kept = &g_array_index (occs, + CalObjTime, + j - 1); + last_occ_kept->is_rdate = TRUE; + } + } else { + /* We've found a new occurrence time. Reset the flag + to indicate that it hasn't been found in the + exceptions array (yet). */ + current_time_is_exception = FALSE; + + if (ex_occ) { + /* Step through the exceptions until we come + to one that matches or follows this + occurrence. */ + while (ex_occ) { + cmp = cal_obj_date_only_compare_func (ex_occ, occ); + if (cmp > 0) + break; + + /* Move to the next exception, or set + ex_occ to NULL when we reach the + end of array. */ + ex_index++; + if (ex_index < ex_occs_len) + ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index); + else + ex_occ = NULL; + + /* If the exception did match this + occurrence we remove it, and set the + flag to indicate that the current + time is an exception. */ + if (cmp == 0) { + current_time_is_exception = TRUE; + keep_occ = FALSE; + break; + } } } } @@ -3183,12 +3358,13 @@ cal_object_time_from_time (CalObjTime *cotime, tmp_time_t = t; tmp_tm = localtime (&tmp_time_t); - cotime->year = tmp_tm->tm_year + 1900; - cotime->month = tmp_tm->tm_mon; - cotime->day = tmp_tm->tm_mday; - cotime->hour = tmp_tm->tm_hour; - cotime->minute = tmp_tm->tm_min; - cotime->second = tmp_tm->tm_sec; + cotime->year = tmp_tm->tm_year + 1900; + cotime->month = tmp_tm->tm_mon; + cotime->day = tmp_tm->tm_mday; + cotime->hour = tmp_tm->tm_hour; + cotime->minute = tmp_tm->tm_min; + cotime->second = tmp_tm->tm_sec; + cotime->is_rdate = FALSE; } diff --git a/calendar/cal-util/cal-recur.h b/calendar/cal-util/cal-recur.h index 5adc235573..bbfaafdde2 100644 --- a/calendar/cal-util/cal-recur.h +++ b/calendar/cal-util/cal-recur.h @@ -30,64 +30,6 @@ BEGIN_GNOME_DECLS -typedef struct { - icalrecurrencetype_frequency freq; - - int interval; - - /* Specifies the end of the recurrence. No occurrences are generated - after this date. If it is 0, the event recurs forever. */ - time_t enddate; - - /* WKST property - the week start day: 0 = Monday to 6 = Sunday. */ - gint week_start_day; - - - /* NOTE: I've used GList's here, but it doesn't matter if we use - other data structures like arrays. The code should be easy to - change. So long as it is easy to see if the modifier is set. */ - - /* For BYMONTH modifier. A list of GINT_TO_POINTERs, 0-11. */ - GList *bymonth; - - /* For BYWEEKNO modifier. A list of GINT_TO_POINTERs, [+-]1-53. */ - GList *byweekno; - - /* For BYYEARDAY modifier. A list of GINT_TO_POINTERs, [+-]1-366. */ - GList *byyearday; - - /* For BYMONTHDAY modifier. A list of GINT_TO_POINTERs, [+-]1-31. */ - GList *bymonthday; - - /* For BYDAY modifier. A list of GINT_TO_POINTERs, in pairs. - The first of each pair is the weekday, 0 = Monday to 6 = Sunday. - The second of each pair is the week number [+-]0-53. */ - GList *byday; - - /* For BYHOUR modifier. A list of GINT_TO_POINTERs, 0-23. */ - GList *byhour; - - /* For BYMINUTE modifier. A list of GINT_TO_POINTERs, 0-59. */ - GList *byminute; - - /* For BYSECOND modifier. A list of GINT_TO_POINTERs, 0-60. */ - GList *bysecond; - - /* For BYSETPOS modifier. A list of GINT_TO_POINTERs, +ve or -ve. */ - GList *bysetpos; -} CalRecurrence; - -/* This is what we use to represent a date & time. */ -typedef struct _CalObjTime CalObjTime; -struct _CalObjTime { - guint16 year; - guint8 month; /* 0 - 11 */ - guint8 day; /* 1 - 31 */ - guint8 hour; /* 0 - 23 */ - guint8 minute; /* 0 - 59 */ - guint8 second; /* 0 - 59 (maybe 60 for leap second) */ -}; - typedef gboolean (* CalRecurInstanceFn) (CalComponent *comp, time_t instance_start, time_t instace_end, @@ -97,7 +39,7 @@ typedef gboolean (* CalRecurInstanceFn) (CalComponent *comp, * Calls the given callback function for each occurrence of the event between * the given start and end times. If end is 0 it continues until the event * ends or forever if the event has an infinite recurrence rule. - * If the callback routine return 0 the occurrence generation stops. + * If the callback routine return FALSE the occurrence generation stops. */ void cal_recur_generate_instances (CalComponent *comp, time_t start, diff --git a/calendar/cal-util/test-recur.c b/calendar/cal-util/test-recur.c index 83602bb0d6..f3b75eb94f 100644 --- a/calendar/cal-util/test-recur.c +++ b/calendar/cal-util/test-recur.c @@ -24,6 +24,7 @@ /* * This tests the recurrence rule expansion functions. + * FIXME: Needs to be updated to just use the public interface. */ #include diff --git a/calendar/gui/calendar-model.c b/calendar/gui/calendar-model.c index 7274c13e18..fa05ba407a 100644 --- a/calendar/gui/calendar-model.c +++ b/calendar/gui/calendar-model.c @@ -52,6 +52,9 @@ struct _CalendarModelPrivate { /* UID -> array index hash */ GHashTable *uid_index_hash; + /* Whether we display dates in 24-hour format. */ + gboolean use_24_hour_format; + /* HACK: so that ETable can do its stupid append_row() thing */ guint appending_row : 1; }; @@ -157,6 +160,7 @@ calendar_model_init (CalendarModel *model) priv->objects = g_array_new (FALSE, TRUE, sizeof (CalComponent *)); priv->uid_index_hash = g_hash_table_new (g_str_hash, g_str_equal); + priv->use_24_hour_format = TRUE; } /* Called from g_hash_table_foreach_remove(), frees a stored UID->index @@ -261,10 +265,11 @@ calendar_model_row_count (ETableModel *etm) /* Creates a nice string representation of a time value */ static char* -get_time_t (time_t *t, gboolean skip_midnight) +get_time_t (CalendarModel *model, time_t *t, gboolean skip_midnight) { - static char buffer[32]; + static char buffer[64]; struct tm *tmp_tm; + char *format; if (*t <= 0) { buffer[0] = '\0'; @@ -273,9 +278,18 @@ get_time_t (time_t *t, gboolean skip_midnight) if (skip_midnight && tmp_tm->tm_hour == 0 && tmp_tm->tm_min == 0 && tmp_tm->tm_sec == 0) - strftime (buffer, 32, "%a %x", tmp_tm); + /* strftime format of a weekday and a date. */ + format = _("%a %m/%d/%Y"); + else if (model->priv->use_24_hour_format) + /* strftime format of a weekday, a date and a time, + in 24-hour format. */ + format = _("%a %m/%d/%Y %H:%M:%S"); else - strftime (buffer, 32, "%a %x %T", tmp_tm); + /* strftime format of a weekday, a date and a time, + in 12-hour format. */ + format = _("%a %m/%d/%Y %I:%M:%S %p"); + + strftime (buffer, sizeof (buffer), format, tmp_tm); } return buffer; @@ -346,7 +360,8 @@ get_classification (CalComponent *comp) /* Builds a string for the COMPLETED property of a calendar component */ static char * -get_completed (CalComponent *comp) +get_completed (CalendarModel *model, + CalComponent *comp) { struct icaltimetype *completed; time_t t; @@ -360,12 +375,12 @@ get_completed (CalComponent *comp) cal_component_free_icaltimetype (completed); } - return get_time_t (&t, FALSE); + return get_time_t (model, &t, FALSE); } /* Builds a string for and frees a date/time value */ static char * -get_and_free_datetime (CalComponentDateTime dt) +get_and_free_datetime (CalendarModel *model, CalComponentDateTime dt) { time_t t; @@ -376,37 +391,37 @@ get_and_free_datetime (CalComponentDateTime dt) cal_component_free_datetime (&dt); - return get_time_t (&t, FALSE); + return get_time_t (model, &t, FALSE); } /* Builds a string for the DTEND property of a calendar component */ static char * -get_dtend (CalComponent *comp) +get_dtend (CalendarModel *model, CalComponent *comp) { CalComponentDateTime dt; cal_component_get_dtend (comp, &dt); - return get_and_free_datetime (dt); + return get_and_free_datetime (model, dt); } /* Builds a string for the DTSTART property of a calendar component */ static char * -get_dtstart (CalComponent *comp) +get_dtstart (CalendarModel *model, CalComponent *comp) { CalComponentDateTime dt; cal_component_get_dtstart (comp, &dt); - return get_and_free_datetime (dt); + return get_and_free_datetime (model, dt); } /* Builds a string for the DUE property of a calendar component */ static char * -get_due (CalComponent *comp) +get_due (CalendarModel *model, CalComponent *comp) { CalComponentDateTime dt; cal_component_get_due (comp, &dt); - return get_and_free_datetime (dt); + return get_and_free_datetime (model, dt); } /* Builds a string for the GEO property of a calendar component */ @@ -546,8 +561,6 @@ get_is_complete (CalComponent *comp) struct icaltimetype *t; gboolean retval; - /* May not be reliable, especially at the moment when - we can't set a Completed Date to None. */ cal_component_get_completed (comp, &t); retval = (t != NULL); @@ -627,16 +640,16 @@ calendar_model_value_at (ETableModel *etm, int col, int row) return get_classification (comp); case CAL_COMPONENT_FIELD_COMPLETED: - return get_completed (comp); + return get_completed (model, comp); case CAL_COMPONENT_FIELD_DTEND: - return get_dtend (comp); + return get_dtend (model, comp); case CAL_COMPONENT_FIELD_DTSTART: - return get_dtstart (comp); + return get_dtstart (model, comp); case CAL_COMPONENT_FIELD_DUE: - return get_due (comp); + return get_due (model, comp); case CAL_COMPONENT_FIELD_GEO: return get_geo (comp); @@ -712,16 +725,24 @@ 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 (void) +show_date_warning (CalendarModel *model) { GtkWidget *dialog; - char buffer[32], message[256]; + char buffer[64], message[256], *format; time_t t; struct tm *tmp_tm; t = time (NULL); tmp_tm = localtime (&t); - strftime (buffer, 32, "%a %x %T", tmp_tm); + + 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"), @@ -801,62 +822,137 @@ set_categories (CalComponent *comp, const char *value) static time_t parse_time (const char *value) { - struct tm tmp_tm; + struct tm discard_tm, date_tm, time_tm; struct tm *today_tm; time_t t; - const char *p; + const char *pos, *parse_end; + char *format[4]; + gboolean parsed_date = FALSE, parsed_time = FALSE; + gint i; if (string_is_empty (value)) return 0; - /* Skip any weekday name. */ - p = strptime (value, "%a", &tmp_tm); - if (!p) - p = value; + pos = value; - /* Try to match the full date & time, or without the seconds, - or just the date, or just the time with/without seconds. - The info pages say we should clear the tm before calling - strptime. It also means that if we don't match a time we - get 00:00:00 which is good. */ - memset (&tmp_tm, 0, sizeof (tmp_tm)); - if (!strptime (value, "%x %T", &tmp_tm)) { - memset (&tmp_tm, 0, sizeof (tmp_tm)); - if (!strptime (value, "%x %H:%M", &tmp_tm)) { - memset (&tmp_tm, 0, sizeof (tmp_tm)); - if (!strptime (value, "%x", &tmp_tm)) { - memset (&tmp_tm, 0, sizeof (tmp_tm)); - if (!strptime (value, "%T", &tmp_tm)) { - memset (&tmp_tm, 0, sizeof (tmp_tm)); - if (!strptime (value, "%H:%M", &tmp_tm)) - return -1; /* Could not parse it */ - } - - /* We only got a time, so we use the - current day. */ - t = time (NULL); - today_tm = localtime (&t); - tmp_tm.tm_mday = today_tm->tm_mday; - tmp_tm.tm_mon = today_tm->tm_mon; - tmp_tm.tm_year = today_tm->tm_year; - } + /* Skip any whitespace. */ + while (isspace (*pos)) + pos++; + + /* Skip any weekday name, full or abbreviated. */ + parse_end = strptime (pos, "%a ", &discard_tm); + if (parse_end) + pos = parse_end; + + memset (&date_tm, 0, sizeof (date_tm)); + /* strptime format for a date. */ + parse_end = strptime (pos, _("%m/%d/%Y"), &date_tm); + if (parse_end) { + pos = parse_end; + parsed_date = TRUE; + } + + /* Skip any whitespace. */ + while (isspace (*pos)) + pos++; + + /* Skip any weekday name, full or abbreviated, again. */ + parse_end = strptime (pos, "%a ", &discard_tm); + if (parse_end) + pos = parse_end; + + + /* strptime format for a time of day, in 12-hour format. + If it is is not appropriate in the locale set to an empty string. */ + format[0] = _("%I:%M:%S %p%n"); + + /* strptime format for a time of day, in 24-hour format. */ + format[1] = _("%H:%M:%S%n"); + + /* strptime format for time of day, without seconds, 12-hour format. + If it is is not appropriate in the locale set to an empty string. */ + format[2] = _("%I:%M %p%n"); + + /* strptime format for time of day, without seconds 24-hour format. */ + format[3] = _("%H:%M%n"); + + for (i = 0; i < sizeof (format) / sizeof (format[0]); i++) { + memset (&time_tm, 0, sizeof (time_tm)); + parse_end = strptime (pos, format[i], &time_tm); + if (parse_end) { + pos = parse_end; + parsed_time = TRUE; + break; + } + } + + /* Skip any whitespace. */ + while (isspace (*pos)) + pos++; + + /* If we haven't already parsed a date, try again. */ + if (!parsed_date) { + memset (&date_tm, 0, sizeof (date_tm)); + /* strptime format for a date. */ + parse_end = strptime (pos, _("%m/%d/%Y"), &date_tm); + if (parse_end) { + pos = parse_end; + parsed_date = TRUE; } } - tmp_tm.tm_isdst = -1; - return mktime (&tmp_tm); + /* If we don't have a date or a time it must be invalid. */ + if (!parsed_date && !parsed_time) + return -1; + + + if (parsed_date) { + /* If a 2-digit year was used we use the current century. */ + if (date_tm.tm_year < 0) { + t = time (NULL); + today_tm = localtime (&t); + + /* This should convert it into a value from 0 to 99. */ + date_tm.tm_year += 1900; + + /* Now add on the century. */ + date_tm.tm_year += today_tm->tm_year + - (today_tm->tm_year % 100); + } + } else { + /* If we didn't get a date we use the current day. */ + t = time (NULL); + today_tm = localtime (&t); + date_tm.tm_mday = today_tm->tm_mday; + date_tm.tm_mon = today_tm->tm_mon; + date_tm.tm_year = today_tm->tm_year; + } + + if (parsed_time) { + date_tm.tm_hour = time_tm.tm_hour; + date_tm.tm_min = time_tm.tm_min; + date_tm.tm_sec = time_tm.tm_sec; + } else { + date_tm.tm_hour = 0; + date_tm.tm_min = 0; + date_tm.tm_sec = 0; + } + + + date_tm.tm_isdst = -1; + return mktime (&date_tm); } /* 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 (CalComponent *comp, const char *value) +set_completed (CalendarModel *model, CalComponent *comp, const char *value) { time_t t; t = parse_time (value); if (t == -1) { - show_date_warning (); + show_date_warning (model); } else if (t == 0) { ensure_task_not_complete (comp); } else { @@ -866,14 +962,14 @@ set_completed (CalComponent *comp, const char *value) /* Sets a CalComponentDateTime value */ static void -set_datetime (CalComponent *comp, const char *value, +set_datetime (CalendarModel *model, CalComponent *comp, const char *value, void (* set_func) (CalComponent *comp, CalComponentDateTime *dt)) { time_t t; t = parse_time (value); if (t == -1) { - show_date_warning (); + show_date_warning (model); return; } else if (t == 0) { (* set_func) (comp, NULL); @@ -1073,21 +1169,21 @@ calendar_model_set_value_at (ETableModel *etm, int col, int row, const void *val /* FIXME: CLASSIFICATION requires an option menu cell renderer */ case CAL_COMPONENT_FIELD_COMPLETED: - set_completed (comp, value); + set_completed (model, comp, value); break; case CAL_COMPONENT_FIELD_DTEND: /* FIXME: Need to reset dtstart if dtend happens before it */ - set_datetime (comp, value, cal_component_set_dtend); + set_datetime (model, comp, value, cal_component_set_dtend); break; case CAL_COMPONENT_FIELD_DTSTART: /* FIXME: Need to reset dtend if dtstart happens after it */ - set_datetime (comp, value, cal_component_set_dtstart); + set_datetime (model, comp, value, cal_component_set_dtstart); break; case CAL_COMPONENT_FIELD_DUE: - set_datetime (comp, value, cal_component_set_due); + set_datetime (model, comp, value, cal_component_set_due); break; case CAL_COMPONENT_FIELD_GEO: @@ -1904,3 +2000,26 @@ ensure_task_not_complete (CalComponent *comp) } +/* Whether we use 24 hour format to display the times. */ +gboolean +calendar_model_get_use_24_hour_format (CalendarModel *model) +{ + g_return_val_if_fail (IS_CALENDAR_MODEL (model), TRUE); + + return model->priv->use_24_hour_format; +} + + +void +calendar_model_set_use_24_hour_format (CalendarModel *model, + gboolean use_24_hour_format) +{ + g_return_if_fail (IS_CALENDAR_MODEL (model)); + + if (model->priv->use_24_hour_format != use_24_hour_format) { + model->priv->use_24_hour_format = use_24_hour_format; + /* Get the views to redraw themselves. */ + e_table_model_changed (E_TABLE_MODEL (model)); + } +} + diff --git a/calendar/gui/calendar-model.h b/calendar/gui/calendar-model.h index 3a1685e90c..ed04706090 100644 --- a/calendar/gui/calendar-model.h +++ b/calendar/gui/calendar-model.h @@ -70,6 +70,11 @@ void calendar_model_delete_task (CalendarModel *model, CalComponent* calendar_model_get_cal_object (CalendarModel *model, gint row); +/* Whether we use 24 hour format to display the times. */ +gboolean calendar_model_get_use_24_hour_format (CalendarModel *model); +void calendar_model_set_use_24_hour_format (CalendarModel *model, + gboolean use_24_hour_format); + END_GNOME_DECLS diff --git a/calendar/gui/e-day-view-top-item.c b/calendar/gui/e-day-view-top-item.c index b6b8a565b5..6b00f34a60 100644 --- a/calendar/gui/e-day-view-top-item.c +++ b/calendar/gui/e-day-view-top-item.c @@ -183,7 +183,7 @@ e_day_view_top_item_draw (GnomeCanvasItem *canvas_item, EDayView *day_view; GtkStyle *style; GdkGC *fg_gc, *bg_gc, *light_gc, *dark_gc; - gchar buffer[128]; + gchar buffer[128], *format; GdkRectangle clip_rect; GdkFont *font; gint canvas_width, canvas_height, left_edge, day, date_width, date_x; @@ -256,12 +256,22 @@ e_day_view_top_item_draw (GnomeCanvasItem *canvas_item, day_start = localtime (&day_view->day_starts[day]); if (day_view->date_format == E_DAY_VIEW_DATE_FULL) - strftime (buffer, 128, "%d %B", day_start); + /* strftime format %A = full weekday name, %d = day of month, + %B = full month name. Don't use any other specifiers. */ + format = _("%A %d %B"); else if (day_view->date_format == E_DAY_VIEW_DATE_ABBREVIATED) - strftime (buffer, 128, "%d %b", day_start); + /* strftime format %a = abbreviated weekday name, %d = day of month, + %b = abbreviated month name. Don't use any other specifiers. */ + format = _("%a %d %b"); + else if (day_view->date_format == E_DAY_VIEW_DATE_NO_WEEKDAY) + /* strftime format %d = day of month, %b = abbreviated month name. + Don't use any other specifiers. */ + format = _("%d %b"); else - strftime (buffer, 128, "%d", day_start); + format = "%d"; + strftime (buffer, sizeof (buffer), format, day_start); + clip_rect.x = day_view->day_offsets[day] - x; clip_rect.y = 2 - y; clip_rect.width = day_view->day_widths[day]; diff --git a/calendar/gui/e-day-view.c b/calendar/gui/e-day-view.c index af402722cd..76925be0bd 100644 --- a/calendar/gui/e-day-view.c +++ b/calendar/gui/e-day-view.c @@ -961,10 +961,12 @@ e_day_view_style_set (GtkWidget *widget, EDayView *day_view; GdkFont *font; gint top_rows, top_canvas_height; - gint month, max_month_width, max_abbr_month_width, number_width; - gint hour, max_large_hour_width, month_width; + gint hour, max_large_hour_width; gint minute, max_minute_width, i; - GDate date; + gint month, day, width; + gint longest_month_width, longest_abbreviated_month_width; + gint longest_weekday_width, longest_abbreviated_weekday_width; + struct tm date_tm; gchar buffer[128]; gint times_width; @@ -975,7 +977,7 @@ e_day_view_style_set (GtkWidget *widget, font = widget->style->font; /* Recalculate the height of each row based on the font size. */ - day_view->row_height = font->ascent + font->descent + E_DAY_VIEW_EVENT_BORDER_HEIGHT * 2 + E_DAY_VIEW_EVENT_Y_PAD * 2 + 2 /* FIXME */; + day_view->row_height = font->ascent + font->descent + E_DAY_VIEW_EVENT_BORDER_HEIGHT + E_DAY_VIEW_EVENT_Y_PAD * 2 + 2 /* FIXME */; day_view->row_height = MAX (day_view->row_height, E_DAY_VIEW_ICON_HEIGHT + E_DAY_VIEW_ICON_Y_PAD + 2); GTK_LAYOUT (day_view->main_canvas)->vadjustment->step_increment = day_view->row_height; @@ -988,27 +990,59 @@ e_day_view_style_set (GtkWidget *widget, top_canvas_height = (top_rows + 2) * day_view->top_row_height; gtk_widget_set_usize (day_view->top_canvas, -1, top_canvas_height); - /* Find the biggest full month name. */ - g_date_clear (&date, 1); - g_date_set_dmy (&date, 20, 1, 2000); - max_month_width = 0; - max_abbr_month_width = 0; - for (month = 1; month <= 12; month++) { - g_date_set_month (&date, month); + /* Find the longest full & abbreviated month names. */ + memset (&date_tm, 0, sizeof (date_tm)); + date_tm.tm_year = 100; + date_tm.tm_mday = 1; + date_tm.tm_isdst = -1; + + longest_month_width = 0; + longest_abbreviated_month_width = 0; + for (month = 0; month < 12; month++) { + date_tm.tm_mon = month; + + strftime (buffer, sizeof (buffer), "%B", &date_tm); + width = gdk_string_width (font, buffer); + if (width > longest_month_width) { + longest_month_width = width; + day_view->longest_month_name = month; + } + + strftime (buffer, sizeof (buffer), "%b", &date_tm); + width = gdk_string_width (font, buffer); + if (width > longest_abbreviated_month_width) { + longest_abbreviated_month_width = width; + day_view->longest_abbreviated_month_name = month; + } + } + + /* Find the longest full & abbreviated weekday names. */ + memset (&date_tm, 0, sizeof (date_tm)); + date_tm.tm_year = 100; + date_tm.tm_mon = 0; + date_tm.tm_isdst = -1; + + longest_weekday_width = 0; + longest_abbreviated_weekday_width = 0; + for (day = 0; day < 7; day++) { + date_tm.tm_mday = 2 + day; + date_tm.tm_wday = day; - g_date_strftime (buffer, 128, "%B", &date); - month_width = gdk_string_width (font, buffer); - max_month_width = MAX (max_month_width, month_width); + strftime (buffer, sizeof (buffer), "%A", &date_tm); + width = gdk_string_width (font, buffer); + if (width > longest_weekday_width) { + longest_weekday_width = width; + day_view->longest_weekday_name = day; + } - g_date_strftime (buffer, 128, "%b", &date); - month_width = gdk_string_width (font, buffer); - max_abbr_month_width = MAX (max_abbr_month_width, month_width); + strftime (buffer, sizeof (buffer), "%a", &date_tm); + width = gdk_string_width (font, buffer); + if (width > longest_abbreviated_weekday_width) { + longest_abbreviated_weekday_width = width; + day_view->longest_abbreviated_weekday_name = day; + } } - number_width = gdk_string_width (font, "31 "); - day_view->long_format_width = number_width + max_month_width - + E_DAY_VIEW_DATE_X_PAD; - day_view->abbreviated_format_width = number_width - + max_abbr_month_width + E_DAY_VIEW_DATE_X_PAD; + /* Calculate the widths of all the time strings necessary. */ day_view->max_small_hour_width = 0; @@ -1090,8 +1124,18 @@ e_day_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) static void e_day_view_recalc_cell_sizes (EDayView *day_view) { + /* An array of dates, one for each month in the year 2000. They must + all be Sundays. */ + static const int days[12] = { 23, 20, 19, 23, 21, 18, + 23, 20, 17, 22, 19, 24 }; gfloat width, offset; - gint day; + gint day, max_width; + struct tm date_tm; + GdkFont *font; + char buffer[128]; + + g_return_if_fail (((GtkWidget*)day_view)->style != NULL); + font = GTK_WIDGET (day_view)->style->font; /* Calculate the column sizes, using floating point so that pixels get divided evenly. Note that we use one more element than the @@ -1109,11 +1153,53 @@ e_day_view_recalc_cell_sizes (EDayView *day_view) day_view->day_widths[day] = day_view->day_offsets[day + 1] - day_view->day_offsets[day]; } - /* Determine which date format to use, based on the column widths. */ - if (day_view->day_widths[0] > day_view->long_format_width) + /* Determine which date format to use, based on the column widths. + We want to check the widths using the longest full or abbreviated + month name and the longest full or abbreviated weekday name, as + appropriate. */ + max_width = day_view->day_widths[0]; + + memset (&date_tm, 0, sizeof (date_tm)); + date_tm.tm_year = 100; + + /* Try "Thursday 21 January". */ + date_tm.tm_mon = day_view->longest_month_name; + date_tm.tm_mday = days[date_tm.tm_mon] + + day_view->longest_weekday_name; + date_tm.tm_wday = day_view->longest_weekday_name; + date_tm.tm_isdst = -1; + /* strftime format %A = full weekday name, %d = day of month, + %B = full month name. Don't use any other specifiers. */ + strftime (buffer, sizeof (buffer), _("%A %d %B"), &date_tm); + if (gdk_string_width (font, buffer) < max_width) { day_view->date_format = E_DAY_VIEW_DATE_FULL; - else if (day_view->day_widths[0] > day_view->abbreviated_format_width) + return; + } + + /* Try "Thu 21 Jan". */ + date_tm.tm_mon = day_view->longest_abbreviated_month_name; + date_tm.tm_mday = days[date_tm.tm_mon] + + day_view->longest_abbreviated_weekday_name; + date_tm.tm_wday = day_view->longest_abbreviated_weekday_name; + date_tm.tm_isdst = -1; + /* strftime format %a = abbreviated weekday name, %d = day of month, + %b = abbreviated month name. Don't use any other specifiers. */ + strftime (buffer, sizeof (buffer), _("%a %d %b"), &date_tm); + if (gdk_string_width (font, buffer) < max_width) { day_view->date_format = E_DAY_VIEW_DATE_ABBREVIATED; + return; + } + + /* Try "23 Jan". */ + date_tm.tm_mon = day_view->longest_abbreviated_month_name; + date_tm.tm_mday = 23; + date_tm.tm_wday = 0; + date_tm.tm_isdst = -1; + /* strftime format %d = day of month, %b = abbreviated month name. + Don't use any other specifiers. */ + strftime (buffer, sizeof (buffer), _("%d %b"), &date_tm); + if (gdk_string_width (font, buffer) < max_width) + day_view->date_format = E_DAY_VIEW_DATE_NO_WEEKDAY; else day_view->date_format = E_DAY_VIEW_DATE_SHORT; } diff --git a/calendar/gui/e-day-view.h b/calendar/gui/e-day-view.h index cfd11b057c..fda44c1289 100644 --- a/calendar/gui/e-day-view.h +++ b/calendar/gui/e-day-view.h @@ -118,13 +118,15 @@ typedef enum } EDayViewBusyType; /* This is used to specify the format used when displaying the dates. - The full format is like 'September 12'. The abbreviated format is like - 'Sep 12'. The short format is like '12'. The actual format used is - determined in style_set(), once we know the font being used. */ + The full format is like 'Thursday 12 September'. The abbreviated format is + like 'Thu 12 Sep'. The no weekday format is like '12 Sep'. The short format + is like '12'. The actual format used is determined in + e_day_view_recalc_cell_sizes(), once we know the font being used. */ typedef enum { E_DAY_VIEW_DATE_FULL, E_DAY_VIEW_DATE_ABBREVIATED, + E_DAY_VIEW_DATE_NO_WEEKDAY, E_DAY_VIEW_DATE_SHORT } EDayViewDateFormat; @@ -301,9 +303,12 @@ struct _EDayView /* This specifies how we are displaying the dates at the top. */ EDayViewDateFormat date_format; - /* These are the maximum widths of the different types of dates. */ - gint long_format_width; - gint abbreviated_format_width; + /* These are the longest month & weekday names in the current font. + Months are 0 to 11. Weekdays are 0 (Sun) to 6 (Sat). */ + gint longest_month_name; + gint longest_abbreviated_month_name; + gint longest_weekday_name; + gint longest_abbreviated_weekday_name; /* The large font use to display the hours. I don't think we need a fontset since we only display numbers. */ diff --git a/calendar/gui/e-week-view-main-item.c b/calendar/gui/e-week-view-main-item.c index d30404305d..10990324ff 100644 --- a/calendar/gui/e-week-view-main-item.c +++ b/calendar/gui/e-week-view-main-item.c @@ -315,22 +315,41 @@ e_week_view_main_item_draw_day (EWeekViewMainItem *wvmitem, max_width = width - 4; format_string = NULL; if (show_day_name) { - if (week_view->max_abbr_day_width + - week_view->digit_width * 2 + week_view->space_width * 2 + if (week_view->max_day_width + week_view->digit_width * 2 + + week_view->space_width * 2 + week_view->month_widths[month - 1] < max_width) - format_string = "%a %d %B"; + /* strftime format %A = full weekday name, %d = day of + month, %B = full month name. You can change the + order but don't change the specifiers or add + anything. */ + format_string = _("%A %d %B"); + else if (week_view->max_abbr_day_width + + week_view->digit_width * 2 + + week_view->space_width * 2 + + week_view->abbr_month_widths[month - 1] < max_width) + /* strftime format %a = abbreviated weekday name, + %d = day of month, %b = abbreviated month name. + You can change the order but don't change the + specifiers or add anything. */ + format_string = _("%a %d %b"); } if (!format_string && show_month_name) { if (week_view->digit_width * 2 + week_view->space_width + week_view->month_widths[month - 1] < max_width) - format_string = "%d %B"; + /* strftime format %d = day of month, %B = full + month name. You can change the order but don't + change the specifiers or add anything. */ + format_string = _("%d %B"); else if (week_view->digit_width * 2 + week_view->space_width + week_view->abbr_month_widths[month - 1] < max_width) - format_string = "%d %b"; + /* strftime format %d = day of month, %b = abbreviated + month name. You can change the order but don't + change the specifiers or add anything. */ + format_string = _("%d %b"); } - g_date_strftime (buffer, 128, format_string ? format_string : "%d", - date); + g_date_strftime (buffer, sizeof (buffer), + format_string ? format_string : "%d", date); date_width = gdk_string_width (font, buffer); date_x = x + width - date_width - E_WEEK_VIEW_DATE_R_PAD; date_x = MAX (date_x, x + 1); -- cgit v1.2.3