evolution-3.6.4/calendar/gui/e-meeting-time-sel.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found e-meeting-time-sel.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found e-meeting-time-sel.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
   1 /*
   2  * This program is free software; you can redistribute it and/or
   3  * modify it under the terms of the GNU Lesser General Public
   4  * License as published by the Free Software Foundation; either
   5  * version 2 of the License, or (at your option) version 3.
   6  *
   7  * This program is distributed in the hope that it will be useful,
   8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  10  * Lesser General Public License for more details.
  11  *
  12  * You should have received a copy of the GNU Lesser General Public
  13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  14  *
  15  *
  16  * Authors:
  17  *		Damon Chaplin <damon@gtk.org>
  18  *		Rodrigo Moya <rodrigo@novell.com>
  19  *
  20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  21  *
  22  */
  23 
  24 #ifdef HAVE_CONFIG_H
  25 #include <config.h>
  26 #endif
  27 
  28 #include "e-meeting-time-sel.h"
  29 
  30 #include <stdlib.h>
  31 #include <stdio.h>
  32 #include <string.h>
  33 #include <time.h>
  34 #include <glib/gi18n.h>
  35 #include <gdk/gdkkeysyms.h>
  36 #include <libebackend/libebackend.h>
  37 #include <libgnomecanvas/libgnomecanvas.h>
  38 
  39 #include "misc/e-canvas.h"
  40 #include "misc/e-canvas-utils.h"
  41 #include "misc/e-dateedit.h"
  42 
  43 #include "e-util/e-util.h"
  44 #include "e-util/e-datetime-format.h"
  45 
  46 #include "e-meeting-utils.h"
  47 #include "e-meeting-list-view.h"
  48 #include "e-meeting-time-sel-item.h"
  49 
  50 #define E_MEETING_TIME_SELECTOR_GET_PRIVATE(obj) \
  51 	(G_TYPE_INSTANCE_GET_PRIVATE \
  52 	((obj), E_TYPE_MEETING_TIME_SELECTOR, EMeetingTimeSelectorPrivate))
  53 
  54 struct _EMeetingTimeSelectorPrivate {
  55 	gint week_start_day;
  56 	guint show_week_numbers  : 1;
  57 	guint use_24_hour_format : 1;
  58 };
  59 
  60 /* An array of hour strings for 24 hour time, "0:00" .. "23:00". */
  61 const gchar *EMeetingTimeSelectorHours[24] = {
  62 	"0:00", "1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00",
  63 	"8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00",
  64 	"16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"
  65 };
  66 
  67 /* An array of hour strings for 12 hour time, "12:00am" .. "11:00pm". */
  68 const gchar *EMeetingTimeSelectorHours12[24] = {
  69 	"12:00am", "1:00am", "2:00am", "3:00am", "4:00am", "5:00am", "6:00am",
  70 	"7:00am", "8:00am", "9:00am", "10:00am", "11:00am", "12:00pm",
  71 	"1:00pm", "2:00pm", "3:00pm", "4:00pm", "5:00pm", "6:00pm", "7:00pm",
  72 	"8:00pm", "9:00pm", "10:00pm", "11:00pm"
  73 };
  74 
  75 /* The number of days shown in the entire canvas. */
  76 #define E_MEETING_TIME_SELECTOR_DAYS_SHOWN		35
  77 #define E_MEETING_TIME_SELECTOR_DAYS_START_BEFORE	7
  78 #define E_MEETING_TIME_SELECTOR_FB_DAYS_BEFORE          7
  79 #define E_MEETING_TIME_SELECTOR_FB_DAYS_AFTER           28
  80 
  81 /* This is the number of pixels between the mouse has to move before the
  82  * scroll speed is incremented. */
  83 #define E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH	10
  84 
  85 /* This is the maximum scrolling speed. */
  86 #define E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED	4
  87 
  88 enum {
  89 	PROP_0,
  90 	PROP_SHOW_WEEK_NUMBERS,
  91 	PROP_USE_24_HOUR_FORMAT,
  92 	PROP_WEEK_START_DAY
  93 };
  94 
  95 enum {
  96 	CHANGED,
  97 	LAST_SIGNAL
  98 };
  99 
 100 static gint signals[LAST_SIGNAL] = { 0 };
 101 
 102 static void e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector * mts,
 103 						       const gchar *name, GdkColor *c);
 104 static void e_meeting_time_selector_add_key_color (EMeetingTimeSelector * mts,
 105 						   GtkWidget *hbox,
 106 						   gchar *label_text,
 107 						   GdkColor *color);
 108 static gint e_meeting_time_selector_draw_key_color (GtkWidget *darea,
 109 						      cairo_t *cr,
 110 						      GdkColor *color);
 111 static void e_meeting_time_selector_options_menu_detacher (GtkWidget *widget,
 112 							   GtkMenu   *menu);
 113 static void e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget,
 114 							    GtkMenu   *menu);
 115 static void e_meeting_time_selector_realize (GtkWidget *widget);
 116 static void e_meeting_time_selector_unrealize (GtkWidget *widget);
 117 static void e_meeting_time_selector_style_set (GtkWidget *widget,
 118 					       GtkStyle  *previous_style);
 119 static gint e_meeting_time_selector_draw (GtkWidget *widget, cairo_t *cr);
 120 static void e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts, cairo_t *cr);
 121 static void e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment,
 122 							 EMeetingTimeSelector *mts);
 123 static void e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment,
 124 							 EMeetingTimeSelector *mts);
 125 static void e_meeting_time_selector_on_canvas_realized (GtkWidget *widget,
 126 							EMeetingTimeSelector *mts);
 127 
 128 static void e_meeting_time_selector_on_options_button_clicked (GtkWidget *button,
 129 							       EMeetingTimeSelector *mts);
 130 static void e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu,
 131 								    gint *x,
 132 								    gint *y,
 133 								    gboolean *push_in,
 134 								    gpointer user_data);
 135 static void e_meeting_time_selector_on_zoomed_out_toggled (GtkCheckMenuItem *button,
 136 							   EMeetingTimeSelector *mts);
 137 static void e_meeting_time_selector_on_working_hours_toggled (GtkCheckMenuItem *menuitem,
 138 							      EMeetingTimeSelector *mts);
 139 static void e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button,
 140 								     EMeetingTimeSelector *mts);
 141 static void e_meeting_time_selector_on_update_free_busy (GtkWidget *button,
 142 							 EMeetingTimeSelector *mts);
 143 static void e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button,
 144 								EMeetingTimeSelector *mts);
 145 static void e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu,
 146 								     gint *x,
 147 								     gint *y,
 148 								     gboolean *push_in,
 149 								     gpointer user_data);
 150 static void e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button,
 151 								EMeetingTimeSelector *mts);
 152 static void e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button,
 153 							    EMeetingTimeSelector *mts);
 154 static void e_meeting_time_selector_on_next_button_clicked (GtkWidget *button,
 155 							    EMeetingTimeSelector *mts);
 156 static void e_meeting_time_selector_autopick (EMeetingTimeSelector *mts,
 157 					      gboolean forward);
 158 static void e_meeting_time_selector_calculate_time_difference (EMeetingTime *start,
 159 							       EMeetingTime *end,
 160 							       gint *days,
 161 							       gint *hours,
 162 							       gint *minutes);
 163 static void e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts,
 164 							   EMeetingTime *start_time,
 165 							   EMeetingTime *end_time,
 166 							   gint days, gint hours, gint mins);
 167 static void e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts,
 168 								    EMeetingTime *start_time,
 169 								    EMeetingTime *end_time,
 170 								    gint days, gint hours, gint mins);
 171 static void e_meeting_time_selector_adjust_time (EMeetingTime *mtstime,
 172 						 gint days, gint hours, gint minutes);
 173 static EMeetingFreeBusyPeriod * e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
 174 									EMeetingAttendee *attendee,
 175 									EMeetingTime *start_time,
 176 									EMeetingTime *end_time);
 177 
 178 static void e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts);
 179 static void e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts);
 180 static void e_meeting_time_selector_save_position (EMeetingTimeSelector *mts,
 181 						   EMeetingTime *mtstime);
 182 static void e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts,
 183 						      EMeetingTime *mtstime);
 184 static void e_meeting_time_selector_on_start_time_changed (GtkWidget *widget,
 185 							   EMeetingTimeSelector *mts);
 186 static void e_meeting_time_selector_on_end_time_changed (GtkWidget *widget,
 187 							 EMeetingTimeSelector *mts);
 188 static void e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts);
 189 static void e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget,
 190 							     GtkAllocation *allocation,
 191 							     EMeetingTimeSelector *mts);
 192 static void e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts);
 193 static gboolean e_meeting_time_selector_timeout_handler (gpointer data);
 194 static void e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts);
 195 static void e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts);
 196 static void e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts);
 197 static void e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts);
 198 static gboolean e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget, GdkEventScroll *event, EMeetingTimeSelector *mts);
 199 static gboolean e_meeting_time_selector_on_canvas_query_tooltip (GtkWidget *widget,
 200                                                                  gint x,
 201                                                                  gint y,
 202                                                                  gboolean keyboard_mode,
 203                                                                  GtkTooltip *tooltip,
 204                                                                  gpointer user_data);
 205 
 206 static void row_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);
 207 static void row_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);
 208 static void row_deleted_cb (GtkTreeModel *model, GtkTreePath *path, gpointer data);
 209 
 210 static void free_busy_template_changed_cb (EMeetingTimeSelector *mts);
 211 
 212 G_DEFINE_TYPE_WITH_CODE (
 213 	EMeetingTimeSelector, e_meeting_time_selector, GTK_TYPE_TABLE,
 214 	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
 215 
 216 static void
 217 meeting_time_selector_set_property (GObject *object,
 218                                     guint property_id,
 219                                     const GValue *value,
 220                                     GParamSpec *pspec)
 221 {
 222 	switch (property_id) {
 223 		case PROP_SHOW_WEEK_NUMBERS:
 224 			e_meeting_time_selector_set_show_week_numbers (
 225 				E_MEETING_TIME_SELECTOR (object),
 226 				g_value_get_boolean (value));
 227 			return;
 228 
 229 		case PROP_USE_24_HOUR_FORMAT:
 230 			e_meeting_time_selector_set_use_24_hour_format (
 231 				E_MEETING_TIME_SELECTOR (object),
 232 				g_value_get_boolean (value));
 233 			return;
 234 
 235 		case PROP_WEEK_START_DAY:
 236 			e_meeting_time_selector_set_week_start_day (
 237 				E_MEETING_TIME_SELECTOR (object),
 238 				g_value_get_int (value));
 239 			return;
 240 	}
 241 
 242 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 243 }
 244 
 245 static void
 246 meeting_time_selector_get_property (GObject *object,
 247                                     guint property_id,
 248                                     GValue *value,
 249                                     GParamSpec *pspec)
 250 {
 251 	switch (property_id) {
 252 		case PROP_SHOW_WEEK_NUMBERS:
 253 			g_value_set_boolean (
 254 				value,
 255 				e_meeting_time_selector_get_show_week_numbers (
 256 				E_MEETING_TIME_SELECTOR (object)));
 257 			return;
 258 
 259 		case PROP_USE_24_HOUR_FORMAT:
 260 			g_value_set_boolean (
 261 				value,
 262 				e_meeting_time_selector_get_use_24_hour_format (
 263 				E_MEETING_TIME_SELECTOR (object)));
 264 			return;
 265 
 266 		case PROP_WEEK_START_DAY:
 267 			g_value_set_int (
 268 				value,
 269 				e_meeting_time_selector_get_week_start_day (
 270 				E_MEETING_TIME_SELECTOR (object)));
 271 			return;
 272 	}
 273 
 274 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 275 }
 276 
 277 static void
 278 meeting_time_selector_dispose (GObject *object)
 279 {
 280 	EMeetingTimeSelector *mts;
 281 
 282 	mts = E_MEETING_TIME_SELECTOR (object);
 283 
 284 	e_meeting_time_selector_remove_timeout (mts);
 285 
 286 	if (mts->model) {
 287 		g_signal_handlers_disconnect_matched (
 288 			mts->model, G_SIGNAL_MATCH_DATA,
 289 			0, 0, NULL, NULL, mts);
 290 		g_object_unref (mts->model);
 291 		mts->model = NULL;
 292 	}
 293 
 294 	mts->display_top = NULL;
 295 	mts->display_main = NULL;
 296 
 297 	if (mts->fb_refresh_not != 0) {
 298 		g_source_remove (mts->fb_refresh_not);
 299 		mts->fb_refresh_not = 0;
 300 	}
 301 
 302 	if (mts->style_change_idle_id != 0) {
 303 		g_source_remove (mts->style_change_idle_id);
 304 		mts->style_change_idle_id = 0;
 305 	}
 306 
 307 	/* Chain up to parent's dispose() method. */
 308 	G_OBJECT_CLASS (e_meeting_time_selector_parent_class)->dispose (object);
 309 }
 310 
 311 static void
 312 e_meeting_time_selector_class_init (EMeetingTimeSelectorClass *class)
 313 {
 314 	GObjectClass *object_class;
 315 	GtkWidgetClass *widget_class;
 316 
 317 	g_type_class_add_private (class, sizeof (EMeetingTimeSelectorPrivate));
 318 
 319 	object_class = G_OBJECT_CLASS (class);
 320 	object_class->set_property = meeting_time_selector_set_property;
 321 	object_class->get_property = meeting_time_selector_get_property;
 322 	object_class->dispose = meeting_time_selector_dispose;
 323 
 324 	widget_class = GTK_WIDGET_CLASS (class);
 325 	widget_class->realize = e_meeting_time_selector_realize;
 326 	widget_class->unrealize = e_meeting_time_selector_unrealize;
 327 	widget_class->style_set = e_meeting_time_selector_style_set;
 328 	widget_class->draw = e_meeting_time_selector_draw;
 329 
 330 	g_object_class_install_property (
 331 		object_class,
 332 		PROP_SHOW_WEEK_NUMBERS,
 333 		g_param_spec_boolean (
 334 			"show-week-numbers",
 335 			"Show Week Numbers",
 336 			NULL,
 337 			TRUE,
 338 			G_PARAM_READWRITE));
 339 
 340 	g_object_class_install_property (
 341 		object_class,
 342 		PROP_USE_24_HOUR_FORMAT,
 343 		g_param_spec_boolean (
 344 			"use-24-hour-format",
 345 			"Use 24-Hour Format",
 346 			NULL,
 347 			TRUE,
 348 			G_PARAM_READWRITE));
 349 
 350 	g_object_class_install_property (
 351 		object_class,
 352 		PROP_WEEK_START_DAY,
 353 		g_param_spec_int (
 354 			"week-start-day",
 355 			"Week Start Day",
 356 			NULL,
 357 			0,  /* Monday */
 358 			6,  /* Sunday */
 359 			0,
 360 			G_PARAM_READWRITE));
 361 
 362 	signals[CHANGED] = g_signal_new (
 363 		"changed",
 364 		G_TYPE_FROM_CLASS (object_class),
 365 		G_SIGNAL_RUN_FIRST,
 366 		G_STRUCT_OFFSET (EMeetingTimeSelectorClass, changed),
 367 		NULL, NULL,
 368 		g_cclosure_marshal_VOID__VOID,
 369 		G_TYPE_NONE, 0);
 370 }
 371 
 372 static void
 373 e_meeting_time_selector_init (EMeetingTimeSelector *mts)
 374 {
 375 	mts->priv = E_MEETING_TIME_SELECTOR_GET_PRIVATE (mts);
 376 
 377 	/* The shadow is drawn in the border so it must be >= 2 pixels. */
 378 	gtk_container_set_border_width (GTK_CONTAINER (mts), 2);
 379 
 380 	mts->accel_group = gtk_accel_group_new ();
 381 
 382 	mts->working_hours_only = TRUE;
 383 	mts->day_start_hour = 9;
 384 	mts->day_start_minute = 0;
 385 	mts->day_end_hour = 18;
 386 	mts->day_end_minute = 0;
 387 	mts->zoomed_out = TRUE;
 388 	mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE;
 389 
 390 	mts->list_view = NULL;
 391 
 392 	mts->fb_refresh_not = 0;
 393 	mts->style_change_idle_id = 0;
 394 
 395 	e_extensible_load_extensions (E_EXTENSIBLE (mts));
 396 }
 397 
 398 void
 399 e_meeting_time_selector_construct (EMeetingTimeSelector *mts,
 400                                    EMeetingStore *ems)
 401 {
 402 	GtkWidget *hbox, *vbox, *separator, *label, *table, *sw;
 403 	GtkWidget *alignment, *child_hbox, *arrow, *menuitem;
 404 	GtkWidget *child;
 405 	GtkAdjustment *adjustment;
 406 	GtkScrollable *scrollable;
 407 	GSList *group;
 408 	guint accel_key;
 409 	time_t meeting_start_time;
 410 	struct tm *meeting_start_tm;
 411 	AtkObject *a11y_label, *a11y_date_edit;
 412 
 413 	/* The default meeting time is the nearest half-hour interval in the
 414 	 * future, in working hours. */
 415 	meeting_start_time = time (NULL);
 416 	g_date_clear (&mts->meeting_start_time.date, 1);
 417 	g_date_set_time_t (&mts->meeting_start_time.date, meeting_start_time);
 418 	meeting_start_tm = localtime (&meeting_start_time);
 419 	mts->meeting_start_time.hour = meeting_start_tm->tm_hour;
 420 	mts->meeting_start_time.minute = meeting_start_tm->tm_min;
 421 
 422 	e_meeting_time_selector_find_nearest_interval (
 423 		mts, &mts->meeting_start_time,
 424 		&mts->meeting_end_time,
 425 		0, 0, 30);
 426 
 427 	e_meeting_time_selector_update_dates_shown (mts);
 428 
 429 	mts->meeting_positions_valid = FALSE;
 430 
 431 	mts->row_height = 17;
 432 	mts->col_width = 55;
 433 	mts->day_width = 55 * 24 + 1;
 434 
 435 	mts->auto_scroll_timeout_id = 0;
 436 
 437 	vbox = gtk_vbox_new (FALSE, 0);
 438 	gtk_table_attach (
 439 		GTK_TABLE (mts),
 440 		vbox, 0, 1, 0, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
 441 	gtk_widget_show (vbox);
 442 
 443 	mts->attendees_vbox_spacer = gtk_vbox_new (FALSE, 0);
 444 	gtk_box_pack_start (GTK_BOX (vbox), mts->attendees_vbox_spacer, FALSE, FALSE, 0);
 445 	gtk_widget_show (mts->attendees_vbox_spacer);
 446 
 447 	mts->attendees_vbox = gtk_vbox_new (FALSE, 0);
 448 	gtk_box_pack_start (GTK_BOX (vbox), mts->attendees_vbox, TRUE, TRUE, 0);
 449 	gtk_widget_show (mts->attendees_vbox);
 450 
 451 	/* build the etable */
 452 	mts->model = ems;
 453 
 454 	if (mts->model)
 455 		g_object_ref (mts->model);
 456 
 457 	g_signal_connect_swapped (
 458 		mts->model, "notify::free-busy-template",
 459 		G_CALLBACK (free_busy_template_changed_cb), mts);
 460 
 461 	g_signal_connect (
 462 		mts->model, "row_inserted",
 463 		G_CALLBACK (row_inserted_cb), mts);
 464 	g_signal_connect (
 465 		mts->model, "row_changed",
 466 		G_CALLBACK (row_changed_cb), mts);
 467 	g_signal_connect (
 468 		mts->model, "row_deleted",
 469 		G_CALLBACK (row_deleted_cb), mts);
 470 
 471 	mts->list_view = e_meeting_list_view_new (mts->model);
 472 	e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_ROLE_COL, FALSE);
 473 	e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_RSVP_COL, FALSE);
 474 	e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_STATUS_COL, FALSE);
 475 	e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_TYPE_COL, FALSE);
 476 
 477 	gtk_widget_show (GTK_WIDGET (mts->list_view));
 478 
 479 	sw = gtk_scrolled_window_new (NULL, NULL);
 480 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
 481 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
 482 	gtk_widget_set_child_visible (
 483 		gtk_scrolled_window_get_vscrollbar (
 484 		GTK_SCROLLED_WINDOW (sw)), FALSE);
 485 	gtk_widget_show (sw);
 486 	gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (mts->list_view));
 487 
 488 #if 0
 489 	/* FIXME: do we need sorting here */
 490 	g_signal_connect (
 491 		real_table->sort_info, "sort_info_changed",
 492 		G_CALLBACK (sort_info_changed_cb), mts);
 493 #endif
 494 
 495 	gtk_box_pack_start (GTK_BOX (mts->attendees_vbox), GTK_WIDGET (sw), TRUE, TRUE, 6);
 496 
 497 	/* The free/busy information */
 498 	mts->display_top = gnome_canvas_new ();
 499 	gtk_widget_set_size_request (mts->display_top, -1, mts->row_height * 3);
 500 	gnome_canvas_set_scroll_region (
 501 		GNOME_CANVAS (mts->display_top),
 502 		0, 0,
 503 		mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
 504 		mts->row_height * 3);
 505 	/* Add some horizontal padding for the shadow around the display. */
 506 	gtk_table_attach (
 507 		GTK_TABLE (mts), mts->display_top,
 508 		1, 4, 0, 1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
 509 	gtk_widget_show (mts->display_top);
 510 	g_signal_connect (
 511 		mts->display_top, "realize",
 512 		G_CALLBACK (e_meeting_time_selector_on_canvas_realized), mts);
 513 
 514 	mts->display_main = gnome_canvas_new ();
 515 	e_meeting_time_selector_update_main_canvas_scroll_region (mts);
 516 	/* Add some horizontal padding for the shadow around the display. */
 517 	gtk_table_attach (
 518 		GTK_TABLE (mts), mts->display_main,
 519 		1, 4, 1, 2,
 520 		GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
 521 	gtk_widget_show (mts->display_main);
 522 	g_signal_connect (
 523 		mts->display_main, "realize",
 524 		G_CALLBACK (e_meeting_time_selector_on_canvas_realized), mts);
 525 	g_signal_connect (
 526 		mts->display_main, "size_allocate",
 527 		G_CALLBACK (e_meeting_time_selector_on_canvas_size_allocate), mts);
 528 	g_signal_connect (
 529 		mts->display_main, "scroll-event",
 530 		G_CALLBACK (e_meeting_time_selector_on_canvas_scroll_event), mts);
 531 	/* used for displaying extended free/busy (XFB) display when hovering
 532 	 * over a busy period which carries XFB information */
 533 	g_signal_connect (mts->display_main,
 534 	                  "query-tooltip",
 535 	                  G_CALLBACK (e_meeting_time_selector_on_canvas_query_tooltip),
 536 	                  mts);
 537 	g_object_set (G_OBJECT (mts->display_main),
 538 	              "has-tooltip", TRUE,
 539 	              NULL);
 540 
 541 	scrollable = GTK_SCROLLABLE (mts->display_main);
 542 
 543 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
 544 	gtk_scrolled_window_set_vadjustment (
 545 		GTK_SCROLLED_WINDOW (sw), adjustment);
 546 
 547 	adjustment = gtk_scrollable_get_hadjustment (scrollable);
 548 	mts->hscrollbar = gtk_hscrollbar_new (adjustment);
 549 	gtk_adjustment_set_step_increment (adjustment, mts->day_width);
 550 	gtk_table_attach (
 551 		GTK_TABLE (mts), mts->hscrollbar,
 552 		1, 4, 2, 3, GTK_EXPAND | GTK_FILL, 0, 0, 0);
 553 	gtk_widget_show (mts->hscrollbar);
 554 
 555 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
 556 	mts->vscrollbar = gtk_vscrollbar_new (adjustment);
 557 	gtk_adjustment_set_step_increment (adjustment, mts->row_height);
 558 	gtk_table_attach (
 559 		GTK_TABLE (mts), mts->vscrollbar,
 560 		4, 5, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0);
 561 	gtk_widget_show (mts->vscrollbar);
 562 
 563 	/* Create the item in the top canvas. */
 564 	mts->item_top = gnome_canvas_item_new (
 565 		GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_top)->root),
 566 		e_meeting_time_selector_item_get_type (),
 567 		"EMeetingTimeSelectorItem::meeting_time_selector", mts,
 568 		NULL);
 569 
 570 	/* Create the item in the main canvas. */
 571 	mts->item_main = gnome_canvas_item_new (
 572 		GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_main)->root),
 573 		e_meeting_time_selector_item_get_type (),
 574 		"EMeetingTimeSelectorItem::meeting_time_selector", mts,
 575 		NULL);
 576 
 577 	/* Create the hbox containing the color key. */
 578 	hbox = gtk_hbox_new (FALSE, 2);
 579 	gtk_table_attach (
 580 		GTK_TABLE (mts), hbox,
 581 		1, 4, 3, 4, GTK_FILL, 0, 0, 8);
 582 	gtk_widget_show (hbox);
 583 
 584 	e_meeting_time_selector_add_key_color (mts, hbox, _("Tentative"), &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]);
 585 	e_meeting_time_selector_add_key_color (mts, hbox, _("Busy"), &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]);
 586 	e_meeting_time_selector_add_key_color (mts, hbox, _("Out of Office"), &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]);
 587 	e_meeting_time_selector_add_key_color (
 588 		mts, hbox, _("No Information"),
 589 		NULL);
 590 
 591 	separator = gtk_hseparator_new ();
 592 	gtk_table_attach (
 593 		GTK_TABLE (mts), separator,
 594 		0, 5, 4, 5, GTK_FILL, 0, 6, 6);
 595 	gtk_widget_show (separator);
 596 
 597 	/* Create the Invite Others & Options buttons on the left. */
 598 	hbox = gtk_hbox_new (FALSE, 4);
 599 	gtk_table_attach (
 600 		GTK_TABLE (mts), hbox,
 601 		0, 1, 3, 4, GTK_FILL, 0, 0, 0);
 602 	gtk_widget_show (hbox);
 603 
 604 	mts->add_attendees_button =
 605 		gtk_button_new_with_mnemonic (_("Atte_ndees..."));
 606 	gtk_button_set_image (
 607 		GTK_BUTTON (mts->add_attendees_button),
 608 		gtk_image_new_from_stock (
 609 			GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON));
 610 	gtk_box_pack_start (GTK_BOX (hbox), mts->add_attendees_button, TRUE, TRUE, 6);
 611 	gtk_widget_show (mts->add_attendees_button);
 612 	g_signal_connect (
 613 		mts->add_attendees_button, "clicked",
 614 		G_CALLBACK (e_meeting_time_selector_on_invite_others_button_clicked), mts);
 615 
 616 	mts->options_button = gtk_button_new ();
 617 	gtk_box_pack_start (GTK_BOX (hbox), mts->options_button, TRUE, TRUE, 6);
 618 	gtk_widget_show (mts->options_button);
 619 
 620 	g_signal_connect (
 621 		mts->options_button, "clicked",
 622 		G_CALLBACK (e_meeting_time_selector_on_options_button_clicked), mts);
 623 
 624 	child_hbox = gtk_hbox_new (FALSE, 2);
 625 	gtk_container_add (GTK_CONTAINER (mts->options_button), child_hbox);
 626 	gtk_widget_show (child_hbox);
 627 
 628 	label = gtk_label_new_with_mnemonic (_("O_ptions"));
 629 	accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
 630 	gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
 631 	gtk_widget_show (label);
 632 	gtk_widget_add_accelerator (
 633 		mts->options_button, "clicked", mts->accel_group,
 634 		accel_key, GDK_MOD1_MASK, 0);
 635 
 636 	arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
 637 	gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 6);
 638 	gtk_widget_show (arrow);
 639 
 640 	/* Create the Options menu. */
 641 	mts->options_menu = gtk_menu_new ();
 642 	gtk_menu_attach_to_widget (
 643 		GTK_MENU (mts->options_menu), mts->options_button,
 644 		e_meeting_time_selector_options_menu_detacher);
 645 
 646 	menuitem = gtk_check_menu_item_new_with_label ("");
 647 	child = gtk_bin_get_child (GTK_BIN (menuitem));
 648 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Show _only working hours"));
 649 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
 650 	gtk_check_menu_item_set_active (
 651 		GTK_CHECK_MENU_ITEM (menuitem),
 652 		mts->working_hours_only);
 653 
 654 	g_signal_connect (
 655 		menuitem, "toggled",
 656 		G_CALLBACK (e_meeting_time_selector_on_working_hours_toggled), mts);
 657 	gtk_widget_show (menuitem);
 658 
 659 	menuitem = gtk_check_menu_item_new_with_label ("");
 660 	child = gtk_bin_get_child (GTK_BIN (menuitem));
 661 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Show _zoomed out"));
 662 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
 663 	gtk_check_menu_item_set_active (
 664 		GTK_CHECK_MENU_ITEM (menuitem),
 665 		mts->zoomed_out);
 666 
 667 	g_signal_connect (
 668 		menuitem, "toggled",
 669 		G_CALLBACK (e_meeting_time_selector_on_zoomed_out_toggled), mts);
 670 	gtk_widget_show (menuitem);
 671 
 672 	menuitem = gtk_menu_item_new ();
 673 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
 674 	gtk_widget_set_sensitive (menuitem, FALSE);
 675 	gtk_widget_show (menuitem);
 676 
 677 	menuitem = gtk_menu_item_new_with_label ("");
 678 	child = gtk_bin_get_child (GTK_BIN (menuitem));
 679 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_Update free/busy"));
 680 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
 681 
 682 	g_signal_connect (
 683 		menuitem, "activate",
 684 		G_CALLBACK (e_meeting_time_selector_on_update_free_busy), mts);
 685 	gtk_widget_show (menuitem);
 686 
 687 	/* Create the 3 AutoPick buttons on the left. */
 688 	hbox = gtk_hbox_new (FALSE, 0);
 689 	gtk_table_attach (
 690 		GTK_TABLE (mts), hbox,
 691 		0, 1, 5, 6, GTK_FILL, 0, 0, 0);
 692 	gtk_widget_show (hbox);
 693 
 694 	mts->autopick_down_button = gtk_button_new_with_label ("");
 695 	child = gtk_bin_get_child (GTK_BIN (mts->autopick_down_button));
 696 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_<<"));
 697 	accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
 698 	gtk_widget_add_accelerator (
 699 		mts->autopick_down_button, "clicked", mts->accel_group,
 700 		accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0);
 701 	gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_down_button, TRUE, TRUE, 6);
 702 	gtk_widget_show (mts->autopick_down_button);
 703 	g_signal_connect (
 704 		mts->autopick_down_button, "clicked",
 705 		G_CALLBACK (e_meeting_time_selector_on_prev_button_clicked), mts);
 706 
 707 	mts->autopick_button = gtk_button_new ();
 708 	gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_button, TRUE, TRUE, 6);
 709 	gtk_widget_show (mts->autopick_button);
 710 
 711 	child_hbox = gtk_hbox_new (FALSE, 2);
 712 	gtk_container_add (GTK_CONTAINER (mts->autopick_button), child_hbox);
 713 	gtk_widget_show (child_hbox);
 714 
 715 	label = gtk_label_new ("");
 716 	gtk_label_set_text_with_mnemonic (GTK_LABEL (label), _("_Autopick"));
 717 	accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
 718 	gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
 719 	gtk_widget_show (label);
 720 	gtk_widget_add_accelerator (
 721 		mts->autopick_button, "clicked", mts->accel_group,
 722 		accel_key, GDK_MOD1_MASK, 0);
 723 	g_signal_connect (
 724 		mts->autopick_button, "clicked",
 725 		G_CALLBACK (e_meeting_time_selector_on_autopick_button_clicked), mts);
 726 
 727 	arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
 728 	gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 6);
 729 	gtk_widget_show (arrow);
 730 
 731 	mts->autopick_up_button = gtk_button_new_with_label ("");
 732 	child = gtk_bin_get_child (GTK_BIN (mts->autopick_up_button));
 733 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _(">_>"));
 734 	accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
 735 	gtk_widget_add_accelerator (
 736 		mts->autopick_up_button, "clicked", mts->accel_group,
 737 		accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0);
 738 	gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_up_button, TRUE, TRUE, 6);
 739 	gtk_widget_show (mts->autopick_up_button);
 740 	g_signal_connect (
 741 		mts->autopick_up_button, "clicked",
 742 		G_CALLBACK (e_meeting_time_selector_on_next_button_clicked), mts);
 743 
 744 	/* Create the Autopick menu. */
 745 	mts->autopick_menu = gtk_menu_new ();
 746 	gtk_menu_attach_to_widget (
 747 		GTK_MENU (mts->autopick_menu), mts->autopick_button,
 748 		e_meeting_time_selector_autopick_menu_detacher);
 749 
 750 	menuitem = gtk_radio_menu_item_new_with_label (NULL, "");
 751 	mts->autopick_all_item = menuitem;
 752 	child = gtk_bin_get_child (GTK_BIN (menuitem));
 753 	group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
 754 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_All people and resources"));
 755 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
 756 	g_signal_connect (
 757 		menuitem, "toggled",
 758 		G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
 759 	gtk_widget_show (menuitem);
 760 
 761 	menuitem = gtk_radio_menu_item_new_with_label (group, "");
 762 	mts->autopick_all_people_one_resource_item = menuitem;
 763 	child = gtk_bin_get_child (GTK_BIN (menuitem));
 764 	group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
 765 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("All _people and one resource"));
 766 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
 767 	g_signal_connect (
 768 		menuitem, "toggled",
 769 		G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
 770 	gtk_widget_show (menuitem);
 771 
 772 	menuitem = gtk_radio_menu_item_new_with_label (group, "");
 773 	mts->autopick_required_people_item = menuitem;
 774 	child = gtk_bin_get_child (GTK_BIN (menuitem));
 775 	group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
 776 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_Required people"));
 777 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
 778 	g_signal_connect (
 779 		menuitem, "activate",
 780 		G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
 781 	gtk_widget_show (menuitem);
 782 
 783 	menuitem = gtk_radio_menu_item_new_with_label (group, "");
 784 	mts->autopick_required_people_one_resource_item = menuitem;
 785 	child = gtk_bin_get_child (GTK_BIN (menuitem));
 786 	gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Required people and _one resource"));
 787 	gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
 788 	g_signal_connect (
 789 		menuitem, "activate",
 790 		G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
 791 	gtk_widget_show (menuitem);
 792 
 793 	/* Create the date entry fields on the right. */
 794 	alignment = gtk_alignment_new (0.0, 0.5, 0, 0);
 795 	gtk_table_attach (
 796 		GTK_TABLE (mts), alignment,
 797 		1, 4, 5, 6, GTK_FILL, 0, 0, 0);
 798 	gtk_widget_show (alignment);
 799 
 800 	table = gtk_table_new (2, 2, FALSE);
 801 	gtk_table_set_row_spacings (GTK_TABLE (table), 4);
 802 	gtk_container_add (GTK_CONTAINER (alignment), table);
 803 	gtk_widget_show (table);
 804 
 805 	mts->start_date_edit = e_date_edit_new ();
 806 	gtk_label_set_mnemonic_widget (GTK_LABEL (label), mts->start_date_edit);
 807 	a11y_label = gtk_widget_get_accessible (label);
 808 	a11y_date_edit = gtk_widget_get_accessible (mts->start_date_edit);
 809 	if (a11y_label != NULL && a11y_date_edit != NULL) {
 810 		atk_object_add_relationship (
 811 			a11y_date_edit,
 812 			ATK_RELATION_LABELLED_BY,
 813 			a11y_label);
 814 	}
 815 	e_date_edit_set_show_time (E_DATE_EDIT (mts->start_date_edit), TRUE);
 816 
 817 	g_object_bind_property (
 818 		mts, "show-week-numbers",
 819 		mts->start_date_edit, "show-week-numbers",
 820 		G_BINDING_SYNC_CREATE);
 821 
 822 	g_object_bind_property (
 823 		mts, "use-24-hour-format",
 824 		mts->start_date_edit, "use-24-hour-format",
 825 		G_BINDING_SYNC_CREATE);
 826 
 827 	g_object_bind_property (
 828 		mts, "week-start-day",
 829 		mts->start_date_edit, "week-start-day",
 830 		G_BINDING_SYNC_CREATE);
 831 
 832 	gtk_table_attach (
 833 		GTK_TABLE (table), mts->start_date_edit,
 834 		1, 2, 0, 1, GTK_FILL, 0, 0, 0);
 835 	gtk_widget_show (mts->start_date_edit);
 836 	g_signal_connect (
 837 		mts->start_date_edit, "changed",
 838 		G_CALLBACK (e_meeting_time_selector_on_start_time_changed), mts);
 839 
 840 	label = gtk_label_new_with_mnemonic (_("_Start time:"));
 841 	gtk_label_set_mnemonic_widget (GTK_LABEL (label), (mts->start_date_edit));
 842 
 843 	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 844 	gtk_table_attach (
 845 		GTK_TABLE (table), label,
 846 		0, 1, 0, 1, GTK_FILL, 0, 4, 0);
 847 	gtk_widget_show (label);
 848 
 849 	mts->end_date_edit = e_date_edit_new ();
 850 	gtk_label_set_mnemonic_widget (GTK_LABEL (label), mts->end_date_edit);
 851 	a11y_label = gtk_widget_get_accessible (label);
 852 	a11y_date_edit = gtk_widget_get_accessible (mts->end_date_edit);
 853 	if (a11y_label != NULL && a11y_date_edit != NULL) {
 854 		atk_object_add_relationship (
 855 			a11y_date_edit,
 856 					ATK_RELATION_LABELLED_BY,
 857 					a11y_label);
 858 	}
 859 	e_date_edit_set_show_time (E_DATE_EDIT (mts->end_date_edit), TRUE);
 860 
 861 	g_object_bind_property (
 862 		mts, "show-week-numbers",
 863 		mts->end_date_edit, "show-week-numbers",
 864 		G_BINDING_SYNC_CREATE);
 865 
 866 	g_object_bind_property (
 867 		mts, "use-24-hour-format",
 868 		mts->end_date_edit, "use-24-hour-format",
 869 		G_BINDING_SYNC_CREATE);
 870 
 871 	g_object_bind_property (
 872 		mts, "week-start-day",
 873 		mts->end_date_edit, "week-start-day",
 874 		G_BINDING_SYNC_CREATE);
 875 
 876 	gtk_table_attach (
 877 		GTK_TABLE (table), mts->end_date_edit,
 878 		1, 2, 1, 2, GTK_FILL, 0, 0, 0);
 879 	gtk_widget_show (mts->end_date_edit);
 880 	g_signal_connect (
 881 		mts->end_date_edit, "changed",
 882 		G_CALLBACK (e_meeting_time_selector_on_end_time_changed), mts);
 883 
 884 	label = gtk_label_new_with_mnemonic (_("_End time:"));
 885 	gtk_label_set_mnemonic_widget (GTK_LABEL (label), (mts->end_date_edit));
 886 
 887 	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 888 	gtk_table_attach (
 889 		GTK_TABLE (table), label,
 890 		0, 1, 1, 2, GTK_FILL, 0, 4, 0);
 891 	gtk_widget_show (label);
 892 
 893 	gtk_table_set_col_spacing (GTK_TABLE (mts), 0, 4);
 894 	gtk_table_set_row_spacing (GTK_TABLE (mts), 4, 12);
 895 
 896 	/* Allocate the colors. */
 897 	e_meeting_time_selector_alloc_named_color (mts, "snow", &mts->bg_color);
 898 	e_meeting_time_selector_alloc_named_color (mts, "snow3", &mts->all_attendees_bg_color);
 899 	e_meeting_time_selector_alloc_named_color (mts, "black", &mts->grid_color);
 900 	e_meeting_time_selector_alloc_named_color (mts, "white", &mts->grid_shadow_color);
 901 	e_meeting_time_selector_alloc_named_color (mts, "gray50", &mts->grid_unused_color);
 902 	e_meeting_time_selector_alloc_named_color (mts, "white", &mts->attendee_list_bg_color);
 903 
 904 	e_meeting_time_selector_alloc_named_color (mts, "snow4", &mts->meeting_time_bg_color);
 905 	e_meeting_time_selector_alloc_named_color (mts, "snow", &mts->busy_colors[E_MEETING_FREE_BUSY_FREE]);
 906 	e_meeting_time_selector_alloc_named_color (mts, "#a5d3ef", &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]);
 907 	e_meeting_time_selector_alloc_named_color (mts, "blue", &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]);
 908 	e_meeting_time_selector_alloc_named_color (mts, "#ce6194", &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]);
 909 
 910 	/* Connect handlers to the adjustments  scroll the other items. */
 911 	scrollable = GTK_SCROLLABLE (mts->display_main);
 912 	adjustment = gtk_scrollable_get_hadjustment (scrollable);
 913 	g_signal_connect (
 914 		adjustment, "value_changed",
 915 		G_CALLBACK (e_meeting_time_selector_hadjustment_changed), mts);
 916 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
 917 	g_signal_connect (
 918 		adjustment, "value_changed",
 919 		G_CALLBACK (e_meeting_time_selector_vadjustment_changed), mts);
 920 	g_signal_connect (
 921 		adjustment, "changed",
 922 		G_CALLBACK (e_meeting_time_selector_vadjustment_changed), mts);
 923 
 924 	e_meeting_time_selector_recalc_grid (mts);
 925 	e_meeting_time_selector_ensure_meeting_time_shown (mts);
 926 	e_meeting_time_selector_update_start_date_edit (mts);
 927 	e_meeting_time_selector_update_end_date_edit (mts);
 928 	e_meeting_time_selector_update_date_popup_menus (mts);
 929 
 930 	g_signal_emit (mts, signals[CHANGED], 0);
 931 }
 932 
 933 /* This adds a color to the color key beneath the main display. If color is
 934  * NULL, it displays the No Info pattern instead. */
 935 static void
 936 e_meeting_time_selector_add_key_color (EMeetingTimeSelector *mts,
 937                                        GtkWidget *hbox,
 938                                        gchar *label_text,
 939                                        GdkColor *color)
 940 {
 941 	GtkWidget *child_hbox, *darea, *label;
 942 
 943 	child_hbox = gtk_hbox_new (FALSE, 4);
 944 	gtk_box_pack_start (GTK_BOX (hbox), child_hbox, TRUE, TRUE, 0);
 945 	gtk_widget_show (child_hbox);
 946 
 947 	darea = gtk_drawing_area_new ();
 948 	gtk_box_pack_start (GTK_BOX (child_hbox), darea, FALSE, FALSE, 0);
 949 	g_object_set_data (G_OBJECT (darea), "data", mts);
 950 	gtk_widget_set_size_request (darea, 14, 14);
 951 	gtk_widget_show (darea);
 952 
 953 	label = gtk_label_new (label_text);
 954 	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 955 	gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
 956 	gtk_widget_show (label);
 957 
 958 	g_signal_connect (
 959 		darea, "draw",
 960 		G_CALLBACK (e_meeting_time_selector_draw_key_color), color);
 961 }
 962 
 963 static gint
 964 e_meeting_time_selector_draw_key_color (GtkWidget *darea,
 965                                         cairo_t *cr,
 966                                         GdkColor *color)
 967 {
 968 	EMeetingTimeSelector * mts;
 969 	GtkAllocation allocation;
 970 	GtkStyle *style;
 971 
 972 	style = gtk_widget_get_style (darea);
 973 	gtk_widget_get_allocation (darea, &allocation);
 974 
 975 	mts = g_object_get_data (G_OBJECT (darea), "data");
 976 
 977 	gtk_paint_shadow (
 978 		style, cr, GTK_STATE_NORMAL,
 979 		GTK_SHADOW_IN, NULL, NULL, 0, 0,
 980 		allocation.width, allocation.height);
 981 
 982 	if (color) {
 983 		gdk_cairo_set_source_color (cr, color);
 984 	} else {
 985 		cairo_set_source (cr, mts->no_info_pattern);
 986 	}
 987 	cairo_rectangle (
 988 		cr,
 989 		1, 1,
 990 		allocation.width - 2, allocation.height - 2);
 991 	cairo_fill (cr);
 992 
 993 	return TRUE;
 994 }
 995 
 996 static void
 997 e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector *mts,
 998                                            const gchar *name,
 999                                            GdkColor *c)
1000 {
1001 	g_return_if_fail (name != NULL);
1002 	g_return_if_fail (c != NULL);
1003 
1004 	if (!gdk_color_parse (name, c))
1005 		g_warning ("Failed to parse color: %s\n", name);
1006 }
1007 
1008 static void
1009 e_meeting_time_selector_options_menu_detacher (GtkWidget *widget,
1010                                                GtkMenu *menu)
1011 {
1012 	EMeetingTimeSelector *mts;
1013 
1014 	g_return_if_fail (widget != NULL);
1015 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (widget));
1016 
1017 	mts = E_MEETING_TIME_SELECTOR (widget);
1018 	g_return_if_fail (mts->options_menu == (GtkWidget *) menu);
1019 
1020 	mts->options_menu = NULL;
1021 }
1022 
1023 static void
1024 e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget,
1025                                                 GtkMenu *menu)
1026 {
1027 	EMeetingTimeSelector *mts;
1028 
1029 	g_return_if_fail (widget != NULL);
1030 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (widget));
1031 
1032 	mts = E_MEETING_TIME_SELECTOR (widget);
1033 	g_return_if_fail (mts->autopick_menu == (GtkWidget *) menu);
1034 
1035 	mts->autopick_menu = NULL;
1036 }
1037 
1038 GtkWidget *
1039 e_meeting_time_selector_new (EMeetingStore *ems)
1040 {
1041 	GtkWidget *mts;
1042 
1043 	mts = g_object_new (E_TYPE_MEETING_TIME_SELECTOR, NULL);
1044 
1045 	e_meeting_time_selector_construct (E_MEETING_TIME_SELECTOR (mts), ems);
1046 
1047 	return mts;
1048 }
1049 
1050 gboolean
1051 e_meeting_time_selector_get_show_week_numbers (EMeetingTimeSelector *mts)
1052 {
1053 	g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);
1054 
1055 	return mts->priv->show_week_numbers;
1056 }
1057 
1058 void
1059 e_meeting_time_selector_set_show_week_numbers (EMeetingTimeSelector *mts,
1060                                                gboolean show_week_numbers)
1061 {
1062 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1063 
1064 	if (mts->priv->show_week_numbers == show_week_numbers)
1065 		return;
1066 
1067 	mts->priv->show_week_numbers = show_week_numbers;
1068 
1069 	g_object_notify (G_OBJECT (mts), "show-week-numbers");
1070 }
1071 
1072 gboolean
1073 e_meeting_time_selector_get_use_24_hour_format (EMeetingTimeSelector *mts)
1074 {
1075 	g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);
1076 
1077 	return mts->priv->use_24_hour_format;
1078 }
1079 
1080 void
1081 e_meeting_time_selector_set_use_24_hour_format (EMeetingTimeSelector *mts,
1082                                                 gboolean use_24_hour_format)
1083 {
1084 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1085 
1086 	if (mts->priv->use_24_hour_format == use_24_hour_format)
1087 		return;
1088 
1089 	mts->priv->use_24_hour_format = use_24_hour_format;
1090 
1091 	g_object_notify (G_OBJECT (mts), "use-24-hour-format");
1092 }
1093 
1094 gint
1095 e_meeting_time_selector_get_week_start_day (EMeetingTimeSelector *mts)
1096 {
1097 	g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), 0);
1098 
1099 	return mts->priv->week_start_day;
1100 }
1101 
1102 void
1103 e_meeting_time_selector_set_week_start_day (EMeetingTimeSelector *mts,
1104                                             gint week_start_day)
1105 {
1106 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1107 
1108 	if (mts->priv->week_start_day == week_start_day)
1109 		return;
1110 
1111 	mts->priv->week_start_day = week_start_day;
1112 
1113 	g_object_notify (G_OBJECT (mts), "week-start-day");
1114 }
1115 
1116 static cairo_pattern_t *
1117 e_meeting_time_selector_create_no_info_pattern (EMeetingTimeSelector *mts)
1118 {
1119 	cairo_surface_t *surface;
1120 	cairo_pattern_t *pattern;
1121 	GdkColor color;
1122 	cairo_t *cr;
1123 
1124 	surface = gdk_window_create_similar_surface (
1125 		gtk_widget_get_window (GTK_WIDGET (mts)),
1126 		CAIRO_CONTENT_COLOR, 8, 8);
1127 	cr = cairo_create (surface);
1128 
1129 	gdk_color_parse ("white", &color);
1130 	gdk_cairo_set_source_color (cr, &color);
1131 	cairo_paint (cr);
1132 
1133 	gdk_cairo_set_source_color (cr, &mts->grid_color);
1134 	cairo_set_line_width (cr, 1.0);
1135 	cairo_move_to (cr, -1,  5);
1136 	cairo_line_to (cr,  9, -5);
1137 	cairo_move_to (cr, -1, 13);
1138 	cairo_line_to (cr,  9,  3);
1139 	cairo_stroke (cr);
1140 
1141 	cairo_destroy (cr);
1142 
1143 	pattern = cairo_pattern_create_for_surface (surface);
1144 	cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
1145 
1146 	cairo_surface_destroy (surface);
1147 
1148 	return pattern;
1149 }
1150 
1151 static void
1152 e_meeting_time_selector_realize (GtkWidget *widget)
1153 {
1154 	EMeetingTimeSelector *mts;
1155 
1156 	if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->realize)
1157 		(*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->realize)(widget);
1158 
1159 	mts = E_MEETING_TIME_SELECTOR (widget);
1160 
1161 	mts->no_info_pattern = e_meeting_time_selector_create_no_info_pattern (mts);
1162 }
1163 
1164 static void
1165 e_meeting_time_selector_unrealize (GtkWidget *widget)
1166 {
1167 	EMeetingTimeSelector *mts;
1168 
1169 	mts = E_MEETING_TIME_SELECTOR (widget);
1170 
1171 	cairo_pattern_destroy (mts->no_info_pattern);
1172 	mts->no_info_pattern = NULL;
1173 
1174 	if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->unrealize)
1175 		(*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->unrealize)(widget);
1176 }
1177 
1178 static gint
1179 get_cell_height (GtkTreeView *tree)
1180 {
1181 	GtkTreeViewColumn *column;
1182 	gint height = -1;
1183 
1184 	column = gtk_tree_view_get_column (tree, 0);
1185 	gtk_tree_view_column_cell_get_size (
1186 		column, NULL,
1187 		NULL, NULL,
1188 		NULL, &height);
1189 
1190 	return height;
1191 }
1192 
1193 static gboolean
1194 style_change_idle_func (EMeetingTimeSelector *mts)
1195 {
1196 	EMeetingTime saved_time;
1197 	GtkAdjustment *adjustment;
1198 	GtkWidget *widget;
1199 	gint hour, max_hour_width;
1200 	/*int maxheight;      */
1201 	PangoFontDescription *font_desc;
1202 	PangoContext *pango_context;
1203 	PangoFontMetrics *font_metrics;
1204 	PangoLayout *layout;
1205 
1206 	/* Set up Pango prerequisites */
1207 	widget = GTK_WIDGET (mts);
1208 	font_desc = gtk_widget_get_style (widget)->font_desc;
1209 	pango_context = gtk_widget_get_pango_context (widget);
1210 	font_metrics = pango_context_get_metrics (
1211 		pango_context, font_desc,
1212 		pango_context_get_language (pango_context));
1213 	layout = pango_layout_new (pango_context);
1214 
1215 	/* Calculate the widths of the hour strings in the style's font. */
1216 	max_hour_width = 0;
1217 	for (hour = 0; hour < 24; hour++) {
1218 		if (e_meeting_time_selector_get_use_24_hour_format (mts))
1219 			pango_layout_set_text (layout, EMeetingTimeSelectorHours[hour], -1);
1220 		else
1221 			pango_layout_set_text (layout, EMeetingTimeSelectorHours12[hour], -1);
1222 
1223 		pango_layout_get_pixel_size (layout, &mts->hour_widths[hour], NULL);
1224 		max_hour_width = MAX (max_hour_width, mts->hour_widths[hour]);
1225 	}
1226 
1227 	/* add also some padding for lines so it fits better */
1228 	mts->row_height = get_cell_height (GTK_TREE_VIEW (mts->list_view)) + 2;
1229 	mts->col_width = max_hour_width + 6;
1230 
1231 	e_meeting_time_selector_save_position (mts, &saved_time);
1232 	e_meeting_time_selector_recalc_grid (mts);
1233 	e_meeting_time_selector_restore_position (mts, &saved_time);
1234 
1235 	gtk_widget_set_size_request (mts->display_top, -1, mts->row_height * 3 + 4);
1236 
1237 	/*
1238 	 * FIXME: I can't find a way to get the treeview header heights
1239 	 * other than the below but it isn't nice to realize that widget here
1240 	 *
1241  *
1242 	gtk_widget_realize (mts->list_view);
1243 	gdk_window_get_position (
1244 		gtk_tree_view_get_bin_window (GTK_TREE_VIEW (mts->list_view)),
1245 		NULL, &maxheight);
1246 	gtk_widget_set_size_request (mts->attendees_vbox_spacer, 1, mts->row_height * 3 - maxheight);
1247  *
1248 	*/
1249 
1250 	gtk_widget_set_size_request (mts->attendees_vbox_spacer, 1, mts->row_height * 2 - 6);
1251 
1252 	widget = mts->display_main;
1253 
1254 	adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
1255 	gtk_adjustment_set_step_increment (adjustment, mts->day_width);
1256 
1257 	adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
1258 	gtk_adjustment_set_step_increment (adjustment, mts->row_height);
1259 
1260 	g_object_unref (layout);
1261 	pango_font_metrics_unref (font_metrics);
1262 
1263 	mts->style_change_idle_id = 0;
1264 
1265 	return FALSE;
1266 }
1267 
1268 static void
1269 e_meeting_time_selector_style_set (GtkWidget *widget,
1270                                    GtkStyle *previous_style)
1271 {
1272 	EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (widget);
1273 
1274 	if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)
1275 		(*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)(widget, previous_style);
1276 
1277 	if (!mts->style_change_idle_id)
1278 		mts->style_change_idle_id = g_idle_add (
1279 			(GSourceFunc) style_change_idle_func, widget);
1280 }
1281 
1282 /* This draws a shadow around the top display and main display. */
1283 static gint
1284 e_meeting_time_selector_draw (GtkWidget *widget,
1285                               cairo_t *cr)
1286 {
1287 	EMeetingTimeSelector *mts;
1288 
1289 	mts = E_MEETING_TIME_SELECTOR (widget);
1290 
1291 	e_meeting_time_selector_draw_shadow (mts, cr);
1292 
1293 	if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->draw)
1294 		(*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->draw)(widget, cr);
1295 
1296 	return FALSE;
1297 }
1298 
1299 static void
1300 e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts,
1301                                      cairo_t *cr)
1302 {
1303 	GtkAllocation allocation;
1304 	GtkStyle *style;
1305 	gint x, y, w, h;
1306 
1307 	cairo_save (cr);
1308 
1309 	/* Draw the shadow around the graphical displays. */
1310 	gtk_widget_get_allocation (mts->display_top, &allocation);
1311 	x = allocation.x - 2;
1312 	y = allocation.y - 2;
1313 	w = allocation.width + 4;
1314 	h = allocation.height + allocation.height + 4;
1315 
1316 	style = gtk_widget_get_style (GTK_WIDGET (mts));
1317 
1318 	gtk_paint_shadow (
1319 		style, cr, GTK_STATE_NORMAL,
1320 		GTK_SHADOW_IN, NULL, NULL, x, y, w, h);
1321 
1322 	cairo_restore (cr);
1323 }
1324 
1325 /* When the main canvas scrolls, we scroll the other canvases. */
1326 static void
1327 e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment,
1328                                              EMeetingTimeSelector *mts)
1329 {
1330 	GtkAdjustment *hadjustment;
1331 	GtkScrollable *scrollable;
1332 	gdouble value;
1333 
1334 	scrollable = GTK_SCROLLABLE (mts->display_top);
1335 	hadjustment = gtk_scrollable_get_hadjustment (scrollable);
1336 
1337 	value = gtk_adjustment_get_value (adjustment);
1338 	gtk_adjustment_set_value (hadjustment, value);
1339 }
1340 
1341 static void
1342 e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment,
1343                                              EMeetingTimeSelector *mts)
1344 {
1345 	GtkAdjustment *vadjustment;
1346 	GtkTreeView *tree_view;
1347 	gdouble value;
1348 
1349 	tree_view = GTK_TREE_VIEW (mts->list_view);
1350 	vadjustment = gtk_tree_view_get_vadjustment (tree_view);
1351 
1352 	value = gtk_adjustment_get_value (adjustment);
1353 	gtk_adjustment_set_value (vadjustment, value);
1354 }
1355 
1356 void
1357 e_meeting_time_selector_get_meeting_time (EMeetingTimeSelector *mts,
1358                                           gint *start_year,
1359                                           gint *start_month,
1360                                           gint *start_day,
1361                                           gint *start_hour,
1362                                           gint *start_minute,
1363                                           gint *end_year,
1364                                           gint *end_month,
1365                                           gint *end_day,
1366                                           gint *end_hour,
1367                                           gint *end_minute)
1368 {
1369 	*start_year = g_date_get_year (&mts->meeting_start_time.date);
1370 	*start_month = g_date_get_month (&mts->meeting_start_time.date);
1371 	*start_day = g_date_get_day (&mts->meeting_start_time.date);
1372 	*start_hour = mts->meeting_start_time.hour;
1373 	*start_minute = mts->meeting_start_time.minute;
1374 
1375 	*end_year = g_date_get_year (&mts->meeting_end_time.date);
1376 	*end_month = g_date_get_month (&mts->meeting_end_time.date);
1377 	*end_day = g_date_get_day (&mts->meeting_end_time.date);
1378 	*end_hour = mts->meeting_end_time.hour;
1379 	*end_minute = mts->meeting_end_time.minute;
1380 }
1381 
1382 gboolean
1383 e_meeting_time_selector_set_meeting_time (EMeetingTimeSelector *mts,
1384                                           gint start_year,
1385                                           gint start_month,
1386                                           gint start_day,
1387                                           gint start_hour,
1388                                           gint start_minute,
1389                                           gint end_year,
1390                                           gint end_month,
1391                                           gint end_day,
1392                                           gint end_hour,
1393                                           gint end_minute)
1394 {
1395 	g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);
1396 
1397 	/* Check the dates are valid. */
1398 	if (!g_date_valid_dmy (start_day, start_month, start_year)
1399 	    || !g_date_valid_dmy (end_day, end_month, end_year)
1400 	    || start_hour < 0 || start_hour > 23
1401 	    || end_hour < 0 || end_hour > 23
1402 	    || start_minute < 0 || start_minute > 59
1403 	    || end_minute < 0 || end_minute > 59)
1404 		return FALSE;
1405 
1406 	g_date_set_dmy (
1407 		&mts->meeting_start_time.date, start_day, start_month,
1408 			start_year);
1409 	mts->meeting_start_time.hour = start_hour;
1410 	mts->meeting_start_time.minute = start_minute;
1411 	g_date_set_dmy (
1412 		&mts->meeting_end_time.date,
1413 		end_day, end_month, end_year);
1414 	mts->meeting_end_time.hour = end_hour;
1415 	mts->meeting_end_time.minute = end_minute;
1416 
1417 	mts->meeting_positions_valid = FALSE;
1418 
1419 	gtk_widget_queue_draw (mts->display_top);
1420 	gtk_widget_queue_draw (mts->display_main);
1421 
1422 	/* Set the times in the EDateEdit widgets. */
1423 	e_meeting_time_selector_update_start_date_edit (mts);
1424 	e_meeting_time_selector_update_end_date_edit (mts);
1425 
1426 	g_signal_emit (mts, signals[CHANGED], 0);
1427 
1428 	return TRUE;
1429 }
1430 
1431 void
1432 e_meeting_time_selector_set_all_day (EMeetingTimeSelector *mts,
1433                                      gboolean all_day)
1434 {
1435 	EMeetingTime saved_time;
1436 
1437 	mts->all_day = all_day;
1438 
1439 	e_date_edit_set_show_time (
1440 		E_DATE_EDIT (mts->start_date_edit),
1441 		!all_day);
1442 	e_date_edit_set_show_time (
1443 		E_DATE_EDIT (mts->end_date_edit),
1444 		!all_day);
1445 
1446 	e_meeting_time_selector_save_position (mts, &saved_time);
1447 	e_meeting_time_selector_recalc_grid (mts);
1448 	e_meeting_time_selector_restore_position (mts, &saved_time);
1449 
1450 	gtk_widget_queue_draw (mts->display_top);
1451 	gtk_widget_queue_draw (mts->display_main);
1452 	e_meeting_time_selector_update_date_popup_menus (mts);
1453 }
1454 
1455 void
1456 e_meeting_time_selector_set_working_hours_only (EMeetingTimeSelector *mts,
1457                                                 gboolean working_hours_only)
1458 {
1459 	EMeetingTime saved_time;
1460 
1461 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1462 
1463 	if (mts->working_hours_only == working_hours_only)
1464 		return;
1465 
1466 	mts->working_hours_only = working_hours_only;
1467 
1468 	e_meeting_time_selector_save_position (mts, &saved_time);
1469 	e_meeting_time_selector_recalc_grid (mts);
1470 	e_meeting_time_selector_restore_position (mts, &saved_time);
1471 
1472 	gtk_widget_queue_draw (mts->display_top);
1473 	gtk_widget_queue_draw (mts->display_main);
1474 	e_meeting_time_selector_update_date_popup_menus (mts);
1475 }
1476 
1477 void
1478 e_meeting_time_selector_set_working_hours (EMeetingTimeSelector *mts,
1479                                            gint day_start_hour,
1480                                            gint day_start_minute,
1481                                            gint day_end_hour,
1482                                            gint day_end_minute)
1483 {
1484 	EMeetingTime saved_time;
1485 
1486 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1487 
1488 	if (mts->day_start_hour == day_start_hour
1489 	    && mts->day_start_minute == day_start_minute
1490 	    && mts->day_end_hour == day_end_hour
1491 	    && mts->day_end_minute == day_end_minute)
1492 		return;
1493 
1494 	mts->day_start_hour = day_start_hour;
1495 	mts->day_start_minute = day_start_minute;
1496 
1497 	/* Make sure we always show atleast an hour */
1498 	if (day_start_hour * 60 + day_start_minute + 60 < day_end_hour * 60 + day_end_minute) {
1499 		mts->day_end_hour = day_end_hour;
1500 		mts->day_end_minute = day_end_minute;
1501 	} else {
1502 		mts->day_end_hour = day_start_hour + 1;
1503 		mts->day_end_minute = day_start_minute;
1504 	}
1505 
1506 	e_meeting_time_selector_save_position (mts, &saved_time);
1507 	e_meeting_time_selector_recalc_grid (mts);
1508 	e_meeting_time_selector_restore_position (mts, &saved_time);
1509 
1510 	gtk_widget_queue_draw (mts->display_top);
1511 	gtk_widget_queue_draw (mts->display_main);
1512 	e_meeting_time_selector_update_date_popup_menus (mts);
1513 }
1514 
1515 void
1516 e_meeting_time_selector_set_zoomed_out (EMeetingTimeSelector *mts,
1517                                         gboolean zoomed_out)
1518 {
1519 	EMeetingTime saved_time;
1520 
1521 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1522 
1523 	if (mts->zoomed_out == zoomed_out)
1524 		return;
1525 
1526 	mts->zoomed_out = zoomed_out;
1527 
1528 	e_meeting_time_selector_save_position (mts, &saved_time);
1529 	e_meeting_time_selector_recalc_grid (mts);
1530 	e_meeting_time_selector_restore_position (mts, &saved_time);
1531 
1532 	gtk_widget_queue_draw (mts->display_top);
1533 	gtk_widget_queue_draw (mts->display_main);
1534 }
1535 
1536 static gboolean
1537 e_meeting_time_selector_refresh_cb (gpointer data)
1538 {
1539 	EMeetingTimeSelector *mts = data;
1540 
1541 	if (e_meeting_store_get_num_queries (mts->model) == 0) {
1542 		GdkCursor *cursor;
1543 		GdkWindow *window;
1544 
1545 		cursor = gdk_cursor_new (GDK_LEFT_PTR);
1546 		window = gtk_widget_get_window (GTK_WIDGET (mts));
1547 		if (window)
1548 			gdk_window_set_cursor (window, cursor);
1549 		g_object_unref (cursor);
1550 
1551 		mts->last_cursor_set = GDK_LEFT_PTR;
1552 
1553 		e_meeting_time_selector_item_set_normal_cursor (E_MEETING_TIME_SELECTOR_ITEM (mts->item_top));
1554 		e_meeting_time_selector_item_set_normal_cursor (E_MEETING_TIME_SELECTOR_ITEM (mts->item_main));
1555 	}
1556 
1557 	if (mts->display_top != NULL)
1558 		gtk_widget_queue_draw (mts->display_top);
1559 	if (mts->display_main != NULL)
1560 		gtk_widget_queue_draw (mts->display_main);
1561 
1562 	g_object_unref (mts);
1563 
1564 	return FALSE;
1565 }
1566 
1567 void
1568 e_meeting_time_selector_refresh_free_busy (EMeetingTimeSelector *mts,
1569                                            gint row,
1570                                            gboolean all)
1571 {
1572 	EMeetingTime start, end;
1573 
1574 	/* nothing to refresh, lets not leak a busy cursor */
1575 	if (e_meeting_store_count_actual_attendees (mts->model) <= 0)
1576 		return;
1577 
1578 	start = mts->meeting_start_time;
1579 	g_date_subtract_days (&start.date, E_MEETING_TIME_SELECTOR_FB_DAYS_BEFORE);
1580 	start.hour = 0;
1581 	start.minute = 0;
1582 	end = mts->meeting_end_time;
1583 	g_date_add_days (&end.date, E_MEETING_TIME_SELECTOR_FB_DAYS_AFTER);
1584 	end.hour = 0;
1585 	end.minute = 0;
1586 
1587 	/* XXX This function is called during schedule page initialization
1588 	 *     before the meeting time selector is realized, meaning it has
1589 	 *     no GdkWindow yet.  This avoids a runtime warning. */
1590 	if (gtk_widget_get_realized (GTK_WIDGET (mts))) {
1591 		GdkCursor *cursor;
1592 		GdkWindow *window;
1593 
1594 		/* Set the cursor to Busy.  We need to reset it to
1595 		 * normal once the free busy queries are complete. */
1596 		cursor = gdk_cursor_new (GDK_WATCH);
1597 		window = gtk_widget_get_window (GTK_WIDGET (mts));
1598 		gdk_window_set_cursor (window, cursor);
1599 		g_object_unref (cursor);
1600 
1601 		mts->last_cursor_set = GDK_WATCH;
1602 	}
1603 
1604 	/* Ref ourselves in case we are called back after destruction,
1605 	 * we can do this because we will get a call back even after
1606 	 * an error */
1607 	/* FIXME We should really have a mechanism to unqueue the
1608 	 * notification */
1609 	if (all) {
1610 		gint i;
1611 
1612 		for (i = 0; i < e_meeting_store_count_actual_attendees (mts->model); i++)
1613 			g_object_ref (mts);
1614 	} else {
1615 		g_object_ref (mts);
1616 	}
1617 
1618 	if (all)
1619 		e_meeting_store_refresh_all_busy_periods (
1620 			mts->model, &start, &end,
1621 			e_meeting_time_selector_refresh_cb, mts);
1622 	else
1623 		e_meeting_store_refresh_busy_periods (
1624 			mts->model, row, &start, &end,
1625 			e_meeting_time_selector_refresh_cb, mts);
1626 }
1627 
1628 EMeetingTimeSelectorAutopickOption
1629 e_meeting_time_selector_get_autopick_option (EMeetingTimeSelector *mts)
1630 {
1631 	GtkWidget *widget;
1632 
1633 	widget = mts->autopick_all_item;
1634 	if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
1635 		return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES;
1636 
1637 	widget = mts->autopick_all_people_one_resource_item;
1638 	if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
1639 		return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE;
1640 
1641 	widget = mts->autopick_required_people_item;
1642 	if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
1643 		return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE;
1644 
1645 	return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE;
1646 }
1647 
1648 void
1649 e_meeting_time_selector_set_autopick_option (EMeetingTimeSelector *mts,
1650                                              EMeetingTimeSelectorAutopickOption autopick_option)
1651 {
1652 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1653 
1654 	switch (autopick_option) {
1655 	case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES:
1656 		gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_item), TRUE);
1657 		break;
1658 	case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE:
1659 		gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_people_one_resource_item), TRUE);
1660 		break;
1661 	case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE:
1662 		gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_item), TRUE);
1663 		break;
1664 	case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE:
1665 		gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_one_resource_item), TRUE);
1666 		break;
1667 	}
1668 }
1669 
1670 void
1671 e_meeting_time_selector_set_read_only (EMeetingTimeSelector *mts,
1672                                        gboolean read_only)
1673 {
1674 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1675 
1676 	gtk_widget_set_sensitive (GTK_WIDGET (mts->list_view), !read_only);
1677 	gtk_widget_set_sensitive (mts->display_main, !read_only);
1678 	gtk_widget_set_sensitive (mts->add_attendees_button, !read_only);
1679 	gtk_widget_set_sensitive (mts->autopick_down_button, !read_only);
1680 	gtk_widget_set_sensitive (mts->autopick_button, !read_only);
1681 	gtk_widget_set_sensitive (mts->autopick_up_button, !read_only);
1682 	gtk_widget_set_sensitive (mts->start_date_edit, !read_only);
1683 	gtk_widget_set_sensitive (mts->end_date_edit, !read_only);
1684 }
1685 
1686 /*
1687  * DEBUGGING ROUTINES - functions to output various bits of data.
1688  */
1689 
1690 #ifdef E_MEETING_TIME_SELECTOR_DEBUG
1691 
1692 /* Debugging function to dump information on all attendees. */
1693 void
1694 e_meeting_time_selector_dump (EMeetingTimeSelector *mts)
1695 {
1696 	EMeetingTimeSelectorAttendee *attendee;
1697 	EMeetingTimeSelectorPeriod *period;
1698 	gint row, period_num;
1699 	gchar buffer[128];
1700 
1701 	g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));
1702 
1703 	g_print ("\n\nAttendee Information:\n");
1704 
1705 	for (row = 0; row < mts->attendees->len; row++) {
1706 		attendee = &g_array_index (mts->attendees,
1707 					   EMeetingTimeSelectorAttendee, row);
1708 		g_print ("Attendee: %s\n", attendee->name);
1709 		g_print (
1710 			"  Longest Busy Period: %i days\n",
1711 			attendee->longest_period_in_days);
1712 
1713 		e_meeting_time_selector_attendee_ensure_periods_sorted (mts, attendee);
1714 #if 1
1715 		for (period_num = 0;
1716 		     period_num < attendee->busy_periods->len;
1717 		     period_num++) {
1718 			period = &g_array_index (attendee->busy_periods,
1719 						 EMeetingTimeSelectorPeriod,
1720 						 period_num);
1721 
1722 			/* These are just for debugging so don't need i18n. */
1723 			g_date_strftime (
1724 				buffer, sizeof (buffer),
1725 				"%A, %B %d, %Y", &period->start.date);
1726 			g_print (
1727 				"  Start: %s %i:%02i\n", buffer,
1728 				period->start.hour, period->start.minute);
1729 
1730 			g_date_strftime (
1731 				buffer, sizeof (buffer),
1732 				"%A, %B %d, %Y", &period->end.date);
1733 			g_print (
1734 				"  End  : %s %i:%02i\n", buffer,
1735 				period->end.hour, period->end.minute);
1736 		}
1737 #endif
1738 	}
1739 
1740 }
1741 
1742 /* This formats a EMeetingTimein a string and returns it.
1743  * Note that it uses a static buffer. */
1744 gchar *
1745 e_meeting_time_selector_dump_time (EMeetingTime *mtstime)
1746 {
1747 	static gchar buffer[128];
1748 
1749 	gchar buffer2[128];
1750 
1751 	/* This is just for debugging so doesn't need i18n. */
1752 	g_date_strftime (
1753 		buffer, sizeof (buffer), "%A, %B %d, %Y",
1754 		&mtstime->date);
1755 	sprintf (
1756 		buffer2, " at %i:%02i", (gint) mtstime->hour,
1757 		(gint) mtstime->minute);
1758 	strcat (buffer, buffer2);
1759 
1760 	return buffer;
1761 }
1762 
1763 /* This formats a GDate in a string and returns it.
1764  * Note that it uses a static buffer. */
1765 gchar *
1766 e_meeting_time_selector_dump_date (GDate *date)
1767 {
1768 	static gchar buffer[128];
1769 
1770 	/* This is just for debugging so doesn't need i18n. */
1771 	g_date_strftime (buffer, sizeof (buffer), "%A, %B %d, %Y", date);
1772 	return buffer;
1773 }
1774 
1775 #endif /* E_MEETING_TIME_SELECTOR_DEBUG */
1776 
1777 static void
1778 e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button,
1779                                                          EMeetingTimeSelector *mts)
1780 {
1781 	e_meeting_list_view_invite_others_dialog (mts->list_view);
1782 }
1783 
1784 static void
1785 e_meeting_time_selector_on_options_button_clicked (GtkWidget *button,
1786                                                    EMeetingTimeSelector *mts)
1787 {
1788 	gtk_menu_popup (
1789 		GTK_MENU (mts->options_menu), NULL, NULL,
1790 		e_meeting_time_selector_options_menu_position_callback,
1791 		mts, 1, GDK_CURRENT_TIME);
1792 }
1793 
1794 static void
1795 e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu,
1796                                                         gint *x,
1797                                                         gint *y,
1798                                                         gboolean *push_in,
1799                                                         gpointer user_data)
1800 {
1801 	EMeetingTimeSelector *mts;
1802 	GtkRequisition menu_requisition;
1803 	GtkAllocation allocation;
1804 	GtkWidget *widget;
1805 	GdkWindow *window;
1806 	gint max_x, max_y;
1807 
1808 	mts = E_MEETING_TIME_SELECTOR (user_data);
1809 
1810 	/* Calculate our preferred position. */
1811 	widget = mts->options_button;
1812 	window = gtk_widget_get_window (widget);
1813 	gdk_window_get_origin (window, x, y);
1814 	gtk_widget_get_allocation (widget, &allocation);
1815 	*x += allocation.x;
1816 	*y += allocation.y + allocation.height - 2;
1817 
1818 	/* Now make sure we are on the screen. */
1819 	gtk_widget_get_preferred_size (mts->options_menu, &menu_requisition, NULL);
1820 	max_x = MAX (0, gdk_screen_width () - menu_requisition.width);
1821 	max_y = MAX (0, gdk_screen_height () - menu_requisition.height);
1822 	*x = CLAMP (*x, 0, max_x);
1823 	*y = CLAMP (*y, 0, max_y);
1824 }
1825 
1826 static void
1827 e_meeting_time_selector_on_update_free_busy (GtkWidget *button,
1828                                              EMeetingTimeSelector *mts)
1829 {
1830 	/* Make sure the menu pops down, which doesn't happen by default if
1831 	 * keyboard accelerators are used. */
1832 	if (gtk_widget_get_visible (mts->options_menu))
1833 		gtk_menu_popdown (GTK_MENU (mts->options_menu));
1834 
1835 	e_meeting_time_selector_refresh_free_busy (mts, 0, TRUE);
1836 }
1837 
1838 static void
1839 e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button,
1840                                                     EMeetingTimeSelector *mts)
1841 {
1842 	gtk_menu_popup (
1843 		GTK_MENU (mts->autopick_menu), NULL, NULL,
1844 		e_meeting_time_selector_autopick_menu_position_callback,
1845 		mts, 1, GDK_CURRENT_TIME);
1846 }
1847 
1848 static void
1849 e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu,
1850                                                          gint *x,
1851                                                          gint *y,
1852                                                          gboolean *push_in,
1853                                                          gpointer user_data)
1854 {
1855 	EMeetingTimeSelector *mts;
1856 	GtkRequisition menu_requisition;
1857 	GtkAllocation allocation;
1858 	GtkWidget *widget;
1859 	GdkWindow *window;
1860 	gint max_x, max_y;
1861 
1862 	mts = E_MEETING_TIME_SELECTOR (user_data);
1863 
1864 	/* Calculate our preferred position. */
1865 	widget = mts->autopick_button;
1866 	window = gtk_widget_get_window (widget);
1867 	gdk_window_get_origin (window, x, y);
1868 	gtk_widget_get_allocation (widget, &allocation);
1869 	*x += allocation.x;
1870 	*y += allocation.y + allocation.height - 2;
1871 
1872 	/* Now make sure we are on the screen. */
1873 	gtk_widget_get_preferred_size (mts->autopick_menu, &menu_requisition, NULL);
1874 	max_x = MAX (0, gdk_screen_width () - menu_requisition.width);
1875 	max_y = MAX (0, gdk_screen_height () - menu_requisition.height);
1876 	*x = CLAMP (*x, 0, max_x);
1877 	*y = CLAMP (*y, 0, max_y);
1878 }
1879 
1880 static void
1881 e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button,
1882                                                     EMeetingTimeSelector *mts)
1883 {
1884 	/* Make sure the menu pops down, which doesn't happen by default if
1885 	 * keyboard accelerators are used. */
1886 	if (gtk_widget_get_visible (mts->autopick_menu))
1887 		gtk_menu_popdown (GTK_MENU (mts->autopick_menu));
1888 }
1889 
1890 static void
1891 e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button,
1892                                                 EMeetingTimeSelector *mts)
1893 {
1894 	e_meeting_time_selector_autopick (mts, FALSE);
1895 }
1896 
1897 static void
1898 e_meeting_time_selector_on_next_button_clicked (GtkWidget *button,
1899                                                 EMeetingTimeSelector *mts)
1900 {
1901 	e_meeting_time_selector_autopick (mts, TRUE);
1902 }
1903 
1904 /* This tries to find the previous or next meeting time for which all
1905  * attendees will be available. */
1906 static void
1907 e_meeting_time_selector_autopick (EMeetingTimeSelector *mts,
1908                                   gboolean forward)
1909 {
1910 	EMeetingTime start_time, end_time, *resource_free;
1911 	EMeetingAttendee *attendee;
1912 	EMeetingFreeBusyPeriod *period;
1913 	EMeetingTimeSelectorAutopickOption autopick_option;
1914 	gint duration_days, duration_hours, duration_minutes, row;
1915 	gboolean meeting_time_ok, skip_optional = FALSE;
1916 	gboolean need_one_resource = FALSE, found_resource;
1917 
1918 	/* Get the current meeting duration in days + hours + minutes. */
1919 	e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes);
1920 
1921 	/* Find the first appropriate start time. */
1922 	start_time = mts->meeting_start_time;
1923 	if (forward)
1924 		e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
1925 	else
1926 		e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
1927 
1928 	/* Determine if we can skip optional people and if we only need one
1929 	 * resource based on the autopick option. */
1930 	autopick_option = e_meeting_time_selector_get_autopick_option (mts);
1931 	if (autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE
1932 	    || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE)
1933 		skip_optional = TRUE;
1934 	if (autopick_option == E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE
1935 	    || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE)
1936 		need_one_resource = TRUE;
1937 
1938 	/* Keep moving forward or backward until we find a possible meeting
1939 	 * time. */
1940 	for (;;) {
1941 		meeting_time_ok = TRUE;
1942 		found_resource = FALSE;
1943 		resource_free = NULL;
1944 
1945 		/* Step through each attendee, checking if the meeting time
1946 		 * intersects one of the attendees busy periods. */
1947 		for (row = 0; row <  e_meeting_store_count_actual_attendees (mts->model); row++) {
1948 			attendee = e_meeting_store_find_attendee_at_row (mts->model, row);
1949 
1950 			/* Skip optional people if they don't matter. */
1951 			if (skip_optional && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_OPTIONAL_PERSON)
1952 				continue;
1953 
1954 			period = e_meeting_time_selector_find_time_clash (mts, attendee, &start_time, &end_time);
1955 
1956 			if (need_one_resource && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_RESOURCE) {
1957 				if (period) {
1958 					/* We want to remember the closest
1959 					 * prev/next time that one resource is
1960 					 * available, in case we don't find any
1961 					 * free resources. */
1962 					if (forward) {
1963 						if (!resource_free || e_meeting_time_compare_times (resource_free, &period->end) > 0)
1964 							resource_free = &period->end;
1965 					} else {
1966 						if (!resource_free || e_meeting_time_compare_times (resource_free, &period->start) < 0)
1967 							resource_free = &period->start;
1968 					}
1969 
1970 				} else {
1971 					found_resource = TRUE;
1972 				}
1973 			} else if (period) {
1974 				/* Skip the period which clashed. */
1975 				if (forward) {
1976 					start_time = period->end;
1977 				} else {
1978 					start_time = period->start;
1979 					e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes);
1980 				}
1981 				meeting_time_ok = FALSE;
1982 				break;
1983 			}
1984 		}
1985 
1986 		/* Check that we found one resource if necessary. If not, skip
1987 		 * to the closest time that a resource is free. Note that if
1988 		 * there are no resources, resource_free will never get set,
1989 		 * so we assume the meeting time is OK. */
1990 		if (meeting_time_ok && need_one_resource && !found_resource
1991 		    && resource_free) {
1992 			if (forward) {
1993 				start_time = *resource_free;
1994 			} else {
1995 				start_time = *resource_free;
1996 				e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes);
1997 			}
1998 			meeting_time_ok = FALSE;
1999 		}
2000 
2001 		if (meeting_time_ok) {
2002 			mts->meeting_start_time = start_time;
2003 			mts->meeting_end_time = end_time;
2004 			mts->meeting_positions_valid = FALSE;
2005 			gtk_widget_queue_draw (mts->display_top);
2006 			gtk_widget_queue_draw (mts->display_main);
2007 
2008 			/* Make sure the time is shown. */
2009 			e_meeting_time_selector_ensure_meeting_time_shown (mts);
2010 
2011 			/* Set the times in the EDateEdit widgets. */
2012 			e_meeting_time_selector_update_start_date_edit (mts);
2013 			e_meeting_time_selector_update_end_date_edit (mts);
2014 
2015 			g_signal_emit (mts, signals[CHANGED], 0);
2016 
2017 			return;
2018 		}
2019 
2020 		/* Move forward to the next possible interval. */
2021 		if (forward)
2022 			e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
2023 		else
2024 			e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
2025 	}
2026 }
2027 
2028 static void
2029 e_meeting_time_selector_calculate_time_difference (EMeetingTime *start,
2030                                                    EMeetingTime *end,
2031                                                    gint *days,
2032                                                    gint *hours,
2033                                                    gint *minutes)
2034 {
2035 	*days = g_date_get_julian (&end->date) - g_date_get_julian (&start->date);
2036 	*hours = end->hour - start->hour;
2037 	*minutes = end->minute - start->minute;
2038 	if (*minutes < 0) {
2039 		*minutes += 60;
2040 		*hours = *hours - 1;
2041 	}
2042 	if (*hours < 0) {
2043 		*hours += 24;
2044 		*days = *days - 1;
2045 	}
2046 }
2047 
2048 /* This moves the given time forward to the next suitable start of a meeting.
2049  * If zoomed_out is set, this means every hour. If not every half-hour. */
2050 static void
2051 e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts,
2052                                                EMeetingTime *start_time,
2053                                                EMeetingTime *end_time,
2054                                                gint days,
2055                                                gint hours,
2056                                                gint mins)
2057 {
2058 	gint minutes_shown;
2059 	gboolean set_to_start_of_working_day = FALSE;
2060 
2061 	if (!mts->all_day) {
2062 		if (mts->zoomed_out) {
2063 			start_time->hour++;
2064 			start_time->minute = 0;
2065 		} else {
2066 			start_time->minute += 30;
2067 			start_time->minute -= start_time->minute % 30;
2068 		}
2069 	} else {
2070 		g_date_add_days (&start_time->date, 1);
2071 		start_time->hour = 0;
2072 		start_time->minute = 0;
2073 	}
2074 	e_meeting_time_selector_fix_time_overflows (start_time);
2075 
2076 	*end_time = *start_time;
2077 	e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2078 
2079 	/* Check if the interval is less than a day as seen in the display.
2080 	 * If it isn't we don't worry about the working day. */
2081 	if (!mts->working_hours_only || days > 0)
2082 		return;
2083 	minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60;
2084 	minutes_shown += mts->day_end_minute - mts->day_start_minute;
2085 	if (hours * 60 + mins > minutes_shown)
2086 		return;
2087 
2088 	/* If the meeting time finishes past the end of the working day, move
2089 	 * onto the start of the next working day. If the meeting time starts
2090 	 * before the working day, move it on as well. */
2091 	if (start_time->hour > mts->day_end_hour
2092 	    || (start_time->hour == mts->day_end_hour
2093 		&& start_time->minute > mts->day_end_minute)
2094 	    || end_time->hour > mts->day_end_hour
2095 	    || (end_time->hour == mts->day_end_hour
2096 		&& end_time->minute > mts->day_end_minute)) {
2097 		g_date_add_days (&start_time->date, 1);
2098 		set_to_start_of_working_day = TRUE;
2099 	} else if (start_time->hour < mts->day_start_hour
2100 		   || (start_time->hour == mts->day_start_hour
2101 		       && start_time->minute < mts->day_start_minute)) {
2102 		set_to_start_of_working_day = TRUE;
2103 	}
2104 
2105 	if (set_to_start_of_working_day) {
2106 		start_time->hour = mts->day_start_hour;
2107 		start_time->minute = mts->day_start_minute;
2108 
2109 		if (mts->zoomed_out) {
2110 			if (start_time->minute > 0) {
2111 				start_time->hour++;
2112 				start_time->minute = 0;
2113 			}
2114 		} else {
2115 			start_time->minute += 29;
2116 			start_time->minute -= start_time->minute % 30;
2117 		}
2118 
2119 		e_meeting_time_selector_fix_time_overflows (start_time);
2120 
2121 		*end_time = *start_time;
2122 		e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2123 	}
2124 }
2125 
2126 /* This moves the given time backward to the next suitable start of a meeting.
2127  * If zoomed_out is set, this means every hour. If not every half-hour. */
2128 static void
2129 e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts,
2130                                                         EMeetingTime *start_time,
2131                                                         EMeetingTime *end_time,
2132                                                         gint days,
2133                                                         gint hours,
2134                                                         gint mins)
2135 {
2136 	gint new_hour, minutes_shown;
2137 	gboolean set_to_end_of_working_day = FALSE;
2138 
2139 	if (!mts->all_day) {
2140 		new_hour = start_time->hour;
2141 		if (mts->zoomed_out) {
2142 			if (start_time->minute == 0)
2143 				new_hour--;
2144 			start_time->minute = 0;
2145 		} else {
2146 			if (start_time->minute == 0) {
2147 				start_time->minute = 30;
2148 				new_hour--;
2149 			} else if (start_time->minute <= 30)
2150 				start_time->minute = 0;
2151 			else
2152 				start_time->minute = 30;
2153 		}
2154 		if (new_hour < 0) {
2155 			new_hour += 24;
2156 			g_date_subtract_days (&start_time->date, 1);
2157 		}
2158 		start_time->hour = new_hour;
2159 	} else {
2160 		g_date_subtract_days (&start_time->date, 1);
2161 		start_time->hour = 0;
2162 		start_time->minute = 0;
2163 	}
2164 
2165 	*end_time = *start_time;
2166 	e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2167 
2168 	/* Check if the interval is less than a day as seen in the display.
2169 	 * If it isn't we don't worry about the working day. */
2170 	if (!mts->working_hours_only || days > 0)
2171 		return;
2172 	minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60;
2173 	minutes_shown += mts->day_end_minute - mts->day_start_minute;
2174 	if (hours * 60 + mins > minutes_shown)
2175 		return;
2176 
2177 	/* If the meeting time finishes past the end of the working day, move
2178 	 * back to the end of the working day. If the meeting time starts
2179 	 * before the working day, move it back to the end of the previous
2180 	 * working day. */
2181 	if (start_time->hour > mts->day_end_hour
2182 	    || (start_time->hour == mts->day_end_hour
2183 		&& start_time->minute > mts->day_end_minute)
2184 	    || end_time->hour > mts->day_end_hour
2185 	    || (end_time->hour == mts->day_end_hour
2186 		&& end_time->minute > mts->day_end_minute)) {
2187 		set_to_end_of_working_day = TRUE;
2188 	} else if (start_time->hour < mts->day_start_hour
2189 		   || (start_time->hour == mts->day_start_hour
2190 		       && start_time->minute < mts->day_start_minute)) {
2191 		g_date_subtract_days (&end_time->date, 1);
2192 		set_to_end_of_working_day = TRUE;
2193 	}
2194 
2195 	if (set_to_end_of_working_day) {
2196 		end_time->hour = mts->day_end_hour;
2197 		end_time->minute = mts->day_end_minute;
2198 		*start_time = *end_time;
2199 		e_meeting_time_selector_adjust_time (start_time, -days, -hours, -mins);
2200 
2201 		if (mts->zoomed_out) {
2202 			start_time->minute = 0;
2203 		} else {
2204 			start_time->minute -= start_time->minute % 30;
2205 		}
2206 
2207 		*end_time = *start_time;
2208 		e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
2209 	}
2210 }
2211 
2212 /* This adds on the given days, hours & minutes to a EMeetingTimeSelectorTime.
2213  * It is used to calculate the end of a period given a start & duration.
2214  * Days, hours & minutes can be negative, to move backwards, but they should
2215  * be within normal ranges, e.g. hours should be between -23 and 23. */
2216 static void
2217 e_meeting_time_selector_adjust_time (EMeetingTime *mtstime,
2218                                      gint days,
2219                                      gint hours,
2220                                      gint minutes)
2221 {
2222 	gint new_hours, new_minutes;
2223 
2224 	/* We have to handle negative values for hous and minutes here, since
2225 	 * EMeetingTimeuses guint8s to store them. */
2226 	new_minutes = mtstime->minute + minutes;
2227 	if (new_minutes < 0) {
2228 		new_minutes += 60;
2229 		hours -= 1;
2230 	}
2231 
2232 	new_hours = mtstime->hour + hours;
2233 	if (new_hours < 0) {
2234 		new_hours += 24;
2235 		days -= 1;
2236 	}
2237 
2238 	g_date_add_days (&mtstime->date, days);
2239 	mtstime->hour = new_hours;
2240 	mtstime->minute = new_minutes;
2241 
2242 	e_meeting_time_selector_fix_time_overflows (mtstime);
2243 }
2244 
2245 /* This looks for any busy period of the given attendee which clashes with
2246  * the start and end time. It uses a binary search. */
2247 static EMeetingFreeBusyPeriod *
2248 e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
2249                                          EMeetingAttendee *attendee,
2250                                          EMeetingTime *start_time,
2251                                          EMeetingTime *end_time)
2252 {
2253 	EMeetingFreeBusyPeriod *period;
2254 	const GArray *busy_periods;
2255 	gint period_num;
2256 
2257 	busy_periods = e_meeting_attendee_get_busy_periods (attendee);
2258 	period_num = e_meeting_attendee_find_first_busy_period (attendee, &start_time->date);
2259 
2260 	if (period_num == -1)
2261 		return NULL;
2262 
2263 	/* Step forward through the busy periods until we find a clash or we
2264 	 * go past the end_time. */
2265 	while (period_num < busy_periods->len) {
2266 		period = &g_array_index (busy_periods,  EMeetingFreeBusyPeriod, period_num);
2267 
2268 		/* If the period starts at or after the end time, there is no
2269 		 * clash and we are finished. The busy periods are sorted by
2270 		 * their start times, so all the rest will be later. */
2271 		if (e_meeting_time_compare_times (&period->start, end_time) >= 0)
2272 			return NULL;
2273 
2274 		/* If the period ends after the start time, we have found a
2275 		 * clash. From the above test we already know the busy period
2276 		 * isn't completely after the meeting time. */
2277 		if (e_meeting_time_compare_times (&period->end, start_time) > 0)
2278 			return period;
2279 
2280 		period_num++;
2281 	}
2282 
2283 	return NULL;
2284 }
2285 
2286 static void
2287 e_meeting_time_selector_on_zoomed_out_toggled (GtkCheckMenuItem *menuitem,
2288                                                EMeetingTimeSelector *mts)
2289 {
2290 	gboolean active;
2291 
2292 	/* Make sure the menu pops down, which doesn't happen by default if
2293 	 * keyboard accelerators are used. */
2294 	if (gtk_widget_get_visible (mts->options_menu))
2295 		gtk_menu_popdown (GTK_MENU (mts->options_menu));
2296 
2297 	active = gtk_check_menu_item_get_active (menuitem);
2298 	e_meeting_time_selector_set_zoomed_out (mts, active);
2299 	e_meeting_time_selector_ensure_meeting_time_shown (mts);
2300 }
2301 
2302 static void
2303 e_meeting_time_selector_on_working_hours_toggled (GtkCheckMenuItem *menuitem,
2304                                                   EMeetingTimeSelector *mts)
2305 {
2306 	gboolean active;
2307 
2308 	/* Make sure the menu pops down, which doesn't happen by default if
2309 	 * keyboard accelerators are used. */
2310 	if (gtk_widget_get_visible (mts->options_menu))
2311 		gtk_menu_popdown (GTK_MENU (mts->options_menu));
2312 
2313 	active = gtk_check_menu_item_get_active (menuitem);
2314 	e_meeting_time_selector_set_working_hours_only (mts, active);
2315 	e_meeting_time_selector_ensure_meeting_time_shown (mts);
2316 }
2317 
2318 /* This recalculates day_width, first_hour_shown and last_hour_shown. */
2319 static void
2320 e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts)
2321 {
2322 	if (mts->working_hours_only) {
2323 		mts->first_hour_shown = mts->day_start_hour;
2324 		mts->last_hour_shown = mts->day_end_hour;
2325 		if (mts->day_end_minute != 0)
2326 			mts->last_hour_shown += 1;
2327 	} else {
2328 		mts->first_hour_shown = 0;
2329 		mts->last_hour_shown = 24;
2330 	}
2331 
2332 	/* In the brief view we use the nearest hours divisible by 3. */
2333 	if (mts->zoomed_out) {
2334 		mts->first_hour_shown -= mts->first_hour_shown % 3;
2335 		mts->last_hour_shown += 2;
2336 		mts->last_hour_shown -= mts->last_hour_shown % 3;
2337 	}
2338 
2339 	mts->day_width = mts->col_width	* (mts->last_hour_shown - mts->first_hour_shown);
2340 	if (mts->zoomed_out)
2341 		mts->day_width /= 3;
2342 
2343 	/* Add one pixel for the extra vertical grid line. */
2344 	mts->day_width++;
2345 
2346 	gnome_canvas_set_scroll_region (
2347 		GNOME_CANVAS (mts->display_top),
2348 		0, 0,
2349 		mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
2350 		mts->row_height * 3);
2351 	e_meeting_time_selector_update_main_canvas_scroll_region (mts);
2352 
2353 	e_meeting_time_selector_recalc_date_format (mts);
2354 	mts->meeting_positions_valid = FALSE;
2355 }
2356 
2357 /* This saves the first visible time in the given EMeetingTimeSelectorTime. */
2358 static void
2359 e_meeting_time_selector_save_position (EMeetingTimeSelector *mts,
2360                                        EMeetingTime *mtstime)
2361 {
2362 	gint scroll_x, scroll_y;
2363 
2364 	gnome_canvas_get_scroll_offsets (
2365 		GNOME_CANVAS (mts->display_main),
2366 		&scroll_x, &scroll_y);
2367 	e_meeting_time_selector_calculate_time (mts, scroll_x, mtstime);
2368 }
2369 
2370 /* This restores a saved position. */
2371 static void
2372 e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts,
2373                                           EMeetingTime *mtstime)
2374 {
2375 	gint scroll_x, scroll_y, new_scroll_x;
2376 
2377 	new_scroll_x = e_meeting_time_selector_calculate_time_position (
2378 		mts, mtstime);
2379 	gnome_canvas_get_scroll_offsets (
2380 		GNOME_CANVAS (mts->display_main),
2381 		&scroll_x, &scroll_y);
2382 	gnome_canvas_scroll_to (
2383 		GNOME_CANVAS (mts->display_main),
2384 		new_scroll_x, scroll_y);
2385 }
2386 
2387 /* This returns the x pixel coords of the meeting time in the entire scroll
2388  * region. It recalculates them if they have been marked as invalid.
2389  * If it returns FALSE then no meeting time is set or the meeting time is
2390  * not visible in the current scroll area. */
2391 gboolean
2392 e_meeting_time_selector_get_meeting_time_positions (EMeetingTimeSelector *mts,
2393                                                     gint *start_x,
2394                                                     gint *end_x)
2395 {
2396 	if (mts->meeting_positions_valid) {
2397 		if (mts->meeting_positions_in_scroll_area) {
2398 			*start_x = mts->meeting_start_x;
2399 			*end_x = mts->meeting_end_x;
2400 			return TRUE;
2401 		} else {
2402 			return FALSE;
2403 		}
2404 	}
2405 
2406 	mts->meeting_positions_valid = TRUE;
2407 
2408 	/* Check if the days aren't in our current range. */
2409 	if (g_date_compare (&mts->meeting_start_time.date, &mts->last_date_shown) > 0
2410 	    || g_date_compare (&mts->meeting_end_time.date, &mts->first_date_shown) < 0) {
2411 		mts->meeting_positions_in_scroll_area = FALSE;
2412 		return FALSE;
2413 	}
2414 
2415 	mts->meeting_positions_in_scroll_area = TRUE;
2416 	*start_x = mts->meeting_start_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_start_time);
2417 	*end_x = mts->meeting_end_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_end_time);
2418 
2419 	return TRUE;
2420 }
2421 
2422 /* This recalculates the date format to used, by computing the width of the
2423  * longest date strings in the widget's font and seeing if they fit. */
2424 static void
2425 e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts)
2426 {
2427 	/* An array of dates, one for each month in the year 2000. They must
2428 	 * all be Sundays. */
2429 	static const gint days[12] = { 23, 20, 19, 23, 21, 18,
2430 				      23, 20, 17, 22, 19, 24 };
2431 	GDate date;
2432 	gint max_date_width, longest_weekday_width, longest_month_width, width;
2433 	gint day, longest_weekday, month, longest_month;
2434 	gchar buffer[128], *str;
2435 	const gchar *name;
2436 	PangoContext *pango_context;
2437 	PangoLayout *layout;
2438 	struct tm tm_time;
2439 
2440 	/* Set up Pango prerequisites */
2441 	pango_context = gtk_widget_get_pango_context (GTK_WIDGET (mts));
2442 	layout = pango_layout_new (pango_context);
2443 
2444 	/* Calculate the maximum date width we can fit into the display. */
2445 	max_date_width = mts->day_width - 2;
2446 
2447 	/* Find the biggest full weekday name. We start on a particular
2448 	 * Monday and go through seven days. */
2449 	g_date_clear (&date, 1);
2450 	g_date_set_dmy (&date, 3, 1, 2000);	/* Monday 3rd Jan 2000. */
2451 	longest_weekday_width = 0;
2452 	longest_weekday = G_DATE_MONDAY;
2453 	for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) {
2454 		name = e_get_weekday_name (day, FALSE);
2455 		pango_layout_set_text (layout, name, -1);
2456 		pango_layout_get_pixel_size (layout, &width, NULL);
2457 		if (width > longest_weekday_width) {
2458 			longest_weekday = day;
2459 			longest_weekday_width = width;
2460 		}
2461 	}
2462 
2463 	/* Now find the biggest month name. */
2464 	longest_month_width = 0;
2465 	longest_month = G_DATE_JANUARY;
2466 	for (month = G_DATE_JANUARY; month <= G_DATE_DECEMBER; month++) {
2467 		name = e_get_month_name (month, FALSE);
2468 		pango_layout_set_text (layout, name, -1);
2469 		pango_layout_get_pixel_size (layout, &width, NULL);
2470 		if (width > longest_month_width) {
2471 			longest_month = month;
2472 			longest_month_width = width;
2473 		}
2474 	}
2475 
2476 	/* Now try it with abbreviated weekday names. */
2477 	longest_weekday_width = 0;
2478 	longest_weekday = G_DATE_MONDAY;
2479 	for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) {
2480 		name = e_get_weekday_name (day, TRUE);
2481 		pango_layout_set_text (layout, name, -1);
2482 		pango_layout_get_pixel_size (layout, &width, NULL);
2483 		if (width > longest_weekday_width) {
2484 			longest_weekday = day;
2485 			longest_weekday_width = width;
2486 		}
2487 	}
2488 
2489 	g_date_set_dmy (
2490 		&date, days[longest_month - 1] + longest_weekday,
2491 		longest_month, 2000);
2492 
2493 	g_date_to_struct_tm (&date, &tm_time);
2494 	str = e_datetime_format_format_tm ("calendar", "table",  DTFormatKindDate, &tm_time);
2495 
2496 	g_return_if_fail (str != NULL);
2497 
2498 	if (!e_datetime_format_includes_day_name ("calendar", "table",  DTFormatKindDate)) {
2499 		gchar *tmp;
2500 
2501 		g_date_strftime (buffer, sizeof (buffer), "%a", &date);
2502 
2503 		tmp = str;
2504 		str = g_strconcat (buffer, " ", str, NULL);
2505 		g_free (tmp);
2506 	}
2507 
2508 #if 0
2509 	g_print (
2510 		"longest_month: %i longest_weekday: %i date: %s\n",
2511 		longest_month, longest_weekday, str);
2512 #endif
2513 
2514 	pango_layout_set_text (layout, str, -1);
2515 	pango_layout_get_pixel_size (layout, &width, NULL);
2516 	if (width < max_date_width)
2517 		mts->date_format = E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY;
2518 	else
2519 		mts->date_format = E_MEETING_TIME_SELECTOR_DATE_SHORT;
2520 
2521 	g_object_unref (layout);
2522 	g_free (str);
2523 }
2524 
2525 /* Turn off the background of the canvas windows. This reduces flicker
2526  * considerably when scrolling. (Why isn't it in GnomeCanvas?). */
2527 static void
2528 e_meeting_time_selector_on_canvas_realized (GtkWidget *widget,
2529                                             EMeetingTimeSelector *mts)
2530 {
2531 	GdkWindow *window;
2532 
2533 	window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
2534 	gdk_window_set_background_pattern (window, NULL);
2535 }
2536 
2537 /* This is called when the meeting start time GnomeDateEdit is changed,
2538  * either via the "date_changed". "time_changed" or "activate" signals on one
2539  * of the GtkEntry widgets. So don't use the widget parameter since it may be
2540  * one of the child GtkEntry widgets. */
2541 static void
2542 e_meeting_time_selector_on_start_time_changed (GtkWidget *widget,
2543                                                EMeetingTimeSelector *mts)
2544 {
2545 	gint duration_days, duration_hours, duration_minutes;
2546 	EMeetingTime mtstime;
2547 	gint hour = 0, minute = 0;
2548 	time_t newtime;
2549 
2550 	/* Date */
2551 	newtime = e_date_edit_get_time (E_DATE_EDIT (mts->start_date_edit));
2552 	g_date_clear (&mtstime.date, 1);
2553 	g_date_set_time_t (&mtstime.date, newtime);
2554 
2555 	/* Time */
2556 	e_date_edit_get_time_of_day (E_DATE_EDIT (mts->start_date_edit), &hour, &minute);
2557 	mtstime.hour = hour;
2558 	mtstime.minute = minute;
2559 
2560 	/* If the time hasn't changed, just return. */
2561 	if (e_meeting_time_compare_times (&mtstime, &mts->meeting_start_time) == 0)
2562 		return;
2563 
2564 	/* Calculate the current meeting duration. */
2565 	e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes);
2566 
2567 	/* Set the new start time. */
2568 	mts->meeting_start_time = mtstime;
2569 
2570 	/* Update the end time so the meeting duration stays the same. */
2571 	mts->meeting_end_time = mts->meeting_start_time;
2572 	e_meeting_time_selector_adjust_time (&mts->meeting_end_time, duration_days, duration_hours, duration_minutes);
2573 	e_meeting_time_selector_update_end_date_edit (mts);
2574 
2575 	mts->meeting_positions_valid = FALSE;
2576 	e_meeting_time_selector_ensure_meeting_time_shown (mts);
2577 	gtk_widget_queue_draw (mts->display_top);
2578 	gtk_widget_queue_draw (mts->display_main);
2579 
2580 	g_signal_emit (mts, signals[CHANGED], 0);
2581 }
2582 
2583 /* This is called when the meeting end time GnomeDateEdit is changed,
2584  * either via the "date_changed", "time_changed" or "activate" signals on one
2585  * of the GtkEntry widgets. So don't use the widget parameter since it may be
2586  * one of the child GtkEntry widgets. */
2587 static void
2588 e_meeting_time_selector_on_end_time_changed (GtkWidget *widget,
2589                                              EMeetingTimeSelector *mts)
2590 {
2591 	EMeetingTime mtstime;
2592 	gint hour = 0, minute = 0;
2593 	time_t newtime;
2594 
2595 	/* Date */
2596 	newtime = e_date_edit_get_time (E_DATE_EDIT (mts->end_date_edit));
2597 	g_date_clear (&mtstime.date, 1);
2598 	g_date_set_time_t (&mtstime.date, newtime);
2599 	if (mts->all_day)
2600 		g_date_add_days (&mtstime.date, 1);
2601 
2602 	/* Time */
2603 	e_date_edit_get_time_of_day (E_DATE_EDIT (mts->end_date_edit), &hour, &minute);
2604 	mtstime.hour = hour;
2605 	mtstime.minute = minute;
2606 
2607 	/* If the time hasn't changed, just return. */
2608 	if (e_meeting_time_compare_times (&mtstime, &mts->meeting_end_time) == 0)
2609 		return;
2610 
2611 	/* Set the new end time. */
2612 	mts->meeting_end_time = mtstime;
2613 
2614 	/* If the start time is after the end time, set it to the same time. */
2615 	if (e_meeting_time_compare_times (&mtstime, &mts->meeting_start_time) <= 0) {
2616 		/* We set it first, before updating the widget, so the signal
2617 		 * handler will just return. */
2618 		mts->meeting_start_time = mtstime;
2619 		if (mts->all_day)
2620 			g_date_subtract_days (&mts->meeting_start_time.date, 1);
2621 		e_meeting_time_selector_update_start_date_edit (mts);
2622 	}
2623 
2624 	mts->meeting_positions_valid = FALSE;
2625 	e_meeting_time_selector_ensure_meeting_time_shown (mts);
2626 	gtk_widget_queue_draw (mts->display_top);
2627 	gtk_widget_queue_draw (mts->display_main);
2628 
2629 	g_signal_emit (mts, signals[CHANGED], 0);
2630 }
2631 
2632 /* This updates the ranges shown in the GnomeDateEdit popup menus, according
2633  * to working_hours_only etc. */
2634 static void
2635 e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts)
2636 {
2637 	EDateEdit *start_edit, *end_edit;
2638 	gint low_hour, high_hour;
2639 
2640 	start_edit = E_DATE_EDIT (mts->start_date_edit);
2641 	end_edit = E_DATE_EDIT (mts->end_date_edit);
2642 
2643 	if (mts->working_hours_only) {
2644 		low_hour = mts->day_start_hour;
2645 		high_hour = mts->day_end_hour;
2646 	} else {
2647 		low_hour = 0;
2648 		high_hour = 23;
2649 	}
2650 
2651 	e_date_edit_set_time_popup_range (start_edit, low_hour, high_hour);
2652 	e_date_edit_set_time_popup_range (end_edit, low_hour, high_hour);
2653 }
2654 
2655 static void
2656 e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget,
2657                                                  GtkAllocation *allocation,
2658                                                  EMeetingTimeSelector *mts)
2659 {
2660 	e_meeting_time_selector_update_main_canvas_scroll_region (mts);
2661 
2662 	e_meeting_time_selector_ensure_meeting_time_shown (mts);
2663 }
2664 
2665 static gboolean
2666 e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget,
2667                                                 GdkEventScroll *event,
2668                                                 EMeetingTimeSelector *mts)
2669 {
2670 	gboolean return_val = FALSE;
2671 
2672 	/* escalate to the list view's parent, which is a scrolled window */
2673 	g_signal_emit_by_name (gtk_widget_get_parent (GTK_WIDGET (mts->list_view)), "scroll-event", event, &return_val);
2674 
2675 	return return_val;
2676 }
2677 
2678 /* Sets a tooltip for the busy periods canvas. If the mouse pointer
2679  * hovers over a busy period for which extended free/busy (XFB) data
2680  * could be extracted from the vfreebusy calendar object, the tooltip
2681  * will be shown (currently displays the summary and the location of
2682  * for the busy period, if available). See EMeetingXfbData for a reference.
2683  *
2684  */
2685 static gboolean
2686 e_meeting_time_selector_on_canvas_query_tooltip (GtkWidget *widget,
2687                                                  gint x,
2688                                                  gint y,
2689                                                  gboolean keyboard_mode,
2690                                                  GtkTooltip *tooltip,
2691                                                  gpointer user_data)
2692 {
2693 	EMeetingTimeSelector *mts = NULL;
2694 	EMeetingAttendee *attendee = NULL;
2695 	EMeetingFreeBusyPeriod *period = NULL;
2696 	EMeetingXfbData *xfb = NULL;
2697 	GtkScrollable *scrollable = NULL;
2698 	GtkAdjustment *adjustment = NULL;
2699 	const GArray *periods = NULL;
2700 	gint scroll_x = 0;
2701 	gint scroll_y = 0;
2702 	gint mouse_x = 0;
2703 	gint row = 0;
2704 	gint first_idx = 0;
2705 	gint ii = 0;
2706 	gchar *tt_text = NULL;
2707 	
2708 	g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE);
2709 	g_return_val_if_fail (GTK_IS_TOOLTIP (tooltip), FALSE);
2710 	g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (user_data), FALSE);
2711 
2712 	mts = E_MEETING_TIME_SELECTOR (user_data);
2713 	
2714 	scrollable = GTK_SCROLLABLE (widget);
2715 	adjustment = gtk_scrollable_get_hadjustment (scrollable);
2716 	scroll_x = (gint) gtk_adjustment_get_value (adjustment);
2717 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
2718 	scroll_y = (gint) gtk_adjustment_get_value (adjustment);
2719 	
2720 	/* calculate the attendee index (row) we're at */
2721 	row = (scroll_y + y) / mts->row_height;
2722 
2723 	/* no tooltip if we have no attendee in the row */
2724 	if (row > e_meeting_store_count_actual_attendees (mts->model) - 1)
2725 		return FALSE;
2726 
2727 	/* no tooltip if attendee has no calendar info */
2728 	attendee = e_meeting_store_find_attendee_at_row (mts->model, row);
2729 	g_return_val_if_fail (E_IS_MEETING_ATTENDEE (attendee), FALSE);
2730 	if (!e_meeting_attendee_get_has_calendar_info (attendee))
2731 		return FALSE;
2732 
2733 	/* get the attendee's busy times array */
2734 	periods = e_meeting_attendee_get_busy_periods (attendee);
2735 	g_return_val_if_fail (periods != NULL, FALSE);
2736 	g_return_val_if_fail (periods->len > 0, FALSE);
2737 
2738 	/* no tooltip if no busy period reaches into the current canvas area */
2739 	first_idx = e_meeting_attendee_find_first_busy_period (attendee,
2740 	                                                       &(mts->first_date_shown));
2741 	if (first_idx < 0)
2742 		return FALSE;
2743 
2744 	/* calculate the mouse tip x position in the canvas area */
2745 	mouse_x = x + scroll_x;
2746 
2747 	/* find out whether mouse_x lies inside a busy
2748 	 * period (we start with the index of the first
2749 	 * one reaching into the current canvas area)
2750 	 */
2751 	for (ii = first_idx; ii < periods->len; ii++) {
2752 		EMeetingFreeBusyPeriod *p = NULL;
2753 		gint sx = 0;
2754 		gint ex = 0;
2755 		
2756 		p = &(g_array_index (periods,
2757 		                     EMeetingFreeBusyPeriod,
2758 		                     ii));
2759 		/* meeting start time x position */
2760 		sx = e_meeting_time_selector_calculate_time_position (mts,
2761 		                                                      &(p->start));
2762 		/* meeting end time x position */
2763 		ex = e_meeting_time_selector_calculate_time_position (mts,
2764 		                                                      &(p->end));
2765 		if ((mouse_x >= sx) && (mouse_x <= ex)) {
2766 			/* found busy period the mouse tip is over */
2767 			period = p;
2768 			break;
2769 		}
2770 	}
2771 
2772 	/* no tooltip if we did not find a busy period under
2773 	 * the mouse pointer
2774 	 */
2775 	if (period == NULL)
2776 		return FALSE;
2777 
2778 	/* get the extended free/busy data
2779 	 * (no tooltip if none available)
2780 	 */
2781 	xfb = &(period->xfb);
2782 	if ((xfb->summary == NULL) && (xfb->location == NULL))
2783 		return FALSE;
2784 
2785 	/* Create the tooltip text. The data sent by the server will
2786 	 * have been validated for UTF-8 conformance (and possibly
2787 	 * forced into) as well as length-limited by a call to the
2788 	 * e_meeting_xfb_utf8_string_new_from_ical() function in
2789 	 * process_free_busy_comp_get_xfb() (e-meeting-store.c)
2790 	 */
2791 	tt_text = g_strdup_printf ("%s\n\n%s",
2792 	                           (xfb->summary == NULL) ? "" : xfb->summary,
2793 	                           (xfb->location == NULL) ? "" : xfb->location);
2794 
2795 	/* set XFB information as tooltip text */
2796 	gtk_tooltip_set_text (tooltip, tt_text);
2797 	g_free (tt_text);
2798 	
2799 	return TRUE;
2800 }
2801 
2802 /* This updates the canvas scroll regions according to the number of attendees.
2803  * If the total height needed is less than the height of the canvas, we must
2804  * use the height of the canvas, or it causes problems. */
2805 static void
2806 e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts)
2807 {
2808 	GtkAllocation allocation;
2809 	gint height;
2810 
2811 	gtk_widget_get_allocation (mts->display_main, &allocation);
2812 	height = mts->row_height * (e_meeting_store_count_actual_attendees (mts->model) + 2);
2813 	height = MAX (height, allocation.height);
2814 
2815 	gnome_canvas_set_scroll_region (
2816 		GNOME_CANVAS (mts->display_main),
2817 		0, 0,
2818 		mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
2819 		height);
2820 }
2821 
2822 /* This changes the meeting time based on the given x coordinate and whether
2823  * we are dragging the start or end bar. It returns the new position, which
2824  * will be swapped if the start bar is dragged past the end bar or vice versa.
2825  * It make sure the meeting time is never dragged outside the visible canvas
2826  * area. */
2827 void
2828 e_meeting_time_selector_drag_meeting_time (EMeetingTimeSelector *mts,
2829                                            gint x)
2830 {
2831 	EMeetingTime first_time, last_time, drag_time, *time_to_set;
2832 	gint scroll_x, scroll_y, canvas_width;
2833 	gboolean set_both_times = FALSE;
2834 	GtkAllocation allocation;
2835 
2836 	/* Get the x coords of visible part of the canvas. */
2837 	gnome_canvas_get_scroll_offsets (
2838 		GNOME_CANVAS (mts->display_main),
2839 		&scroll_x, &scroll_y);
2840 	gtk_widget_get_allocation (mts->display_main, &allocation);
2841 	canvas_width = allocation.width;
2842 
2843 	/* Save the x coordinate for the timeout handler. */
2844 	mts->last_drag_x = (x < scroll_x) ? x - scroll_x
2845 		: x - scroll_x - canvas_width + 1;
2846 
2847 	/* Check if the mouse is off the edge of the canvas. */
2848 	if (x < scroll_x || x > scroll_x + canvas_width) {
2849 		/* If we haven't added a timeout function, add one. */
2850 		if (mts->auto_scroll_timeout_id == 0) {
2851 			mts->auto_scroll_timeout_id = g_timeout_add (60, e_meeting_time_selector_timeout_handler, mts);
2852 			mts->scroll_count = 0;
2853 
2854 			/* Call the handler to start scrolling now. */
2855 			e_meeting_time_selector_timeout_handler (mts);
2856 			return;
2857 		}
2858 	} else {
2859 		e_meeting_time_selector_remove_timeout (mts);
2860 	}
2861 
2862 	/* Calculate the minimum & maximum times we can use, based on the
2863 	 * scroll offsets and whether zoomed_out is set. */
2864 	e_meeting_time_selector_calculate_time (mts, scroll_x, &first_time);
2865 	e_meeting_time_selector_calculate_time (
2866 		mts, scroll_x + canvas_width - 1, &last_time);
2867 	if (!mts->all_day) {
2868 		if (mts->zoomed_out) {
2869 			if (first_time.minute > 30)
2870 				first_time.hour++;
2871 			first_time.minute = 0;
2872 			last_time.minute = 0;
2873 		} else {
2874 			first_time.minute += 15;
2875 			first_time.minute -= first_time.minute % 30;
2876 			last_time.minute -= last_time.minute % 30;
2877 		}
2878 	} else {
2879 		if (first_time.hour > 0 || first_time.minute > 0)
2880 			g_date_add_days (&first_time.date, 1);
2881 		first_time.hour = 0;
2882 		first_time.minute = 0;
2883 		last_time.hour = 0;
2884 		last_time.minute = 0;
2885 	}
2886 	e_meeting_time_selector_fix_time_overflows (&first_time);
2887 	e_meeting_time_selector_fix_time_overflows (&last_time);
2888 
2889 	/* Calculate the time from x coordinate. */
2890 	e_meeting_time_selector_calculate_time (mts, x, &drag_time);
2891 
2892 	/* Calculate the nearest half-hour or hour, depending on whether
2893 	 * zoomed_out is set. */
2894 	if (!mts->all_day) {
2895 		if (mts->zoomed_out) {
2896 			if (drag_time.minute > 30)
2897 				drag_time.hour++;
2898 			drag_time.minute = 0;
2899 		} else {
2900 			drag_time.minute += 15;
2901 			drag_time.minute -= drag_time.minute % 30;
2902 		}
2903 	} else {
2904 		if (drag_time.hour > 12)
2905 			g_date_add_days (&drag_time.date, 1);
2906 		drag_time.hour = 0;
2907 		drag_time.minute = 0;
2908 	}
2909 	e_meeting_time_selector_fix_time_overflows (&drag_time);
2910 
2911 	/* Now make sure we are between first_time & last_time. */
2912 	if (e_meeting_time_compare_times (&drag_time, &first_time) < 0)
2913 		drag_time = first_time;
2914 	if (e_meeting_time_compare_times (&drag_time, &last_time) > 0)
2915 		drag_time = last_time;
2916 
2917 	/* Set the meeting start or end time to drag_time. */
2918 	if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2919 		time_to_set = &mts->meeting_start_time;
2920 	else
2921 		time_to_set = &mts->meeting_end_time;
2922 
2923 	/* If the time is unchanged, just return. */
2924 	if (e_meeting_time_compare_times (time_to_set, &drag_time) == 0)
2925 		return;
2926 
2927 	/* Don't let an empty occur for all day events */
2928 	if (mts->all_day
2929 	    && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START
2930 	    && e_meeting_time_compare_times (&mts->meeting_end_time, &drag_time) == 0)
2931 		return;
2932 	else if (mts->all_day
2933 		 && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
2934 		 && e_meeting_time_compare_times (&mts->meeting_start_time, &drag_time) == 0)
2935 		return;
2936 
2937 	*time_to_set = drag_time;
2938 
2939 	/* Check if the start time and end time need to be switched. */
2940 	if (e_meeting_time_compare_times (&mts->meeting_start_time,
2941 					  &mts->meeting_end_time) > 0) {
2942 		drag_time = mts->meeting_start_time;
2943 		mts->meeting_start_time = mts->meeting_end_time;
2944 		mts->meeting_end_time = drag_time;
2945 
2946 		if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2947 			mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END;
2948 		else
2949 			mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START;
2950 
2951 		set_both_times = TRUE;
2952 	}
2953 
2954 	/* Mark the calculated positions as invalid. */
2955 	mts->meeting_positions_valid = FALSE;
2956 
2957 	/* Redraw the canvases. */
2958 	gtk_widget_queue_draw (mts->display_top);
2959 	gtk_widget_queue_draw (mts->display_main);
2960 
2961 	/* Set the times in the GnomeDateEdit widgets. */
2962 	if (set_both_times
2963 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2964 		e_meeting_time_selector_update_start_date_edit (mts);
2965 
2966 	if (set_both_times
2967 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END)
2968 		e_meeting_time_selector_update_end_date_edit (mts);
2969 
2970 	if (set_both_times
2971 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
2972 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
2973 		g_signal_emit (mts, signals[CHANGED], 0);
2974 }
2975 
2976 /* This is the timeout function which handles auto-scrolling when the user is
2977  * dragging one of the meeting time vertical bars outside the left or right
2978  * edge of the canvas. */
2979 static gboolean
2980 e_meeting_time_selector_timeout_handler (gpointer data)
2981 {
2982 	EMeetingTimeSelector *mts;
2983 	EMeetingTime drag_time, *time_to_set;
2984 	gint scroll_x, max_scroll_x, scroll_y, canvas_width;
2985 	gint scroll_speed, scroll_offset;
2986 	gboolean set_both_times = FALSE;
2987 	GtkAllocation allocation;
2988 
2989 	mts = E_MEETING_TIME_SELECTOR (data);
2990 
2991 	/* Return if we don't need to scroll yet. */
2992 	if (mts->scroll_count-- > 0)
2993 		return TRUE;
2994 
2995 	/* Get the x coords of visible part of the canvas. */
2996 	gnome_canvas_get_scroll_offsets (
2997 		GNOME_CANVAS (mts->display_main),
2998 		&scroll_x, &scroll_y);
2999 	gtk_widget_get_allocation (mts->display_main, &allocation);
3000 	canvas_width = allocation.width;
3001 
3002 	/* Calculate the scroll delay, between 0 and MAX_SCROLL_SPEED. */
3003 	scroll_speed = abs (mts->last_drag_x / E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH);
3004 	scroll_speed = MIN (
3005 		scroll_speed,
3006 		E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED);
3007 
3008 	/* Reset the scroll count. */
3009 	mts->scroll_count = E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED - scroll_speed;
3010 
3011 	/* Calculate how much we need to scroll. */
3012 	if (mts->last_drag_x >= 0)
3013 		scroll_offset = mts->col_width;
3014 	else
3015 		scroll_offset = -mts->col_width;
3016 
3017 	scroll_x += scroll_offset;
3018 	max_scroll_x = (mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN)
3019 		- canvas_width;
3020 	scroll_x = CLAMP (scroll_x, 0, max_scroll_x);
3021 
3022 	/* Calculate the minimum or maximum visible time in the canvas, which
3023 	 * we will now set the dragged time to. */
3024 	if (scroll_offset > 0) {
3025 		e_meeting_time_selector_calculate_time (
3026 			mts, scroll_x + canvas_width - 1, &drag_time);
3027 		if (!mts->all_day) {
3028 			if (mts->zoomed_out) {
3029 				drag_time.minute = 0;
3030 			} else {
3031 				drag_time.minute -= drag_time.minute % 30;
3032 			}
3033 		} else {
3034 			drag_time.hour = 0;
3035 			drag_time.minute = 0;
3036 		}
3037 	} else {
3038 		e_meeting_time_selector_calculate_time (
3039 			mts, scroll_x, &drag_time);
3040 		if (!mts->all_day) {
3041 			if (mts->zoomed_out) {
3042 				if (drag_time.minute > 30)
3043 					drag_time.hour++;
3044 				drag_time.minute = 0;
3045 			} else {
3046 				drag_time.minute += 15;
3047 				drag_time.minute -= drag_time.minute % 30;
3048 			}
3049 		} else {
3050 			if (drag_time.hour > 0 || drag_time.minute > 0)
3051 				g_date_add_days (&drag_time.date, 1);
3052 			drag_time.hour = 0;
3053 			drag_time.minute = 0;
3054 		}
3055 	}
3056 	e_meeting_time_selector_fix_time_overflows (&drag_time);
3057 
3058 	/* Set the meeting start or end time to drag_time. */
3059 	if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3060 		time_to_set = &mts->meeting_start_time;
3061 	else
3062 		time_to_set = &mts->meeting_end_time;
3063 
3064 	/* If the time is unchanged, just return. */
3065 	if (e_meeting_time_compare_times (time_to_set, &drag_time) == 0)
3066 		goto scroll;
3067 
3068 	/* Don't let an empty occur for all day events */
3069 	if (mts->all_day
3070 	    && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START
3071 	    && e_meeting_time_compare_times (&mts->meeting_end_time, &drag_time) == 0)
3072 		goto scroll;
3073 	else if (mts->all_day
3074 		 && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
3075 		 && e_meeting_time_compare_times (&mts->meeting_start_time, &drag_time) == 0)
3076 		goto scroll;
3077 
3078 	*time_to_set = drag_time;
3079 
3080 	/* Check if the start time and end time need to be switched. */
3081 	if (e_meeting_time_compare_times (&mts->meeting_start_time, &mts->meeting_end_time) > 0) {
3082 		drag_time = mts->meeting_start_time;
3083 		mts->meeting_start_time = mts->meeting_end_time;
3084 		mts->meeting_end_time = drag_time;
3085 
3086 		if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3087 			mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END;
3088 		else
3089 			mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START;
3090 
3091 		set_both_times = TRUE;
3092 	}
3093 
3094 	/* Mark the calculated positions as invalid. */
3095 	mts->meeting_positions_valid = FALSE;
3096 
3097 	/* Set the times in the GnomeDateEdit widgets. */
3098 	if (set_both_times
3099 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3100 		e_meeting_time_selector_update_start_date_edit (mts);
3101 
3102 	if (set_both_times
3103 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END)
3104 		e_meeting_time_selector_update_end_date_edit (mts);
3105 
3106 	if (set_both_times
3107 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
3108 	    || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
3109 		g_signal_emit (mts, signals[CHANGED], 0);
3110 
3111  scroll:
3112 	/* Redraw the canvases. We freeze and thaw the layouts so that they
3113 	 * get redrawn completely. Otherwise the pixels get scrolled left or
3114 	 * right which is not good for us (since our vertical bars have been
3115 	 * moved) and causes flicker. */
3116 	gnome_canvas_scroll_to (
3117 		GNOME_CANVAS (mts->display_main),
3118 		scroll_x, scroll_y);
3119 	gnome_canvas_scroll_to (
3120 		GNOME_CANVAS (mts->display_top),
3121 		scroll_x, scroll_y);
3122 
3123 	return TRUE;
3124 }
3125 
3126 /* This removes our auto-scroll timeout function, if we have one installed. */
3127 void
3128 e_meeting_time_selector_remove_timeout (EMeetingTimeSelector *mts)
3129 {
3130 	if (mts->auto_scroll_timeout_id) {
3131 		g_source_remove (mts->auto_scroll_timeout_id);
3132 		mts->auto_scroll_timeout_id = 0;
3133 	}
3134 }
3135 
3136 /* This updates the GnomeDateEdit widget displaying the meeting start time. */
3137 static void
3138 e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts)
3139 {
3140 	e_date_edit_set_date_and_time_of_day (
3141 		E_DATE_EDIT (mts->start_date_edit),
3142 		g_date_get_year (&mts->meeting_start_time.date),
3143 		g_date_get_month (&mts->meeting_start_time.date),
3144 		g_date_get_day (&mts->meeting_start_time.date),
3145 		mts->meeting_start_time.hour,
3146 		mts->meeting_start_time.minute);
3147 }
3148 
3149 /* This updates the GnomeDateEdit widget displaying the meeting end time. */
3150 static void
3151 e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts)
3152 {
3153 	GDate date;
3154 
3155 	date = mts->meeting_end_time.date;
3156 	if (mts->all_day)
3157 		g_date_subtract_days (&date, 1);
3158 
3159 	e_date_edit_set_date_and_time_of_day (
3160 		E_DATE_EDIT (mts->end_date_edit),
3161 		g_date_get_year (&date),
3162 		g_date_get_month (&date),
3163 		g_date_get_day (&date),
3164 		mts->meeting_end_time.hour,
3165 		mts->meeting_end_time.minute);
3166 }
3167 
3168 /* This ensures that the meeting time is shown on screen, by scrolling the
3169  * canvas and possibly by changing the range of dates shown in the canvas. */
3170 static void
3171 e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts)
3172 {
3173 	gint start_x, end_x, scroll_x, scroll_y;
3174 	gint new_scroll_x;
3175 	GtkAllocation allocation;
3176 	EMeetingTime time;
3177 
3178 	/* Check if we need to change the range of dates shown. */
3179 	if (g_date_compare (&mts->meeting_start_time.date,
3180 			    &mts->first_date_shown) < 0
3181 	    || g_date_compare (&mts->meeting_end_time.date,
3182 			       &mts->last_date_shown) > 0) {
3183 		e_meeting_time_selector_update_dates_shown (mts);
3184 		gtk_widget_queue_draw (mts->display_top);
3185 		gtk_widget_queue_draw (mts->display_main);
3186 	}
3187 
3188 	/* If all of the meeting time is visible, just return. */
3189 	if (e_meeting_time_selector_get_meeting_time_positions (mts, &start_x,
3190 							    &end_x)) {
3191 		time.date = mts->meeting_start_time.date;
3192 		time.hour = 0;
3193 		time.minute = 0;
3194 		start_x = e_meeting_time_selector_calculate_time_position (mts, &time);
3195 	}
3196 
3197 	gnome_canvas_get_scroll_offsets (
3198 		GNOME_CANVAS (mts->display_main),
3199 		&scroll_x, &scroll_y);
3200 	gtk_widget_get_allocation (mts->display_main, &allocation);
3201 	if (start_x > scroll_x && end_x <= scroll_x + allocation.width)
3202 		return;
3203 
3204 	new_scroll_x = start_x;
3205 	gnome_canvas_scroll_to (
3206 		GNOME_CANVAS (mts->display_main),
3207 		new_scroll_x, scroll_y);
3208 }
3209 
3210 /* This updates the range of dates shown in the canvas, to make sure that the
3211  * currently selected meeting time is in the range. */
3212 static void
3213 e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts)
3214 {
3215 	mts->first_date_shown = mts->meeting_start_time.date;
3216 	g_date_subtract_days (
3217 		&mts->first_date_shown,
3218 		E_MEETING_TIME_SELECTOR_DAYS_START_BEFORE);
3219 
3220 	mts->last_date_shown = mts->first_date_shown;
3221 	g_date_add_days (
3222 		&mts->last_date_shown,
3223 		E_MEETING_TIME_SELECTOR_DAYS_SHOWN - 1);
3224 }
3225 
3226 /* This checks if the time's hour is over 24 or its minute is over 60 and if
3227  * so it updates the day/hour appropriately. Note that hours and minutes are
3228  * stored in guint8's so they can't overflow by much. */
3229 void
3230 e_meeting_time_selector_fix_time_overflows (EMeetingTime *mtstime)
3231 {
3232 	gint hours_to_add, days_to_add;
3233 
3234 	hours_to_add = mtstime->minute / 60;
3235 	if (hours_to_add > 0) {
3236 		mtstime->minute -= hours_to_add * 60;
3237 		mtstime->hour += hours_to_add;
3238 	}
3239 
3240 	days_to_add = mtstime->hour / 24;
3241 	if (days_to_add > 0) {
3242 		mtstime->hour -= days_to_add * 24;
3243 		g_date_add_days (&mtstime->date, days_to_add);
3244 	}
3245 }
3246 
3247 /*
3248  * CONVERSION ROUTINES - functions to convert between different coordinate
3249  *			 spaces and dates.
3250  */
3251 
3252 /* This takes an x pixel coordinate within the entire canvas scroll region and
3253  * returns the date in which it falls. If day_position is not NULL it also
3254  * returns the x coordinate within the date, relative to the visible part of
3255  * the canvas. It is used when painting the days in the item_draw function.
3256  * Note that it must handle negative x coordinates in case we are dragging off
3257  * the edge of the canvas. */
3258 void
3259 e_meeting_time_selector_calculate_day_and_position (EMeetingTimeSelector *mts,
3260                                                     gint x,
3261                                                     GDate *date,
3262                                                     gint *day_position)
3263 {
3264 	gint days_from_first_shown;
3265 
3266 	*date = mts->first_date_shown;
3267 
3268 	if (x >= 0) {
3269 		days_from_first_shown = x / mts->day_width;
3270 		g_date_add_days (date, days_from_first_shown);
3271 		if (day_position)
3272 			*day_position = - x % mts->day_width;
3273 	} else {
3274 		days_from_first_shown = -x / mts->day_width + 1;
3275 		g_date_subtract_days (date, days_from_first_shown);
3276 		if (day_position)
3277 			*day_position = -mts->day_width - x % mts->day_width;
3278 	}
3279 }
3280 
3281 /* This takes an x pixel coordinate within a day, and converts it to hours
3282  * and minutes, depending on working_hours_only and zoomed_out. */
3283 void
3284 e_meeting_time_selector_convert_day_position_to_hours_and_mins (EMeetingTimeSelector *mts,
3285                                                                 gint day_position,
3286                                                                 guint8 *hours,
3287                                                                 guint8 *minutes)
3288 {
3289 	if (mts->zoomed_out)
3290 		day_position *= 3;
3291 
3292 	/* Calculate the hours & minutes from the first displayed. */
3293 	*hours = day_position / mts->col_width;
3294 	*minutes = (day_position % mts->col_width) * 60 / mts->col_width;
3295 
3296 	/* Now add on the first hour shown. */
3297 	*hours += mts->first_hour_shown;
3298 }
3299 
3300 /* This takes an x pixel coordinate within the entire canvas scroll region and
3301  * returns the time in which it falls. Note that it won't be extremely
3302  * accurate since hours may only be a few pixels wide in the display.
3303  * With zoomed_out set each pixel may represent 5 minutes or more, depending
3304  * on how small the font is. */
3305 void
3306 e_meeting_time_selector_calculate_time (EMeetingTimeSelector *mts,
3307                                         gint x,
3308                                         EMeetingTime *time)
3309 {
3310 	gint day_position;
3311 
3312 	/* First get the day and the x position within the day. */
3313 	e_meeting_time_selector_calculate_day_and_position (
3314 		mts, x, &time->date, NULL);
3315 
3316 	/* Now convert the day_position into an hour and minute. */
3317 	if (x >= 0)
3318 		day_position = x % mts->day_width;
3319 	else
3320 		day_position = mts->day_width + x % mts->day_width;
3321 
3322 	e_meeting_time_selector_convert_day_position_to_hours_and_mins (
3323 		mts, day_position, &time->hour, &time->minute);
3324 }
3325 
3326 /* This takes a EMeetingTime and calculates the x pixel coordinate
3327  * within the entire canvas scroll region. It is used to draw the selected
3328  * meeting time and all the busy periods. */
3329 gint
3330 e_meeting_time_selector_calculate_time_position (EMeetingTimeSelector *mts,
3331                                                  EMeetingTime *mtstime)
3332 {
3333 	gint x, date_offset, day_offset;
3334 
3335 	/* Calculate the number of days since the first date shown in the
3336 	 * entire canvas scroll region. */
3337 	date_offset =
3338 		g_date_get_julian (&mtstime->date) -
3339 		g_date_get_julian (&mts->first_date_shown);
3340 
3341 	/* Calculate the x pixel coordinate of the start of the day. */
3342 	x = date_offset * mts->day_width;
3343 
3344 	/* Add on the hours and minutes, depending on whether zoomed_out and
3345 	 * working_hours_only are set. */
3346 	day_offset = (mtstime->hour - mts->first_hour_shown) * 60
3347 		+ mtstime->minute;
3348 	/* The day width includes an extra vertical grid line so subtract 1. */
3349 	day_offset *= (mts->day_width - 1);
3350 	day_offset /= (mts->last_hour_shown - mts->first_hour_shown) * 60;
3351 
3352 	/* Clamp the day_offset in case the time isn't actually visible. */
3353 	x += CLAMP (day_offset, 0, mts->day_width);
3354 
3355 	return x;
3356 }
3357 
3358 static void
3359 row_inserted_cb (GtkTreeModel *model,
3360                  GtkTreePath *path,
3361                  GtkTreeIter *iter,
3362                  gpointer data)
3363 {
3364 	EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3365 	gint row = gtk_tree_path_get_indices (path)[0];
3366 	/* Update the scroll region. */
3367 	e_meeting_time_selector_update_main_canvas_scroll_region (mts);
3368 
3369 	/* Redraw */
3370 	gtk_widget_queue_draw (mts->display_top);
3371 	gtk_widget_queue_draw (mts->display_main);
3372 
3373 	/* Get the latest free/busy info */
3374 	e_meeting_time_selector_refresh_free_busy (mts, row, FALSE);
3375 }
3376 
3377 static void
3378 row_changed_cb (GtkTreeModel *model,
3379                 GtkTreePath *path,
3380                 GtkTreeIter *iter,
3381                 gpointer data)
3382 {
3383 	EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3384 	gint row = gtk_tree_path_get_indices (path)[0];
3385 
3386 	/* Get the latest free/busy info */
3387 	e_meeting_time_selector_refresh_free_busy (mts, row, FALSE);
3388 }
3389 
3390 static void
3391 row_deleted_cb (GtkTreeModel *model,
3392                 GtkTreePath *path,
3393                 gpointer data)
3394 {
3395 	EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3396 
3397 	/* Update the scroll region. */
3398 	e_meeting_time_selector_update_main_canvas_scroll_region (mts);
3399 
3400 	/* Redraw */
3401 	gtk_widget_queue_draw (mts->display_top);
3402 	gtk_widget_queue_draw (mts->display_main);
3403 }
3404 
3405 #define REFRESH_PAUSE 5
3406 
3407 static gboolean
3408 free_busy_timeout_refresh (gpointer data)
3409 {
3410 	EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
3411 
3412 	/* Update all free/busy info, so we use the new template uri */
3413 	e_meeting_time_selector_refresh_free_busy (mts, 0, TRUE);
3414 
3415 	mts->fb_refresh_not = 0;
3416 
3417 	return FALSE;
3418 }
3419 
3420 static void
3421 free_busy_template_changed_cb (EMeetingTimeSelector *mts)
3422 {
3423 	/* Wait REFRESH_PAUSE before refreshing, using the latest uri value */
3424 	if (mts->fb_refresh_not != 0)
3425 		g_source_remove (mts->fb_refresh_not);
3426 
3427 	mts->fb_refresh_not = g_timeout_add_seconds (
3428 		REFRESH_PAUSE, free_busy_timeout_refresh, mts);
3429 }