Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
recurrence-page.c:2111:26 | clang-analyzer | Access to field 'value' results in a dereference of a null pointer (loaded from field 'start') | ||
recurrence-page.c:2111:26 | clang-analyzer | Access to field 'value' results in a dereference of a null pointer (loaded from field 'start') | ||
recurrence-page.c:2112:24 | clang-analyzer | Access to field 'value' results in a dereference of a null pointer (loaded from field 'end') | ||
recurrence-page.c:2112:24 | clang-analyzer | Access to field 'value' results in a dereference of a null pointer (loaded from field 'end') |
1 /*
2 * Evolution calendar - Recurrence page of the calendar component dialogs
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
16 *
17 *
18 * Authors:
19 * Federico Mena-Quintero <federico@ximian.com>
20 * Miguel de Icaza <miguel@ximian.com>
21 * Seth Alves <alves@hungry.com>
22 * JP Rosevear <jpr@ximian.com>
23 * Hans Petter Jansson <hpj@ximiman.com>
24 *
25 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
26 *
27 */
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35
36 #include <misc/e-dateedit.h>
37 #include "../tag-calendar.h"
38 #include "../weekday-picker.h"
39 #include "comp-editor-util.h"
40 #include "../e-date-time-list.h"
41 #include "recurrence-page.h"
42
43 #include "e-util/e-util.h"
44 #include "e-util/e-dialog-widgets.h"
45 #include "e-util/e-util-private.h"
46
47 #define RECURRENCE_PAGE_GET_PRIVATE(obj) \
48 (G_TYPE_INSTANCE_GET_PRIVATE \
49 ((obj), TYPE_RECURRENCE_PAGE, RecurrencePagePrivate))
50
51 enum month_num_options {
52 MONTH_NUM_FIRST,
53 MONTH_NUM_SECOND,
54 MONTH_NUM_THIRD,
55 MONTH_NUM_FOURTH,
56 MONTH_NUM_FIFTH,
57 MONTH_NUM_LAST,
58 MONTH_NUM_DAY,
59 MONTH_NUM_OTHER
60 };
61
62 static const gint month_num_options_map[] = {
63 MONTH_NUM_FIRST,
64 MONTH_NUM_SECOND,
65 MONTH_NUM_THIRD,
66 MONTH_NUM_FOURTH,
67 MONTH_NUM_FIFTH,
68 MONTH_NUM_LAST,
69 MONTH_NUM_DAY,
70 MONTH_NUM_OTHER,
71 -1
72 };
73
74 enum month_day_options {
75 MONTH_DAY_NTH,
76 MONTH_DAY_MON,
77 MONTH_DAY_TUE,
78 MONTH_DAY_WED,
79 MONTH_DAY_THU,
80 MONTH_DAY_FRI,
81 MONTH_DAY_SAT,
82 MONTH_DAY_SUN
83 };
84
85 static const gint month_day_options_map[] = {
86 MONTH_DAY_NTH,
87 MONTH_DAY_MON,
88 MONTH_DAY_TUE,
89 MONTH_DAY_WED,
90 MONTH_DAY_THU,
91 MONTH_DAY_FRI,
92 MONTH_DAY_SAT,
93 MONTH_DAY_SUN,
94 -1
95 };
96
97 enum recur_type {
98 RECUR_NONE,
99 RECUR_SIMPLE,
100 RECUR_CUSTOM
101 };
102
103 static const gint type_map[] = {
104 RECUR_NONE,
105 RECUR_SIMPLE,
106 RECUR_CUSTOM,
107 -1
108 };
109
110 static const gint freq_map[] = {
111 ICAL_DAILY_RECURRENCE,
112 ICAL_WEEKLY_RECURRENCE,
113 ICAL_MONTHLY_RECURRENCE,
114 ICAL_YEARLY_RECURRENCE,
115 -1
116 };
117
118 enum ending_type {
119 ENDING_FOR,
120 ENDING_UNTIL,
121 ENDING_FOREVER
122 };
123
124 static const gint ending_types_map[] = {
125 ENDING_FOR,
126 ENDING_UNTIL,
127 ENDING_FOREVER,
128 -1
129 };
130
131 /* Private part of the RecurrencePage structure */
132 struct _RecurrencePagePrivate {
133 /* Component we use to expand the recurrence rules for the preview */
134 ECalComponent *comp;
135
136 GtkBuilder *builder;
137
138 /* Widgets from the UI file */
139 GtkWidget *main;
140
141 GtkWidget *recurs;
142 gboolean custom;
143
144 GtkWidget *params;
145 GtkWidget *interval_value;
146 GtkWidget *interval_unit_combo;
147 GtkWidget *special;
148 GtkWidget *ending_combo;
149 GtkWidget *ending_special;
150 GtkWidget *custom_warning_bin;
151
152 /* For weekly recurrences, created by hand */
153 GtkWidget *weekday_picker;
154 guint8 weekday_day_mask;
155 guint8 weekday_blocked_day_mask;
156
157 /* For monthly recurrences, created by hand */
158 gint month_index;
159
160 GtkWidget *month_day_combo;
161 enum month_day_options month_day;
162
163 GtkWidget *month_num_combo;
164 enum month_num_options month_num;
165
166 /* For ending date, created by hand */
167 GtkWidget *ending_date_edit;
168 struct icaltimetype ending_date_tt;
169
170 /* For ending count of occurrences, created by hand */
171 GtkWidget *ending_count_spin;
172 gint ending_count;
173
174 /* More widgets from the Glade file */
175 GtkWidget *exception_list; /* This is a GtkTreeView now */
176 GtkWidget *exception_add;
177 GtkWidget *exception_modify;
178 GtkWidget *exception_delete;
179
180 GtkWidget *preview_bin;
181
182 /* Store for exception_list */
183 EDateTimeList *exception_list_store;
184
185 /* For the recurrence preview, the actual widget */
186 GtkWidget *preview_calendar;
187
188 /* This just holds some settings we need */
189 EMeetingStore *meeting_store;
190
191 GCancellable *cancellable;
192 };
193
194 static void recurrence_page_finalize (GObject *object);
195
196 static gboolean fill_component (RecurrencePage *rpage, ECalComponent *comp);
197 static GtkWidget *recurrence_page_get_widget (CompEditorPage *page);
198 static void recurrence_page_focus_main_widget (CompEditorPage *page);
199 static gboolean recurrence_page_fill_widgets (CompEditorPage *page, ECalComponent *comp);
200 static gboolean recurrence_page_fill_component (CompEditorPage *page, ECalComponent *comp);
201 static void recurrence_page_set_dates (CompEditorPage *page, CompEditorPageDates *dates);
202 static void preview_date_range_changed_cb (ECalendarItem *item, RecurrencePage *rpage);
203
204 static void make_ending_count_special (RecurrencePage *rpage);
205 static void make_ending_special (RecurrencePage *rpage);
206
207 G_DEFINE_TYPE (RecurrencePage, recurrence_page, TYPE_COMP_EDITOR_PAGE)
208
209 /* Re-tags the recurrence preview calendar based on the current information of
210 * the widgets in the recurrence page.
211 */
212 static void
213 preview_recur (RecurrencePage *rpage)
214 {
215 RecurrencePagePrivate *priv = rpage->priv;
216 CompEditor *editor;
217 ECalClient *client;
218 ECalComponent *comp;
219 ECalComponentDateTime cdt;
220 GSList *l;
221 icaltimezone *zone = NULL;
222
223 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
224 client = comp_editor_get_client (editor);
225
226 /* If our component has not been set yet through ::fill_widgets(), we
227 * cannot preview the recurrence.
228 */
229 if (!priv || !priv->comp || e_cal_component_is_instance (priv->comp))
230 return;
231
232 /* Create a scratch component with the start/end and
233 * recurrence/exception information from the one we are editing.
234 */
235
236 comp = e_cal_component_new ();
237 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
238
239 e_cal_component_get_dtstart (priv->comp, &cdt);
240 if (cdt.tzid != NULL) {
241 /* FIXME Will e_cal_client_get_timezone_sync really not return builtin zones? */
242 if (!e_cal_client_get_timezone_sync (client, cdt.tzid, &zone, NULL, NULL))
243 zone = icaltimezone_get_builtin_timezone_from_tzid (cdt.tzid);
244 }
245 e_cal_component_set_dtstart (comp, &cdt);
246 e_cal_component_free_datetime (&cdt);
247
248 e_cal_component_get_dtend (priv->comp, &cdt);
249 e_cal_component_set_dtend (comp, &cdt);
250 e_cal_component_free_datetime (&cdt);
251
252 e_cal_component_get_exdate_list (priv->comp, &l);
253 e_cal_component_set_exdate_list (comp, l);
254 e_cal_component_free_exdate_list (l);
255
256 e_cal_component_get_exrule_list (priv->comp, &l);
257 e_cal_component_set_exrule_list (comp, l);
258 e_cal_component_free_recur_list (l);
259
260 e_cal_component_get_rdate_list (priv->comp, &l);
261 e_cal_component_set_rdate_list (comp, l);
262 e_cal_component_free_period_list (l);
263
264 e_cal_component_get_rrule_list (priv->comp, &l);
265 e_cal_component_set_rrule_list (comp, l);
266 e_cal_component_free_recur_list (l);
267
268 fill_component (rpage, comp);
269
270 tag_calendar_by_comp (
271 E_CALENDAR (priv->preview_calendar), comp,
272 client, zone, TRUE, FALSE, FALSE, priv->cancellable);
273 g_object_unref (comp);
274 }
275
276 static GObject *
277 recurrence_page_constructor (GType type,
278 guint n_construct_properties,
279 GObjectConstructParam *construct_properties)
280 {
281 GObject *object;
282 CompEditor *editor;
283
284 /* Chain up to parent's constructor() method. */
285 object = G_OBJECT_CLASS (recurrence_page_parent_class)->constructor (
286 type, n_construct_properties, construct_properties);
287
288 /* Keep the calendar updated as the user twizzles widgets. */
289 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (object));
290
291 g_signal_connect_swapped (
292 editor, "notify::changed",
293 G_CALLBACK (preview_recur), object);
294
295 return object;
296 }
297
298 static void
299 recurrence_page_dispose (GObject *object)
300 {
301 RecurrencePagePrivate *priv;
302
303 priv = RECURRENCE_PAGE_GET_PRIVATE (object);
304
305 if (priv->main != NULL) {
306 g_object_unref (priv->main);
307 priv->main = NULL;
308 }
309
310 if (priv->builder != NULL) {
311 g_object_unref (priv->builder);
312 priv->builder = NULL;
313 }
314
315 if (priv->comp != NULL) {
316 g_object_unref (priv->comp);
317 priv->comp = NULL;
318 }
319
320 if (priv->exception_list_store != NULL) {
321 g_object_unref (priv->exception_list_store);
322 priv->exception_list_store = NULL;
323 }
324
325 if (priv->meeting_store != NULL) {
326 g_object_unref (priv->meeting_store);
327 priv->meeting_store = NULL;
328 }
329
330 if (priv->cancellable) {
331 g_cancellable_cancel (priv->cancellable);
332 g_object_unref (priv->cancellable);
333 priv->cancellable = NULL;
334 }
335
336 /* Chain up to parent's dispose() method. */
337 G_OBJECT_CLASS (recurrence_page_parent_class)->dispose (object);
338 }
339
340 static void
341 recurrence_page_finalize (GObject *object)
342 {
343 RecurrencePagePrivate *priv;
344
345 priv = RECURRENCE_PAGE_GET_PRIVATE (object);
346
347 g_signal_handlers_disconnect_matched (
348 E_CALENDAR (priv->preview_calendar)->calitem,
349 G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
350 preview_date_range_changed_cb, NULL);
351
352 g_signal_handlers_disconnect_matched (
353 priv->interval_unit_combo, G_SIGNAL_MATCH_DATA,
354 0, 0, NULL, NULL, object);
355
356 g_signal_handlers_disconnect_matched (
357 priv->ending_combo, G_SIGNAL_MATCH_DATA,
358 0, 0, NULL, NULL, object);
359
360 /* Chain up to parent's finalize() method. */
361 G_OBJECT_CLASS (recurrence_page_parent_class)->finalize (object);
362 }
363
364 static void
365 recurrence_page_class_init (RecurrencePageClass *class)
366 {
367 GObjectClass *object_class;
368 CompEditorPageClass *editor_page_class;
369
370 g_type_class_add_private (class, sizeof (RecurrencePagePrivate));
371
372 object_class = G_OBJECT_CLASS (class);
373 object_class->constructor = recurrence_page_constructor;
374 object_class->dispose = recurrence_page_dispose;
375 object_class->finalize = recurrence_page_finalize;
376
377 editor_page_class = COMP_EDITOR_PAGE_CLASS (class);
378 editor_page_class->get_widget = recurrence_page_get_widget;
379 editor_page_class->focus_main_widget = recurrence_page_focus_main_widget;
380 editor_page_class->fill_widgets = recurrence_page_fill_widgets;
381 editor_page_class->fill_component = recurrence_page_fill_component;
382 editor_page_class->set_dates = recurrence_page_set_dates;
383
384 }
385
386 static void
387 recurrence_page_init (RecurrencePage *rpage)
388 {
389 rpage->priv = RECURRENCE_PAGE_GET_PRIVATE (rpage);
390
391 rpage->priv->cancellable = g_cancellable_new ();
392 }
393
394 /* get_widget handler for the recurrence page */
395 static GtkWidget *
396 recurrence_page_get_widget (CompEditorPage *page)
397 {
398 RecurrencePagePrivate *priv;
399
400 priv = RECURRENCE_PAGE_GET_PRIVATE (page);
401
402 return priv->main;
403 }
404
405 /* focus_main_widget handler for the recurrence page */
406 static void
407 recurrence_page_focus_main_widget (CompEditorPage *page)
408 {
409 RecurrencePagePrivate *priv;
410
411 priv = RECURRENCE_PAGE_GET_PRIVATE (page);
412
413 gtk_widget_grab_focus (priv->recurs);
414 }
415
416 /* Fills the widgets with default values */
417 static void
418 clear_widgets (RecurrencePage *rpage)
419 {
420 RecurrencePagePrivate *priv;
421 GtkAdjustment *adj;
422
423 priv = rpage->priv;
424
425 priv->custom = FALSE;
426
427 priv->weekday_day_mask = 0;
428
429 priv->month_index = 1;
430 priv->month_num = MONTH_NUM_DAY;
431 priv->month_day = MONTH_DAY_NTH;
432
433 g_signal_handlers_block_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
434 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->recurs), FALSE);
435 g_signal_handlers_unblock_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
436
437 adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->interval_value));
438 g_signal_handlers_block_matched (adj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
439 gtk_spin_button_set_value (
440 GTK_SPIN_BUTTON (priv->interval_value), 1);
441 g_signal_handlers_unblock_matched (adj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
442
443 g_signal_handlers_block_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
444 e_dialog_combo_box_set (
445 priv->interval_unit_combo,
446 ICAL_DAILY_RECURRENCE,
447 freq_map);
448 g_signal_handlers_unblock_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
449
450 priv->ending_date_tt = icaltime_today ();
451 priv->ending_count = 2;
452
453 g_signal_handlers_block_matched (priv->ending_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
454 e_dialog_combo_box_set (
455 priv->ending_combo,
456 priv->ending_count == -1 ? ENDING_FOREVER : ENDING_FOR,
457 ending_types_map);
458 g_signal_handlers_unblock_matched (priv->ending_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
459 if (priv->ending_count == -1)
460 priv->ending_count = 2;
461 make_ending_special (rpage);
462 /* Exceptions list */
463 e_date_time_list_clear (priv->exception_list_store);
464 }
465
466 /* Appends an exception date to the list */
467 static void
468 append_exception (RecurrencePage *rpage,
469 ECalComponentDateTime *datetime)
470 {
471 RecurrencePagePrivate *priv;
472 GtkTreeView *view;
473 GtkTreeIter iter;
474
475 priv = rpage->priv;
476 view = GTK_TREE_VIEW (priv->exception_list);
477
478 e_date_time_list_append (priv->exception_list_store, &iter, datetime);
479 gtk_tree_selection_select_iter (gtk_tree_view_get_selection (view), &iter);
480 }
481
482 /* Fills in the exception widgets with the data from the calendar component */
483 static void
484 fill_exception_widgets (RecurrencePage *rpage,
485 ECalComponent *comp)
486 {
487 GSList *list, *l;
488
489 e_cal_component_get_exdate_list (comp, &list);
490
491 for (l = list; l; l = l->next) {
492 ECalComponentDateTime *cdt;
493
494 cdt = l->data;
495 append_exception (rpage, cdt);
496 }
497
498 e_cal_component_free_exdate_list (list);
499 }
500
501 /* Computes a weekday mask for the start day of a calendar component,
502 * for use in a WeekdayPicker widget.
503 */
504 static guint8
505 get_start_weekday_mask (ECalComponent *comp)
506 {
507 ECalComponentDateTime dt;
508 guint8 retval;
509
510 e_cal_component_get_dtstart (comp, &dt);
511
512 if (dt.value) {
513 gshort weekday;
514
515 weekday = icaltime_day_of_week (*dt.value);
516 retval = 0x1 << (weekday - 1);
517 } else
518 retval = 0;
519
520 e_cal_component_free_datetime (&dt);
521
522 return retval;
523 }
524
525 /* Sets some sane defaults for the data sources for the recurrence special
526 * widgets, even if they will not be used immediately.
527 */
528 static void
529 set_special_defaults (RecurrencePage *rpage)
530 {
531 RecurrencePagePrivate *priv;
532 guint8 mask;
533
534 priv = rpage->priv;
535
536 mask = get_start_weekday_mask (priv->comp);
537
538 priv->weekday_day_mask = mask;
539 priv->weekday_blocked_day_mask = mask;
540 }
541
542 /* Sensitizes the recurrence widgets based on the state of the recurrence type
543 * radio group.
544 */
545 static void
546 sensitize_recur_widgets (RecurrencePage *rpage)
547 {
548 RecurrencePagePrivate *priv = rpage->priv;
549 CompEditor *editor;
550 CompEditorFlags flags;
551 gboolean recurs, sens = TRUE;
552 GtkWidget *child;
553 GtkWidget *label;
554
555 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
556 flags = comp_editor_get_flags (editor);
557
558 if (flags & COMP_EDITOR_MEETING)
559 sens = flags & COMP_EDITOR_USER_ORG;
560
561 recurs = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->recurs));
562
563 /* We can't preview that well for instances right now */
564 if (e_cal_component_is_instance (priv->comp))
565 gtk_widget_set_sensitive (priv->preview_calendar, FALSE);
566 else
567 gtk_widget_set_sensitive (priv->preview_calendar, TRUE && sens);
568
569 child = gtk_bin_get_child (GTK_BIN (priv->custom_warning_bin));
570 if (child != NULL)
571 gtk_widget_destroy (child);
572
573 if (recurs && priv->custom) {
574 gtk_widget_set_sensitive (priv->params, FALSE);
575 gtk_widget_hide (priv->params);
576
577 label = gtk_label_new (
578 _("This appointment contains "
579 "recurrences that Evolution "
580 "cannot edit."));
581 gtk_container_add (
582 GTK_CONTAINER (priv->custom_warning_bin),
583 label);
584 gtk_widget_show_all (priv->custom_warning_bin);
585 } else if (recurs) {
586 gtk_widget_set_sensitive (priv->params, sens);
587 gtk_widget_show (priv->params);
588 gtk_widget_hide (priv->custom_warning_bin);
589 } else {
590 gtk_widget_set_sensitive (priv->params, FALSE);
591 gtk_widget_show (priv->params);
592 gtk_widget_hide (priv->custom_warning_bin);
593 }
594 }
595
596 static void
597 update_with_readonly (RecurrencePage *rpage,
598 gboolean read_only)
599 {
600 RecurrencePagePrivate *priv = rpage->priv;
601 CompEditor *editor;
602 CompEditorFlags flags;
603 gint selected_rows;
604 gboolean sensitize = TRUE;
605
606 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
607 flags = comp_editor_get_flags (editor);
608
609 if (flags & COMP_EDITOR_MEETING)
610 sensitize = flags & COMP_EDITOR_USER_ORG;
611
612 selected_rows = gtk_tree_selection_count_selected_rows (
613 gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->exception_list)));
614
615 if (!read_only)
616 sensitize_recur_widgets (rpage);
617 else
618 gtk_widget_set_sensitive (priv->params, FALSE);
619
620 gtk_widget_set_sensitive (priv->recurs, !read_only && sensitize);
621 gtk_widget_set_sensitive (priv->exception_add, !read_only && sensitize && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->recurs)));
622 gtk_widget_set_sensitive (priv->exception_modify, !read_only && selected_rows > 0 && sensitize);
623 gtk_widget_set_sensitive (priv->exception_delete, !read_only && selected_rows > 0 && sensitize);
624 }
625
626 static void
627 rpage_get_objects_for_uid_cb (GObject *source_object,
628 GAsyncResult *result,
629 gpointer user_data)
630 {
631 ECalClient *client = E_CAL_CLIENT (source_object);
632 RecurrencePage *rpage = user_data;
633 GSList *ecalcomps = NULL;
634 GError *error = NULL;
635
636 if (result && !e_cal_client_get_objects_for_uid_finish (client, result, &ecalcomps, &error)) {
637 ecalcomps = NULL;
638 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
639 g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
640 g_clear_error (&error);
641 return;
642 }
643
644 g_clear_error (&error);
645 }
646
647 update_with_readonly (rpage, g_slist_length (ecalcomps) > 1);
648
649 g_slist_foreach (ecalcomps, (GFunc) g_object_unref, NULL);
650 g_slist_free (ecalcomps);
651 }
652
653 static void
654 rpage_get_object_cb (GObject *source_object,
655 GAsyncResult *result,
656 gpointer user_data)
657 {
658 ECalClient *client = E_CAL_CLIENT (source_object);
659 RecurrencePage *rpage = user_data;
660 icalcomponent *icalcomp = NULL;
661 const gchar *uid = NULL;
662 GError *error = NULL;
663
664 if (result && !e_cal_client_get_object_finish (client, result, &icalcomp, &error)) {
665 icalcomp = NULL;
666 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
667 g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
668 g_clear_error (&error);
669 return;
670 }
671
672 g_clear_error (&error);
673 }
674
675 if (icalcomp) {
676 icalcomponent_free (icalcomp);
677 update_with_readonly (rpage, TRUE);
678 return;
679 }
680
681 if (rpage->priv->comp)
682 e_cal_component_get_uid (rpage->priv->comp, &uid);
683
684 if (!uid || !*uid) {
685 update_with_readonly (rpage, FALSE);
686 return;
687 }
688
689 /* see if we have detached instances */
690 e_cal_client_get_objects_for_uid (client, uid, rpage->priv->cancellable, rpage_get_objects_for_uid_cb, rpage);
691 }
692
693 static void
694 sensitize_buttons (RecurrencePage *rpage)
695 {
696 RecurrencePagePrivate *priv = rpage->priv;
697 CompEditor *editor;
698 ECalClient *client;
699 const gchar *uid;
700
701 if (priv->comp == NULL)
702 return;
703
704 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
705 client = comp_editor_get_client (editor);
706
707 if (e_client_is_readonly (E_CLIENT (client))) {
708 update_with_readonly (rpage, TRUE);
709 return;
710 }
711
712 if (priv->cancellable) {
713 g_cancellable_cancel (priv->cancellable);
714 g_object_unref (priv->cancellable);
715 }
716 priv->cancellable = g_cancellable_new ();
717
718 e_cal_component_get_uid (priv->comp, &uid);
719 if (!uid || !*uid) {
720 update_with_readonly (rpage, FALSE);
721 return;
722 }
723
724 if (e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_NO_CONV_TO_RECUR)) {
725 e_cal_client_get_object (client, uid, NULL, priv->cancellable, rpage_get_object_cb, rpage);
726 } else {
727 rpage_get_object_cb (G_OBJECT (client), NULL, rpage);
728 }
729 }
730
731 /* Gets the simple recurrence data from the recurrence widgets and stores it in
732 * the calendar component.
733 */
734 static void
735 simple_recur_to_comp (RecurrencePage *rpage,
736 ECalComponent *comp)
737 {
738 RecurrencePagePrivate *priv;
739 struct icalrecurrencetype r;
740 GSList l;
741 enum ending_type ending_type;
742 gboolean date_set;
743
744 priv = rpage->priv;
745
746 icalrecurrencetype_clear (&r);
747
748 /* Frequency, interval, week start */
749
750 r.freq = e_dialog_combo_box_get (priv->interval_unit_combo, freq_map);
751 r.interval = gtk_spin_button_get_value_as_int (
752 GTK_SPIN_BUTTON (priv->interval_value));
753 r.week_start = ICAL_SUNDAY_WEEKDAY
754 + e_meeting_store_get_week_start_day (priv->meeting_store);
755
756 /* Frequency-specific data */
757
758 switch (r.freq) {
759 case ICAL_DAILY_RECURRENCE:
760 /* Nothing else is required */
761 break;
762
763 case ICAL_WEEKLY_RECURRENCE: {
764 guint8 day_mask;
765 gint i;
766
767 g_return_if_fail (gtk_bin_get_child (GTK_BIN (priv->special)) != NULL);
768 g_return_if_fail (priv->weekday_picker != NULL);
769 g_return_if_fail (IS_WEEKDAY_PICKER (priv->weekday_picker));
770
771 day_mask = weekday_picker_get_days (WEEKDAY_PICKER (priv->weekday_picker));
772
773 i = 0;
774
775 if (day_mask & (1 << 0))
776 r.by_day[i++] = ICAL_SUNDAY_WEEKDAY;
777
778 if (day_mask & (1 << 1))
779 r.by_day[i++] = ICAL_MONDAY_WEEKDAY;
780
781 if (day_mask & (1 << 2))
782 r.by_day[i++] = ICAL_TUESDAY_WEEKDAY;
783
784 if (day_mask & (1 << 3))
785 r.by_day[i++] = ICAL_WEDNESDAY_WEEKDAY;
786
787 if (day_mask & (1 << 4))
788 r.by_day[i++] = ICAL_THURSDAY_WEEKDAY;
789
790 if (day_mask & (1 << 5))
791 r.by_day[i++] = ICAL_FRIDAY_WEEKDAY;
792
793 if (day_mask & (1 << 6))
794 r.by_day[i] = ICAL_SATURDAY_WEEKDAY;
795
796 break;
797 }
798
799 case ICAL_MONTHLY_RECURRENCE: {
800 enum month_num_options month_num;
801 enum month_day_options month_day;
802
803 g_return_if_fail (gtk_bin_get_child (GTK_BIN (priv->special)) != NULL);
804 g_return_if_fail (priv->month_day_combo != NULL);
805 g_return_if_fail (GTK_IS_COMBO_BOX (priv->month_day_combo));
806 g_return_if_fail (priv->month_num_combo != NULL);
807 g_return_if_fail (GTK_IS_COMBO_BOX (priv->month_num_combo));
808
809 month_num = e_dialog_combo_box_get (
810 priv->month_num_combo,
811 month_num_options_map);
812 month_day = e_dialog_combo_box_get (
813 priv->month_day_combo,
814 month_day_options_map);
815
816 if (month_num == MONTH_NUM_LAST)
817 month_num = -1;
818 else
819 month_num++;
820
821 switch (month_day) {
822 case MONTH_DAY_NTH:
823 if (month_num == -1)
824 r.by_month_day[0] = -1;
825 else
826 r.by_month_day[0] = priv->month_index;
827 break;
828
829 /* Outlook 2000 uses BYDAY=TU;BYSETPOS=2, and will not
830 * accept BYDAY=2TU. So we now use the same as Outlook
831 * by default. */
832 case MONTH_DAY_MON:
833 r.by_day[0] = ICAL_MONDAY_WEEKDAY;
834 r.by_set_pos[0] = month_num;
835 break;
836
837 case MONTH_DAY_TUE:
838 r.by_day[0] = ICAL_TUESDAY_WEEKDAY;
839 r.by_set_pos[0] = month_num;
840 break;
841
842 case MONTH_DAY_WED:
843 r.by_day[0] = ICAL_WEDNESDAY_WEEKDAY;
844 r.by_set_pos[0] = month_num;
845 break;
846
847 case MONTH_DAY_THU:
848 r.by_day[0] = ICAL_THURSDAY_WEEKDAY;
849 r.by_set_pos[0] = month_num;
850 break;
851
852 case MONTH_DAY_FRI:
853 r.by_day[0] = ICAL_FRIDAY_WEEKDAY;
854 r.by_set_pos[0] = month_num;
855 break;
856
857 case MONTH_DAY_SAT:
858 r.by_day[0] = ICAL_SATURDAY_WEEKDAY;
859 r.by_set_pos[0] = month_num;
860 break;
861
862 case MONTH_DAY_SUN:
863 r.by_day[0] = ICAL_SUNDAY_WEEKDAY;
864 r.by_set_pos[0] = month_num;
865 break;
866
867 default:
868 g_return_if_reached ();
869 }
870
871 break;
872 }
873
874 case ICAL_YEARLY_RECURRENCE:
875 /* Nothing else is required */
876 break;
877
878 default:
879 g_return_if_reached ();
880 }
881
882 /* Ending date */
883
884 ending_type = e_dialog_combo_box_get (priv->ending_combo, ending_types_map);
885
886 switch (ending_type) {
887 case ENDING_FOR:
888 g_return_if_fail (priv->ending_count_spin != NULL);
889 g_return_if_fail (GTK_IS_SPIN_BUTTON (priv->ending_count_spin));
890
891 r.count = gtk_spin_button_get_value_as_int (
892 GTK_SPIN_BUTTON (priv->ending_count_spin));
893 break;
894
895 case ENDING_UNTIL:
896 g_return_if_fail (priv->ending_date_edit != NULL);
897 g_return_if_fail (E_IS_DATE_EDIT (priv->ending_date_edit));
898
899 /* We only allow a DATE value to be set for the UNTIL property,
900 * since we don't support sub-day recurrences. */
901 date_set = e_date_edit_get_date (
902 E_DATE_EDIT (priv->ending_date_edit),
903 &r.until.year,
904 &r.until.month,
905 &r.until.day);
906 g_return_if_fail (date_set);
907
908 r.until.is_date = 1;
909
910 break;
911
912 case ENDING_FOREVER:
913 /* Nothing to be done */
914 break;
915
916 default:
917 g_return_if_reached ();
918 }
919
920 /* Set the recurrence */
921
922 l.data = &r;
923 l.next = NULL;
924
925 e_cal_component_set_rrule_list (comp, &l);
926 }
927
928 /* Fills a component with the data from the recurrence page; in the case of a
929 * custom recurrence, it leaves it intact.
930 */
931 static gboolean
932 fill_component (RecurrencePage *rpage,
933 ECalComponent *comp)
934 {
935 RecurrencePagePrivate *priv;
936 gboolean recurs;
937 GtkTreeModel *model;
938 GtkTreeIter iter;
939 gboolean valid_iter;
940 GSList *list;
941
942 priv = rpage->priv;
943 model = GTK_TREE_MODEL (priv->exception_list_store);
944
945 recurs = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->recurs));
946
947 if (recurs && priv->custom) {
948 /* We just keep whatever the component has currently */
949 } else if (recurs) {
950 e_cal_component_set_rdate_list (comp, NULL);
951 e_cal_component_set_exrule_list (comp, NULL);
952 simple_recur_to_comp (rpage, comp);
953 } else {
954 e_cal_component_set_rdate_list (comp, NULL);
955 e_cal_component_set_rrule_list (comp, NULL);
956 e_cal_component_set_exrule_list (comp, NULL);
957 e_cal_component_set_recurid (comp, NULL);
958 }
959
960 /* Set exceptions */
961
962 list = NULL;
963
964 for (valid_iter = gtk_tree_model_get_iter_first (model, &iter); valid_iter;
965 valid_iter = gtk_tree_model_iter_next (model, &iter)) {
966 const ECalComponentDateTime *dt;
967 ECalComponentDateTime *cdt;
968
969 cdt = g_new (ECalComponentDateTime, 1);
970 cdt->value = g_new (struct icaltimetype, 1);
971
972 dt = e_date_time_list_get_date_time (E_DATE_TIME_LIST (model), &iter);
973 g_return_val_if_fail (dt != NULL, FALSE);
974
975 if (!icaltime_is_valid_time (*dt->value)) {
976 comp_editor_page_display_validation_error (
977 COMP_EDITOR_PAGE (rpage),
978 _("Recurrence date is invalid"),
979 priv->exception_list);
980 return FALSE;
981 }
982
983 *cdt->value = *dt->value;
984 cdt->tzid = g_strdup (dt->tzid);
985
986 list = g_slist_prepend (list, cdt);
987 }
988
989 e_cal_component_set_exdate_list (comp, list);
990 e_cal_component_free_exdate_list (list);
991
992 if (gtk_widget_get_visible (priv->ending_combo) && gtk_widget_get_sensitive (priv->ending_combo) &&
993 e_dialog_combo_box_get (priv->ending_combo, ending_types_map) == ENDING_UNTIL) {
994 /* check whether the "until" date is in the future */
995 struct icaltimetype tt;
996 gboolean ok = TRUE;
997
998 if (e_date_edit_get_date (E_DATE_EDIT (priv->ending_date_edit), &tt.year, &tt.month, &tt.day)) {
999 ECalComponentDateTime dtstart;
1000
1001 /* the dtstart should be set already */
1002 e_cal_component_get_dtstart (comp, &dtstart);
1003
1004 tt.is_date = 1;
1005 tt.zone = NULL;
1006
1007 if (dtstart.value && icaltime_is_valid_time (*dtstart.value)) {
1008 ok = icaltime_compare_date_only (*dtstart.value, tt) <= 0;
1009
1010 if (!ok)
1011 e_date_edit_set_date (E_DATE_EDIT (priv->ending_date_edit), dtstart.value->year, dtstart.value->month, dtstart.value->day);
1012 else {
1013 /* to have the date shown in "normalized" format */
1014 e_date_edit_set_date (E_DATE_EDIT (priv->ending_date_edit), tt.year, tt.month, tt.day);
1015 }
1016 }
1017
1018 e_cal_component_free_datetime (&dtstart);
1019 }
1020
1021 if (!ok) {
1022 comp_editor_page_display_validation_error (COMP_EDITOR_PAGE (rpage), _("End time of the recurrence was before event's start"), priv->ending_date_edit);
1023 return FALSE;
1024 }
1025 }
1026
1027 return TRUE;
1028 }
1029
1030 /* Creates the special contents for weekly recurrences */
1031 static void
1032 make_weekly_special (RecurrencePage *rpage)
1033 {
1034 RecurrencePagePrivate *priv;
1035 GtkWidget *hbox;
1036 GtkWidget *label;
1037 WeekdayPicker *wp;
1038 gint week_start_day;
1039
1040 priv = rpage->priv;
1041
1042 g_return_if_fail (gtk_bin_get_child (GTK_BIN (priv->special)) == NULL);
1043 g_return_if_fail (priv->weekday_picker == NULL);
1044
1045 /* Create the widgets */
1046
1047 hbox = gtk_hbox_new (FALSE, 2);
1048 gtk_container_add (GTK_CONTAINER (priv->special), hbox);
1049
1050 /* TRANSLATORS: Entire string is for example: This appointment recurs/Every [x] week(s) on [Wednesday] [forever]'
1051 * (dropdown menu options are in [square brackets]). This means that after the 'on', name of a week day always follows. */
1052 label = gtk_label_new (_("on"));
1053 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
1054
1055 wp = WEEKDAY_PICKER (weekday_picker_new ());
1056
1057 priv->weekday_picker = GTK_WIDGET (wp);
1058 gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (wp), FALSE, FALSE, 6);
1059
1060 gtk_widget_show_all (hbox);
1061
1062 /* Set the weekdays */
1063
1064 week_start_day = e_meeting_store_get_week_start_day (priv->meeting_store);
1065 weekday_picker_set_week_start_day (wp, week_start_day);
1066 weekday_picker_set_days (wp, priv->weekday_day_mask);
1067
1068 g_signal_connect_swapped (
1069 wp, "changed",
1070 G_CALLBACK (comp_editor_page_changed), rpage);
1071 }
1072
1073 /* Creates the subtree for the monthly recurrence number */
1074 static void
1075 make_recur_month_num_subtree (GtkTreeStore *store,
1076 GtkTreeIter *par,
1077 const gchar *title,
1078 gint start,
1079 gint end)
1080 {
1081 GtkTreeIter iter, parent;
1082 gint i;
1083
1084 gtk_tree_store_append (store, &parent, par);
1085 gtk_tree_store_set (store, &parent, 0, _(title), 1, -1, -1);
1086
1087 for (i = start; i < end; i++) {
1088 gtk_tree_store_append (store, &iter, &parent);
1089 gtk_tree_store_set (store, &iter, 0, _(e_cal_recur_nth[i]), 1, i + 1, -1);
1090 }
1091 }
1092
1093 static void
1094 only_leaf_sensitive (GtkCellLayout *cell_layout,
1095 GtkCellRenderer *cell,
1096 GtkTreeModel *tree_model,
1097 GtkTreeIter *iter,
1098 gpointer data)
1099 {
1100 gboolean sensitive;
1101
1102 sensitive = !gtk_tree_model_iter_has_child (tree_model, iter);
1103
1104 g_object_set (cell, "sensitive", sensitive, NULL);
1105 }
1106
1107 static GtkWidget *
1108 make_recur_month_num_combo (gint month_index)
1109 {
1110 static const gchar *options[] = {
1111 /* TRANSLATORS: Entire string is for example: This appointment recurs/Every [x] month(s) on the [first] [Monday] [forever]'
1112 * (dropdown menu options are in [square brackets]). This means that after 'first', either the string 'day' or
1113 * the name of a week day (like 'Monday' or 'Friday') always follow.
1114 */
1115 N_("first"),
1116 /* TRANSLATORS: here, "second" is the ordinal number (like "third"), not the time division (like "minute")
1117 * Entire string is for example: This appointment recurs/Every [x] month(s) on the [second] [Monday] [forever]'
1118 * (dropdown menu options are in [square brackets]). This means that after 'second', either the string 'day' or
1119 * the name of a week day (like 'Monday' or 'Friday') always follow.
1120 */
1121 N_("second"),
1122 /* TRANSLATORS: Entire string is for example: This appointment recurs/Every [x] month(s) on the [third] [Monday] [forever]'
1123 * (dropdown menu options are in [square brackets]). This means that after 'third', either the string 'day' or
1124 * the name of a week day (like 'Monday' or 'Friday') always follow.
1125 */
1126 N_("third"),
1127 /* TRANSLATORS: Entire string is for example: This appointment recurs/Every [x] month(s) on the [fourth] [Monday] [forever]'
1128 * (dropdown menu options are in [square brackets]). This means that after 'fourth', either the string 'day' or
1129 * the name of a week day (like 'Monday' or 'Friday') always follow.
1130 */
1131 N_("fourth"),
1132 /* TRANSLATORS: Entire string is for example: This appointment recurs/Every [x] month(s) on the [fifth] [Monday] [forever]'
1133 * (dropdown menu options are in [square brackets]). This means that after 'fifth', either the string 'day' or
1134 * the name of a week day (like 'Monday' or 'Friday') always follow.
1135 */
1136 N_("fifth"),
1137 /* TRANSLATORS: Entire string is for example: This appointment recurs/Every [x] month(s) on the [last] [Monday] [forever]'
1138 * (dropdown menu options are in [square brackets]). This means that after 'last', either the string 'day' or
1139 * the name of a week day (like 'Monday' or 'Friday') always follow.
1140 */
1141 N_("last")
1142 };
1143
1144 gint i;
1145 GtkTreeStore *store;
1146 GtkTreeIter iter;
1147 GtkWidget *combo;
1148 GtkCellRenderer *cell;
1149
1150 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT);
1151
1152 /* Relation */
1153 for (i = 0; i < G_N_ELEMENTS (options); i++) {
1154 gtk_tree_store_append (store, &iter, NULL);
1155 gtk_tree_store_set (store, &iter, 0, _(options[i]), 1, month_num_options_map[i], -1);
1156 }
1157
1158 /* Current date */
1159 gtk_tree_store_append (store, &iter, NULL);
1160 gtk_tree_store_set (store, &iter, 0, _(e_cal_recur_nth[month_index - 1]), 1, MONTH_NUM_DAY, -1);
1161
1162 gtk_tree_store_append (store, &iter, NULL);
1163 /* TRANSLATORS: Entire string is for example: This appointment recurs/Every [x] month(s) on the [Other date] [11th to 20th] [17th] [forever]'
1164 * (dropdown menu options are in [square brackets]). */
1165 gtk_tree_store_set (store, &iter, 0, _("Other Date"), 1, MONTH_NUM_OTHER, -1);
1166
1167 /* TRANSLATORS: This is a submenu option string to split the date range into three submenus to choose the exact day of
1168 * the month to setup an appointment recurrence. The entire string is for example: This appointment recurs/Every [x] month(s)
1169 * on the [Other date] [1st to 10th] [7th] [forever]' (dropdown menu options are in [square brackets]).
1170 */
1171 make_recur_month_num_subtree (store, &iter, _("1st to 10th"), 0, 10);
1172
1173 /* TRANSLATORS: This is a submenu option string to split the date range into three submenus to choose the exact day of
1174 * the month to setup an appointment recurrence. The entire string is for example: This appointment recurs/Every [x] month(s)
1175 * on the [Other date] [11th to 20th] [17th] [forever]' (dropdown menu options are in [square brackets]).
1176 */
1177 make_recur_month_num_subtree (store, &iter, _("11th to 20th"), 10, 20);
1178
1179 /* TRANSLATORS: This is a submenu option string to split the date range into three submenus to choose the exact day of
1180 * the month to setup an appointment recurrence. The entire string is for example: This appointment recurs/Every [x] month(s)
1181 * on the [Other date] [21th to 31th] [27th] [forever]' (dropdown menu options are in [square brackets]).
1182 */
1183 make_recur_month_num_subtree (store, &iter, _("21st to 31st"), 20, 31);
1184
1185 combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
1186 g_object_unref (store);
1187
1188 cell = gtk_cell_renderer_text_new ();
1189 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
1190 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, "text", 0, NULL);
1191
1192 gtk_cell_layout_set_cell_data_func (
1193 GTK_CELL_LAYOUT (combo),
1194 cell,
1195 only_leaf_sensitive,
1196 NULL, NULL);
1197
1198 return combo;
1199 }
1200
1201 /* Creates the combo box for the monthly recurrence days */
1202 static GtkWidget *
1203 make_recur_month_combobox (void)
1204 {
1205 static const gchar *options[] = {
1206 /* For Translator : 'day' is part of the sentence of the form 'appointment recurs/Every [x] month(s) on the [first] [day] [forever]'
1207 * (dropdown menu options are in[square brackets]). This means that after 'first', either the string 'day' or
1208 * the name of a week day (like 'Monday' or 'Friday') always follow. */
1209 N_("day"),
1210 N_("Monday"),
1211 N_("Tuesday"),
1212 N_("Wednesday"),
1213 N_("Thursday"),
1214 N_("Friday"),
1215 N_("Saturday"),
1216 N_("Sunday")
1217 };
1218
1219 GtkWidget *combo;
1220 gint i;
1221
1222 combo = gtk_combo_box_text_new ();
1223
1224 for (i = 0; i < G_N_ELEMENTS (options); i++) {
1225 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), _(options[i]));
1226 }
1227
1228 return combo;
1229 }
1230
1231 static void
1232 month_num_combo_changed_cb (GtkComboBox *combo,
1233 RecurrencePage *rpage)
1234 {
1235 GtkTreeIter iter;
1236 RecurrencePagePrivate *priv;
1237 enum month_num_options month_num;
1238 enum month_day_options month_day;
1239
1240 priv = rpage->priv;
1241
1242 month_day = e_dialog_combo_box_get (
1243 priv->month_day_combo,
1244 month_day_options_map);
1245
1246 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (priv->month_num_combo), &iter)) {
1247 gint value;
1248 GtkTreeIter parent;
1249 GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->month_num_combo));
1250
1251 gtk_tree_model_get (model, &iter, 1, &value, -1);
1252
1253 if (value == -1) {
1254 return;
1255 }
1256
1257 if (gtk_tree_model_iter_parent (model, &parent, &iter)) {
1258 /* it's a leaf, thus the day number */
1259 month_num = MONTH_NUM_DAY;
1260 priv->month_index = value;
1261
1262 g_return_if_fail (gtk_tree_model_iter_nth_child (model, &iter, NULL, month_num));
1263
1264 gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 0, _(e_cal_recur_nth[priv->month_index - 1]), -1);
1265 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->month_num_combo), &iter);
1266 } else {
1267 /* top level node */
1268 month_num = value;
1269
1270 if (month_num == MONTH_NUM_OTHER)
1271 month_num = MONTH_NUM_DAY;
1272 }
1273 } else {
1274 month_num = 0;
1275 }
1276
1277 if (month_num == MONTH_NUM_DAY && month_day != MONTH_DAY_NTH)
1278 e_dialog_combo_box_set (
1279 priv->month_day_combo,
1280 MONTH_DAY_NTH,
1281 month_day_options_map);
1282 else if (month_num != MONTH_NUM_DAY && month_num != MONTH_NUM_LAST && month_day == MONTH_DAY_NTH)
1283 e_dialog_combo_box_set (
1284 priv->month_day_combo,
1285 MONTH_DAY_MON,
1286 month_num_options_map);
1287
1288 comp_editor_page_changed (COMP_EDITOR_PAGE (rpage));
1289 }
1290
1291 /* Callback used when the monthly day selection changes. We need
1292 * to change the valid range of the day index spin button; e.g. days
1293 * are 1-31 while a Sunday is the 1st through 5th.
1294 */
1295 static void
1296 month_day_combo_changed_cb (GtkComboBox *combo,
1297 RecurrencePage *rpage)
1298 {
1299 RecurrencePagePrivate *priv;
1300 enum month_num_options month_num;
1301 enum month_day_options month_day;
1302
1303 priv = rpage->priv;
1304
1305 month_num = e_dialog_combo_box_get (
1306 priv->month_num_combo,
1307 month_num_options_map);
1308 month_day = e_dialog_combo_box_get (
1309 priv->month_day_combo,
1310 month_day_options_map);
1311 if (month_day == MONTH_DAY_NTH && month_num != MONTH_NUM_LAST && month_num != MONTH_NUM_DAY)
1312 e_dialog_combo_box_set (
1313 priv->month_num_combo,
1314 MONTH_NUM_DAY,
1315 month_num_options_map);
1316 else if (month_day != MONTH_DAY_NTH && month_num == MONTH_NUM_DAY)
1317 e_dialog_combo_box_set (
1318 priv->month_num_combo,
1319 MONTH_NUM_FIRST,
1320 month_num_options_map);
1321
1322 comp_editor_page_changed (COMP_EDITOR_PAGE (rpage));
1323 }
1324
1325 /* Creates the special contents for monthly recurrences */
1326 static void
1327 make_monthly_special (RecurrencePage *rpage)
1328 {
1329 RecurrencePagePrivate *priv;
1330 GtkWidget *hbox;
1331 GtkWidget *label;
1332 GtkAdjustment *adj;
1333
1334 priv = rpage->priv;
1335
1336 g_return_if_fail (gtk_bin_get_child (GTK_BIN (priv->special)) == NULL);
1337 g_return_if_fail (priv->month_day_combo == NULL);
1338
1339 /* Create the widgets */
1340
1341 hbox = gtk_hbox_new (FALSE, 2);
1342 gtk_container_add (GTK_CONTAINER (priv->special), hbox);
1343
1344 /* TRANSLATORS: Entire string is for example: 'This appointment recurs/Every [x] month(s) on the [second] [Tuesday] [forever]'
1345 * (dropdown menu options are in [square brackets])."
1346 */
1347 label = gtk_label_new (_("on the"));
1348 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
1349
1350 adj = GTK_ADJUSTMENT (gtk_adjustment_new (1, 1, 31, 1, 10, 10));
1351
1352 priv->month_num_combo = make_recur_month_num_combo (priv->month_index);
1353 gtk_box_pack_start (
1354 GTK_BOX (hbox), priv->month_num_combo,
1355 FALSE, FALSE, 6);
1356
1357 priv->month_day_combo = make_recur_month_combobox ();
1358 gtk_box_pack_start (
1359 GTK_BOX (hbox), priv->month_day_combo,
1360 FALSE, FALSE, 6);
1361
1362 gtk_widget_show_all (hbox);
1363
1364 /* Set the options */
1365 e_dialog_combo_box_set (
1366 priv->month_num_combo,
1367 priv->month_num,
1368 month_num_options_map);
1369 e_dialog_combo_box_set (
1370 priv->month_day_combo,
1371 priv->month_day,
1372 month_day_options_map);
1373
1374 g_signal_connect_swapped (
1375 adj, "value-changed",
1376 G_CALLBACK (comp_editor_page_changed), rpage);
1377
1378 g_signal_connect (
1379 priv->month_num_combo, "changed",
1380 G_CALLBACK (month_num_combo_changed_cb), rpage);
1381 g_signal_connect (
1382 priv->month_day_combo, "changed",
1383 G_CALLBACK (month_day_combo_changed_cb), rpage);
1384 }
1385
1386 /* Changes the recurrence-special widget to match the interval units.
1387 *
1388 * For daily recurrences: nothing.
1389 * For weekly recurrences: weekday selector.
1390 * For monthly recurrences: "on the" <nth> [day, Weekday]
1391 * For yearly recurrences: nothing.
1392 */
1393 static void
1394 make_recurrence_special (RecurrencePage *rpage)
1395 {
1396 RecurrencePagePrivate *priv;
1397 icalrecurrencetype_frequency frequency;
1398 GtkWidget *child;
1399
1400 priv = rpage->priv;
1401
1402 if (priv->month_num_combo != NULL) {
1403 gtk_widget_destroy (priv->month_num_combo);
1404 priv->month_num_combo = NULL;
1405 }
1406
1407 child = gtk_bin_get_child (GTK_BIN (priv->special));
1408 if (child != NULL) {
1409 gtk_widget_destroy (child);
1410
1411 priv->weekday_picker = NULL;
1412 priv->month_day_combo = NULL;
1413 }
1414
1415 frequency = e_dialog_combo_box_get (priv->interval_unit_combo, freq_map);
1416
1417 switch (frequency) {
1418 case ICAL_DAILY_RECURRENCE:
1419 gtk_widget_hide (priv->special);
1420 break;
1421
1422 case ICAL_WEEKLY_RECURRENCE:
1423 make_weekly_special (rpage);
1424 gtk_widget_show (priv->special);
1425 break;
1426
1427 case ICAL_MONTHLY_RECURRENCE:
1428 make_monthly_special (rpage);
1429 gtk_widget_show (priv->special);
1430 break;
1431
1432 case ICAL_YEARLY_RECURRENCE:
1433 gtk_widget_hide (priv->special);
1434 break;
1435
1436 default:
1437 g_return_if_reached ();
1438 }
1439 }
1440
1441 /* Counts the elements in the by_xxx fields of an icalrecurrencetype */
1442 static gint
1443 count_by_xxx (gshort *field,
1444 gint max_elements)
1445 {
1446 gint i;
1447
1448 for (i = 0; i < max_elements; i++)
1449 if (field[i] == ICAL_RECURRENCE_ARRAY_MAX)
1450 break;
1451
1452 return i;
1453 }
1454
1455 /* Creates the special contents for "ending until" (end date) recurrences */
1456 static void
1457 make_ending_until_special (RecurrencePage *rpage)
1458 {
1459 RecurrencePagePrivate *priv = rpage->priv;
1460 CompEditor *editor;
1461 CompEditorFlags flags;
1462 EDateEdit *de;
1463 ECalComponentDateTime dt_start;
1464
1465 g_return_if_fail (gtk_bin_get_child (GTK_BIN (priv->ending_special)) == NULL);
1466 g_return_if_fail (priv->ending_date_edit == NULL);
1467
1468 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
1469 flags = comp_editor_get_flags (editor);
1470
1471 /* Create the widget */
1472
1473 priv->ending_date_edit = comp_editor_new_date_edit (TRUE, FALSE, FALSE);
1474 de = E_DATE_EDIT (priv->ending_date_edit);
1475
1476 gtk_container_add (
1477 GTK_CONTAINER (priv->ending_special),
1478 GTK_WIDGET (de));
1479 gtk_widget_show_all (GTK_WIDGET (de));
1480
1481 /* Set the value */
1482
1483 if (flags & COMP_EDITOR_NEW_ITEM) {
1484 e_cal_component_get_dtstart (priv->comp, &dt_start);
1485 /* Setting the default until time to 2 weeks */
1486 icaltime_adjust (dt_start.value, 14, 0, 0, 0);
1487 e_date_edit_set_date (de, dt_start.value->year, dt_start.value->month, dt_start.value->day);
1488 e_cal_component_free_datetime (&dt_start);
1489 } else {
1490 e_date_edit_set_date (de, priv->ending_date_tt.year, priv->ending_date_tt.month, priv->ending_date_tt.day);
1491 }
1492
1493 g_signal_connect_swapped (
1494 e_date_edit_get_entry (de), "focus-out-event",
1495 G_CALLBACK (comp_editor_page_changed), rpage);
1496
1497 /* Make sure the EDateEdit widget uses our timezones to get the
1498 * current time. */
1499 e_date_edit_set_get_time_callback (
1500 de,
1501 (EDateEditGetTimeCallback) comp_editor_get_current_time,
1502 g_object_ref (editor),
1503 (GDestroyNotify) g_object_unref);
1504 }
1505
1506 /* Creates the special contents for the occurrence count case */
1507 static void
1508 make_ending_count_special (RecurrencePage *rpage)
1509 {
1510 RecurrencePagePrivate *priv;
1511 GtkWidget *hbox;
1512 GtkWidget *label;
1513 GtkAdjustment *adj;
1514
1515 priv = rpage->priv;
1516
1517 g_return_if_fail (gtk_bin_get_child (GTK_BIN (priv->ending_special)) == NULL);
1518 g_return_if_fail (priv->ending_count_spin == NULL);
1519
1520 /* Create the widgets */
1521
1522 hbox = gtk_hbox_new (FALSE, 2);
1523 gtk_container_add (GTK_CONTAINER (priv->ending_special), hbox);
1524
1525 adj = GTK_ADJUSTMENT (gtk_adjustment_new (1, 1, 10000, 1, 10, 0));
1526 priv->ending_count_spin = gtk_spin_button_new (adj, 1, 0);
1527 gtk_spin_button_set_numeric ((GtkSpinButton *) priv->ending_count_spin, TRUE);
1528 gtk_box_pack_start (
1529 GTK_BOX (hbox), priv->ending_count_spin,
1530 FALSE, FALSE, 6);
1531
1532 label = gtk_label_new (_("occurrences"));
1533 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
1534
1535 gtk_widget_show_all (hbox);
1536
1537 /* Set the values */
1538
1539 gtk_spin_button_set_value (
1540 GTK_SPIN_BUTTON (priv->ending_count_spin),
1541 priv->ending_count);
1542
1543 g_signal_connect_swapped (
1544 adj, "value-changed",
1545 G_CALLBACK (comp_editor_page_changed), rpage);
1546 }
1547
1548 /* Changes the recurrence-ending-special widget to match the ending date option
1549 *
1550 * For: <n> [days, weeks, months, years, occurrences]
1551 * Until: <date selector>
1552 * Forever: nothing.
1553 */
1554 static void
1555 make_ending_special (RecurrencePage *rpage)
1556 {
1557 RecurrencePagePrivate *priv;
1558 enum ending_type ending_type;
1559 GtkWidget *child;
1560
1561 priv = rpage->priv;
1562
1563 child = gtk_bin_get_child (GTK_BIN (priv->ending_special));
1564 if (child != NULL) {
1565 gtk_widget_destroy (child);
1566
1567 priv->ending_date_edit = NULL;
1568 priv->ending_count_spin = NULL;
1569 }
1570
1571 ending_type = e_dialog_combo_box_get (priv->ending_combo, ending_types_map);
1572
1573 switch (ending_type) {
1574 case ENDING_FOR:
1575 make_ending_count_special (rpage);
1576 gtk_widget_show (priv->ending_special);
1577 break;
1578
1579 case ENDING_UNTIL:
1580 make_ending_until_special (rpage);
1581 gtk_widget_show (priv->ending_special);
1582 break;
1583
1584 case ENDING_FOREVER:
1585 gtk_widget_hide (priv->ending_special);
1586 break;
1587
1588 default:
1589 g_return_if_reached ();
1590 }
1591 }
1592
1593 /* Fills the recurrence ending date widgets with the values from the calendar
1594 * component.
1595 */
1596 static void
1597 fill_ending_date (RecurrencePage *rpage,
1598 struct icalrecurrencetype *r)
1599 {
1600 RecurrencePagePrivate *priv = rpage->priv;
1601 CompEditor *editor;
1602 ECalClient *client;
1603
1604 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
1605 client = comp_editor_get_client (editor);
1606
1607 g_signal_handlers_block_matched (priv->ending_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1608
1609 if (r->count == 0) {
1610 if (r->until.year == 0) {
1611 /* Forever */
1612
1613 e_dialog_combo_box_set (
1614 priv->ending_combo,
1615 ENDING_FOREVER,
1616 ending_types_map);
1617 } else {
1618 /* Ending date */
1619
1620 if (!r->until.is_date) {
1621 ECalComponentDateTime dt;
1622 icaltimezone *from_zone, *to_zone;
1623
1624 e_cal_component_get_dtstart (priv->comp, &dt);
1625
1626 if (dt.value->is_date)
1627 to_zone = e_meeting_store_get_timezone (priv->meeting_store);
1628 else if (dt.tzid == NULL)
1629 to_zone = icaltimezone_get_utc_timezone ();
1630 else {
1631 GError *error = NULL;
1632 /* FIXME Error checking? */
1633 e_cal_client_get_timezone_sync (client, dt.tzid, &to_zone, NULL, &error);
1634
1635 if (error != NULL) {
1636 g_warning (
1637 "%s: Failed to get timezone: %s",
1638 G_STRFUNC, error->message);
1639 g_error_free (error);
1640 }
1641 }
1642 from_zone = icaltimezone_get_utc_timezone ();
1643
1644 icaltimezone_convert_time (&r->until, from_zone, to_zone);
1645
1646 r->until.hour = 0;
1647 r->until.minute = 0;
1648 r->until.second = 0;
1649 r->until.is_date = TRUE;
1650 r->until.is_utc = FALSE;
1651
1652 e_cal_component_free_datetime (&dt);
1653 }
1654
1655 priv->ending_date_tt = r->until;
1656 e_dialog_combo_box_set (
1657 priv->ending_combo,
1658 ENDING_UNTIL,
1659 ending_types_map);
1660 }
1661 } else {
1662 /* Count of occurrences */
1663
1664 priv->ending_count = r->count;
1665 e_dialog_combo_box_set (
1666 priv->ending_combo,
1667 ENDING_FOR,
1668 ending_types_map);
1669 }
1670
1671 g_signal_handlers_unblock_matched (priv->ending_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1672
1673 make_ending_special (rpage);
1674 }
1675
1676 /* fill_widgets handler for the recurrence page. This function is particularly
1677 * tricky because it has to discriminate between recurrences we support for
1678 * editing and the ones we don't. We only support at most one recurrence rule;
1679 * no rdates or exrules (exdates are handled just fine elsewhere).
1680 */
1681 static gboolean
1682 recurrence_page_fill_widgets (CompEditorPage *page,
1683 ECalComponent *comp)
1684 {
1685 RecurrencePage *rpage;
1686 RecurrencePagePrivate *priv;
1687 ECalComponentText text;
1688 CompEditor *editor;
1689 CompEditorFlags flags;
1690 CompEditorPageDates dates;
1691 GSList *rrule_list;
1692 gint len;
1693 struct icalrecurrencetype *r;
1694 gint n_by_second, n_by_minute, n_by_hour;
1695 gint n_by_day, n_by_month_day, n_by_year_day;
1696 gint n_by_week_no, n_by_month, n_by_set_pos;
1697 GtkAdjustment *adj;
1698
1699 rpage = RECURRENCE_PAGE (page);
1700 priv = rpage->priv;
1701
1702 editor = comp_editor_page_get_editor (page);
1703 flags = comp_editor_get_flags (editor);
1704
1705 /* Keep a copy of the component so that we can expand the recurrence
1706 * set for the preview.
1707 */
1708
1709 if (priv->comp)
1710 g_object_unref (priv->comp);
1711
1712 priv->comp = e_cal_component_clone (comp);
1713
1714 if (!e_cal_component_has_organizer (comp)) {
1715 flags |= COMP_EDITOR_USER_ORG;
1716 comp_editor_set_flags (editor, flags);
1717 }
1718
1719 /* Clean the page */
1720 clear_widgets (rpage);
1721
1722 /* Summary */
1723 e_cal_component_get_summary (comp, &text);
1724
1725 /* Dates */
1726 comp_editor_dates (&dates, comp);
1727 recurrence_page_set_dates (page, &dates);
1728 comp_editor_free_dates (&dates);
1729
1730 /* Exceptions */
1731 fill_exception_widgets (rpage, comp);
1732
1733 /* Set up defaults for the special widgets */
1734 set_special_defaults (rpage);
1735
1736 /* No recurrences? */
1737
1738 if (!e_cal_component_has_rdates (comp)
1739 && !e_cal_component_has_rrules (comp)
1740 && !e_cal_component_has_exrules (comp)) {
1741 g_signal_handlers_block_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1742 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->recurs), FALSE);
1743 g_signal_handlers_unblock_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1744
1745 sensitize_buttons (rpage);
1746 preview_recur (rpage);
1747
1748 return TRUE;
1749 }
1750
1751 /* See if it is a custom set we don't support */
1752
1753 e_cal_component_get_rrule_list (comp, &rrule_list);
1754 len = g_slist_length (rrule_list);
1755 if (len > 1
1756 || e_cal_component_has_rdates (comp)
1757 || e_cal_component_has_exrules (comp))
1758 goto custom;
1759
1760 /* Down to one rule, so test that one */
1761
1762 g_return_val_if_fail (len == 1, TRUE);
1763 r = rrule_list->data;
1764
1765 /* Any funky frequency? */
1766
1767 if (r->freq == ICAL_SECONDLY_RECURRENCE
1768 || r->freq == ICAL_MINUTELY_RECURRENCE
1769 || r->freq == ICAL_HOURLY_RECURRENCE)
1770 goto custom;
1771
1772 /* Any funky shit? */
1773
1774 #define N_HAS_BY(field) (count_by_xxx (field, G_N_ELEMENTS (field)))
1775
1776 n_by_second = N_HAS_BY (r->by_second);
1777 n_by_minute = N_HAS_BY (r->by_minute);
1778 n_by_hour = N_HAS_BY (r->by_hour);
1779 n_by_day = N_HAS_BY (r->by_day);
1780 n_by_month_day = N_HAS_BY (r->by_month_day);
1781 n_by_year_day = N_HAS_BY (r->by_year_day);
1782 n_by_week_no = N_HAS_BY (r->by_week_no);
1783 n_by_month = N_HAS_BY (r->by_month);
1784 n_by_set_pos = N_HAS_BY (r->by_set_pos);
1785
1786 if (n_by_second != 0
1787 || n_by_minute != 0
1788 || n_by_hour != 0)
1789 goto custom;
1790
1791 /* Filter the funky shit based on the frequency; if there is nothing
1792 * weird we can actually set the widgets.
1793 */
1794
1795 switch (r->freq) {
1796 case ICAL_DAILY_RECURRENCE:
1797 if (n_by_day != 0
1798 || n_by_month_day != 0
1799 || n_by_year_day != 0
1800 || n_by_week_no != 0
1801 || n_by_month != 0
1802 || n_by_set_pos != 0)
1803 goto custom;
1804
1805 g_signal_handlers_block_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1806 e_dialog_combo_box_set (
1807 priv->interval_unit_combo,
1808 ICAL_DAILY_RECURRENCE,
1809 freq_map);
1810 g_signal_handlers_unblock_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1811 break;
1812
1813 case ICAL_WEEKLY_RECURRENCE: {
1814 gint i;
1815 guint8 day_mask;
1816
1817 if (n_by_month_day != 0
1818 || n_by_year_day != 0
1819 || n_by_week_no != 0
1820 || n_by_month != 0
1821 || n_by_set_pos != 0)
1822 goto custom;
1823
1824 day_mask = 0;
1825
1826 for (i = 0; i < 8 && r->by_day[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
1827 enum icalrecurrencetype_weekday weekday;
1828 gint pos;
1829
1830 weekday = icalrecurrencetype_day_day_of_week (r->by_day[i]);
1831 pos = icalrecurrencetype_day_position (r->by_day[i]);
1832
1833 if (pos != 0)
1834 goto custom;
1835
1836 switch (weekday) {
1837 case ICAL_SUNDAY_WEEKDAY:
1838 day_mask |= 1 << 0;
1839 break;
1840
1841 case ICAL_MONDAY_WEEKDAY:
1842 day_mask |= 1 << 1;
1843 break;
1844
1845 case ICAL_TUESDAY_WEEKDAY:
1846 day_mask |= 1 << 2;
1847 break;
1848
1849 case ICAL_WEDNESDAY_WEEKDAY:
1850 day_mask |= 1 << 3;
1851 break;
1852
1853 case ICAL_THURSDAY_WEEKDAY:
1854 day_mask |= 1 << 4;
1855 break;
1856
1857 case ICAL_FRIDAY_WEEKDAY:
1858 day_mask |= 1 << 5;
1859 break;
1860
1861 case ICAL_SATURDAY_WEEKDAY:
1862 day_mask |= 1 << 6;
1863 break;
1864
1865 default:
1866 break;
1867 }
1868 }
1869
1870 priv->weekday_day_mask = day_mask;
1871
1872 g_signal_handlers_block_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1873 e_dialog_combo_box_set (
1874 priv->interval_unit_combo,
1875 ICAL_WEEKLY_RECURRENCE,
1876 freq_map);
1877 g_signal_handlers_unblock_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1878 break;
1879 }
1880
1881 case ICAL_MONTHLY_RECURRENCE:
1882 if (n_by_year_day != 0
1883 || n_by_week_no != 0
1884 || n_by_month != 0
1885 || n_by_set_pos > 1)
1886 goto custom;
1887
1888 if (n_by_month_day == 1) {
1889 gint nth;
1890
1891 if (n_by_set_pos != 0)
1892 goto custom;
1893
1894 nth = r->by_month_day[0];
1895 if (nth < 1 && nth != -1)
1896 goto custom;
1897
1898 if (nth == -1) {
1899 ECalComponentDateTime dt;
1900
1901 e_cal_component_get_dtstart (comp, &dt);
1902 priv->month_index = dt.value->day;
1903 priv->month_num = MONTH_NUM_LAST;
1904 e_cal_component_free_datetime (&dt);
1905 } else {
1906 priv->month_index = nth;
1907 priv->month_num = MONTH_NUM_DAY;
1908 }
1909 priv->month_day = MONTH_DAY_NTH;
1910
1911 } else if (n_by_day == 1) {
1912 enum icalrecurrencetype_weekday weekday;
1913 gint pos;
1914 enum month_day_options month_day;
1915
1916 /* Outlook 2000 uses BYDAY=TU;BYSETPOS=2, and will not
1917 * accept BYDAY=2TU. So we now use the same as Outlook
1918 * by default. */
1919
1920 weekday = icalrecurrencetype_day_day_of_week (r->by_day[0]);
1921 pos = icalrecurrencetype_day_position (r->by_day[0]);
1922
1923 if (pos == 0) {
1924 if (n_by_set_pos != 1)
1925 goto custom;
1926 pos = r->by_set_pos[0];
1927 } else if (pos < 0) {
1928 goto custom;
1929 }
1930
1931 switch (weekday) {
1932 case ICAL_MONDAY_WEEKDAY:
1933 month_day = MONTH_DAY_MON;
1934 break;
1935
1936 case ICAL_TUESDAY_WEEKDAY:
1937 month_day = MONTH_DAY_TUE;
1938 break;
1939
1940 case ICAL_WEDNESDAY_WEEKDAY:
1941 month_day = MONTH_DAY_WED;
1942 break;
1943
1944 case ICAL_THURSDAY_WEEKDAY:
1945 month_day = MONTH_DAY_THU;
1946 break;
1947
1948 case ICAL_FRIDAY_WEEKDAY:
1949 month_day = MONTH_DAY_FRI;
1950 break;
1951
1952 case ICAL_SATURDAY_WEEKDAY:
1953 month_day = MONTH_DAY_SAT;
1954 break;
1955
1956 case ICAL_SUNDAY_WEEKDAY:
1957 month_day = MONTH_DAY_SUN;
1958 break;
1959
1960 default:
1961 goto custom;
1962 }
1963
1964 if (pos == -1)
1965 priv->month_num = MONTH_NUM_LAST;
1966 else
1967 priv->month_num = pos - 1;
1968 priv->month_day = month_day;
1969 } else
1970 goto custom;
1971
1972 g_signal_handlers_block_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1973 e_dialog_combo_box_set (
1974 priv->interval_unit_combo,
1975 ICAL_MONTHLY_RECURRENCE,
1976 freq_map);
1977 g_signal_handlers_unblock_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1978 break;
1979
1980 case ICAL_YEARLY_RECURRENCE:
1981 if (n_by_day != 0
1982 || n_by_month_day != 0
1983 || n_by_year_day != 0
1984 || n_by_week_no != 0
1985 || n_by_month != 0
1986 || n_by_set_pos != 0)
1987 goto custom;
1988
1989 g_signal_handlers_block_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1990 e_dialog_combo_box_set (
1991 priv->interval_unit_combo,
1992 ICAL_YEARLY_RECURRENCE,
1993 freq_map);
1994 g_signal_handlers_unblock_matched (priv->interval_unit_combo, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
1995 break;
1996
1997 default:
1998 goto custom;
1999 }
2000
2001 /* If we got here it means it is a simple recurrence */
2002
2003 g_signal_handlers_block_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
2004 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->recurs), TRUE);
2005 g_signal_handlers_unblock_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
2006
2007 sensitize_buttons (rpage);
2008 make_recurrence_special (rpage);
2009
2010 adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->interval_value));
2011 g_signal_handlers_block_matched (adj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
2012 gtk_spin_button_set_value (
2013 GTK_SPIN_BUTTON (priv->interval_value), r->interval);
2014 g_signal_handlers_unblock_matched (adj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
2015
2016 fill_ending_date (rpage, r);
2017
2018 goto out;
2019
2020 custom:
2021
2022 g_signal_handlers_block_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
2023 priv->custom = TRUE;
2024 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->recurs), TRUE);
2025 g_signal_handlers_unblock_matched (priv->recurs, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, rpage);
2026 /* FIXME Desensitize recurrence page */
2027
2028 sensitize_buttons (rpage);
2029
2030 out:
2031 priv->custom = FALSE;
2032 e_cal_component_free_recur_list (rrule_list);
2033 preview_recur (rpage);
2034
2035 return TRUE;
2036 }
2037
2038 /* fill_component handler for the recurrence page */
2039 static gboolean
2040 recurrence_page_fill_component (CompEditorPage *page,
2041 ECalComponent *comp)
2042 {
2043 RecurrencePage *rpage;
2044
2045 rpage = RECURRENCE_PAGE (page);
2046 return fill_component (rpage, comp);
2047 }
2048
2049 /* set_dates handler for the recurrence page */
2050 static void
2051 recurrence_page_set_dates (CompEditorPage *page,
2052 CompEditorPageDates *dates)
2053 {
2054 RecurrencePage *rpage;
2055 RecurrencePagePrivate *priv;
2056 ECalComponentDateTime dt;
2057 CompEditor *editor;
2058 CompEditorFlags flags;
2059 struct icaltimetype icaltime;
2060 guint8 mask;
2061
2062 rpage = RECURRENCE_PAGE (page);
2063 priv = rpage->priv;
2064
2065 editor = comp_editor_page_get_editor (page);
2066 flags = comp_editor_get_flags (editor);
2067
2068 /* Copy the dates to our component */
2069
2070 if (!priv->comp)
2071 return;
2072
2073 dt.value = &icaltime;
2074
2075 if (dates->start) {
2076 icaltime = *dates->start->value;
2077 dt.tzid = dates->start->tzid;
2078 e_cal_component_set_dtstart (priv->comp, &dt);
2079 }
2080
2081 if (dates->end) {
2082 icaltime = *dates->end->value;
2083 dt.tzid = dates->end->tzid;
2084 e_cal_component_set_dtend (priv->comp, &dt);
2085 }
2086
2087 /* Update the weekday picker if necessary */
2088 mask = get_start_weekday_mask (priv->comp);
2089 if (mask != priv->weekday_blocked_day_mask) {
2090 priv->weekday_day_mask = priv->weekday_day_mask | mask;
2091 priv->weekday_blocked_day_mask = mask;
2092
2093 if (priv->weekday_picker != NULL) {
2094 weekday_picker_set_days (
2095 WEEKDAY_PICKER (priv->weekday_picker),
2096 priv->weekday_day_mask);
2097 weekday_picker_set_blocked_days (
2098 WEEKDAY_PICKER (priv->weekday_picker),
2099 priv->weekday_blocked_day_mask);
2100 }
2101 }
2102
2103 if (flags & COMP_EDITOR_NEW_ITEM) {
2104 ECalendar *ecal;
2105 GDate *start, *end;
2106
2107 ecal = E_CALENDAR (priv->preview_calendar);
2108 start = g_date_new ();
2109 end = g_date_new ();
2110
2111 g_date_set_dmy (start, dates->start->value->day, dates->start->value->month, dates->start->value->year);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2112 g_date_set_dmy (end, dates->end->value->day, dates->end->value->month, dates->end->value->year);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2113 e_calendar_item_set_selection (ecal->calitem, start, end);
2114
2115 g_date_free (start);
2116 g_date_free (end);
2117 }
2118
2119 /* Make sure the preview gets updated. */
2120 preview_recur (rpage);
2121 }
2122
2123 /* Gets the widgets from the XML file and returns if they are all available. */
2124 static gboolean
2125 get_widgets (RecurrencePage *rpage)
2126 {
2127 CompEditorPage *page = COMP_EDITOR_PAGE (rpage);
2128 RecurrencePagePrivate *priv;
2129 GSList *accel_groups;
2130 GtkWidget *toplevel;
2131 GtkWidget *parent;
2132
2133 priv = rpage->priv;
2134
2135 #define GW(name) e_builder_get_widget (priv->builder, name)
2136
2137 priv->main = GW ("recurrence-page");
2138 if (!priv->main)
2139 return FALSE;
2140
2141 /* Get the GtkAccelGroup from the toplevel window, so we can install
2142 * it when the notebook page is mapped. */
2143 toplevel = gtk_widget_get_toplevel (priv->main);
2144 accel_groups = gtk_accel_groups_from_object (G_OBJECT (toplevel));
2145 if (accel_groups)
2146 page->accel_group = g_object_ref (accel_groups->data);
2147
2148 g_object_ref (priv->main);
2149 parent = gtk_widget_get_parent (priv->main);
2150 gtk_container_remove (GTK_CONTAINER (parent), priv->main);
2151
2152 priv->recurs = GW ("recurs");
2153 priv->params = GW ("params");
2154
2155 priv->interval_value = GW ("interval-value");
2156 priv->interval_unit_combo = GW ("interval-unit-combobox");
2157 priv->special = GW ("special");
2158 priv->ending_combo = GW ("ending-combobox");
2159 priv->ending_special = GW ("ending-special");
2160 priv->custom_warning_bin = GW ("custom-warning-bin");
2161
2162 priv->exception_list = GW ("exception-list");
2163 priv->exception_add = GW ("exception-add");
2164 priv->exception_modify = GW ("exception-modify");
2165 priv->exception_delete = GW ("exception-delete");
2166
2167 priv->preview_bin = GW ("preview-bin");
2168
2169 #undef GW
2170
2171 return (priv->recurs
2172 && priv->params
2173 && priv->interval_value
2174 && priv->interval_unit_combo
2175 && priv->special
2176 && priv->ending_combo
2177 && priv->ending_special
2178 && priv->custom_warning_bin
2179 && priv->exception_list
2180 && priv->exception_add
2181 && priv->exception_modify
2182 && priv->exception_delete
2183 && priv->preview_bin);
2184 }
2185
2186 /* Callback used when the displayed date range in the recurrence preview
2187 * calendar changes.
2188 */
2189 static void
2190 preview_date_range_changed_cb (ECalendarItem *item,
2191 RecurrencePage *rpage)
2192 {
2193 preview_recur (rpage);
2194 }
2195
2196 /* Callback used when one of the recurrence type radio buttons is toggled. We
2197 * enable or disable the recurrence parameters.
2198 */
2199 static void
2200 type_toggled_cb (GtkToggleButton *toggle,
2201 RecurrencePage *rpage)
2202 {
2203 RecurrencePagePrivate *priv = rpage->priv;
2204 CompEditor *editor;
2205 ECalClient *client;
2206 gboolean read_only;
2207
2208 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
2209 client = comp_editor_get_client (editor);
2210
2211 comp_editor_page_changed (COMP_EDITOR_PAGE (rpage));
2212 sensitize_buttons (rpage);
2213
2214 /* enable/disable the 'Add' button */
2215 read_only = e_client_is_readonly (E_CLIENT (client));
2216
2217 if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->recurs)) || read_only)
2218 gtk_widget_set_sensitive (priv->exception_add, FALSE);
2219 else
2220 gtk_widget_set_sensitive (priv->exception_add, TRUE);
2221 }
2222
2223 static GtkWidget *
2224 create_exception_dialog (RecurrencePage *rpage,
2225 const gchar *title,
2226 GtkWidget **date_edit)
2227 {
2228 RecurrencePagePrivate *priv;
2229 GtkWidget *dialog, *toplevel;
2230 GtkWidget *container;
2231
2232 priv = rpage->priv;
2233
2234 toplevel = gtk_widget_get_toplevel (priv->main);
2235 dialog = gtk_dialog_new_with_buttons (
2236 title, GTK_WINDOW (toplevel),
2237 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
2238 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2239 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2240 NULL);
2241
2242 *date_edit = comp_editor_new_date_edit (TRUE, FALSE, TRUE);
2243 gtk_widget_show (*date_edit);
2244 container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
2245 gtk_box_pack_start (GTK_BOX (container), *date_edit, FALSE, TRUE, 6);
2246
2247 return dialog;
2248 }
2249
2250 /* Callback for the "add exception" button */
2251 static void
2252 exception_add_cb (GtkWidget *widget,
2253 RecurrencePage *rpage)
2254 {
2255 GtkWidget *dialog, *date_edit;
2256 gboolean date_set;
2257
2258 dialog = create_exception_dialog (rpage, _("Add exception"), &date_edit);
2259
2260 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
2261 ECalComponentDateTime dt;
2262 struct icaltimetype icaltime = icaltime_null_time ();
2263
2264 dt.value = &icaltime;
2265
2266 /* We use DATE values for exceptions, so we don't need a TZID. */
2267 dt.tzid = NULL;
2268 icaltime.is_date = 1;
2269
2270 date_set = e_date_edit_get_date (
2271 E_DATE_EDIT (date_edit),
2272 &icaltime.year,
2273 &icaltime.month,
2274 &icaltime.day);
2275 g_return_if_fail (date_set);
2276
2277 append_exception (rpage, &dt);
2278
2279 comp_editor_page_changed (COMP_EDITOR_PAGE (rpage));
2280 }
2281
2282 gtk_widget_destroy (dialog);
2283 }
2284
2285 /* Callback for the "modify exception" button */
2286 static void
2287 exception_modify_cb (GtkWidget *widget,
2288 RecurrencePage *rpage)
2289 {
2290 RecurrencePagePrivate *priv;
2291 GtkWidget *dialog, *date_edit;
2292 const ECalComponentDateTime *current_dt;
2293 GtkTreeSelection *selection;
2294 GtkTreeIter iter;
2295
2296 priv = rpage->priv;
2297
2298 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->exception_list));
2299 if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) {
2300 g_warning (_("Could not get a selection to modify."));
2301 return;
2302 }
2303
2304 current_dt = e_date_time_list_get_date_time (priv->exception_list_store, &iter);
2305
2306 dialog = create_exception_dialog (rpage, _("Modify exception"), &date_edit);
2307 e_date_edit_set_date (
2308 E_DATE_EDIT (date_edit),
2309 current_dt->value->year, current_dt->value->month, current_dt->value->day);
2310
2311 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
2312 ECalComponentDateTime dt;
2313 struct icaltimetype icaltime = icaltime_null_time ();
2314 struct icaltimetype *tt;
2315
2316 dt.value = &icaltime;
2317 tt = dt.value;
2318 e_date_edit_get_date (
2319 E_DATE_EDIT (date_edit),
2320 &tt->year, &tt->month, &tt->day);
2321 tt->hour = 0;
2322 tt->minute = 0;
2323 tt->second = 0;
2324 tt->is_date = 1;
2325
2326 /* No TZID, since we are using a DATE value now. */
2327 dt.tzid = NULL;
2328
2329 e_date_time_list_set_date_time (priv->exception_list_store, &iter, &dt);
2330
2331 comp_editor_page_changed (COMP_EDITOR_PAGE (rpage));
2332 }
2333
2334 gtk_widget_destroy (dialog);
2335 }
2336
2337 /* Callback for the "delete exception" button */
2338 static void
2339 exception_delete_cb (GtkWidget *widget,
2340 RecurrencePage *rpage)
2341 {
2342 RecurrencePagePrivate *priv;
2343 GtkTreeSelection *selection;
2344 GtkTreeIter iter;
2345 GtkTreePath *path;
2346 gboolean valid_iter;
2347
2348 priv = rpage->priv;
2349
2350 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->exception_list));
2351 if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) {
2352 g_warning (_("Could not get a selection to delete."));
2353 return;
2354 }
2355
2356 path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->exception_list_store), &iter);
2357 e_date_time_list_remove (priv->exception_list_store, &iter);
2358
2359 /* Select closest item after removal */
2360 valid_iter = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->exception_list_store), &iter, path);
2361 if (!valid_iter) {
2362 gtk_tree_path_prev (path);
2363 valid_iter = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->exception_list_store), &iter, path);
2364 }
2365
2366 if (valid_iter)
2367 gtk_tree_selection_select_iter (selection, &iter);
2368
2369 gtk_tree_path_free (path);
2370
2371 comp_editor_page_changed (COMP_EDITOR_PAGE (rpage));
2372 }
2373
2374 /* Callback used when a row is selected in the list of exception
2375 * dates. We must update the date/time widgets to reflect the
2376 * exception's value.
2377 */
2378 static void
2379 exception_selection_changed_cb (GtkTreeSelection *selection,
2380 RecurrencePage *rpage)
2381 {
2382 RecurrencePagePrivate *priv;
2383 GtkTreeIter iter;
2384
2385 priv = rpage->priv;
2386
2387 if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) {
2388 gtk_widget_set_sensitive (priv->exception_modify, FALSE);
2389 gtk_widget_set_sensitive (priv->exception_delete, FALSE);
2390 return;
2391 }
2392
2393 gtk_widget_set_sensitive (priv->exception_modify, TRUE);
2394 gtk_widget_set_sensitive (priv->exception_delete, TRUE);
2395 }
2396
2397 /* Hooks the widget signals */
2398 static void
2399 init_widgets (RecurrencePage *rpage)
2400 {
2401 RecurrencePagePrivate *priv;
2402 CompEditor *editor;
2403 ECalendar *ecal;
2404 GtkAdjustment *adj;
2405 GtkTreeViewColumn *column;
2406 GtkCellRenderer *cell_renderer;
2407
2408 priv = rpage->priv;
2409
2410 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
2411
2412 /* Recurrence preview */
2413
2414 priv->preview_calendar = e_calendar_new ();
2415 ecal = E_CALENDAR (priv->preview_calendar);
2416
2417 g_signal_connect (
2418 ecal->calitem, "date_range_changed",
2419 G_CALLBACK (preview_date_range_changed_cb), rpage);
2420 e_calendar_item_set_max_days_sel (ecal->calitem, 0);
2421 gtk_container_add (
2422 GTK_CONTAINER (priv->preview_bin),
2423 priv->preview_calendar);
2424 gtk_widget_show (priv->preview_calendar);
2425
2426 e_calendar_item_set_get_time_callback (
2427 ecal->calitem,
2428 (ECalendarItemGetTimeCallback) comp_editor_get_current_time,
2429 g_object_ref (editor),
2430 (GDestroyNotify) g_object_unref);
2431
2432 /* Recurrence types */
2433
2434 g_signal_connect (
2435 priv->recurs, "toggled",
2436 G_CALLBACK (type_toggled_cb), rpage);
2437
2438 /* Recurrence interval */
2439
2440 adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->interval_value));
2441 g_signal_connect_swapped (
2442 adj, "value-changed",
2443 G_CALLBACK (comp_editor_page_changed), rpage);
2444
2445 /* Recurrence units */
2446
2447 g_signal_connect_swapped (
2448 priv->interval_unit_combo, "changed",
2449 G_CALLBACK (make_recurrence_special), rpage);
2450 g_signal_connect_swapped (
2451 priv->interval_unit_combo, "changed",
2452 G_CALLBACK (comp_editor_page_changed), rpage);
2453
2454 /* Recurrence ending */
2455
2456 g_signal_connect_swapped (
2457 priv->ending_combo, "changed",
2458 G_CALLBACK (make_ending_special), rpage);
2459 g_signal_connect_swapped (
2460 priv->ending_combo, "changed",
2461 G_CALLBACK (comp_editor_page_changed), rpage);
2462
2463 /* Exception buttons */
2464
2465 g_signal_connect (
2466 priv->exception_add, "clicked",
2467 G_CALLBACK (exception_add_cb), rpage);
2468 g_signal_connect (
2469 priv->exception_modify, "clicked",
2470 G_CALLBACK (exception_modify_cb), rpage);
2471 g_signal_connect (
2472 priv->exception_delete, "clicked",
2473 G_CALLBACK (exception_delete_cb), rpage);
2474
2475 gtk_widget_set_sensitive (priv->exception_modify, FALSE);
2476 gtk_widget_set_sensitive (priv->exception_delete, FALSE);
2477
2478 /* Exception list */
2479
2480 /* Model */
2481 priv->exception_list_store = e_date_time_list_new ();
2482 gtk_tree_view_set_model (
2483 GTK_TREE_VIEW (priv->exception_list),
2484 GTK_TREE_MODEL (priv->exception_list_store));
2485
2486 g_object_bind_property (
2487 editor, "use-24-hour-format",
2488 priv->exception_list_store, "use-24-hour-format",
2489 G_BINDING_SYNC_CREATE);
2490
2491 /* View */
2492 column = gtk_tree_view_column_new ();
2493 gtk_tree_view_column_set_title (column, _("Date/Time"));
2494 cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ());
2495 gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
2496 gtk_tree_view_column_add_attribute (column, cell_renderer, "text", E_DATE_TIME_LIST_COLUMN_DESCRIPTION);
2497 gtk_tree_view_append_column (GTK_TREE_VIEW (priv->exception_list), column);
2498
2499 g_signal_connect (
2500 gtk_tree_view_get_selection (
2501 GTK_TREE_VIEW (priv->exception_list)), "changed",
2502 G_CALLBACK (exception_selection_changed_cb), rpage);
2503 }
2504
2505 /**
2506 * recurrence_page_construct:
2507 * @rpage: A recurrence page.
2508 *
2509 * Constructs a recurrence page by loading its Glade data.
2510 *
2511 * Return value: The same object as @rpage, or NULL if the widgets could not be
2512 * created.
2513 **/
2514 RecurrencePage *
2515 recurrence_page_construct (RecurrencePage *rpage,
2516 EMeetingStore *meeting_store)
2517 {
2518 RecurrencePagePrivate *priv;
2519 CompEditor *editor;
2520
2521 priv = rpage->priv;
2522 priv->meeting_store = g_object_ref (meeting_store);
2523
2524 editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (rpage));
2525
2526 priv->builder = gtk_builder_new ();
2527 e_load_ui_builder_definition (priv->builder, "recurrence-page.ui");
2528
2529 if (!get_widgets (rpage)) {
2530 g_message (
2531 "recurrence_page_construct(): "
2532 "Could not find all widgets in the XML file!");
2533 return NULL;
2534 }
2535
2536 init_widgets (rpage);
2537
2538 g_signal_connect_swapped (
2539 editor, "notify::client",
2540 G_CALLBACK (sensitize_buttons), rpage);
2541
2542 return rpage;
2543 }
2544
2545 /**
2546 * recurrence_page_new:
2547 *
2548 * Creates a new recurrence page.
2549 *
2550 * Return value: A newly-created recurrence page, or NULL if the page could not
2551 * be created.
2552 **/
2553 RecurrencePage *
2554 recurrence_page_new (EMeetingStore *meeting_store,
2555 CompEditor *editor)
2556 {
2557 RecurrencePage *rpage;
2558
2559 g_return_val_if_fail (E_IS_MEETING_STORE (meeting_store), NULL);
2560 g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL);
2561
2562 rpage = g_object_new (TYPE_RECURRENCE_PAGE, "editor", editor, NULL);
2563 if (!recurrence_page_construct (rpage, meeting_store)) {
2564 g_object_unref (rpage);
2565 g_return_val_if_reached (NULL);
2566 }
2567
2568 return rpage;
2569 }
2570
2571 GtkWidget *make_exdate_date_edit (void);
2572
2573 GtkWidget *
2574 make_exdate_date_edit (void)
2575 {
2576 return comp_editor_new_date_edit (TRUE, TRUE, FALSE);
2577 }