evolution-3.6.4/calendar/gui/e-day-view-time-item.c

No issues found

   1 /*
   2  * EDayViewTimeItem - canvas item which displays the times down the left of
   3  * the EDayView.
   4  *
   5  * This program is free software; you can redistribute it and/or
   6  * modify it under the terms of the GNU Lesser General Public
   7  * License as published by the Free Software Foundation; either
   8  * version 2 of the License, or (at your option) version 3.
   9  *
  10  * This program is distributed in the hope that it will be useful,
  11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13  * Lesser General Public License for more details.
  14  *
  15  * You should have received a copy of the GNU Lesser General Public
  16  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  17  *
  18  * Authors:
  19  *		Damon Chaplin <damon@ximian.com>
  20  *
  21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  22  */
  23 
  24 #ifdef HAVE_CONFIG_H
  25 #include <config.h>
  26 #endif
  27 
  28 #include <string.h>
  29 #include <gtk/gtk.h>
  30 #include <glib/gi18n.h>
  31 
  32 #include "e-day-view-time-item.h"
  33 #include "calendar-config.h"
  34 #include <widgets/e-timezone-dialog/e-timezone-dialog.h>
  35 
  36 /* The spacing between items in the time column. GRID_X_PAD is the space down
  37  * either side of the column, i.e. outside the main horizontal grid lines.
  38  * HOUR_L_PAD & HOUR_R_PAD are the spaces on the left & right side of the
  39  * big hour number (this is inside the horizontal grid lines).
  40  * MIN_X_PAD is the spacing either side of the minute number. The smaller
  41  * horizontal grid lines match with this.
  42  * 60_MIN_X_PAD is the space either side of the HH:MM display used when
  43  * we are displaying 60 mins per row (inside the main grid lines).
  44  * LARGE_HOUR_Y_PAD is the offset of the large hour string from the top of the
  45  * row.
  46  * SMALL_FONT_Y_PAD is the offset of the small time/minute string from the top
  47  * of the row. */
  48 #define E_DVTMI_TIME_GRID_X_PAD		4
  49 #define E_DVTMI_HOUR_L_PAD		4
  50 #define E_DVTMI_HOUR_R_PAD		2
  51 #define E_DVTMI_MIN_X_PAD		2
  52 #define E_DVTMI_60_MIN_X_PAD		4
  53 #define E_DVTMI_LARGE_HOUR_Y_PAD	1
  54 #define E_DVTMI_SMALL_FONT_Y_PAD	1
  55 
  56 #define E_DAY_VIEW_TIME_ITEM_GET_PRIVATE(obj) \
  57 	(G_TYPE_INSTANCE_GET_PRIVATE \
  58 	((obj), E_TYPE_DAY_VIEW_TIME_ITEM, EDayViewTimeItemPrivate))
  59 
  60 struct _EDayViewTimeItemPrivate {
  61 	/* The parent EDayView widget. */
  62 	EDayView *day_view;
  63 
  64 	/* The width of the time column. */
  65 	gint column_width;
  66 
  67 	/* TRUE if we are currently dragging the selection times. */
  68 	gboolean dragging_selection;
  69 
  70 	/* The second timezone if shown, or else NULL. */
  71 	icaltimezone *second_zone;
  72 };
  73 
  74 static void	e_day_view_time_item_update	(GnomeCanvasItem *item,
  75 						 const cairo_matrix_t *i2c,
  76 						 gint flags);
  77 static void	e_day_view_time_item_draw	(GnomeCanvasItem *item,
  78 						 cairo_t *cr,
  79 						 gint x,
  80 						 gint y,
  81 						 gint width,
  82 						 gint height);
  83 static GnomeCanvasItem *
  84 		e_day_view_time_item_point	(GnomeCanvasItem *item,
  85 						 gdouble x,
  86 						 gdouble y,
  87 						 gint cx,
  88 						 gint cy);
  89 static gint	e_day_view_time_item_event	(GnomeCanvasItem *item,
  90 						 GdkEvent *event);
  91 static void	e_day_view_time_item_increment_time
  92 						(gint *hour,
  93 						 gint *minute,
  94 						 gint time_divisions);
  95 static void	e_day_view_time_item_show_popup_menu
  96 						(EDayViewTimeItem *time_item,
  97 						 GdkEvent *event);
  98 static void	e_day_view_time_item_on_set_divisions
  99 						(GtkWidget *item,
 100 						 EDayViewTimeItem *time_item);
 101 static void	e_day_view_time_item_on_button_press
 102 						(EDayViewTimeItem *time_item,
 103 						 GdkEvent *event);
 104 static void	e_day_view_time_item_on_button_release
 105 						(EDayViewTimeItem *time_item,
 106 						 GdkEvent *event);
 107 static void	e_day_view_time_item_on_motion_notify
 108 						(EDayViewTimeItem *time_item,
 109 						 GdkEvent *event);
 110 static gint	e_day_view_time_item_convert_position_to_row
 111 						(EDayViewTimeItem *time_item,
 112 						 gint y);
 113 
 114 static void	edvti_second_zone_changed_cb	(GSettings *settings,
 115 						 const gchar *key,
 116 						 gpointer user_data);
 117 
 118 enum {
 119 	PROP_0,
 120 	PROP_DAY_VIEW
 121 };
 122 
 123 G_DEFINE_TYPE (
 124 	EDayViewTimeItem,
 125 	e_day_view_time_item,
 126 	GNOME_TYPE_CANVAS_ITEM)
 127 
 128 static void
 129 day_view_time_item_set_property (GObject *object,
 130                                  guint property_id,
 131                                  const GValue *value,
 132                                  GParamSpec *pspec)
 133 {
 134 	switch (property_id) {
 135 		case PROP_DAY_VIEW:
 136 			e_day_view_time_item_set_day_view (
 137 				E_DAY_VIEW_TIME_ITEM (object),
 138 				g_value_get_object (value));
 139 			return;
 140 	}
 141 
 142 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 143 }
 144 
 145 static void
 146 day_view_time_item_get_property (GObject *object,
 147                                  guint property_id,
 148                                  GValue *value,
 149                                  GParamSpec *pspec)
 150 {
 151 	switch (property_id) {
 152 		case PROP_DAY_VIEW:
 153 			g_value_set_object (
 154 				value, e_day_view_time_item_get_day_view (
 155 				E_DAY_VIEW_TIME_ITEM (object)));
 156 			return;
 157 	}
 158 
 159 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 160 }
 161 
 162 static void
 163 day_view_time_item_dispose (GObject *object)
 164 {
 165 	EDayViewTimeItemPrivate *priv;
 166 
 167 	priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (object);
 168 
 169 	if (priv->day_view != NULL) {
 170 		g_object_unref (priv->day_view);
 171 		priv->day_view = NULL;
 172 	}
 173 
 174 	/* Chain up to parent's dispose() method. */
 175 	G_OBJECT_CLASS (e_day_view_time_item_parent_class)->dispose (object);
 176 }
 177 
 178 static void
 179 day_view_time_item_finalize (GObject *object)
 180 {
 181 	EDayViewTimeItem *time_item;
 182 
 183 	time_item = E_DAY_VIEW_TIME_ITEM (object);
 184 
 185 	calendar_config_remove_notification (
 186 		(CalendarConfigChangedFunc)
 187 		edvti_second_zone_changed_cb, time_item);
 188 
 189 	/* Chain up to parent's dispose() method. */
 190 	G_OBJECT_CLASS (e_day_view_time_item_parent_class)->finalize (object);
 191 }
 192 
 193 static void
 194 e_day_view_time_item_class_init (EDayViewTimeItemClass *class)
 195 {
 196 	GObjectClass *object_class;
 197 	GnomeCanvasItemClass *item_class;
 198 
 199 	g_type_class_add_private (class, sizeof (EDayViewTimeItemPrivate));
 200 
 201 	object_class = G_OBJECT_CLASS (class);
 202 	object_class->set_property = day_view_time_item_set_property;
 203 	object_class->get_property = day_view_time_item_get_property;
 204 	object_class->dispose = day_view_time_item_dispose;
 205 	object_class->finalize = day_view_time_item_finalize;
 206 
 207 	item_class = GNOME_CANVAS_ITEM_CLASS (class);
 208 	item_class->update = e_day_view_time_item_update;
 209 	item_class->draw = e_day_view_time_item_draw;
 210 	item_class->point = e_day_view_time_item_point;
 211 	item_class->event = e_day_view_time_item_event;
 212 
 213 	g_object_class_install_property (
 214 		object_class,
 215 		PROP_DAY_VIEW,
 216 		g_param_spec_object (
 217 			"day-view",
 218 			"Day View",
 219 			NULL,
 220 			E_TYPE_DAY_VIEW,
 221 			G_PARAM_READWRITE));
 222 }
 223 
 224 static void
 225 e_day_view_time_item_init (EDayViewTimeItem *time_item)
 226 {
 227 	gchar *last;
 228 
 229 	time_item->priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (time_item);
 230 
 231 	last = calendar_config_get_day_second_zone ();
 232 
 233 	if (last) {
 234 		if (*last)
 235 			time_item->priv->second_zone =
 236 				icaltimezone_get_builtin_timezone (last);
 237 		g_free (last);
 238 	}
 239 
 240 	calendar_config_add_notification_day_second_zone (
 241 		(CalendarConfigChangedFunc) edvti_second_zone_changed_cb,
 242 		time_item);
 243 }
 244 
 245 static void
 246 e_day_view_time_item_update (GnomeCanvasItem *item,
 247                              const cairo_matrix_t *i2c,
 248                              gint flags)
 249 {
 250 	if (GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update)
 251 		(* GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update) (item, i2c, flags);
 252 
 253 	/* The item covers the entire canvas area. */
 254 	item->x1 = 0;
 255 	item->y1 = 0;
 256 	item->x2 = INT_MAX;
 257 	item->y2 = INT_MAX;
 258 }
 259 
 260 /*
 261  * DRAWING ROUTINES - functions to paint the canvas item.
 262  */
 263 static void
 264 edvti_draw_zone (GnomeCanvasItem *canvas_item,
 265                  cairo_t *cr,
 266                  gint x,
 267                  gint y,
 268                  gint width,
 269                  gint height,
 270                  gint x_offset,
 271                  icaltimezone *use_zone)
 272 {
 273 	EDayView *day_view;
 274 	EDayViewTimeItem *time_item;
 275 	ECalendarView *cal_view;
 276 	ECalModel *model;
 277 	GtkStyle *style;
 278 	const gchar *suffix;
 279 	gchar buffer[64], *midnight_day = NULL, *midnight_month = NULL;
 280 	gint time_divisions;
 281 	gint hour, display_hour, minute, row;
 282 	gint row_y, start_y, large_hour_y_offset, small_font_y_offset;
 283 	gint long_line_x1, long_line_x2, short_line_x1;
 284 	gint large_hour_x2, minute_x2;
 285 	gint hour_width, minute_width, suffix_width;
 286 	gint max_suffix_width, max_minute_or_suffix_width;
 287 	PangoLayout *layout;
 288 	PangoContext *context;
 289 	PangoFontDescription *small_font_desc;
 290 	PangoFontMetrics *large_font_metrics, *small_font_metrics;
 291 	GdkColor fg, dark;
 292 	GdkColor mb_color;
 293 
 294 	time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
 295 	day_view = e_day_view_time_item_get_day_view (time_item);
 296 	g_return_if_fail (day_view != NULL);
 297 
 298 	cal_view = E_CALENDAR_VIEW (day_view);
 299 	model = e_calendar_view_get_model (cal_view);
 300 	time_divisions = e_calendar_view_get_time_divisions (cal_view);
 301 
 302 	style = gtk_widget_get_style (GTK_WIDGET (day_view));
 303 	small_font_desc = style->font_desc;
 304 
 305 	context = gtk_widget_get_pango_context (GTK_WIDGET (day_view));
 306 	large_font_metrics = pango_context_get_metrics (
 307 		context, day_view->large_font_desc,
 308 		pango_context_get_language (context));
 309 	small_font_metrics = pango_context_get_metrics (
 310 		context, small_font_desc,
 311 		pango_context_get_language (context));
 312 
 313 	fg = style->fg[GTK_STATE_NORMAL];
 314 	dark = style->dark[GTK_STATE_NORMAL];
 315 
 316 	/* The start and end of the long horizontal line between hours. */
 317 	long_line_x1 =
 318 		(use_zone ? 0 : E_DVTMI_TIME_GRID_X_PAD) - x + x_offset;
 319 	long_line_x2 =
 320 		time_item->priv->column_width -
 321 		E_DVTMI_TIME_GRID_X_PAD - x -
 322 		(use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0) + x_offset;
 323 
 324 	if (time_divisions == 60) {
 325 		/* The right edge of the complete time string in 60-min
 326 		 * divisions, e.g. "14:00" or "2 pm". */
 327 		minute_x2 = long_line_x2 - E_DVTMI_60_MIN_X_PAD;
 328 
 329 		/* These aren't used for 60-minute divisions, but we initialize
 330 		 * them to keep gcc happy. */
 331 		short_line_x1 = 0;
 332 		large_hour_x2 = 0;
 333 	} else {
 334 		max_suffix_width = MAX (
 335 			day_view->am_string_width,
 336 			day_view->pm_string_width);
 337 
 338 		max_minute_or_suffix_width = MAX (
 339 			max_suffix_width,
 340 			day_view->max_minute_width);
 341 
 342 		/* The start of the short horizontal line between the periods
 343 		 * within each hour. */
 344 		short_line_x1 = long_line_x2 - E_DVTMI_MIN_X_PAD * 2
 345 			- max_minute_or_suffix_width;
 346 
 347 		/* The right edge of the large hour string. */
 348 		large_hour_x2 = short_line_x1 - E_DVTMI_HOUR_R_PAD;
 349 
 350 		/* The right edge of the minute part of the time. */
 351 		minute_x2 = long_line_x2 - E_DVTMI_MIN_X_PAD;
 352 	}
 353 
 354 	/* Start with the first hour & minute shown in the EDayView. */
 355 	hour = day_view->first_hour_shown;
 356 	minute = day_view->first_minute_shown;
 357 
 358 	if (use_zone) {
 359 		/* shift time with a difference between
 360 		 * local time and the other timezone */
 361 		icaltimezone *cal_zone;
 362 		struct icaltimetype tt;
 363 		gint diff;
 364 		struct tm mn;
 365 
 366 		cal_zone = e_calendar_view_get_timezone (
 367 			E_CALENDAR_VIEW (day_view));
 368 		tt = icaltime_from_timet_with_zone (
 369 			day_view->day_starts[0], 0, cal_zone);
 370 
 371 		/* diff is number of minutes */
 372 		diff =(icaltimezone_get_utc_offset (use_zone, &tt, NULL) -
 373 			icaltimezone_get_utc_offset (cal_zone, &tt, NULL)) / 60;
 374 
 375 		tt = icaltime_from_timet_with_zone (day_view->day_starts[0], 0, cal_zone);
 376 		tt.is_date = FALSE;
 377 		icaltime_set_timezone (&tt, cal_zone);
 378 		tt = icaltime_convert_to_zone (tt, use_zone);
 379 
 380 		if (diff != 0) {
 381 			/* shows the next midnight */
 382 			icaltime_adjust (&tt, 1, 0, 0, 0);
 383 		}
 384 
 385 		mn = icaltimetype_to_tm (&tt);
 386 
 387 		/* up to two characters/numbers */
 388 		e_utf8_strftime (buffer, sizeof (buffer), "%d", &mn);
 389 		midnight_day = g_strdup (buffer);
 390 		/* up to three characters, abbreviated month name */
 391 		e_utf8_strftime (buffer, sizeof (buffer), "%b", &mn);
 392 		midnight_month = g_strdup (buffer);
 393 
 394 		minute += (diff % 60);
 395 		hour += (diff / 60) + (minute / 60);
 396 
 397 		minute = minute % 60;
 398 		if (minute < 0) {
 399 			hour--;
 400 			minute += 60;
 401 		}
 402 
 403 		hour = (hour + 48) % 24;
 404 	}
 405 
 406 	/* The offset of the large hour string from the top of the row. */
 407 	large_hour_y_offset = E_DVTMI_LARGE_HOUR_Y_PAD;
 408 
 409 	/* The offset of the small time/minute string from top of row. */
 410 	small_font_y_offset = E_DVTMI_SMALL_FONT_Y_PAD;
 411 
 412 	/* Calculate the minimum y position of the first row we need to draw.
 413 	 * This is normally one row height above the 0 position, but if we
 414 	 * are using the large font we may have to go back a bit further. */
 415 	start_y = 0 - MAX (day_view->row_height,
 416 			   (pango_font_metrics_get_ascent (large_font_metrics) +
 417 			    pango_font_metrics_get_descent (large_font_metrics)) / PANGO_SCALE +
 418 			   E_DVTMI_LARGE_HOUR_Y_PAD);
 419 
 420 	/* Draw the Marcus Bains Line first, so it appears under other elements. */
 421 	if (e_day_view_marcus_bains_get_show_line (day_view)) {
 422 		struct icaltimetype time_now;
 423 		gint marcus_bains_y;
 424 
 425 		cairo_save (cr);
 426 		gdk_cairo_set_source_color (
 427 			cr, &day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE]);
 428 
 429 		if (day_view->marcus_bains_time_bar_color &&
 430 			gdk_color_parse (
 431 				day_view->marcus_bains_time_bar_color,
 432 				&mb_color)) {
 433 			gdk_cairo_set_source_color (cr, &mb_color);
 434 		} else
 435 			mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
 436 
 437 		time_now = icaltime_current_time_with_zone (
 438 			e_calendar_view_get_timezone (
 439 			E_CALENDAR_VIEW (day_view)));
 440 		marcus_bains_y =
 441 			(time_now.hour * 60 + time_now.minute) *
 442 			day_view->row_height / time_divisions - y;
 443 		cairo_set_line_width (cr, 1.5);
 444 		cairo_move_to (
 445 			cr, long_line_x1 -
 446 			(use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0),
 447 			marcus_bains_y);
 448 		cairo_line_to (cr, long_line_x2, marcus_bains_y);
 449 		cairo_stroke (cr);
 450 		cairo_restore (cr);
 451 	} else {
 452 		mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
 453 
 454 		if (day_view->marcus_bains_time_bar_color &&
 455 			gdk_color_parse (
 456 				day_view->marcus_bains_time_bar_color,
 457 				&mb_color)) {
 458 			gdk_cairo_set_source_color (cr, &mb_color);
 459 		}
 460 	}
 461 
 462 	/* Step through each row, drawing the times and the horizontal lines
 463 	 * between them. */
 464 	for (row = 0, row_y = 0 - y;
 465 	     row < day_view->rows && row_y < height;
 466 	     row++, row_y += day_view->row_height) {
 467 		gboolean show_midnight_date;
 468 
 469 		show_midnight_date =
 470 			use_zone && hour == 0 &&
 471 			(minute == 0 || time_divisions == 60) &&
 472 			midnight_day && midnight_month;
 473 
 474 		/* If the row is above the first row we want to draw just
 475 		 * increment the time and skip to the next row. */
 476 		if (row_y < start_y) {
 477 			e_day_view_time_item_increment_time (
 478 				&hour, &minute, time_divisions);
 479 			continue;
 480 		}
 481 
 482 		/* Calculate the actual hour number to display. For 12-hour
 483 		 * format we convert 0-23 to 12-11am / 12 - 11pm. */
 484 		e_day_view_convert_time_to_display (
 485 			day_view, hour,
 486 			&display_hour,
 487 			&suffix, &suffix_width);
 488 
 489 		if (time_divisions == 60) {
 490 			/* 60 minute intervals - draw a long horizontal line
 491 			 * between hours and display as one long string,
 492 			 * e.g. "14:00" or "2 pm". */
 493 			cairo_save (cr);
 494 			gdk_cairo_set_source_color (cr, &dark);
 495 			cairo_save (cr);
 496 			cairo_set_line_width (cr, 0.7);
 497 			cairo_move_to (cr, long_line_x1, row_y);
 498 			cairo_line_to (cr, long_line_x2, row_y);
 499 			cairo_stroke (cr);
 500 			cairo_restore (cr);
 501 
 502 			if (show_midnight_date) {
 503 				strcpy (buffer, midnight_day);
 504 				strcat (buffer, " ");
 505 				strcat (buffer, midnight_month);
 506 			} else if (e_cal_model_get_use_24_hour_format (model)) {
 507 				g_snprintf (
 508 					buffer, sizeof (buffer), "%i:%02i",
 509 					display_hour, minute);
 510 			} else {
 511 				g_snprintf (
 512 					buffer, sizeof (buffer), "%i %s",
 513 					display_hour, suffix);
 514 			}
 515 
 516 			cairo_save (cr);
 517 			if (show_midnight_date)
 518 				gdk_cairo_set_source_color (cr, &mb_color);
 519 			else
 520 				gdk_cairo_set_source_color (cr, &fg);
 521 			layout = pango_cairo_create_layout (cr);
 522 			pango_layout_set_text (layout, buffer, -1);
 523 			pango_layout_get_pixel_size (layout, &minute_width, NULL);
 524 			cairo_translate (
 525 				cr, minute_x2 - minute_width,
 526 				row_y + small_font_y_offset);
 527 			pango_cairo_update_layout (cr, layout);
 528 			pango_cairo_show_layout (cr, layout);
 529 			cairo_restore (cr);
 530 
 531 			g_object_unref (layout);
 532 		} else {
 533 			/* 5/10/15/30 minute intervals. */
 534 
 535 			if (minute == 0) {
 536 				/* On the hour - draw a long horizontal line
 537 				 * before the hour and display the hour in the
 538 				 * large font. */
 539 
 540 				cairo_save (cr);
 541 				gdk_cairo_set_source_color (cr, &dark);
 542 				if (show_midnight_date)
 543 					strcpy (buffer, midnight_day);
 544 				else
 545 					g_snprintf (
 546 						buffer, sizeof (buffer), "%i",
 547 						display_hour);
 548 
 549 				cairo_set_line_width (cr, 0.7);
 550 				cairo_move_to (cr, long_line_x1, row_y);
 551 				cairo_line_to (cr, long_line_x2, row_y);
 552 				cairo_stroke (cr);
 553 				cairo_restore (cr);
 554 
 555 				cairo_save (cr);
 556 				if (show_midnight_date)
 557 					gdk_cairo_set_source_color (cr, &mb_color);
 558 				else
 559 					gdk_cairo_set_source_color (cr, &fg);
 560 				layout = pango_cairo_create_layout (cr);
 561 				pango_layout_set_text (layout, buffer, -1);
 562 				pango_layout_set_font_description (
 563 					layout, day_view->large_font_desc);
 564 				pango_layout_get_pixel_size (
 565 					layout, &hour_width, NULL);
 566 				cairo_translate (
 567 					cr, large_hour_x2 - hour_width,
 568 					row_y + large_hour_y_offset);
 569 				pango_cairo_update_layout (cr, layout);
 570 				pango_cairo_show_layout (cr, layout);
 571 				cairo_restore (cr);
 572 
 573 				g_object_unref (layout);
 574 			} else {
 575 				/* Within the hour - draw a short line before
 576 				 * the time. */
 577 				cairo_save (cr);
 578 				gdk_cairo_set_source_color (cr, &dark);
 579 				cairo_set_line_width (cr, 0.7);
 580 				cairo_move_to (cr, short_line_x1, row_y);
 581 				cairo_line_to (cr, long_line_x2, row_y);
 582 				cairo_stroke (cr);
 583 				cairo_restore (cr);
 584 			}
 585 
 586 			/* Normally we display the minute in each
 587 			 * interval, but when using 30-minute intervals
 588 			 * we don't display the '30'. */
 589 			if (time_divisions != 30 || minute != 30) {
 590 				/* In 12-hour format we display 'am' or 'pm'
 591 				 * instead of '00'. */
 592 				if (show_midnight_date)
 593 					strcpy (buffer, midnight_month);
 594 				else if (minute == 0
 595 				    && !e_cal_model_get_use_24_hour_format (model)) {
 596 					strcpy (buffer, suffix);
 597 				} else {
 598 					g_snprintf (
 599 						buffer, sizeof (buffer),
 600 						"%02i", minute);
 601 				}
 602 
 603 				cairo_save (cr);
 604 				if (show_midnight_date)
 605 					gdk_cairo_set_source_color (cr, &mb_color);
 606 				else
 607 					gdk_cairo_set_source_color (cr, &fg);
 608 				layout = pango_cairo_create_layout (cr);
 609 				pango_layout_set_text (layout, buffer, -1);
 610 				pango_layout_set_font_description (
 611 					layout, day_view->small_font_desc);
 612 				pango_layout_get_pixel_size (
 613 					layout, &minute_width, NULL);
 614 				cairo_translate (
 615 					cr, minute_x2 - minute_width,
 616 					row_y + small_font_y_offset);
 617 				pango_cairo_update_layout (cr, layout);
 618 				pango_cairo_show_layout (cr, layout);
 619 				cairo_restore (cr);
 620 
 621 				g_object_unref (layout);
 622 			}
 623 		}
 624 
 625 		e_day_view_time_item_increment_time (
 626 			&hour, &minute,
 627 			time_divisions);
 628 	}
 629 
 630 	pango_font_metrics_unref (large_font_metrics);
 631 	pango_font_metrics_unref (small_font_metrics);
 632 
 633 	g_free (midnight_day);
 634 	g_free (midnight_month);
 635 }
 636 
 637 static void
 638 e_day_view_time_item_draw (GnomeCanvasItem *canvas_item,
 639                            cairo_t *cr,
 640                            gint x,
 641                            gint y,
 642                            gint width,
 643                            gint height)
 644 {
 645 	EDayViewTimeItem *time_item;
 646 
 647 	time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
 648 	g_return_if_fail (time_item != NULL);
 649 
 650 	edvti_draw_zone (canvas_item, cr, x, y, width, height, 0, NULL);
 651 
 652 	if (time_item->priv->second_zone)
 653 		edvti_draw_zone (
 654 			canvas_item, cr, x, y, width, height,
 655 			time_item->priv->column_width,
 656 			time_item->priv->second_zone);
 657 }
 658 
 659 /* Increment the time by the 5/10/15/30/60 minute interval.
 660  * Note that time_divisions is never > 60, so we never have to
 661  * worry about adding more than 60 minutes. */
 662 static void
 663 e_day_view_time_item_increment_time (gint *hour,
 664                                      gint *minute,
 665                                      gint time_divisions)
 666 {
 667 	*minute += time_divisions;
 668 	if (*minute >= 60) {
 669 		*minute -= 60;
 670 		/* Currently we never wrap around to the next day, but
 671 		 * we may do if we display extra timezones. */
 672 		*hour = (*hour + 1) % 24;
 673 	}
 674 }
 675 
 676 static GnomeCanvasItem *
 677 e_day_view_time_item_point (GnomeCanvasItem *item,
 678                             gdouble x,
 679                             gdouble y,
 680                             gint cx,
 681                             gint cy)
 682 {
 683 	return item;
 684 }
 685 
 686 static gint
 687 e_day_view_time_item_event (GnomeCanvasItem *item,
 688                             GdkEvent *event)
 689 {
 690 	EDayViewTimeItem *time_item;
 691 
 692 	time_item = E_DAY_VIEW_TIME_ITEM (item);
 693 
 694 	switch (event->type) {
 695 	case GDK_BUTTON_PRESS:
 696 		if (event->button.button == 1) {
 697 			e_day_view_time_item_on_button_press (time_item, event);
 698 		} else if (event->button.button == 3) {
 699 			e_day_view_time_item_show_popup_menu (time_item, event);
 700 			return TRUE;
 701 		}
 702 		break;
 703 	case GDK_BUTTON_RELEASE:
 704 		if (event->button.button == 1)
 705 			e_day_view_time_item_on_button_release (
 706 				time_item, event);
 707 		break;
 708 
 709 	case GDK_MOTION_NOTIFY:
 710 		e_day_view_time_item_on_motion_notify (time_item, event);
 711 		break;
 712 
 713 	default:
 714 		break;
 715 	}
 716 
 717 	return FALSE;
 718 }
 719 
 720 static void
 721 edvti_second_zone_changed_cb (GSettings *settings,
 722                               const gchar *key,
 723                               gpointer user_data)
 724 {
 725 	EDayViewTimeItem *time_item = user_data;
 726 	EDayView *day_view;
 727 	gchar *location;
 728 
 729 	g_return_if_fail (user_data != NULL);
 730 	g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
 731 
 732 	location = calendar_config_get_day_second_zone ();
 733 	time_item->priv->second_zone =
 734 		location ? icaltimezone_get_builtin_timezone (location) : NULL;
 735 	g_free (location);
 736 
 737 	day_view = e_day_view_time_item_get_day_view (time_item);
 738 	gtk_widget_set_size_request (
 739 		day_view->time_canvas,
 740 		e_day_view_time_item_get_column_width (time_item), -1);
 741 	gtk_widget_queue_draw (day_view->time_canvas);
 742 }
 743 
 744 static void
 745 edvti_on_select_zone (GtkWidget *item,
 746                       EDayViewTimeItem *time_item)
 747 {
 748 	calendar_config_select_day_second_zone ();
 749 }
 750 
 751 static void
 752 edvti_on_set_zone (GtkWidget *item,
 753                    EDayViewTimeItem *time_item)
 754 {
 755 	if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
 756 		return;
 757 
 758 	calendar_config_set_day_second_zone (
 759 		g_object_get_data (G_OBJECT (item), "timezone"));
 760 }
 761 
 762 static void
 763 e_day_view_time_item_show_popup_menu (EDayViewTimeItem *time_item,
 764                                       GdkEvent *event)
 765 {
 766 	static gint divisions[] = { 60, 30, 15, 10, 5 };
 767 	EDayView *day_view;
 768 	ECalendarView *cal_view;
 769 	GtkWidget *menu, *item, *submenu;
 770 	gchar buffer[256];
 771 	GSList *group = NULL, *recent_zones, *s;
 772 	gint current_divisions, i;
 773 	icaltimezone *zone;
 774 
 775 	day_view = e_day_view_time_item_get_day_view (time_item);
 776 	g_return_if_fail (day_view != NULL);
 777 
 778 	cal_view = E_CALENDAR_VIEW (day_view);
 779 	current_divisions = e_calendar_view_get_time_divisions (cal_view);
 780 
 781 	menu = gtk_menu_new ();
 782 
 783 	/* Make sure the menu is destroyed when it disappears. */
 784 	g_signal_connect (
 785 		menu, "selection-done",
 786 		G_CALLBACK (gtk_widget_destroy), NULL);
 787 
 788 	for (i = 0; i < G_N_ELEMENTS (divisions); i++) {
 789 		g_snprintf (
 790 			buffer, sizeof (buffer),
 791 			/* Translators: %02i is the number of minutes;
 792 			 * this is a context menu entry to change the
 793 			 * length of the time division in the calendar
 794 			 * day view, e.g. a day is displayed in
 795 			 * 24 "60 minute divisions" or
 796 			 * 48 "30 minute divisions". */
 797 			_("%02i minute divisions"), divisions[i]);
 798 		item = gtk_radio_menu_item_new_with_label (group, buffer);
 799 		group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
 800 		gtk_widget_show (item);
 801 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
 802 
 803 		if (current_divisions == divisions[i])
 804 			gtk_check_menu_item_set_active (
 805 				GTK_CHECK_MENU_ITEM (item), TRUE);
 806 
 807 		g_object_set_data (
 808 			G_OBJECT (item), "divisions",
 809 			GINT_TO_POINTER (divisions[i]));
 810 
 811 		g_signal_connect (
 812 			item, "toggled",
 813 			G_CALLBACK (e_day_view_time_item_on_set_divisions),
 814 			time_item);
 815 	}
 816 
 817 	item = gtk_separator_menu_item_new ();
 818 	gtk_widget_show (item);
 819 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
 820 
 821 	submenu = gtk_menu_new ();
 822 	item = gtk_menu_item_new_with_label (_("Show the second time zone"));
 823 	gtk_widget_show (item);
 824 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
 825 	gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
 826 
 827 	zone = e_calendar_view_get_timezone (E_CALENDAR_VIEW (day_view));
 828 	if (zone)
 829 		item = gtk_menu_item_new_with_label (icaltimezone_get_display_name (zone));
 830 	else
 831 		item = gtk_menu_item_new_with_label ("---");
 832 	gtk_widget_set_sensitive (item, FALSE);
 833 	gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
 834 
 835 	item = gtk_separator_menu_item_new ();
 836 	gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
 837 
 838 	group = NULL;
 839 	item = gtk_radio_menu_item_new_with_label (group, C_("cal-second-zone", "None"));
 840 	group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
 841 	if (!time_item->priv->second_zone)
 842 		gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
 843 	gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
 844 	g_signal_connect (
 845 		item, "toggled",
 846 		G_CALLBACK (edvti_on_set_zone), time_item);
 847 
 848 	recent_zones = calendar_config_get_day_second_zones ();
 849 	for (s = recent_zones; s != NULL; s = s->next) {
 850 		zone = icaltimezone_get_builtin_timezone (s->data);
 851 		if (!zone)
 852 			continue;
 853 
 854 		item = gtk_radio_menu_item_new_with_label (
 855 			group, icaltimezone_get_display_name (zone));
 856 		group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
 857 		/* both comes from builtin, thus no problem to compare pointers */
 858 		if (zone == time_item->priv->second_zone)
 859 			gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
 860 		gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
 861 		g_object_set_data_full (
 862 			G_OBJECT (item), "timezone",
 863 			g_strdup (s->data), g_free);
 864 		g_signal_connect (
 865 			item, "toggled",
 866 			G_CALLBACK (edvti_on_set_zone), time_item);
 867 	}
 868 	calendar_config_free_day_second_zones (recent_zones);
 869 
 870 	item = gtk_separator_menu_item_new ();
 871 	gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
 872 
 873 	item = gtk_menu_item_new_with_label (_("Select..."));
 874 	g_signal_connect (
 875 		item, "activate",
 876 		G_CALLBACK (edvti_on_select_zone), time_item);
 877 	gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
 878 
 879 	gtk_widget_show_all (submenu);
 880 
 881 	gtk_menu_popup (
 882 		GTK_MENU (menu), NULL, NULL, NULL, NULL,
 883 		event->button.button, event->button.time);
 884 }
 885 
 886 static void
 887 e_day_view_time_item_on_set_divisions (GtkWidget *item,
 888                                        EDayViewTimeItem *time_item)
 889 {
 890 	EDayView *day_view;
 891 	ECalendarView *cal_view;
 892 	gint divisions;
 893 
 894 	day_view = e_day_view_time_item_get_day_view (time_item);
 895 	g_return_if_fail (day_view != NULL);
 896 
 897 	if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
 898 		return;
 899 
 900 	divisions = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "divisions"));
 901 
 902 	cal_view = E_CALENDAR_VIEW (day_view);
 903 	e_calendar_view_set_time_divisions (cal_view, divisions);
 904 }
 905 
 906 static void
 907 e_day_view_time_item_on_button_press (EDayViewTimeItem *time_item,
 908                                       GdkEvent *event)
 909 {
 910 	GdkWindow *window;
 911 	EDayView *day_view;
 912 	GnomeCanvas *canvas;
 913 	gint row;
 914 
 915 	day_view = e_day_view_time_item_get_day_view (time_item);
 916 	g_return_if_fail (day_view != NULL);
 917 
 918 	canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
 919 
 920 	row = e_day_view_time_item_convert_position_to_row (
 921 		time_item,
 922 		event->button.y);
 923 
 924 	if (row == -1)
 925 		return;
 926 
 927 	if (!gtk_widget_has_focus (GTK_WIDGET (day_view)))
 928 		gtk_widget_grab_focus (GTK_WIDGET (day_view));
 929 
 930 	window = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
 931 
 932 	if (gdk_pointer_grab (window, FALSE,
 933 			      GDK_POINTER_MOTION_MASK
 934 			      | GDK_BUTTON_RELEASE_MASK,
 935 			      NULL, NULL, event->button.time) == 0) {
 936 		e_day_view_start_selection (day_view, -1, row);
 937 		time_item->priv->dragging_selection = TRUE;
 938 	}
 939 }
 940 
 941 static void
 942 e_day_view_time_item_on_button_release (EDayViewTimeItem *time_item,
 943                                         GdkEvent *event)
 944 {
 945 	EDayView *day_view;
 946 
 947 	day_view = e_day_view_time_item_get_day_view (time_item);
 948 	g_return_if_fail (day_view != NULL);
 949 
 950 	if (time_item->priv->dragging_selection) {
 951 		gdk_pointer_ungrab (event->button.time);
 952 		e_day_view_finish_selection (day_view);
 953 		e_day_view_stop_auto_scroll (day_view);
 954 	}
 955 
 956 	time_item->priv->dragging_selection = FALSE;
 957 }
 958 
 959 static void
 960 e_day_view_time_item_on_motion_notify (EDayViewTimeItem *time_item,
 961                                        GdkEvent *event)
 962 {
 963 	EDayView *day_view;
 964 	GnomeCanvas *canvas;
 965 	gdouble window_y;
 966 	gint y, row;
 967 
 968 	if (!time_item->priv->dragging_selection)
 969 		return;
 970 
 971 	day_view = e_day_view_time_item_get_day_view (time_item);
 972 	g_return_if_fail (day_view != NULL);
 973 
 974 	canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
 975 
 976 	y = event->motion.y;
 977 	row = e_day_view_time_item_convert_position_to_row (time_item, y);
 978 
 979 	if (row != -1) {
 980 		gnome_canvas_world_to_window (
 981 			canvas, 0, event->motion.y,
 982 			NULL, &window_y);
 983 		e_day_view_update_selection (day_view, -1, row);
 984 		e_day_view_check_auto_scroll (day_view, -1, (gint) window_y);
 985 	}
 986 }
 987 
 988 /* Returns the row corresponding to the y position, or -1. */
 989 static gint
 990 e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *time_item,
 991                                               gint y)
 992 {
 993 	EDayView *day_view;
 994 	gint row;
 995 
 996 	day_view = e_day_view_time_item_get_day_view (time_item);
 997 	g_return_val_if_fail (day_view != NULL, -1);
 998 
 999 	if (y < 0)
1000 		return -1;
1001 
1002 	row = y / day_view->row_height;
1003 	if (row >= day_view->rows)
1004 		return -1;
1005 
1006 	return row;
1007 }
1008 
1009 EDayView *
1010 e_day_view_time_item_get_day_view (EDayViewTimeItem *time_item)
1011 {
1012 	g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
1013 
1014 	return time_item->priv->day_view;
1015 }
1016 
1017 void
1018 e_day_view_time_item_set_day_view (EDayViewTimeItem *time_item,
1019                                    EDayView *day_view)
1020 {
1021 	g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
1022 	g_return_if_fail (E_IS_DAY_VIEW (day_view));
1023 
1024 	if (time_item->priv->day_view == day_view)
1025 		return;
1026 
1027 	if (time_item->priv->day_view != NULL)
1028 		g_object_unref (time_item->priv->day_view);
1029 
1030 	time_item->priv->day_view = g_object_ref (day_view);
1031 
1032 	g_object_notify (G_OBJECT (time_item), "day-view");
1033 }
1034 
1035 /* Returns the minimum width needed for the column, by adding up all the
1036  * maximum widths of the strings. The string widths are all calculated in
1037  * the style_set handlers of EDayView and EDayViewTimeCanvas. */
1038 gint
1039 e_day_view_time_item_get_column_width (EDayViewTimeItem *time_item)
1040 {
1041 	EDayView *day_view;
1042 	GtkStyle *style;
1043 	gint digit, large_digit_width, max_large_digit_width = 0;
1044 	gint max_suffix_width, max_minute_or_suffix_width;
1045 	gint column_width_default, column_width_60_min_rows;
1046 
1047 	day_view = e_day_view_time_item_get_day_view (time_item);
1048 	g_return_val_if_fail (day_view != NULL, 0);
1049 
1050 	style = gtk_widget_get_style (GTK_WIDGET (day_view));
1051 	g_return_val_if_fail (style != NULL, 0);
1052 
1053 	/* Find the maximum width a digit can have. FIXME: We could use pango's
1054 	 * approximation function, but I worry it won't be precise enough. Also
1055 	 * it needs a language tag that I don't know where to get. */
1056 	for (digit = '0'; digit <= '9'; digit++) {
1057 		PangoLayout *layout;
1058 		gchar digit_str[2];
1059 
1060 		digit_str[0] = digit;
1061 		digit_str[1] = '\0';
1062 
1063 		layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), digit_str);
1064 		pango_layout_set_font_description (layout, day_view->large_font_desc);
1065 		pango_layout_get_pixel_size (layout, &large_digit_width, NULL);
1066 
1067 		g_object_unref (layout);
1068 
1069 		max_large_digit_width = MAX (
1070 			max_large_digit_width,
1071 			large_digit_width);
1072 	}
1073 
1074 	/* Calculate the width of each time column, using the maximum of the
1075 	 * default format with large hour numbers, and the 60-min divisions
1076 	 * format which uses small text. */
1077 	max_suffix_width = MAX (
1078 		day_view->am_string_width,
1079 		day_view->pm_string_width);
1080 
1081 	max_minute_or_suffix_width = MAX (
1082 		max_suffix_width,
1083 		day_view->max_minute_width);
1084 
1085 	column_width_default = max_large_digit_width * 2
1086 		+ max_minute_or_suffix_width
1087 		+ E_DVTMI_MIN_X_PAD * 2
1088 		+ E_DVTMI_HOUR_L_PAD
1089 		+ E_DVTMI_HOUR_R_PAD
1090 		+ E_DVTMI_TIME_GRID_X_PAD * 2;
1091 
1092 	column_width_60_min_rows = day_view->max_small_hour_width
1093 		+ day_view->colon_width
1094 		+ max_minute_or_suffix_width
1095 		+ E_DVTMI_60_MIN_X_PAD * 2
1096 		+ E_DVTMI_TIME_GRID_X_PAD * 2;
1097 
1098 	time_item->priv->column_width =
1099 		MAX (column_width_default, column_width_60_min_rows);
1100 
1101 	if (time_item->priv->second_zone)
1102 		return (2 * time_item->priv->column_width) -
1103 			E_DVTMI_TIME_GRID_X_PAD;
1104 
1105 	return time_item->priv->column_width;
1106 }
1107 
1108 icaltimezone *
1109 e_day_view_time_item_get_second_zone (EDayViewTimeItem *time_item)
1110 {
1111 	g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
1112 
1113 	return time_item->priv->second_zone;
1114 }