From 4eb47f9a4696877218bf05d53c52f829d2afcdd5 Mon Sep 17 00:00:00 2001 From: Damon Chaplin Date: Sat, 20 May 2000 10:38:19 +0000 Subject: new files to implement iCalendar recurrence rules. These are only part 2000-05-20 Damon Chaplin * cal-util/cal-recur.[hc]: new files to implement iCalendar recurrence rules. These are only part finished, but people may like to check that the architecture seems OK. 2000-05-17 Damon Chaplin * gui/e-day-view.c (e_day_view_on_delete_occurrence): * gui/e-week-view.c (e_week_view_on_delete_occurrence): use a copy of the iCalObject so we detect the change in the "update_event" callback. Maybe we should just update the view ourselves and then we wouldn't need to detect any change in the callback. * cal-util/calobj.c (ical_object_reset_recurrence): new function to get rid of any recurrence rules. Used when we 'unrecur' an event. * gui/e-day-view.c (e_day_view_key_press): don't add a new event if it won't fit, or we end up adding a new event for each key press. (e_day_view_update_event_label): don't update it if it doesn't have an EText item (i.e. it isn't visible). * gui/e-day-view-time-item.c: allow selection of times using this column. svn path=/trunk/; revision=3144 --- calendar/ChangeLog | 25 + calendar/cal-util/Makefile.am | 2 + calendar/cal-util/cal-recur.c | 1131 +++++++++++++++++++++++++++++++++++ calendar/cal-util/cal-recur.h | 102 ++++ calendar/cal-util/calobj.c | 8 + calendar/cal-util/calobj.h | 4 + calendar/gui/e-day-view-main-item.c | 10 +- calendar/gui/e-day-view-time-item.c | 126 +++- calendar/gui/e-day-view-time-item.h | 3 + calendar/gui/e-day-view-top-item.c | 6 +- calendar/gui/e-day-view.c | 266 +++++--- calendar/gui/e-day-view.h | 19 +- calendar/gui/e-week-view.c | 46 +- 13 files changed, 1623 insertions(+), 125 deletions(-) create mode 100644 calendar/cal-util/cal-recur.c create mode 100644 calendar/cal-util/cal-recur.h (limited to 'calendar') diff --git a/calendar/ChangeLog b/calendar/ChangeLog index c4af6b969c..804605bd84 100644 --- a/calendar/ChangeLog +++ b/calendar/ChangeLog @@ -1,3 +1,28 @@ +2000-05-20 Damon Chaplin + + * cal-util/cal-recur.[hc]: new files to implement iCalendar recurrence + rules. These are only part finished, but people may like to check that + the architecture seems OK. + +2000-05-17 Damon Chaplin + + * gui/e-day-view.c (e_day_view_on_delete_occurrence): + * gui/e-week-view.c (e_week_view_on_delete_occurrence): use a copy of + the iCalObject so we detect the change in the "update_event" callback. + Maybe we should just update the view ourselves and then we wouldn't + need to detect any change in the callback. + + * cal-util/calobj.c (ical_object_reset_recurrence): new function to + get rid of any recurrence rules. Used when we 'unrecur' an event. + + * gui/e-day-view.c (e_day_view_key_press): don't add a new event if it + won't fit, or we end up adding a new event for each key press. + (e_day_view_update_event_label): don't update it if it doesn't have + an EText item (i.e. it isn't visible). + + * gui/e-day-view-time-item.c: allow selection of times using this + column. + 2000-05-19 Federico Mena Quintero * cal-util/timeutil.c (time_add_minutes): Fixed warning message. diff --git a/calendar/cal-util/Makefile.am b/calendar/cal-util/Makefile.am index 250cbf95c9..f110e96394 100644 --- a/calendar/cal-util/Makefile.am +++ b/calendar/cal-util/Makefile.am @@ -11,6 +11,7 @@ INCLUDES = \ lib_LTLIBRARIES = libcal-util.la libcal_util_la_SOURCES = \ + cal-recur.c \ cal-util.c \ calobj.c \ timeutil.c @@ -18,6 +19,7 @@ libcal_util_la_SOURCES = \ libcal_utilincludedir = $(includedir)/evolution/cal-util libcal_utilinclude_HEADERS = \ + cal-recur.h \ cal-util.h \ calobj.h \ timeutil.h diff --git a/calendar/cal-util/cal-recur.c b/calendar/cal-util/cal-recur.c new file mode 100644 index 0000000000..711ef5e64d --- /dev/null +++ b/calendar/cal-util/cal-recur.c @@ -0,0 +1,1131 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Evolution calendar recurrence rule functions + * + * Copyright (C) 2000 Helix Code, Inc. + * + * Author: Damon Chaplin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + + +/* + * Introduction to The Recurrence Generation Functions: + * + * Note: This is pretty complicated. See the iCalendar spec (RFC 2445) for + * the specification of the recurrence rules and lots of examples + * (sections 4.3.10 & 4.8.5). We also want to support the older + * vCalendar spec, though this should be easy since it is basically a + * subset of iCalendar. + * + * o An iCalendar event can have any number of recurrence rules specifying + * occurrences of the event, as well as dates & times of specific + * occurrences. It can also have any number of recurrence rules and + * specific dates & times specifying exceptions to the occurrences. + * So we first merge all the occurrences generated, eliminating any + * duplicates, then we generate all the exceptions and remove these to + * form the final set of occurrences. + * + * o There are 7 frequencies of occurrences: YEARLY, MONTHLY, WEEKLY, DAILY, + * HOURLY, MINUTELY & SECONDLY. The 'interval' property specifies the + * multiples of the frequency between each 'set' of occurrences. So for + * a YEARLY frequency with an interval of 3, we generate a set of occurrences + * for every 3rd year. We use complete years here - any generated + * occurrences that occur before the event's start (or after its end) + * are just discarded. + * + * o There are 8 frequency modifiers: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, + * BYDAY, BYHOUR & BYSECOND. These can either add extra occurrences or + * filter out occurrences. For example 'FREQ=YEARLY;BYMONTH=1,2' produces + * 2 occurrences for each year rather than the default 1. And + * 'FREQ=DAILY; BYMONTH=1' filters out all occurrences except those in Jan. + * If the modifier works on periods which are less than the recurrence + * frequency, then extra occurrences are added, else occurrences are + * filtered. So we have 2 functions for each modifier - one to expand events + * and the other to filter. We use a table of functions for each frequency + * which points to the appropriate function to use for each modifier. + * + * o Any number of frequency modifiers can be used in a recurrence rule + * (though BYWEEKNO can only be used in a YEARLY rule). They are applied in + * the order given above. + * + * o After the set of occurrences for the frequency interval are generated, + * the BYSETPOS property is used to select which of the occurrences are + * finally output. If BYSETPOS is not specified then all the occurrences are + * output. + */ + + +#define CAL_OBJ_NUM_FILTERS 8 + +typedef gboolean (*CalObjFindStartFn) (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +typedef gboolean (*CalObjFindNextFn) (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); +typedef GArray* (*CalObjFilterFn) (Recurrence *recur, + GArray *occs); + +typedef struct _CalObjRecurVTable CalObjRecurVTable; +struct _CalObjRecurVTable { + CalObjFindStartFn find_start_position; + CalObjFindNextFn find_next_position; + CalObjFilterFn filters[CAL_OBJ_NUM_FILTERS]; +}; + + +static CalObjRecurVTable* cal_obj_get_vtable (Recurrence *recur); +static void cal_obj_sort_occurrences (GArray *occs); +static gint cal_obj_time_compare_func (const void *arg1, + const void *arg2); +static void cal_obj_remove_duplicates (GArray *occs); +static GArray* cal_obj_bysetpos_filter (GArray *occs); + + +static gboolean cal_obj_yearly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +static gboolean cal_obj_yearly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); + +static gboolean cal_obj_monthly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +static gboolean cal_obj_monthly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); + +static gboolean cal_obj_weekly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +static gboolean cal_obj_weekly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); + +static gboolean cal_obj_daily_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +static gboolean cal_obj_daily_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); + +static gboolean cal_obj_hourly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +static gboolean cal_obj_hourly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); + +static gboolean cal_obj_minutely_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +static gboolean cal_obj_minutely_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); + +static gboolean cal_obj_secondly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime); +static gboolean cal_obj_secondly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end); + +static GArray* cal_obj_bymonth_expand (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_bymonth_filter (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byweekno_expand (Recurrence *recur, + GArray *occs); +#if 0 +/* This isn't used at present. */ +static GArray* cal_obj_byweekno_filter (Recurrence *recur, + GArray *occs); +#endif +static GArray* cal_obj_byyearday_expand (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byyearday_filter (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_bymonthday_expand (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_bymonthday_filter (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byday_expand (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byday_filter (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byhour_expand (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byhour_filter (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byminute_expand (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_byminute_filter (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_bysecond_expand (Recurrence *recur, + GArray *occs); +static GArray* cal_obj_bysecond_filter (Recurrence *recur, + GArray *occs); + +static void cal_obj_time_add_days (CalObjTime *cotime, + gint days); + + +CalObjRecurVTable cal_obj_yearly_vtable = { + cal_obj_yearly_find_start_position, + cal_obj_yearly_find_next_position, + { + cal_obj_bymonth_expand, + cal_obj_byweekno_expand, + cal_obj_byyearday_expand, + cal_obj_bymonthday_expand, + cal_obj_byday_expand, + cal_obj_byhour_expand, + cal_obj_byminute_expand, + cal_obj_bysecond_expand + }, +}; + +CalObjRecurVTable cal_obj_monthly_vtable = { + cal_obj_monthly_find_start_position, + cal_obj_monthly_find_next_position, + { + cal_obj_bymonth_filter, + NULL, + cal_obj_byyearday_filter, + cal_obj_bymonthday_expand, + cal_obj_byday_expand, + cal_obj_byhour_expand, + cal_obj_byminute_expand, + cal_obj_bysecond_expand + }, +}; + +CalObjRecurVTable cal_obj_weekly_vtable = { + cal_obj_weekly_find_start_position, + cal_obj_weekly_find_next_position, + { + cal_obj_bymonth_filter, + NULL, + cal_obj_byyearday_filter, + cal_obj_bymonthday_filter, + cal_obj_byday_expand, + cal_obj_byhour_expand, + cal_obj_byminute_expand, + cal_obj_bysecond_expand + }, +}; + +CalObjRecurVTable cal_obj_daily_vtable = { + cal_obj_daily_find_start_position, + cal_obj_daily_find_next_position, + { + cal_obj_bymonth_filter, + NULL, + cal_obj_byyearday_filter, + cal_obj_bymonthday_filter, + cal_obj_byday_filter, + cal_obj_byhour_expand, + cal_obj_byminute_expand, + cal_obj_bysecond_expand + }, +}; + +CalObjRecurVTable cal_obj_hourly_vtable = { + cal_obj_hourly_find_start_position, + cal_obj_hourly_find_next_position, + { + cal_obj_bymonth_filter, + NULL, + cal_obj_byyearday_filter, + cal_obj_bymonthday_filter, + cal_obj_byday_filter, + cal_obj_byhour_filter, + cal_obj_byminute_expand, + cal_obj_bysecond_expand + }, +}; + +CalObjRecurVTable cal_obj_minutely_vtable = { + cal_obj_minutely_find_start_position, + cal_obj_minutely_find_next_position, + { + cal_obj_bymonth_filter, + NULL, + cal_obj_byyearday_filter, + cal_obj_bymonthday_filter, + cal_obj_byday_filter, + cal_obj_byhour_filter, + cal_obj_byminute_filter, + cal_obj_bysecond_expand + }, +}; + +CalObjRecurVTable cal_obj_secondly_vtable = { + cal_obj_secondly_find_start_position, + cal_obj_secondly_find_next_position, + { + cal_obj_bymonth_filter, + NULL, + cal_obj_byyearday_filter, + cal_obj_bymonthday_filter, + cal_obj_byday_filter, + cal_obj_byhour_filter, + cal_obj_byminute_filter, + cal_obj_bysecond_filter + }, +}; + + + + +/* Returns an unsorted array of time_t's resulting from expanding the + recurrence within the given interval. Each iCalendar event can have any + number of recurrence rules specifying occurrences of the event, as well as + any number of recurrence rules specifying exceptions. */ +GArray* +cal_obj_expand_recurrence (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end) +{ + CalObjRecurVTable *vtable; + CalObjTime occ; + GArray *all_occs, *occs; + gint filter; + + vtable = cal_obj_get_vtable (recur); + + /* This is the resulting array of CalObjTime elements. */ + all_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + /* Get the first period based on the frequency and the interval that + intersects the interval between start and end. */ + if ((*vtable->find_start_position) (event_start, event_end, recur, + interval_start, interval_end, + &occ)) + return all_occs; + + /* Loop until the event ends or we go past the end of the required + interval. */ + for (;;) { + + /* We start with just the one time in the set. */ + occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + g_array_append_val (occs, occ); + + /* Generate the set of occurrences for this period. */ + for (filter = 0; filter < CAL_OBJ_NUM_FILTERS; filter++) { + if (vtable->filters[filter]) + occs = (*vtable->filters[filter]) (recur, + occs); + } + + /* Sort the occurrences and remove duplicates. */ + cal_obj_sort_occurrences (occs); + cal_obj_remove_duplicates (occs); + + /* Apply the BYSETPOS property. */ + occs = cal_obj_bysetpos_filter (occs); + + /* Add the occurrences onto the main array. */ + g_array_append_vals (all_occs, occs->data, occs->len); + + /* Skip to the next period, or exit the loop if finished. */ + if ((*vtable->find_next_position) (&occ, event_end, recur, + interval_end)) + break; + } + + return all_occs; +} + + +/* Returns the function table corresponding to the recurrence frequency. */ +static CalObjRecurVTable* +cal_obj_get_vtable (Recurrence *recur) +{ + switch (recur->type) { + case RECUR_YEARLY: + return &cal_obj_yearly_vtable; + case RECUR_MONTHLY: + return &cal_obj_monthly_vtable; + case RECUR_WEEKLY: + return &cal_obj_weekly_vtable; + case RECUR_DAILY: + return &cal_obj_daily_vtable; + case RECUR_HOURLY: + return &cal_obj_hourly_vtable; + case RECUR_MINUTELY: + return &cal_obj_minutely_vtable; + case RECUR_SECONDLY: + return &cal_obj_secondly_vtable; + } + return NULL; +} + + +static void +cal_obj_sort_occurrences (GArray *occs) +{ + qsort (occs->data, occs->len, sizeof (CalObjTime), + cal_obj_time_compare_func); +} + + +static gint +cal_obj_time_compare_func (const void *arg1, + const void *arg2) +{ + CalObjTime *cotime1, *cotime2; + + cotime1 = (CalObjTime*) arg1; + cotime2 = (CalObjTime*) arg2; + + if (cotime1->year < cotime2->year) + return -1; + if (cotime1->year > cotime2->year) + return 1; + + if (cotime1->month < cotime2->month) + return -1; + if (cotime1->month > cotime2->month) + return 1; + + if (cotime1->day < cotime2->day) + return -1; + if (cotime1->day > cotime2->day) + return 1; + + if (cotime1->hour < cotime2->hour) + return -1; + if (cotime1->hour > cotime2->hour) + return 1; + + if (cotime1->minute < cotime2->minute) + return -1; + if (cotime1->minute > cotime2->minute) + return 1; + + if (cotime1->second < cotime2->second) + return -1; + if (cotime1->second > cotime2->second) + return 1; + + return 0; +} + + +static void +cal_obj_remove_duplicates (GArray *occs) +{ + CalObjTime *occ, *prev_occ = NULL; + gint len, i, j = 0; + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + + if (!prev_occ + || cal_obj_time_compare_func (occ, prev_occ) != 0) { + if (i != j) + g_array_index (occs, CalObjTime, j) + = g_array_index (occs, CalObjTime, i); + j++; + } + + prev_occ = occ; + } + + g_array_set_size (occs, j); +} + + +static GArray* +cal_obj_bysetpos_filter (GArray *occs) +{ + + return occs; +} + + + + +static gboolean +cal_obj_yearly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_yearly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end) +{ + /* NOTE: The day may now be invalid, e.g. 29th Feb. + Make sure we remove these eventually. */ + cotime->year += recur->interval; + + if (cotime->year > event_end->year + || cotime->year > interval_end->year) + return TRUE; + + return FALSE; +} + + + +static gboolean +cal_obj_monthly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_monthly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end) +{ + cotime->month += recur->interval; + cotime->year += cotime->month / 12; + cotime->month %= 12; + + if (cotime->year > event_end->year + || cotime->year > interval_end->year + || (cotime->year == event_end->year + && cotime->month > event_end->month) + || (cotime->year == interval_end->year + && cotime->month > interval_end->month)) + return TRUE; + + return FALSE; +} + + + +static gboolean +cal_obj_weekly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_weekly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end) +{ + cal_obj_time_add_days (cotime, recur->interval); + + + + return FALSE; +} + + +static gboolean +cal_obj_daily_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_daily_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end) +{ + + cal_obj_time_add_days (cotime, recur->interval); + + + return FALSE; +} + + +static gboolean +cal_obj_hourly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_hourly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_minutely_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_minutely_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_secondly_find_start_position (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end, + CalObjTime *cotime) +{ + + + return FALSE; +} + + +static gboolean +cal_obj_secondly_find_next_position (CalObjTime *cotime, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_end) +{ + + + return FALSE; +} + + + + + +/* If the BYMONTH rule is specified it expands each occurrence in occs, by + using each of the months in the bymonth list. */ +static GArray* +cal_obj_bymonth_expand (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + GList *elem; + gint len, i; + + /* If BYMONTH has not been specified, or the array is empty, just + return the array. */ + if (!recur->bymonth || occs->len == 0) + return occs; + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + + elem = recur->bymonth; + while (elem) { + /* NOTE: The day may now be invalid, e.g. 31st Feb. + Make sure we remove these eventually. */ + occ->month = GPOINTER_TO_INT (elem->data); + g_array_append_vals (new_occs, occ, 1); + elem = elem->next; + } + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + +/* If the BYMONTH rule is specified it filters out all occurrences in occs + which do not match one of the months in the bymonth list. */ +static GArray* +cal_obj_bymonth_filter (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + guint8 months[12]; + gint mon, len, i; + GList *elem; + + /* If BYMONTH has not been specified, or the array is empty, just + return the array. */ + elem = recur->bymonth; + if (!elem || occs->len == 0) + return occs; + + /* Create an array of months from bymonths for fast lookup. */ + memset (&months, 0, sizeof (months)); + while (elem) { + mon = GPOINTER_TO_INT (elem->data); + months[mon] = 1; + elem = elem->next; + } + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + if (months[occ->month]) + g_array_append_vals (new_occs, occ, 1); + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + + +static GArray* +cal_obj_byweekno_expand (Recurrence *recur, + GArray *occs) +{ + + return occs; +} + + +#if 0 +/* This isn't used at present. */ +static GArray* +cal_obj_byweekno_filter (Recurrence *recur, + GArray *occs) +{ + + return occs; +} +#endif + + +static GArray* +cal_obj_byyearday_expand (Recurrence *recur, + GArray *occs) +{ + + return occs; +} + + +static GArray* +cal_obj_byyearday_filter (Recurrence *recur, + GArray *occs) +{ + + return occs; +} + + + +static GArray* +cal_obj_bymonthday_expand (Recurrence *recur, + GArray *occs) +{ + + return occs; +} + + +static GArray* +cal_obj_bymonthday_filter (Recurrence *recur, + GArray *occs) +{ + + return occs; +} + + + +static GArray* +cal_obj_byday_expand (Recurrence *recur, + GArray *occs) +{ + + return occs; +} + + +static GArray* +cal_obj_byday_filter (Recurrence *recur, + GArray *occs) +{ + + return occs; +} + + + +/* If the BYHOUR rule is specified it expands each occurrence in occs, by + using each of the hours in the byhour list. */ +static GArray* +cal_obj_byhour_expand (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + GList *elem; + gint len, i; + + /* If BYHOUR has not been specified, or the array is empty, just + return the array. */ + if (!recur->byhour || occs->len == 0) + return occs; + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + + elem = recur->byhour; + while (elem) { + occ->hour = GPOINTER_TO_INT (elem->data); + g_array_append_vals (new_occs, occ, 1); + elem = elem->next; + } + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + +/* If the BYHOUR rule is specified it filters out all occurrences in occs + which do not match one of the hours in the byhour list. */ +static GArray* +cal_obj_byhour_filter (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + guint8 hours[24]; + gint hour, len, i; + GList *elem; + + /* If BYHOURUTE has not been specified, or the array is empty, just + return the array. */ + elem = recur->byhour; + if (!elem || occs->len == 0) + return occs; + + /* Create an array of hours from byhour for fast lookup. */ + memset (&hours, 0, sizeof (hours)); + while (elem) { + hour = GPOINTER_TO_INT (elem->data); + hours[hour] = 1; + elem = elem->next; + } + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + if (hours[occ->hour]) + g_array_append_vals (new_occs, occ, 1); + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + + +/* If the BYMINUTE rule is specified it expands each occurrence in occs, by + using each of the minutes in the byminute list. */ +static GArray* +cal_obj_byminute_expand (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + GList *elem; + gint len, i; + + /* If BYMINUTE has not been specified, or the array is empty, just + return the array. */ + if (!recur->byminute || occs->len == 0) + return occs; + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + + elem = recur->byminute; + while (elem) { + occ->minute = GPOINTER_TO_INT (elem->data); + g_array_append_vals (new_occs, occ, 1); + elem = elem->next; + } + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + +/* If the BYMINUTE rule is specified it filters out all occurrences in occs + which do not match one of the minutes in the byminute list. */ +static GArray* +cal_obj_byminute_filter (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + guint8 minutes[60]; + gint min, len, i; + GList *elem; + + /* If BYMINUTE has not been specified, or the array is empty, just + return the array. */ + elem = recur->byminute; + if (!elem || occs->len == 0) + return occs; + + /* Create an array of minutes from byminutes for fast lookup. */ + memset (&minutes, 0, sizeof (minutes)); + while (elem) { + min = GPOINTER_TO_INT (elem->data); + minutes[min] = 1; + elem = elem->next; + } + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + if (minutes[occ->minute]) + g_array_append_vals (new_occs, occ, 1); + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + + +/* If the BYSECOND rule is specified it expands each occurrence in occs, by + using each of the seconds in the bysecond list. */ +static GArray* +cal_obj_bysecond_expand (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + GList *elem; + gint len, i; + + /* If BYSECOND has not been specified, or the array is empty, just + return the array. */ + if (!recur->bysecond || occs->len == 0) + return occs; + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + + elem = recur->bysecond; + while (elem) { + occ->second = GPOINTER_TO_INT (elem->data); + g_array_append_vals (new_occs, occ, 1); + elem = elem->next; + } + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + +/* If the BYSECOND rule is specified it filters out all occurrences in occs + which do not match one of the seconds in the bysecond list. */ +static GArray* +cal_obj_bysecond_filter (Recurrence *recur, + GArray *occs) +{ + GArray *new_occs; + CalObjTime *occ; + guint8 seconds[61]; + gint sec, len, i; + GList *elem; + + /* If BYSECOND has not been specified, or the array is empty, just + return the array. */ + elem = recur->bysecond; + if (!elem || occs->len == 0) + return occs; + + /* Create an array of seconds from byseconds for fast lookup. */ + memset (&seconds, 0, sizeof (seconds)); + while (elem) { + sec = GPOINTER_TO_INT (elem->data); + seconds[sec] = 1; + elem = elem->next; + } + + new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime)); + + len = occs->len; + for (i = 0; i < len; i++) { + occ = &g_array_index (occs, CalObjTime, i); + if (seconds[occ->second]) + g_array_append_vals (new_occs, occ, 1); + } + + g_array_free (occs, TRUE); + + return new_occs; +} + + + +static void +cal_obj_time_add_days (CalObjTime *cotime, + gint days) +{ + guint day, days_in_month; + + /* We use a guint to avoid overflow on the guint8. */ + day = (guint) cotime->day; + day += days; + + for (;;) { + days_in_month = time_days_in_month (cotime->year, + cotime->month); + if (day <= days_in_month) + break; + + cotime->month++; + if (cotime->month >= 12) { + cotime->year++; + cotime->month = 0; + } + + day -= days_in_month; + } + + cotime->day = (guint8) day; +} diff --git a/calendar/cal-util/cal-recur.h b/calendar/cal-util/cal-recur.h new file mode 100644 index 0000000000..340b897417 --- /dev/null +++ b/calendar/cal-util/cal-recur.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Evolution calendar recurrence rule functions + * + * Copyright (C) 2000 Helix Code, Inc. + * + * Author: Damon Chaplin + * + * 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. + */ + +#ifndef CAL_RECURL_H +#define CAL_RECUR_H + +#include +#include + +BEGIN_GNOME_DECLS + + +/* FIXME: I've put modified versions of RecurType and Recurrence here, since + the ones in calobj.h don't support all of iCalendar. Hopefully Seth will + update those soon and these can be removed. */ + +enum RecurType { + RECUR_YEARLY, + RECUR_MONTHLY, + RECUR_WEEKLY, + RECUR_DAILY, + RECUR_HOURLY, + RECUR_MINUTELY, + RECUR_SECONDLY, +}; + +typedef struct { + enum RecurType type; + + int interval; + + int weekday; + + int month_pos; + + int month_day; + + + /* For BYMONTH modifier. A list of GINT_TO_POINTERs, 0-11. */ + GList *bymonth; + + + /* 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; + +} Recurrence; + + + +/* 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) */ +}; + + + +/* Returns an unsorted array of time_t's resulting from expanding the + recurrence within the given interval. Each iCalendar event can have any + number of recurrence rules specifying occurrences of the event, as well as + any number of recurrence rules specifying exceptions. */ +GArray* +cal_obj_expand_recurrence (CalObjTime *event_start, + CalObjTime *event_end, + Recurrence *recur, + CalObjTime *interval_start, + CalObjTime *interval_end); + +END_GNOME_DECLS + +#endif diff --git a/calendar/cal-util/calobj.c b/calendar/cal-util/calobj.c index 4a3afd81ec..d2c47fbc65 100644 --- a/calendar/cal-util/calobj.c +++ b/calendar/cal-util/calobj.c @@ -157,6 +157,14 @@ ical_object_destroy (iCalObject *ico) g_free (ico); } +/* This resets any recurrence rules of the iCalObject. */ +void +ical_object_reset_recurrence (iCalObject *ico) +{ + free_if_defined (ico->recur); + lfree_if_defined (ico->exdate); +} + static GList * set_list (char *str) { diff --git a/calendar/cal-util/calobj.h b/calendar/cal-util/calobj.h index e44b2ad7b1..cf5483a228 100644 --- a/calendar/cal-util/calobj.h +++ b/calendar/cal-util/calobj.h @@ -277,6 +277,10 @@ gboolean ical_object_compare_dates (iCalObject *ico1, iCalObject *ico2); /* Generates a new uid for a calendar object. Should be g_free'd eventually. */ char *ical_gen_uid (void); +/* This resets any recurrence rules of the iCalObject. */ +void ical_object_reset_recurrence (iCalObject *ico); + + END_GNOME_DECLS #endif diff --git a/calendar/gui/e-day-view-main-item.c b/calendar/gui/e-day-view-main-item.c index 629d2bd42c..b3a531f62e 100644 --- a/calendar/gui/e-day-view-main-item.c +++ b/calendar/gui/e-day-view-main-item.c @@ -229,17 +229,17 @@ e_day_view_main_item_draw (GnomeCanvasItem *canvas_item, GdkDrawable *drawable, /* Paint the selection background. */ if (GTK_WIDGET_HAS_FOCUS (day_view) - && day_view->selection_start_col != -1 + && day_view->selection_start_day != -1 && !day_view->selection_in_top_canvas) { - for (day = day_view->selection_start_col; - day <= day_view->selection_end_col; + for (day = day_view->selection_start_day; + day <= day_view->selection_end_day; day++) { - if (day == day_view->selection_start_col + if (day == day_view->selection_start_day && day_view->selection_start_row != -1) start_row = day_view->selection_start_row; else start_row = 0; - if (day == day_view->selection_end_col + if (day == day_view->selection_end_day && day_view->selection_end_row != -1) end_row = day_view->selection_end_row; else diff --git a/calendar/gui/e-day-view-time-item.c b/calendar/gui/e-day-view-time-item.c index a3b1c35dd3..c2dcf55727 100644 --- a/calendar/gui/e-day-view-time-item.c +++ b/calendar/gui/e-day-view-time-item.c @@ -72,6 +72,14 @@ static void e_day_view_time_item_show_popup_menu (EDayViewTimeItem *dvtmitem, GdkEvent *event); static void e_day_view_time_item_on_set_divisions (GtkWidget *item, EDayViewTimeItem *dvtmitem); +static void e_day_view_time_item_on_button_press (EDayViewTimeItem *dvtmitem, + GdkEvent *event); +static void e_day_view_time_item_on_button_release (EDayViewTimeItem *dvtmitem, + GdkEvent *event); +static void e_day_view_time_item_on_motion_notify (EDayViewTimeItem *dvtmitem, + GdkEvent *event); +static gint e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *dvtmitem, + gint y); static GnomeCanvasItemClass *parent_class; @@ -134,9 +142,9 @@ e_day_view_time_item_class_init (EDayViewTimeItemClass *class) static void -e_day_view_time_item_init (EDayViewTimeItem *dvtmitm) +e_day_view_time_item_init (EDayViewTimeItem *dvtmitem) { - + dvtmitem->dragging_selection = FALSE; } @@ -332,14 +340,22 @@ e_day_view_time_item_event (GnomeCanvasItem *item, switch (event->type) { case GDK_BUTTON_PRESS: - if (event->button.button == 3) { + if (event->button.button == 1) { + e_day_view_time_item_on_button_press (dvtmitem, event); + } else if (event->button.button == 3) { e_day_view_time_item_show_popup_menu (dvtmitem, event); return TRUE; } break; case GDK_BUTTON_RELEASE: + if (event->button.button == 1) + e_day_view_time_item_on_button_release (dvtmitem, + event); + break; case GDK_MOTION_NOTIFY: + e_day_view_time_item_on_motion_notify (dvtmitem, event); + break; default: break; @@ -361,8 +377,6 @@ e_day_view_time_item_show_popup_menu (EDayViewTimeItem *dvtmitem, GSList *group = NULL; gint current_divisions, i; - g_print ("In e_day_view_time_item_show_popup_menu\n"); - day_view = dvtmitem->day_view; g_return_if_fail (day_view != NULL); @@ -413,3 +427,105 @@ e_day_view_time_item_on_set_divisions (GtkWidget *item, "divisions")); e_day_view_set_mins_per_row (day_view, divisions); } + + +static void +e_day_view_time_item_on_button_press (EDayViewTimeItem *dvtmitem, + GdkEvent *event) +{ + EDayView *day_view; + GnomeCanvas *canvas; + gint row; + + day_view = dvtmitem->day_view; + g_return_if_fail (day_view != NULL); + + canvas = GNOME_CANVAS_ITEM (dvtmitem)->canvas; + + row = e_day_view_time_item_convert_position_to_row (dvtmitem, + event->button.y); + + if (row == -1) + return; + + if (!GTK_WIDGET_HAS_FOCUS (day_view)) + gtk_widget_grab_focus (GTK_WIDGET (day_view)); + + if (gdk_pointer_grab (GTK_LAYOUT (canvas)->bin_window, FALSE, + GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK, + FALSE, NULL, event->button.time) == 0) { + e_day_view_start_selection (day_view, -1, row); + dvtmitem->dragging_selection = TRUE; + } +} + + +static void +e_day_view_time_item_on_button_release (EDayViewTimeItem *dvtmitem, + GdkEvent *event) +{ + EDayView *day_view; + + day_view = dvtmitem->day_view; + g_return_if_fail (day_view != NULL); + + if (dvtmitem->dragging_selection) { + gdk_pointer_ungrab (event->button.time); + e_day_view_finish_selection (day_view); + e_day_view_stop_auto_scroll (day_view); + } + + dvtmitem->dragging_selection = FALSE; +} + + +static void +e_day_view_time_item_on_motion_notify (EDayViewTimeItem *dvtmitem, + GdkEvent *event) +{ + EDayView *day_view; + GnomeCanvas *canvas; + gdouble window_y; + gint y, row; + + if (!dvtmitem->dragging_selection) + return; + + day_view = dvtmitem->day_view; + g_return_if_fail (day_view != NULL); + + canvas = GNOME_CANVAS_ITEM (dvtmitem)->canvas; + + y = event->motion.y; + row = e_day_view_time_item_convert_position_to_row (dvtmitem, y); + + if (row != -1) { + gnome_canvas_world_to_window (canvas, 0, event->motion.y, + NULL, &window_y); + e_day_view_update_selection (day_view, -1, row); + e_day_view_check_auto_scroll (day_view, -1, (gint) window_y); + } +} + + +/* Returns the row corresponding to the y position, or -1. */ +static gint +e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *dvtmitem, + gint y) +{ + EDayView *day_view; + gint row; + + day_view = dvtmitem->day_view; + g_return_val_if_fail (day_view != NULL, -1); + + if (y < 0) + return -1; + + row = y / day_view->row_height; + if (row >= day_view->rows) + return -1; + + return row; +} diff --git a/calendar/gui/e-day-view-time-item.h b/calendar/gui/e-day-view-time-item.h index 9fec96d7dc..8b20fe999e 100644 --- a/calendar/gui/e-day-view-time-item.h +++ b/calendar/gui/e-day-view-time-item.h @@ -50,6 +50,9 @@ typedef struct { /* The width of the time column. */ gint column_width; + + /* TRUE if we are currently dragging the selection times. */ + gboolean dragging_selection; } EDayViewTimeItem; typedef struct { diff --git a/calendar/gui/e-day-view-top-item.c b/calendar/gui/e-day-view-top-item.c index 8e084f4d42..45872238a7 100644 --- a/calendar/gui/e-day-view-top-item.c +++ b/calendar/gui/e-day-view-top-item.c @@ -230,11 +230,11 @@ e_day_view_top_item_draw (GnomeCanvasItem *canvas_item, /* Draw the selection background. */ if (GTK_WIDGET_HAS_FOCUS (day_view) - && day_view->selection_start_col != -1) { + && day_view->selection_start_day != -1) { gint start_col, end_col, rect_x, rect_y, rect_w, rect_h; - start_col = day_view->selection_start_col; - end_col = day_view->selection_end_col; + start_col = day_view->selection_start_day; + end_col = day_view->selection_end_day; if (end_col > start_col || day_view->selection_start_row == -1 diff --git a/calendar/gui/e-day-view.c b/calendar/gui/e-day-view.c index 84cd31464a..0963b00acb 100644 --- a/calendar/gui/e-day-view.c +++ b/calendar/gui/e-day-view.c @@ -92,6 +92,7 @@ static gint e_day_view_focus_out (GtkWidget *widget, GdkEventFocus *event); static gint e_day_view_key_press (GtkWidget *widget, GdkEventKey *event); +static gboolean e_day_view_check_if_new_event_fits (EDayView *day_view); static void e_day_view_on_canvas_realized (GtkWidget *widget, EDayView *day_view); @@ -121,9 +122,6 @@ static gboolean e_day_view_convert_event_coords (EDayView *day_view, GdkWindow *window, gint *x_return, gint *y_return); -static void e_day_view_update_selection (EDayView *day_view, - gint row, - gint col); static void e_day_view_update_long_event_resize (EDayView *day_view, gint day); static void e_day_view_update_resize (EDayView *day_view, @@ -271,11 +269,8 @@ static gboolean e_day_view_convert_time_to_grid_position (EDayView *day_view, gint *col, gint *row); -static void e_day_view_check_auto_scroll (EDayView *day_view, - gint event_y); static void e_day_view_start_auto_scroll (EDayView *day_view, gboolean scroll_up); -static void e_day_view_stop_auto_scroll (EDayView *day_view); static gboolean e_day_view_auto_scroll_handler (gpointer data); static void e_day_view_on_new_appointment (GtkWidget *widget, @@ -470,9 +465,9 @@ e_day_view_init (EDayView *day_view) day_view->resize_bars_event_num = -1; day_view->selection_start_row = -1; - day_view->selection_start_col = -1; + day_view->selection_start_day = -1; day_view->selection_end_row = -1; - day_view->selection_end_col = -1; + day_view->selection_end_day = -1; day_view->selection_drag_pos = E_DAY_VIEW_DRAG_NONE; day_view->selection_in_top_canvas = FALSE; @@ -1098,9 +1093,10 @@ e_day_view_update_event (EDayView *day_view, g_return_if_fail (E_IS_DAY_VIEW (day_view)); -#if 0 +#if 1 /* FIXME: Just for testing. */ chdir ("/home/damon/tmp"); + g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL); g_print ("In e_day_view_update_event day_view:%p uid:%s\n", day_view, uid); @@ -1146,6 +1142,7 @@ e_day_view_update_event (EDayView *day_view, EDayViewEvent, event_num); if (ical_object_compare_dates (event->ico, ico)) { + g_print ("updated object's dates unchanged\n"); e_day_view_foreach_event_with_uid (day_view, uid, e_day_view_update_event_cb, ico); ical_object_unref (ico); gtk_widget_queue_draw (day_view->top_canvas); @@ -1155,6 +1152,7 @@ e_day_view_update_event (EDayView *day_view, /* The dates have changed, so we need to remove the old occurrrences before adding the new ones. */ + g_print ("dates changed - removing occurrences\n"); e_day_view_foreach_event_with_uid (day_view, uid, e_day_view_remove_event_cb, NULL); @@ -1269,7 +1267,7 @@ e_day_view_remove_event (EDayView *day_view, { g_return_if_fail (E_IS_DAY_VIEW (day_view)); -#if 0 +#if 1 g_print ("In e_day_view_remove_event day_view:%p uid:%s\n", day_view, uid); #endif @@ -1291,6 +1289,11 @@ e_day_view_remove_event_cb (EDayView *day_view, { EDayViewEvent *event; +#if 1 + g_print ("In e_day_view_remove_event_cb day:%i event_num:%i\n", + day, event_num); +#endif + if (day == E_DAY_VIEW_LONG_EVENT) event = &g_array_index (day_view->long_events, EDayViewEvent, event_num); @@ -1334,6 +1337,10 @@ e_day_view_update_event_label (EDayView *day_view, event = &g_array_index (day_view->events[day], EDayViewEvent, event_num); + /* If the event isn't visible just return. */ + if (!event->canvas_item) + return; + text = event->ico->summary ? event->ico->summary : ""; if (day_view->editing_event_day == day @@ -1374,6 +1381,10 @@ e_day_view_update_long_event_label (EDayView *day_view, event = &g_array_index (day_view->long_events, EDayViewEvent, event_num); + /* If the event isn't visible just return. */ + if (!event->canvas_item) + return; + gnome_canvas_item_set (event->canvas_item, "text", event->ico->summary ? event->ico->summary : "", NULL); @@ -1527,19 +1538,19 @@ e_day_view_set_selected_time_range (EDayView *day_view, } if (start_row != day_view->selection_start_row - || start_col != day_view->selection_start_col) { + || start_col != day_view->selection_start_day) { need_redraw = TRUE; day_view->selection_in_top_canvas = FALSE; day_view->selection_start_row = start_row; - day_view->selection_start_col = start_col; + day_view->selection_start_day = start_col; } if (end_row != day_view->selection_end_row - || end_col != day_view->selection_end_col) { + || end_col != day_view->selection_end_day) { need_redraw = TRUE; day_view->selection_in_top_canvas = FALSE; day_view->selection_end_row = end_row; - day_view->selection_end_col = end_col; + day_view->selection_end_day = end_col; } if (need_redraw) { @@ -1557,9 +1568,9 @@ e_day_view_get_selected_time_range (EDayView *day_view, { gint start_col, start_row, end_col, end_row; - start_col = day_view->selection_start_col; + start_col = day_view->selection_start_day; start_row = day_view->selection_start_row; - end_col = day_view->selection_end_col; + end_col = day_view->selection_end_day; end_row = day_view->selection_end_row; if (start_col == -1) { @@ -1799,16 +1810,7 @@ e_day_view_on_top_canvas_button_press (GtkWidget *widget, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, FALSE, NULL, event->time) == 0) { - day_view->selection_start_row = -1; - day_view->selection_start_col = day; - day_view->selection_end_row = -1; - day_view->selection_end_col = day; - day_view->selection_drag_pos = E_DAY_VIEW_DRAG_END; - day_view->selection_in_top_canvas = TRUE; - - /* FIXME: Optimise? */ - gtk_widget_queue_draw (day_view->top_canvas); - gtk_widget_queue_draw (day_view->main_canvas); + e_day_view_start_selection (day_view, day, -1); } } else if (event->button == 3) { e_day_view_on_event_right_click (day_view, event, -1, -1); @@ -1912,16 +1914,7 @@ e_day_view_on_main_canvas_button_press (GtkWidget *widget, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, FALSE, NULL, event->time) == 0) { - day_view->selection_start_row = row; - day_view->selection_start_col = day; - day_view->selection_end_row = row; - day_view->selection_end_col = day; - day_view->selection_drag_pos = E_DAY_VIEW_DRAG_END; - day_view->selection_in_top_canvas = FALSE; - - /* FIXME: Optimise? */ - gtk_widget_queue_draw (day_view->top_canvas); - gtk_widget_queue_draw (day_view->main_canvas); + e_day_view_start_selection (day_view, day, row); } } else if (event->button == 3) { e_day_view_on_event_right_click (day_view, event, -1, -1); @@ -2257,7 +2250,7 @@ e_day_view_on_event_right_click (EDayView *day_view, }; have_selection = GTK_WIDGET_HAS_FOCUS (day_view) - && day_view->selection_start_col != -1; + && day_view->selection_start_day != -1; if (event_num == -1) { items = 1; @@ -2352,6 +2345,7 @@ e_day_view_on_delete_occurrence (GtkWidget *widget, gpointer data) { EDayView *day_view; EDayViewEvent *event; + iCalObject *ico; day_view = E_DAY_VIEW (data); @@ -2359,9 +2353,13 @@ e_day_view_on_delete_occurrence (GtkWidget *widget, gpointer data) if (event == NULL) return; - ical_object_add_exdate (event->ico, event->start); - gnome_calendar_object_changed (day_view->calendar, event->ico, - CHANGE_DATES); + /* We must duplicate the iCalObject, or we won't know it has changed + when we get the "update_event" callback. */ + ico = ical_object_duplicate (event->ico); + + ical_object_add_exdate (ico, event->start); + gnome_calendar_object_changed (day_view->calendar, ico, CHANGE_DATES); + ical_object_unref (ico); } @@ -2389,7 +2387,7 @@ e_day_view_on_unrecur_appointment (GtkWidget *widget, gpointer data) { EDayView *day_view; EDayViewEvent *event; - iCalObject *ico; + iCalObject *ico, *new_ico; day_view = E_DAY_VIEW (data); @@ -2397,26 +2395,29 @@ e_day_view_on_unrecur_appointment (GtkWidget *widget, gpointer data) if (event == NULL) return; - /* For the unrecurred instance we duplicate the original object, - create a new uid for it, get rid of the recurrence rules, and set - the start & end times to the instances times. */ - ico = ical_object_duplicate (event->ico); - g_free (ico->uid); - ico->uid = ical_gen_uid (); - g_free (ico->recur); - ico->recur = 0; - ico->dtstart = event->start; - ico->dtend = event->end; - /* For the recurring object, we add a exception to get rid of the instance. */ - ical_object_add_exdate (event->ico, event->start); - - gnome_calendar_object_changed (day_view->calendar, event->ico, - CHANGE_ALL); - gnome_calendar_add_object (day_view->calendar, ico); + ico = ical_object_duplicate (event->ico); + ical_object_add_exdate (ico, event->start); + /* For the unrecurred instance we duplicate the original object, + create a new uid for it, get rid of the recurrence rules, and set + the start & end times to the instances times. */ + new_ico = ical_object_duplicate (event->ico); + g_free (new_ico->uid); + new_ico->uid = ical_gen_uid (); + ical_object_reset_recurrence (new_ico); + new_ico->dtstart = event->start; + new_ico->dtend = event->end; + + /* Now update both iCalObjects. Note that we do this last since at + present the updates happen synchronously so our event may disappear. + */ + gnome_calendar_object_changed (day_view->calendar, ico, CHANGE_ALL); ical_object_unref (ico); + + gnome_calendar_add_object (day_view->calendar, new_ico); + ical_object_unref (new_ico); } @@ -2443,9 +2444,8 @@ e_day_view_on_top_canvas_button_release (GtkWidget *widget, EDayView *day_view) { if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { - day_view->selection_drag_pos = E_DAY_VIEW_DRAG_NONE; gdk_pointer_ungrab (event->time); - e_day_view_update_calendar_selection_time (day_view); + e_day_view_finish_selection (day_view); } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { e_day_view_finish_long_event_resize (day_view); gdk_pointer_ungrab (event->time); @@ -2468,10 +2468,9 @@ e_day_view_on_main_canvas_button_release (GtkWidget *widget, EDayView *day_view) { if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { - day_view->selection_drag_pos = E_DAY_VIEW_DRAG_NONE; gdk_pointer_ungrab (event->time); + e_day_view_finish_selection (day_view); e_day_view_stop_auto_scroll (day_view); - e_day_view_update_calendar_selection_time (day_view); } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { e_day_view_finish_resize (day_view); gdk_pointer_ungrab (event->time); @@ -2536,7 +2535,7 @@ e_day_view_on_top_canvas_motion (GtkWidget *widget, event_num); if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { - e_day_view_update_selection (day_view, -1, day); + e_day_view_update_selection (day_view, day, -1); return TRUE; } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { if (pos != E_DAY_VIEW_POS_OUTSIDE) { @@ -2620,9 +2619,6 @@ e_day_view_on_main_canvas_motion (GtkWidget *widget, &event_x, &event_y)) return FALSE; - day_view->last_mouse_x = event_x; - day_view->last_mouse_y = event_y; - gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), &scroll_x, &scroll_y); canvas_x = event_x + scroll_x; @@ -2638,14 +2634,16 @@ e_day_view_on_main_canvas_motion (GtkWidget *widget, if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { if (pos != E_DAY_VIEW_POS_OUTSIDE) { - e_day_view_update_selection (day_view, row, day); - e_day_view_check_auto_scroll (day_view, event_y); + e_day_view_update_selection (day_view, day, row); + e_day_view_check_auto_scroll (day_view, + event_x, event_y); return TRUE; } } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { if (pos != E_DAY_VIEW_POS_OUTSIDE) { e_day_view_update_resize (day_view, row); - e_day_view_check_auto_scroll (day_view, event_y); + e_day_view_check_auto_scroll (day_view, + event_x, event_y); return TRUE; } } else if (day_view->pressed_event_day != -1 @@ -2705,45 +2703,81 @@ e_day_view_on_main_canvas_motion (GtkWidget *widget, } -static void +/* This sets the selection to a single cell. If day is -1 then the current + start day is reused. If row is -1 then the selection is in the top canvas. +*/ +void +e_day_view_start_selection (EDayView *day_view, + gint day, + gint row) +{ + if (day == -1) { + day = day_view->selection_start_day; + if (day == -1) + day = 0; + } + + day_view->selection_start_day = day; + day_view->selection_end_day = day; + + day_view->selection_start_row = row; + day_view->selection_end_row = row; + + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_END; + day_view->selection_in_top_canvas = (row == -1) ? TRUE : FALSE; + + /* FIXME: Optimise? */ + gtk_widget_queue_draw (day_view->top_canvas); + gtk_widget_queue_draw (day_view->main_canvas); +} + + +/* Updates the selection during a drag. If day is -1 the selection day is + unchanged. */ +void e_day_view_update_selection (EDayView *day_view, - gint row, - gint col) + gint day, + gint row) { - gint tmp_row, tmp_col; + gint tmp_row, tmp_day; gboolean need_redraw = FALSE; #if 0 - g_print ("Updating selection %i,%i\n", col, row); + g_print ("Updating selection %i,%i\n", day, row); #endif day_view->selection_in_top_canvas = (row == -1) ? TRUE : FALSE; + if (day == -1) + day = (day_view->selection_drag_pos == E_DAY_VIEW_DRAG_START) + ? day_view->selection_start_day + : day_view->selection_end_day; + if (day_view->selection_drag_pos == E_DAY_VIEW_DRAG_START) { if (row != day_view->selection_start_row - || col != day_view->selection_start_col) { + || day != day_view->selection_start_day) { need_redraw = TRUE; day_view->selection_start_row = row; - day_view->selection_start_col = col; + day_view->selection_start_day = day; } } else { if (row != day_view->selection_end_row - || col != day_view->selection_end_col) { + || day != day_view->selection_end_day) { need_redraw = TRUE; day_view->selection_end_row = row; - day_view->selection_end_col = col; + day_view->selection_end_day = day; } } /* Switch the drag position if necessary. */ - if (day_view->selection_start_col > day_view->selection_end_col - || (day_view->selection_start_col == day_view->selection_end_col + if (day_view->selection_start_day > day_view->selection_end_day + || (day_view->selection_start_day == day_view->selection_end_day && day_view->selection_start_row > day_view->selection_end_row)) { tmp_row = day_view->selection_start_row; - tmp_col = day_view->selection_start_col; - day_view->selection_start_col = day_view->selection_end_col; + tmp_day = day_view->selection_start_day; + day_view->selection_start_day = day_view->selection_end_day; day_view->selection_start_row = day_view->selection_end_row; - day_view->selection_end_col = tmp_col; + day_view->selection_end_day = tmp_day; day_view->selection_end_row = tmp_row; if (day_view->selection_drag_pos == E_DAY_VIEW_DRAG_START) day_view->selection_drag_pos = E_DAY_VIEW_DRAG_END; @@ -2759,6 +2793,14 @@ e_day_view_update_selection (EDayView *day_view, } +void +e_day_view_finish_selection (EDayView *day_view) +{ + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_NONE; + e_day_view_update_calendar_selection_time (day_view); +} + + static void e_day_view_update_long_event_resize (EDayView *day_view, gint day) @@ -3808,9 +3850,16 @@ e_day_view_key_press (GtkWidget *widget, GdkEventKey *event) return FALSE; } + if (day_view->selection_start_day == -1) + return FALSE; - if (day_view->selection_start_col == -1) + /* Check if there is room for a new event to be typed in. If there + isn't we don't want to add an event as we will then add a new + event for every key press. */ + if (!e_day_view_check_if_new_event_fits (day_view)) { + g_print ("Skipping new event. No more room\n"); return FALSE; + } /* We only want to start an edit with a return key or a simple character. */ @@ -3861,6 +3910,33 @@ e_day_view_key_press (GtkWidget *widget, GdkEventKey *event) } +static gboolean +e_day_view_check_if_new_event_fits (EDayView *day_view) +{ + gint day, start_row, end_row, row; + + day = day_view->selection_start_day; + start_row = day_view->selection_start_row; + end_row = day_view->selection_end_row; + + /* Long events always fit, since we keep adding rows to the top + canvas. */ + if (day != day_view->selection_end_day) + return FALSE; + if (start_row == 0 && end_row == day_view->rows) + return FALSE; + + /* If any of the rows already have E_DAY_VIEW_MAX_COLUMNS columns, + return FALSE. */ + for (row = start_row; row <= end_row; row++) { + if (day_view->cols_per_row[day][row] >= E_DAY_VIEW_MAX_COLUMNS) + return FALSE; + } + + return TRUE; +} + + static void e_day_view_start_editing_event (EDayView *day_view, gint day, @@ -4160,10 +4236,14 @@ e_day_view_convert_time_to_grid_position (EDayView *day_view, /* This starts or stops auto-scrolling when dragging a selection or resizing an event. */ -static void +void e_day_view_check_auto_scroll (EDayView *day_view, + gint event_x, gint event_y) { + day_view->last_mouse_x = event_x; + day_view->last_mouse_y = event_y; + if (event_y < E_DAY_VIEW_AUTO_SCROLL_OFFSET) e_day_view_start_auto_scroll (day_view, TRUE); else if (event_y >= day_view->main_canvas->allocation.height @@ -4186,7 +4266,7 @@ e_day_view_start_auto_scroll (EDayView *day_view, } -static void +void e_day_view_stop_auto_scroll (EDayView *day_view) { if (day_view->auto_scroll_timeout_id != 0) { @@ -4241,14 +4321,23 @@ e_day_view_auto_scroll_handler (gpointer data) canvas_x = day_view->last_mouse_x + scroll_x; canvas_y = day_view->last_mouse_y + new_scroll_y; + /* The last_mouse_x position is set to -1 when we are selecting using + the time column. In this case we set canvas_x to 0 and we ignore + the resulting day. */ + if (day_view->last_mouse_x == -1) + canvas_x = 0; + /* Update the selection/resize/drag if necessary. */ pos = e_day_view_convert_position_in_main_canvas (day_view, canvas_x, canvas_y, &day, &row, NULL); + if (day_view->last_mouse_x == -1) + day = -1; + if (pos != E_DAY_VIEW_POS_OUTSIDE) { if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { - e_day_view_update_selection (day_view, row, day); + e_day_view_update_selection (day_view, day, row); } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { e_day_view_update_resize (day_view, row); } else if (day_view->drag_item->object.flags @@ -4675,9 +4764,6 @@ e_day_view_on_main_canvas_drag_motion (GtkWidget *widget, { gint scroll_x, scroll_y; - day_view->last_mouse_x = x; - day_view->last_mouse_y = y; - gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), &scroll_x, &scroll_y); day_view->drag_event_x = x + scroll_x; @@ -4686,7 +4772,7 @@ e_day_view_on_main_canvas_drag_motion (GtkWidget *widget, e_day_view_reshape_main_canvas_drag_item (day_view); e_day_view_reshape_main_canvas_resize_bars (day_view); - e_day_view_check_auto_scroll (day_view, y); + e_day_view_check_auto_scroll (day_view, x, y); return TRUE; } diff --git a/calendar/gui/e-day-view.h b/calendar/gui/e-day-view.h index 2c992f8207..17bbff6be5 100644 --- a/calendar/gui/e-day-view.h +++ b/calendar/gui/e-day-view.h @@ -330,11 +330,11 @@ struct _EDayView gint popup_event_day; gint popup_event_num; - /* The currently selected region. If selection_start_col is -1 there is + /* The currently selected region. If selection_start_day is -1 there is no current selection. If start_row or end_row is -1 then the selection is in the top canvas. */ - gint selection_start_col; - gint selection_end_col; + gint selection_start_day; + gint selection_end_day; gint selection_start_row; gint selection_end_row; @@ -482,6 +482,19 @@ gboolean e_day_view_find_long_event_days (EDayView *day_view, gint *start_day, gint *end_day); +void e_day_view_start_selection (EDayView *day_view, + gint day, + gint row); +void e_day_view_update_selection (EDayView *day_view, + gint day, + gint row); +void e_day_view_finish_selection (EDayView *day_view); + +void e_day_view_check_auto_scroll (EDayView *day_view, + gint event_x, + gint event_y); +void e_day_view_stop_auto_scroll (EDayView *day_view); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/calendar/gui/e-week-view.c b/calendar/gui/e-week-view.c index 26fce815f9..a233d2a66d 100644 --- a/calendar/gui/e-week-view.c +++ b/calendar/gui/e-week-view.c @@ -2659,6 +2659,7 @@ e_week_view_on_delete_occurrence (GtkWidget *widget, gpointer data) { EWeekView *week_view; EWeekViewEvent *event; + iCalObject *ico; week_view = E_WEEK_VIEW (data); @@ -2668,9 +2669,13 @@ e_week_view_on_delete_occurrence (GtkWidget *widget, gpointer data) event = &g_array_index (week_view->events, EWeekViewEvent, week_view->popup_event_num); - ical_object_add_exdate (event->ico, event->start); - gnome_calendar_object_changed (week_view->calendar, event->ico, - CHANGE_DATES); + /* We must duplicate the iCalObject, or we won't know it has changed + when we get the "update_event" callback. */ + ico = ical_object_duplicate (event->ico); + + ical_object_add_exdate (ico, event->start); + gnome_calendar_object_changed (week_view->calendar, ico, CHANGE_DATES); + ical_object_unref (ico); } @@ -2697,7 +2702,7 @@ e_week_view_on_unrecur_appointment (GtkWidget *widget, gpointer data) { EWeekView *week_view; EWeekViewEvent *event; - iCalObject *ico; + iCalObject *ico, *new_ico; week_view = E_WEEK_VIEW (data); @@ -2707,24 +2712,27 @@ e_week_view_on_unrecur_appointment (GtkWidget *widget, gpointer data) event = &g_array_index (week_view->events, EWeekViewEvent, week_view->popup_event_num); + /* For the recurring object, we add a exception to get rid of the + instance. */ + ico = ical_object_duplicate (event->ico); + ical_object_add_exdate (ico, event->start); + /* For the unrecurred instance we duplicate the original object, create a new uid for it, get rid of the recurrence rules, and set the start & end times to the instances times. */ - ico = ical_object_duplicate (event->ico); - g_free (ico->uid); - ico->uid = ical_gen_uid (); - g_free (ico->recur); - ico->recur = 0; - ico->dtstart = event->start; - ico->dtend = event->end; + new_ico = ical_object_duplicate (event->ico); + g_free (new_ico->uid); + new_ico->uid = ical_gen_uid (); + ical_object_reset_recurrence (new_ico); + new_ico->dtstart = event->start; + new_ico->dtend = event->end; - /* For the recurring object, we add a exception to get rid of the - instance. */ - ical_object_add_exdate (event->ico, event->start); - - gnome_calendar_object_changed (week_view->calendar, event->ico, - CHANGE_ALL); - gnome_calendar_add_object (week_view->calendar, ico); - + /* Now update both iCalObjects. Note that we do this last since at + present the updates happen synchronously so our event may disappear. + */ + gnome_calendar_object_changed (week_view->calendar, ico, CHANGE_ALL); ical_object_unref (ico); + + gnome_calendar_add_object (week_view->calendar, new_ico); + ical_object_unref (new_ico); } -- cgit