evolution-3.6.4/calendar/gui/dialogs/recurrence-page.c

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);
Access to field 'value' results in a dereference of a null pointer (loaded from field 'start')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'value' results in a dereference of a null pointer (loaded from field 'start')
(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);
Access to field 'value' results in a dereference of a null pointer (loaded from field 'end')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'value' results in a dereference of a null pointer (loaded from field 'end')
(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 }