aboutsummaryrefslogtreecommitdiffstats
path: root/calendar/gui/dialogs/recurrence-page.c
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/gui/dialogs/recurrence-page.c')
-rw-r--r--calendar/gui/dialogs/recurrence-page.c1176
1 files changed, 1166 insertions, 10 deletions
diff --git a/calendar/gui/dialogs/recurrence-page.c b/calendar/gui/dialogs/recurrence-page.c
index 0b0eace9cf..363ca94cc5 100644
--- a/calendar/gui/dialogs/recurrence-page.c
+++ b/calendar/gui/dialogs/recurrence-page.c
@@ -26,16 +26,80 @@
#include <config.h>
#endif
+#include <gtk/gtkoptionmenu.h>
#include <gtk/gtksignal.h>
+#include <gtk/gtkspinbutton.h>
#include <libgnome/gnome-defs.h>
#include <libgnome/gnome-i18n.h>
#include <glade/glade.h>
#include <gal/widgets/e-unicode.h>
#include "e-util/e-dialog-widgets.h"
+#include "widgets/misc/e-dateedit.h"
+#include "../calendar-config.h"
+#include "../tag-calendar.h"
+#include "../weekday-picker.h"
+#include "../widget-util.h"
#include "recurrence-page.h"
+enum month_day_options {
+ MONTH_DAY_NTH,
+ MONTH_DAY_MON,
+ MONTH_DAY_TUE,
+ MONTH_DAY_WED,
+ MONTH_DAY_THU,
+ MONTH_DAY_FRI,
+ MONTH_DAY_SAT,
+ MONTH_DAY_SUN
+};
+
+static const int month_day_options_map[] = {
+ MONTH_DAY_NTH,
+ MONTH_DAY_MON,
+ MONTH_DAY_TUE,
+ MONTH_DAY_WED,
+ MONTH_DAY_THU,
+ MONTH_DAY_FRI,
+ MONTH_DAY_SAT,
+ MONTH_DAY_SUN,
+ -1
+};
+
+enum recur_type {
+ RECUR_NONE,
+ RECUR_SIMPLE,
+ RECUR_CUSTOM
+};
+
+static const int type_map[] = {
+ RECUR_NONE,
+ RECUR_SIMPLE,
+ RECUR_CUSTOM,
+ -1
+};
+
+static const int freq_map[] = {
+ ICAL_DAILY_RECURRENCE,
+ ICAL_WEEKLY_RECURRENCE,
+ ICAL_MONTHLY_RECURRENCE,
+ ICAL_YEARLY_RECURRENCE,
+ -1
+};
+
+enum ending_type {
+ ENDING_FOR,
+ ENDING_UNTIL,
+ ENDING_FOREVER
+};
+
+static const int ending_types_map[] = {
+ ENDING_FOR,
+ ENDING_UNTIL,
+ ENDING_FOREVER,
+ -1
+};
+
/* Private part of the RecurrencePage structure */
struct _RecurrencePagePrivate {
/* Glade XML data */
@@ -92,20 +156,23 @@ struct _RecurrencePagePrivate {
/* For the recurrence preview, the actual widget */
GtkWidget *preview_calendar;
+
+ /* Component we use to expand the recurrence rules for the preview calendar */
+ CalComponent *comp;
};
static void recurrence_page_class_init (RecurrencePageClass *class);
static void recurrence_page_init (RecurrencePage *rpage);
-static void recurrence_page_destroy (RecurrencePage *rpage);
+static void recurrence_page_destroy (GtkObject *object);
static GtkWidget *recurrence_page_get_widget (EditorPage *page);
static void recurrence_page_fill_widgets (EditorPage *page, CalComponent *comp);
static void recurrence_page_fill_component (EditorPage *page, CalComponent *comp);
static void recurrence_page_set_summary (EditorPage *page, const char *summary);
static char *recurrence_page_get_summary (EditorPage *page);
-static void recurrence_page_set_dtstart (EditorPage *page, time_t start);
+static void recurrence_page_set_dates (EditorPage *page, time_t start, time_t end);
static EditorPageClass *parent_class = NULL;
@@ -136,7 +203,7 @@ recurrence_page_get_type (void)
(GtkClassInitFunc) NULL
};
- recurrence_page_type = gtk_type_unique (EDITOR_PAGE_TYPE, &recurrence_page_info);
+ recurrence_page_type = gtk_type_unique (TYPE_EDITOR_PAGE, &recurrence_page_info);
}
return recurrence_page_type;
@@ -152,14 +219,14 @@ recurrence_page_class_init (RecurrencePageClass *class)
editor_page_class = (EditorPageClass *) class;
object_class = (GtkObjectClass *) class;
- parent_class = gtk_type_class (EDITOR_PAGE_TYPE);
+ parent_class = gtk_type_class (TYPE_EDITOR_PAGE);
editor_page_class->get_widget = recurrence_page_get_widget;
editor_page_class->fill_widgets = recurrence_page_fill_widgets;
editor_page_class->fill_component = recurrence_page_fill_component;
editor_page_class->set_summary = recurrence_page_set_summary;
editor_page_class->get_summary = recurrence_page_get_summary;
- editor_page_class->set_dtstart = recurrence_page_set_dtstart;
+ editor_page_class->set_dates = recurrence_page_set_dates;
object_class->destroy = recurrence_page_destroy;
}
@@ -200,6 +267,8 @@ recurrence_page_init (RecurrencePage *rpage)
priv->exception_delete = NULL;
priv->preview_bin = NULL;
priv->preview_calendar = NULL;
+
+ priv->comp = NULL;
}
/* Frees the rows and the row data in the exceptions GtkCList */
@@ -272,6 +341,8 @@ static void
clear_widgets (RecurrencePage *rpage)
{
RecurrencePagePrivate *priv;
+ GtkAdjustment *adj;
+ GtkWidget *menu;
priv = rpage->priv;
@@ -307,7 +378,7 @@ clear_widgets (RecurrencePage *rpage)
gtk_signal_handler_unblock_by_data (GTK_OBJECT (menu), rpage);
/* Exceptions list */
- free_exception_clist_data (GTK_CLIST (priv->exception_list));
+ free_exception_clist_data (rpage);
}
/* Builds a static string out of an exception date */
@@ -473,6 +544,240 @@ sensitize_recur_widgets (RecurrencePage *rpage)
}
}
+/* Encondes a position/weekday pair into the proper format for
+ * icalrecurrencetype.by_day.
+ */
+static short
+nth_weekday (int pos, icalrecurrencetype_weekday weekday)
+{
+ g_assert (pos > 0 && pos <= 5);
+
+ return (pos << 3) | (int) weekday;
+}
+
+/* Gets the simple recurrence data from the recurrence widgets and stores it in
+ * the calendar component.
+ */
+static void
+simple_recur_to_comp (RecurrencePage *rpage, CalComponent *comp)
+{
+ RecurrencePagePrivate *priv;
+ struct icalrecurrencetype r;
+ GSList l;
+ enum ending_type ending_type;
+
+ priv = rpage->priv;
+
+ icalrecurrencetype_clear (&r);
+
+ /* Frequency, interval, week start */
+
+ r.freq = e_dialog_option_menu_get (priv->interval_unit, freq_map);
+ r.interval = e_dialog_spin_get_int (priv->interval_value);
+ r.week_start = ICAL_SUNDAY_WEEKDAY + calendar_config_get_week_start_day ();
+
+ /* Frequency-specific data */
+
+ switch (r.freq) {
+ case ICAL_DAILY_RECURRENCE:
+ /* Nothing else is required */
+ break;
+
+ case ICAL_WEEKLY_RECURRENCE: {
+ guint8 day_mask;
+ int i;
+
+ g_assert (GTK_BIN (priv->special)->child != NULL);
+ g_assert (priv->weekday_picker != NULL);
+ g_assert (IS_WEEKDAY_PICKER (priv->weekday_picker));
+
+ day_mask = weekday_picker_get_days (WEEKDAY_PICKER (priv->weekday_picker));
+
+ i = 0;
+
+ if (day_mask & (1 << 0))
+ r.by_day[i++] = ICAL_SUNDAY_WEEKDAY;
+
+ if (day_mask & (1 << 1))
+ r.by_day[i++] = ICAL_MONDAY_WEEKDAY;
+
+ if (day_mask & (1 << 2))
+ r.by_day[i++] = ICAL_TUESDAY_WEEKDAY;
+
+ if (day_mask & (1 << 3))
+ r.by_day[i++] = ICAL_WEDNESDAY_WEEKDAY;
+
+ if (day_mask & (1 << 4))
+ r.by_day[i++] = ICAL_THURSDAY_WEEKDAY;
+
+ if (day_mask & (1 << 5))
+ r.by_day[i++] = ICAL_FRIDAY_WEEKDAY;
+
+ if (day_mask & (1 << 6))
+ r.by_day[i++] = ICAL_SATURDAY_WEEKDAY;
+
+ break;
+ }
+
+ case ICAL_MONTHLY_RECURRENCE: {
+ int day_index;
+ enum month_day_options month_day;
+
+ g_assert (GTK_BIN (priv->special)->child != NULL);
+ g_assert (priv->month_index_spin != NULL);
+ g_assert (GTK_IS_SPIN_BUTTON (priv->month_index_spin));
+ g_assert (priv->month_day_menu != NULL);
+ g_assert (GTK_IS_OPTION_MENU (priv->month_day_menu));
+
+ day_index = e_dialog_spin_get_int (priv->month_index_spin);
+ month_day = e_dialog_option_menu_get (priv->month_day_menu,
+ month_day_options_map);
+
+ switch (month_day) {
+ case MONTH_DAY_NTH:
+ r.by_month_day[0] = day_index;
+ break;
+
+ case MONTH_DAY_MON:
+ r.by_day[0] = nth_weekday (day_index, ICAL_MONDAY_WEEKDAY);
+ break;
+
+ case MONTH_DAY_TUE:
+ r.by_day[0] = nth_weekday (day_index, ICAL_TUESDAY_WEEKDAY);
+ break;
+
+ case MONTH_DAY_WED:
+ r.by_day[0] = nth_weekday (day_index, ICAL_WEDNESDAY_WEEKDAY);
+ break;
+
+ case MONTH_DAY_THU:
+ r.by_day[0] = nth_weekday (day_index, ICAL_THURSDAY_WEEKDAY);
+ break;
+
+ case MONTH_DAY_FRI:
+ r.by_day[0] = nth_weekday (day_index, ICAL_FRIDAY_WEEKDAY);
+ break;
+
+ case MONTH_DAY_SAT:
+ r.by_day[0] = nth_weekday (day_index, ICAL_SATURDAY_WEEKDAY);
+ break;
+
+ case MONTH_DAY_SUN:
+ r.by_day[0] = nth_weekday (day_index, ICAL_SUNDAY_WEEKDAY);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ break;
+ }
+
+ case ICAL_YEARLY_RECURRENCE:
+ /* Nothing else is required */
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Ending date */
+
+ ending_type = e_dialog_option_menu_get (priv->ending_menu, ending_types_map);
+
+ switch (ending_type) {
+ case ENDING_FOR:
+ g_assert (priv->ending_count_spin != NULL);
+ g_assert (GTK_IS_SPIN_BUTTON (priv->ending_count_spin));
+
+ r.count = e_dialog_spin_get_int (priv->ending_count_spin);
+ break;
+
+ case ENDING_UNTIL:
+ g_assert (priv->ending_date_edit != NULL);
+ g_assert (E_IS_DATE_EDIT (priv->ending_date_edit));
+
+ r.until = icaltime_from_timet (
+ e_date_edit_get_time (E_DATE_EDIT (priv->ending_date_edit)),
+ TRUE);
+ break;
+
+ case ENDING_FOREVER:
+ /* Nothing to be done */
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Set the recurrence */
+
+ l.data = &r;
+ l.next = NULL;
+
+ cal_component_set_rrule_list (comp, &l);
+}
+
+/* Fills a component with the data from the recurrence page; in the case of a
+ * custom recurrence, it leaves it intact.
+ */
+static void
+fill_component (RecurrencePage *rpage, CalComponent *comp)
+{
+ RecurrencePagePrivate *priv;
+ enum recur_type recur_type;
+ GtkCList *exception_list;
+ GSList *list;
+ int i;
+
+ priv = rpage->priv;
+
+ recur_type = e_dialog_radio_get (priv->none, type_map);
+
+ switch (recur_type) {
+ case RECUR_NONE:
+ cal_component_set_rdate_list (comp, NULL);
+ cal_component_set_rrule_list (comp, NULL);
+ cal_component_set_exrule_list (comp, NULL);
+ break;
+
+ case RECUR_SIMPLE:
+ cal_component_set_rdate_list (comp, NULL);
+ cal_component_set_exrule_list (comp, NULL);
+ simple_recur_to_comp (rpage, comp);
+ break;
+
+ case RECUR_CUSTOM:
+ /* We just keep whatever the component has currently */
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Set exceptions */
+
+ list = NULL;
+ exception_list = GTK_CLIST (priv->exception_list);
+ for (i = 0; i < exception_list->rows; i++) {
+ CalComponentDateTime *cdt;
+ time_t *tim;
+
+ cdt = g_new (CalComponentDateTime, 1);
+ cdt->value = g_new (struct icaltimetype, 1);
+ cdt->tzid = NULL;
+
+ tim = gtk_clist_get_row_data (exception_list, i);
+ g_assert (tim != NULL);
+ *cdt->value = icaltime_from_timet (*tim, FALSE);
+
+ list = g_slist_prepend (list, cdt);
+ }
+
+ cal_component_set_exdate_list (comp, list);
+ cal_component_free_exdate_list (list);
+}
+
/* Re-tags the recurrence preview calendar based on the current information of
* the widgets in the recurrence page.
*/
@@ -485,10 +790,15 @@ preview_recur (RecurrencePage *rpage)
GSList *l;
priv = rpage->priv;
- g_assert (priv->comp != NULL);
+
+ /* If our component has not been set yet through ::fill_widgets(), we
+ * cannot preview the recurrence.
+ */
+ if (!priv->comp)
+ return;
/* Create a scratch component with the start/end and
- * recurrence/excepttion information from the one we are editing.
+ * recurrence/exception information from the one we are editing.
*/
comp = cal_component_new ();
@@ -518,12 +828,462 @@ preview_recur (RecurrencePage *rpage)
cal_component_set_rrule_list (comp, l);
cal_component_free_recur_list (l);
- recur_to_comp_object (rpage, comp);
+ fill_component (rpage, comp);
tag_calendar_by_comp (E_CALENDAR (priv->preview_calendar), comp);
gtk_object_unref (GTK_OBJECT (comp));
}
+/* Callback used when the recurrence weekday picker changes */
+static void
+weekday_picker_changed_cb (WeekdayPicker *wp, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ preview_recur (rpage);
+}
+
+/* Creates the special contents for weekly recurrences */
+static void
+make_weekly_special (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ WeekdayPicker *wp;
+
+ priv = rpage->priv;
+
+ g_assert (GTK_BIN (priv->special)->child == NULL);
+ g_assert (priv->weekday_picker == NULL);
+
+ /* Create the widgets */
+
+ hbox = gtk_hbox_new (FALSE, 2);
+ gtk_container_add (GTK_CONTAINER (priv->special), hbox);
+
+ label = gtk_label_new (_("on"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ wp = WEEKDAY_PICKER (weekday_picker_new ());
+
+ priv->weekday_picker = GTK_WIDGET (wp);
+ gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (wp), FALSE, FALSE, 0);
+
+ gtk_widget_show_all (hbox);
+
+ /* Set the weekdays */
+
+ weekday_picker_set_week_start_day (wp, calendar_config_get_week_start_day ());
+ weekday_picker_set_days (wp, priv->weekday_day_mask);
+ weekday_picker_set_blocked_days (wp, priv->weekday_blocked_day_mask);
+
+ gtk_signal_connect (GTK_OBJECT (wp), "changed",
+ GTK_SIGNAL_FUNC (weekday_picker_changed_cb), rpage);
+}
+
+/* Creates the option menu for the monthly recurrence days */
+static GtkWidget *
+make_recur_month_menu (void)
+{
+ static const char *options[] = {
+ N_("day"),
+ N_("Monday"),
+ N_("Tuesday"),
+ N_("Wednesday"),
+ N_("Thursday"),
+ N_("Friday"),
+ N_("Saturday"),
+ N_("Sunday")
+ };
+
+ GtkWidget *menu;
+ GtkWidget *omenu;
+ int i;
+
+ menu = gtk_menu_new ();
+
+ for (i = 0; i < sizeof (options) / sizeof (options[0]); i++) {
+ GtkWidget *item;
+
+ item = gtk_menu_item_new_with_label (_(options[i]));
+ gtk_menu_append (GTK_MENU (menu), item);
+ gtk_widget_show (item);
+ }
+
+ omenu = gtk_option_menu_new ();
+ gtk_option_menu_set_menu (GTK_OPTION_MENU (omenu), menu);
+
+ return omenu;
+}
+
+/* For monthly recurrences, changes the valid range of the recurrence day index
+ * spin button; e.g. month days are 1-31 while the valid range for a Sunday is
+ * the 1st through 5th of the month.
+ */
+static void
+adjust_day_index_spin (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ GtkAdjustment *adj;
+ enum month_day_options month_day;
+
+ priv = rpage->priv;
+
+ g_assert (priv->month_day_menu != NULL);
+ g_assert (GTK_IS_OPTION_MENU (priv->month_day_menu));
+ g_assert (priv->month_index_spin != NULL);
+ g_assert (GTK_IS_SPIN_BUTTON (priv->month_index_spin));
+
+ month_day = e_dialog_option_menu_get (priv->month_day_menu, month_day_options_map);
+
+ adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->month_index_spin));
+
+ switch (month_day) {
+ case MONTH_DAY_NTH:
+ adj->upper = 31;
+ gtk_adjustment_changed (adj);
+ break;
+
+ case MONTH_DAY_MON:
+ case MONTH_DAY_TUE:
+ case MONTH_DAY_WED:
+ case MONTH_DAY_THU:
+ case MONTH_DAY_FRI:
+ case MONTH_DAY_SAT:
+ case MONTH_DAY_SUN:
+ adj->upper = 5;
+ gtk_adjustment_changed (adj);
+
+ if (adj->value > 5) {
+ adj->value = 5;
+ gtk_adjustment_value_changed (adj);
+ }
+
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* Callback used when the monthly day selection menu changes. We need to change
+ * the valid range of the day index spin button; e.g. days are 1-31 while a
+ * Sunday is the 1st through 5th.
+ */
+static void
+month_day_menu_selection_done_cb (GtkMenuShell *menu_shell, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+
+ adjust_day_index_spin (rpage);
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ preview_recur (rpage);
+}
+
+/* Callback used when the month index value changes. */
+static void
+month_index_value_changed_cb (GtkAdjustment *adj, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ preview_recur (rpage);
+}
+
+/* Creates the special contents for monthly recurrences */
+static void
+make_monthly_special (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkAdjustment *adj;
+ GtkWidget *menu;
+
+ priv = rpage->priv;
+
+ g_assert (GTK_BIN (priv->special)->child == NULL);
+ g_assert (priv->month_index_spin == NULL);
+ g_assert (priv->month_day_menu == NULL);
+
+ /* Create the widgets */
+
+ hbox = gtk_hbox_new (FALSE, 2);
+ gtk_container_add (GTK_CONTAINER (priv->special), hbox);
+
+ label = gtk_label_new (_("on the"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ adj = GTK_ADJUSTMENT (gtk_adjustment_new (1, 1, 31, 1, 10, 10));
+ priv->month_index_spin = gtk_spin_button_new (adj, 1, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->month_index_spin, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("th"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ priv->month_day_menu = make_recur_month_menu ();
+ gtk_box_pack_start (GTK_BOX (hbox), priv->month_day_menu, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (hbox);
+
+ /* Set the options */
+
+ e_dialog_spin_set (priv->month_index_spin, priv->month_index);
+ e_dialog_option_menu_set (priv->month_day_menu,
+ priv->month_day,
+ month_day_options_map);
+ adjust_day_index_spin (rpage);
+
+ gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
+ GTK_SIGNAL_FUNC (month_index_value_changed_cb), rpage);
+
+ menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (priv->month_day_menu));
+ gtk_signal_connect (GTK_OBJECT (menu), "selection_done",
+ GTK_SIGNAL_FUNC (month_day_menu_selection_done_cb), rpage);
+}
+
+/* Changes the recurrence-special widget to match the interval units.
+ *
+ * For daily recurrences: nothing.
+ * For weekly recurrences: weekday selector.
+ * For monthly recurrences: "on the" <nth> [day, Weekday]
+ * For yearly recurrences: nothing.
+ */
+static void
+make_recurrence_special (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ icalrecurrencetype_frequency frequency;
+
+ priv = rpage->priv;
+
+ if (GTK_BIN (priv->special)->child != NULL) {
+ gtk_widget_destroy (GTK_BIN (priv->special)->child);
+
+ priv->weekday_picker = NULL;
+ priv->month_index_spin = NULL;
+ priv->month_day_menu = NULL;
+ }
+
+ frequency = e_dialog_option_menu_get (priv->interval_unit, freq_map);
+
+ switch (frequency) {
+ case ICAL_DAILY_RECURRENCE:
+ gtk_widget_hide (priv->special);
+ break;
+
+ case ICAL_WEEKLY_RECURRENCE:
+ make_weekly_special (rpage);
+ gtk_widget_show (priv->special);
+ break;
+
+ case ICAL_MONTHLY_RECURRENCE:
+ make_monthly_special (rpage);
+ gtk_widget_show (priv->special);
+ break;
+
+ case ICAL_YEARLY_RECURRENCE:
+ gtk_widget_hide (priv->special);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* Counts the number of elements in the by_xxx fields of an icalrecurrencetype */
+static int
+count_by_xxx (short *field, int max_elements)
+{
+ int i;
+
+ for (i = 0; i < max_elements; i++)
+ if (field[i] == ICAL_RECURRENCE_ARRAY_MAX)
+ break;
+
+ return i;
+}
+
+/* Callback used when the ending-until date editor changes */
+static void
+ending_until_changed_cb (EDateEdit *de, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ preview_recur (rpage);
+}
+
+/* Creates the special contents for "ending until" (end date) recurrences */
+static void
+make_ending_until_special (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ EDateEdit *de;
+
+ priv = rpage->priv;
+
+ g_assert (GTK_BIN (priv->ending_special)->child == NULL);
+ g_assert (priv->ending_date_edit == NULL);
+
+ /* Create the widget */
+
+ priv->ending_date_edit = date_edit_new (TRUE, FALSE);
+ de = E_DATE_EDIT (priv->ending_date_edit);
+
+ gtk_container_add (GTK_CONTAINER (priv->ending_special), GTK_WIDGET (de));
+ gtk_widget_show_all (GTK_WIDGET (de));
+
+ /* Set the value */
+
+ e_date_edit_set_time (de, priv->ending_date);
+
+ gtk_signal_connect (GTK_OBJECT (de), "changed",
+ GTK_SIGNAL_FUNC (ending_until_changed_cb), rpage);
+}
+
+/* Callback used when the ending-count value changes */
+static void
+ending_count_value_changed_cb (GtkAdjustment *adj, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ preview_recur (rpage);
+}
+
+/* Creates the special contents for the occurrence count case */
+static void
+make_ending_count_special (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkAdjustment *adj;
+
+ priv = rpage->priv;
+
+ g_assert (GTK_BIN (priv->ending_special)->child == NULL);
+ g_assert (priv->ending_count_spin == NULL);
+
+ /* Create the widgets */
+
+ hbox = gtk_hbox_new (FALSE, 2);
+ gtk_container_add (GTK_CONTAINER (priv->ending_special), hbox);
+
+ adj = GTK_ADJUSTMENT (gtk_adjustment_new (1, 1, 10000, 1, 10, 10));
+ priv->ending_count_spin = gtk_spin_button_new (adj, 1, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), priv->ending_count_spin, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("occurrences"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (hbox);
+
+ /* Set the values */
+
+ e_dialog_spin_set (priv->ending_count_spin, priv->ending_count);
+
+ gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
+ GTK_SIGNAL_FUNC (ending_count_value_changed_cb), rpage);
+}
+
+/* Changes the recurrence-ending-special widget to match the ending date option.
+ *
+ * For: <n> [days, weeks, months, years, occurrences]
+ * Until: <date selector>
+ * Forever: nothing.
+ */
+static void
+make_ending_special (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ enum ending_type ending_type;
+
+ priv = rpage->priv;
+
+ if (GTK_BIN (priv->ending_special)->child != NULL) {
+ gtk_widget_destroy (GTK_BIN (priv->ending_special)->child);
+
+ priv->ending_date_edit = NULL;
+ priv->ending_count_spin = NULL;
+ }
+
+ ending_type = e_dialog_option_menu_get (priv->ending_menu, ending_types_map);
+
+ switch (ending_type) {
+ case ENDING_FOR:
+ make_ending_count_special (rpage);
+ gtk_widget_show (priv->ending_special);
+ break;
+
+ case ENDING_UNTIL:
+ make_ending_until_special (rpage);
+ gtk_widget_show (priv->ending_special);
+ break;
+
+ case ENDING_FOREVER:
+ gtk_widget_hide (priv->ending_special);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* Fills the recurrence ending date widgets with the values from the calendar
+ * component.
+ */
+static void
+fill_ending_date (RecurrencePage *rpage, struct icalrecurrencetype *r)
+{
+ RecurrencePagePrivate *priv;
+ GtkWidget *menu;
+
+ priv = rpage->priv;
+
+ menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (priv->ending_menu));
+ gtk_signal_handler_block_by_data (GTK_OBJECT (menu), rpage);
+
+ if (r->count == 0) {
+ if (r->until.year == 0) {
+ /* Forever */
+
+ e_dialog_option_menu_set (priv->ending_menu,
+ ENDING_FOREVER,
+ ending_types_map);
+ } else {
+ /* Ending date */
+
+ priv->ending_date = icaltime_as_timet (r->until);
+ e_dialog_option_menu_set (priv->ending_menu,
+ ENDING_UNTIL,
+ ending_types_map);
+ }
+ } else {
+ /* Count of occurrences */
+
+ priv->ending_count = r->count;
+ e_dialog_option_menu_set (priv->ending_menu,
+ ENDING_FOR,
+ ending_types_map);
+ }
+
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (menu), rpage);
+
+ make_ending_special (rpage);
+}
+
/* fill_widgets handler for the recurrence page. This function is particularly
* tricky because it has to discriminate between recurrences we support for
* editing and the ones we don't. We only support at most one recurrence rule;
@@ -546,6 +1306,17 @@ recurrence_page_fill_widgets (EditorPage *page, CalComponent *comp)
rpage = RECURRENCE_PAGE (page);
priv = rpage->priv;
+ /* Keep a copy of the component so that we can expand the recurrence set
+ * for the preview.
+ */
+
+ if (priv->comp)
+ gtk_object_unref (GTK_OBJECT (priv->comp));
+
+ priv->comp = cal_component_clone (comp);
+
+ /* Clean the page */
+
clear_widgets (rpage);
fill_exception_widgets (rpage, comp);
@@ -832,6 +1603,80 @@ recurrence_page_fill_widgets (EditorPage *page, CalComponent *comp)
preview_recur (rpage);
}
+/* fill_component handler for the recurrence page */
+static void
+recurrence_page_fill_component (EditorPage *page, CalComponent *comp)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (page);
+ fill_component (rpage, comp);
+}
+
+/* set_summary handler for the recurrence page */
+static void
+recurrence_page_set_summary (EditorPage *page, const char *summary)
+{
+ RecurrencePage *rpage;
+ RecurrencePagePrivate *priv;
+
+ rpage = RECURRENCE_PAGE (page);
+ priv = rpage->priv;
+
+ gtk_signal_handler_block_by_data (GTK_OBJECT (priv->summary), rpage);
+ e_utf8_gtk_entry_set_text (GTK_ENTRY (priv->summary), summary);
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->summary), rpage);
+}
+
+/* get_summary handler for the recurrence page */
+static char *
+recurrence_page_get_summary (EditorPage *page)
+{
+ RecurrencePage *rpage;
+ RecurrencePagePrivate *priv;
+
+ rpage = RECURRENCE_PAGE (page);
+ priv = rpage->priv;
+
+ return e_utf8_gtk_entry_get_text (GTK_ENTRY (priv->summary));
+}
+
+/* set_dates handler for the recurrence page */
+static void
+recurrence_page_set_dates (EditorPage *page, time_t start, time_t end)
+{
+ RecurrencePage *rpage;
+ RecurrencePagePrivate *priv;
+ char str[128];
+ struct tm tm;
+ CalComponentDateTime dt;
+ struct icaltimetype icaltime;
+
+ rpage = RECURRENCE_PAGE (page);
+ priv = rpage->priv;
+
+ /* Set the starting date label */
+
+ tm = *localtime (&start);
+ strftime (str, sizeof (str), _("%A %b %d %Y %H:%M:%S"), &tm);
+
+ gtk_label_set_text (GTK_LABEL (priv->starting_date), str);
+
+ /* Copy the dates to our component */
+
+ if (!priv->comp)
+ return;
+
+ dt.value = &icaltime;
+ dt.tzid = NULL;
+
+ *dt.value = icaltime_from_timet (start, FALSE);
+ cal_component_set_dtstart (priv->comp, &dt);
+
+ *dt.value = icaltime_from_timet (end, FALSE);
+ cal_component_set_dtend (priv->comp, &dt);
+}
+
/* Gets the widgets from the XML file and returns if they are all available. */
@@ -848,7 +1693,7 @@ get_widgets (RecurrencePage *rpage)
toplevel = GW ("recurrence-toplevel");
priv->main = GW ("recurrence-page");
if (!(toplevel && priv->main))
- return NULL;
+ return FALSE;
gtk_widget_ref (priv->main);
gtk_widget_unparent (priv->main);
@@ -898,3 +1743,314 @@ get_widgets (RecurrencePage *rpage)
&& priv->exception_delete
&& priv->preview_bin);
}
+
+/* Callback used when the summary changes; we emit the notification signal. */
+static void
+summary_changed_cb (GtkEditable *editable, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+ editor_page_notify_summary_changed (EDITOR_PAGE (rpage));
+}
+
+/* Callback used when the displayed date range in the recurrence preview
+ * calendar changes.
+ */
+static void
+preview_date_range_changed_cb (ECalendarItem *item, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+ preview_recur (rpage);
+}
+
+/* Callback used when one of the recurrence type radio buttons is toggled. We
+ * enable or disable the recurrence parameters.
+ */
+static void
+type_toggled_cb (GtkToggleButton *toggle, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+
+ if (toggle->active) {
+ sensitize_recur_widgets (rpage);
+ preview_recur (rpage);
+ }
+}
+
+/* Callback used when the recurrence interval value spin button changes. */
+static void
+interval_value_changed_cb (GtkAdjustment *adj, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ preview_recur (rpage);
+}
+
+/* Callback used when the recurrence interval option menu changes. We need to
+ * change the contents of the recurrence special widget.
+ */
+static void
+interval_selection_done_cb (GtkMenuShell *menu_shell, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ make_recurrence_special (rpage);
+ preview_recur (rpage);
+}
+
+/* Callback used when the recurrence ending option menu changes. We need to
+ * change the contents of the ending special widget.
+ */
+static void
+ending_selection_done_cb (GtkMenuShell *menu_shell, gpointer data)
+{
+ RecurrencePage *rpage;
+
+ rpage = RECURRENCE_PAGE (data);
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+ make_ending_special (rpage);
+ preview_recur (rpage);
+}
+
+/* Callback for the "add exception" button */
+static void
+exception_add_cb (GtkWidget *widget, gpointer data)
+{
+ RecurrencePage *rpage;
+ RecurrencePagePrivate *priv;
+ time_t t;
+
+ rpage = RECURRENCE_PAGE (data);
+ priv = rpage->priv;
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+
+ t = e_date_edit_get_time (E_DATE_EDIT (priv->exception_date));
+ append_exception (rpage, t);
+ preview_recur (rpage);
+}
+
+/* Callback for the "modify exception" button */
+static void
+exception_modify_cb (GtkWidget *widget, gpointer data)
+{
+ RecurrencePage *rpage;
+ RecurrencePagePrivate *priv;
+ GtkCList *clist;
+ time_t *t;
+ int sel;
+
+ rpage = RECURRENCE_PAGE (data);
+ priv = rpage->priv;
+
+ clist = GTK_CLIST (priv->exception_list);
+ if (!clist->selection)
+ return;
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+
+ sel = GPOINTER_TO_INT (clist->selection->data);
+
+ t = gtk_clist_get_row_data (clist, sel);
+ *t = e_date_edit_get_time (E_DATE_EDIT (priv->exception_date));
+
+ gtk_clist_set_text (clist, sel, 0, get_exception_string (*t));
+
+ preview_recur (rpage);
+}
+
+/* Callback for the "delete exception" button */
+static void
+exception_delete_cb (GtkWidget *widget, gpointer data)
+{
+ RecurrencePage *rpage;
+ RecurrencePagePrivate *priv;
+ GtkCList *clist;
+ int sel;
+ time_t *t;
+
+ rpage = RECURRENCE_PAGE (data);
+ priv = rpage->priv;
+
+ clist = GTK_CLIST (priv->exception_list);
+ if (!clist->selection)
+ return;
+
+ editor_page_notify_changed (EDITOR_PAGE (rpage));
+
+ sel = GPOINTER_TO_INT (clist->selection->data);
+
+ t = gtk_clist_get_row_data (clist, sel);
+ g_assert (t != NULL);
+ g_free (t);
+
+ gtk_clist_remove (clist, sel);
+ if (sel >= clist->rows)
+ sel--;
+
+ if (clist->rows > 0)
+ gtk_clist_select_row (clist, sel, 0);
+ else {
+ gtk_widget_set_sensitive (priv->exception_modify, FALSE);
+ gtk_widget_set_sensitive (priv->exception_delete, FALSE);
+ }
+
+ preview_recur (rpage);
+}
+
+/* Callback used when a row is selected in the list of exception dates. We must
+ * update the date/time widgets to reflect the exception's value.
+ */
+static void
+exception_select_row_cb (GtkCList *clist, gint row, gint col, GdkEvent *event, gpointer data)
+{
+ RecurrencePage *rpage;
+ RecurrencePagePrivate *priv;
+ time_t *t;
+
+ rpage = RECURRENCE_PAGE (data);
+ priv = rpage->priv;
+
+ t = gtk_clist_get_row_data (clist, row);
+ g_assert (t != NULL);
+
+ e_date_edit_set_time (E_DATE_EDIT (priv->exception_date), *t);
+}
+
+/* Hooks the widget signals */
+static void
+init_widgets (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+ ECalendar *ecal;
+ GtkAdjustment *adj;
+ GtkWidget *menu;
+
+ priv = rpage->priv;
+
+ /* Summary */
+ gtk_signal_connect (GTK_OBJECT (priv->summary), "changed",
+ GTK_SIGNAL_FUNC (summary_changed_cb), rpage);
+
+ /* Recurrence preview */
+
+ priv->preview_calendar = e_calendar_new ();
+ ecal = E_CALENDAR (priv->preview_calendar);
+ gtk_signal_connect (GTK_OBJECT (ecal->calitem), "date_range_changed",
+ GTK_SIGNAL_FUNC (preview_date_range_changed_cb), rpage);
+ calendar_config_configure_e_calendar (ecal);
+ e_calendar_item_set_max_days_sel (ecal->calitem, 0);
+ gtk_container_add (GTK_CONTAINER (priv->preview_bin), priv->preview_calendar);
+ gtk_widget_show (priv->preview_calendar);
+
+ /* Recurrence types */
+
+ gtk_signal_connect (GTK_OBJECT (priv->none), "toggled",
+ GTK_SIGNAL_FUNC (type_toggled_cb), rpage);
+ gtk_signal_connect (GTK_OBJECT (priv->simple), "toggled",
+ GTK_SIGNAL_FUNC (type_toggled_cb), rpage);
+ gtk_signal_connect (GTK_OBJECT (priv->custom), "toggled",
+ GTK_SIGNAL_FUNC (type_toggled_cb), rpage);
+
+ /* Recurrence interval */
+
+ adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->interval_value));
+ gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
+ GTK_SIGNAL_FUNC (interval_value_changed_cb), rpage);
+
+ /* Recurrence units */
+
+ menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (priv->interval_unit));
+ gtk_signal_connect (GTK_OBJECT (menu), "selection_done",
+ GTK_SIGNAL_FUNC (interval_selection_done_cb), rpage);
+
+ /* Recurrence ending */
+
+ menu = gtk_option_menu_get_menu (GTK_OPTION_MENU (priv->ending_menu));
+ gtk_signal_connect (GTK_OBJECT (menu), "selection_done",
+ GTK_SIGNAL_FUNC (ending_selection_done_cb), rpage);
+
+ /* Exception buttons */
+
+ gtk_signal_connect (GTK_OBJECT (priv->exception_add), "clicked",
+ GTK_SIGNAL_FUNC (exception_add_cb), rpage);
+ gtk_signal_connect (GTK_OBJECT (priv->exception_modify), "clicked",
+ GTK_SIGNAL_FUNC (exception_modify_cb), rpage);
+ gtk_signal_connect (GTK_OBJECT (priv->exception_delete), "clicked",
+ GTK_SIGNAL_FUNC (exception_delete_cb), rpage);
+
+ /* Selections in the exceptions list */
+
+ gtk_signal_connect (GTK_OBJECT (priv->exception_list), "select_row",
+ GTK_SIGNAL_FUNC (exception_select_row_cb), rpage);
+}
+
+
+
+/**
+ * recurrence_page_construct:
+ * @rpage: A recurrence page.
+ *
+ * Constructs a recurrence page by loading its Glade data.
+ *
+ * Return value: The same object as @rpage, or NULL if the widgets could not be
+ * created.
+ **/
+RecurrencePage *
+recurrence_page_construct (RecurrencePage *rpage)
+{
+ RecurrencePagePrivate *priv;
+
+ priv = rpage->priv;
+
+ priv->xml = glade_xml_new (EVOLUTION_GLADEDIR "/recurrence-page.glade", NULL);
+ if (!priv->xml) {
+ g_message ("recurrence_page_construct(): Could not load the Glade XML file!");
+ return NULL;
+ }
+
+ if (!get_widgets (rpage)) {
+ g_message ("recurrence_page_construct(): Could not find all widgets in the XML file!");
+ return NULL;
+ }
+
+ init_widgets (rpage);
+
+ return rpage;
+}
+
+/**
+ * recurrence_page_new:
+ *
+ * Creates a new recurrence page.
+ *
+ * Return value: A newly-created recurrence page, or NULL if the page could not
+ * be created.
+ **/
+RecurrencePage *
+recurrence_page_new (void)
+{
+ RecurrencePage *rpage;
+
+ rpage = gtk_type_new (TYPE_RECURRENCE_PAGE);
+ if (!recurrence_page_construct (rpage)) {
+ gtk_object_unref (GTK_OBJECT (rpage));
+ return NULL;
+ }
+
+ return rpage;
+}