evolution-3.6.4/calendar/gui/e-week-view.c

Location Tool Test ID Function Issue
e-week-view.c:3467:45 clang-analyzer Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
e-week-view.c:3467:45 clang-analyzer Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
e-week-view.c:3476:8 clang-analyzer Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
e-week-view.c:3476:8 clang-analyzer Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
   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@ximian.com>
  18  *		Rodrigo Moya <rodrigo@ximian.com>
  19  *
  20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  21  *
  22  */
  23 
  24 /*
  25  * EWeekView - displays the Week & Month views of the calendar.
  26  */
  27 
  28 #ifdef HAVE_CONFIG_H
  29 #include <config.h>
  30 #endif
  31 
  32 #include "e-week-view.h"
  33 #include "ea-calendar.h"
  34 
  35 #include <math.h>
  36 #include <gdk/gdkkeysyms.h>
  37 #include <glib/gi18n.h>
  38 #include <libgnomecanvas/libgnomecanvas.h>
  39 #include <text/e-text.h>
  40 #include <misc/e-canvas-utils.h>
  41 #include <e-util/e-unicode.h>
  42 #include <e-util/e-categories-config.h>
  43 #include <e-util/e-dialog-utils.h>
  44 #include <e-util/e-util.h>
  45 #include "dialogs/delete-comp.h"
  46 #include "dialogs/delete-error.h"
  47 #include "dialogs/send-comp.h"
  48 #include "dialogs/cancel-comp.h"
  49 #include "dialogs/recur-comp.h"
  50 #include "dialogs/goto-dialog.h"
  51 #include "calendar-config.h"
  52 #include "comp-util.h"
  53 #include "itip-utils.h"
  54 #include "calendar-config.h"
  55 #include "print.h"
  56 #include "e-cal-model-calendar.h"
  57 #include "e-week-view-event-item.h"
  58 #include "e-week-view-layout.h"
  59 #include "e-week-view-main-item.h"
  60 #include "e-week-view-titles-item.h"
  61 #include "misc.h"
  62 #include <e-util/e-icon-factory.h>
  63 
  64 /* Images */
  65 #include "art/jump.xpm"
  66 
  67 #define E_WEEK_VIEW_SMALL_FONT_PTSIZE 7
  68 
  69 #define E_WEEK_VIEW_JUMP_BUTTON_WIDTH	16
  70 #define E_WEEK_VIEW_JUMP_BUTTON_HEIGHT	8
  71 
  72 #define E_WEEK_VIEW_JUMP_BUTTON_X_PAD	3
  73 #define E_WEEK_VIEW_JUMP_BUTTON_Y_PAD	3
  74 
  75 #define E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS -1
  76 
  77 /* The timeout before we do a layout, so we don't do a layout for each event
  78  * we get from the server. */
  79 #define E_WEEK_VIEW_LAYOUT_TIMEOUT	100
  80 
  81 typedef struct {
  82 	EWeekView *week_view;
  83 	ECalModelComponent *comp_data;
  84 } AddEventData;
  85 
  86 static void e_week_view_dispose (GObject *object);
  87 static void e_week_view_realize (GtkWidget *widget);
  88 static void e_week_view_set_colors (EWeekView *week_view, GtkWidget *widget);
  89 static void e_week_view_unrealize (GtkWidget *widget);
  90 static void e_week_view_style_set (GtkWidget *widget,
  91 				   GtkStyle  *previous_style);
  92 static void e_week_view_size_allocate (GtkWidget *widget,
  93 				       GtkAllocation *allocation);
  94 static void e_week_view_recalc_cell_sizes (EWeekView *week_view);
  95 static gint e_week_view_focus_in (GtkWidget *widget,
  96 				  GdkEventFocus *event);
  97 static gint e_week_view_focus_out (GtkWidget *widget,
  98 				   GdkEventFocus *event);
  99 static gboolean e_week_view_get_next_tab_event (EWeekView *week_view,
 100 						GtkDirectionType direction,
 101 						gint current_event_num,
 102 						gint current_span_num,
 103 						gint *next_event_num,
 104 						gint *next_span_num);
 105 static gboolean e_week_view_focus (GtkWidget *widget,
 106 				   GtkDirectionType direction);
 107 static GList *e_week_view_get_selected_events (ECalendarView *cal_view);
 108 static gboolean e_week_view_get_selected_time_range (ECalendarView *cal_view, time_t *start_time, time_t *end_time);
 109 static void e_week_view_set_selected_time_range (ECalendarView *cal_view, time_t start_time, time_t end_time);
 110 static gboolean e_week_view_get_visible_time_range (ECalendarView *cal_view, time_t *start_time, time_t *end_time);
 111 static void e_week_view_paste_text (ECalendarView *week_view);
 112 static void e_week_view_update_query (EWeekView *week_view);
 113 
 114 static gboolean e_week_view_on_button_press (GtkWidget *widget,
 115 					     GdkEventButton *event,
 116 					     EWeekView *week_view);
 117 static gboolean e_week_view_on_button_release (GtkWidget *widget,
 118 					       GdkEventButton *event,
 119 					       EWeekView *week_view);
 120 static gboolean e_week_view_on_scroll (GtkWidget *widget,
 121 				       GdkEventScroll *scroll,
 122 				       EWeekView *week_view);
 123 static gboolean e_week_view_on_motion (GtkWidget *widget,
 124 				       GdkEventMotion *event,
 125 				       EWeekView *week_view);
 126 static gint e_week_view_convert_position_to_day (EWeekView *week_view,
 127 						 gint x,
 128 						 gint y);
 129 static void e_week_view_update_selection (EWeekView *week_view,
 130 					  gint day);
 131 
 132 static void e_week_view_free_events (EWeekView *week_view);
 133 static gboolean e_week_view_add_event (ECalComponent *comp,
 134 				       time_t	  start,
 135 				       time_t	  end,
 136 				       gboolean prepend,
 137 				       gpointer	  data);
 138 static void e_week_view_check_layout (EWeekView *week_view);
 139 static void e_week_view_ensure_events_sorted (EWeekView *week_view);
 140 static void e_week_view_reshape_events (EWeekView *week_view);
 141 static void e_week_view_reshape_event_span (EWeekView *week_view,
 142 					    gint event_num,
 143 					    gint span_num);
 144 static void e_week_view_recalc_day_starts (EWeekView *week_view,
 145 					   time_t lower);
 146 static void e_week_view_on_editing_started (EWeekView *week_view,
 147 					    GnomeCanvasItem *item);
 148 static void e_week_view_on_editing_stopped (EWeekView *week_view,
 149 					    GnomeCanvasItem *item);
 150 static gboolean e_week_view_find_event_from_uid (EWeekView	  *week_view,
 151 						 ECalClient             *client,
 152 						 const gchar	  *uid,
 153 						 const gchar      *rid,
 154 						 gint		  *event_num_return);
 155 typedef gboolean (* EWeekViewForeachEventCallback) (EWeekView *week_view,
 156 						    gint event_num,
 157 						    gpointer data);
 158 static void e_week_view_foreach_event_with_uid (EWeekView *week_view,
 159 						const gchar *uid,
 160 						EWeekViewForeachEventCallback callback,
 161 						gpointer data);
 162 static gboolean e_week_view_on_text_item_event (GnomeCanvasItem *item,
 163 						GdkEvent *event,
 164 						EWeekView *week_view);
 165 static gboolean e_week_view_event_move (ECalendarView *cal_view, ECalViewMoveDirection direction);
 166 static gint e_week_view_get_day_offset_of_event (EWeekView *week_view, time_t event_time);
 167 static void e_week_view_change_event_time (EWeekView *week_view, time_t start_dt, time_t end_dt, gboolean is_all_day);
 168 static gboolean e_week_view_on_jump_button_event (GnomeCanvasItem *item,
 169 						  GdkEvent *event,
 170 						  EWeekView *week_view);
 171 static gboolean e_week_view_key_press (GtkWidget *widget, GdkEventKey *event);
 172 static gboolean e_week_view_do_key_press (GtkWidget *widget,
 173 					  GdkEventKey *event);
 174 static gint e_week_view_get_adjust_days_for_move_up (EWeekView *week_view, gint
 175 current_day);
 176 static gint e_week_view_get_adjust_days_for_move_down (EWeekView *week_view,gint current_day);
 177 static gint e_week_view_get_adjust_days_for_move_left (EWeekView *week_view,gint current_day);
 178 static gint e_week_view_get_adjust_days_for_move_right (EWeekView *week_view,gint current_day);
 179 static gboolean e_week_view_popup_menu (GtkWidget *widget);
 180 
 181 static gboolean e_week_view_remove_event_cb (EWeekView *week_view,
 182 					     gint event_num,
 183 					     gpointer data);
 184 static gboolean e_week_view_recalc_display_start_day	(EWeekView	*week_view);
 185 
 186 static void e_week_view_queue_layout (EWeekView *week_view);
 187 static void e_week_view_cancel_layout (EWeekView *week_view);
 188 static gboolean e_week_view_layout_timeout_cb (gpointer data);
 189 
 190 G_DEFINE_TYPE (EWeekView, e_week_view, E_TYPE_CALENDAR_VIEW)
 191 
 192 enum {
 193 	PROP_0,
 194 	PROP_COMPRESS_WEEKEND,
 195 	PROP_SHOW_EVENT_END_TIMES
 196 };
 197 
 198 static gint map_left[] = {0, 1, 2, 0, 1, 2, 2};
 199 static gint map_right[] = {3, 4, 5, 3, 4, 5, 6};
 200 
 201 static void
 202 week_view_process_component (EWeekView *week_view,
 203                              ECalModelComponent *comp_data)
 204 {
 205 	ECalComponent *comp = NULL;
 206 	AddEventData add_event_data;
 207 	/* rid is never used in this function? */
 208 	const gchar *uid;
 209 	gchar *rid = NULL;
 210 
 211 	/* If we don't have a valid date set yet, just return. */
 212 	if (!g_date_valid (&week_view->first_day_shown))
 213 		return;
 214 
 215 	comp = e_cal_component_new ();
 216 	if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (comp_data->icalcomp))) {
 217 		g_object_unref (comp);
 218 
 219 		g_message (G_STRLOC ": Could not set icalcomponent on ECalComponent");
 220 		return;
 221 	}
 222 
 223 	e_cal_component_get_uid (comp, &uid);
 224 	if (e_cal_component_is_instance (comp))
 225 		rid = e_cal_component_get_recurid_as_string (comp);
 226 	else
 227 		rid = NULL;
 228 
 229 	/* Add the object */
 230 	add_event_data.week_view = week_view;
 231 	add_event_data.comp_data = comp_data;
 232 	e_week_view_add_event (comp, comp_data->instance_start, comp_data->instance_end, FALSE, &add_event_data);
 233 
 234 	g_object_unref (comp);
 235 	g_free (rid);
 236 }
 237 
 238 static void
 239 week_view_update_row (EWeekView *week_view,
 240                       gint row)
 241 {
 242 	ECalModelComponent *comp_data;
 243 	ECalModel *model;
 244 	gint event_num;
 245 	const gchar *uid;
 246 	gchar *rid = NULL;
 247 
 248 	model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
 249 	comp_data = e_cal_model_get_component_at (model, row);
 250 	g_return_if_fail (comp_data != NULL);
 251 
 252 	uid = icalcomponent_get_uid (comp_data->icalcomp);
 253 	if (e_cal_util_component_is_instance (comp_data->icalcomp)) {
 254 		icalproperty *prop;
 255 
 256 		prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY);
 257 		if (prop)
 258 			rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp));
 259 	}
 260 
 261 	if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num))
 262 		e_week_view_remove_event_cb (week_view, event_num, NULL);
 263 
 264 	g_free (rid);
 265 
 266 	week_view_process_component (week_view, comp_data);
 267 
 268 	gtk_widget_queue_draw (week_view->main_canvas);
 269 	e_week_view_queue_layout (week_view);
 270 }
 271 
 272 static void
 273 week_view_model_cell_changed_cb (EWeekView *week_view,
 274                                  gint col,
 275                                  gint row)
 276 {
 277 	if (!E_CALENDAR_VIEW (week_view)->in_focus) {
 278 		e_week_view_free_events (week_view);
 279 		week_view->requires_update = TRUE;
 280 		return;
 281 	}
 282 
 283 	week_view_update_row (week_view, row);
 284 }
 285 
 286 static void
 287 week_view_model_comps_deleted_cb (EWeekView *week_view,
 288                                   gpointer data)
 289 {
 290 	GSList *l, *list = data;
 291 
 292 	/* FIXME Stop editing? */
 293 	if (!E_CALENDAR_VIEW (week_view)->in_focus) {
 294 		e_week_view_free_events (week_view);
 295 		week_view->requires_update = TRUE;
 296 		return;
 297 	}
 298 
 299 	for (l = list; l != NULL; l = g_slist_next (l)) {
 300 		gint event_num;
 301 		const gchar *uid;
 302 		gchar *rid = NULL;
 303 		ECalModelComponent *comp_data = l->data;
 304 
 305 		uid = icalcomponent_get_uid (comp_data->icalcomp);
 306 		if (e_cal_util_component_is_instance (comp_data->icalcomp)) {
 307 			icalproperty *prop;
 308 
 309 			prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY);
 310 			if (prop)
 311 				rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp));
 312 		}
 313 
 314 		if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num))
 315 			e_week_view_remove_event_cb (week_view, event_num, NULL);
 316 		g_free (rid);
 317 	}
 318 
 319 	gtk_widget_queue_draw (week_view->main_canvas);
 320 	e_week_view_queue_layout (week_view);
 321 }
 322 
 323 static void
 324 week_view_model_row_changed_cb (EWeekView *week_view,
 325                                 gint row)
 326 {
 327 	if (!E_CALENDAR_VIEW (week_view)->in_focus) {
 328 		e_week_view_free_events (week_view);
 329 		week_view->requires_update = TRUE;
 330 		return;
 331 	}
 332 
 333 	week_view_update_row (week_view, row);
 334 }
 335 
 336 static void
 337 week_view_model_rows_inserted_cb (EWeekView *week_view,
 338                                   gint row,
 339                                   gint count)
 340 {
 341 	ECalModel *model;
 342 	gint i;
 343 
 344 	if (!E_CALENDAR_VIEW (week_view)->in_focus) {
 345 		e_week_view_free_events (week_view);
 346 		week_view->requires_update = TRUE;
 347 		return;
 348 	}
 349 
 350 	model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
 351 
 352 	for (i = 0; i < count; i++) {
 353 		ECalModelComponent *comp_data;
 354 
 355 		comp_data = e_cal_model_get_component_at (model, row + i);
 356 		if (comp_data == NULL) {
 357 			g_warning ("comp_data is NULL\n");
 358 			continue;
 359 		}
 360 		week_view_process_component (week_view, comp_data);
 361 	}
 362 
 363 	gtk_widget_queue_draw (week_view->main_canvas);
 364 	e_week_view_queue_layout (week_view);
 365 }
 366 
 367 static void
 368 week_view_time_range_changed_cb (EWeekView *week_view,
 369                                  time_t start_time,
 370                                  time_t end_time,
 371                                  ECalModel *model)
 372 {
 373 	GDate date, base_date;
 374 	gint day_offset, weekday, week_start_offset;
 375 	gboolean update_adjustment_value = FALSE;
 376 
 377 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
 378 
 379 	time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
 380 
 381 	/* Calculate the weekday of the given date, 0 = Mon. */
 382 	weekday = g_date_get_weekday (&date) - 1;
 383 
 384 	/* Convert it to an offset from the start of the display. */
 385 	week_start_offset = (weekday + 7 - week_view->display_start_day) % 7;
 386 
 387 	/* Set the day_offset to the result, so we move back to the
 388 	 * start of the week. */
 389 	day_offset = week_start_offset;
 390 
 391 	/* Calculate the base date, i.e. the first day shown when the
 392 	 * scrollbar adjustment value is 0. */
 393 	base_date = date;
 394 	g_date_subtract_days (&base_date, day_offset);
 395 
 396 	/* See if we need to update the base date. */
 397 	if (!g_date_valid (&week_view->base_date)
 398 	    || week_view->update_base_date) {
 399 		week_view->base_date = base_date;
 400 		update_adjustment_value = TRUE;
 401 	}
 402 
 403 	/* See if we need to update the first day shown. */
 404 	if (!g_date_valid (&week_view->first_day_shown)
 405 	    || g_date_compare (&week_view->first_day_shown, &base_date)) {
 406 		week_view->first_day_shown = base_date;
 407 		start_time = time_add_day_with_zone (
 408 			start_time, -day_offset,
 409 			e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
 410 		start_time = time_day_begin_with_zone (
 411 			start_time,
 412 			e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
 413 		e_week_view_recalc_day_starts (week_view, start_time);
 414 	}
 415 
 416 	/* Reset the adjustment value to 0 if the base address has changed.
 417 	 * Note that we do this after updating first_day_shown so that our
 418 	 * signal handler will not try to reload the events. */
 419 	if (update_adjustment_value) {
 420 		GtkRange *range;
 421 		GtkAdjustment *adjustment;
 422 
 423 		range = GTK_RANGE (week_view->vscrollbar);
 424 		adjustment = gtk_range_get_adjustment (range);
 425 		gtk_adjustment_set_value (adjustment, 0);
 426 	}
 427 
 428 	if (!E_CALENDAR_VIEW (week_view)->in_focus) {
 429 		e_week_view_free_events (week_view);
 430 		week_view->requires_update = TRUE;
 431 		return;
 432 	}
 433 
 434 	gtk_widget_queue_draw (week_view->main_canvas);
 435 
 436 	/* FIXME Preserve selection if possible */
 437 	if (week_view->selection_start_day == -1 ||
 438 	    (week_view->multi_week_view ? week_view->weeks_shown * 7 : 7) <= week_view->selection_start_day)
 439 		e_week_view_set_selected_time_range (E_CALENDAR_VIEW (week_view), start_time, start_time);
 440 }
 441 
 442 static void
 443 timezone_changed_cb (ECalModel *cal_model,
 444                      icaltimezone *old_zone,
 445                      icaltimezone *new_zone,
 446                      gpointer user_data)
 447 {
 448 	ECalendarView *cal_view = (ECalendarView *) user_data;
 449 	struct icaltimetype tt = icaltime_null_time ();
 450 	time_t lower;
 451 	EWeekView *week_view = (EWeekView *) cal_view;
 452 
 453 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
 454 
 455 	if (!cal_view->in_focus) {
 456 		e_week_view_free_events (week_view);
 457 		week_view->requires_update = TRUE;
 458 		return;
 459 	}
 460 
 461 	/* If we don't have a valid date set yet, just return. */
 462 	if (!g_date_valid (&week_view->first_day_shown))
 463 		return;
 464 
 465 	/* Recalculate the new start of the first week. We just use exactly
 466 	 * the same time, but with the new timezone. */
 467 	tt.year = g_date_get_year (&week_view->first_day_shown);
 468 	tt.month = g_date_get_month (&week_view->first_day_shown);
 469 	tt.day = g_date_get_day (&week_view->first_day_shown);
 470 
 471 	lower = icaltime_as_timet_with_zone (tt, new_zone);
 472 
 473 	e_week_view_recalc_day_starts (week_view, lower);
 474 	e_week_view_update_query (week_view);
 475 }
 476 
 477 static void
 478 week_view_notify_week_start_day_cb (EWeekView *week_view)
 479 {
 480 	GDate *first_day_shown;
 481 
 482 	first_day_shown = &week_view->first_day_shown;
 483 
 484 	e_week_view_recalc_display_start_day (week_view);
 485 
 486 	/* Recalculate the days shown and reload if necessary. */
 487 	if (g_date_valid (first_day_shown))
 488 		e_week_view_set_first_day_shown (week_view, first_day_shown);
 489 
 490 	gtk_widget_queue_draw (week_view->titles_canvas);
 491 	gtk_widget_queue_draw (week_view->main_canvas);
 492 }
 493 
 494 static void
 495 week_view_set_property (GObject *object,
 496                         guint property_id,
 497                         const GValue *value,
 498                         GParamSpec *pspec)
 499 {
 500 	switch (property_id) {
 501 		case PROP_COMPRESS_WEEKEND:
 502 			e_week_view_set_compress_weekend (
 503 				E_WEEK_VIEW (object),
 504 				g_value_get_boolean (value));
 505 			return;
 506 
 507 		case PROP_SHOW_EVENT_END_TIMES:
 508 			e_week_view_set_show_event_end_times (
 509 				E_WEEK_VIEW (object),
 510 				g_value_get_boolean (value));
 511 			return;
 512 	}
 513 
 514 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 515 }
 516 
 517 static void
 518 week_view_get_property (GObject *object,
 519                         guint property_id,
 520                         GValue *value,
 521                         GParamSpec *pspec)
 522 {
 523 	switch (property_id) {
 524 		case PROP_COMPRESS_WEEKEND:
 525 			g_value_set_boolean (
 526 				value,
 527 				e_week_view_get_compress_weekend (
 528 				E_WEEK_VIEW (object)));
 529 			return;
 530 
 531 		case PROP_SHOW_EVENT_END_TIMES:
 532 			g_value_set_boolean (
 533 				value,
 534 				e_week_view_get_show_event_end_times (
 535 				E_WEEK_VIEW (object)));
 536 			return;
 537 	}
 538 
 539 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 540 }
 541 
 542 static void
 543 week_view_constructed (GObject *object)
 544 {
 545 	ECalModel *model;
 546 	ECalendarView *calendar_view;
 547 
 548 	/* Chain up to parent's constructed() method. */
 549 	G_OBJECT_CLASS (e_week_view_parent_class)->constructed (object);
 550 
 551 	calendar_view = E_CALENDAR_VIEW (object);
 552 	model = e_calendar_view_get_model (calendar_view);
 553 
 554 	e_week_view_recalc_display_start_day (E_WEEK_VIEW (object));
 555 
 556 	g_signal_connect_swapped (
 557 		model, "notify::week-start-day",
 558 		G_CALLBACK (week_view_notify_week_start_day_cb), object);
 559 
 560 	g_signal_connect_swapped (
 561 		model, "comps-deleted",
 562 		G_CALLBACK (week_view_model_comps_deleted_cb), object);
 563 
 564 	g_signal_connect_swapped (
 565 		model, "model-cell-changed",
 566 		G_CALLBACK (week_view_model_cell_changed_cb), object);
 567 
 568 	g_signal_connect_swapped (
 569 		model, "model-row-changed",
 570 		G_CALLBACK (week_view_model_row_changed_cb), object);
 571 
 572 	g_signal_connect_swapped (
 573 		model, "model-rows-inserted",
 574 		G_CALLBACK (week_view_model_rows_inserted_cb), object);
 575 
 576 	g_signal_connect_swapped (
 577 		model, "time-range-changed",
 578 		G_CALLBACK (week_view_time_range_changed_cb), object);
 579 
 580 }
 581 
 582 static void
 583 week_view_cursor_key_up (EWeekView *week_view)
 584 {
 585 	if (week_view->selection_start_day == -1)
 586 		return;
 587 
 588 	week_view->selection_start_day--;
 589 
 590 	if (week_view->selection_start_day < 0) {
 591 		e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_UP);
 592 		week_view->selection_start_day = 6;
 593 	}
 594 
 595 	week_view->selection_end_day = week_view->selection_start_day;
 596 	g_signal_emit_by_name (week_view, "selected_time_changed");
 597 	gtk_widget_queue_draw (week_view->main_canvas);
 598 }
 599 
 600 static void
 601 week_view_cursor_key_down (EWeekView *week_view)
 602 {
 603 	if (week_view->selection_start_day == -1)
 604 		return;
 605 
 606 	week_view->selection_start_day++;
 607 
 608 	if (week_view->selection_start_day > 6) {
 609 		e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_DOWN);
 610 		week_view->selection_start_day = 0;
 611 	}
 612 
 613 	week_view->selection_end_day = week_view->selection_start_day;
 614 	g_signal_emit_by_name (week_view, "selected_time_changed");
 615 	gtk_widget_queue_draw (week_view->main_canvas);
 616 }
 617 
 618 static void
 619 week_view_cursor_key_left (EWeekView *week_view)
 620 {
 621 	if (week_view->selection_start_day == -1)
 622 		return;
 623 
 624 	week_view->selection_start_day = map_left[week_view->selection_start_day];
 625 	week_view->selection_end_day = week_view->selection_start_day;
 626 	g_signal_emit_by_name (week_view, "selected_time_changed");
 627 	gtk_widget_queue_draw (week_view->main_canvas);
 628 }
 629 
 630 static void
 631 week_view_cursor_key_right (EWeekView *week_view)
 632 {
 633 	if (week_view->selection_start_day == -1)
 634 		return;
 635 
 636 	week_view->selection_start_day = map_right[week_view->selection_start_day];
 637 	week_view->selection_end_day = week_view->selection_start_day;
 638 	g_signal_emit_by_name (week_view, "selected_time_changed");
 639 	gtk_widget_queue_draw (week_view->main_canvas);
 640 }
 641 
 642 static void
 643 month_scrol_by_week_changed_cb (GSettings *settings,
 644                                 const gchar *key,
 645                                 gpointer user_data)
 646 {
 647 	EWeekView *week_view = user_data;
 648 
 649 	g_return_if_fail (week_view != NULL);
 650 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
 651 
 652 	if (week_view->multi_week_view && week_view->month_scroll_by_week != calendar_config_get_month_scroll_by_week ()) {
 653 		week_view->multi_week_view = FALSE;
 654 		e_week_view_set_multi_week_view (week_view, TRUE);
 655 	}
 656 }
 657 
 658 static void
 659 e_week_view_class_init (EWeekViewClass *class)
 660 {
 661 	GObjectClass *object_class;
 662 	GtkWidgetClass *widget_class;
 663 	ECalendarViewClass *view_class;
 664 
 665 	object_class = G_OBJECT_CLASS (class);
 666 	object_class->set_property = week_view_set_property;
 667 	object_class->get_property = week_view_get_property;
 668 	object_class->constructed = week_view_constructed;
 669 	object_class->dispose = e_week_view_dispose;
 670 
 671 	widget_class = GTK_WIDGET_CLASS (class);
 672 	widget_class->realize = e_week_view_realize;
 673 	widget_class->unrealize = e_week_view_unrealize;
 674 	widget_class->style_set = e_week_view_style_set;
 675 	widget_class->size_allocate = e_week_view_size_allocate;
 676 	widget_class->focus_in_event = e_week_view_focus_in;
 677 	widget_class->focus_out_event = e_week_view_focus_out;
 678 	widget_class->key_press_event = e_week_view_key_press;
 679 	widget_class->popup_menu = e_week_view_popup_menu;
 680 	widget_class->focus = e_week_view_focus;
 681 
 682 	view_class = E_CALENDAR_VIEW_CLASS (class);
 683 	view_class->get_selected_events = e_week_view_get_selected_events;
 684 	view_class->get_selected_time_range = e_week_view_get_selected_time_range;
 685 	view_class->set_selected_time_range = e_week_view_set_selected_time_range;
 686 	view_class->get_visible_time_range = e_week_view_get_visible_time_range;
 687 	view_class->paste_text = e_week_view_paste_text;
 688 
 689 	class->cursor_key_up = week_view_cursor_key_up;
 690 	class->cursor_key_down = week_view_cursor_key_down;
 691 	class->cursor_key_left = week_view_cursor_key_left;
 692 	class->cursor_key_right = week_view_cursor_key_right;
 693 
 694 	/* XXX This property really belongs in EMonthView,
 695 	 *     but too much drawing code is tied to it. */
 696 	g_object_class_install_property (
 697 		object_class,
 698 		PROP_COMPRESS_WEEKEND,
 699 		g_param_spec_boolean (
 700 			"compress-weekend",
 701 			"Compress Weekend",
 702 			NULL,
 703 			TRUE,
 704 			G_PARAM_READWRITE));
 705 
 706 	g_object_class_install_property (
 707 		object_class,
 708 		PROP_SHOW_EVENT_END_TIMES,
 709 		g_param_spec_boolean (
 710 			"show-event-end-times",
 711 			"Show Event End Times",
 712 			NULL,
 713 			TRUE,
 714 			G_PARAM_READWRITE));
 715 
 716 	/* init the accessibility support for e_week_view */
 717 	e_week_view_a11y_init ();
 718 }
 719 
 720 static void
 721 e_week_view_init (EWeekView *week_view)
 722 {
 723 	GnomeCanvasGroup *canvas_group;
 724 	GtkAdjustment *adjustment;
 725 	GdkPixbuf *pixbuf;
 726 	gint i;
 727 
 728 	gtk_widget_set_can_focus (GTK_WIDGET (week_view), TRUE);
 729 
 730 	week_view->event_destroyed = FALSE;
 731 	week_view->events = g_array_new (
 732 		FALSE, FALSE,
 733 		sizeof (EWeekViewEvent));
 734 	week_view->events_sorted = TRUE;
 735 	week_view->events_need_layout = FALSE;
 736 	week_view->events_need_reshape = FALSE;
 737 
 738 	week_view->layout_timeout_id = 0;
 739 
 740 	week_view->spans = NULL;
 741 
 742 	week_view->multi_week_view = FALSE;
 743 	week_view->month_scroll_by_week = FALSE;
 744 	week_view->scroll_by_week_notif_id = 0;
 745 	week_view->update_base_date = TRUE;
 746 	week_view->weeks_shown = 6;
 747 	week_view->rows = 6;
 748 	week_view->columns = 2;
 749 	week_view->compress_weekend = TRUE;
 750 	week_view->show_event_end_times = TRUE;
 751 	week_view->display_start_day = 0;	/* Monday. */
 752 
 753 	g_date_clear (&week_view->base_date, 1);
 754 	g_date_clear (&week_view->first_day_shown, 1);
 755 
 756 	week_view->row_height = 10;
 757 	week_view->rows_per_cell = 1;
 758 
 759 	week_view->selection_start_day = -1;
 760 	week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;
 761 
 762 	week_view->pressed_event_num = -1;
 763 	week_view->editing_event_num = -1;
 764 
 765 	week_view->last_edited_comp_string = NULL;
 766 
 767 	/* Create the small font. */
 768 	week_view->use_small_font = TRUE;
 769 
 770 	week_view->small_font_desc =
 771 		pango_font_description_copy (gtk_widget_get_style (GTK_WIDGET (week_view))->font_desc);
 772 	pango_font_description_set_size (
 773 		week_view->small_font_desc,
 774 		E_WEEK_VIEW_SMALL_FONT_PTSIZE * PANGO_SCALE);
 775 
 776 	/* String to use in 12-hour time format for times in the morning. */
 777 	week_view->am_string = _("am");
 778 
 779 	/* String to use in 12-hour time format for times in the afternoon. */
 780 	week_view->pm_string = _("pm");
 781 
 782 	week_view->bc_event_time = 0;
 783 	week_view->before_click_dtstart = 0;
 784 	week_view->before_click_dtend = 0;
 785 
 786 	/*
 787 	 * Titles Canvas. Note that we don't show it is only shown in the
 788 	 * Month view.
 789 	 */
 790 	week_view->titles_canvas = e_canvas_new ();
 791 	gtk_table_attach (
 792 		GTK_TABLE (week_view), week_view->titles_canvas,
 793 		1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
 794 
 795 	canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->titles_canvas)->root);
 796 
 797 	week_view->titles_canvas_item =
 798 		gnome_canvas_item_new (
 799 			canvas_group,
 800 			e_week_view_titles_item_get_type (),
 801 			"EWeekViewTitlesItem::week_view", week_view,
 802 			NULL);
 803 
 804 	/*
 805 	 * Main Canvas
 806 	 */
 807 	week_view->main_canvas = e_canvas_new ();
 808 	gtk_table_attach (
 809 		GTK_TABLE (week_view), week_view->main_canvas,
 810 		1, 2, 1, 2,
 811 		GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
 812 	gtk_widget_show (week_view->main_canvas);
 813 
 814 	canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root);
 815 
 816 	week_view->main_canvas_item =
 817 		gnome_canvas_item_new (
 818 			canvas_group,
 819 			e_week_view_main_item_get_type (),
 820 			"EWeekViewMainItem::week_view", week_view,
 821 			NULL);
 822 
 823 	g_signal_connect_after (
 824 		week_view->main_canvas, "button_press_event",
 825 		G_CALLBACK (e_week_view_on_button_press), week_view);
 826 	g_signal_connect (
 827 		week_view->main_canvas, "button_release_event",
 828 		G_CALLBACK (e_week_view_on_button_release), week_view);
 829 	g_signal_connect (
 830 		week_view->main_canvas, "scroll_event",
 831 		G_CALLBACK (e_week_view_on_scroll), week_view);
 832 	g_signal_connect (
 833 		week_view->main_canvas, "motion_notify_event",
 834 		G_CALLBACK (e_week_view_on_motion), week_view);
 835 
 836 	/* Create the buttons to jump to each days. */
 837 	pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm);
 838 
 839 	for (i = 0; i < E_WEEK_VIEW_MAX_WEEKS * 7; i++) {
 840 		week_view->jump_buttons[i] = gnome_canvas_item_new
 841 			(canvas_group,
 842 			 gnome_canvas_pixbuf_get_type (),
 843 			 "GnomeCanvasPixbuf::pixbuf", pixbuf,
 844 			 NULL);
 845 
 846 		g_signal_connect (
 847 			week_view->jump_buttons[i], "event",
 848 			G_CALLBACK (e_week_view_on_jump_button_event), week_view);
 849 	}
 850 	week_view->focused_jump_button = E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS;
 851 
 852 	g_object_unref (pixbuf);
 853 
 854 	/*
 855 	 * Scrollbar.
 856 	 */
 857 	adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, -52, 52, 1, 1, 1));
 858 
 859 	week_view->vscrollbar = gtk_vscrollbar_new (adjustment);
 860 	gtk_table_attach (
 861 		GTK_TABLE (week_view), week_view->vscrollbar,
 862 		2, 3, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0);
 863 	gtk_widget_show (week_view->vscrollbar);
 864 
 865 	/* Create the cursors. */
 866 	week_view->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR);
 867 	week_view->move_cursor = gdk_cursor_new (GDK_FLEUR);
 868 	week_view->resize_width_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
 869 	week_view->last_cursor_set = NULL;
 870 
 871 	week_view->requires_update = FALSE;
 872 }
 873 
 874 /**
 875  * e_week_view_new:
 876  * @Returns: a new #EWeekView.
 877  *
 878  * Creates a new #EWeekView.
 879  **/
 880 ECalendarView *
 881 e_week_view_new (ECalModel *model)
 882 {
 883 	ECalendarView *view;
 884 	g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
 885 
 886 	view = g_object_new (E_TYPE_WEEK_VIEW, "model", model, NULL);
 887 
 888 	g_signal_connect (
 889 		model, "timezone_changed",
 890 		G_CALLBACK (timezone_changed_cb), view);
 891 
 892 	return view;
 893 }
 894 
 895 static void
 896 e_week_view_dispose (GObject *object)
 897 {
 898 	EWeekView *week_view;
 899 
 900 	week_view = E_WEEK_VIEW (object);
 901 
 902 	e_week_view_cancel_layout (week_view);
 903 
 904 	if (week_view->events) {
 905 		e_week_view_free_events (week_view);
 906 		g_array_free (week_view->events, TRUE);
 907 		week_view->events = NULL;
 908 	}
 909 
 910 	if (week_view->small_font_desc) {
 911 		pango_font_description_free (week_view->small_font_desc);
 912 		week_view->small_font_desc = NULL;
 913 	}
 914 
 915 	if (week_view->normal_cursor) {
 916 		g_object_unref (week_view->normal_cursor);
 917 		week_view->normal_cursor = NULL;
 918 	}
 919 	if (week_view->move_cursor) {
 920 		g_object_unref (week_view->move_cursor);
 921 		week_view->move_cursor = NULL;
 922 	}
 923 	if (week_view->resize_width_cursor) {
 924 		g_object_unref (week_view->resize_width_cursor);
 925 		week_view->resize_width_cursor = NULL;
 926 	}
 927 
 928 	calendar_config_remove_notification (month_scrol_by_week_changed_cb, week_view);
 929 
 930 	/* Chain up to parent's dispose() method. */
 931 	G_OBJECT_CLASS (e_week_view_parent_class)->dispose (object);
 932 }
 933 
 934 static void
 935 e_week_view_realize (GtkWidget *widget)
 936 {
 937 	EWeekView *week_view;
 938 
 939 	if (GTK_WIDGET_CLASS (e_week_view_parent_class)->realize)
 940 		(*GTK_WIDGET_CLASS (e_week_view_parent_class)->realize)(widget);
 941 
 942 	week_view = E_WEEK_VIEW (widget);
 943 
 944 	/* Allocate the colors. */
 945 	e_week_view_set_colors (week_view, widget);
 946 
 947 	/* Create the pixmaps. */
 948 	week_view->reminder_icon = e_icon_factory_get_icon ("stock_bell", GTK_ICON_SIZE_MENU);
 949 	week_view->recurrence_icon = e_icon_factory_get_icon ("view-refresh", GTK_ICON_SIZE_MENU);
 950 	week_view->timezone_icon = e_icon_factory_get_icon ("stock_timezone", GTK_ICON_SIZE_MENU);
 951 	week_view->attach_icon = e_icon_factory_get_icon ("mail-attachment", GTK_ICON_SIZE_MENU);
 952 	week_view->meeting_icon = e_icon_factory_get_icon ("stock_people", GTK_ICON_SIZE_MENU);
 953 }
 954 
 955 static GdkColor
 956 color_inc (GdkColor c,
 957            gint amount)
 958 {
 959 	#define dec(x)				\
 960 		if (x + amount >= 0		\
 961 		    && x + amount <= 0xFFFF)	\
 962 			x += amount;		\
 963 		else if (amount <= 0)		\
 964 			x = 0;			\
 965 		else				\
 966 			x = 0xFFFF;
 967 
 968 	dec (c.red);
 969 	dec (c.green);
 970 	dec (c.blue);
 971 
 972 	#undef dec
 973 
 974 	return c;
 975 }
 976 
 977 static void
 978 e_week_view_set_colors (EWeekView *week_view,
 979                         GtkWidget *widget)
 980 {
 981 	GtkStyle *style;
 982 
 983 	style = gtk_widget_get_style (widget);
 984 
 985 	week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS] = style->base[GTK_STATE_INSENSITIVE];
 986 	week_view->colors[E_WEEK_VIEW_COLOR_ODD_MONTHS] = style->base[GTK_STATE_NORMAL];
 987 	week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND] = style->base[GTK_STATE_NORMAL];
 988 	week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BORDER] = style->dark[GTK_STATE_NORMAL];
 989 	week_view->colors[E_WEEK_VIEW_COLOR_EVENT_TEXT] = style->text[GTK_STATE_NORMAL];
 990 	week_view->colors[E_WEEK_VIEW_COLOR_GRID] = style->dark[GTK_STATE_NORMAL];
 991 	week_view->colors[E_WEEK_VIEW_COLOR_SELECTED] = style->base[GTK_STATE_SELECTED];
 992 	week_view->colors[E_WEEK_VIEW_COLOR_SELECTED_UNFOCUSSED] = style->bg[GTK_STATE_SELECTED];
 993 	week_view->colors[E_WEEK_VIEW_COLOR_DATES] = style->text[GTK_STATE_NORMAL];
 994 	week_view->colors[E_WEEK_VIEW_COLOR_DATES_SELECTED] = style->text[GTK_STATE_SELECTED];
 995 	week_view->colors[E_WEEK_VIEW_COLOR_TODAY] = style->base[GTK_STATE_SELECTED];
 996 	week_view->colors[E_WEEK_VIEW_COLOR_TODAY_BACKGROUND] = get_today_background (week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND]);
 997 	week_view->colors[E_WEEK_VIEW_COLOR_MONTH_NONWORKING_DAY] = color_inc (week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS], -0x0A0A);
 998 }
 999 
1000 static void
1001 e_week_view_unrealize (GtkWidget *widget)
1002 {
1003 	EWeekView *week_view;
1004 
1005 	week_view = E_WEEK_VIEW (widget);
1006 
1007 	g_object_unref (week_view->reminder_icon);
1008 	week_view->reminder_icon = NULL;
1009 	g_object_unref (week_view->recurrence_icon);
1010 	week_view->recurrence_icon = NULL;
1011 	g_object_unref (week_view->timezone_icon);
1012 	week_view->timezone_icon = NULL;
1013 	g_object_unref (week_view->attach_icon);
1014 	week_view->attach_icon = NULL;
1015 	g_object_unref (week_view->meeting_icon);
1016 	week_view->meeting_icon = NULL;
1017 
1018 	if (GTK_WIDGET_CLASS (e_week_view_parent_class)->unrealize)
1019 		(*GTK_WIDGET_CLASS (e_week_view_parent_class)->unrealize)(widget);
1020 }
1021 
1022 static gint
1023 get_string_width (PangoLayout *layout,
1024                   const gchar *string)
1025 {
1026 	gint width;
1027 
1028 	pango_layout_set_text (layout, string, -1);
1029 	pango_layout_get_pixel_size (layout, &width, NULL);
1030 	return width;
1031 }
1032 
1033 /* FIXME: This is also needed in e-day-view-time-item.c. We should probably use
1034  * pango's approximation function, but it needs a language tag. Find out how to
1035  * get one of those properly. */
1036 static gint
1037 get_digit_width (PangoLayout *layout)
1038 {
1039 	gint digit;
1040 	gint max_digit_width = 1;
1041 
1042 	for (digit = '0'; digit <= '9'; digit++) {
1043 		gchar digit_char;
1044 		gint  digit_width;
1045 
1046 		digit_char = digit;
1047 
1048 		pango_layout_set_text (layout, &digit_char, 1);
1049 		pango_layout_get_pixel_size (layout, &digit_width, NULL);
1050 
1051 		max_digit_width = MAX (max_digit_width, digit_width);
1052 	}
1053 
1054 	return max_digit_width;
1055 }
1056 
1057 static GdkColor
1058 e_week_view_get_text_color (EWeekView *week_view,
1059                             EWeekViewEvent *event,
1060                             GtkWidget *widget)
1061 {
1062 	GtkStyle *style;
1063 	GdkColor bg_color;
1064 	guint16 red, green, blue;
1065 	gdouble	cc = 65535.0;
1066 
1067 	red = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].red;
1068 	green = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].green;
1069 	blue = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].blue;
1070 
1071 	if (is_comp_data_valid (event) && gdk_color_parse (e_cal_model_get_color_for_component (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), event->comp_data),
1072 	     &bg_color)) {
1073 		red = bg_color.red;
1074 		green = bg_color.green;
1075 		blue = bg_color.blue;
1076 	}
1077 
1078 	style = gtk_widget_get_style (widget);
1079 
1080 	if ((red / cc > 0.7) || (green / cc > 0.7) || (blue / cc > 0.7))
1081 		return style->black;
1082 	else
1083 		return style->white;
1084 }
1085 
1086 static void
1087 e_week_view_style_set (GtkWidget *widget,
1088                        GtkStyle *previous_style)
1089 {
1090 	EWeekView *week_view;
1091 	GtkStyle *style;
1092 	gint day, day_width, max_day_width, max_abbr_day_width;
1093 	gint month, month_width, max_month_width, max_abbr_month_width;
1094 	gint span_num;
1095 	const gchar *name;
1096 	PangoFontDescription *font_desc;
1097 	PangoContext *pango_context;
1098 	PangoFontMetrics *font_metrics;
1099 	PangoLayout *layout;
1100 	EWeekViewEventSpan *span;
1101 
1102 	if (GTK_WIDGET_CLASS (e_week_view_parent_class)->style_set)
1103 		(*GTK_WIDGET_CLASS (e_week_view_parent_class)->style_set)(widget, previous_style);
1104 
1105 	week_view = E_WEEK_VIEW (widget);
1106 	style = gtk_widget_get_style (widget);
1107 
1108 	e_week_view_set_colors (week_view, widget);
1109 	if (week_view->spans) {
1110 		for (span_num = 0; span_num < week_view->spans->len;
1111 				span_num++) {
1112 			span = &g_array_index (week_view->spans,
1113 					EWeekViewEventSpan, span_num);
1114 			if (span->text_item) {
1115 				gnome_canvas_item_set (
1116 					span->text_item,
1117 					"fill_color_gdk", &style->text[GTK_STATE_NORMAL],
1118 					NULL);
1119 			}
1120 		}
1121 	}
1122 
1123 	/* Set up Pango prerequisites */
1124 	font_desc = style->font_desc;
1125 	pango_context = gtk_widget_get_pango_context (widget);
1126 	font_metrics = pango_context_get_metrics (
1127 		pango_context, font_desc,
1128 		pango_context_get_language (pango_context));
1129 	layout = pango_layout_new (pango_context);
1130 
1131 	/* Recalculate the height of each row based on the font size. */
1132 	week_view->row_height = PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1133 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) +
1134 		E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2 + E_WEEK_VIEW_EVENT_TEXT_Y_PAD * 2;
1135 	week_view->row_height = MAX (week_view->row_height, E_WEEK_VIEW_ICON_HEIGHT + E_WEEK_VIEW_ICON_Y_PAD + E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2);
1136 
1137 	/* Check that the small font is smaller than the default font.
1138 	 * If it isn't, we won't use it. */
1139 	if (week_view->small_font_desc) {
1140 		if (PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1141 		    PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1142 		    <= E_WEEK_VIEW_SMALL_FONT_PTSIZE)
1143 			week_view->use_small_font = FALSE;
1144 	}
1145 
1146 	/* Set the height of the top canvas. */
1147 	gtk_widget_set_size_request (
1148 		week_view->titles_canvas, -1,
1149 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1150 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + 5);
1151 
1152 	/* Save the sizes of various strings in the font, so we can quickly
1153 	 * decide which date formats to use. */
1154 
1155 	max_day_width = 0;
1156 	max_abbr_day_width = 0;
1157 	for (day = 0; day < 7; day++) {
1158 		name = e_get_weekday_name (day + 1, FALSE);
1159 		day_width = get_string_width (layout, name);
1160 		week_view->day_widths[day] = day_width;
1161 		max_day_width = MAX (max_day_width, day_width);
1162 
1163 		name = e_get_weekday_name (day + 1, TRUE);
1164 		day_width = get_string_width (layout, name);
1165 		week_view->abbr_day_widths[day] = day_width;
1166 		max_abbr_day_width = MAX (max_abbr_day_width, day_width);
1167 	}
1168 
1169 	max_month_width = 0;
1170 	max_abbr_month_width = 0;
1171 	for (month = 0; month < 12; month++) {
1172 		name = e_get_month_name (month + 1, FALSE);
1173 		month_width = get_string_width (layout, name);
1174 		week_view->month_widths[month] = month_width;
1175 		max_month_width = MAX (max_month_width, month_width);
1176 
1177 		name = e_get_month_name (month + 1, TRUE);
1178 		month_width = get_string_width (layout, name);
1179 		week_view->abbr_month_widths[month] = month_width;
1180 		max_abbr_month_width = MAX (max_abbr_month_width, month_width);
1181 	}
1182 
1183 	week_view->space_width = get_string_width (layout, " ");
1184 	week_view->colon_width = get_string_width (layout, ":");
1185 	week_view->slash_width = get_string_width (layout, "/");
1186 	week_view->digit_width = get_digit_width (layout);
1187 	if (week_view->small_font_desc) {
1188 		pango_layout_set_font_description (layout, week_view->small_font_desc);
1189 		week_view->small_digit_width = get_digit_width (layout);
1190 		pango_layout_set_font_description (layout, style->font_desc);
1191 	}
1192 	week_view->max_day_width = max_day_width;
1193 	week_view->max_abbr_day_width = max_abbr_day_width;
1194 	week_view->max_month_width = max_month_width;
1195 	week_view->max_abbr_month_width = max_abbr_month_width;
1196 
1197 	week_view->am_string_width = get_string_width (
1198 		layout,
1199 		week_view->am_string);
1200 	week_view->pm_string_width = get_string_width (
1201 		layout,
1202 		week_view->pm_string);
1203 
1204 	g_object_unref (layout);
1205 	pango_font_metrics_unref (font_metrics);
1206 }
1207 
1208 /* This recalculates the sizes of each column. */
1209 static void
1210 e_week_view_size_allocate (GtkWidget *widget,
1211                            GtkAllocation *allocation)
1212 {
1213 	EWeekView *week_view;
1214 	GtkAllocation canvas_allocation;
1215 	gdouble old_x2, old_y2, new_x2, new_y2;
1216 
1217 	week_view = E_WEEK_VIEW (widget);
1218 
1219 	(*GTK_WIDGET_CLASS (e_week_view_parent_class)->size_allocate) (widget, allocation);
1220 
1221 	e_week_view_recalc_cell_sizes (week_view);
1222 
1223 	/* Set the scroll region of the top canvas to its allocated size. */
1224 	gnome_canvas_get_scroll_region (
1225 		GNOME_CANVAS (week_view->titles_canvas),
1226 		NULL, NULL, &old_x2, &old_y2);
1227 	gtk_widget_get_allocation (
1228 		week_view->titles_canvas, &canvas_allocation);
1229 	new_x2 = canvas_allocation.width - 1;
1230 	new_y2 = canvas_allocation.height - 1;
1231 	if (old_x2 != new_x2 || old_y2 != new_y2)
1232 		gnome_canvas_set_scroll_region (
1233 			GNOME_CANVAS (week_view->titles_canvas),
1234 			0, 0, new_x2, new_y2);
1235 
1236 	/* Set the scroll region of the main canvas to its allocated width,
1237 	 * but with the height depending on the number of rows needed. */
1238 	gnome_canvas_get_scroll_region (
1239 		GNOME_CANVAS (week_view->main_canvas),
1240 		NULL, NULL, &old_x2, &old_y2);
1241 	gtk_widget_get_allocation (
1242 		week_view->main_canvas, &canvas_allocation);
1243 	new_x2 = canvas_allocation.width - 1;
1244 	new_y2 = canvas_allocation.height - 1;
1245 	if (old_x2 != new_x2 || old_y2 != new_y2)
1246 		gnome_canvas_set_scroll_region (
1247 			GNOME_CANVAS (week_view->main_canvas),
1248 			0, 0, new_x2, new_y2);
1249 
1250 	/* Flag that we need to reshape the events. */
1251 	if (old_x2 != new_x2 || old_y2 != new_y2) {
1252 		week_view->events_need_reshape = TRUE;
1253 		e_week_view_check_layout (week_view);
1254 	}
1255 }
1256 
1257 static void
1258 e_week_view_recalc_cell_sizes (EWeekView *week_view)
1259 {
1260 	gfloat canvas_width, canvas_height, offset;
1261 	gint row, col;
1262 	GtkAllocation allocation;
1263 	GtkWidget *widget;
1264 	GtkStyle *style;
1265 	gint width, height, time_width;
1266 	PangoFontDescription *font_desc;
1267 	PangoContext *pango_context;
1268 	PangoFontMetrics *font_metrics;
1269 
1270 	if (week_view->multi_week_view) {
1271 		week_view->rows = week_view->weeks_shown * 2;
1272 		week_view->columns = week_view->compress_weekend ? 6 : 7;
1273 	} else {
1274 		week_view->rows = 6;
1275 		week_view->columns = 2;
1276 	}
1277 
1278 	gtk_widget_get_allocation (week_view->main_canvas, &allocation);
1279 
1280 	/* Calculate the column sizes, using floating point so that pixels
1281 	 * get divided evenly. Note that we use one more element than the
1282 	 * number of columns, to make it easy to get the column widths.
1283 	 * We also add one to the width so that the right border of the last
1284 	 * column is off the edge of the displayed area. */
1285 	canvas_width = allocation.width + 1;
1286 	canvas_width /= week_view->columns;
1287 	offset = 0;
1288 	for (col = 0; col <= week_view->columns; col++) {
1289 		week_view->col_offsets[col] = floor (offset + 0.5);
1290 		offset += canvas_width;
1291 	}
1292 
1293 	/* Calculate the cell widths based on the offsets. */
1294 	for (col = 0; col < week_view->columns; col++) {
1295 		week_view->col_widths[col] = week_view->col_offsets[col + 1]
1296 			- week_view->col_offsets[col];
1297 	}
1298 
1299 	/* Now do the same for the row heights. */
1300 	canvas_height = allocation.height + 1;
1301 	canvas_height /= week_view->rows;
1302 	offset = 0;
1303 	for (row = 0; row <= week_view->rows; row++) {
1304 		week_view->row_offsets[row] = floor (offset + 0.5);
1305 		offset += canvas_height;
1306 	}
1307 
1308 	/* Calculate the cell heights based on the offsets. */
1309 	for (row = 0; row < week_view->rows; row++) {
1310 		week_view->row_heights[row] = week_view->row_offsets[row + 1]
1311 			- week_view->row_offsets[row];
1312 	}
1313 
1314 	/* If the font hasn't been set yet just return. */
1315 	widget = GTK_WIDGET (week_view);
1316 	style = gtk_widget_get_style (widget);
1317 	if (!style)
1318 		return;
1319 	font_desc = style->font_desc;
1320 	if (!font_desc)
1321 		return;
1322 
1323 	pango_context = gtk_widget_get_pango_context (widget);
1324 	font_metrics = pango_context_get_metrics (
1325 		pango_context, font_desc,
1326 		pango_context_get_language (pango_context));
1327 
1328 	/* Calculate the number of rows of events in each cell, for the large
1329 	 * cells and the compressed weekend cells. */
1330 	if (week_view->multi_week_view) {
1331 		week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD +
1332 			+ PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
1333 			+ PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1334 			+ E_WEEK_VIEW_DATE_B_PAD;
1335 	} else {
1336 		week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD
1337 			+ PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
1338 			+ PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1339 			+ E_WEEK_VIEW_DATE_LINE_T_PAD + 1
1340 			+ E_WEEK_VIEW_DATE_LINE_B_PAD;
1341 	}
1342 
1343 	height = week_view->row_heights[0];
1344 	week_view->rows_per_cell =
1345 		(height * 2 - week_view->events_y_offset) /
1346 		(week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING);
1347 	week_view->rows_per_cell = MIN (
1348 		week_view->rows_per_cell,
1349 		E_WEEK_VIEW_MAX_ROWS_PER_CELL);
1350 
1351 	week_view->rows_per_compressed_cell =
1352 		(height - week_view->events_y_offset) /
1353 		(week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING);
1354 	week_view->rows_per_compressed_cell = MIN (
1355 		week_view->rows_per_compressed_cell,
1356 		E_WEEK_VIEW_MAX_ROWS_PER_CELL);
1357 
1358 	/* Determine which time format to use, based on the width of the cells.
1359 	 * We only allow the time to take up about half of the width. */
1360 	width = week_view->col_widths[0];
1361 
1362 	time_width = e_week_view_get_time_string_width (week_view);
1363 
1364 	week_view->time_format = E_WEEK_VIEW_TIME_NONE;
1365 	if (week_view->use_small_font && week_view->small_font_desc) {
1366 		if (week_view->show_event_end_times
1367 		    && width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING)
1368 			week_view->time_format = E_WEEK_VIEW_TIME_BOTH_SMALL_MIN;
1369 		else if (width / 2 > time_width)
1370 			week_view->time_format = E_WEEK_VIEW_TIME_START_SMALL_MIN;
1371 	} else {
1372 		if (week_view->show_event_end_times
1373 		    && width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING)
1374 			week_view->time_format = E_WEEK_VIEW_TIME_BOTH;
1375 		else if (width / 2 > time_width)
1376 			week_view->time_format = E_WEEK_VIEW_TIME_START;
1377 	}
1378 
1379 	pango_font_metrics_unref (font_metrics);
1380 }
1381 
1382 static gint
1383 e_week_view_focus_in (GtkWidget *widget,
1384                       GdkEventFocus *event)
1385 {
1386 	EWeekView *week_view;
1387 
1388 	g_return_val_if_fail (widget != NULL, FALSE);
1389 	g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
1390 	g_return_val_if_fail (event != NULL, FALSE);
1391 
1392 	week_view = E_WEEK_VIEW (widget);
1393 
1394 	/* XXX Can't access flags directly anymore, but is it really needed?
1395 	 *     If so, could we call gtk_widget_send_focus_change() instead? */
1396 #if 0
1397 	GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
1398 #endif
1399 
1400 	if (E_CALENDAR_VIEW (week_view)->in_focus && week_view->requires_update) {
1401 		time_t my_start = 0, my_end = 0, model_start = 0, model_end = 0;
1402 
1403 		week_view->requires_update = FALSE;
1404 
1405 		e_cal_model_get_time_range (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), &model_start, &model_end);
1406 
1407 		if (e_calendar_view_get_visible_time_range (E_CALENDAR_VIEW (week_view), &my_start, &my_end) &&
1408 		    model_start == my_start && model_end == my_end) {
1409 			/* update only when the same time range is set in a view and in a model;
1410 			 * otherwise time range change invokes also query update */
1411 			e_week_view_update_query (week_view);
1412 		}
1413 	}
1414 
1415 	gtk_widget_queue_draw (week_view->main_canvas);
1416 
1417 	return FALSE;
1418 }
1419 
1420 static gint
1421 e_week_view_focus_out (GtkWidget *widget,
1422                        GdkEventFocus *event)
1423 {
1424 	EWeekView *week_view;
1425 
1426 	g_return_val_if_fail (widget != NULL, FALSE);
1427 	g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
1428 	g_return_val_if_fail (event != NULL, FALSE);
1429 
1430 	week_view = E_WEEK_VIEW (widget);
1431 
1432 	/* XXX Can't access flags directly anymore, but is it really needed?
1433 	 *     If so, could we call gtk_widget_send_focus_change() instead? */
1434 #if 0
1435 	GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
1436 #endif
1437 
1438 	gtk_widget_queue_draw (week_view->main_canvas);
1439 
1440 	return FALSE;
1441 }
1442 
1443 /**
1444  * e_week_view_get_next_tab_event
1445  * @week_view: the week_view widget operate on
1446  * @direction: GTK_DIR_TAB_BACKWARD or GTK_DIR_TAB_FORWARD.
1447  * @current_event_num and @current_span_num: current status.
1448  * @next_event_num: the event number focus should go next.
1449  *                  -1 indicates focus should go to week_view widget.
1450  * @next_span_num: always return 0.
1451  **/
1452 static gboolean
1453 e_week_view_get_next_tab_event (EWeekView *week_view,
1454                                 GtkDirectionType direction,
1455                                 gint current_event_num,
1456                                 gint current_span_num,
1457                                 gint *next_event_num,
1458                                 gint *next_span_num)
1459 {
1460 	gint event_num;
1461 
1462 	g_return_val_if_fail (week_view != NULL, FALSE);
1463 	g_return_val_if_fail (next_event_num != NULL, FALSE);
1464 	g_return_val_if_fail (next_span_num != NULL, FALSE);
1465 
1466 	if (week_view->events->len <= 0)
1467 		return FALSE;
1468 
1469 	/* we only tab through events not spans */
1470 	*next_span_num = 0;
1471 
1472 	switch (direction) {
1473 	case GTK_DIR_TAB_BACKWARD:
1474 		event_num = current_event_num - 1;
1475 		break;
1476 	case GTK_DIR_TAB_FORWARD:
1477 		event_num = current_event_num + 1;
1478 		break;
1479 	default:
1480 		return FALSE;
1481 	}
1482 
1483 	if (event_num == -1)
1484 		/* backward, out of event range, go to week view widget
1485 		 */
1486 		*next_event_num = -1;
1487 	else if (event_num < -1)
1488 		/* backward from week_view, go to the last event
1489 		 */
1490 		*next_event_num = week_view->events->len - 1;
1491 	else if (event_num >= week_view->events->len)
1492 		/* forward, out of event range, go to week view widget
1493 		 */
1494 		*next_event_num = -1;
1495 	else
1496 		*next_event_num = event_num;
1497 	return TRUE;
1498 }
1499 
1500 static gboolean
1501 e_week_view_focus (GtkWidget *widget,
1502                    GtkDirectionType direction)
1503 {
1504 	EWeekView *week_view;
1505 	gint new_event_num;
1506 	gint new_span_num;
1507 	gint event_loop;
1508 	gboolean editable = FALSE;
1509 	static gint last_focus_event_num = -1, last_focus_span_num = -1;
1510 
1511 	g_return_val_if_fail (widget != NULL, FALSE);
1512 	g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
1513 
1514 	week_view = E_WEEK_VIEW (widget);
1515 
1516 	e_week_view_check_layout (week_view);
1517 
1518 	if (week_view->focused_jump_button == E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS) {
1519 		last_focus_event_num = week_view->editing_event_num;
1520 		last_focus_span_num = week_view->editing_span_num;
1521 	}
1522 
1523 	/* if there is not event, just grab week_view */
1524 	if (week_view->events->len == 0) {
1525 		gtk_widget_grab_focus (widget);
1526 		return TRUE;
1527 	}
1528 
1529 	for (event_loop = 0; event_loop < week_view->events->len;
1530 	     ++event_loop) {
1531 		if (!e_week_view_get_next_tab_event (week_view, direction,
1532 						     last_focus_event_num,
1533 						     last_focus_span_num,
1534 						     &new_event_num,
1535 						     &new_span_num))
1536 			return FALSE;
1537 
1538 		if (new_event_num == -1) {
1539 			/* focus should go to week_view widget
1540 			 */
1541 			gtk_widget_grab_focus (widget);
1542 			return TRUE;
1543 		}
1544 
1545 		editable = e_week_view_start_editing_event (
1546 			week_view,
1547 			new_event_num,
1548 			new_span_num,
1549 			NULL);
1550 		last_focus_event_num = new_event_num;
1551 		last_focus_span_num = new_span_num;
1552 
1553 		if (editable)
1554 			break;
1555 		else {
1556 			/* check if we should go to the jump button */
1557 
1558 			EWeekViewEvent *event;
1559 			EWeekViewEventSpan *span;
1560 			gint current_day;
1561 
1562 			if (!is_array_index_in_bounds (week_view->events, new_event_num))
1563 				break;
1564 
1565 			event = &g_array_index (week_view->events,
1566 						EWeekViewEvent,
1567 						new_event_num);
1568 
1569 			if (!is_array_index_in_bounds (week_view->spans, event->spans_index + new_span_num))
1570 				break;
1571 
1572 			span = &g_array_index (week_view->spans,
1573 					       EWeekViewEventSpan,
1574 					       event->spans_index + new_span_num);
1575 			current_day = span->start_day;
1576 
1577 			if ((week_view->focused_jump_button != current_day) &&
1578 			    e_week_view_is_jump_button_visible (week_view, current_day)) {
1579 
1580 				/* focus go to the jump button */
1581 				e_week_view_stop_editing_event (week_view);
1582 				gnome_canvas_item_grab_focus (week_view->jump_buttons[current_day]);
1583 				return TRUE;
1584 			}
1585 		}
1586 	}
1587 	return editable;
1588 }
1589 
1590 /* Returns the currently-selected event, or NULL if none */
1591 static GList *
1592 e_week_view_get_selected_events (ECalendarView *cal_view)
1593 {
1594 	EWeekViewEvent *event = NULL;
1595 	GList *list = NULL;
1596 	EWeekView *week_view = (EWeekView *) cal_view;
1597 
1598 	g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), NULL);
1599 
1600 	if (week_view->editing_event_num != -1) {
1601 		if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
1602 			return NULL;
1603 
1604 		event = &g_array_index (week_view->events, EWeekViewEvent,
1605 					week_view->editing_event_num);
1606 	} else if (week_view->popup_event_num != -1) {
1607 		if (!is_array_index_in_bounds (week_view->events, week_view->popup_event_num))
1608 			return NULL;
1609 
1610 		event = &g_array_index (week_view->events, EWeekViewEvent,
1611 					week_view->popup_event_num);
1612 	}
1613 
1614 	if (event)
1615 		list = g_list_prepend (list, event);
1616 
1617 	return list;
1618 }
1619 
1620 /* Restarts a query for the week view */
1621 static void
1622 e_week_view_update_query (EWeekView *week_view)
1623 {
1624 	gint rows, r;
1625 
1626 	if (!E_CALENDAR_VIEW (week_view)->in_focus) {
1627 		e_week_view_free_events (week_view);
1628 		week_view->requires_update = TRUE;
1629 		return;
1630 	}
1631 
1632 	gtk_widget_queue_draw (week_view->main_canvas);
1633 	e_week_view_free_events (week_view);
1634 	e_week_view_queue_layout (week_view);
1635 
1636 	rows = e_table_model_row_count (E_TABLE_MODEL (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view))));
1637 	for (r = 0; r < rows; r++) {
1638 		ECalModelComponent *comp_data;
1639 
1640 		comp_data = e_cal_model_get_component_at (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), r);
1641 		if (comp_data == NULL) {
1642 			g_warning ("comp_data is NULL\n");
1643 			continue;
1644 		}
1645 		week_view_process_component (week_view, comp_data);
1646 	}
1647 }
1648 
1649 /* This sets the selected time range. The EWeekView will show the corresponding
1650  * month and the days between start_time and end_time will be selected.
1651  * To select a single day, use the same value for start_time & end_time. */
1652 static void
1653 e_week_view_set_selected_time_range (ECalendarView *cal_view,
1654                                      time_t start_time,
1655                                      time_t end_time)
1656 {
1657 	GDate date, end_date;
1658 	gint num_days;
1659 	gboolean update_adjustment_value = FALSE;
1660 	EWeekView *week_view = E_WEEK_VIEW (cal_view);
1661 
1662 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1663 
1664 	if (!g_date_valid (&week_view->base_date)) {
1665 		/* This view has not been initialized/shown yet, thus skip this. */
1666 		return;
1667 	}
1668 
1669 	time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1670 
1671 	/* Set the selection to the given days. */
1672 	week_view->selection_start_day = g_date_get_julian (&date)
1673 		- g_date_get_julian (&week_view->base_date);
1674 	if (end_time == start_time
1675 	    || end_time <= time_add_day_with_zone (start_time, 1,
1676 						   e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))))
1677 		week_view->selection_end_day = week_view->selection_start_day;
1678 	else {
1679 		time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1680 		week_view->selection_end_day = g_date_get_julian (&end_date)
1681 			- g_date_get_julian (&week_view->base_date);
1682 	}
1683 
1684 	/* Make sure the selection is valid. */
1685 	num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1686 	num_days--;
1687 	week_view->selection_start_day = CLAMP (
1688 		week_view->selection_start_day, 0, num_days);
1689 	week_view->selection_end_day = CLAMP (
1690 		week_view->selection_end_day,
1691 		week_view->selection_start_day,
1692 		num_days);
1693 
1694 	/* Reset the adjustment value to 0 if the base address has changed.
1695 	 * Note that we do this after updating first_day_shown so that our
1696 	 * signal handler will not try to reload the events. */
1697 	if (update_adjustment_value) {
1698 		GtkRange *range;
1699 		GtkAdjustment *adjustment;
1700 
1701 		range = GTK_RANGE (week_view->vscrollbar);
1702 		adjustment = gtk_range_get_adjustment (range);
1703 		gtk_adjustment_set_value (adjustment, 0);
1704 	}
1705 
1706 	gtk_widget_queue_draw (week_view->main_canvas);
1707 }
1708 
1709 void
1710 e_week_view_set_selected_time_range_visible (EWeekView *week_view,
1711                                              time_t start_time,
1712                                              time_t end_time)
1713 {
1714 	GDate date, end_date;
1715 	gint num_days;
1716 
1717 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1718 
1719 	time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1720 
1721 	/* Set the selection to the given days. */
1722 	week_view->selection_start_day = g_date_get_julian (&date)
1723 		- g_date_get_julian (&week_view->first_day_shown);
1724 	if (end_time == start_time
1725 	    || end_time <= time_add_day_with_zone (start_time, 1,
1726 						   e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))))
1727 		week_view->selection_end_day = week_view->selection_start_day;
1728 	else {
1729 		time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1730 		week_view->selection_end_day = g_date_get_julian (&end_date)
1731 			- g_date_get_julian (&week_view->first_day_shown);
1732 	}
1733 
1734 	/* Make sure the selection is valid. */
1735 	num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1736 	num_days--;
1737 	week_view->selection_start_day = CLAMP (
1738 		week_view->selection_start_day,
1739 						0, num_days);
1740 	week_view->selection_end_day = CLAMP (
1741 		week_view->selection_end_day,
1742 		week_view->selection_start_day,
1743 		num_days);
1744 
1745 	gtk_widget_queue_draw (week_view->main_canvas);
1746 }
1747 
1748 /* Returns the selected time range. */
1749 static gboolean
1750 e_week_view_get_selected_time_range (ECalendarView *cal_view,
1751                                      time_t *start_time,
1752                                      time_t *end_time)
1753 {
1754 	gint start_day, end_day;
1755 	EWeekView *week_view = E_WEEK_VIEW (cal_view);
1756 
1757 	start_day = week_view->selection_start_day;
1758 	end_day = week_view->selection_end_day;
1759 
1760 	if (start_day == -1) {
1761 		start_day = 0;
1762 		end_day = 0;
1763 	}
1764 
1765 	if (start_time)
1766 		*start_time = week_view->day_starts[start_day];
1767 
1768 	if (end_time)
1769 		*end_time = week_view->day_starts[end_day + 1];
1770 
1771 	return  TRUE;
1772 }
1773 
1774 /* Gets the visible time range. Returns FALSE if no time range has been set. */
1775 static gboolean
1776 e_week_view_get_visible_time_range (ECalendarView *cal_view,
1777                                     time_t *start_time,
1778                                     time_t *end_time)
1779 {
1780 	gint num_days;
1781 	EWeekView *week_view = E_WEEK_VIEW (cal_view);
1782 
1783 	/* If we don't have a valid date set yet, return FALSE. */
1784 	if (!g_date_valid (&week_view->first_day_shown))
1785 		return FALSE;
1786 
1787 	num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1788 	*start_time = week_view->day_starts[0];
1789 	*end_time = week_view->day_starts[num_days];
1790 
1791 	return TRUE;
1792 }
1793 
1794 /* Note that the returned date may be invalid if no date has been set yet. */
1795 void
1796 e_week_view_get_first_day_shown (EWeekView *week_view,
1797                                  GDate *date)
1798 {
1799 	*date = week_view->first_day_shown;
1800 }
1801 
1802 /* This sets the first day shown in the view. It will be rounded down to the
1803  * nearest week. */
1804 void
1805 e_week_view_set_first_day_shown (EWeekView *week_view,
1806                                  GDate *date)
1807 {
1808 	GDate base_date;
1809 	gint weekday, day_offset, num_days;
1810 	gboolean update_adjustment_value = FALSE;
1811 	guint32 old_selection_start_julian = 0, old_selection_end_julian = 0;
1812 	struct icaltimetype start_tt = icaltime_null_time ();
1813 	time_t start_time;
1814 
1815 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1816 
1817 	/* Calculate the old selection range. */
1818 	if (week_view->selection_start_day != -1) {
1819 		old_selection_start_julian =
1820 			g_date_get_julian (&week_view->base_date)
1821 			+ week_view->selection_start_day;
1822 		old_selection_end_julian =
1823 			g_date_get_julian (&week_view->base_date)
1824 			+ week_view->selection_end_day;
1825 	}
1826 
1827 	/* Calculate the weekday of the given date, 0 = Mon. */
1828 	weekday = g_date_get_weekday (date) - 1;
1829 
1830 	/* Convert it to an offset from the start of the display. */
1831 	day_offset = (weekday + 7 - week_view->display_start_day) % 7;
1832 
1833 	/* Calculate the base date, i.e. the first day shown when the
1834 	 * scrollbar adjustment value is 0. */
1835 	base_date = *date;
1836 	g_date_subtract_days (&base_date, day_offset);
1837 
1838 	/* See if we need to update the base date. */
1839 	if (!g_date_valid (&week_view->base_date)
1840 	    || g_date_compare (&week_view->base_date, &base_date)) {
1841 		week_view->base_date = base_date;
1842 		update_adjustment_value = TRUE;
1843 	}
1844 
1845 	/* See if we need to update the first day shown. */
1846 	if (!g_date_valid (&week_view->first_day_shown)
1847 	    || g_date_compare (&week_view->first_day_shown, &base_date)) {
1848 		week_view->first_day_shown = base_date;
1849 
1850 		start_tt.year = g_date_get_year (&base_date);
1851 		start_tt.month = g_date_get_month (&base_date);
1852 		start_tt.day = g_date_get_day (&base_date);
1853 
1854 		start_time = icaltime_as_timet_with_zone (
1855 			start_tt,
1856 			e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1857 
1858 		e_week_view_recalc_day_starts (week_view, start_time);
1859 		e_week_view_update_query (week_view);
1860 	}
1861 
1862 	/* Try to keep the previous selection, but if it is no longer shown
1863 	 * just select the first day. */
1864 	if (week_view->selection_start_day != -1) {
1865 		week_view->selection_start_day = old_selection_start_julian
1866 			- g_date_get_julian (&base_date);
1867 		week_view->selection_end_day = old_selection_end_julian
1868 			- g_date_get_julian (&base_date);
1869 
1870 		/* Make sure the selection is valid. */
1871 		num_days = week_view->multi_week_view
1872 			? week_view->weeks_shown * 7 : 7;
1873 		num_days--;
1874 		week_view->selection_start_day = CLAMP (
1875 			week_view->selection_start_day, 0, num_days);
1876 		week_view->selection_end_day = CLAMP (
1877 			week_view->selection_end_day,
1878 			week_view->selection_start_day,
1879 			num_days);
1880 	}
1881 
1882 	/* Reset the adjustment value to 0 if the base address has changed.
1883 	 * Note that we do this after updating first_day_shown so that our
1884 	 * signal handler will not try to reload the events. */
1885 	if (update_adjustment_value) {
1886 		GtkRange *range;
1887 		GtkAdjustment *adjustment;
1888 
1889 		range = GTK_RANGE (week_view->vscrollbar);
1890 		adjustment = gtk_range_get_adjustment (range);
1891 		gtk_adjustment_set_value (adjustment, 0);
1892 	}
1893 
1894 	e_week_view_update_query (week_view);
1895 	gtk_widget_queue_draw (week_view->main_canvas);
1896 }
1897 
1898 /* Recalculates the time_t corresponding to the start of each day. */
1899 static void
1900 e_week_view_recalc_day_starts (EWeekView *week_view,
1901                                time_t lower)
1902 {
1903 	gint num_days, day;
1904 	time_t tmp_time;
1905 
1906 	num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
1907 
1908 	tmp_time = lower;
1909 	week_view->day_starts[0] = tmp_time;
1910 	for (day = 1; day <= num_days; day++) {
1911 		tmp_time = time_add_day_with_zone (
1912 			tmp_time, 1,
1913 			e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
1914 		week_view->day_starts[day] = tmp_time;
1915 	}
1916 }
1917 
1918 gboolean
1919 e_week_view_get_multi_week_view (EWeekView *week_view)
1920 {
1921 	g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
1922 
1923 	return week_view->multi_week_view;
1924 }
1925 
1926 void
1927 e_week_view_set_multi_week_view (EWeekView *week_view,
1928                                  gboolean multi_week_view)
1929 {
1930 	GtkRange *range;
1931 	GtkAdjustment *adjustment;
1932 	gint page_increment, page_size;
1933 
1934 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1935 
1936 	if (week_view->multi_week_view == multi_week_view)
1937 		return;
1938 
1939 	week_view->multi_week_view = multi_week_view;
1940 
1941 	if (multi_week_view) {
1942 		gtk_widget_show (week_view->titles_canvas);
1943 		week_view->month_scroll_by_week = calendar_config_get_month_scroll_by_week ();
1944 
1945 		calendar_config_add_notification_month_scroll_by_week (month_scrol_by_week_changed_cb, week_view);
1946 
1947 		if (week_view->month_scroll_by_week) {
1948 			page_increment = 1;
1949 			page_size = 1;
1950 		} else {
1951 			page_increment = 4;
1952 			page_size = 5;
1953 		}
1954 	} else {
1955 		gtk_widget_hide (week_view->titles_canvas);
1956 		page_increment = page_size = 1;
1957 
1958 		if (week_view->scroll_by_week_notif_id) {
1959 			calendar_config_remove_notification (month_scrol_by_week_changed_cb, week_view);
1960 			week_view->scroll_by_week_notif_id = 0;
1961 		}
1962 	}
1963 
1964 	range = GTK_RANGE (week_view->vscrollbar);
1965 	adjustment = gtk_range_get_adjustment (range);
1966 	gtk_adjustment_set_page_increment (adjustment, page_increment);
1967 	gtk_adjustment_set_page_size (adjustment, page_size);
1968 
1969 	e_week_view_recalc_display_start_day (week_view);
1970 	e_week_view_recalc_cell_sizes (week_view);
1971 
1972 	if (g_date_valid (&week_view->first_day_shown))
1973 		e_week_view_set_first_day_shown (
1974 			week_view,
1975 			&week_view->first_day_shown);
1976 }
1977 
1978 gboolean
1979 e_week_view_get_update_base_date (EWeekView *week_view)
1980 {
1981 	g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
1982 
1983 	return week_view->update_base_date;
1984 }
1985 
1986 void
1987 e_week_view_set_update_base_date (EWeekView *week_view,
1988                                   gboolean update_base_date)
1989 {
1990 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
1991 
1992 	week_view->update_base_date = update_base_date;
1993 }
1994 
1995 gint
1996 e_week_view_get_weeks_shown (EWeekView *week_view)
1997 {
1998 	g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), 1);
1999 
2000 	return week_view->weeks_shown;
2001 }
2002 
2003 void
2004 e_week_view_set_weeks_shown (EWeekView *week_view,
2005                              gint weeks_shown)
2006 {
2007 	GtkRange *range;
2008 	GtkAdjustment *adjustment;
2009 	gint page_increment, page_size;
2010 
2011 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
2012 
2013 	weeks_shown = MIN (weeks_shown, E_WEEK_VIEW_MAX_WEEKS);
2014 
2015 	if (week_view->weeks_shown == weeks_shown)
2016 		return;
2017 
2018 	week_view->weeks_shown = weeks_shown;
2019 
2020 	if (week_view->multi_week_view) {
2021 		if (week_view->month_scroll_by_week) {
2022 			page_increment = 1;
2023 			page_size = 1;
2024 		} else {
2025 			page_increment = 4;
2026 			page_size = 5;
2027 		}
2028 
2029 		range = GTK_RANGE (week_view->vscrollbar);
2030 		adjustment = gtk_range_get_adjustment (range);
2031 		gtk_adjustment_set_page_increment (adjustment, page_increment);
2032 		gtk_adjustment_set_page_size (adjustment, page_size);
2033 
2034 		e_week_view_recalc_cell_sizes (week_view);
2035 
2036 		if (g_date_valid (&week_view->first_day_shown))
2037 			e_week_view_set_first_day_shown (week_view, &week_view->first_day_shown);
2038 
2039 		e_week_view_update_query (week_view);
2040 	}
2041 }
2042 
2043 gboolean
2044 e_week_view_get_compress_weekend (EWeekView *week_view)
2045 {
2046 	g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
2047 
2048 	return week_view->compress_weekend;
2049 }
2050 
2051 void
2052 e_week_view_set_compress_weekend (EWeekView *week_view,
2053                                   gboolean compress_weekend)
2054 {
2055 	gboolean need_reload = FALSE;
2056 
2057 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
2058 
2059 	if (week_view->compress_weekend == compress_weekend)
2060 		return;
2061 
2062 	week_view->compress_weekend = compress_weekend;
2063 
2064 	/* The option only affects the month view. */
2065 	if (!week_view->multi_week_view)
2066 		return;
2067 
2068 	e_week_view_recalc_cell_sizes (week_view);
2069 
2070 	need_reload = e_week_view_recalc_display_start_day (week_view);
2071 
2072 	/* If the display_start_day has changed we need to recalculate the
2073 	 * date range shown and reload all events, otherwise we only need to
2074 	 * do a reshape. */
2075 	if (need_reload) {
2076 		/* Recalculate the days shown and reload if necessary. */
2077 		if (g_date_valid (&week_view->first_day_shown))
2078 			e_week_view_set_first_day_shown (week_view, &week_view->first_day_shown);
2079 	} else {
2080 		week_view->events_need_reshape = TRUE;
2081 		e_week_view_check_layout (week_view);
2082 	}
2083 
2084 	gtk_widget_queue_draw (week_view->titles_canvas);
2085 	gtk_widget_queue_draw (week_view->main_canvas);
2086 
2087 	g_object_notify (G_OBJECT (week_view), "compress-weekend");
2088 }
2089 
2090 /* Whether we display event end times. */
2091 gboolean
2092 e_week_view_get_show_event_end_times (EWeekView *week_view)
2093 {
2094 	g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), TRUE);
2095 
2096 	return week_view->show_event_end_times;
2097 }
2098 
2099 void
2100 e_week_view_set_show_event_end_times (EWeekView *week_view,
2101                                       gboolean show_event_end_times)
2102 {
2103 	g_return_if_fail (E_IS_WEEK_VIEW (week_view));
2104 
2105 	if (week_view->show_event_end_times == show_event_end_times)
2106 		return;
2107 
2108 	week_view->show_event_end_times = show_event_end_times;
2109 	e_week_view_recalc_cell_sizes (week_view);
2110 	week_view->events_need_reshape = TRUE;
2111 	e_week_view_check_layout (week_view);
2112 
2113 	gtk_widget_queue_draw (week_view->titles_canvas);
2114 	gtk_widget_queue_draw (week_view->main_canvas);
2115 
2116 	g_object_notify (G_OBJECT (week_view), "show-event-end-times");
2117 }
2118 
2119 static gboolean
2120 e_week_view_recalc_display_start_day (EWeekView *week_view)
2121 {
2122 	ECalModel *model;
2123 	gint week_start_day;
2124 	gint display_start_day;
2125 
2126 	model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
2127 	week_start_day = e_cal_model_get_week_start_day (model);
2128 
2129 	/* The display start day defaults to week_start_day, but we have
2130 	 * to use Saturday if the weekend is compressed and week_start_day
2131 	 * is Sunday. */
2132 	display_start_day = week_start_day;
2133 
2134 	if (display_start_day == 6
2135 	    && (!week_view->multi_week_view || week_view->compress_weekend))
2136 		display_start_day = 5;
2137 
2138 	if (week_view->display_start_day != display_start_day) {
2139 		week_view->display_start_day = display_start_day;
2140 		return TRUE;
2141 	}
2142 
2143 	return FALSE;
2144 }
2145 
2146 /* Checks if the users participation status is NEEDS-ACTION and shows the summary as bold text */
2147 static void
2148 set_text_as_bold (EWeekViewEvent *event,
2149                   EWeekViewEventSpan *span,
2150                   ESourceRegistry *registry)
2151 {
2152 	ECalComponent *comp;
2153 	GSList *attendees = NULL, *l;
2154 	gchar *address;
2155 	ECalComponentAttendee *at = NULL;
2156 
2157 	if (!is_comp_data_valid (event))
2158 		return;
2159 
2160 	comp = e_cal_component_new ();
2161 	e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
2162 	address = itip_get_comp_attendee (
2163 		registry, comp, event->comp_data->client);
2164 	e_cal_component_get_attendee_list (comp, &attendees);
2165 	for (l = attendees; l; l = l->next) {
2166 		ECalComponentAttendee *attendee = l->data;
2167 
2168 		if ((g_str_equal (itip_strip_mailto (attendee->value), address))
2169 		 || (attendee->sentby && g_str_equal (itip_strip_mailto (attendee->sentby), address))) {
2170 			at = attendee;
2171 			break;
2172 		}
2173 	}
2174 
2175 	/* The attendee has not yet accepted the meeting, display the summary as bolded.
2176 	 * If the attendee is not present, it might have come through a mailing list.
2177 	 * In that case, we never show the meeting as bold even if it is unaccepted. */
2178 	if (at && (at->status == ICAL_PARTSTAT_NEEDSACTION))
2179 		gnome_canvas_item_set (span->text_item, "bold", TRUE, NULL);
2180 
2181 	e_cal_component_free_attendee_list (attendees);
2182 	g_free (address);
2183 	g_object_unref (comp);
2184 }
2185 
2186 /* This calls a given function for each event instance that matches the given
2187  * uid. Note that it is safe for the callback to remove the event (since we
2188  * step backwards through the arrays). */
2189 static void
2190 e_week_view_foreach_event_with_uid (EWeekView *week_view,
2191                                     const gchar *uid,
2192                                     EWeekViewForeachEventCallback callback,
2193                                     gpointer data)
2194 {
2195 	EWeekViewEvent *event;
2196 	gint event_num;
2197 
2198 	for (event_num = week_view->events->len - 1;
2199 	     event_num >= 0;
2200 	     event_num--) {
2201 		const gchar *u;
2202 
2203 		event = &g_array_index (week_view->events, EWeekViewEvent,
2204 					event_num);
2205 
2206 		if (!is_comp_data_valid (event))
2207 			continue;
2208 
2209 		u = icalcomponent_get_uid (event->comp_data->icalcomp);
2210 		if (u && !strcmp (uid, u)) {
2211 			if (!(*callback) (week_view, event_num, data))
2212 				return;
2213 		}
2214 	}
2215 }
2216 
2217 static gboolean
2218 e_week_view_remove_event_cb (EWeekView *week_view,
2219                              gint event_num,
2220                              gpointer data)
2221 {
2222 	EWeekViewEvent *event;
2223 	EWeekViewEventSpan *span;
2224 	gint span_num;
2225 
2226 	if (!is_array_index_in_bounds (week_view->events, event_num))
2227 		return TRUE;
2228 
2229 	event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
2230 	if (!event)
2231 		return TRUE;
2232 
2233 	/* If we were editing this event, set editing_event_num to -1 so
2234 	 * on_editing_stopped doesn't try to update the event. */
2235 	if (week_view->editing_event_num == event_num)
2236 		week_view->editing_event_num = -1;
2237 
2238 	if (week_view->popup_event_num == event_num)
2239 		week_view->popup_event_num = -1;
2240 
2241 	if (is_comp_data_valid (event))
2242 		g_object_unref (event->comp_data);
2243 	event->comp_data = NULL;
2244 
2245 	if (week_view->spans) {
2246 		/* We leave the span elements in the array, but set the canvas item
2247 		 * pointers to NULL. */
2248 		for (span_num = 0; span_num < event->num_spans; span_num++) {
2249 			if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
2250 				break;
2251 
2252 			span = &g_array_index (week_view->spans, EWeekViewEventSpan,
2253 					       event->spans_index + span_num);
2254 
2255 			if (span->text_item) {
2256 				g_object_run_dispose (G_OBJECT (span->text_item));
2257 				span->text_item = NULL;
2258 			}
2259 			if (span->background_item) {
2260 				g_object_run_dispose (G_OBJECT (span->background_item));
2261 				span->background_item = NULL;
2262 			}
2263 		}
2264 
2265 		/* Update event_num numbers for already created spans with event_num higher than our event_num */
2266 		for (span_num = 0; span_num < week_view->spans->len; span_num++) {
2267 			span = &g_array_index (week_view->spans, EWeekViewEventSpan, span_num);
2268 
2269 			if (span && span->background_item && E_IS_WEEK_VIEW_EVENT_ITEM (span->background_item)) {
2270 				EWeekViewEventItem *wveitem = E_WEEK_VIEW_EVENT_ITEM (span->background_item);
2271 				gint wveitem_event_num;
2272 
2273 				wveitem_event_num =
2274 					e_week_view_event_item_get_event_num (wveitem);
2275 
2276 				if (wveitem_event_num > event_num)
2277 					e_week_view_event_item_set_event_num (
2278 						wveitem, wveitem_event_num - 1);
2279 			}
2280 		}
2281 	}
2282 
2283 	g_array_remove_index (week_view->events, event_num);
2284 
2285 	week_view->events_need_layout = TRUE;
2286 
2287 	return TRUE;
2288 }
2289 
2290 void
2291 e_week_view_get_day_position (EWeekView *week_view,
2292                               gint day,
2293                               gint *day_x,
2294                               gint *day_y,
2295                               gint *day_w,
2296                               gint *day_h)
2297 {
2298 	gint cell_x, cell_y, cell_h;
2299 
2300 	e_week_view_layout_get_day_position (
2301 		day,
2302 		week_view->multi_week_view,
2303 		week_view->weeks_shown,
2304 		week_view->display_start_day,
2305 		week_view->compress_weekend,
2306 		&cell_x, &cell_y, &cell_h);
2307 
2308 	*day_x = week_view->col_offsets[cell_x];
2309 	*day_y = week_view->row_offsets[cell_y];
2310 
2311 	*day_w = week_view->col_widths[cell_x];
2312 	*day_h = week_view->row_heights[cell_y];
2313 
2314 	while (cell_h > 1) {
2315 		*day_h += week_view->row_heights[cell_y + 1];
2316 		cell_h--;
2317 		cell_y++;
2318 	}
2319 }
2320 
2321 /* Returns the bounding box for a span of an event. Usually this can easily
2322  * be determined by the start & end days and row of the span, which are set in
2323  * e_week_view_layout_event (). Though we need a special case for the weekends
2324  * when they are compressed, since the span may not fit.
2325  * The bounding box includes the entire width of the days in the view (but
2326  * not the vertical line down the right of the last day), though the displayed
2327  * event doesn't normally extend to the edges of the day.
2328  * It returns FALSE if the span isn't visible. */
2329 gboolean
2330 e_week_view_get_span_position (EWeekView *week_view,
2331                                gint event_num,
2332                                gint span_num,
2333                                gint *span_x,
2334                                gint *span_y,
2335                                gint *span_w)
2336 {
2337 	EWeekViewEvent *event;
2338 	EWeekViewEventSpan *span;
2339 	gint num_days;
2340 	gint start_x, start_y, start_w, start_h;
2341 	gint end_x, end_y, end_w, end_h;
2342 
2343 	g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
2344 	g_return_val_if_fail (event_num < week_view->events->len, FALSE);
2345 
2346 	event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
2347 
2348 	g_return_val_if_fail (span_num < event->num_spans, FALSE);
2349 
2350 	if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
2351 		return FALSE;
2352 
2353 	span = &g_array_index (week_view->spans, EWeekViewEventSpan,
2354 			       event->spans_index + span_num);
2355 
2356 	if (!e_week_view_layout_get_span_position (event, span,
2357 						   week_view->rows_per_cell,
2358 						   week_view->rows_per_compressed_cell,
2359 						   week_view->display_start_day,
2360 						   week_view->multi_week_view,
2361 						   week_view->compress_weekend,
2362 						   &num_days)) {
2363 		return FALSE;
2364 	}
2365 
2366 	e_week_view_get_day_position (
2367 		week_view, span->start_day,
2368 		&start_x, &start_y, &start_w, &start_h);
2369 	*span_y = start_y + week_view->events_y_offset
2370 		+ span->row * (week_view->row_height
2371 			       + E_WEEK_VIEW_EVENT_Y_SPACING);
2372 	if (num_days == 1) {
2373 		*span_x = start_x;
2374 		*span_w = start_w - 1;
2375 	} else {
2376 		e_week_view_get_day_position (
2377 			week_view,
2378 			span->start_day + num_days - 1,
2379 			&end_x, &end_y, &end_w, &end_h);
2380 		*span_x = start_x;
2381 		*span_w = end_x + end_w - start_x - 1;
2382 	}
2383 
2384 	return TRUE;
2385 }
2386 
2387 static gboolean
2388 ewv_pass_gdkevent_to_etext (EWeekView *week_view,
2389                             GdkEvent *gevent)
2390 {
2391 	g_return_val_if_fail (week_view != NULL, FALSE);
2392 	g_return_val_if_fail (gevent != NULL, FALSE);
2393 
2394 	if (week_view->editing_event_num != -1 && week_view->editing_span_num != -1) {
2395 		EWeekViewEvent *event;
2396 		EWeekViewEventSpan *span;
2397 
2398 		if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
2399 			return FALSE;
2400 
2401 		event = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num);
2402 
2403 		if (!is_array_index_in_bounds (week_view->spans, event->spans_index + week_view->editing_span_num))
2404 			return FALSE;
2405 
2406 		span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + week_view->editing_span_num);
2407 
2408 		if (span->text_item && E_IS_TEXT (span->text_item)) {
2409 			GNOME_CANVAS_ITEM_GET_CLASS (span->text_item)->event (span->text_item, gevent);
2410 			return TRUE;
2411 		}
2412 	}
2413 
2414 	return FALSE;
2415 }
2416 
2417 static gboolean
2418 e_week_view_on_button_press (GtkWidget *widget,
2419                              GdkEventButton *event,
2420                              EWeekView *week_view)
2421 {
2422 	gint x, y, day;
2423 
2424 #if 0
2425 	g_print ("In e_week_view_on_button_press\n");
2426 	if (event->type == GDK_2BUTTON_PRESS)
2427 		g_print (" is a double-click\n");
2428 	if (week_view->pressed_event_num != -1)
2429 		g_print (" item is pressed\n");
2430 #endif
2431 
2432 	/* Convert the mouse position to a week & day. */
2433 	x = event->x;
2434 	y = event->y;
2435 	day = e_week_view_convert_position_to_day (week_view, x, y);
2436 	if (day == -1)
2437 		return FALSE;
2438 
2439 	if (ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) event))
2440 		return TRUE;
2441 
2442 	/* If an event is pressed just return. */
2443 	if (week_view->pressed_event_num != -1)
2444 		return FALSE;
2445 
2446 	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
2447 		time_t dtstart, dtend;
2448 
2449 		e_calendar_view_get_selected_time_range ((ECalendarView *) week_view, &dtstart, &dtend);
2450 		if (dtstart < week_view->before_click_dtend && dtend > week_view->before_click_dtstart) {
2451 			e_week_view_set_selected_time_range ((ECalendarView *) week_view, week_view->before_click_dtstart, week_view->before_click_dtend);
2452 		}
2453 		e_calendar_view_new_appointment_full (E_CALENDAR_VIEW (week_view), FALSE, FALSE, calendar_config_get_prefer_meeting ());
2454 		return TRUE;
2455 	}
2456 
2457 	if (event->button == 1) {
2458 		GdkWindow *window;
2459 
2460 		/* Start the selection drag. */
2461 		if (!gtk_widget_has_focus (GTK_WIDGET (week_view)) &&  !gtk_widget_has_focus (GTK_WIDGET (week_view->main_canvas)))
2462 			gtk_widget_grab_focus (GTK_WIDGET (week_view));
2463 
2464 		window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
2465 
2466 		if (gdk_pointer_grab (window, FALSE,
2467 				      GDK_POINTER_MOTION_MASK
2468 				      | GDK_BUTTON_RELEASE_MASK,
2469 				      NULL, NULL, event->time) == 0) {
2470 			if (event->time - week_view->bc_event_time > 250)
2471 				e_calendar_view_get_selected_time_range ((ECalendarView *) week_view, &week_view->before_click_dtstart, &week_view->before_click_dtend);
2472 			week_view->bc_event_time = event->time;
2473 			week_view->selection_start_day = day;
2474 			week_view->selection_end_day = day;
2475 			week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END;
2476 			g_signal_emit_by_name (week_view, "selected_time_changed");
2477 
2478 			/* FIXME: Optimise? */
2479 			gtk_widget_queue_draw (week_view->main_canvas);
2480 		}
2481 	} else if (event->button == 3) {
2482 		if (!gtk_widget_has_focus (GTK_WIDGET (week_view)))
2483 			gtk_widget_grab_focus (GTK_WIDGET (week_view));
2484 
2485 		if (day < week_view->selection_start_day || day > week_view->selection_end_day) {
2486 			week_view->selection_start_day = day;
2487 			week_view->selection_end_day = day;
2488 			week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;
2489 
2490 			/* FIXME: Optimise? */
2491 			gtk_widget_queue_draw (week_view->main_canvas);
2492 		}
2493 
2494 		e_week_view_show_popup_menu (week_view, event, -1);
2495 	}
2496 
2497 	return TRUE;
2498 }
2499 
2500 static gboolean
2501 e_week_view_on_button_release (GtkWidget *widget,
2502                                GdkEventButton *event,
2503                                EWeekView *week_view)
2504 {
2505 #if 0
2506 	g_print ("In e_week_view_on_button_release\n");
2507 #endif
2508 
2509 	if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) {
2510 		week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;
2511 		gdk_pointer_ungrab (event->time);
2512 	} else {
2513 		ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) event);
2514 	}
2515 
2516 	return FALSE;
2517 }
2518 
2519 static gboolean
2520 e_week_view_on_scroll (GtkWidget *widget,
2521                        GdkEventScroll *scroll,
2522                        EWeekView *week_view)
2523 {
2524 	GtkRange *range;
2525 	GtkAdjustment *adjustment;
2526 	gdouble page_increment;
2527 	gdouble new_value;
2528 	gdouble page_size;
2529 	gdouble lower;
2530 	gdouble upper;
2531 	gdouble value;
2532 	GtkWidget *tool_window = g_object_get_data (G_OBJECT (week_view), "tooltip-window");
2533 	guint timeout;
2534 
2535 	timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout"));
2536 	if (timeout) {
2537 		g_source_remove (timeout);
2538 		g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL);
2539 	}
2540 
2541 	if (tool_window) {
2542 		gtk_widget_destroy (tool_window);
2543 		g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL);
2544 	}
2545 
2546 	range = GTK_RANGE (week_view->vscrollbar);
2547 	adjustment = gtk_range_get_adjustment (range);
2548 
2549 	page_increment = gtk_adjustment_get_page_increment (adjustment);
2550 	page_size = gtk_adjustment_get_page_size (adjustment);
2551 	lower = gtk_adjustment_get_lower (adjustment);
2552 	upper = gtk_adjustment_get_upper (adjustment);
2553 	value = gtk_adjustment_get_value (adjustment);
2554 
2555 	switch (scroll->direction) {
2556 		case GDK_SCROLL_UP:
2557 			new_value = value - page_increment;
2558 			break;
2559 		case GDK_SCROLL_DOWN:
2560 			new_value = value + page_increment;
2561 			break;
2562 		#if GTK_CHECK_VERSION(3,3,18)
2563 		case GDK_SCROLL_SMOOTH:
2564 			if (scroll->delta_y < -0.001 || scroll->delta_y > 0.001) {
2565 				new_value = value + scroll->delta_y * page_increment;
2566 				break;
2567 			}
2568 			return FALSE;
2569 		#endif
2570 		default:
2571 			return FALSE;
2572 	}
2573 
2574 	new_value = CLAMP (new_value, lower, upper - page_size);
2575 	gtk_adjustment_set_value (adjustment, new_value);
2576 
2577 	return TRUE;
2578 }
2579 
2580 static gboolean
2581 e_week_view_on_motion (GtkWidget *widget,
2582                        GdkEventMotion *mevent,
2583                        EWeekView *week_view)
2584 {
2585 	gint x, y, day;
2586 
2587 #if 0
2588 	g_print ("In e_week_view_on_motion\n");
2589 #endif
2590 
2591 	/* Convert the mouse position to a week & day. */
2592 	x = mevent->x;
2593 	y = mevent->y;
2594 	day = e_week_view_convert_position_to_day (week_view, x, y);
2595 	if (day == -1)
2596 		return FALSE;
2597 
2598 	if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) {
2599 		e_week_view_update_selection (week_view, day);
2600 		return TRUE;
2601 	}
2602 
2603 	ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) mevent);
2604 
2605 	return FALSE;
2606 }
2607 
2608 /* Converts a position in the canvas window to a day offset from the first
2609  * day displayed. Returns -1 if the position is outside the grid. */
2610 static gint
2611 e_week_view_convert_position_to_day (EWeekView *week_view,
2612                                      gint x,
2613                                      gint y)
2614 {
2615 	gint col, row, grid_x = -1, grid_y = -1, week, day;
2616 	gint weekend_col;
2617 
2618 	/* First we convert it to a grid position. */
2619 	for (col = 0; col <= week_view->columns; col++) {
2620 		if (x < week_view->col_offsets[col]) {
2621 			grid_x = col - 1;
2622 			break;
2623 		}
2624 	}
2625 
2626 	for (row = 0; row <= week_view->rows; row++) {
2627 		if (y < week_view->row_offsets[row]) {
2628 			grid_y = row - 1;
2629 			break;
2630 		}
2631 	}
2632 
2633 	/* If the mouse is outside the grid return FALSE. */
2634 	if (grid_x == -1 || grid_y == -1)
2635 		return -1;
2636 
2637 	/* Now convert the grid position to a week and day. */
2638 	if (week_view->multi_week_view) {
2639 		week = grid_y / 2;
2640 		day = grid_x;
2641 
2642 		if (week_view->compress_weekend) {
2643 			weekend_col = (5 + 7 - week_view->display_start_day) % 7;
2644 			if (grid_x > weekend_col
2645 			    || (grid_x == weekend_col && grid_y % 2 == 1))
2646 				day++;
2647 		}
2648 	} else {
2649 		week = 0;
2650 
2651 		for (day = 0; day < 7; day++) {
2652 			gint day_x = 0, day_y = 0, rows = 0;
2653 			e_week_view_layout_get_day_position (
2654 				day, FALSE, 1,
2655 				week_view->display_start_day,
2656 				week_view->compress_weekend,
2657 				&day_x, &day_y, &rows);
2658 
2659 			if (grid_x == day_x && grid_y >= day_y && grid_y < day_y + rows)
2660 				break;
2661 		}
2662 
2663 		if (day == 7)
2664 			return -1;
2665 	}
2666 
2667 	return week * 7 + day;
2668 }
2669 
2670 static void
2671 e_week_view_update_selection (EWeekView *week_view,
2672                               gint day)
2673 {
2674 	gint tmp_day;
2675 	gboolean need_redraw = FALSE;
2676 
2677 #if 0
2678 	g_print ("Updating selection %i,%i\n", week, day);
2679 #endif
2680 
2681 	if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START) {
2682 		if (day != week_view->selection_start_day) {
2683 			need_redraw = TRUE;
2684 			week_view->selection_start_day = day;
2685 		}
2686 	} else {
2687 		if (day != week_view->selection_end_day) {
2688 			need_redraw = TRUE;
2689 			week_view->selection_end_day = day;
2690 		}
2691 	}
2692 
2693 	/* Switch the drag position if necessary. */
2694 	if (week_view->selection_start_day > week_view->selection_end_day) {
2695 		tmp_day = week_view->selection_start_day;
2696 		week_view->selection_start_day = week_view->selection_end_day;
2697 		week_view->selection_end_day = tmp_day;
2698 		if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START)
2699 			week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END;
2700 		else
2701 			week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_START;
2702 	}
2703 
2704 	/* FIXME: Optimise? */
2705 	if (need_redraw) {
2706 		gtk_widget_queue_draw (week_view->main_canvas);
2707 	}
2708 }
2709 
2710 static void
2711 e_week_view_free_events (EWeekView *week_view)
2712 {
2713 	EWeekViewEvent *event;
2714 	EWeekViewEventSpan *span;
2715 	gint event_num, span_num, num_days, day;
2716 
2717 	/* Reset all our indices. */
2718 	week_view->pressed_event_num = -1;
2719 	week_view->pressed_span_num = -1;
2720 	week_view->editing_event_num = -1;
2721 	week_view->editing_span_num = -1;
2722 	week_view->popup_event_num = -1;
2723 
2724 	for (event_num = 0; event_num < week_view->events->len; event_num++) {
2725 		event = &g_array_index (week_view->events, EWeekViewEvent,
2726 					event_num);
2727 
2728 		if (is_comp_data_valid (event))
2729 			g_object_unref (event->comp_data);
2730 	}
2731 
2732 	g_array_set_size (week_view->events, 0);
2733 
2734 	/* Destroy all the old canvas items. */
2735 	if (week_view->spans) {
2736 		for (span_num = 0; span_num < week_view->spans->len;
2737 		     span_num++) {
2738 			span = &g_array_index (week_view->spans,
2739 					       EWeekViewEventSpan, span_num);
2740 			if (span->background_item)
2741 				g_object_run_dispose (G_OBJECT (span->background_item));
2742 			if (span->text_item)
2743 				g_object_run_dispose (G_OBJECT (span->text_item));
2744 		}
2745 		g_array_free (week_view->spans, TRUE);
2746 		week_view->spans = NULL;
2747 	}
2748 
2749 	/* Clear the number of rows used per day. */
2750 	num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
2751 	for (day = 0; day <= num_days; day++) {
2752 		week_view->rows_per_day[day] = 0;
2753 	}
2754 
2755 	/* Hide all the jump buttons. */
2756 	for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) {
2757 		gnome_canvas_item_hide (week_view->jump_buttons[day]);
2758 	}
2759 }
2760 
2761 /* This adds one event to the view, adding it to the appropriate array. */
2762 static gboolean
2763 e_week_view_add_event (ECalComponent *comp,
2764                        time_t start,
2765                        time_t end,
2766                        gboolean prepend,
2767                        gpointer data)
2768 
2769 {
2770 	AddEventData *add_event_data;
2771 	EWeekViewEvent event;
2772 	gint num_days;
2773 	struct icaltimetype start_tt, end_tt;
2774 
2775 	add_event_data = data;
2776 
2777 	/* Check that the event times are valid. */
2778 	num_days = add_event_data->week_view->multi_week_view ? add_event_data->week_view->weeks_shown * 7 : 7;
2779 
2780 	/*if (start > end || start >= add_event_data->week_view->day_starts[num_days] || end <= add_event_data->week_view->day_starts[0]) {
2781 		g_print ("%s: week_view:%p\n", G_STRFUNC, add_event_data->week_view);
2782 		g_print ("\tstart: %s", ctime (&start));
2783 		g_print ("\tend: %s", ctime (&end));
2784 		g_print ("\tday_starts[0]: %s", ctime (&add_event_data->week_view->day_starts[0]));
2785 		g_print ("\tday_starts[%d]: %s\n", num_days, ctime (&add_event_data->week_view->day_starts[num_days]));
2786 	}*/
2787 
2788 	g_return_val_if_fail (start <= end, TRUE);
2789 	g_return_val_if_fail (start < add_event_data->week_view->day_starts[num_days], TRUE);
2790 	g_return_val_if_fail (end > add_event_data->week_view->day_starts[0], TRUE);
2791 
2792 	start_tt = icaltime_from_timet_with_zone (
2793 		start, FALSE,
2794 		e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view)));
2795 	end_tt = icaltime_from_timet_with_zone (
2796 		end, FALSE,
2797 						e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view)));
2798 
2799 	if (add_event_data->comp_data) {
2800 		event.comp_data = g_object_ref (add_event_data->comp_data);
2801 	} else {
2802 		event.comp_data = g_object_new (E_TYPE_CAL_MODEL_COMPONENT, NULL);
2803 
2804 		event.comp_data->client = g_object_ref (e_cal_model_get_default_client (e_calendar_view_get_model (E_CALENDAR_VIEW (add_event_data->week_view))));
2805 		e_cal_component_abort_sequence (comp);
2806 		event.comp_data->icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2807 	}
2808 	event.start = start;
2809 	event.end = end;
2810 	event.tooltip = NULL;
2811 	event.timeout = -1;
2812 	event.color = NULL;
2813 	event.spans_index = 0;
2814 	event.num_spans = 0;
2815 	event.comp_data->instance_start = start;
2816 	event.comp_data->instance_end = end;
2817 
2818 	event.start_minute = start_tt.hour * 60 + start_tt.minute;
2819 	event.end_minute = end_tt.hour * 60 + end_tt.minute;
2820 	if (event.end_minute == 0 && start != end)
2821 		event.end_minute = 24 * 60;
2822 
2823 	event.different_timezone = FALSE;
2824 	if (!cal_comp_util_compare_event_timezones (
2825 		    comp,
2826 		    event.comp_data->client,
2827 		    e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view))))
2828 		event.different_timezone = TRUE;
2829 
2830 	if (prepend)
2831 		g_array_prepend_val (add_event_data->week_view->events, event);
2832 	else
2833 		g_array_append_val (add_event_data->week_view->events, event);
2834 	add_event_data->week_view->events_sorted = FALSE;
2835 	add_event_data->week_view->events_need_layout = TRUE;
2836 
2837 	return TRUE;
2838 }
2839 
2840 /* This lays out the events, or reshapes them, as necessary. */
2841 static void
2842 e_week_view_check_layout (EWeekView *week_view)
2843 {
2844 	/* Don't bother if we aren't visible. */
2845 	if (!E_CALENDAR_VIEW (week_view)->in_focus) {
2846 		e_week_view_free_events (week_view);
2847 		week_view->requires_update = TRUE;
2848 		return;
2849 	}
2850 
2851 	/* Make sure the events are sorted (by start and size). */
2852 	e_week_view_ensure_events_sorted (week_view);
2853 
2854 	if (week_view->events_need_layout)
2855 		week_view->spans = e_week_view_layout_events
2856 			(week_view->events, week_view->spans,
2857 			 week_view->multi_week_view,
2858 			 week_view->weeks_shown,
2859 			 week_view->compress_weekend,
2860 			 week_view->display_start_day,
2861 			 week_view->day_starts,
2862 			 week_view->rows_per_day);
2863 
2864 	if (week_view->events_need_layout || week_view->events_need_reshape)
2865 		e_week_view_reshape_events (week_view);
2866 
2867 	week_view->events_need_layout = FALSE;
2868 	week_view->events_need_reshape = FALSE;
2869 }
2870 
2871 static void
2872 e_week_view_ensure_events_sorted (EWeekView *week_view)
2873 {
2874 	if (!week_view->events_sorted) {
2875 		qsort (
2876 			week_view->events->data,
2877 			week_view->events->len,
2878 			sizeof (EWeekViewEvent),
2879 			e_week_view_event_sort_func);
2880 		week_view->events_sorted = TRUE;
2881 	}
2882 }
2883 
2884 gint
2885 e_week_view_event_sort_func (gconstpointer arg1,
2886                              gconstpointer arg2)
2887 {
2888 	EWeekViewEvent *event1, *event2;
2889 
2890 	event1 = (EWeekViewEvent *) arg1;
2891 	event2 = (EWeekViewEvent *) arg2;
2892 
2893 	if (event1->start < event2->start)
2894 		return -1;
2895 	if (event1->start > event2->start)
2896 		return 1;
2897 
2898 	if (event1->end > event2->end)
2899 		return -1;
2900 	if (event1->end < event2->end)
2901 		return 1;
2902 
2903 	return 0;
2904 }
2905 
2906 static void
2907 e_week_view_reshape_events (EWeekView *week_view)
2908 {
2909 	EWeekViewEvent *event;
2910 	gint event_num, span_num;
2911 	gint num_days, day, day_x, day_y, day_w, day_h, max_rows;
2912 	gboolean is_weekend;
2913 
2914 	for (event_num = 0; event_num < week_view->events->len; event_num++) {
2915 		event = &g_array_index (week_view->events, EWeekViewEvent,
2916 					event_num);
2917 		if (!is_comp_data_valid (event))
2918 			continue;
2919 
2920 		for (span_num = 0; span_num < event->num_spans; span_num++) {
2921 			gchar *current_comp_string;
2922 
2923 			e_week_view_reshape_event_span (
2924 				week_view, event_num, span_num);
2925 			if (week_view->last_edited_comp_string == NULL)
2926 				continue;
2927 			current_comp_string = icalcomponent_as_ical_string_r (event->comp_data->icalcomp);
2928 			if (strncmp (current_comp_string, week_view->last_edited_comp_string,50) == 0) {
2929 				EWeekViewEventSpan *span;
2930 
2931 				if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) {
2932 					g_free (current_comp_string);
2933 					continue;
2934 				}
2935 
2936 				span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num);
2937 				e_canvas_item_grab_focus (span->text_item, TRUE);
2938 				g_free (week_view->last_edited_comp_string);
2939 				week_view->last_edited_comp_string = NULL;
2940 			}
2941 			g_free (current_comp_string);
2942 		}
2943 	}
2944 
2945 	/* Reshape the jump buttons and show/hide them as appropriate. */
2946 	num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
2947 	for (day = 0; day < num_days; day++) {
2948 
2949 		is_weekend = ((week_view->display_start_day + day) % 7 >= 5) ? TRUE : FALSE;
2950 		if (!is_weekend || (week_view->multi_week_view
2951 				    && !week_view->compress_weekend))
2952 			max_rows = week_view->rows_per_cell;
2953 		else
2954 			max_rows = week_view->rows_per_compressed_cell;
2955 
2956 		/* Determine whether the jump button should be shown. */
2957 		if (week_view->rows_per_day[day] <= max_rows) {
2958 			gnome_canvas_item_hide (week_view->jump_buttons[day]);
2959 		} else {
2960 			cairo_matrix_t matrix;
2961 
2962 			e_week_view_get_day_position (
2963 				week_view, day,
2964 				&day_x, &day_y,
2965 				&day_w, &day_h);
2966 
2967 			cairo_matrix_init_translate (
2968 				&matrix,
2969 				day_x + day_w - E_WEEK_VIEW_JUMP_BUTTON_X_PAD - E_WEEK_VIEW_JUMP_BUTTON_WIDTH,
2970 				day_y + day_h - E_WEEK_VIEW_JUMP_BUTTON_Y_PAD - E_WEEK_VIEW_JUMP_BUTTON_HEIGHT);
2971 			gnome_canvas_item_set_matrix (week_view->jump_buttons[day], &matrix);
2972 
2973 			gnome_canvas_item_show (week_view->jump_buttons[day]);
2974 			gnome_canvas_item_raise_to_top (week_view->jump_buttons[day]);
2975 		}
2976 	}
2977 
2978 	for (day = num_days; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) {
2979 		gnome_canvas_item_hide (week_view->jump_buttons[day]);
2980 	}
2981 }
2982 
2983 static EWeekViewEvent *
2984 tooltip_get_view_event (EWeekView *week_view,
2985                         gint day,
2986                         gint event_num)
2987 {
2988 	EWeekViewEvent *pevent;
2989 
2990 	if (!is_array_index_in_bounds (week_view->events, event_num))
2991 		return NULL;
2992 
2993 	pevent = &g_array_index (week_view->events, EWeekViewEvent, event_num);
2994 
2995 	return pevent;
2996 }
2997 
2998 static void
2999 tooltip_destroy (EWeekView *week_view,
3000                  GnomeCanvasItem *item)
3001 {
3002 	gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
3003 	EWeekViewEvent *pevent;
3004 	guint timeout;
3005 
3006 	timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout"));
3007 	if (timeout) {
3008 		g_source_remove (timeout);
3009 		g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL);
3010 	}
3011 
3012 	pevent = tooltip_get_view_event (week_view, -1, event_num);
3013 	if (pevent) {
3014 		if (pevent->tooltip && g_object_get_data (G_OBJECT (week_view), "tooltip-window")) {
3015 			gtk_widget_destroy (pevent->tooltip);
3016 			pevent->tooltip = NULL;
3017 		}
3018 
3019 		g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL);
3020 	}
3021 }
3022 
3023 static gboolean
3024 tooltip_event_cb (GnomeCanvasItem *item,
3025                   GdkEvent *event,
3026                   EWeekView *view)
3027 {
3028 	gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
3029 	EWeekViewEvent *pevent;
3030 
3031 	pevent = tooltip_get_view_event (view, -1, event_num);
3032 
3033 	switch (event->type) {
3034 		case GDK_ENTER_NOTIFY:
3035 		if (view->editing_event_num == -1) {
3036 			ECalendarViewEventData *data;
3037 
3038 			data = g_malloc (sizeof (ECalendarViewEventData));
3039 
3040 			pevent->x = ((GdkEventCrossing *) event)->x_root;
3041 			pevent->y = ((GdkEventCrossing *) event)->y_root;
3042 			pevent->tooltip = NULL;
3043 
3044 			data->cal_view = (ECalendarView *) view;
3045 			data->day = -1;
3046 			data->event_num = event_num;
3047 			data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event;
3048 			pevent->timeout = g_timeout_add_full (
3049 				G_PRIORITY_DEFAULT, 500,
3050 				(GSourceFunc) e_calendar_view_get_tooltips,
3051 				data, (GDestroyNotify) g_free);
3052 			g_object_set_data ((GObject *) view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout));
3053 
3054 			return TRUE;
3055 		} else {
3056 			return FALSE;
3057 		}
3058 		case GDK_MOTION_NOTIFY:
3059 			pevent->x = ((GdkEventMotion *) event)->x_root;
3060 			pevent->y = ((GdkEventMotion *) event)->y_root;
3061 			pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (view), "tooltip-window");
3062 
3063 			if (pevent->tooltip) {
3064 				e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16);
3065 			}
3066 
3067 			return TRUE;
3068 		case GDK_LEAVE_NOTIFY:
3069 		case GDK_KEY_PRESS:
3070 		case GDK_BUTTON_PRESS:
3071 			tooltip_destroy (view, item);
3072 		default:
3073 			return FALSE;
3074 	}
3075 }
3076 
3077 static const gchar *
3078 get_comp_summary (ECalClient *client,
3079                   icalcomponent *icalcomp,
3080                   gboolean *free_text)
3081 {
3082 	const gchar *my_summary, *location;
3083 	const gchar *summary;
3084 	gboolean my_free_text = FALSE;
3085 
3086 	g_return_val_if_fail (icalcomp != NULL && free_text != NULL, NULL);
3087 
3088 	my_summary = e_calendar_view_get_icalcomponent_summary (client, icalcomp, &my_free_text);
3089 
3090 	location = icalcomponent_get_location (icalcomp);
3091 	if (location && *location) {
3092 		*free_text = TRUE;
3093 		summary = g_strdup_printf ("%s (%s)", my_summary, location);
3094 
3095 		if (my_free_text)
3096 			g_free ((gchar *) my_summary);
3097 	} else {
3098 		*free_text = my_free_text;
3099 		summary = my_summary;
3100 	}
3101 
3102 	return summary;
3103 }
3104 
3105 static void
3106 e_week_view_reshape_event_span (EWeekView *week_view,
3107                                 gint event_num,
3108                                 gint span_num)
3109 {
3110 	ECalendarView *cal_view;
3111 	ECalModel *model;
3112 	ESourceRegistry *registry;
3113 	EWeekViewEvent *event;
3114 	EWeekViewEventSpan *span;
3115 	gint span_x, span_y, span_w, num_icons, icons_width, time_width;
3116 	gint min_text_x, max_text_w, width;
3117 	gboolean show_icons = TRUE, use_max_width = FALSE;
3118 	gboolean one_day_event;
3119 	ECalComponent *comp;
3120 	gdouble text_x, text_y, text_w, text_h;
3121 	gchar *text, *end_of_line;
3122 	gint line_len, text_width;
3123 	PangoFontDescription *font_desc;
3124 	PangoContext *pango_context;
3125 	PangoFontMetrics *font_metrics;
3126 	PangoLayout *layout;
3127 
3128 	cal_view = E_CALENDAR_VIEW (week_view);
3129 	model = e_calendar_view_get_model (cal_view);
3130 
3131 	registry = e_cal_model_get_registry (model);
3132 
3133 	if (!is_array_index_in_bounds (week_view->events, event_num))
3134 		return;
3135 
3136 	event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3137 
3138 	if (!is_comp_data_valid (event))
3139 		return;
3140 
3141 	if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
3142 		return;
3143 
3144 	span = &g_array_index (week_view->spans, EWeekViewEventSpan,
3145 			       event->spans_index + span_num);
3146 	comp = e_cal_component_new ();
3147 	e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
3148 
3149 	one_day_event = e_week_view_is_one_day_event (week_view, event_num);
3150 
3151 	/* If the span will not be visible destroy the canvas items and
3152 	 * return. */
3153 	if (!e_week_view_get_span_position (week_view, event_num, span_num,
3154 					    &span_x, &span_y, &span_w)) {
3155 		if (span->background_item)
3156 			g_object_run_dispose (G_OBJECT (span->background_item));
3157 		if (span->text_item)
3158 			g_object_run_dispose (G_OBJECT (span->text_item));
3159 		span->background_item = NULL;
3160 		span->text_item = NULL;
3161 
3162 		g_object_unref (comp);
3163 		return;
3164 	}
3165 
3166 	/* Set up Pango prerequisites */
3167 	font_desc = gtk_widget_get_style (GTK_WIDGET (week_view))->font_desc;
3168 	pango_context = gtk_widget_get_pango_context (GTK_WIDGET (week_view));
3169 	font_metrics = pango_context_get_metrics (
3170 		pango_context, font_desc,
3171 		pango_context_get_language (pango_context));
3172 	layout = pango_layout_new (pango_context);
3173 
3174 	/* If we are editing a long event we don't show the icons and the EText
3175 	 * item uses the maximum width available. */
3176 	if (!one_day_event && week_view->editing_event_num == event_num
3177 	    && week_view->editing_span_num == span_num) {
3178 		show_icons = FALSE;
3179 		use_max_width = TRUE;
3180 	}
3181 
3182 	/* Calculate how many icons we need to show. */
3183 	num_icons = 0;
3184 	if (show_icons) {
3185 		if (e_cal_component_has_alarms (comp))
3186 			num_icons++;
3187 		if (e_cal_component_has_recurrences (comp) || e_cal_component_is_instance (comp))
3188 			num_icons++;
3189 		if (e_cal_component_has_attachments (comp))
3190 			num_icons++;
3191 		if (e_cal_component_has_attendees (comp))
3192 			num_icons++;
3193 		if (event->different_timezone)
3194 			num_icons++;
3195 		num_icons += cal_comp_util_get_n_icons (comp, NULL);
3196 	}
3197 
3198 	/* Create the background canvas item if necessary. */
3199 	if (!span->background_item) {
3200 		span->background_item =
3201 			gnome_canvas_item_new (
3202 				GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root),
3203 				e_week_view_event_item_get_type (),
3204 				NULL);
3205 	}
3206 
3207 	g_object_set_data ((GObject *) span->background_item, "event-num", GINT_TO_POINTER (event_num));
3208 	g_signal_connect (
3209 		span->background_item, "event",
3210 		G_CALLBACK (tooltip_event_cb), week_view);
3211 
3212 	gnome_canvas_item_set (
3213 		span->background_item,
3214 		"event_num", event_num,
3215 		"span_num", span_num,
3216 		NULL);
3217 
3218 	/* Create the text item if necessary. */
3219 	if (!span->text_item) {
3220 		const gchar *summary;
3221 		GtkWidget *widget;
3222 		GdkColor color;
3223 		gboolean free_text = FALSE;
3224 
3225 		widget = (GtkWidget *) week_view;
3226 
3227 		color = e_week_view_get_text_color (week_view, event, widget);
3228 		summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text);
3229 
3230 		span->text_item =
3231 			gnome_canvas_item_new (
3232 				GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root),
3233 				e_text_get_type (),
3234 				"clip", TRUE,
3235 				"max_lines", 1,
3236 				"editable", TRUE,
3237 				"text", summary ? summary : "",
3238 				"use_ellipsis", TRUE,
3239 				"fill_color_gdk", &color,
3240 				"im_context", E_CANVAS (week_view->main_canvas)->im_context,
3241 				NULL);
3242 
3243 		if (free_text)
3244 			g_free ((gchar *) summary);
3245 
3246 		if (e_client_check_capability (E_CLIENT (event->comp_data->client), CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING)
3247 				&& e_cal_util_component_has_attendee (event->comp_data->icalcomp)) {
3248 			set_text_as_bold (event, span, registry);
3249 		}
3250 		g_object_set_data (G_OBJECT (span->text_item), "event-num", GINT_TO_POINTER (event_num));
3251 		g_signal_connect (
3252 			span->text_item, "event",
3253 			G_CALLBACK (e_week_view_on_text_item_event), week_view);
3254 		g_signal_emit_by_name (
3255 			G_OBJECT (week_view),
3256 			"event_added", event);
3257 
3258 	}
3259 
3260 	/* Calculate the position of the text item.
3261 	 * For events < 1 day it starts after the times & icons and ends at the
3262 	 * right edge of the span.
3263 	 * For events >= 1 day we need to determine whether times are shown at
3264 	 * the start and end of the span, then try to center the text item with
3265 	 * the icons in the middle, but making sure we don't go over the times.
3266 	*/
3267 
3268 	/* Calculate the space necessary to display a time, e.g. "13:00". */
3269 	time_width = e_week_view_get_time_string_width (week_view);
3270 
3271 	/* Calculate the space needed for the icons. */
3272 	icons_width = (E_WEEK_VIEW_ICON_WIDTH + E_WEEK_VIEW_ICON_X_PAD)
3273 		* num_icons - E_WEEK_VIEW_ICON_X_PAD + E_WEEK_VIEW_ICON_R_PAD;
3274 
3275 	/* The y position and height are the same for both event types. */
3276 	text_y = span_y + E_WEEK_VIEW_EVENT_BORDER_HEIGHT
3277 		+ E_WEEK_VIEW_EVENT_TEXT_Y_PAD;
3278 
3279 	text_h =
3280 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
3281 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
3282 
3283 	if (one_day_event) {
3284 		/* Note that 1-day events don't have a border. Although we
3285 		 * still use the border height to position the events
3286 		 * vertically so they still line up neatly (see above),
3287 		 * we don't use the border width or edge padding at all. */
3288 		text_x = span_x	+ E_WEEK_VIEW_EVENT_L_PAD;
3289 
3290 		switch (week_view->time_format) {
3291 		case E_WEEK_VIEW_TIME_BOTH_SMALL_MIN:
3292 		case E_WEEK_VIEW_TIME_BOTH:
3293 			/* These have 2 time strings with a small space between
3294 			 * them and some space before the EText item. */
3295 			text_x += time_width * 2
3296 				+ E_WEEK_VIEW_EVENT_TIME_SPACING
3297 				+ E_WEEK_VIEW_EVENT_TIME_X_PAD;
3298 			break;
3299 		case E_WEEK_VIEW_TIME_START_SMALL_MIN:
3300 		case E_WEEK_VIEW_TIME_START:
3301 			/* These have just 1 time string with some space
3302 			 * before the EText item. */
3303 			text_x += time_width + E_WEEK_VIEW_EVENT_TIME_X_PAD;
3304 			break;
3305 		case E_WEEK_VIEW_TIME_NONE:
3306 			break;
3307 		}
3308 
3309 		/* The icons_width includes space on the right of the icons. */
3310 		text_x += icons_width;
3311 
3312 		/* The width of the EText item extends right to the edge of the
3313 		 * event, just inside the border. */
3314 		text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD - text_x;
3315 
3316 	} else {
3317 		if (use_max_width) {
3318 			/* When we are editing the event we use all the
3319 			 * available width. */
3320 			text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD
3321 				+ E_WEEK_VIEW_EVENT_BORDER_WIDTH
3322 				+ E_WEEK_VIEW_EVENT_EDGE_X_PAD;
3323 			text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD
3324 				- E_WEEK_VIEW_EVENT_BORDER_WIDTH
3325 				- E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x;
3326 		} else {
3327 			text = NULL;
3328 			/* Get the width of the text of the event. This is a
3329 			 * bit of a hack. It would be better if EText could
3330 			 * tell us this. */
3331 			g_object_get (span->text_item, "text", &text, NULL);
3332 			text_width = 0;
3333 			if (text) {
3334 				/* It should only have one line of text in it.
3335 				 * I'm not sure we need this any more. */
3336 				end_of_line = strchr (text, '\n');
3337 				if (end_of_line)
3338 					line_len = end_of_line - text;
3339 				else
3340 					line_len = strlen (text);
3341 
3342 				pango_layout_set_text (layout, text, line_len);
3343 				pango_layout_get_pixel_size (layout, &text_width, NULL);
3344 				g_free (text);
3345 			}
3346 
3347 			/* Add on the width of the icons and find the default
3348 			 * position, which centers the icons + text. */
3349 			width = text_width + icons_width;
3350 			text_x = span_x + (span_w - width) / 2;
3351 
3352 			/* Now calculate the left-most valid position, and make
3353 			 * sure we don't go to the left of that. */
3354 			min_text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD
3355 				+ E_WEEK_VIEW_EVENT_BORDER_WIDTH
3356 				+ E_WEEK_VIEW_EVENT_EDGE_X_PAD;
3357 			/* See if we will want to display the start time, and
3358 			 * if so take that into account. */
3359 			if (event->start > week_view->day_starts[span->start_day])
3360 				min_text_x += time_width
3361 					+ E_WEEK_VIEW_EVENT_TIME_X_PAD;
3362 
3363 			/* Now make sure we don't go to the left of the minimum
3364 			 * position. */
3365 			text_x = MAX (text_x, min_text_x);
3366 
3367 			/* Now calculate the largest valid width, using the
3368 			 * calculated x position, and make sure we don't
3369 			 * exceed that. */
3370 			max_text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD
3371 				- E_WEEK_VIEW_EVENT_BORDER_WIDTH
3372 				- E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x;
3373 			if (event->end < week_view->day_starts[span->start_day
3374 							      + span->num_days])
3375 				max_text_w -= time_width
3376 					+ E_WEEK_VIEW_EVENT_TIME_X_PAD;
3377 
3378 			text_w = MIN (width, max_text_w);
3379 
3380 			/* Now take out the space for the icons. */
3381 			text_x += icons_width;
3382 			text_w -= icons_width;
3383 		}
3384 	}
3385 
3386 	/* Make sure we don't try to use a negative width. */
3387 	text_w = MAX (text_w, 0);
3388 
3389 	gnome_canvas_item_set (
3390 		span->text_item,
3391 		"clip_width", (gdouble) text_w,
3392 		"clip_height", (gdouble) text_h,
3393 		NULL);
3394 	e_canvas_item_move_absolute (span->text_item, text_x, text_y);
3395 
3396 	g_object_unref (comp);
3397 	g_object_unref (layout);
3398 	pango_font_metrics_unref (font_metrics);
3399 }
3400 
3401 gboolean
3402 e_week_view_start_editing_event (EWeekView *week_view,
3403                                  gint event_num,
3404                                  gint span_num,
3405                                  gchar *initial_text)
3406 {
3407 	EWeekViewEvent *event;
3408 	EWeekViewEventSpan *span;
3409 	ETextEventProcessor *event_processor = NULL;
3410 	ETextEventProcessorCommand command;
3411 	ECalModelComponent *comp_data;
3412 
3413 	/* If we are already editing the event, just return. */
3414 	if (event_num == week_view->editing_event_num
3415 	    && span_num == week_view->editing_span_num)
3416 		return TRUE;
3417 
3418 	if (!is_array_index_in_bounds (week_view->events, event_num))
3419 		return FALSE;
3420 
3421 	event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3422 
3423 	if (!is_comp_data_valid (event))
3424 		return FALSE;
3425 
3426 	if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
3427 		return FALSE;
3428 
3429 	span = &g_array_index (week_view->spans, EWeekViewEventSpan,
3430 			       event->spans_index + span_num);
3431 
3432 	if (e_client_is_readonly (E_CLIENT (event->comp_data->client)))
3433 		return FALSE;
3434 
3435 	/* If the event is not shown, don't try to edit it. */
3436 	if (!span->text_item)
3437 		return FALSE;
3438 
3439 	if (week_view->editing_event_num >= 0) {
3440 		EWeekViewEvent *editing;
3441 
3442 		if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
3443 			return FALSE;
3444 
3445 		editing = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num);
3446 
3447 		/* do not change to other part of same component - the event is spread into more days */
3448 		if (editing && event && editing->comp_data == event->comp_data)
3449 			return FALSE;
3450 	}
3451 
3452 	gnome_canvas_item_set (
3453 		span->text_item,
3454 		"text", initial_text ? initial_text : icalcomponent_get_summary (event->comp_data->icalcomp),
3455 		NULL);
3456 
3457 	/* Save the comp_data value because we use that as our invariant */
3458 	comp_data = event->comp_data;
3459 
3460 	e_canvas_item_grab_focus (span->text_item, TRUE);
3461 
3462 	/* If the above focus caused things to redraw, then find the
3463 	 * the event and the span again */
3464 	if (event_num < week_view->events->len)
3465 		event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
3466 
3467 	if (event_num >= week_view->events->len || event->comp_data != comp_data) {
Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
(emitted by clang-analyzer)

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

Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
(emitted by clang-analyzer)

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

3468 /* When got in because of other comp_data, then be sure we go through all events */ 3469 event_num = week_view->events->len; 3470 3471 /* Unfocussing can cause a removal but not a new 3472 * addition so just run backwards through the 3473 * events */ 3474 for (event_num--; event_num >= 0; event_num--) { 3475 event = &g_array_index (week_view->events, EWeekViewEvent, event_num); 3476 if (event->comp_data == comp_data)
Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
(emitted by clang-analyzer)

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

Access to field 'comp_data' results in a dereference of a null pointer (loaded from variable 'event')
(emitted by clang-analyzer)

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

3477 break; 3478 } 3479 g_return_val_if_fail (event_num >= 0, FALSE); 3480 } 3481 3482 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) 3483 return FALSE; 3484 3485 span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); 3486 3487 /* Try to move the cursor to the end of the text. */ 3488 g_object_get (span->text_item, "event_processor", &event_processor, NULL); 3489 if (event_processor) { 3490 command.action = E_TEP_MOVE; 3491 command.position = E_TEP_END_OF_BUFFER; 3492 g_signal_emit_by_name ( 3493 event_processor, 3494 "command", &command); 3495 } 3496 return TRUE; 3497 } 3498 3499 /* This stops any current edit. */ 3500 void 3501 e_week_view_stop_editing_event (EWeekView *week_view) 3502 { 3503 GtkWidget *toplevel; 3504 3505 /* Check we are editing an event. */ 3506 if (week_view->editing_event_num == -1) 3507 return; 3508 3509 /* Set focus to the toplevel so the item loses focus. */ 3510 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (week_view)); 3511 if (toplevel && GTK_IS_WINDOW (toplevel)) 3512 gtk_window_set_focus (GTK_WINDOW (toplevel), NULL); 3513 } 3514 3515 /* Cancels the current edition by resetting the appointment's text to its original value */ 3516 static void 3517 cancel_editing (EWeekView *week_view) 3518 { 3519 gint event_num, span_num; 3520 EWeekViewEvent *event; 3521 EWeekViewEventSpan *span; 3522 const gchar *summary; 3523 3524 event_num = week_view->editing_event_num; 3525 span_num = week_view->editing_span_num; 3526 3527 g_return_if_fail (event_num != -1); 3528 3529 if (!is_array_index_in_bounds (week_view->events, event_num)) 3530 return; 3531 3532 event = &g_array_index (week_view->events, EWeekViewEvent, event_num); 3533 3534 if (!is_comp_data_valid (event)) 3535 return; 3536 3537 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) 3538 return; 3539 3540 span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); 3541 3542 /* Reset the text to what was in the component */ 3543 3544 summary = icalcomponent_get_summary (event->comp_data->icalcomp); 3545 g_object_set (span->text_item, "text", summary ? summary : "", NULL); 3546 3547 /* Stop editing */ 3548 e_week_view_stop_editing_event (week_view); 3549 } 3550 3551 static gboolean 3552 e_week_view_on_text_item_event (GnomeCanvasItem *item, 3553 GdkEvent *gdkevent, 3554 EWeekView *week_view) 3555 { 3556 EWeekViewEvent *event; 3557 gint event_num, span_num; 3558 gint nevent = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num")); 3559 EWeekViewEvent *pevent; 3560 3561 pevent = tooltip_get_view_event (week_view, -1, nevent); 3562 3563 #if 0 3564 g_print ("In e_week_view_on_text_item_event\n"); 3565 #endif 3566 3567 switch (gdkevent->type) { 3568 case GDK_KEY_PRESS: 3569 tooltip_destroy (week_view, item); 3570 if (!E_TEXT (item)->preedit_len && gdkevent && gdkevent->key.keyval == GDK_KEY_Return) { 3571 /* We set the keyboard focus to the EDayView, so the 3572 * EText item loses it and stops the edit. */ 3573 gtk_widget_grab_focus (GTK_WIDGET (week_view)); 3574 3575 /* Stop the signal last or we will also stop any 3576 * other events getting to the EText item. */ 3577 g_signal_stop_emission_by_name (item, "event"); 3578 return TRUE; 3579 } else if (gdkevent->key.keyval == GDK_KEY_Escape) { 3580 cancel_editing (week_view); 3581 g_signal_stop_emission_by_name (item, "event"); 3582 /* focus should go to week view when stop editing */ 3583 gtk_widget_grab_focus (GTK_WIDGET (week_view)); 3584 return TRUE; 3585 } 3586 break; 3587 case GDK_2BUTTON_PRESS: 3588 if (!e_week_view_find_event_from_item (week_view, item, 3589 &event_num, &span_num)) 3590 return FALSE; 3591 3592 if (!is_array_index_in_bounds (week_view->events, event_num)) 3593 return FALSE; 3594 3595 event = &g_array_index (week_view->events, EWeekViewEvent, 3596 event_num); 3597 3598 if (!is_comp_data_valid (event)) 3599 return FALSE; 3600 3601 /* if we started to editing new item on the canvas, then do not open editing dialog until it's saved, 3602 * because the save of the event recalculates event numbers and you can edit different one */ 3603 if (!is_icalcomp_on_the_server (event->comp_data->icalcomp, event->comp_data->client)) 3604 return TRUE; 3605 3606 e_calendar_view_edit_appointment ( 3607 E_CALENDAR_VIEW (week_view), 3608 event->comp_data->client, 3609 event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT); 3610 3611 g_signal_stop_emission_by_name (item, "event"); 3612 return TRUE; 3613 case GDK_BUTTON_PRESS: 3614 tooltip_destroy (week_view, item); 3615 if (!e_week_view_find_event_from_item (week_view, item, 3616 &event_num, &span_num)) 3617 return FALSE; 3618 3619 if (gdkevent->button.button == 3) { 3620 EWeekViewEvent *e; 3621 3622 if (E_TEXT (item)->editing) { 3623 e_week_view_stop_editing_event (week_view); 3624 gtk_widget_grab_focus (GTK_WIDGET (week_view)); 3625 return FALSE; 3626 } 3627 3628 if (!is_array_index_in_bounds (week_view->events, event_num)) 3629 return FALSE; 3630 3631 e = &g_array_index (week_view->events, EWeekViewEvent, event_num); 3632 3633 if (!gtk_widget_has_focus (GTK_WIDGET (week_view))) 3634 gtk_widget_grab_focus (GTK_WIDGET (week_view)); 3635 3636 e_week_view_set_selected_time_range_visible (week_view, e->start, e->end); 3637 3638 e_week_view_show_popup_menu ( 3639 week_view, 3640 (GdkEventButton *) gdkevent, 3641 event_num); 3642 3643 g_signal_stop_emission_by_name ( 3644 item->canvas, "button_press_event"); 3645 return TRUE; 3646 } 3647 3648 if (gdkevent->button.button != 3) { 3649 week_view->pressed_event_num = event_num; 3650 week_view->pressed_span_num = span_num; 3651 } 3652 3653 /* Only let the EText handle the event while editing. */ 3654 if (!E_TEXT (item)->editing) { 3655 g_signal_stop_emission_by_name (item, "event"); 3656 3657 if (gdkevent) { 3658 week_view->drag_event_x = gdkevent->button.x; 3659 week_view->drag_event_y = gdkevent->button.y; 3660 } else 3661 g_warning ("No GdkEvent"); 3662 3663 /* FIXME: Remember the day offset from the start of 3664 * the event, for DnD. */ 3665 3666 return TRUE; 3667 } 3668 break; 3669 case GDK_BUTTON_RELEASE: 3670 if (!E_TEXT (item)->editing) { 3671 /* This shouldn't ever happen. */ 3672 if (!e_week_view_find_event_from_item (week_view, 3673 item, 3674 &event_num, 3675 &span_num)) 3676 return FALSE; 3677 3678 if (week_view->pressed_event_num != -1 3679 && week_view->pressed_event_num == event_num 3680 && week_view->pressed_span_num == span_num) { 3681 e_week_view_start_editing_event ( 3682 week_view, 3683 event_num, 3684 span_num, 3685 NULL); 3686 week_view->pressed_event_num = -1; 3687 } 3688 3689 /* Stop the signal last or we will also stop any 3690 * other events getting to the EText item. */ 3691 g_signal_stop_emission_by_name (item, "event"); 3692 return TRUE; 3693 } 3694 week_view->pressed_event_num = -1; 3695 break; 3696 case GDK_ENTER_NOTIFY: 3697 { 3698 ECalendarViewEventData *data; 3699 gint nspan; 3700 3701 if (week_view->editing_event_num != -1 3702 || !e_week_view_find_event_from_item (week_view, item, &nevent, &nspan)) 3703 return FALSE; 3704 3705 g_object_set_data ((GObject *) item, "event-num", GINT_TO_POINTER (nevent)); 3706 3707 pevent = tooltip_get_view_event (week_view, -1, nevent); 3708 3709 data = g_malloc (sizeof (ECalendarViewEventData)); 3710 3711 pevent->x = ((GdkEventCrossing *) gdkevent)->x_root; 3712 pevent->y = ((GdkEventCrossing *) gdkevent)->y_root; 3713 pevent->tooltip = NULL; 3714 3715 data->cal_view = (ECalendarView *) week_view; 3716 data->day = -1; 3717 data->event_num = nevent; 3718 data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event; 3719 pevent->timeout = g_timeout_add_full ( 3720 G_PRIORITY_DEFAULT, 500, 3721 (GSourceFunc) e_calendar_view_get_tooltips, 3722 data, (GDestroyNotify) g_free); 3723 g_object_set_data ((GObject *) week_view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout)); 3724 3725 return TRUE; 3726 } 3727 case GDK_LEAVE_NOTIFY: 3728 tooltip_destroy (week_view, item); 3729 3730 return FALSE; 3731 case GDK_MOTION_NOTIFY: 3732 pevent->x = ((GdkEventMotion *) gdkevent)->x_root; 3733 pevent->y = ((GdkEventMotion *) gdkevent)->y_root; 3734 pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (week_view), "tooltip-window"); 3735 3736 if (pevent->tooltip) { 3737 e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16); 3738 } 3739 return TRUE; 3740 case GDK_FOCUS_CHANGE: 3741 if (gdkevent->focus_change.in) { 3742 e_week_view_on_editing_started (week_view, item); 3743 } else { 3744 e_week_view_on_editing_stopped (week_view, item); 3745 } 3746 3747 return FALSE; 3748 default: 3749 break; 3750 } 3751 3752 return FALSE; 3753 } 3754 3755 static gboolean e_week_view_event_move (ECalendarView *cal_view, ECalViewMoveDirection direction) 3756 { 3757 EWeekViewEvent *event; 3758 gint event_num, adjust_days, current_start_day, current_end_day; 3759 time_t start_dt, end_dt; 3760 struct icaltimetype start_time,end_time; 3761 EWeekView *week_view = E_WEEK_VIEW (cal_view); 3762 gboolean is_all_day = FALSE; 3763 3764 event_num = week_view->editing_event_num; 3765 adjust_days = 0; 3766 3767 /* If no item is being edited, just return. */ 3768 if (event_num == -1) 3769 return FALSE; 3770 3771 if (!is_array_index_in_bounds (week_view->events, event_num)) 3772 return FALSE; 3773 3774 event = &g_array_index (week_view->events, EWeekViewEvent, event_num); 3775 3776 if (!is_comp_data_valid (event)) 3777 return FALSE; 3778 3779 end_dt = event->end; 3780 start_time = icalcomponent_get_dtstart (event->comp_data->icalcomp); 3781 end_time = icalcomponent_get_dtend (event->comp_data->icalcomp); 3782 3783 if (start_time.is_date && end_time.is_date) 3784 is_all_day = TRUE; 3785 3786 current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt); 3787 3788 switch (direction) { 3789 case E_CAL_VIEW_MOVE_UP: 3790 adjust_days = e_week_view_get_adjust_days_for_move_up (week_view,current_end_day); 3791 break; 3792 case E_CAL_VIEW_MOVE_DOWN: 3793 adjust_days = e_week_view_get_adjust_days_for_move_down (week_view,current_end_day); 3794 break; 3795 case E_CAL_VIEW_MOVE_LEFT: 3796 adjust_days = e_week_view_get_adjust_days_for_move_left (week_view,current_end_day); 3797 break; 3798 case E_CAL_VIEW_MOVE_RIGHT: 3799 adjust_days = e_week_view_get_adjust_days_for_move_right (week_view,current_end_day); 3800 break; 3801 default: 3802 break; 3803 } 3804 3805 icaltime_adjust (&start_time ,adjust_days,0,0,0); 3806 icaltime_adjust (&end_time ,adjust_days,0,0,0); 3807 start_dt = icaltime_as_timet_with_zone ( 3808 start_time, 3809 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); 3810 end_dt = icaltime_as_timet_with_zone ( 3811 end_time, 3812 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); 3813 3814 current_start_day = e_week_view_get_day_offset_of_event (week_view,start_dt); 3815 current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt); 3816 if (is_all_day) 3817 current_end_day--; 3818 3819 if (current_start_day < 0) { 3820 return TRUE; 3821 } 3822 if (week_view->multi_week_view) { 3823 if (current_end_day >= week_view->weeks_shown * 7) { 3824 return TRUE; 3825 } 3826 }else { 3827 if (current_end_day >= 7) { 3828 return TRUE; 3829 } 3830 } 3831 3832 e_week_view_change_event_time (week_view, start_dt, end_dt, is_all_day); 3833 return TRUE; 3834 } 3835 3836 static gint 3837 e_week_view_get_day_offset_of_event (EWeekView *week_view, 3838 time_t event_time) 3839 { 3840 time_t first_day = week_view->day_starts[0]; 3841 3842 if (event_time - first_day < 0) 3843 return -1; 3844 else 3845 return (event_time - first_day) / (24 * 60 * 60); 3846 } 3847 3848 void 3849 e_week_view_scroll_a_step (EWeekView *week_view, 3850 ECalViewMoveDirection direction) 3851 { 3852 GtkAdjustment *adjustment; 3853 GtkRange *range; 3854 gdouble step_increment; 3855 gdouble page_size; 3856 gdouble new_value; 3857 gdouble lower; 3858 gdouble upper; 3859 gdouble value; 3860 3861 range = GTK_RANGE (week_view->vscrollbar); 3862 adjustment = gtk_range_get_adjustment (range); 3863 3864 step_increment = gtk_adjustment_get_step_increment (adjustment); 3865 page_size = gtk_adjustment_get_page_size (adjustment); 3866 lower = gtk_adjustment_get_lower (adjustment); 3867 upper = gtk_adjustment_get_upper (adjustment); 3868 value = gtk_adjustment_get_value (adjustment); 3869 3870 switch (direction) { 3871 case E_CAL_VIEW_MOVE_UP: 3872 new_value = value - step_increment; 3873 break; 3874 case E_CAL_VIEW_MOVE_DOWN: 3875 new_value = value + step_increment; 3876 break; 3877 case E_CAL_VIEW_MOVE_PAGE_UP: 3878 new_value = value - page_size; 3879 break; 3880 case E_CAL_VIEW_MOVE_PAGE_DOWN: 3881 new_value = value + page_size; 3882 break; 3883 default: 3884 return; 3885 } 3886 3887 new_value = CLAMP (new_value, lower, upper - page_size); 3888 gtk_adjustment_set_value (adjustment, new_value); 3889 } 3890 3891 static void 3892 e_week_view_change_event_time (EWeekView *week_view, 3893 time_t start_dt, 3894 time_t end_dt, 3895 gboolean is_all_day) 3896 { 3897 EWeekViewEvent *event; 3898 gint event_num; 3899 ECalComponent *comp; 3900 ECalComponentDateTime date; 3901 struct icaltimetype itt; 3902 ECalClient *client; 3903 CalObjModType mod = CALOBJ_MOD_ALL; 3904 GtkWindow *toplevel; 3905 3906 event_num = week_view->editing_event_num; 3907 3908 /* If no item is being edited, just return. */ 3909 if (event_num == -1) 3910 return; 3911 3912 if (!is_array_index_in_bounds (week_view->events, event_num)) 3913 return; 3914 3915 event = &g_array_index (week_view->events, EWeekViewEvent, event_num); 3916 3917 if (!is_comp_data_valid (event)) 3918 return; 3919 3920 client = event->comp_data->client; 3921 3922 /* We use a temporary shallow copy of the ico since we don't want to 3923 * change the original ico here. Otherwise we would not detect that 3924 * the event's time had changed in the "update_event" callback. */ 3925 comp = e_cal_component_new (); 3926 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); 3927 date.value = &itt; 3928 /* FIXME: Should probably keep the timezone of the original start 3929 * and end times. */ 3930 date.tzid = icaltimezone_get_tzid (e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); 3931 3932 *date.value = icaltime_from_timet_with_zone (start_dt, is_all_day, 3933 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); 3934 cal_comp_set_dtstart_with_oldzone (client, comp, &date); 3935 *date.value = icaltime_from_timet_with_zone (end_dt, is_all_day, 3936 e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); 3937 cal_comp_set_dtend_with_oldzone (client, comp, &date); 3938 3939 e_cal_component_commit_sequence (comp); 3940 3941 if (week_view->last_edited_comp_string != NULL) { 3942 g_free (week_view->last_edited_comp_string); 3943 week_view->last_edited_comp_string = NULL; 3944 } 3945 3946 week_view->last_edited_comp_string = e_cal_component_get_as_string (comp); 3947 3948 if (e_cal_component_has_recurrences (comp)) { 3949 if (!recur_component_dialog (client, comp, &mod, NULL, FALSE)) { 3950 gtk_widget_queue_draw (week_view->main_canvas); 3951 goto out; 3952 } 3953 3954 if (mod == CALOBJ_MOD_ALL) 3955 comp_util_sanitize_recurrence_master (comp, client); 3956 3957 if (mod == CALOBJ_MOD_THIS) { 3958 e_cal_component_set_rdate_list (comp, NULL); 3959 e_cal_component_set_rrule_list (comp, NULL); 3960 e_cal_component_set_exdate_list (comp, NULL); 3961 e_cal_component_set_exrule_list (comp, NULL); 3962 } 3963 } else if (e_cal_component_is_instance (comp)) 3964 mod = CALOBJ_MOD_THIS; 3965 3966 toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (week_view))); 3967 3968 e_cal_component_commit_sequence (comp); 3969 3970 e_calendar_view_modify_and_send ( 3971 E_CALENDAR_VIEW (week_view), 3972 comp, client, mod, toplevel, TRUE); 3973 3974 out: 3975 g_object_unref (comp); 3976 } 3977 3978 static void 3979 e_week_view_on_editing_started (EWeekView *week_view, 3980 GnomeCanvasItem *item) 3981 { 3982 gint event_num, span_num; 3983 3984 if (!e_week_view_find_event_from_item (week_view, item, 3985 &event_num, &span_num)) 3986 return; 3987 3988 #if 0 3989 g_print ("In e_week_view_on_editing_started event_num:%i span_num:%i\n", event_num, span_num); 3990 #endif 3991 3992 week_view->editing_event_num = event_num; 3993 week_view->editing_span_num = span_num; 3994 3995 /* We need to reshape long events so the whole width is used while 3996 * editing. */ 3997 if (!e_week_view_is_one_day_event (week_view, event_num)) { 3998 e_week_view_reshape_event_span ( 3999 week_view, event_num, span_num); 4000 } 4001 4002 g_signal_emit_by_name (week_view, "selection_changed"); 4003 } 4004 4005 static void 4006 e_week_view_on_editing_stopped (EWeekView *week_view, 4007 GnomeCanvasItem *item) 4008 { 4009 gint event_num, span_num; 4010 EWeekViewEvent *event; 4011 EWeekViewEventSpan *span; 4012 gchar *text = NULL; 4013 ECalComponent *comp; 4014 ECalComponentText summary; 4015 ECalClient *client; 4016 const gchar *uid; 4017 gboolean on_server; 4018 4019 /* Note: the item we are passed here isn't reliable, so we just stop 4020 * the edit of whatever item was being edited. We also receive this 4021 * event twice for some reason. */ 4022 event_num = week_view->editing_event_num; 4023 span_num = week_view->editing_span_num; 4024 4025 /* If no item is being edited, just return. */ 4026 if (event_num == -1) 4027 return; 4028 4029 if (!is_array_index_in_bounds (week_view->events, event_num)) 4030 return; 4031 4032 event = &g_array_index (week_view->events, EWeekViewEvent, event_num); 4033 4034 if (!is_comp_data_valid (event)) 4035 return; 4036 4037 if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) 4038 return; 4039 4040 span = &g_array_index (week_view->spans, EWeekViewEventSpan, 4041 event->spans_index + span_num); 4042 4043 /* Reset the edit fields. */ 4044 week_view->editing_event_num = -1; 4045 4046 /* Check that the event is still valid. */ 4047 uid = icalcomponent_get_uid (event->comp_data->icalcomp); 4048 if (!uid) 4049 return; 4050 4051 text = NULL; 4052 g_object_set (span->text_item, "handle_popup", FALSE, NULL); 4053 g_object_get (span->text_item, "text", &text, NULL); 4054 4055 comp = e_cal_component_new (); 4056 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); 4057 4058 client = event->comp_data->client; 4059 on_server = cal_comp_is_on_server (comp, client); 4060 4061 if (string_is_empty (text) && !on_server) { 4062 e_cal_component_get_uid (comp, &uid); 4063 g_signal_handlers_disconnect_by_func (item, e_week_view_on_text_item_event, week_view); 4064 e_week_view_foreach_event_with_uid (week_view, uid, 4065 e_week_view_remove_event_cb, NULL); 4066 week_view->event_destroyed = TRUE; 4067 gtk_widget_queue_draw (week_view->main_canvas); 4068 e_week_view_check_layout (week_view); 4069 goto out; 4070 } 4071 4072 /* Only update the summary if necessary. */ 4073 e_cal_component_get_summary (comp, &summary); 4074 if (summary.value && !strcmp (text, summary.value)) { 4075 gboolean free_text = FALSE; 4076 const gchar *summary; 4077 4078 summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text); 4079 g_object_set (span->text_item, "text", summary ? summary : "", NULL); 4080 4081 if (free_text) 4082 g_free ((gchar *) summary); 4083 4084 if (!e_week_view_is_one_day_event (week_view, event_num)) 4085 e_week_view_reshape_event_span (week_view, event_num, span_num); 4086 } else if (summary.value || !string_is_empty (text)) { 4087 icalcomponent *icalcomp = e_cal_component_get_icalcomponent (comp); 4088 4089 summary.value = text; 4090 summary.altrep = NULL; 4091 e_cal_component_set_summary (comp, &summary); 4092 e_cal_component_commit_sequence (comp); 4093 4094 if (!on_server) { 4095 gchar *uid = NULL; 4096 GError *error = NULL; 4097 4098 e_cal_client_create_object_sync ( 4099 client, icalcomp, &uid, NULL, &error); 4100 4101 if (error != NULL) { 4102 g_warning ( 4103 G_STRLOC ": Could not create the object! %s", 4104 error->message); 4105 uid = NULL; 4106 } else { 4107 if (uid) 4108 icalcomponent_set_uid (icalcomp, uid); 4109 4110 e_calendar_view_emit_user_created ( 4111 E_CALENDAR_VIEW (week_view