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

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found e-meeting-time-sel-item.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found e-meeting-time-sel-item.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
   1 /*
   2  * EMeetingTimeSelectorItem - A GnomeCanvasItem which is used for both the main
   3  * display canvas and the top display (with the dates, times & All Attendees).
   4  * I didn't make these separate GnomeCanvasItems since they share a lot of
   5  * code.
   6  *
   7  * This program is free software; you can redistribute it and/or
   8  * modify it under the terms of the GNU Lesser General Public
   9  * License as published by the Free Software Foundation; either
  10  * version 2 of the License, or (at your option) version 3.
  11  *
  12  * This program is distributed in the hope that it will be useful,
  13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15  * Lesser General Public License for more details.
  16  *
  17  * You should have received a copy of the GNU Lesser General Public
  18  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  19  *
  20  * Authors:
  21  *		Damon Chaplin <damon@gtk.org>
  22  *
  23  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  24  */
  25 
  26 #ifdef HAVE_CONFIG_H
  27 #include <config.h>
  28 #endif
  29 
  30 #include <time.h>
  31 #include <glib/gi18n.h>
  32 
  33 #include "e-util/e-datetime-format.h"
  34 
  35 #include "calendar-config.h"
  36 #include "e-meeting-time-sel-item.h"
  37 #include "e-meeting-time-sel.h"
  38 
  39 /* Initially the grid lines were drawn at the bottom of cells, but this didn't
  40  * line up well with the GtkEntry widgets, which in the default theme draw a
  41  * black shadow line across the top. So I've switched our code to draw the
  42  * lines across the top of cells. */
  43 #define E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM 0
  44 
  45 static void e_meeting_time_selector_item_dispose (GObject *object);
  46 
  47 static void e_meeting_time_selector_item_set_property (GObject *object,
  48                                                        guint property_id,
  49                                                        const GValue *value,
  50                                                        GParamSpec *pspec);
  51 static void e_meeting_time_selector_item_update (GnomeCanvasItem *item,
  52 						 const cairo_matrix_t *i2c,
  53 						 gint flags);
  54 static void e_meeting_time_selector_item_draw (GnomeCanvasItem *item,
  55 					       cairo_t *cr,
  56 					       gint x, gint y,
  57 					       gint width, gint height);
  58 static GnomeCanvasItem *e_meeting_time_selector_item_point (GnomeCanvasItem *item,
  59                                                             double x, double y,
  60                                                             gint cx, gint cy);
  61 static gint e_meeting_time_selector_item_event (GnomeCanvasItem *item,
  62 						GdkEvent *event);
  63 static gint e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item,
  64 						       GdkEvent *event);
  65 static gint e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item,
  66 							 GdkEvent *event);
  67 static gint e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item,
  68 							GdkEvent *event);
  69 
  70 static void e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item,
  71 							cairo_t *cr,
  72 							GDate *date,
  73 							gint x, gint scroll_y,
  74 							gint width, gint height);
  75 static void e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint y, gint width, gint height);
  76 static void e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item,
  77 						    cairo_t *cr,
  78 						    GDate *date,
  79 						    gint x, gint scroll_y,
  80 						    gint width, gint height);
  81 static void e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height);
  82 static gint e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item, GDate *date, gint row);
  83 static void e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, gint row, gint x, gint y, gint width, gint first_period, EMeetingFreeBusyType busy_type);
  84 
  85 static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y);
  86 static gboolean e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts,
  87 								   gint row,
  88 								   gint x,
  89 								   gint width,
  90 								   gint *start_x,
  91 								   gint *end_x);
  92 
  93 enum {
  94 	PROP_0,
  95 	PROP_MEETING_TIME_SELECTOR
  96 };
  97 
  98 G_DEFINE_TYPE (EMeetingTimeSelectorItem, e_meeting_time_selector_item, GNOME_TYPE_CANVAS_ITEM)
  99 
 100 static void
 101 e_meeting_time_selector_item_class_init (EMeetingTimeSelectorItemClass *class)
 102 {
 103 	GObjectClass *object_class;
 104 	GnomeCanvasItemClass *item_class;
 105 
 106 	object_class = G_OBJECT_CLASS (class);
 107 	object_class->dispose = e_meeting_time_selector_item_dispose;
 108 	object_class->set_property = e_meeting_time_selector_item_set_property;
 109 
 110 	item_class = GNOME_CANVAS_ITEM_CLASS (class);
 111 	item_class->update = e_meeting_time_selector_item_update;
 112 	item_class->draw = e_meeting_time_selector_item_draw;
 113 	item_class->point = e_meeting_time_selector_item_point;
 114 	item_class->event = e_meeting_time_selector_item_event;
 115 
 116 	g_object_class_install_property (
 117 		object_class,
 118 		PROP_MEETING_TIME_SELECTOR,
 119 		g_param_spec_pointer (
 120 			"meeting_time_selector",
 121 			NULL,
 122 			NULL,
 123 			G_PARAM_WRITABLE));
 124 }
 125 
 126 static void
 127 e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item)
 128 {
 129 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (mts_item);
 130 
 131 	mts_item->mts = NULL;
 132 
 133 	/* Create the cursors. */
 134 	mts_item->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR);
 135 	mts_item->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
 136 	mts_item->busy_cursor = gdk_cursor_new (GDK_WATCH);
 137 	mts_item->last_cursor_set = NULL;
 138 
 139 	item->x1 = 0;
 140 	item->y1 = 0;
 141 	item->x2 = 0;
 142 	item->y2 = 0;
 143 }
 144 
 145 static void
 146 e_meeting_time_selector_item_dispose (GObject *object)
 147 {
 148 	EMeetingTimeSelectorItem *mts_item;
 149 
 150 	mts_item = E_MEETING_TIME_SELECTOR_ITEM (object);
 151 
 152 	if (mts_item->normal_cursor) {
 153 		g_object_unref (mts_item->normal_cursor);
 154 		mts_item->normal_cursor = NULL;
 155 	}
 156 	if (mts_item->resize_cursor) {
 157 		g_object_unref (mts_item->resize_cursor);
 158 		mts_item->resize_cursor = NULL;
 159 	}
 160 	if (mts_item->busy_cursor) {
 161 		g_object_unref (mts_item->busy_cursor);
 162 		mts_item->busy_cursor = NULL;
 163 	}
 164 
 165 	G_OBJECT_CLASS (e_meeting_time_selector_item_parent_class)->dispose (object);
 166 }
 167 
 168 static void
 169 e_meeting_time_selector_item_set_property (GObject *object,
 170                                            guint property_id,
 171                                            const GValue *value,
 172                                            GParamSpec *pspec)
 173 {
 174 	EMeetingTimeSelectorItem *mts_item;
 175 
 176 	mts_item = E_MEETING_TIME_SELECTOR_ITEM (object);
 177 
 178 	switch (property_id) {
 179 	case PROP_MEETING_TIME_SELECTOR:
 180 		mts_item->mts = g_value_get_pointer (value);
 181 		return;
 182 	}
 183 
 184 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 185 }
 186 
 187 static void
 188 e_meeting_time_selector_item_update (GnomeCanvasItem *item,
 189                                      const cairo_matrix_t *i2c,
 190                                      gint flags)
 191 {
 192 	if (GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update)
 193 		(* GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update) (item, i2c, flags);
 194 
 195 	/* The grid covers the entire canvas area. */
 196 	item->x1 = 0;
 197 	item->y1 = 0;
 198 	item->x2 = INT_MAX;
 199 	item->y2 = INT_MAX;
 200 }
 201 
 202 /*
 203  * DRAWING ROUTINES - functions to paint the canvas item.
 204  */
 205 
 206 static void
 207 draw_strikeout_box (EMeetingTimeSelectorItem *mts_item,
 208                     cairo_t *cr,
 209                     gint x,
 210                     gint y,
 211                     gint width,
 212                     gint height)
 213 {
 214 	GnomeCanvas *canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
 215 	EMeetingTimeSelector *mts = mts_item->mts;
 216 
 217 	cairo_save (cr);
 218 
 219 	cairo_rectangle (cr, x, y, width, height);
 220 	cairo_clip (cr);
 221 
 222 	cairo_translate (cr, -canvas->draw_xofs, -canvas->draw_yofs);
 223 	cairo_set_source (cr, mts->no_info_pattern);
 224 	cairo_paint (cr);
 225 
 226 	cairo_restore (cr);
 227 }
 228 
 229 static void
 230 e_meeting_time_selector_item_draw (GnomeCanvasItem *item,
 231                                    cairo_t *cr,
 232                                    gint x,
 233                                    gint y,
 234                                    gint width,
 235                                    gint height)
 236 {
 237 	EMeetingTimeSelector *mts;
 238 	EMeetingTimeSelectorItem *mts_item;
 239 	EMeetingAttendee *ia;
 240 	gint day_x, meeting_start_x, meeting_end_x, bar_y, bar_height;
 241 	gint row, row_y, start_x, end_x;
 242 	GDate date, last_date, current_date;
 243 	gboolean is_display_top, show_meeting_time;
 244 
 245 	mts_item = E_MEETING_TIME_SELECTOR_ITEM (item);
 246 	mts = mts_item->mts;
 247 	g_return_if_fail (mts != NULL);
 248 
 249 	is_display_top = (GTK_WIDGET (item->canvas) == mts->display_top)
 250 		? TRUE : FALSE;
 251 
 252 	/* Calculate the first and last visible days and positions. */
 253 	e_meeting_time_selector_calculate_day_and_position (
 254 		mts, x,
 255 		&date, &day_x);
 256 	e_meeting_time_selector_calculate_day_and_position (
 257 		mts, x + width,
 258 		&last_date, NULL);
 259 
 260 	/* For the top display draw the 'All Attendees' row background. */
 261 	cairo_save (cr);
 262 	if (is_display_top) {
 263 		gdk_cairo_set_source_color (cr, &mts->all_attendees_bg_color);
 264 		cairo_rectangle (
 265 			cr, 0, mts->row_height * 2 - y,
 266 			width, mts->row_height);
 267 		cairo_fill (cr);
 268 	} else {
 269 		gdk_cairo_set_source_color (cr, &mts->bg_color);
 270 		cairo_rectangle (cr,  0, 0, width, height);
 271 		cairo_fill (cr);
 272 	}
 273 	cairo_restore (cr);
 274 
 275 	/* Calculate the x coordinates of the meeting time. */
 276 	show_meeting_time = e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x);
 277 
 278 	/* Draw the meeting time background. */
 279 	if (show_meeting_time
 280 	    && (meeting_end_x - 1 >= x) && (meeting_start_x + 1 < x + width)
 281 	    && (meeting_end_x - meeting_start_x > 2)) {
 282 		cairo_save (cr);
 283 		gdk_cairo_set_source_color (cr, &mts->meeting_time_bg_color);
 284 		if (is_display_top) {
 285 			cairo_rectangle (
 286 				cr, meeting_start_x + 1 - x, mts->row_height * 2 - y,
 287 				meeting_end_x - meeting_start_x - 2, mts->row_height);
 288 			cairo_fill (cr);
 289 		} else {
 290 			cairo_rectangle (
 291 				cr, meeting_start_x + 1 - x, 0,
 292 				meeting_end_x - meeting_start_x - 2, height);
 293 			cairo_fill (cr);
 294 		}
 295 		cairo_restore (cr);
 296 	}
 297 
 298 	/* For the main display draw the no-info pattern background for attendee's
 299 	 * that have no calendar information. */
 300 	if (!is_display_top) {
 301 		gdk_cairo_set_source_color (cr, &mts->grid_color);
 302 		row = y / mts->row_height;
 303 		row_y = row * mts->row_height - y;
 304 		while (row < e_meeting_store_count_actual_attendees (mts->model) && row_y < height) {
 305 			ia = e_meeting_store_find_attendee_at_row (mts->model, row);
 306 
 307 			if (e_meeting_attendee_get_has_calendar_info (ia)) {
 308 				if (e_meeting_time_selector_item_calculate_busy_range (mts, row, x, width, &start_x, &end_x)) {
 309 					if (start_x >= width || end_x <= 0) {
 310 						draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height);
 311 					} else {
 312 						if (start_x >= 0) {
 313 							draw_strikeout_box (mts_item, cr, 0, row_y, start_x, mts->row_height);
 314 							cairo_move_to (cr, start_x, row_y);
 315 							cairo_line_to (cr, start_x, row_y + mts->row_height);
 316 							cairo_stroke (cr);
 317 						}
 318 						if (end_x <= width) {
 319 							draw_strikeout_box (mts_item, cr, end_x, row_y, width - end_x, mts->row_height);
 320 							cairo_move_to (cr, end_x, row_y);
 321 							cairo_line_to (cr, end_x, row_y + mts->row_height);
 322 							cairo_stroke (cr);
 323 						}
 324 					}
 325 				}
 326 			} else {
 327 				draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height);
 328 			}
 329 			row++;
 330 			row_y += mts->row_height;
 331 		}
 332 	}
 333 
 334 	/* Now paint the visible days one by one. */
 335 	current_date = date;
 336 	for (;;) {
 337 		/* Currently we use the same GnomeCanvasItem class for the
 338 		 * top display and the main display. We may use separate
 339 		 * classes in future if necessary. */
 340 		if (is_display_top)
 341 			e_meeting_time_selector_item_paint_day_top (mts_item, cr, &current_date, day_x, y, width, height);
 342 		else
 343 			e_meeting_time_selector_item_paint_day (mts_item, cr, &current_date, day_x, y, width, height);
 344 
 345 		day_x += mts_item->mts->day_width;
 346 		if (g_date_compare (&current_date, &last_date) == 0)
 347 			break;
 348 		g_date_add_days (&current_date, 1);
 349 	}
 350 
 351 	/* Draw the busy periods. */
 352 	if (is_display_top)
 353 		e_meeting_time_selector_item_paint_all_attendees_busy_periods (mts_item, cr, &date, x, y, width, height);
 354 	else
 355 		e_meeting_time_selector_item_paint_busy_periods (mts_item, cr, &date, x, y, width, height);
 356 
 357 	/* Draw the currently-selected meeting time vertical bars. */
 358 	if (show_meeting_time) {
 359 		if (is_display_top) {
 360 			bar_y = mts->row_height * 2 - y;
 361 			bar_height = mts->row_height;
 362 		} else {
 363 			bar_y = 0;
 364 			bar_height = height;
 365 		}
 366 
 367 		cairo_save (cr);
 368 		gdk_cairo_set_source_color (cr, &mts->grid_color);
 369 
 370 		if ((meeting_start_x + 2 >= x)
 371 		    && (meeting_start_x - 2 < x + width)) {
 372 			cairo_rectangle (
 373 				cr, meeting_start_x - 2 - x, bar_y,
 374 				5, bar_height);
 375 			cairo_fill (cr);
 376 		}
 377 
 378 		if ((meeting_end_x + 2 >= x)
 379 		    && (meeting_end_x - 2 < x + width)) {
 380 			cairo_rectangle (
 381 				cr, meeting_end_x - 2 - x, bar_y,
 382 				5, bar_height);
 383 			cairo_fill (cr);
 384 
 385 		}
 386 		cairo_restore (cr);
 387 	}
 388 }
 389 
 390 static void
 391 e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item,
 392                                             cairo_t *cr,
 393                                             GDate *date,
 394                                             gint x,
 395                                             gint scroll_y,
 396                                             gint width,
 397                                             gint height)
 398 {
 399 	EMeetingTimeSelector *mts;
 400 	gint y, grid_x;
 401 	gchar *str;
 402 	gint hour, hour_x, hour_y;
 403 	PangoLayout *layout;
 404 	struct tm tm_time;
 405 
 406 	cairo_save (cr);
 407 
 408 	cairo_set_line_width (cr, 1.0);
 409 
 410 	mts = mts_item->mts;
 411 
 412 	layout = gtk_widget_create_pango_layout (GTK_WIDGET (mts), NULL);
 413 
 414 	/* Draw the horizontal lines. */
 415 	y = mts->row_height - 1 - scroll_y;
 416 
 417 	gdk_cairo_set_source_color (cr, &mts->grid_color);
 418 	cairo_move_to (cr, x + 0.5, y + 0.5);
 419 	cairo_rel_line_to (cr, mts->day_width - 1, 0);
 420 	cairo_stroke (cr);
 421 
 422 	gdk_cairo_set_source_color (cr, &mts->grid_shadow_color);
 423 	cairo_move_to (cr, x + 0.5, y + 1.5);
 424 	cairo_rel_line_to (cr, mts->day_width - 1, 0);
 425 	cairo_stroke (cr);
 426 
 427 	gdk_cairo_set_source_color (cr, &mts->grid_color);
 428 	y += mts->row_height;
 429 	cairo_move_to (cr, x + 0.5, y + 0.5);
 430 	cairo_rel_line_to (cr, mts->day_width - 1, 0);
 431 	y += mts->row_height;
 432 	cairo_move_to (cr, x + 0.5, y + 0.5);
 433 	cairo_rel_line_to (cr, mts->day_width - 1, 0);
 434 
 435 	/* Draw the vertical grid lines. */
 436 	for (grid_x = mts->col_width - 1;
 437 	     grid_x < mts->day_width - mts->col_width;
 438 	     grid_x += mts->col_width) {
 439 		cairo_move_to (cr, x + grid_x + 0.5, mts->row_height * 2 - 1 - scroll_y + 0.5);
 440 		cairo_line_to (cr, x + grid_x + 0.5, height + 0.5);
 441 	}
 442 	grid_x = mts->day_width - 2;
 443 	cairo_move_to (cr, x + grid_x + 0.5, 0.5);
 444 	cairo_rel_line_to (cr, 0, height);
 445 	grid_x++;
 446 	cairo_move_to (cr, x + grid_x + 0.5, 0.5);
 447 	cairo_rel_line_to (cr, 0, height);
 448 	cairo_stroke (cr);
 449 
 450 	g_date_to_struct_tm (date, &tm_time);
 451 	str = e_datetime_format_format_tm ("calendar", "table",  DTFormatKindDate, &tm_time);
 452 
 453 	g_return_if_fail (str != NULL);
 454 
 455 	/* Draw the date. Set a clipping rectangle so we don't draw over the
 456 	 * next day. */
 457 	if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY
 458 	    && !e_datetime_format_includes_day_name ("calendar", "table",  DTFormatKindDate)) {
 459 		gchar buffer[128];
 460 		gchar *tmp;
 461 
 462 		g_date_strftime (buffer, sizeof (buffer), "%a", date);
 463 
 464 		tmp = str;
 465 		str = g_strconcat (buffer, " ", str, NULL);
 466 		g_free (tmp);
 467 	}
 468 
 469 	cairo_save (cr);
 470 
 471 	cairo_rectangle (cr, x, -scroll_y, mts->day_width - 2, mts->row_height - 2);
 472 	cairo_clip (cr);
 473 
 474 	pango_layout_set_text (layout, str, -1);
 475 	cairo_move_to (cr, x + 2, 4 - scroll_y);
 476 	pango_cairo_show_layout (cr, layout);
 477 
 478 	cairo_restore (cr);
 479 
 480 	/* Draw the hours. */
 481 	hour = mts->first_hour_shown;
 482 	hour_x = x + 2;
 483 	hour_y = mts->row_height + 4 - scroll_y;
 484 	while (hour < mts->last_hour_shown) {
 485 		if (e_meeting_time_selector_get_use_24_hour_format (mts))
 486 			pango_layout_set_text (layout, EMeetingTimeSelectorHours[hour], -1);
 487 		else
 488 			pango_layout_set_text (layout, EMeetingTimeSelectorHours12[hour], -1);
 489 
 490 		cairo_move_to (cr, hour_x, hour_y);
 491 		pango_cairo_show_layout (cr, layout);
 492 
 493 		hour += mts->zoomed_out ? 3 : 1;
 494 		hour_x += mts->col_width;
 495 	}
 496 
 497 	g_object_unref (layout);
 498 	cairo_restore (cr);
 499 	g_free (str);
 500 }
 501 
 502 /* This paints the colored bars representing busy periods for the combined
 503  * list of attendees. For now we just paint the bars for each attendee of
 504  * each other. If we want to speed it up we could optimise it later. */
 505 static void
 506 e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item,
 507                                                                cairo_t *cr,
 508                                                                GDate *date,
 509                                                                gint x,
 510                                                                gint scroll_y,
 511                                                                gint width,
 512                                                                gint height)
 513 {
 514 	EMeetingTimeSelector *mts;
 515 	EMeetingFreeBusyType busy_type;
 516 	gint row, y;
 517 	gint *first_periods;
 518 
 519 	mts = mts_item->mts;
 520 
 521 	/* Calculate the y coordinate to paint the row at in the drawable. */
 522 	y = 2 * mts->row_height - scroll_y - 1;
 523 
 524 	/* Get the first visible busy periods for all the attendees. */
 525 	first_periods = g_new (gint, e_meeting_store_count_actual_attendees (mts->model));
 526 	for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++)
 527 		first_periods[row] = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row);
 528 
 529 	for (busy_type = 0;
 530 	     busy_type < E_MEETING_FREE_BUSY_LAST;
 531 	     busy_type++) {
 532 		gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]);
 533 		for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++) {
 534 			if (first_periods[row] == -1)
 535 				continue;
 536 			e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_periods[row], busy_type);
 537 		}
 538 	}
 539 
 540 	g_free (first_periods);
 541 }
 542 
 543 static void
 544 e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item,
 545                                         cairo_t *cr,
 546                                         GDate *date,
 547                                         gint x,
 548                                         gint scroll_y,
 549                                         gint width,
 550                                         gint height)
 551 {
 552 	EMeetingTimeSelector *mts;
 553 	gint grid_x, grid_y, attendee_index, unused_y;
 554 
 555 	cairo_save (cr);
 556 	cairo_set_line_width (cr, 1.0);
 557 
 558 	mts = mts_item->mts;
 559 
 560 	/* Draw the grid lines. The grid lines around unused rows are drawn in
 561 	 * a different color. */
 562 
 563 	/* Draw the horizontal grid lines. */
 564 	attendee_index = scroll_y / mts->row_height;
 565 #if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM
 566 	for (grid_y = mts->row_height - 1 - (scroll_y % mts->row_height);
 567 #else
 568 	for (grid_y = - (scroll_y % mts->row_height);
 569 #endif
 570 	     grid_y < height;
 571 	     grid_y += mts->row_height)
 572 	  {
 573 		  if (attendee_index <= e_meeting_store_count_actual_attendees (mts->model)) {
 574 			  gdk_cairo_set_source_color (cr, &mts->grid_color);
 575 		  } else {
 576 			  gdk_cairo_set_source_color (cr, &mts->grid_unused_color);
 577 		  }
 578 		  cairo_move_to (cr, 0, grid_y);
 579 		  cairo_rel_line_to (cr, width, 0);
 580 		  cairo_stroke (cr);
 581 		  attendee_index++;
 582 	  }
 583 
 584 	/* Draw the vertical grid lines. */
 585 	unused_y = (e_meeting_store_count_actual_attendees (mts->model) * mts->row_height) - scroll_y;
 586 	if (unused_y >= 0) {
 587 		gdk_cairo_set_source_color (cr, &mts->grid_color);
 588 		for (grid_x = mts->col_width - 1;
 589 		     grid_x < mts->day_width - mts->col_width;
 590 		     grid_x += mts->col_width)
 591 			{
 592 				cairo_move_to (cr, x + grid_x, 0);
 593 				cairo_line_to (cr, x + grid_x, unused_y - 1);
 594 			}
 595 		cairo_stroke (cr);
 596 
 597 		cairo_rectangle (
 598 			cr,
 599 			x + mts->day_width - 2, 0,
 600 			2, unused_y);
 601 		cairo_fill (cr);
 602 	}
 603 
 604 	if (unused_y < height) {
 605 		gdk_cairo_set_source_color (cr, &mts->grid_unused_color);
 606 		for (grid_x = mts->col_width - 1;
 607 		     grid_x < mts->day_width - mts->col_width;
 608 		     grid_x += mts->col_width)
 609 			{
 610 				cairo_move_to (cr, x + grid_x, unused_y);
 611 				cairo_line_to (cr, x + grid_x, height);
 612 			}
 613 		cairo_stroke (cr);
 614 
 615 		cairo_rectangle (
 616 			cr,
 617 			x + mts->day_width - 2, unused_y,
 618 			2, height - unused_y);
 619 		cairo_fill (cr);
 620 	}
 621 
 622 	cairo_restore (cr);
 623 }
 624 
 625 /* This paints the colored bars representing busy periods for the individual
 626  * attendees. */
 627 static void
 628 e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item,
 629                                                  cairo_t *cr,
 630                                                  GDate *date,
 631                                                  gint x,
 632                                                  gint scroll_y,
 633                                                  gint width,
 634                                                  gint height)
 635 {
 636 	EMeetingTimeSelector *mts;
 637 	EMeetingFreeBusyType busy_type;
 638 	gint row, y, first_period;
 639 
 640 	mts = mts_item->mts;
 641 
 642 	/* Calculate the first visible attendee row. */
 643 	row = scroll_y / mts->row_height;
 644 
 645 	/* Calculate the y coordinate to paint the row at in the drawable. */
 646 	y = row * mts->row_height - scroll_y;
 647 
 648 	/* Step through the attendees painting the busy periods. */
 649 	while (y < height && row < e_meeting_store_count_actual_attendees (mts->model)) {
 650 
 651 		/* Find the first visible busy period. */
 652 		first_period = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row);
 653 		if (first_period != -1) {
 654 			/* Paint the different types of busy periods, in
 655 			 * reverse order of precedence, so the highest
 656 			 * precedences are displayed. */
 657 			for (busy_type = 0;
 658 			     busy_type < E_MEETING_FREE_BUSY_LAST;
 659 			     busy_type++) {
 660 				gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]);
 661 				e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_period, busy_type);
 662 			}
 663 		}
 664 		y += mts->row_height;
 665 		row++;
 666 	}
 667 }
 668 
 669 /* This subtracts the attendees longest_period_in_days from the given date,
 670  * and does a binary search of the attendee's busy periods array to find the
 671  * first one which could possible end on the given day or later.
 672  * If none are found it returns -1. */
 673 static gint
 674 e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item,
 675                                                      GDate *date,
 676                                                      gint row)
 677 {
 678 	EMeetingTimeSelector *mts;
 679 	EMeetingAttendee *ia;
 680 	const GArray *busy_periods;
 681 	EMeetingFreeBusyPeriod *period;
 682 	gint period_num;
 683 
 684 	mts = mts_item->mts;
 685 
 686 	ia = e_meeting_store_find_attendee_at_row (mts->model, row);
 687 
 688 	period_num = e_meeting_attendee_find_first_busy_period (ia, date);
 689 	if (period_num == -1)
 690 		return -1;
 691 
 692 	/* Check if the period starts after the end of the current canvas
 693 	 * scroll area. */
 694 	busy_periods = e_meeting_attendee_get_busy_periods (ia);
 695 	period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num);
 696 	if (g_date_compare (&mts->last_date_shown, &period->start.date) < 0)
 697 		return -1;
 698 
 699 	return period_num;
 700 }
 701 
 702 /* This paints the visible busy periods for one attendee which are of a certain
 703  * busy type, e.g out of office. It is passed the index of the first visible
 704  * busy period of the attendee and continues until it runs off the screen. */
 705 static void
 706 e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item,
 707                                                           cairo_t *cr,
 708                                                           gint x,
 709                                                           gint y,
 710                                                           gint width,
 711                                                           gint row,
 712                                                           gint first_period,
 713                                                           EMeetingFreeBusyType busy_type)
 714 {
 715 	EMeetingTimeSelector *mts;
 716 	EMeetingAttendee *ia;
 717 	const GArray *busy_periods;
 718 	EMeetingFreeBusyPeriod *period;
 719 	gint period_num, x1, x2, x2_within_day, x2_within_col;
 720 
 721 	mts = mts_item->mts;
 722 
 723 	ia = e_meeting_store_find_attendee_at_row (mts->model, row);
 724 
 725 	busy_periods = e_meeting_attendee_get_busy_periods (ia);
 726 	for (period_num = first_period;
 727 	     period_num < busy_periods->len;
 728 	     period_num++) {
 729 		period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num);
 730 
 731 		if (period->busy_type != busy_type)
 732 			continue;
 733 
 734 		/* Convert the period start and end times to x coordinates. */
 735 		x1 = e_meeting_time_selector_calculate_time_position (mts, &period->start);
 736 		/* If the period is off the right of the area being drawn, we
 737 		 * are finished. */
 738 		if (x1 >= x + width)
 739 			return;
 740 
 741 		x2 = e_meeting_time_selector_calculate_time_position (mts, &period->end);
 742 		/* If the period is off the left edge of the area skip it. */
 743 		if (x2 <= x)
 744 			continue;
 745 
 746 		/* We paint from x1 to x2 - 1, so that for example a time
 747 		 * from 5:00-6:00 is distinct from 6:00-7:00.
 748 		 * We never finish on a grid line separating days, and we only
 749 		 * ever paint on a normal vertical grid line if the period is
 750 		 * only 1 pixel wide. */
 751 		x2_within_day = x2 % mts->day_width;
 752 		if (x2_within_day == 0) {
 753 			x2 -= 2;
 754 		} else if (x2_within_day == mts->day_width - 1) {
 755 			x2 -= 1;
 756 		} else {
 757 			x2_within_col = x2_within_day % mts->col_width;
 758 			if (x2_within_col == 0 && x2 > x1 + 1)
 759 				x2 -= 1;
 760 		}
 761 
 762 		/* Paint the rectangle. We leave a gap of 2 pixels at the
 763 		 * top and bottom, remembering that the grid is painted along
 764 		 * the top/bottom line of each row. */
 765 		if (x2 - x1 > 0) {
 766 #if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM
 767 			cairo_rectangle (
 768 				cr, x1 - x, y + 2,
 769 				x2 - x1, mts->row_height - 5);
 770 #else
 771 			cairo_rectangle (
 772 				cr, x1 - x, y + 3,
 773 				x2 - x1, mts->row_height - 5);
 774 #endif
 775 			cairo_fill (cr);
 776 		}
 777 	}
 778 }
 779 
 780 /*
 781  * CANVAS ITEM ROUTINES - functions to be a GnomeCanvasItem.
 782  */
 783 
 784 static GnomeCanvasItem *
 785 e_meeting_time_selector_item_point (GnomeCanvasItem *item,
 786                                     gdouble x,
 787                                     gdouble y,
 788                                     gint cx,
 789                                     gint cy)
 790 {
 791 	return item;
 792 }
 793 
 794 static gint
 795 e_meeting_time_selector_item_event (GnomeCanvasItem *item,
 796                                     GdkEvent *event)
 797 {
 798 	EMeetingTimeSelectorItem *mts_item;
 799 
 800 	mts_item = E_MEETING_TIME_SELECTOR_ITEM (item);
 801 
 802 	switch (event->type) {
 803 	case GDK_BUTTON_PRESS:
 804 		return e_meeting_time_selector_item_button_press (
 805 			mts_item,
 806 			event);
 807 	case GDK_BUTTON_RELEASE:
 808 		return e_meeting_time_selector_item_button_release (
 809 			mts_item,
 810 			event);
 811 	case GDK_MOTION_NOTIFY:
 812 		return e_meeting_time_selector_item_motion_notify (
 813 			mts_item,
 814 			event);
 815 	default:
 816 		break;
 817 	}
 818 
 819 	return FALSE;
 820 }
 821 
 822 /* This handles all button press events for the item. If the cursor is over
 823  * one of the meeting time vertical bars we start a drag. If not we set the
 824  * meeting time to the nearest half-hour interval.
 825  * Note that GnomeCanvas converts the event coords to world coords,
 826  * i.e. relative to the entire canvas scroll area. */
 827 static gint
 828 e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item,
 829                                            GdkEvent *event)
 830 {
 831 	EMeetingTimeSelector *mts;
 832 	EMeetingTime start_time, end_time;
 833 	EMeetingTimeSelectorPosition position;
 834 	GDate *start_date, *end_date;
 835 	gint x, y;
 836 
 837 	mts = mts_item->mts;
 838 	x = (gint) event->button.x;
 839 	y = (gint) event->button.y;
 840 
 841 	/* Check if we are starting a drag of the vertical meeting time bars.*/
 842 	position = e_meeting_time_selector_item_get_drag_position (
 843 		mts_item,
 844 		x, y);
 845 	if (position != E_MEETING_TIME_SELECTOR_POS_NONE) {
 846 		if (gnome_canvas_item_grab (GNOME_CANVAS_ITEM (mts_item),
 847 					    GDK_POINTER_MOTION_MASK
 848 					    | GDK_BUTTON_RELEASE_MASK,
 849 					    mts_item->resize_cursor,
 850 					    event->button.time) == 0 /*Success */) {
 851 			mts->dragging_position = position;
 852 			return TRUE;
 853 		}
 854 	}
 855 
 856 	/* Convert the x coordinate into a EMeetingTimeSelectorTime. */
 857 	e_meeting_time_selector_calculate_time (mts, x, &start_time);
 858 	start_date = &start_time.date;
 859 	end_date = &end_time.date;
 860 
 861 	/* Find the nearest half-hour or hour interval, depending on whether
 862 	 * zoomed_out is set. */
 863 	if (!mts->all_day) {
 864 		gint astart_year, astart_month, astart_day, astart_hour, astart_minute;
 865 		gint aend_year, aend_month, aend_day, aend_hour, aend_minute;
 866 		gint hdiff, mdiff;
 867 		GDate asdate, aedate;
 868 
 869 		e_meeting_time_selector_get_meeting_time (
 870 			mts_item->mts,
 871 			&astart_year,
 872 			&astart_month,
 873 			&astart_day,
 874 			&astart_hour,
 875 			&astart_minute,
 876 			&aend_year,
 877 			&aend_month,
 878 			&aend_day,
 879 			&aend_hour,
 880 			&aend_minute);
 881 		if (mts->zoomed_out)
 882 			start_time.minute = 0;
 883 		else
 884 			start_time.minute -= start_time.minute % 30;
 885 
 886 		g_date_set_dmy (&asdate, astart_day, astart_month, astart_year);
 887 		g_date_set_dmy (&aedate, aend_day, aend_month, aend_year);
 888 		end_time = start_time;
 889 		mdiff = end_time.minute + aend_minute - astart_minute;
 890 		hdiff = end_time.hour + aend_hour - astart_hour + (24 * g_date_days_between (&asdate, &aedate));
 891 		while (mdiff < 0) {
 892 			mdiff += 60;
 893 			hdiff -= 1;
 894 		}
 895 		while (mdiff > 60) {
 896 			mdiff -= 60;
 897 			hdiff += 1;
 898 		}
 899 		while (hdiff < 0) {
 900 			hdiff += 24;
 901 			g_date_subtract_days (end_date, 1);
 902 		}
 903 		while (hdiff >= 24) {
 904 			hdiff -= 24;
 905 			g_date_add_days (end_date, 1);
 906 		}
 907 		end_time.minute = mdiff;
 908 		end_time.hour = hdiff;
 909 	} else {
 910 		start_time.hour = 0;
 911 		start_time.minute = 0;
 912 		end_time = start_time;
 913 		g_date_add_days (&end_time.date, 1);
 914 	}
 915 
 916 	/* Fix any overflows. */
 917 	e_meeting_time_selector_fix_time_overflows (&end_time);
 918 
 919 	/* Set the new meeting time. */
 920 	e_meeting_time_selector_set_meeting_time (
 921 		mts_item->mts,
 922 		g_date_get_year (start_date),
 923 		g_date_get_month (start_date),
 924 		g_date_get_day (start_date),
 925 		start_time.hour,
 926 		start_time.minute,
 927 		g_date_get_year (end_date),
 928 		g_date_get_month (end_date),
 929 		g_date_get_day (end_date),
 930 		end_time.hour,
 931 		end_time.minute);
 932 
 933 	return FALSE;
 934 }
 935 
 936 /* This handles all button release events for the item. If we were dragging,
 937  * we finish the drag. */
 938 static gint
 939 e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item,
 940                                              GdkEvent *event)
 941 {
 942 	EMeetingTimeSelector *mts;
 943 
 944 	mts = mts_item->mts;
 945 
 946 	/* Reset any drag. */
 947 	if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) {
 948 		mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE;
 949 		e_meeting_time_selector_remove_timeout (mts);
 950 		gnome_canvas_item_ungrab (
 951 			GNOME_CANVAS_ITEM (mts_item),
 952 			event->button.time);
 953 	}
 954 
 955 	return FALSE;
 956 }
 957 
 958 /* This handles all motion notify events for the item. If button1 is pressed
 959  * we check if a drag is in progress. If not, we set the cursor if we are over
 960  * the meeting time vertical bars. Note that GnomeCanvas doesn't use motion
 961  * hints, which may affect performance. */
 962 static gint
 963 e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item,
 964                                             GdkEvent *event)
 965 {
 966 	EMeetingTimeSelector *mts;
 967 	EMeetingTimeSelectorPosition position;
 968 	GdkCursor *cursor;
 969 	gint x, y;
 970 
 971 	mts = mts_item->mts;
 972 	x = (gint) event->motion.x;
 973 	y = (gint) event->motion.y;
 974 
 975 	if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) {
 976 		e_meeting_time_selector_drag_meeting_time (mts, x);
 977 		return TRUE;
 978 	}
 979 
 980 	position = e_meeting_time_selector_item_get_drag_position (
 981 		mts_item,
 982 		x, y);
 983 
 984 	/* Determine which cursor should be used. */
 985 	if (position != E_MEETING_TIME_SELECTOR_POS_NONE)
 986 		cursor = mts_item->resize_cursor;
 987 	/* If the Main window shows busy cursor show the same */
 988 	else if (mts_item->mts->last_cursor_set == GDK_WATCH)
 989 		cursor = mts_item->busy_cursor;
 990 	else
 991 		cursor = mts_item->normal_cursor;
 992 
 993 	/* Only set the cursor if it is different to the last one we set. */
 994 	if (mts_item->last_cursor_set != cursor) {
 995 		GdkWindow *window;
 996 		GnomeCanvas *canvas;
 997 
 998 		mts_item->last_cursor_set = cursor;
 999 
1000 		canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
1001 		window = gtk_widget_get_window (GTK_WIDGET (canvas));
1002 		gdk_window_set_cursor (window, cursor);
1003 	}
1004 
1005 	return FALSE;
1006 }
1007 
1008 static EMeetingTimeSelectorPosition
1009 e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item,
1010                                                 gint x,
1011                                                 gint y)
1012 {
1013 	EMeetingTimeSelector *mts;
1014 	gboolean is_display_top;
1015 	gint meeting_start_x, meeting_end_x;
1016 
1017 	mts = mts_item->mts;
1018 
1019 	is_display_top = (GTK_WIDGET (GNOME_CANVAS_ITEM (mts_item)->canvas) == mts->display_top) ? TRUE : FALSE;
1020 
1021 	if (is_display_top && y < mts->row_height * 2)
1022 		return E_MEETING_TIME_SELECTOR_POS_NONE;
1023 
1024 	if (!e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x))
1025 		return E_MEETING_TIME_SELECTOR_POS_NONE;
1026 
1027 	if (x >= meeting_end_x - 2 && x <= meeting_end_x + 2)
1028 		return E_MEETING_TIME_SELECTOR_POS_END;
1029 
1030 	if (x >= meeting_start_x - 2 && x <= meeting_start_x + 2)
1031 		return E_MEETING_TIME_SELECTOR_POS_START;
1032 
1033 	return E_MEETING_TIME_SELECTOR_POS_NONE;
1034 }
1035 
1036 static gboolean
1037 e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts,
1038                                                    gint row,
1039                                                    gint x,
1040                                                    gint width,
1041                                                    gint *start_x,
1042                                                    gint *end_x)
1043 {
1044 	EMeetingAttendee *ia;
1045 	EMeetingTime busy_periods_start;
1046 	EMeetingTime busy_periods_end;
1047 
1048 	ia = e_meeting_store_find_attendee_at_row (mts->model, row);
1049 	busy_periods_start = e_meeting_attendee_get_start_busy_range (ia);
1050 	busy_periods_end = e_meeting_attendee_get_end_busy_range (ia);
1051 
1052 	*start_x = -1;
1053 	*end_x = -1;
1054 
1055 	if (!g_date_valid (&busy_periods_start.date)
1056 	    || !g_date_valid (&busy_periods_end.date))
1057 		return FALSE;
1058 
1059 	*start_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_start) - x - 1;
1060 
1061 	*end_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_end) - x;
1062 
1063 	return TRUE;
1064 }
1065 
1066 void
1067 e_meeting_time_selector_item_set_normal_cursor (EMeetingTimeSelectorItem *mts_item)
1068 {
1069 	GnomeCanvas *canvas;
1070 	GdkWindow *window;
1071 
1072 	g_return_if_fail (IS_E_MEETING_TIME_SELECTOR_ITEM (mts_item));
1073 
1074 	canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
1075 	window = gtk_widget_get_window (GTK_WIDGET (canvas));
1076 	if (window)
1077 		gdk_window_set_cursor (window, mts_item->normal_cursor);
1078 }