evolution-3.6.4/widgets/misc/e-calendar-item.c

No issues found

   1 /*
   2  * ECalendarItem - canvas item displaying a calendar.
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  * Authors:
  18  *		Damon Chaplin <damon@ximian.com>
  19  *		Bolian Yin <bolian.yin@sun.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 <libebackend/libebackend.h>
  29 
  30 #include "e-calendar-item.h"
  31 #include "ea-widgets.h"
  32 
  33 #include <time.h>
  34 #include <stdlib.h>
  35 #include <string.h>
  36 #include <gtk/gtk.h>
  37 #include <gdk/gdkkeysyms.h>
  38 #include <glib/gi18n.h>
  39 #include <e-util/e-util.h>
  40 
  41 static const gint e_calendar_item_days_in_month[12] = {
  42 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  43 };
  44 
  45 #define DAYS_IN_MONTH(year, month) \
  46   e_calendar_item_days_in_month[month] + (((month) == 1 \
  47   && ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))) ? 1 : 0)
  48 
  49 static void	e_calendar_item_dispose		(GObject *object);
  50 static void	e_calendar_item_get_property	(GObject *object,
  51 						 guint property_id,
  52 						 GValue *value,
  53 						 GParamSpec *pspec);
  54 static void	e_calendar_item_set_property	(GObject *object,
  55 						 guint property_id,
  56 						 const GValue *value,
  57 						 GParamSpec *pspec);
  58 static void	e_calendar_item_realize		(GnomeCanvasItem *item);
  59 static void	e_calendar_item_unmap		(GnomeCanvasItem *item);
  60 static void	e_calendar_item_update		(GnomeCanvasItem *item,
  61 						 const cairo_matrix_t *i2c,
  62 						 gint flags);
  63 static void	e_calendar_item_draw		(GnomeCanvasItem *item,
  64 						 cairo_t *cr,
  65 						 gint x,
  66 						 gint y,
  67 						 gint width,
  68 						 gint height);
  69 static void	e_calendar_item_draw_month	(ECalendarItem *calitem,
  70 						 cairo_t *cr,
  71 						 gint x,
  72 						 gint y,
  73 						 gint width,
  74 						 gint height,
  75 						 gint row,
  76 						 gint col);
  77 static void	e_calendar_item_draw_day_numbers
  78 						(ECalendarItem *calitem,
  79 						 cairo_t *cr,
  80 						 gint width,
  81 						 gint height,
  82 						 gint row,
  83 						 gint col,
  84 						 gint year,
  85 						 gint month,
  86 						 gint start_weekday,
  87 						 gint cells_x,
  88 						 gint cells_y);
  89 static GnomeCanvasItem *e_calendar_item_point	(GnomeCanvasItem *item,
  90 						 gdouble x,
  91 						 gdouble y,
  92 						 gint cx,
  93 						 gint cy);
  94 static void	e_calendar_item_stop_selecting	(ECalendarItem *calitem,
  95 						 guint32 time);
  96 static void	e_calendar_item_selection_add_days
  97 						(ECalendarItem *calitem,
  98 						 gint n_days,
  99 						 gboolean multi_selection);
 100 static gint	e_calendar_item_key_press_event	(ECalendarItem *item,
 101 						 GdkEvent *event);
 102 static gint	e_calendar_item_event		(GnomeCanvasItem *item,
 103 						 GdkEvent *event);
 104 static void	e_calendar_item_bounds		(GnomeCanvasItem *item,
 105 						 gdouble *x1,
 106 						 gdouble *y1,
 107 						 gdouble *x2,
 108 						 gdouble *y2);
 109 
 110 static gboolean	e_calendar_item_button_press	(ECalendarItem *calitem,
 111 						 GdkEvent *event);
 112 static gboolean	e_calendar_item_button_release	(ECalendarItem *calitem,
 113 						 GdkEvent *event);
 114 static gboolean	e_calendar_item_motion		(ECalendarItem *calitem,
 115 						 GdkEvent *event);
 116 
 117 static gboolean	e_calendar_item_convert_position_to_day
 118 						(ECalendarItem *calitem,
 119 						 gint x,
 120 						 gint y,
 121 						 gboolean round_empty_positions,
 122 						 gint *month_offset,
 123 						 gint *day,
 124 						 gboolean *entire_week);
 125 static void	e_calendar_item_get_month_info	(ECalendarItem *calitem,
 126 						 gint row,
 127 						 gint col,
 128 						 gint *first_day_offset,
 129 						 gint *days_in_month,
 130 						 gint *days_in_prev_month);
 131 static void	e_calendar_item_recalc_sizes	(ECalendarItem *calitem);
 132 
 133 static void	e_calendar_item_get_day_style	(ECalendarItem *calitem,
 134 						 gint year,
 135 						 gint month,
 136 						 gint day,
 137 						 gint day_style,
 138 						 gboolean today,
 139 						 gboolean prev_or_next_month,
 140 						 gboolean selected,
 141 						 gboolean has_focus,
 142 						 gboolean drop_target,
 143 						 GdkColor **bg_color,
 144 						 GdkColor **fg_color,
 145 						 GdkColor **box_color,
 146 						 gboolean *bold,
 147 						 gboolean *italic);
 148 static void	e_calendar_item_check_selection_end
 149 						(ECalendarItem *calitem,
 150 						 gint start_month,
 151 						 gint start_day,
 152 						 gint *end_month,
 153 						 gint *end_day);
 154 static void	e_calendar_item_check_selection_start
 155 						(ECalendarItem *calitem,
 156 						 gint *start_month,
 157 						 gint *start_day,
 158 						 gint end_month,
 159 						 gint end_day);
 160 static void	e_calendar_item_add_days_to_selection
 161 						(ECalendarItem *calitem,
 162 						 gint days);
 163 static void	e_calendar_item_round_up_selection
 164 						(ECalendarItem *calitem,
 165 						 gint *month_offset,
 166 						 gint *day);
 167 static void	e_calendar_item_round_down_selection
 168 						(ECalendarItem *calitem,
 169 						 gint *month_offset,
 170 						 gint *day);
 171 static gint	e_calendar_item_get_inclusive_days
 172 						(ECalendarItem *calitem,
 173 						 gint start_month_offset,
 174 						 gint start_day,
 175 						 gint end_month_offset,
 176 						 gint end_day);
 177 static void	e_calendar_item_ensure_valid_day
 178 						(ECalendarItem *calitem,
 179 						 gint *month_offset,
 180 						 gint *day);
 181 static gboolean	e_calendar_item_ensure_days_visible
 182 						(ECalendarItem *calitem,
 183 						 gint start_year,
 184 						 gint start_month,
 185 						 gint start_day,
 186 						 gint end_year,
 187 						 gint end_month,
 188 						 gint end_day,
 189 						 gboolean emission);
 190 static void	e_calendar_item_show_popup_menu	(ECalendarItem *calitem,
 191 						 GdkEventButton *event,
 192 						 gint month_offset);
 193 static void	e_calendar_item_on_menu_item_activate
 194 						(GtkWidget *menuitem,
 195 						 ECalendarItem *calitem);
 196 static void	e_calendar_item_position_menu	(GtkMenu *menu,
 197 						 gint *x,
 198 						 gint *y,
 199 						 gboolean *push_in,
 200 						 gpointer user_data);
 201 static void	e_calendar_item_date_range_changed
 202 						(ECalendarItem *calitem);
 203 static void	e_calendar_item_queue_signal_emission
 204 						(ECalendarItem *calitem);
 205 static gboolean	e_calendar_item_signal_emission_idle_cb
 206 						(gpointer data);
 207 static void	e_calendar_item_set_selection_if_emission
 208 						(ECalendarItem *calitem,
 209 						 const GDate *start_date,
 210 						 const GDate *end_date,
 211 						 gboolean emission);
 212 
 213 /* Our arguments. */
 214 enum {
 215 	PROP_0,
 216 	PROP_YEAR,
 217 	PROP_MONTH,
 218 	PROP_X1,
 219 	PROP_Y1,
 220 	PROP_X2,
 221 	PROP_Y2,
 222 	PROP_FONT_DESC,
 223 	PROP_WEEK_NUMBER_FONT,
 224 	PROP_WEEK_NUMBER_FONT_DESC,
 225 	PROP_ROW_HEIGHT,
 226 	PROP_COLUMN_WIDTH,
 227 	PROP_MINIMUM_ROWS,
 228 	PROP_MINIMUM_COLUMNS,
 229 	PROP_MAXIMUM_ROWS,
 230 	PROP_MAXIMUM_COLUMNS,
 231 	PROP_WEEK_START_DAY,
 232 	PROP_SHOW_WEEK_NUMBERS,
 233 	PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
 234 	PROP_MAXIMUM_DAYS_SELECTED,
 235 	PROP_DAYS_TO_START_WEEK_SELECTION,
 236 	PROP_MOVE_SELECTION_WHEN_MOVING,
 237 	PROP_PRESERVE_DAY_WHEN_MOVING,
 238 	PROP_DISPLAY_POPUP
 239 };
 240 
 241 enum {
 242   DATE_RANGE_CHANGED,
 243   SELECTION_CHANGED,
 244   SELECTION_PREVIEW_CHANGED,
 245   LAST_SIGNAL
 246 };
 247 
 248 static guint e_calendar_item_signals[LAST_SIGNAL] = { 0 };
 249 
 250 G_DEFINE_TYPE_WITH_CODE (
 251 	ECalendarItem,
 252 	e_calendar_item,
 253 	GNOME_TYPE_CANVAS_ITEM,
 254 	G_IMPLEMENT_INTERFACE (
 255 		E_TYPE_EXTENSIBLE, NULL))
 256 
 257 static void
 258 e_calendar_item_class_init (ECalendarItemClass *class)
 259 {
 260 	GObjectClass  *object_class;
 261 	GnomeCanvasItemClass *item_class;
 262 
 263 	object_class = G_OBJECT_CLASS (class);
 264 	object_class->dispose = e_calendar_item_dispose;
 265 	object_class->get_property = e_calendar_item_get_property;
 266 	object_class->set_property = e_calendar_item_set_property;
 267 
 268 	item_class = GNOME_CANVAS_ITEM_CLASS (class);
 269 	item_class->realize = e_calendar_item_realize;
 270 	item_class->unmap = e_calendar_item_unmap;
 271 	item_class->update = e_calendar_item_update;
 272 	item_class->draw = e_calendar_item_draw;
 273 	item_class->point = e_calendar_item_point;
 274 	item_class->event = e_calendar_item_event;
 275 	item_class->bounds = e_calendar_item_bounds;
 276 
 277 	class->date_range_changed = NULL;
 278 	class->selection_changed = NULL;
 279 	class->selection_preview_changed = NULL;
 280 
 281 	g_object_class_install_property (
 282 		object_class,
 283 		PROP_YEAR,
 284 		g_param_spec_int (
 285 			"year",
 286 			NULL,
 287 			NULL,
 288 			G_MININT,
 289 			G_MAXINT,
 290 			0,
 291 			G_PARAM_READWRITE));
 292 
 293 	g_object_class_install_property (
 294 		object_class,
 295 		PROP_MONTH,
 296 		g_param_spec_int (
 297 			"month",
 298 			NULL,
 299 			NULL,
 300 			G_MININT,
 301 			G_MAXINT,
 302 			0,
 303 			G_PARAM_READWRITE));
 304 
 305 	g_object_class_install_property (
 306 		object_class,
 307 		PROP_X1,
 308 		g_param_spec_double (
 309 			"x1",
 310 			NULL,
 311 			NULL,
 312 			-G_MAXDOUBLE,
 313 			G_MAXDOUBLE,
 314 			0.,
 315 			G_PARAM_READWRITE));
 316 
 317 	g_object_class_install_property (
 318 		object_class,
 319 		PROP_Y1,
 320 		g_param_spec_double (
 321 			"y1",
 322 			NULL,
 323 			NULL,
 324 			-G_MAXDOUBLE,
 325 			G_MAXDOUBLE,
 326 			0.,
 327 			G_PARAM_READWRITE));
 328 
 329 	g_object_class_install_property (
 330 		object_class,
 331 		PROP_X2,
 332 		g_param_spec_double (
 333 			"x2",
 334 			NULL,
 335 			NULL,
 336 			-G_MAXDOUBLE,
 337 			G_MAXDOUBLE,
 338 			0.,
 339 			G_PARAM_READWRITE));
 340 
 341 	g_object_class_install_property (
 342 		object_class,
 343 		PROP_Y2,
 344 		g_param_spec_double (
 345 			"y2",
 346 			NULL,
 347 			NULL,
 348 			-G_MAXDOUBLE,
 349 			G_MAXDOUBLE,
 350 			0.,
 351 			G_PARAM_READWRITE));
 352 
 353 	g_object_class_install_property (
 354 		object_class,
 355 		PROP_FONT_DESC,
 356 		g_param_spec_boxed (
 357 			"font_desc",
 358 			NULL,
 359 			NULL,
 360 			PANGO_TYPE_FONT_DESCRIPTION,
 361 			G_PARAM_READWRITE));
 362 
 363 	g_object_class_install_property (
 364 		object_class,
 365 		PROP_WEEK_NUMBER_FONT_DESC,
 366 		g_param_spec_boxed (
 367 			"week_number_font_desc",
 368 			NULL,
 369 			NULL,
 370 			PANGO_TYPE_FONT_DESCRIPTION,
 371 			G_PARAM_READWRITE));
 372 
 373 	g_object_class_install_property (
 374 		object_class,
 375 		PROP_ROW_HEIGHT,
 376 		g_param_spec_int (
 377 			"row_height",
 378 			NULL,
 379 			NULL,
 380 			G_MININT,
 381 			G_MAXINT,
 382 			0,
 383 			G_PARAM_READABLE));
 384 
 385 	g_object_class_install_property (
 386 		object_class,
 387 		PROP_COLUMN_WIDTH,
 388 		g_param_spec_int (
 389 			"column_width",
 390 			NULL,
 391 			NULL,
 392 			G_MININT,
 393 			G_MAXINT,
 394 			0,
 395 			G_PARAM_READABLE));
 396 
 397 	g_object_class_install_property (
 398 		object_class,
 399 		PROP_MINIMUM_ROWS,
 400 		g_param_spec_int (
 401 			"minimum_rows",
 402 			NULL,
 403 			NULL,
 404 			G_MININT,
 405 			G_MAXINT,
 406 			0,
 407 			G_PARAM_READWRITE));
 408 
 409 	g_object_class_install_property (
 410 		object_class,
 411 		PROP_MINIMUM_COLUMNS,
 412 		g_param_spec_int (
 413 			"minimum_columns",
 414 			NULL,
 415 			NULL,
 416 			G_MININT,
 417 			G_MAXINT,
 418 			0,
 419 			G_PARAM_READWRITE));
 420 
 421 	g_object_class_install_property (
 422 		object_class,
 423 		PROP_MAXIMUM_ROWS,
 424 		g_param_spec_int (
 425 			"maximum_rows",
 426 			NULL,
 427 			NULL,
 428 			G_MININT,
 429 			G_MAXINT,
 430 			0,
 431 			G_PARAM_READWRITE));
 432 
 433 	g_object_class_install_property (
 434 		object_class,
 435 		PROP_MAXIMUM_COLUMNS,
 436 		g_param_spec_int (
 437 			"maximum_columns",
 438 			NULL,
 439 			NULL,
 440 			G_MININT,
 441 			G_MAXINT,
 442 			0,
 443 			G_PARAM_READWRITE));
 444 
 445 	g_object_class_install_property (
 446 		object_class,
 447 		PROP_WEEK_START_DAY,
 448 		g_param_spec_int (
 449 			"week_start_day",
 450 			NULL,
 451 			NULL,
 452 			G_MININT,
 453 			G_MAXINT,
 454 			0,
 455 			G_PARAM_READWRITE));
 456 
 457 	g_object_class_install_property (
 458 		object_class,
 459 		PROP_SHOW_WEEK_NUMBERS,
 460 		g_param_spec_boolean (
 461 			"show_week_numbers",
 462 			NULL,
 463 			NULL,
 464 			TRUE,
 465 			G_PARAM_READWRITE));
 466 
 467 	g_object_class_install_property (
 468 		object_class,
 469 		PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
 470 		g_param_spec_boolean (
 471 			"keep_wdays_on_weeknum_click",
 472 			NULL,
 473 			NULL,
 474 			FALSE,
 475 			G_PARAM_READWRITE));
 476 
 477 	g_object_class_install_property (
 478 		object_class,
 479 		PROP_MAXIMUM_DAYS_SELECTED,
 480 		g_param_spec_int (
 481 			"maximum_days_selected",
 482 			NULL,
 483 			NULL,
 484 			G_MININT,
 485 			G_MAXINT,
 486 			0,
 487 			G_PARAM_READWRITE));
 488 
 489 	g_object_class_install_property (
 490 		object_class,
 491 		PROP_DAYS_TO_START_WEEK_SELECTION,
 492 		g_param_spec_int (
 493 			"days_to_start_week_selection",
 494 			NULL,
 495 			NULL,
 496 			G_MININT,
 497 			G_MAXINT,
 498 			0,
 499 			G_PARAM_READWRITE));
 500 
 501 	g_object_class_install_property (
 502 		object_class,
 503 		PROP_MOVE_SELECTION_WHEN_MOVING,
 504 		g_param_spec_boolean (
 505 			"move_selection_when_moving",
 506 			NULL,
 507 			NULL,
 508 			TRUE,
 509 			G_PARAM_READWRITE));
 510 
 511 	g_object_class_install_property (
 512 		object_class,
 513 		PROP_PRESERVE_DAY_WHEN_MOVING,
 514 		g_param_spec_boolean (
 515 			"preserve_day_when_moving",
 516 			NULL,
 517 			NULL,
 518 			TRUE,
 519 			G_PARAM_READWRITE));
 520 
 521 	g_object_class_install_property (
 522 		object_class,
 523 		PROP_DISPLAY_POPUP,
 524 		g_param_spec_boolean (
 525 			"display_popup",
 526 			NULL,
 527 			NULL,
 528 			TRUE,
 529 			G_PARAM_READWRITE));
 530 
 531 	e_calendar_item_signals[DATE_RANGE_CHANGED] = g_signal_new (
 532 		"date_range_changed",
 533 		G_TYPE_FROM_CLASS (object_class),
 534 		G_SIGNAL_RUN_FIRST,
 535 		G_STRUCT_OFFSET (ECalendarItemClass, date_range_changed),
 536 		NULL, NULL,
 537 		g_cclosure_marshal_VOID__VOID,
 538 		G_TYPE_NONE, 0);
 539 
 540 	e_calendar_item_signals[SELECTION_CHANGED] = g_signal_new (
 541 		"selection_changed",
 542 		G_TYPE_FROM_CLASS (object_class),
 543 		G_SIGNAL_RUN_FIRST,
 544 		G_STRUCT_OFFSET (ECalendarItemClass, selection_changed),
 545 		NULL, NULL,
 546 		g_cclosure_marshal_VOID__VOID,
 547 		G_TYPE_NONE, 0);
 548 
 549 	e_calendar_item_signals[SELECTION_PREVIEW_CHANGED] = g_signal_new (
 550 		"selection_preview_changed",
 551 		G_TYPE_FROM_CLASS (object_class),
 552 		G_SIGNAL_RUN_LAST,
 553 		G_STRUCT_OFFSET (ECalendarItemClass, selection_preview_changed),
 554 		NULL, NULL,
 555 		g_cclosure_marshal_VOID__VOID,
 556 		G_TYPE_NONE, 0);
 557 
 558 	e_calendar_item_a11y_init ();
 559 }
 560 
 561 static void
 562 e_calendar_item_init (ECalendarItem *calitem)
 563 {
 564 	struct tm *tmp_tm;
 565 	time_t t;
 566 
 567 	/* Set the default time to the current month. */
 568 	t = time (NULL);
 569 	tmp_tm = localtime (&t);
 570 	calitem->year = tmp_tm->tm_year + 1900;
 571 	calitem->month = tmp_tm->tm_mon;
 572 
 573 	calitem->styles = NULL;
 574 
 575 	calitem->min_cols = 1;
 576 	calitem->min_rows = 1;
 577 	calitem->max_cols = -1;
 578 	calitem->max_rows = -1;
 579 
 580 	calitem->rows = 0;
 581 	calitem->cols = 0;
 582 
 583 	calitem->show_week_numbers = FALSE;
 584 	calitem->keep_wdays_on_weeknum_click = FALSE;
 585 	calitem->week_start_day = 0;
 586 	calitem->expand = TRUE;
 587 	calitem->max_days_selected = 1;
 588 	calitem->days_to_start_week_selection = -1;
 589 	calitem->move_selection_when_moving = TRUE;
 590 	calitem->preserve_day_when_moving = FALSE;
 591 	calitem->display_popup = TRUE;
 592 
 593 	calitem->x1 = 0.0;
 594 	calitem->y1 = 0.0;
 595 	calitem->x2 = 0.0;
 596 	calitem->y2 = 0.0;
 597 
 598 	calitem->selecting = FALSE;
 599 	calitem->selecting_axis = NULL;
 600 
 601 	calitem->selection_set = FALSE;
 602 
 603 	calitem->selection_changed = FALSE;
 604 	calitem->date_range_changed = FALSE;
 605 
 606 	calitem->style_callback = NULL;
 607 	calitem->style_callback_data = NULL;
 608 	calitem->style_callback_destroy = NULL;
 609 
 610 	calitem->time_callback = NULL;
 611 	calitem->time_callback_data = NULL;
 612 	calitem->time_callback_destroy = NULL;
 613 
 614 	calitem->signal_emission_idle_id = 0;
 615 }
 616 
 617 static void
 618 e_calendar_item_dispose (GObject *object)
 619 {
 620 	ECalendarItem *calitem;
 621 
 622 	calitem = E_CALENDAR_ITEM (object);
 623 
 624 	e_calendar_item_set_style_callback (calitem, NULL, NULL, NULL);
 625 	e_calendar_item_set_get_time_callback (calitem, NULL, NULL, NULL);
 626 
 627 	if (calitem->styles) {
 628 		g_free (calitem->styles);
 629 		calitem->styles = NULL;
 630 	}
 631 
 632 	if (calitem->signal_emission_idle_id > 0) {
 633 		g_source_remove (calitem->signal_emission_idle_id);
 634 		calitem->signal_emission_idle_id = -1;
 635 	}
 636 
 637 	if (calitem->font_desc) {
 638 		pango_font_description_free (calitem->font_desc);
 639 		calitem->font_desc = NULL;
 640 	}
 641 
 642 	if (calitem->week_number_font_desc) {
 643 		pango_font_description_free (calitem->week_number_font_desc);
 644 		calitem->week_number_font_desc = NULL;
 645 	}
 646 
 647 	if (calitem->selecting_axis)
 648 		g_free (calitem->selecting_axis);
 649 
 650 	G_OBJECT_CLASS (e_calendar_item_parent_class)->dispose (object);
 651 }
 652 
 653 static void
 654 e_calendar_item_get_property (GObject *object,
 655                               guint property_id,
 656                               GValue *value,
 657                               GParamSpec *pspec)
 658 {
 659 	ECalendarItem *calitem;
 660 
 661 	calitem = E_CALENDAR_ITEM (object);
 662 
 663 	switch (property_id) {
 664 	case PROP_YEAR:
 665 		g_value_set_int (value, calitem->year);
 666 		return;
 667 	case PROP_MONTH:
 668 		g_value_set_int (value, calitem->month);
 669 		return;
 670 	case PROP_X1:
 671 		g_value_set_double (value, calitem->x1);
 672 		return;
 673 	case PROP_Y1:
 674 		g_value_set_double (value, calitem->y1);
 675 		return;
 676 	case PROP_X2:
 677 		g_value_set_double (value, calitem->x2);
 678 		return;
 679 	case PROP_Y2:
 680 		g_value_set_double (value, calitem->y2);
 681 		return;
 682 	case PROP_FONT_DESC:
 683 		g_value_set_boxed (value, calitem->font_desc);
 684 		return;
 685 	case PROP_WEEK_NUMBER_FONT_DESC:
 686 		g_value_set_boxed (value, calitem->week_number_font_desc);
 687 		return;
 688 	case PROP_ROW_HEIGHT:
 689 		e_calendar_item_recalc_sizes (calitem);
 690 		g_value_set_int (value, calitem->min_month_height);
 691 		return;
 692 	case PROP_COLUMN_WIDTH:
 693 		e_calendar_item_recalc_sizes (calitem);
 694 		g_value_set_int (value, calitem->min_month_width);
 695 		return;
 696 	case PROP_MINIMUM_ROWS:
 697 		g_value_set_int (value, calitem->min_rows);
 698 		return;
 699 	case PROP_MINIMUM_COLUMNS:
 700 		g_value_set_int (value, calitem->min_cols);
 701 		return;
 702 	case PROP_MAXIMUM_ROWS:
 703 		g_value_set_int (value, calitem->max_rows);
 704 		return;
 705 	case PROP_MAXIMUM_COLUMNS:
 706 		g_value_set_int (value, calitem->max_cols);
 707 		return;
 708 	case PROP_WEEK_START_DAY:
 709 		g_value_set_int (value, calitem->week_start_day);
 710 		return;
 711 	case PROP_SHOW_WEEK_NUMBERS:
 712 		g_value_set_boolean (value, calitem->show_week_numbers);
 713 		return;
 714 	case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
 715 		g_value_set_boolean (value, calitem->keep_wdays_on_weeknum_click);
 716 		return;
 717 	case PROP_MAXIMUM_DAYS_SELECTED:
 718 		g_value_set_int (value, e_calendar_item_get_max_days_sel (calitem));
 719 		return;
 720 	case PROP_DAYS_TO_START_WEEK_SELECTION:
 721 		g_value_set_int (value, e_calendar_item_get_days_start_week_sel (calitem));
 722 		return;
 723 	case PROP_MOVE_SELECTION_WHEN_MOVING:
 724 		g_value_set_boolean (value, calitem->move_selection_when_moving);
 725 		return;
 726 	case PROP_PRESERVE_DAY_WHEN_MOVING:
 727 		g_value_set_boolean (value, calitem->preserve_day_when_moving);
 728 		return;
 729 	case PROP_DISPLAY_POPUP:
 730 		g_value_set_boolean (value, e_calendar_item_get_display_popup (calitem));
 731 		return;
 732 	}
 733 
 734 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 735 }
 736 
 737 static void
 738 e_calendar_item_set_property (GObject *object,
 739                               guint property_id,
 740                               const GValue *value,
 741                               GParamSpec *pspec)
 742 {
 743 	GnomeCanvasItem *item;
 744 	ECalendarItem *calitem;
 745 	PangoFontDescription *font_desc;
 746 	gdouble dvalue;
 747 	gint ivalue;
 748 	gboolean bvalue;
 749 
 750 	item = GNOME_CANVAS_ITEM (object);
 751 	calitem = E_CALENDAR_ITEM (object);
 752 
 753 	switch (property_id) {
 754 	case PROP_YEAR:
 755 		ivalue = g_value_get_int (value);
 756 		e_calendar_item_set_first_month (
 757 			calitem, ivalue, calitem->month);
 758 		return;
 759 	case PROP_MONTH:
 760 		ivalue = g_value_get_int (value);
 761 		e_calendar_item_set_first_month (
 762 			calitem, calitem->year, ivalue);
 763 		return;
 764 	case PROP_X1:
 765 		dvalue = g_value_get_double (value);
 766 		if (calitem->x1 != dvalue) {
 767 			calitem->x1 = dvalue;
 768 			gnome_canvas_item_request_update (item);
 769 		}
 770 		return;
 771 	case PROP_Y1:
 772 		dvalue = g_value_get_double (value);
 773 		if (calitem->y1 != dvalue) {
 774 			calitem->y1 = dvalue;
 775 			gnome_canvas_item_request_update (item);
 776 		}
 777 		return;
 778 	case PROP_X2:
 779 		dvalue = g_value_get_double (value);
 780 		if (calitem->x2 != dvalue) {
 781 			calitem->x2 = dvalue;
 782 			gnome_canvas_item_request_update (item);
 783 		}
 784 		return;
 785 	case PROP_Y2:
 786 		dvalue = g_value_get_double (value);
 787 		if (calitem->y2 != dvalue) {
 788 			calitem->y2 = dvalue;
 789 			gnome_canvas_item_request_update (item);
 790 		}
 791 		return;
 792 	case PROP_FONT_DESC:
 793 		font_desc = g_value_get_boxed (value);
 794 		if (calitem->font_desc)
 795 			pango_font_description_free (calitem->font_desc);
 796 		calitem->font_desc = pango_font_description_copy (font_desc);
 797 		gnome_canvas_item_request_update (item);
 798 		return;
 799 	case PROP_WEEK_NUMBER_FONT_DESC:
 800 		font_desc = g_value_get_boxed (value);
 801 		if (calitem->week_number_font_desc)
 802 			pango_font_description_free (calitem->week_number_font_desc);
 803 		calitem->week_number_font_desc = pango_font_description_copy (font_desc);
 804 		gnome_canvas_item_request_update (item);
 805 		return;
 806 	case PROP_MINIMUM_ROWS:
 807 		ivalue = g_value_get_int (value);
 808 		ivalue = MAX (1, ivalue);
 809 		if (calitem->min_rows != ivalue) {
 810 			calitem->min_rows = ivalue;
 811 			gnome_canvas_item_request_update (item);
 812 		}
 813 		return;
 814 	case PROP_MINIMUM_COLUMNS:
 815 		ivalue = g_value_get_int (value);
 816 		ivalue = MAX (1, ivalue);
 817 		if (calitem->min_cols != ivalue) {
 818 			calitem->min_cols = ivalue;
 819 			gnome_canvas_item_request_update (item);
 820 		}
 821 		return;
 822 	case PROP_MAXIMUM_ROWS:
 823 		ivalue = g_value_get_int (value);
 824 		if (calitem->max_rows != ivalue) {
 825 			calitem->max_rows = ivalue;
 826 			gnome_canvas_item_request_update (item);
 827 		}
 828 		return;
 829 	case PROP_MAXIMUM_COLUMNS:
 830 		ivalue = g_value_get_int (value);
 831 		if (calitem->max_cols != ivalue) {
 832 			calitem->max_cols = ivalue;
 833 			gnome_canvas_item_request_update (item);
 834 		}
 835 		return;
 836 	case PROP_WEEK_START_DAY:
 837 		ivalue = g_value_get_int (value);
 838 		if (calitem->week_start_day != ivalue) {
 839 			calitem->week_start_day = ivalue;
 840 			gnome_canvas_item_request_update (item);
 841 		}
 842 		return;
 843 	case PROP_SHOW_WEEK_NUMBERS:
 844 		bvalue = g_value_get_boolean (value);
 845 		if (calitem->show_week_numbers != bvalue) {
 846 			calitem->show_week_numbers = bvalue;
 847 			gnome_canvas_item_request_update (item);
 848 		}
 849 		return;
 850 	case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
 851 		calitem->keep_wdays_on_weeknum_click = g_value_get_boolean (value);
 852 		return;
 853 	case PROP_MAXIMUM_DAYS_SELECTED:
 854 		ivalue = g_value_get_int (value);
 855 		e_calendar_item_set_max_days_sel (calitem, ivalue);
 856 		return;
 857 	case PROP_DAYS_TO_START_WEEK_SELECTION:
 858 		ivalue = g_value_get_int (value);
 859 		e_calendar_item_set_days_start_week_sel (calitem, ivalue);
 860 		return;
 861 	case PROP_MOVE_SELECTION_WHEN_MOVING:
 862 		bvalue = g_value_get_boolean (value);
 863 		calitem->move_selection_when_moving = bvalue;
 864 		return;
 865 	case PROP_PRESERVE_DAY_WHEN_MOVING:
 866 		bvalue = g_value_get_boolean (value);
 867 		calitem->preserve_day_when_moving = bvalue;
 868 		return;
 869 	case PROP_DISPLAY_POPUP:
 870 		bvalue = g_value_get_boolean (value);
 871 		e_calendar_item_set_display_popup (calitem, bvalue);
 872 		return;
 873 	}
 874 
 875 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 876 }
 877 
 878 static void
 879 e_calendar_item_realize (GnomeCanvasItem *item)
 880 {
 881 	ECalendarItem *calitem;
 882 
 883 	if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize)
 884 		(* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize) (item);
 885 
 886 	calitem = E_CALENDAR_ITEM (item);
 887 
 888 	e_calendar_item_style_set (GTK_WIDGET (item->canvas), calitem);
 889 
 890 	e_extensible_load_extensions (E_EXTENSIBLE (calitem));
 891 }
 892 
 893 static void
 894 e_calendar_item_unmap (GnomeCanvasItem *item)
 895 {
 896 	ECalendarItem *calitem;
 897 
 898 	calitem = E_CALENDAR_ITEM (item);
 899 
 900 	if (calitem->selecting) {
 901 		gnome_canvas_item_ungrab (item, GDK_CURRENT_TIME);
 902 		calitem->selecting = FALSE;
 903 	}
 904 
 905 	if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap)
 906 		(* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap) (item);
 907 }
 908 
 909 static void
 910 e_calendar_item_update (GnomeCanvasItem *item,
 911                         const cairo_matrix_t *i2c,
 912                         gint flags)
 913 {
 914 	GnomeCanvasItemClass *item_class;
 915 	ECalendarItem *calitem;
 916 	GtkStyle *style;
 917 	gint char_height, width, height, space, space_per_cal, space_per_cell;
 918 	gint rows, cols, xthickness, ythickness;
 919 	PangoFontDescription *font_desc;
 920 	PangoContext *pango_context;
 921 	PangoFontMetrics *font_metrics;
 922 
 923 	item_class = GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class);
 924 	if (item_class->update != NULL)
 925 		item_class->update (item, i2c, flags);
 926 
 927 	calitem = E_CALENDAR_ITEM (item);
 928 	style = gtk_widget_get_style (GTK_WIDGET (item->canvas));
 929 	xthickness = style->xthickness;
 930 	ythickness = style->ythickness;
 931 
 932 	item->x1 = calitem->x1;
 933 	item->y1 = calitem->y1;
 934 	item->x2 = calitem->x2 >= calitem->x1 ? calitem->x2 : calitem->x1;
 935 	item->y2 = calitem->y2 >= calitem->y1 ? calitem->y2 : calitem->y1;
 936 
 937 	/* Set up Pango prerequisites */
 938 	font_desc = style->font_desc;
 939 	pango_context = gtk_widget_get_pango_context (GTK_WIDGET (item->canvas));
 940 	font_metrics = pango_context_get_metrics (
 941 		pango_context, font_desc,
 942 		pango_context_get_language (pango_context));
 943 
 944 	/*
 945 	 * Calculate the new layout of the calendar.
 946 	 */
 947 
 948 	/* Make sure the minimum row width & cell height and the widths of
 949 	 * all the digits and characters are up to date. */
 950 	e_calendar_item_recalc_sizes (calitem);
 951 
 952 	/* Calculate how many rows & cols we can fit in. */
 953 	width = item->x2 - item->x1;
 954 	height = item->y2 - item->y1;
 955 
 956 	width -= xthickness * 2;
 957 	height -= ythickness * 2;
 958 
 959 	if (calitem->min_month_height == 0)
 960 		rows = 1;
 961 	else
 962 		rows = height / calitem->min_month_height;
 963 	rows = MAX (rows, calitem->min_rows);
 964 	if (calitem->max_rows > 0)
 965 		rows = MIN (rows, calitem->max_rows);
 966 
 967 	if (calitem->min_month_width == 0)
 968 		cols = 1;
 969 	else
 970 		cols = width / calitem->min_month_width;
 971 	cols = MAX (cols, calitem->min_cols);
 972 	if (calitem->max_cols > 0)
 973 		cols = MIN (cols, calitem->max_cols);
 974 
 975 	if (rows != calitem->rows || cols != calitem->cols)
 976 		e_calendar_item_date_range_changed (calitem);
 977 
 978 	calitem->rows = rows;
 979 	calitem->cols = cols;
 980 
 981 	/* Split up the empty space according to the configuration.
 982 	 * If the calendar is set to expand, we divide the space between the
 983 	 * cells and the spaces around the calendar, otherwise we place the
 984 	 * calendars in the center of the available area. */
 985 
 986 	char_height =
 987 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
 988 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
 989 
 990 	calitem->month_width = calitem->min_month_width;
 991 	calitem->month_height = calitem->min_month_height;
 992 	calitem->cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
 993 		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
 994 	calitem->cell_height = char_height
 995 		+ E_CALENDAR_ITEM_MIN_CELL_YPAD;
 996 	calitem->month_tpad = 0;
 997 	calitem->month_bpad = 0;
 998 	calitem->month_lpad = 0;
 999 	calitem->month_rpad = 0;
1000 
1001 	space = height - calitem->rows * calitem->month_height;
1002 	if (space > 0) {
1003 		space_per_cal = space / calitem->rows;
1004 		calitem->month_height += space_per_cal;
1005 
1006 		if (calitem->expand) {
1007 			space_per_cell = space_per_cal / E_CALENDAR_ROWS_PER_MONTH;
1008 			calitem->cell_height += space_per_cell;
1009 			space_per_cal -= space_per_cell * E_CALENDAR_ROWS_PER_MONTH;
1010 		}
1011 
1012 		calitem->month_tpad = space_per_cal / 2;
1013 		calitem->month_bpad = space_per_cal - calitem->month_tpad;
1014 	}
1015 
1016 	space = width - calitem->cols * calitem->month_width;
1017 	if (space > 0) {
1018 		space_per_cal = space / calitem->cols;
1019 		calitem->month_width += space_per_cal;
1020 		space -= space_per_cal * calitem->cols;
1021 
1022 		if (calitem->expand) {
1023 			space_per_cell = space_per_cal / E_CALENDAR_COLS_PER_MONTH;
1024 			calitem->cell_width += space_per_cell;
1025 			space_per_cal -= space_per_cell * E_CALENDAR_COLS_PER_MONTH;
1026 		}
1027 
1028 		calitem->month_lpad = space_per_cal / 2;
1029 		calitem->month_rpad = space_per_cal - calitem->month_lpad;
1030 	}
1031 
1032 	space = MAX (0, space);
1033 	calitem->x_offset = space / 2;
1034 
1035 	gnome_canvas_request_redraw (
1036 		item->canvas, item->x1, item->y1,
1037 		item->x2, item->y2);
1038 
1039 	pango_font_metrics_unref (font_metrics);
1040 }
1041 
1042 /*
1043  * DRAWING ROUTINES - functions to paint the canvas item.
1044  */
1045 static void
1046 e_calendar_item_draw (GnomeCanvasItem *canvas_item,
1047                       cairo_t *cr,
1048                       gint x,
1049                       gint y,
1050                       gint width,
1051                       gint height)
1052 {
1053 	ECalendarItem *calitem;
1054 	GtkStyle *style;
1055 	gint char_height, row, col, row_y, bar_height, col_x;
1056 	gint xthickness, ythickness;
1057 	PangoFontDescription *font_desc;
1058 	PangoContext *pango_context;
1059 	PangoFontMetrics *font_metrics;
1060 	GdkColor base, bg;
1061 
1062 #if 0
1063 	g_print (
1064 		"In e_calendar_item_draw %i,%i %ix%i\n",
1065 		x, y, width, height);
1066 #endif
1067 	calitem = E_CALENDAR_ITEM (canvas_item);
1068 	style = gtk_widget_get_style (GTK_WIDGET (canvas_item->canvas));
1069 
1070 	/* Set up Pango prerequisites */
1071 	font_desc = calitem->font_desc;
1072 	if (!font_desc)
1073 		font_desc = style->font_desc;
1074 	pango_context = gtk_widget_get_pango_context (GTK_WIDGET (canvas_item->canvas));
1075 	font_metrics = pango_context_get_metrics (
1076 		pango_context, font_desc,
1077 		pango_context_get_language (pango_context));
1078 
1079 	char_height =
1080 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1081 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
1082 	xthickness = style->xthickness;
1083 	ythickness = style->ythickness;
1084 
1085 	base = style->base[GTK_STATE_NORMAL];
1086 	bg = style->bg[GTK_STATE_NORMAL];
1087 
1088 	/* Clear the entire background. */
1089 	cairo_save (cr);
1090 	gdk_cairo_set_source_color (cr, &base);
1091 	cairo_rectangle (
1092 		cr, calitem->x1 - x, calitem->y1 - y,
1093 		calitem->x2 - calitem->x1 + 1,
1094 		calitem->y2 - calitem->y1 + 1);
1095 	cairo_fill (cr);
1096 	cairo_restore (cr);
1097 
1098 	/* Draw the shadow around the entire item. */
1099 	gtk_paint_shadow (
1100 		style, cr, GTK_STATE_NORMAL, GTK_SHADOW_IN,
1101 		NULL, "entry",
1102 		calitem->x1 - x, calitem->y1 - y,
1103 		calitem->x2 - calitem->x1 + 1,
1104 		calitem->y2 - calitem->y1 + 1);
1105 
1106 	row_y = canvas_item->y1 + ythickness;
1107 	bar_height = ythickness * 2
1108 		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height
1109 		+ E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME;
1110 
1111 	for (row = 0; row < calitem->rows; row++) {
1112 		/* Draw the background for the title bars and the shadow around
1113 		 * it, and the vertical lines between columns. */
1114 
1115 		cairo_save (cr);
1116 		gdk_cairo_set_source_color (cr, &bg);
1117 		cairo_rectangle (
1118 			cr, calitem->x1 + xthickness - x, row_y - y,
1119 			calitem->x2 - calitem->x1 + 1
1120 			- xthickness * 2,
1121 			bar_height);
1122 		cairo_fill (cr);
1123 		cairo_restore (cr);
1124 
1125 		gtk_paint_shadow (
1126 			style, cr,
1127 			GTK_STATE_NORMAL, GTK_SHADOW_OUT,
1128 			NULL, "calendar-header",
1129 			calitem->x1 + xthickness - x, row_y - y,
1130 			calitem->x2 - calitem->x1 + 1
1131 			- xthickness * 2,
1132 			bar_height);
1133 
1134 		for (col = 0; col < calitem->cols; col++) {
1135 			if (col != 0) {
1136 				col_x = calitem->x1 + calitem->x_offset
1137 					+ calitem->month_width * col;
1138 				gtk_paint_vline (
1139 					style, cr,
1140 					GTK_STATE_NORMAL, NULL,
1141 					"calendar-separator",
1142 					row_y + ythickness + 1 - y,
1143 					row_y + bar_height
1144 					- ythickness - 2 - y,
1145 					col_x - 1 - x);
1146 			}
1147 
1148 			e_calendar_item_draw_month (
1149 				calitem, cr, x, y,
1150 				width, height, row, col);
1151 		}
1152 
1153 		row_y += calitem->month_height;
1154 	}
1155 
1156 	pango_font_metrics_unref (font_metrics);
1157 }
1158 
1159 static void
1160 layout_set_day_text (ECalendarItem *calitem,
1161                      PangoLayout *layout,
1162                      gint day_index)
1163 {
1164 	const gchar *abbr_name;
1165 
1166 	/* day_index: 0 = Monday ... 6 = Sunday */
1167 	abbr_name = e_get_weekday_name (day_index + 1, TRUE);
1168 	pango_layout_set_text (layout, abbr_name, -1);
1169 }
1170 
1171 static void
1172 e_calendar_item_draw_month (ECalendarItem *calitem,
1173                             cairo_t *cr,
1174                             gint x,
1175                             gint y,
1176                             gint width,
1177                             gint height,
1178                             gint row,
1179                             gint col)
1180 {
1181 	GnomeCanvasItem *item;
1182 	GtkWidget *widget;
1183 	GtkStyle *style;
1184 	PangoFontDescription *font_desc;
1185 	struct tm tmp_tm;
1186 	GdkRectangle clip_rect;
1187 	gint char_height, xthickness, ythickness, start_weekday;
1188 	gint year, month;
1189 	gint month_x, month_y, month_w, month_h;
1190 	gint min_x, max_x, text_x, text_y;
1191 	gint day, day_index, cells_x, cells_y, min_cell_width, text_width, arrow_button_size;
1192 	gint clip_width, clip_height;
1193 	gchar buffer[64];
1194 	PangoContext *pango_context;
1195 	PangoFontMetrics *font_metrics;
1196 	PangoLayout *layout;
1197 
1198 #if 0
1199 	g_print (
1200 		"In e_calendar_item_draw_month: %i,%i %ix%i row:%i col:%i\n",
1201 		x, y, width, height, row, col);
1202 #endif
1203 	item = GNOME_CANVAS_ITEM (calitem);
1204 	widget = GTK_WIDGET (item->canvas);
1205 	style = gtk_widget_get_style (widget);
1206 
1207 	/* Set up Pango prerequisites */
1208 	font_desc = calitem->font_desc;
1209 	if (!font_desc)
1210 		font_desc = style->font_desc;
1211 	pango_context = gtk_widget_get_pango_context (widget);
1212 	font_metrics = pango_context_get_metrics (
1213 		pango_context, font_desc,
1214 		pango_context_get_language (pango_context));
1215 
1216 	char_height =
1217 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1218 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
1219 	xthickness = style->xthickness;
1220 	ythickness = style->ythickness;
1221 	arrow_button_size =
1222 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
1223 		+ PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1224 		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
1225 		+ E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
1226 		+ 2 * xthickness;
1227 
1228 	pango_font_metrics_unref (font_metrics);
1229 
1230 	/* Calculate the top-left position of the entire month display. */
1231 	month_x = item->x1 + xthickness + calitem->x_offset
1232 		+ ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1233 		? (calitem->cols - 1 - col) : col) * calitem->month_width - x;
1234 	month_w = item->x2 - item->x1 - xthickness * 2;
1235 	month_w = MIN (month_w, calitem->month_width);
1236 	month_y = item->y1 + ythickness + row * calitem->month_height - y;
1237 	month_h = item->y2 - item->y1 - ythickness * 2;
1238 	month_h = MIN (month_h, calitem->month_height);
1239 
1240 	/* Just return if the month is outside the given area. */
1241 	if (month_x >= width || month_x + calitem->month_width <= 0
1242 	    || month_y >= height || month_y + calitem->month_height <= 0)
1243 		return;
1244 
1245 	month = calitem->month + row * calitem->cols + col;
1246 	year = calitem->year + month / 12;
1247 	month %= 12;
1248 
1249 	/* Draw the month name & year, with clipping. Note that the top row
1250 	 * needs extra space around it for the buttons. */
1251 
1252 	layout = gtk_widget_create_pango_layout (widget, NULL);
1253 
1254 	if (row == 0 && col == 0)
1255 		min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON;
1256 	else
1257 		min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME;
1258 
1259 	max_x = month_w;
1260 	if (row == 0 && col == 0)
1261 		max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON;
1262 	else
1263 		max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME;
1264 
1265 	text_y = month_y + style->ythickness
1266 		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME;
1267 	clip_rect.x = month_x + min_x;
1268 	clip_rect.x = MAX (0, clip_rect.x);
1269 	clip_rect.y = MAX (0, text_y);
1270 
1271 	memset (&tmp_tm, 0, sizeof (tmp_tm));
1272 	tmp_tm.tm_year = year - 1900;
1273 	tmp_tm.tm_mon = month;
1274 	tmp_tm.tm_mday = 1;
1275 	tmp_tm.tm_isdst = -1;
1276 	mktime (&tmp_tm);
1277 	start_weekday = (tmp_tm.tm_wday + 6) % 7;
1278 
1279 	if (month_x + max_x - clip_rect.x > 0) {
1280 		cairo_save (cr);
1281 
1282 		clip_rect.width = month_x + max_x - clip_rect.x;
1283 		clip_rect.height = text_y + char_height - clip_rect.y;
1284 		gdk_cairo_rectangle (cr, &clip_rect);
1285 		cairo_clip (cr);
1286 
1287 		gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]);
1288 
1289 		if (row == 0 && col == 0) {
1290 			PangoLayout *layout_yr;
1291 			gchar buffer_yr[64];
1292 			gdouble max_width;
1293 
1294 			layout_yr = gtk_widget_create_pango_layout (widget, NULL);
1295 
1296 			/* This is a strftime() format. %B = Month name. */
1297 			e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);
1298 			/* This is a strftime() format. %Y = Year. */
1299 			e_utf8_strftime (buffer_yr, sizeof (buffer_yr), C_("CalItem", "%Y"), &tmp_tm);
1300 
1301 			pango_layout_set_font_description (layout, font_desc);
1302 			pango_layout_set_text (layout, buffer, -1);
1303 
1304 			pango_layout_set_font_description (layout_yr, font_desc);
1305 			pango_layout_set_text (layout_yr, buffer_yr, -1);
1306 
1307 			if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL) {
1308 				max_width = calitem->max_month_name_width;
1309 				pango_layout_get_pixel_size (layout, &text_width, NULL);
1310 
1311 				cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
1312 				pango_cairo_show_layout (cr, layout);
1313 
1314 				max_width = calitem->max_digit_width * 5;
1315 				pango_layout_get_pixel_size (layout_yr, &text_width, NULL);
1316 
1317 				cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
1318 				pango_cairo_show_layout (cr, layout_yr);
1319 			} else {
1320 				max_width = calitem->max_digit_width * 5;
1321 				pango_layout_get_pixel_size (layout_yr, &text_width, NULL);
1322 
1323 				cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
1324 				pango_cairo_show_layout (cr, layout_yr);
1325 
1326 				max_width = calitem->max_month_name_width;
1327 				pango_layout_get_pixel_size (layout, &text_width, NULL);
1328 
1329 				cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
1330 				pango_cairo_show_layout (cr, layout);
1331 			}
1332 
1333 			g_object_unref (layout_yr);
1334 		} else {
1335 			/* This is a strftime() format. %B = Month name, %Y = Year. */
1336 			e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B %Y"), &tmp_tm);
1337 
1338 			pango_layout_set_font_description (layout, font_desc);
1339 			pango_layout_set_text (layout, buffer, -1);
1340 
1341 			/* Ideally we place the text centered in the month, but we
1342 			 * won't go to the left of the minimum x position. */
1343 			pango_layout_get_pixel_size (layout, &text_width, NULL);
1344 			text_x = (calitem->month_width - text_width) / 2;
1345 			text_x = MAX (min_x, text_x);
1346 
1347 			cairo_move_to (cr, month_x + text_x, text_y);
1348 			pango_cairo_show_layout (cr, layout);
1349 		}
1350 
1351 		cairo_restore (cr);
1352 	}
1353 
1354 	/* Set the clip rectangle for the main month display. */
1355 	clip_rect.x = MAX (0, month_x);
1356 	clip_rect.y = MAX (0, month_y);
1357 	clip_width = month_x + month_w - clip_rect.x;
1358 	clip_height = month_y + month_h - clip_rect.y;
1359 
1360 	if (clip_width <= 0 || clip_height <= 0) {
1361 		g_object_unref (layout);
1362 		return;
1363 	}
1364 
1365 	clip_rect.width = clip_width;
1366 	clip_rect.height = clip_height;
1367 
1368 	cairo_save (cr);
1369 
1370 	gdk_cairo_rectangle (cr, &clip_rect);
1371 	cairo_clip (cr);
1372 
1373 	/* Draw the day initials across the top of the month. */
1374 	min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
1375 		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
1376 
1377 	cells_x = month_x + E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad
1378 		+ E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
1379 	if (calitem->show_week_numbers)
1380 		cells_x += calitem->max_week_number_digit_width * 2
1381 			+ E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
1382 	text_x = cells_x + calitem->cell_width
1383 		- (calitem->cell_width - min_cell_width) / 2;
1384 	text_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
1385 	text_y = month_y + ythickness * 2
1386 		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
1387 		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
1388 		+ E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad;
1389 
1390 	cells_y = text_y + char_height
1391 		+ E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
1392 		+ E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
1393 
1394 	cairo_save (cr);
1395 	gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]);
1396 	cairo_rectangle (
1397 		cr, cells_x ,
1398 		text_y - E_CALENDAR_ITEM_YPAD_ABOVE_CELLS - 1,
1399 			calitem->cell_width * 7  , cells_y - text_y);
1400 	cairo_fill (cr);
1401 	cairo_restore (cr);
1402 
1403 	day_index = calitem->week_start_day;
1404 	pango_layout_set_font_description (layout, font_desc);
1405 	if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1406 		text_x += (7 - 1) * calitem->cell_width;
1407 	gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_ACTIVE]);
1408 	for (day = 0; day < 7; day++) {
1409 		cairo_save (cr);
1410 		layout_set_day_text (calitem, layout, day_index);
1411 		cairo_move_to (
1412 			cr,
1413 			text_x - calitem->day_widths[day_index],
1414 			text_y);
1415 		pango_cairo_show_layout (cr, layout);
1416 		text_x += (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1417 				? -calitem->cell_width : calitem->cell_width;
1418 		day_index++;
1419 		if (day_index == 7)
1420 			day_index = 0;
1421 		cairo_restore (cr);
1422 	}
1423 
1424 	/* Draw the rectangle around the week number. */
1425 	if (calitem->show_week_numbers) {
1426 		cairo_save (cr);
1427 		gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]);
1428 		cairo_rectangle (
1429 			cr, cells_x, cells_y - (cells_y - text_y + 2) ,
1430 				-20, E_CALENDAR_ROWS_PER_MONTH * calitem->cell_height + 18);
1431 		cairo_fill (cr);
1432 		cairo_restore (cr);
1433 	}
1434 
1435 	e_calendar_item_draw_day_numbers (
1436 		calitem, cr, width, height, row, col,
1437 		year, month, start_weekday, cells_x, cells_y);
1438 
1439 	g_object_unref (layout);
1440 	cairo_restore (cr);
1441 }
1442 
1443 static const gchar *
1444 get_digit_fomat (void)
1445 {
1446 
1447 #ifdef HAVE_GNU_GET_LIBC_VERSION
1448 #include <gnu/libc-version.h>
1449 
1450 	const gchar *libc_version = gnu_get_libc_version ();
1451 	gchar **split = g_strsplit (libc_version, ".", -1);
1452 	gint major = 0;
1453 	gint minor = 0;
1454 	gint revision = 0;
1455 
1456 	major = atoi (split[0]);
1457 	minor = atoi (split[1]);
1458 
1459 	if (g_strv_length (split) > 2)
1460 		revision = atoi (split[2]);
1461 	g_strfreev (split);
1462 
1463 	if (major > 2 || minor > 2 || (minor == 2 && revision > 2)) {
1464 		return "%Id";
1465 	}
1466 #endif
1467 
1468 	return "%d";
1469 }
1470 
1471 static void
1472 e_calendar_item_draw_day_numbers (ECalendarItem *calitem,
1473                                   cairo_t *cr,
1474                                   gint width,
1475                                   gint height,
1476                                   gint row,
1477                                   gint col,
1478                                   gint year,
1479                                   gint month,
1480                                   gint start_weekday,
1481                                   gint cells_x,
1482                                   gint cells_y)
1483 {
1484 	GnomeCanvasItem *item;
1485 	GtkWidget *widget;
1486 	GtkStyle *style;
1487 	PangoFontDescription *font_desc;
1488 	GdkColor *bg_color, *fg_color, *box_color;
1489 	struct tm today_tm;
1490 	time_t t;
1491 	gint char_height, min_cell_width, min_cell_height;
1492 	gint day_num, drow, dcol, day_x, day_y;
1493 	gint text_x, text_y;
1494 	gint num_chars, digit;
1495 	gint week_num, mon, days_from_week_start;
1496 	gint years[3], months[3], days_in_month[3];
1497 	gboolean today, selected, has_focus, drop_target = FALSE;
1498 	gboolean bold, italic, draw_day, finished = FALSE;
1499 	gint today_year, today_month, today_mday, month_offset;
1500 	gchar buffer[9];
1501 	gint day_style = 0;
1502 	PangoContext *pango_context;
1503 	PangoFontMetrics *font_metrics;
1504 	PangoLayout *layout;
1505 
1506 	item = GNOME_CANVAS_ITEM (calitem);
1507 	widget = GTK_WIDGET (item->canvas);
1508 	style = gtk_widget_get_style (widget);
1509 
1510 	/* Set up Pango prerequisites */
1511 	font_desc = calitem->font_desc;
1512 	if (!font_desc)
1513 		font_desc = style->font_desc;
1514 
1515 	pango_context = gtk_widget_get_pango_context (widget);
1516 	font_metrics = pango_context_get_metrics (
1517 		pango_context, font_desc,
1518 		pango_context_get_language (pango_context));
1519 
1520 	char_height =
1521 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1522 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
1523 
1524 	min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
1525 		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
1526 	min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;
1527 
1528 	layout = pango_cairo_create_layout (cr);
1529 
1530 	/* Calculate the number of days in the previous, current, and next
1531 	 * months. */
1532 	years[0] = years[1] = years[2] = year;
1533 	months[0] = month - 1;
1534 	months[1] = month;
1535 	months[2] = month + 1;
1536 	if (months[0] == -1) {
1537 		months[0] = 11;
1538 		years[0]--;
1539 	}
1540 	if (months[2] == 12) {
1541 		months[2] = 0;
1542 		years[2]++;
1543 	}
1544 
1545 	days_in_month[0] = DAYS_IN_MONTH (years[0], months[0]);
1546 	days_in_month[1] = DAYS_IN_MONTH (years[1], months[1]);
1547 	days_in_month[2] = DAYS_IN_MONTH (years[2], months[2]);
1548 
1549 	/* Mon 0 is the previous month, which we may show the end of. Mon 1 is
1550 	 * the current month, and mon 2 is the next month. */
1551 	mon = 0;
1552 
1553 	month_offset = row * calitem->cols + col - 1;
1554 	day_num = days_in_month[0];
1555 	days_from_week_start = (start_weekday + 7 - calitem->week_start_day)
1556 		% 7;
1557 	/* For the top-left month we show the end of the previous month, and
1558 	 * if the new month starts on the first day of the week we show a
1559 	 * complete week from the previous month. */
1560 	if (days_from_week_start == 0) {
1561 		if (row == 0 && col == 0) {
1562 			day_num -= 6;
1563 		} else {
1564 			mon++;
1565 			month_offset++;
1566 			day_num = 1;
1567 		}
1568 	} else {
1569 		day_num -= days_from_week_start - 1;
1570 	}
1571 
1572 	/* Get today's date, so we can highlight it. */
1573 	if (calitem->time_callback) {
1574 		today_tm = calitem->time_callback (
1575 			calitem, calitem->time_callback_data);
1576 	} else {
1577 		t = time (NULL);
1578 		today_tm = *localtime (&t);
1579 	}
1580 	today_year = today_tm.tm_year + 1900;
1581 	today_month = today_tm.tm_mon;
1582 	today_mday = today_tm.tm_mday;
1583 
1584 	/* We usually skip the last days of the previous month (mon = 0),
1585 	 * except for the top-left month displayed. */
1586 	draw_day = (mon == 1 || (row == 0 && col == 0));
1587 
1588 	for (drow = 0; drow < 6; drow++) {
1589 		/* Draw the week number. */
1590 		if (calitem->show_week_numbers) {
1591 			week_num = e_calendar_item_get_week_number (
1592 				calitem, day_num, months[mon], years[mon]);
1593 
1594 			text_x = cells_x - E_CALENDAR_ITEM_XPAD_BEFORE_CELLS - 1
1595 				- E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS;
1596 			text_y = cells_y + drow * calitem->cell_height +
1597 				+ (calitem->cell_height - min_cell_height + 1) / 2;
1598 
1599 			num_chars = 0;
1600 			if (week_num >= 10) {
1601 				digit = week_num / 10;
1602 				text_x -= calitem->week_number_digit_widths[digit];
1603 				num_chars += sprintf (
1604 					&buffer[num_chars],
1605 					get_digit_fomat (), digit);
1606 			}
1607 
1608 			digit = week_num % 10;
1609 			text_x -= calitem->week_number_digit_widths[digit] + 6;
1610 			num_chars += sprintf (
1611 				&buffer[num_chars],
1612 				get_digit_fomat (), digit);
1613 
1614 			cairo_save (cr);
1615 			gdk_cairo_set_source_color (
1616 				cr, &style->text[GTK_STATE_ACTIVE]);
1617 			pango_layout_set_font_description (layout, font_desc);
1618 			pango_layout_set_text (layout, buffer, num_chars);
1619 			cairo_move_to (cr, text_x, text_y);
1620 			pango_cairo_update_layout (cr, layout);
1621 			pango_cairo_show_layout (cr, layout);
1622 			cairo_restore (cr);
1623 		}
1624 
1625 		for (dcol = 0; dcol < 7; dcol++) {
1626 			if (draw_day) {
1627 				day_x = cells_x +
1628 					((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1629 					? 7 - 1 - dcol : dcol) * calitem->cell_width;
1630 
1631 				day_y = cells_y + drow * calitem->cell_height;
1632 
1633 				today = years[mon] == today_year
1634 					&& months[mon] == today_month
1635 					&& day_num == today_mday;
1636 
1637 				selected = calitem->selection_set
1638 					&& (calitem->selection_start_month_offset < month_offset
1639 					    || (calitem->selection_start_month_offset == month_offset
1640 						&& calitem->selection_start_day <= day_num))
1641 					&& (calitem->selection_end_month_offset > month_offset
1642 					    || (calitem->selection_end_month_offset == month_offset
1643 						&& calitem->selection_end_day >= day_num));
1644 
1645 				if (calitem->styles)
1646 					day_style = calitem->styles[(month_offset + 1) * 32 + day_num];
1647 
1648 				/* Get the colors & style to use for the day.*/
1649 				if ((gtk_widget_has_focus (GTK_WIDGET (item->canvas))) &&
1650 				    item->canvas->focused_item == item)
1651 					has_focus = TRUE;
1652 				else
1653 					has_focus = FALSE;
1654 
1655 				bold = FALSE;
1656 				italic = FALSE;
1657 
1658 				if (calitem->style_callback)
1659 					calitem->style_callback (
1660 						calitem,
1661 						 years[mon],
1662 						 months[mon],
1663 						 day_num,
1664 						 day_style,
1665 						 today,
1666 						 mon != 1,
1667 						 selected,
1668 						 has_focus,
1669 						 drop_target,
1670 						 &bg_color,
1671 						 &fg_color,
1672 						 &box_color,
1673 						 &bold,
1674 						 &italic,
1675 						 calitem->style_callback_data);
1676 				else
1677 					e_calendar_item_get_day_style (
1678 						calitem,
1679 						years[mon],
1680 						months[mon],
1681 						day_num,
1682 						day_style,
1683 						today,
1684 						mon != 1,
1685 						selected,
1686 						has_focus,
1687 						drop_target,
1688 						&bg_color,
1689 						&fg_color,
1690 						&box_color,
1691 						&bold,
1692 						&italic);
1693 
1694 				/* Draw the background, if set. */
1695 				if (bg_color) {
1696 					cairo_save (cr);
1697 					gdk_cairo_set_source_color (cr, bg_color);
1698 					cairo_rectangle (
1699 						cr, day_x , day_y,
1700 						calitem->cell_width,
1701 						calitem->cell_height);
1702 					cairo_fill (cr);
1703 					cairo_restore (cr);
1704 				}
1705 
1706 				/* Draw the box, if set. */
1707 				if (box_color) {
1708 					cairo_save (cr);
1709 					gdk_cairo_set_source_color (cr, box_color);
1710 					cairo_rectangle (
1711 						cr, day_x , day_y,
1712 						calitem->cell_width - 1,
1713 						calitem->cell_height - 1);
1714 					cairo_stroke (cr);
1715 					cairo_restore (cr);
1716 				}
1717 
1718 				/* Draw the 1- or 2-digit day number. */
1719 				day_x += calitem->cell_width -
1720 					(calitem->cell_width -
1721 					min_cell_width) / 2;
1722 				day_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
1723 				day_y += (calitem->cell_height - min_cell_height + 1) / 2;
1724 				day_y += E_CALENDAR_ITEM_MIN_CELL_YPAD / 2;
1725 
1726 				num_chars = 0;
1727 				if (day_num >= 10) {
1728 					digit = day_num / 10;
1729 					day_x -= calitem->digit_widths[digit];
1730 					num_chars += sprintf (
1731 						&buffer[num_chars],
1732 						get_digit_fomat (), digit);
1733 				}
1734 
1735 				digit = day_num % 10;
1736 				day_x -= calitem->digit_widths[digit];
1737 				num_chars += sprintf (
1738 					&buffer[num_chars],
1739 					get_digit_fomat (), digit);
1740 
1741 				cairo_save (cr);
1742 				if (fg_color) {
1743 					gdk_cairo_set_source_color (
1744 						cr, fg_color);
1745 				} else {
1746 					gdk_cairo_set_source_color (
1747 						cr, &style->fg[GTK_STATE_NORMAL]);
1748 				}
1749 
1750 				if (bold) {
1751 					pango_font_description_set_weight (
1752 						font_desc, PANGO_WEIGHT_BOLD);
1753 				} else {
1754 					pango_font_description_set_weight (
1755 						font_desc, PANGO_WEIGHT_NORMAL);
1756 				}
1757 
1758 				if (italic) {
1759 					pango_font_description_set_style (
1760 						font_desc, PANGO_STYLE_ITALIC);
1761 				} else {
1762 					pango_font_description_set_style (
1763 						font_desc, PANGO_STYLE_NORMAL);
1764 				}
1765 
1766 				pango_layout_set_font_description (layout, font_desc);
1767 				pango_layout_set_text (layout, buffer, num_chars);
1768 				cairo_move_to (cr, day_x, day_y);
1769 				pango_cairo_update_layout (cr, layout);
1770 				pango_cairo_show_layout (cr, layout);
1771 				cairo_restore (cr);
1772 			}
1773 
1774 			/* See if we've reached the end of a month. */
1775 			if (day_num == days_in_month[mon]) {
1776 				month_offset++;
1777 				mon++;
1778 				/* We only draw the start of the next month
1779 				 * for the bottom-right month displayed. */
1780 				if (mon == 2 && (row != calitem->rows - 1
1781 						 || col != calitem->cols - 1)) {
1782 					/* Set a flag so we exit the loop. */
1783 					finished = TRUE;
1784 					break;
1785 				}
1786 				day_num = 1;
1787 				draw_day = TRUE;
1788 			} else {
1789 				day_num++;
1790 			}
1791 		}
1792 
1793 		/* Exit the loop if the flag is set. */
1794 		if (finished)
1795 			break;
1796 	}
1797 
1798 	/* Reset pango weight and style */
1799 	pango_font_description_set_weight (font_desc, PANGO_WEIGHT_NORMAL);
1800 	pango_font_description_set_style (font_desc, PANGO_STYLE_NORMAL);
1801 
1802 	g_object_unref (layout);
1803 
1804 	pango_font_metrics_unref (font_metrics);
1805 }
1806 
1807 gint
1808 e_calendar_item_get_week_number (ECalendarItem *calitem,
1809                                  gint day,
1810                                  gint month,
1811                                  gint year)
1812 {
1813 	GDate date;
1814 	guint weekday, yearday;
1815 	gint week_num;
1816 
1817 	g_date_clear (&date, 1);
1818 	g_date_set_dmy (&date, day, month + 1, year);
1819 
1820 	/* This results in a value of 0 (Monday) - 6 (Sunday).
1821 	 * (or -1 on error - oops!!) */
1822 	weekday = g_date_get_weekday (&date) - 1;
1823 
1824 	if (weekday > 0) {
1825 		/* we want always point to nearest Monday, as the first day of the week,
1826 		 * regardless of the calendar's week_start_day */
1827 		if (weekday >= 3)
1828 			g_date_add_days (&date, 7 - weekday);
1829 		else
1830 			g_date_subtract_days (&date, weekday);
1831 	}
1832 
1833 	/* Calculate the day of the year, from 0 to 365. */
1834 	yearday = g_date_get_day_of_year (&date) - 1;
1835 
1836 	/* If the week starts on or after 29th December, it is week 1 of the
1837 	 * next year, since there are 4 days in the next year. */
1838 	if (g_date_get_month (&date) == 12 && g_date_get_day (&date) >= 29)
1839 		return 1;
1840 
1841 	/* Calculate the week number, from 0. */
1842 	week_num = yearday / 7;
1843 
1844 	/* If the first week starts on or after Jan 5th, then we need to add
1845 	 * 1 since the previous week will really be the first week. */
1846 	if (yearday % 7 >= 4)
1847 		week_num++;
1848 
1849 	/* Add 1 so week numbers are from 1 to 53. */
1850 	return week_num + 1;
1851 }
1852 
1853 /* This is supposed to return the nearest item the the point and the distance.
1854  * Since we are the only item we just return ourself and 0 for the distance.
1855  * This is needed so that we get button/motion events. */
1856 static GnomeCanvasItem *
1857 e_calendar_item_point (GnomeCanvasItem *item,
1858                        gdouble x,
1859                        gdouble y,
1860                        gint cx,
1861                        gint cy)
1862 {
1863 	return item;
1864 }
1865 
1866 static void
1867 e_calendar_item_stop_selecting (ECalendarItem *calitem,
1868                                 guint32 time)
1869 {
1870 	if (!calitem->selecting)
1871 		return;
1872 
1873 	gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (calitem), time);
1874 
1875 	calitem->selecting = FALSE;
1876 
1877 	/* If the user selects the grayed dates before the first month or
1878 	 * after the last month, we move backwards or forwards one month.
1879 	 * The set_month () call should take care of updating the selection. */
1880 	if (calitem->selection_end_month_offset == -1)
1881 		e_calendar_item_set_first_month (
1882 			calitem, calitem->year,
1883 			calitem->month - 1);
1884 	else if (calitem->selection_start_month_offset == calitem->rows * calitem->cols)
1885 		e_calendar_item_set_first_month (
1886 			calitem, calitem->year,
1887 			calitem->month + 1);
1888 
1889 	calitem->selection_changed = TRUE;
1890 	if (calitem->selecting_axis) {
1891 		g_free (calitem->selecting_axis);
1892 		calitem->selecting_axis = NULL;
1893 	}
1894 
1895 	e_calendar_item_queue_signal_emission (calitem);
1896 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
1897 }
1898 
1899 static void
1900 e_calendar_item_selection_add_days (ECalendarItem *calitem,
1901                                     gint n_days,
1902                                     gboolean multi_selection)
1903 {
1904 	GDate gdate_start, gdate_end;
1905 
1906 	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
1907 
1908 	if (!e_calendar_item_get_selection (calitem, &gdate_start, &gdate_end)) {
1909 		/* We set the date to the first day of the month */
1910 		g_date_set_dmy (&gdate_start, 1, calitem->month + 1, calitem->year);
1911 		gdate_end = gdate_start;
1912 	}
1913 
1914 	if (multi_selection && calitem->max_days_selected > 1) {
1915 		gint days_between;
1916 
1917 		days_between = g_date_days_between (&gdate_start, &gdate_end);
1918 		if (!calitem->selecting_axis) {
1919 			calitem->selecting_axis = g_new (GDate, 1);
1920 			*(calitem->selecting_axis) = gdate_start;
1921 		}
1922 		if ((days_between != 0 &&
1923 		     g_date_compare (calitem->selecting_axis, &gdate_end) == 0) ||
1924 		    (days_between == 0 && n_days < 0)) {
1925 			if (days_between - n_days > calitem->max_days_selected - 1)
1926 				n_days =  days_between + 1 - calitem->max_days_selected;
1927 			g_date_add_days (&gdate_start, n_days);
1928 		}
1929 		else {
1930 			if (days_between + n_days > calitem->max_days_selected - 1)
1931 				n_days = calitem->max_days_selected - 1 - days_between;
1932 			g_date_add_days (&gdate_end, n_days);
1933 		}
1934 
1935 		if (g_date_compare (&gdate_end, &gdate_start) < 0) {
1936 			GDate tmp_date;
1937 			tmp_date = gdate_start;
1938 			gdate_start = gdate_end;
1939 			gdate_end = tmp_date;
1940 		}
1941 	}
1942 	else {
1943 		/* clear "selecting_axis", it is only for mulit-selecting */
1944 		if (calitem->selecting_axis) {
1945 			g_free (calitem->selecting_axis);
1946 			calitem->selecting_axis = NULL;
1947 		}
1948 		g_date_add_days (&gdate_start, n_days);
1949 		gdate_end = gdate_start;
1950 	}
1951 
1952 	calitem->selecting = TRUE;
1953 
1954 	e_calendar_item_set_selection_if_emission (
1955 		calitem, &gdate_start, &gdate_end, FALSE);
1956 
1957 	g_signal_emit_by_name (calitem, "selection_preview_changed");
1958 }
1959 
1960 static gint
1961 e_calendar_item_key_press_event (ECalendarItem *calitem,
1962                                  GdkEvent *event)
1963 {
1964 	guint keyval = event->key.keyval;
1965 	gboolean multi_selection = FALSE;
1966 
1967 	if (event->key.state & GDK_CONTROL_MASK ||
1968 	    event->key.state & GDK_MOD1_MASK)
1969 		return FALSE;
1970 
1971 	multi_selection = event->key.state & GDK_SHIFT_MASK;
1972 	switch (keyval) {
1973 	case GDK_KEY_Up:
1974 		e_calendar_item_selection_add_days (
1975 			calitem, -7,
1976 			multi_selection);
1977 		break;
1978 	case GDK_KEY_Down:
1979 		e_calendar_item_selection_add_days (
1980 			calitem, 7,
1981 			multi_selection);
1982 		break;
1983 	case GDK_KEY_Left:
1984 		e_calendar_item_selection_add_days (
1985 			calitem, -1,
1986 			multi_selection);
1987 		break;
1988 	case GDK_KEY_Right:
1989 		e_calendar_item_selection_add_days (
1990 			calitem, 1,
1991 			multi_selection);
1992 		break;
1993 	case GDK_KEY_space:
1994 	case GDK_KEY_Return:
1995 		e_calendar_item_stop_selecting (calitem, event->key.time);
1996 		break;
1997 	default:
1998 		return FALSE;
1999 	}
2000 	return TRUE;
2001 }
2002 
2003 static gint
2004 e_calendar_item_event (GnomeCanvasItem *item,
2005                        GdkEvent *event)
2006 {
2007 	ECalendarItem *calitem;
2008 
2009 	calitem = E_CALENDAR_ITEM (item);
2010 
2011 	switch (event->type) {
2012 	case GDK_BUTTON_PRESS:
2013 		return e_calendar_item_button_press (calitem, event);
2014 	case GDK_BUTTON_RELEASE:
2015 		return e_calendar_item_button_release (calitem, event);
2016 	case GDK_MOTION_NOTIFY:
2017 		return e_calendar_item_motion (calitem, event);
2018 	case GDK_FOCUS_CHANGE:
2019 		gnome_canvas_item_request_update (item);
2020 		return FALSE;
2021 	case GDK_KEY_PRESS:
2022 		return e_calendar_item_key_press_event (calitem, event);
2023 	default:
2024 		break;
2025 	}
2026 
2027 	return FALSE;
2028 }
2029 
2030 static void
2031 e_calendar_item_bounds (GnomeCanvasItem *item,
2032                         gdouble *x1,
2033                         gdouble *y1,
2034                         gdouble *x2,
2035                         gdouble *y2)
2036 {
2037 	ECalendarItem *calitem;
2038 
2039 	g_return_if_fail (E_IS_CALENDAR_ITEM (item));
2040 
2041 	calitem = E_CALENDAR_ITEM (item);
2042 	*x1 = calitem->x1;
2043 	*y1 = calitem->y1;
2044 	*x2 = calitem->x2;
2045 	*y2 = calitem->y2;
2046 }
2047 
2048 /* This checks if any fonts have changed, and if so it recalculates the
2049  * text sizes and the minimum month size. */
2050 static void
2051 e_calendar_item_recalc_sizes (ECalendarItem *calitem)
2052 {
2053 	GnomeCanvasItem *canvas_item;
2054 	GtkStyle *style;
2055 	gint day, max_day_width, digit, max_digit_width, max_week_number_digit_width;
2056 	gint char_height, width, min_cell_width, min_cell_height;
2057 	gchar buffer[64];
2058 	struct tm tmp_tm;
2059 	PangoFontDescription *font_desc, *wkfont_desc;
2060 	PangoContext *pango_context;
2061 	PangoFontMetrics *font_metrics;
2062 	PangoLayout *layout;
2063 
2064 	canvas_item = GNOME_CANVAS_ITEM (calitem);
2065 	style = gtk_widget_get_style (GTK_WIDGET (canvas_item->canvas));
2066 
2067 	if (!style)
2068 		return;
2069 
2070 	/* Set up Pango prerequisites */
2071 	font_desc = calitem->font_desc;
2072 	wkfont_desc = calitem->week_number_font_desc;
2073 	if (!font_desc)
2074 		font_desc = style->font_desc;
2075 
2076 	pango_context = gtk_widget_create_pango_context (
2077 		GTK_WIDGET (canvas_item->canvas));
2078 	font_metrics = pango_context_get_metrics (
2079 		pango_context, font_desc,
2080 		pango_context_get_language (pango_context));
2081 	layout = pango_layout_new (pango_context);
2082 
2083 	char_height =
2084 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
2085 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
2086 
2087 	max_day_width = 0;
2088 	for (day = 0; day < 7; day++) {
2089 		layout_set_day_text (calitem, layout, day);
2090 		pango_layout_get_pixel_size (layout, &width, NULL);
2091 
2092 		calitem->day_widths[day] = width;
2093 		max_day_width = MAX (max_day_width, width);
2094 	}
2095 	calitem->max_day_width = max_day_width;
2096 
2097 	max_digit_width = 0;
2098 	max_week_number_digit_width = 0;
2099 	for (digit = 0; digit < 10; digit++) {
2100 		gchar locale_digit[5];
2101 		gint locale_digit_len;
2102 
2103 		locale_digit_len = sprintf (locale_digit, get_digit_fomat (), digit);
2104 
2105 		pango_layout_set_text (layout, locale_digit, locale_digit_len);
2106 		pango_layout_get_pixel_size (layout, &width, NULL);
2107 
2108 		calitem->digit_widths[digit] = width;
2109 		max_digit_width = MAX (max_digit_width, width);
2110 
2111 		if (wkfont_desc) {
2112 			pango_context_set_font_description (pango_context, wkfont_desc);
2113 			pango_layout_context_changed (layout);
2114 
2115 			pango_layout_set_text (layout, locale_digit, locale_digit_len);
2116 			pango_layout_get_pixel_size (layout, &width, NULL);
2117 
2118 			calitem->week_number_digit_widths[digit] = width;
2119 			max_week_number_digit_width = MAX (max_week_number_digit_width, width);
2120 
2121 			pango_context_set_font_description (pango_context, font_desc);
2122 			pango_layout_context_changed (layout);
2123 		} else {
2124 			calitem->week_number_digit_widths[digit] = width;
2125 			max_week_number_digit_width = max_digit_width;
2126 		}
2127 	}
2128 	calitem->max_digit_width = max_digit_width;
2129 	calitem->max_week_number_digit_width = max_week_number_digit_width;
2130 
2131 	min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
2132 		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
2133 	min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;
2134 
2135 	calitem->min_month_width = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS
2136 		+ E_CALENDAR_ITEM_XPAD_BEFORE_CELLS + min_cell_width * 7
2137 		+ E_CALENDAR_ITEM_XPAD_AFTER_CELLS;
2138 	if (calitem->show_week_numbers) {
2139 		calitem->min_month_width += calitem->max_week_number_digit_width * 2
2140 			+ E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
2141 	}
2142 
2143 	calitem->min_month_height = style->ythickness * 2
2144 		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height
2145 		+ E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + 1
2146 		+ E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS
2147 		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
2148 		+ E_CALENDAR_ITEM_YPAD_ABOVE_CELLS + min_cell_height * 6
2149 		+ E_CALENDAR_ITEM_YPAD_BELOW_CELLS;
2150 
2151 	calitem->max_month_name_width = 50;
2152 	memset (&tmp_tm, 0, sizeof (tmp_tm));
2153 	tmp_tm.tm_year = 2000 - 100;
2154 	tmp_tm.tm_mday = 1;
2155 	tmp_tm.tm_isdst = -1;
2156 	for (tmp_tm.tm_mon = 0; tmp_tm.tm_mon < 12; tmp_tm.tm_mon++) {
2157 		mktime (&tmp_tm);
2158 
2159 		e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);
2160 
2161 		pango_layout_set_text (layout, buffer, -1);
2162 		pango_layout_get_pixel_size (layout, &width, NULL);
2163 
2164 		if (width > calitem->max_month_name_width)
2165 			calitem->max_month_name_width = width;
2166 	}
2167 
2168 	g_object_unref (layout);
2169 	g_object_unref (pango_context);
2170 	pango_font_metrics_unref (font_metrics);
2171 }
2172 
2173 static void
2174 e_calendar_item_get_day_style (ECalendarItem *calitem,
2175                                gint year,
2176                                gint month,
2177                                gint day,
2178                                gint day_style,
2179                                gboolean today,
2180                                gboolean prev_or_next_month,
2181                                gboolean selected,
2182                                gboolean has_focus,
2183                                gboolean drop_target,
2184                                GdkColor **bg_color,
2185                                GdkColor **fg_color,
2186                                GdkColor **box_color,
2187                                gboolean *bold,
2188                                gboolean *italic)
2189 {
2190 	GtkWidget *widget;
2191 	GtkStyle *style;
2192 
2193 	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas);
2194 	style = gtk_widget_get_style (widget);
2195 
2196 	*bg_color = NULL;
2197 	*fg_color = NULL;
2198 	*box_color = NULL;
2199 
2200 	*bold = (day_style & E_CALENDAR_ITEM_MARK_BOLD) ==
2201 		E_CALENDAR_ITEM_MARK_BOLD;
2202 	*italic = (day_style & E_CALENDAR_ITEM_MARK_ITALIC) ==
2203 		E_CALENDAR_ITEM_MARK_ITALIC;
2204 
2205 	if (today)
2206 		*box_color = &calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX];
2207 
2208 	if (prev_or_next_month)
2209 		*fg_color = &style->mid[gtk_widget_get_state (widget)];
2210 
2211 	if (selected) {
2212 		if (has_focus) {
2213 			*fg_color = &style->text[GTK_STATE_SELECTED];
2214 			*bg_color = &style->base[GTK_STATE_SELECTED];
2215 		} else {
2216 			*fg_color = &style->text[GTK_STATE_ACTIVE];
2217 			*bg_color = &style->base[GTK_STATE_ACTIVE];
2218 
2219 			if ((*bg_color)->red == style->base[GTK_STATE_NORMAL].red &&
2220 			    (*bg_color)->green == style->base[GTK_STATE_NORMAL].green &&
2221 			    (*bg_color)->blue == style->base[GTK_STATE_NORMAL].blue) {
2222 				*fg_color = &style->text[GTK_STATE_SELECTED];
2223 				*bg_color = &style->base[GTK_STATE_SELECTED];
2224 			}
2225 		}
2226 	}
2227 }
2228 
2229 static gboolean
2230 e_calendar_item_button_press (ECalendarItem *calitem,
2231                               GdkEvent *event)
2232 {
2233 	gint month_offset, day, add_days = 0;
2234 	gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;
2235 
2236 	if (event->button.button == 4)
2237 		e_calendar_item_set_first_month (
2238 			calitem, calitem->year,
2239 			calitem->month - 1);
2240 	else if (event->button.button == 5)
2241 		e_calendar_item_set_first_month (
2242 			calitem, calitem->year,
2243 			calitem->month + 1);
2244 
2245 	if (!e_calendar_item_convert_position_to_day (calitem,
2246 						      event->button.x,
2247 						      event->button.y,
2248 						      TRUE,
2249 						      &month_offset, &day,
2250 						      &all_week))
2251 		return FALSE;
2252 
2253 	if (event->button.button == 3 && day == -1
2254 	    && e_calendar_item_get_display_popup (calitem)) {
2255 		e_calendar_item_show_popup_menu (
2256 			calitem,
2257 			(GdkEventButton *) event,
2258 			month_offset);
2259 		return TRUE;
2260 	}
2261 
2262 	if (event->button.button != 1 || day == -1)
2263 		return FALSE;
2264 
2265 	if (calitem->max_days_selected < 1)
2266 		return TRUE;
2267 
2268 	if (gnome_canvas_item_grab (GNOME_CANVAS_ITEM (calitem),
2269 				    GDK_POINTER_MOTION_MASK
2270 				    | GDK_BUTTON_RELEASE_MASK,
2271 				    NULL, event->button.time) != 0)
2272 		return FALSE;
2273 
2274 	if (all_week && calitem->keep_wdays_on_weeknum_click) {
2275 		gint tmp_start_moff, tmp_start_day;
2276 
2277 		tmp_start_moff = calitem->selection_start_month_offset;
2278 		tmp_start_day = calitem->selection_start_day;
2279 		e_calendar_item_round_down_selection (
2280 			calitem, &tmp_start_moff, &tmp_start_day);
2281 
2282 		e_calendar_item_round_down_selection (calitem, &month_offset, &day);
2283 		month_offset += calitem->selection_start_month_offset - tmp_start_moff;
2284 		day += calitem->selection_start_day - tmp_start_day;
2285 
2286 		/* keep same count of days selected */
2287 		add_days = e_calendar_item_get_inclusive_days (
2288 			calitem,
2289 			calitem->selection_start_month_offset,
2290 			calitem->selection_start_day,
2291 			calitem->selection_end_month_offset,
2292 			calitem->selection_end_day) - 1;
2293 	}
2294 
2295 	calitem->selection_set = TRUE;
2296 	calitem->selection_start_month_offset = month_offset;
2297 	calitem->selection_start_day = day;
2298 	calitem->selection_end_month_offset = month_offset;
2299 	calitem->selection_end_day = day;
2300 
2301 	if (add_days > 0)
2302 		e_calendar_item_add_days_to_selection (calitem, add_days);
2303 
2304 	calitem->selection_real_start_month_offset = month_offset;
2305 	calitem->selection_real_start_day = day;
2306 
2307 	calitem->selection_from_full_week = FALSE;
2308 	calitem->selecting = TRUE;
2309 	calitem->selection_dragging_end = TRUE;
2310 
2311 	if (all_week && !calitem->keep_wdays_on_weeknum_click) {
2312 		calitem->selection_from_full_week = TRUE;
2313 		round_up_end = TRUE;
2314 	}
2315 
2316 	if (calitem->days_to_start_week_selection == 1) {
2317 		round_down_start = TRUE;
2318 		round_up_end = TRUE;
2319 	}
2320 
2321 	/* Don't round up or down if we can't select a week or more,
2322 	 * or when keeping week days. */
2323 	if (calitem->max_days_selected < 7 ||
2324 		(all_week && calitem->keep_wdays_on_weeknum_click)) {
2325 		round_down_start = FALSE;
2326 		round_up_end = FALSE;
2327 	}
2328 
2329 	if (round_up_end)
2330 		e_calendar_item_round_up_selection (
2331 			calitem, &calitem->selection_end_month_offset,
2332 			&calitem->selection_end_day);
2333 
2334 	if (round_down_start)
2335 		e_calendar_item_round_down_selection (
2336 			calitem, &calitem->selection_start_month_offset,
2337 			&calitem->selection_start_day);
2338 
2339 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
2340 
2341 	return TRUE;
2342 }
2343 
2344 static gboolean
2345 e_calendar_item_button_release (ECalendarItem *calitem,
2346                                 GdkEvent *event)
2347 {
2348 	e_calendar_item_stop_selecting (calitem, event->button.time);
2349 	return FALSE;
2350 }
2351 
2352 static gboolean
2353 e_calendar_item_motion (ECalendarItem *calitem,
2354                         GdkEvent *event)
2355 {
2356 	gint start_month, start_day, end_month, end_day, month_offset, day;
2357 	gint tmp_month, tmp_day, days_in_selection;
2358 	gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;
2359 
2360 	if (!calitem->selecting)
2361 		return FALSE;
2362 
2363 	if (!e_calendar_item_convert_position_to_day (calitem,
2364 						      event->button.x,
2365 						      event->button.y,
2366 						      TRUE,
2367 						      &month_offset, &day,
2368 						      &all_week))
2369 		return FALSE;
2370 
2371 	if (day == -1)
2372 		return FALSE;
2373 
2374 	if (calitem->selection_dragging_end) {
2375 		start_month = calitem->selection_real_start_month_offset;
2376 		start_day = calitem->selection_real_start_day;
2377 		end_month = month_offset;
2378 		end_day = day;
2379 	} else {
2380 		start_month = month_offset;
2381 		start_day = day;
2382 		end_month = calitem->selection_real_start_month_offset;
2383 		end_day = calitem->selection_real_start_day;
2384 	}
2385 
2386 	if (start_month > end_month || (start_month == end_month
2387 					&& start_day > end_day)) {
2388 		tmp_month = start_month;
2389 		tmp_day = start_day;
2390 		start_month = end_month;
2391 		start_day = end_day;
2392 		end_month = tmp_month;
2393 		end_day = tmp_day;
2394 
2395 		calitem->selection_dragging_end =
2396 			!calitem->selection_dragging_end;
2397 	}
2398 
2399 	if (calitem->days_to_start_week_selection > 0) {
2400 		days_in_selection = e_calendar_item_get_inclusive_days (
2401 			calitem, start_month, start_day, end_month, end_day);
2402 		if (days_in_selection >= calitem->days_to_start_week_selection) {
2403 			round_down_start = TRUE;
2404 			round_up_end = TRUE;
2405 		}
2406 	}
2407 
2408 	/* If we are over a week number and we are dragging the end of the
2409 	 * selection, we round up to the end of this week. */
2410 	if (all_week && calitem->selection_dragging_end)
2411 		round_up_end = TRUE;
2412 
2413 	/* If the selection was started from a week number and we are dragging
2414 	 * the start of the selection, we need to round up the end to include
2415 	 * all of the original week selected. */
2416 	if (calitem->selection_from_full_week
2417 	    && !calitem->selection_dragging_end)
2418 			round_up_end = TRUE;
2419 
2420 	/* Don't round up or down if we can't select a week or more. */
2421 	if (calitem->max_days_selected < 7) {
2422 		round_down_start = FALSE;
2423 		round_up_end = FALSE;
2424 	}
2425 
2426 	if (round_up_end)
2427 		e_calendar_item_round_up_selection (
2428 			calitem, &end_month,
2429 			&end_day);
2430 	if (round_down_start)
2431 		e_calendar_item_round_down_selection (
2432 			calitem, &start_month,
2433 			&start_day);
2434 
2435 	/* Check we don't go over the maximum number of days to select. */
2436 	if (calitem->selection_dragging_end) {
2437 		e_calendar_item_check_selection_end (
2438 			calitem,
2439 			start_month,
2440 			start_day,
2441 			&end_month,
2442 			&end_day);
2443 	} else {
2444 		e_calendar_item_check_selection_start (
2445 			calitem,
2446 			&start_month,
2447 			&start_day,
2448 			end_month,
2449 			end_day);
2450 	}
2451 
2452 	if (start_month == calitem->selection_start_month_offset
2453 	    && start_day == calitem->selection_start_day
2454 	    && end_month == calitem->selection_end_month_offset
2455 	    && end_day == calitem->selection_end_day)
2456 		return FALSE;
2457 
2458 	calitem->selection_start_month_offset = start_month;
2459 	calitem->selection_start_day = start_day;
2460 	calitem->selection_end_month_offset = end_month;
2461 	calitem->selection_end_day = end_day;
2462 
2463 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
2464 
2465 	return TRUE;
2466 }
2467 
2468 static void
2469 e_calendar_item_check_selection_end (ECalendarItem *calitem,
2470                                      gint start_month,
2471                                      gint start_day,
2472                                      gint *end_month,
2473                                      gint *end_day)
2474 {
2475 	gint year, month, max_month, max_day, days_in_month;
2476 
2477 	if (calitem->max_days_selected <= 0)
2478 		return;
2479 
2480 	year = calitem->year;
2481 	month = calitem->month + start_month;
2482 	e_calendar_item_normalize_date (calitem, &year, &month);
2483 
2484 	max_month = start_month;
2485 	max_day = start_day + calitem->max_days_selected - 1;
2486 
2487 	for (;;) {
2488 		days_in_month = DAYS_IN_MONTH (year, month);
2489 		if (max_day <= days_in_month)
2490 			break;
2491 		max_month++;
2492 		month++;
2493 		if (month == 12) {
2494 			year++;
2495 			month = 0;
2496 		}
2497 		max_day -= days_in_month;
2498 	}
2499 
2500 	if (*end_month > max_month) {
2501 		*end_month = max_month;
2502 		*end_day = max_day;
2503 	} else if (*end_month == max_month && *end_day > max_day) {
2504 		*end_day = max_day;
2505 	}
2506 }
2507 
2508 static void
2509 e_calendar_item_check_selection_start (ECalendarItem *calitem,
2510                                        gint *start_month,
2511                                        gint *start_day,
2512                                        gint end_month,
2513                                        gint end_day)
2514 {
2515 	gint year, month, min_month, min_day, days_in_month;
2516 
2517 	if (calitem->max_days_selected <= 0)
2518 		return;
2519 
2520 	year = calitem->year;
2521 	month = calitem->month + end_month;
2522 	e_calendar_item_normalize_date (calitem, &year, &month);
2523 
2524 	min_month = end_month;
2525 	min_day = end_day - calitem->max_days_selected + 1;
2526 
2527 	while (min_day <= 0) {
2528 		min_month--;
2529 		month--;
2530 		if (month == -1) {
2531 			year--;
2532 			month = 11;
2533 		}
2534 		days_in_month = DAYS_IN_MONTH (year, month);
2535 		min_day += days_in_month;
2536 	}
2537 
2538 	if (*start_month < min_month) {
2539 		*start_month = min_month;
2540 		*start_day = min_day;
2541 	} else if (*start_month == min_month && *start_day < min_day) {
2542 		*start_day = min_day;
2543 	}
2544 }
2545 
2546 /* Converts a position within the item to a month & day.
2547  * The month returned is 0 for the top-left month displayed.
2548  * If the position is over the month heading -1 is returned for the day.
2549  * If the position is over a week number the first day of the week is returned
2550  * and entire_week is set to TRUE.
2551  * It returns FALSE if the position is completely outside all months. */
2552 static gboolean
2553 e_calendar_item_convert_position_to_day (ECalendarItem *calitem,
2554                                          gint event_x,
2555                                          gint event_y,
2556                                          gboolean round_empty_positions,
2557                                          gint *month_offset,
2558                                          gint *day,
2559                                          gboolean *entire_week)
2560 {
2561 	GnomeCanvasItem *item;
2562 	GtkWidget *widget;
2563 	GtkStyle *style;
2564 	gint xthickness, ythickness, char_height;
2565 	gint x, y, row, col, cells_x, cells_y, day_row, day_col;
2566 	gint first_day_offset, days_in_month, days_in_prev_month;
2567 	gint week_num_x1, week_num_x2;
2568 	PangoFontDescription *font_desc;
2569 	PangoContext *pango_context;
2570 	PangoFontMetrics *font_metrics;
2571 
2572 	item = GNOME_CANVAS_ITEM (calitem);
2573 	widget = GTK_WIDGET (item->canvas);
2574 	style = gtk_widget_get_style (widget);
2575 
2576 	font_desc = calitem->font_desc;
2577 	if (!font_desc)
2578 		font_desc = style->font_desc;
2579 	pango_context = gtk_widget_create_pango_context (widget);
2580 	font_metrics = pango_context_get_metrics (
2581 		pango_context, font_desc,
2582 		pango_context_get_language (pango_context));
2583 
2584 	char_height =
2585 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
2586 		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
2587 	xthickness = style->xthickness;
2588 	ythickness = style->ythickness;
2589 
2590 	pango_font_metrics_unref (font_metrics);
2591 
2592 	*entire_week = FALSE;
2593 
2594 	x = event_x - xthickness - calitem->x_offset;
2595 	y = event_y - ythickness;
2596 
2597 	if (x < 0 || y < 0)
2598 		return FALSE;
2599 
2600 	row = y / calitem->month_height;
2601 	col = x / calitem->month_width;
2602 
2603 	if (row >= calitem->rows || col >= calitem->cols)
2604 		return FALSE;
2605 	if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
2606 		col = calitem->cols - 1 - col;
2607 
2608 	*month_offset = row * calitem->cols + col;
2609 
2610 	x = x % calitem->month_width;
2611 	y = y % calitem->month_height;
2612 
2613 	if (y < ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
2614 	    + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME) {
2615 		*day = -1;
2616 		return TRUE;
2617 	}
2618 
2619 	cells_y = ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
2620 		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
2621 		+ E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad
2622 		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
2623 		+ E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
2624 	y -= cells_y;
2625 	if (y < 0)
2626 		return FALSE;
2627 	day_row = y / calitem->cell_height;
2628 	if (day_row >= E_CALENDAR_ROWS_PER_MONTH)
2629 		return FALSE;
2630 
2631 	week_num_x1 = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad;
2632 
2633 	if (calitem->show_week_numbers) {
2634 		week_num_x2 = week_num_x1
2635 			+ calitem->max_week_number_digit_width * 2;
2636 		if (x >= week_num_x1 && x < week_num_x2)
2637 			*entire_week = TRUE;
2638 		cells_x = week_num_x2 + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
2639 	} else {
2640 		cells_x = week_num_x1;
2641 	}
2642 
2643 	if (*entire_week) {
2644 		day_col = 0;
2645 	} else {
2646 		cells_x += E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
2647 		x -= cells_x;
2648 		if (x < 0)
2649 			return FALSE;
2650 		day_col = x / calitem->cell_width;
2651 		if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
2652 			day_col = E_CALENDAR_COLS_PER_MONTH - 1 - day_col;
2653 		if (day_col >= E_CALENDAR_COLS_PER_MONTH)
2654 			return FALSE;
2655 	}
2656 
2657 	*day = day_row * E_CALENDAR_COLS_PER_MONTH + day_col;
2658 
2659 	e_calendar_item_get_month_info (
2660 		calitem, row, col, &first_day_offset,
2661 		&days_in_month, &days_in_prev_month);
2662 	if (*day < first_day_offset) {
2663 		if (*entire_week || (row == 0 && col == 0)) {
2664 			(*month_offset)--;
2665 			*day = days_in_prev_month + 1 - first_day_offset
2666 				+ *day;
2667 			return TRUE;
2668 		} else if (round_empty_positions) {
2669 			*day = first_day_offset;
2670 		} else {
2671 			return FALSE;
2672 		}
2673 	}
2674 
2675 	*day -= first_day_offset - 1;
2676 
2677 	if (*day > days_in_month) {
2678 		if (row == calitem->rows - 1 && col == calitem->cols - 1) {
2679 			(*month_offset)++;
2680 			*day -= days_in_month;
2681 			return TRUE;
2682 		} else if (round_empty_positions) {
2683 			*day = days_in_month;
2684 		} else {
2685 			return FALSE;
2686 		}
2687 	}
2688 
2689 	return TRUE;
2690 }
2691 
2692 static void
2693 e_calendar_item_get_month_info (ECalendarItem *calitem,
2694                                 gint row,
2695                                 gint col,
2696                                 gint *first_day_offset,
2697                                 gint *days_in_month,
2698                                 gint *days_in_prev_month)
2699 {
2700 	gint year, month, start_weekday, first_day_of_month;
2701 	struct tm tmp_tm = { 0 };
2702 
2703 	month = calitem->month + row * calitem->cols + col;
2704 	year = calitem->year + month / 12;
2705 	month = month % 12;
2706 
2707 	*days_in_month = DAYS_IN_MONTH (year, month);
2708 	if (month == 0)
2709 		*days_in_prev_month = DAYS_IN_MONTH (year - 1, 11);
2710 	else
2711 		*days_in_prev_month = DAYS_IN_MONTH (year, month - 1);
2712 
2713 	tmp_tm.tm_year = year - 1900;
2714 	tmp_tm.tm_mon = month;
2715 	tmp_tm.tm_mday = 1;
2716 	tmp_tm.tm_isdst = -1;
2717 	mktime (&tmp_tm);
2718 
2719 	/* Convert to 0 (Monday) to 6 (Sunday). */
2720 	start_weekday = (tmp_tm.tm_wday + 6) % 7;
2721 
2722 	first_day_of_month = (start_weekday + 7 - calitem->week_start_day) % 7;
2723 
2724 	if (row == 0 && col == 0 && first_day_of_month == 0)
2725 		*first_day_offset = 7;
2726 	else
2727 		*first_day_offset = first_day_of_month;
2728 }
2729 
2730 void
2731 e_calendar_item_get_first_month (ECalendarItem *calitem,
2732                                  gint *year,
2733                                  gint *month)
2734 {
2735 	*year = calitem->year;
2736 	*month = calitem->month;
2737 }
2738 
2739 static void
2740 e_calendar_item_preserve_day_selection (ECalendarItem *calitem,
2741                                         gint selected_day,
2742                                         gint *month_offset,
2743                                         gint *day)
2744 {
2745 	gint year, month, weekday, days, days_in_month;
2746 	struct tm tmp_tm = { 0 };
2747 
2748 	year = calitem->year;
2749 	month = calitem->month + *month_offset;
2750 	e_calendar_item_normalize_date (calitem, &year, &month);
2751 
2752 	tmp_tm.tm_year = year - 1900;
2753 	tmp_tm.tm_mon = month;
2754 	tmp_tm.tm_mday = *day;
2755 	tmp_tm.tm_isdst = -1;
2756 	mktime (&tmp_tm);
2757 
2758 	/* Convert to 0 (Monday) to 6 (Sunday). */
2759 	weekday = (tmp_tm.tm_wday + 6) % 7;
2760 
2761 	/* Calculate how many days to the start of the row. */
2762 	days = (weekday + 7 - selected_day) % 7;
2763 
2764 	*day -= days;
2765 	if (*day <= 0) {
2766 		month--;
2767 		if (month == -1) {
2768 			year--;
2769 			month = 11;
2770 		}
2771 		days_in_month = DAYS_IN_MONTH (year, month);
2772 		(*month_offset)--;
2773 		*day += days_in_month;
2774 	}
2775 }
2776 
2777 /* This also handles values of month < 0 or > 11 by updating the year. */
2778 void
2779 e_calendar_item_set_first_month (ECalendarItem *calitem,
2780                                  gint year,
2781                                  gint month)
2782 {
2783 	gint new_year, new_month, months_diff, num_months;
2784 	gint old_days_in_selection, new_days_in_selection;
2785 
2786 	new_year = year;
2787 	new_month = month;
2788 	e_calendar_item_normalize_date (calitem, &new_year, &new_month);
2789 
2790 	if (calitem->year == new_year && calitem->month == new_month)
2791 		return;
2792 
2793 	/* Update the selection. */
2794 	num_months = calitem->rows * calitem->cols;
2795 	months_diff = (new_year - calitem->year) * 12
2796 		+ new_month - calitem->month;
2797 
2798 	if (calitem->selection_set) {
2799 		if (!calitem->move_selection_when_moving
2800 		    || (calitem->selection_start_month_offset - months_diff >= 0
2801 			&& calitem->selection_end_month_offset - months_diff < num_months)) {
2802 			calitem->selection_start_month_offset -= months_diff;
2803 			calitem->selection_end_month_offset -= months_diff;
2804 			calitem->selection_real_start_month_offset -= months_diff;
2805 
2806 			calitem->year = new_year;
2807 			calitem->month = new_month;
2808 		} else {
2809 			gint selected_day;
2810 			struct tm tmp_tm = { 0 };
2811 
2812 			old_days_in_selection = e_calendar_item_get_inclusive_days (
2813 				calitem,
2814 				calitem->selection_start_month_offset,
2815 				calitem->selection_start_day,
2816 				calitem->selection_end_month_offset,
2817 				calitem->selection_end_day);
2818 
2819 			/* Calculate the currently selected day */
2820 			tmp_tm.tm_year = calitem->year - 1900;
2821 			tmp_tm.tm_mon = calitem->month + calitem->selection_start_month_offset;
2822 			tmp_tm.tm_mday = calitem->selection_start_day;
2823 			tmp_tm.tm_isdst = -1;
2824 			mktime (&tmp_tm);
2825 
2826 			selected_day = (tmp_tm.tm_wday + 6) % 7;
2827 
2828 			/* Make sure the selection will be displayed. */
2829 			if (calitem->selection_start_month_offset < 0
2830 			    || calitem->selection_start_month_offset >= num_months) {
2831 				calitem->selection_end_month_offset -=
2832 					calitem->selection_start_month_offset;
2833 				calitem->selection_start_month_offset = 0;
2834 			}
2835 
2836 			/* We want to ensure that the same number of days are
2837 			 * selected after we have moved the selection. */
2838 			calitem->year = new_year;
2839 			calitem->month = new_month;
2840 
2841 			e_calendar_item_ensure_valid_day (
2842 				calitem, &calitem->selection_start_month_offset,
2843 				&calitem->selection_start_day);
2844 			e_calendar_item_ensure_valid_day (
2845 				calitem, &calitem->selection_end_month_offset,
2846 				&calitem->selection_end_day);
2847 
2848 			if (calitem->preserve_day_when_moving) {
2849 				e_calendar_item_preserve_day_selection (
2850 					calitem, selected_day,
2851 					&calitem->selection_start_month_offset,
2852 					&calitem->selection_start_day);
2853 			}
2854 
2855 			new_days_in_selection = e_calendar_item_get_inclusive_days (
2856 				calitem,
2857 				calitem->selection_start_month_offset,
2858 				calitem->selection_start_day,
2859 				calitem->selection_end_month_offset,
2860 				calitem->selection_end_day);
2861 
2862 			if (old_days_in_selection != new_days_in_selection)
2863 				e_calendar_item_add_days_to_selection (
2864 					calitem, old_days_in_selection -
2865 					new_days_in_selection);
2866 
2867 			/* Flag that we need to emit the "selection_changed"
2868 			 * signal. We don't want to emit it here since setting
2869 			 * the "year" and "month" args would result in 2
2870 			 * signals emitted. */
2871 			calitem->selection_changed = TRUE;
2872 		}
2873 	} else {
2874 		calitem->year = new_year;
2875 		calitem->month = new_month;
2876 	}
2877 
2878 	e_calendar_item_date_range_changed (calitem);
2879 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
2880 }
2881 
2882 /* Get the maximum number of days selectable */
2883 gint
2884 e_calendar_item_get_max_days_sel (ECalendarItem *calitem)
2885 {
2886 	return calitem->max_days_selected;
2887 }
2888 
2889 /* Set the maximum number of days selectable */
2890 void
2891 e_calendar_item_set_max_days_sel (ECalendarItem *calitem,
2892                                   gint days)
2893 {
2894 	calitem->max_days_selected = MAX (0, days);
2895 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
2896 }
2897 
2898 /* Get the maximum number of days before whole weeks are selected */
2899 gint
2900 e_calendar_item_get_days_start_week_sel (ECalendarItem *calitem)
2901 {
2902 	return calitem->days_to_start_week_selection;
2903 }
2904 
2905 /* Set the maximum number of days before whole weeks are selected */
2906 void
2907 e_calendar_item_set_days_start_week_sel (ECalendarItem *calitem,
2908                                          gint days)
2909 {
2910 	calitem->days_to_start_week_selection = days;
2911 }
2912 
2913 gboolean
2914 e_calendar_item_get_display_popup (ECalendarItem *calitem)
2915 {
2916 	return calitem->display_popup;
2917 }
2918 
2919 void
2920 e_calendar_item_set_display_popup (ECalendarItem *calitem,
2921                                    gboolean display)
2922 {
2923 	calitem->display_popup = display;
2924 }
2925 
2926 /* This will make sure that the given year & month are valid, i.e. if month
2927  * is < 0 or > 11 the year and month will be updated accordingly. */
2928 void
2929 e_calendar_item_normalize_date (ECalendarItem *calitem,
2930                                 gint *year,
2931                                 gint *month)
2932 {
2933 	if (*month >= 0) {
2934 		*year += *month / 12;
2935 		*month = *month % 12;
2936 	} else {
2937 		*year += *month / 12 - 1;
2938 		*month = *month % 12;
2939 		if (*month != 0)
2940 			*month += 12;
2941 	}
2942 }
2943 
2944 /* Adds or subtracts days from the selection. It is used when we switch months
2945  * and the selection extends past the end of a month but we want to keep the
2946  * number of days selected the same. days should not be more than 30. */
2947 static void
2948 e_calendar_item_add_days_to_selection (ECalendarItem *calitem,
2949                                        gint days)
2950 {
2951 	gint year, month, days_in_month;
2952 
2953 	year = calitem->year;
2954 	month = calitem->month + calitem->selection_end_month_offset;
2955 	e_calendar_item_normalize_date (calitem, &year,	&month);
2956 
2957 	calitem->selection_end_day += days;
2958 	if (calitem->selection_end_day <= 0) {
2959 		month--;
2960 		e_calendar_item_normalize_date (calitem, &year,	&month);
2961 		calitem->selection_end_month_offset--;
2962 		calitem->selection_end_day += DAYS_IN_MONTH (year, month);
2963 	} else {
2964 		days_in_month = DAYS_IN_MONTH (year, month);
2965 		if (calitem->selection_end_day > days_in_month) {
2966 			calitem->selection_end_month_offset++;
2967 			calitem->selection_end_day -= days_in_month;
2968 		}
2969 	}
2970 }
2971 
2972 /* Gets the range of dates actually shown. Months are 0 to 11.
2973  * This also includes the last days of the previous month and the first days
2974  * of the following month, which are normally shown in gray.
2975  * It returns FALSE if no dates are currently shown. */
2976 gboolean
2977 e_calendar_item_get_date_range (ECalendarItem *calitem,
2978                                 gint *start_year,
2979                                 gint *start_month,
2980                                 gint *start_day,
2981                                 gint *end_year,
2982                                 gint *end_month,
2983                                 gint *end_day)
2984 {
2985 	gint first_day_offset, days_in_month, days_in_prev_month;
2986 
2987 	if (calitem->rows == 0 || calitem->cols == 0)
2988 		return FALSE;
2989 
2990 	/* Calculate the first day shown. This will be one of the greyed-out
2991 	 * days before the first full month begins. */
2992 	e_calendar_item_get_month_info (
2993 		calitem, 0, 0, &first_day_offset,
2994 		&days_in_month, &days_in_prev_month);
2995 	*start_year = calitem->year;
2996 	*start_month = calitem->month - 1;
2997 	if (*start_month == -1) {
2998 		(*start_year)--;
2999 		*start_month = 11;
3000 	}
3001 	*start_day = days_in_prev_month + 1 - first_day_offset;
3002 
3003 	/* Calculate the last day shown. This will be one of the greyed-out
3004 	 * days after the last full month ends. */
3005 	e_calendar_item_get_month_info (
3006 		calitem, calitem->rows - 1,
3007 		calitem->cols - 1, &first_day_offset,
3008 		&days_in_month, &days_in_prev_month);
3009 	*end_month = calitem->month + calitem->rows * calitem->cols;
3010 	*end_year = calitem->year + *end_month / 12;
3011 	*end_month %= 12;
3012 	*end_day = E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH
3013 		- first_day_offset - days_in_month;
3014 
3015 	return TRUE;
3016 }
3017 
3018 /* Simple way to mark days so they appear bold.
3019  * A more flexible interface may be added later. */
3020 void
3021 e_calendar_item_clear_marks (ECalendarItem *calitem)
3022 {
3023 	GnomeCanvasItem *item;
3024 
3025 	item = GNOME_CANVAS_ITEM (calitem);
3026 
3027 	g_free (calitem->styles);
3028 	calitem->styles = NULL;
3029 
3030 	gnome_canvas_request_redraw (
3031 		item->canvas, item->x1, item->y1,
3032 		item->x2, item->y2);
3033 }
3034 
3035 /* add_day_style - whether bit-or with the actual style or change the style fully */
3036 void
3037 e_calendar_item_mark_day (ECalendarItem *calitem,
3038                           gint year,
3039                           gint month,
3040                           gint day,
3041                           guint8 day_style,
3042                           gboolean add_day_style)
3043 {
3044 	gint month_offset;
3045 	gint index;
3046 
3047 	month_offset = (year - calitem->year) * 12 + month - calitem->month;
3048 	if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
3049 		return;
3050 
3051 	if (!calitem->styles)
3052 		calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);
3053 
3054 	index = (month_offset + 1) * 32 + day;
3055 	calitem->styles[index] = day_style |
3056 		(add_day_style ? calitem->styles[index] : 0);
3057 
3058 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3059 }
3060 
3061 void
3062 e_calendar_item_mark_days (ECalendarItem *calitem,
3063                            gint start_year,
3064                            gint start_month,
3065                            gint start_day,
3066                            gint end_year,
3067                            gint end_month,
3068                            gint end_day,
3069                            guint8 day_style,
3070                            gboolean add_day_style)
3071 {
3072 	gint month_offset, end_month_offset, day;
3073 
3074 	month_offset = (start_year - calitem->year) * 12 + start_month
3075 		- calitem->month;
3076 	day = start_day;
3077 	if (month_offset > calitem->rows * calitem->cols)
3078 		return;
3079 	if (month_offset < -1) {
3080 		month_offset = -1;
3081 		day = 1;
3082 	}
3083 
3084 	end_month_offset = (end_year - calitem->year) * 12 + end_month
3085 		- calitem->month;
3086 	if (end_month_offset < -1)
3087 		return;
3088 	if (end_month_offset > calitem->rows * calitem->cols) {
3089 		end_month_offset = calitem->rows * calitem->cols;
3090 		end_day = 31;
3091 	}
3092 
3093 	if (month_offset > end_month_offset)
3094 		return;
3095 
3096 	if (!calitem->styles)
3097 		calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);
3098 
3099 	for (;;) {
3100 		gint index;
3101 
3102 		if (month_offset == end_month_offset && day > end_day)
3103 			break;
3104 
3105 		if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
3106 			g_warning ("Bad month offset: %i\n", month_offset);
3107 		if (day < 1 || day > 31)
3108 			g_warning ("Bad day: %i\n", day);
3109 
3110 #if 0
3111 		g_print ("Marking Month:%i Day:%i\n", month_offset, day);
3112 #endif
3113 		index = (month_offset + 1) * 32 + day;
3114 		calitem->styles[index] = day_style |
3115 			(add_day_style ? calitem->styles[index] : 0);
3116 
3117 		day++;
3118 		if (day == 32) {
3119 			month_offset++;
3120 			day = 1;
3121 			if (month_offset > end_month_offset)
3122 				break;
3123 		}
3124 	}
3125 
3126 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3127 }
3128 
3129 /* Rounds up the given day to the end of the week. */
3130 static void
3131 e_calendar_item_round_up_selection (ECalendarItem *calitem,
3132                                     gint *month_offset,
3133                                     gint *day)
3134 {
3135 	gint year, month, weekday, days, days_in_month;
3136 	struct tm tmp_tm = { 0 };
3137 
3138 	year = calitem->year;
3139 	month = calitem->month + *month_offset;
3140 	e_calendar_item_normalize_date (calitem, &year, &month);
3141 
3142 	tmp_tm.tm_year = year - 1900;
3143 	tmp_tm.tm_mon = month;
3144 	tmp_tm.tm_mday = *day;
3145 	tmp_tm.tm_isdst = -1;
3146 	mktime (&tmp_tm);
3147 
3148 	/* Convert to 0 (Monday) to 6 (Sunday). */
3149 	weekday = (tmp_tm.tm_wday + 6) % 7;
3150 
3151 	/* Calculate how many days to the end of the row. */
3152 	days = (calitem->week_start_day + 6 - weekday) % 7;
3153 
3154 	*day += days;
3155 	days_in_month = DAYS_IN_MONTH (year, month);
3156 	if (*day > days_in_month) {
3157 		(*month_offset)++;
3158 		*day -= days_in_month;
3159 	}
3160 }
3161 
3162 /* Rounds down the given day to the start of the week. */
3163 static void
3164 e_calendar_item_round_down_selection (ECalendarItem *calitem,
3165                                       gint *month_offset,
3166                                       gint *day)
3167 {
3168 	gint year, month, weekday, days, days_in_month;
3169 	struct tm tmp_tm = { 0 };
3170 
3171 	year = calitem->year;
3172 	month = calitem->month + *month_offset;
3173 	e_calendar_item_normalize_date (calitem, &year, &month);
3174 
3175 	tmp_tm.tm_year = year - 1900;
3176 	tmp_tm.tm_mon = month;
3177 	tmp_tm.tm_mday = *day;
3178 	tmp_tm.tm_isdst = -1;
3179 	mktime (&tmp_tm);
3180 
3181 	/* Convert to 0 (Monday) to 6 (Sunday). */
3182 	weekday = (tmp_tm.tm_wday + 6) % 7;
3183 
3184 	/* Calculate how many days to the start of the row. */
3185 	days = (weekday + 7 - calitem->week_start_day) % 7;
3186 
3187 	*day -= days;
3188 	if (*day <= 0) {
3189 		month--;
3190 		if (month == -1) {
3191 			year--;
3192 			month = 11;
3193 		}
3194 		days_in_month = DAYS_IN_MONTH (year, month);
3195 		(*month_offset)--;
3196 		*day += days_in_month;
3197 	}
3198 }
3199 
3200 static gint
3201 e_calendar_item_get_inclusive_days (ECalendarItem *calitem,
3202                                     gint start_month_offset,
3203                                     gint start_day,
3204                                     gint end_month_offset,
3205                                     gint end_day)
3206 {
3207 	gint start_year, start_month, end_year, end_month, days = 0;
3208 
3209 	start_year = calitem->year;
3210 	start_month = calitem->month + start_month_offset;
3211 	e_calendar_item_normalize_date (calitem, &start_year, &start_month);
3212 
3213 	end_year = calitem->year;
3214 	end_month = calitem->month + end_month_offset;
3215 	e_calendar_item_normalize_date (calitem, &end_year, &end_month);
3216 
3217 	while (start_year < end_year || start_month < end_month) {
3218 		days += DAYS_IN_MONTH (start_year, start_month);
3219 		start_month++;
3220 		if (start_month == 12) {
3221 			start_year++;
3222 			start_month = 0;
3223 		}
3224 	}
3225 
3226 	days += end_day - start_day + 1;
3227 
3228 	return days;
3229 }
3230 
3231 /* If the day is off the end of the month it is set to the last day of the
3232  * month. */
3233 static void
3234 e_calendar_item_ensure_valid_day (ECalendarItem *calitem,
3235                                   gint *month_offset,
3236                                   gint *day)
3237 {
3238 	gint year, month, days_in_month;
3239 
3240 	year = calitem->year;
3241 	month = calitem->month + *month_offset;
3242 	e_calendar_item_normalize_date (calitem, &year, &month);
3243 
3244 	days_in_month = DAYS_IN_MONTH (year, month);
3245 	if (*day > days_in_month)
3246 		*day = days_in_month;
3247 }
3248 
3249 gboolean
3250 e_calendar_item_get_selection (ECalendarItem *calitem,
3251                                GDate *start_date,
3252                                GDate *end_date)
3253 {
3254 	gint start_year, start_month, start_day;
3255 	gint end_year, end_month, end_day;
3256 
3257 	g_date_clear (start_date, 1);
3258 	g_date_clear (end_date, 1);
3259 
3260 	if (!calitem->selection_set)
3261 		return FALSE;
3262 
3263 	start_year = calitem->year;
3264 	start_month = calitem->month + calitem->selection_start_month_offset;
3265 	e_calendar_item_normalize_date (calitem, &start_year, &start_month);
3266 	start_day = calitem->selection_start_day;
3267 
3268 	end_year = calitem->year;
3269 	end_month = calitem->month + calitem->selection_end_month_offset;
3270 	e_calendar_item_normalize_date (calitem, &end_year, &end_month);
3271 	end_day = calitem->selection_end_day;
3272 
3273 	g_date_set_dmy (start_date, start_day, start_month + 1, start_year);
3274 	g_date_set_dmy (end_date, end_day, end_month + 1, end_year);
3275 
3276 	return TRUE;
3277 }
3278 
3279 static void
3280 e_calendar_item_set_selection_if_emission (ECalendarItem *calitem,
3281                                            const GDate *start_date,
3282                                            const GDate *end_date,
3283                                            gboolean emission)
3284 {
3285 	gint start_year, start_month, start_day;
3286 	gint end_year, end_month, end_day;
3287 	gint new_start_month_offset, new_start_day;
3288 	gint new_end_month_offset, new_end_day;
3289 	gboolean need_update;
3290 
3291 	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
3292 
3293 	/* If start_date is NULL, we clear the selection without changing the
3294 	 * month shown. */
3295 	if (start_date == NULL) {
3296 		calitem->selection_set = FALSE;
3297 		calitem->selection_changed = TRUE;
3298 		e_calendar_item_queue_signal_emission (calitem);
3299 		gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3300 		return;
3301 	}
3302 
3303 	if (end_date == NULL)
3304 		end_date = start_date;
3305 
3306 	g_return_if_fail (g_date_compare (start_date, end_date) <= 0);
3307 
3308 	start_year = g_date_get_year (start_date);
3309 	start_month = g_date_get_month (start_date) - 1;
3310 	start_day = g_date_get_day (start_date);
3311 	end_year = g_date_get_year (end_date);
3312 	end_month = g_date_get_month (end_date) - 1;
3313 	end_day = g_date_get_day (end_date);
3314 
3315 	need_update = e_calendar_item_ensure_days_visible (
3316 		calitem,
3317 		start_year,
3318 		start_month,
3319 		start_day,
3320 		end_year,
3321 		end_month,
3322 		end_day,
3323 		emission);
3324 
3325 	new_start_month_offset = (start_year - calitem->year) * 12
3326 		+ start_month - calitem->month;
3327 	new_start_day = start_day;
3328 
3329 	/* This may go outside the visible months, but we don't care. */
3330 	new_end_month_offset = (end_year - calitem->year) * 12
3331 		+ end_month - calitem->month;
3332 	new_end_day = end_day;
3333 
3334 	if (!calitem->selection_set
3335 	    || calitem->selection_start_month_offset != new_start_month_offset
3336 	    || calitem->selection_start_day != new_start_day
3337 	    || calitem->selection_end_month_offset != new_end_month_offset
3338 	    || calitem->selection_end_day != new_end_day) {
3339 		need_update = TRUE;
3340 		if (emission) {
3341 			calitem->selection_changed = TRUE;
3342 			e_calendar_item_queue_signal_emission (calitem);
3343 		}
3344 		calitem->selection_set = TRUE;
3345 		calitem->selection_start_month_offset = new_start_month_offset;
3346 		calitem->selection_start_day = new_start_day;
3347 		calitem->selection_end_month_offset = new_end_month_offset;
3348 		calitem->selection_end_day = new_end_day;
3349 
3350 		calitem->selection_real_start_month_offset = new_start_month_offset;
3351 		calitem->selection_real_start_day = new_start_day;
3352 		calitem->selection_from_full_week = FALSE;
3353 	}
3354 
3355 	if (need_update) {
3356 		g_signal_emit (
3357 			calitem,
3358 			e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
3359 		gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3360 	}
3361 }
3362 
3363 void
3364 e_calendar_item_style_set (GtkWidget *widget,
3365                            ECalendarItem *calitem)
3366 {
3367 	GtkStyle *style;
3368 	GdkColor *color;
3369 
3370 	style = gtk_widget_get_style (widget);
3371 
3372 	color = &style->bg[GTK_STATE_SELECTED];
3373 	calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX] = *color;
3374 
3375 	color = &style->base[GTK_STATE_NORMAL];
3376 	calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_FG] = *color;
3377 
3378 	color = &style->bg[GTK_STATE_SELECTED];
3379 	calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG_FOCUSED] = *color;
3380 
3381 	color = &style->fg[GTK_STATE_INSENSITIVE];
3382 	calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG] = *color;
3383 
3384 	color = &style->fg[GTK_STATE_INSENSITIVE];
3385 	calitem->colors[E_CALENDAR_ITEM_COLOR_PREV_OR_NEXT_MONTH_FG] = *color;
3386 
3387 	e_calendar_item_recalc_sizes (calitem);
3388 }
3389 
3390 void
3391 e_calendar_item_set_selection (ECalendarItem *calitem,
3392                                const GDate *start_date,
3393                                const GDate *end_date)
3394 {
3395 	/* If the user is in the middle of a selection, we must abort it. */
3396 	if (calitem->selecting) {
3397 		gnome_canvas_item_ungrab (
3398 			GNOME_CANVAS_ITEM (calitem),
3399 			GDK_CURRENT_TIME);
3400 		calitem->selecting = FALSE;
3401 	}
3402 
3403 	e_calendar_item_set_selection_if_emission (calitem,
3404 						   start_date, end_date,
3405 						   TRUE);
3406 }
3407 
3408 /* This tries to ensure that the given time range is visible. If the range
3409  * given is longer than we can show, only the start of it will be visible.
3410  * Note that this will not update the selection. That should be done somewhere
3411  * else. It returns TRUE if the visible range has been changed. */
3412 static gboolean
3413 e_calendar_item_ensure_days_visible (ECalendarItem *calitem,
3414                                      gint start_year,
3415                                      gint start_month,
3416                                      gint start_day,
3417                                      gint end_year,
3418                                      gint end_month,
3419                                      gint end_day,
3420                                      gboolean emission)
3421 {
3422 	gint current_end_year, current_end_month;
3423 	gint months_shown;
3424 	gint first_day_offset, days_in_month, days_in_prev_month;
3425 	gboolean need_update = FALSE;
3426 
3427 	months_shown = calitem->rows * calitem->cols;
3428 
3429 	/* Calculate the range of months currently displayed. */
3430 	current_end_year = calitem->year;
3431 	current_end_month = calitem->month + months_shown - 1;
3432 	e_calendar_item_normalize_date (
3433 		calitem, &current_end_year,
3434 		&current_end_month);
3435 
3436 	/* Try to ensure that the end month is shown. */
3437 	if ((end_year == current_end_year + 1 &&
3438 		current_end_month == 11 && end_month == 0) ||
3439 	    (end_year == current_end_year && end_month == current_end_month + 1)) {
3440 		/* See if the end of the selection will fit in the
3441 		 * leftover days of the month after the last one shown. */
3442 		calitem->month += (months_shown - 1);
3443 		e_calendar_item_normalize_date (
3444 			calitem, &calitem->year,
3445 			&calitem->month);
3446 
3447 		e_calendar_item_get_month_info (
3448 			calitem, 0, 0,
3449 			&first_day_offset,
3450 			&days_in_month,
3451 			&days_in_prev_month);
3452 
3453 		if (end_day >= E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH -
3454 		    first_day_offset - days_in_month) {
3455 			need_update = TRUE;
3456 
3457 			calitem->year = end_year;
3458 			calitem->month = end_month - months_shown + 1;
3459 		} else {
3460 			calitem->month -= (months_shown - 1);
3461 		}
3462 
3463 		e_calendar_item_normalize_date (
3464 			calitem, &calitem->year,
3465 			&calitem->month);
3466 	}
3467 	else if (end_year > current_end_year ||
3468 		 (end_year == current_end_year && end_month > current_end_month)) {
3469 		/* The selection will definitely not fit in the leftover days
3470 		 * of the month after the last one shown. */
3471 		need_update = TRUE;
3472 
3473 		calitem->year = end_year;
3474 		calitem->month = end_month - months_shown + 1;
3475 
3476 		e_calendar_item_normalize_date (
3477 			calitem, &calitem->year,
3478 			&calitem->month);
3479 	}
3480 
3481 	/* Now try to ensure that the start month is shown. We do this after
3482 	 * the end month so that the start month will always be shown. */
3483 	if (start_year < calitem->year
3484 	    || (start_year == calitem->year
3485 		&& start_month < calitem->month)) {
3486 		need_update = TRUE;
3487 
3488 		/* First we see if the start of the selection will fit in the
3489 		 * leftover days of the month before the first one shown. */
3490 		calitem->year = start_year;
3491 		calitem->month = start_month + 1;
3492 		e_calendar_item_normalize_date (
3493 			calitem, &calitem->year,
3494 			&calitem->month);
3495 
3496 		e_calendar_item_get_month_info (
3497 			calitem, 0, 0,
3498 			&first_day_offset,
3499 			&days_in_month,
3500 			&days_in_prev_month);
3501 
3502 		if (start_day <= days_in_prev_month - first_day_offset) {
3503 			calitem->year = start_year;
3504 			calitem->month = start_month;
3505 		}
3506 	}
3507 
3508 	if (need_update && emission)
3509 		e_calendar_item_date_range_changed (calitem);
3510 
3511 	return need_update;
3512 }
3513 
3514 static gboolean
3515 destroy_menu_idle_cb (gpointer menu)
3516 {
3517 	gtk_widget_destroy (menu);
3518 
3519 	return FALSE;
3520 }
3521 
3522 static void
3523 deactivate_menu_cb (GtkWidget *menu)
3524 {
3525 	g_signal_handlers_disconnect_by_func (menu, deactivate_menu_cb, NULL);
3526 
3527 	g_idle_add (destroy_menu_idle_cb, menu);
3528 }
3529 
3530 static void
3531 e_calendar_item_show_popup_menu (ECalendarItem *calitem,
3532                                  GdkEventButton *event,
3533                                  gint month_offset)
3534 {
3535 	GtkWidget *menu, *submenu, *menuitem, *label;
3536 	gint year, month;
3537 	const gchar *name;
3538 	gchar buffer[64];
3539 
3540 	menu = gtk_menu_new ();
3541 
3542 	for (year = calitem->year - 2; year <= calitem->year + 2; year++) {
3543 		g_snprintf (buffer, 64, "%i", year);
3544 		menuitem = gtk_menu_item_new_with_label (buffer);
3545 		gtk_widget_show (menuitem);
3546 		gtk_container_add (GTK_CONTAINER (menu), menuitem);
3547 
3548 		submenu = gtk_menu_new ();
3549 		gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
3550 
3551 		g_object_set_data (
3552 			G_OBJECT (submenu), "year",
3553 			GINT_TO_POINTER (year));
3554 		g_object_set_data (
3555 			G_OBJECT (submenu), "month_offset",
3556 			GINT_TO_POINTER (month_offset));
3557 
3558 		for (month = 0; month < 12; month++) {
3559 			name = e_get_month_name (month + 1, FALSE);
3560 
3561 			menuitem = gtk_menu_item_new ();
3562 			gtk_widget_show (menuitem);
3563 			gtk_container_add (GTK_CONTAINER (submenu), menuitem);
3564 
3565 			label = gtk_label_new (name);
3566 			gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
3567 			gtk_widget_show (label);
3568 			gtk_container_add (GTK_CONTAINER (menuitem), label);
3569 
3570 			g_object_set_data (
3571 				G_OBJECT (menuitem), "month",
3572 				GINT_TO_POINTER (month));
3573 
3574 			g_signal_connect (
3575 				menuitem, "activate",
3576 				G_CALLBACK (e_calendar_item_on_menu_item_activate),
3577 				calitem);
3578 		}
3579 	}
3580 
3581 	g_signal_connect (
3582 		menu, "deactivate",
3583 		G_CALLBACK (deactivate_menu_cb), NULL);
3584 
3585 	gtk_menu_popup (
3586 		GTK_MENU (menu), NULL, NULL,
3587 		e_calendar_item_position_menu, calitem,
3588 		event->button, event->time);
3589 }
3590 
3591 static void
3592 e_calendar_item_on_menu_item_activate (GtkWidget *menuitem,
3593                                        ECalendarItem *calitem)
3594 {
3595 	GtkWidget *parent;
3596 	gint year, month_offset, month;
3597 	gpointer data;
3598 
3599 	parent = gtk_widget_get_parent (menuitem);
3600 	data = g_object_get_data (G_OBJECT (parent), "year");
3601 	year = GPOINTER_TO_INT (data);
3602 
3603 	parent = gtk_widget_get_parent (menuitem);
3604 	data = g_object_get_data (G_OBJECT (parent), "month_offset");
3605 	month_offset = GPOINTER_TO_INT (data);
3606 
3607 	data = g_object_get_data (G_OBJECT (menuitem), "month");
3608 	month = GPOINTER_TO_INT (data);
3609 
3610 	month -= month_offset;
3611 	e_calendar_item_normalize_date (calitem, &year, &month);
3612 	e_calendar_item_set_first_month (calitem, year, month);
3613 }
3614 
3615 static void
3616 e_calendar_item_position_menu (GtkMenu *menu,
3617                                gint *x,
3618                                gint *y,
3619                                gboolean *push_in,
3620                                gpointer user_data)
3621 {
3622 	GtkRequisition requisition;
3623 	gint screen_width, screen_height;
3624 
3625 	gtk_widget_get_child_requisition (GTK_WIDGET (menu), &requisition);
3626 
3627 	*x -= (gtk_widget_get_direction(GTK_WIDGET(menu)) == GTK_TEXT_DIR_RTL)
3628 		? requisition.width - 2
3629 		: 2;
3630 	*y -= requisition.height / 2;
3631 
3632 	screen_width = gdk_screen_width ();
3633 	screen_height = gdk_screen_height ();
3634 
3635 	*x = CLAMP (*x, 0, screen_width - requisition.width);
3636 	*y = CLAMP (*y, 0, screen_height - requisition.height);
3637 }
3638 
3639 /* Sets the function to call to get the colors to use for a particular day. */
3640 void
3641 e_calendar_item_set_style_callback (ECalendarItem *calitem,
3642                                     ECalendarItemStyleCallback cb,
3643                                     gpointer data,
3644                                     GDestroyNotify destroy)
3645 {
3646 	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
3647 
3648 	if (calitem->style_callback_data && calitem->style_callback_destroy)
3649 		(*calitem->style_callback_destroy) (calitem->style_callback_data);
3650 
3651 	calitem->style_callback = cb;
3652 	calitem->style_callback_data = data;
3653 	calitem->style_callback_destroy = destroy;
3654 }
3655 
3656 static void
3657 e_calendar_item_date_range_changed (ECalendarItem *calitem)
3658 {
3659 	g_free (calitem->styles);
3660 	calitem->styles = NULL;
3661 	calitem->date_range_changed = TRUE;
3662 	e_calendar_item_queue_signal_emission (calitem);
3663 }
3664 
3665 static void
3666 e_calendar_item_queue_signal_emission (ECalendarItem *calitem)
3667 {
3668 	if (calitem->signal_emission_idle_id == 0) {
3669 		calitem->signal_emission_idle_id = g_idle_add_full (
3670 			G_PRIORITY_HIGH, (GSourceFunc)
3671 			e_calendar_item_signal_emission_idle_cb,
3672 			calitem, NULL);
3673 	}
3674 }
3675 
3676 static gboolean
3677 e_calendar_item_signal_emission_idle_cb (gpointer data)
3678 {
3679 	ECalendarItem *calitem;
3680 
3681 	g_return_val_if_fail (E_IS_CALENDAR_ITEM (data), FALSE);
3682 
3683 	calitem = E_CALENDAR_ITEM (data);
3684 
3685 	calitem->signal_emission_idle_id = 0;
3686 
3687 	/* We ref the calitem & check in case it gets destroyed, since we
3688 	 * were getting a free memory write here. */
3689 	g_object_ref ((calitem));
3690 
3691 	if (calitem->date_range_changed) {
3692 		calitem->date_range_changed = FALSE;
3693 		g_signal_emit (calitem, e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
3694 	}
3695 
3696 	if (calitem->selection_changed) {
3697 		calitem->selection_changed = FALSE;
3698 		g_signal_emit (calitem, e_calendar_item_signals[SELECTION_CHANGED], 0);
3699 	}
3700 
3701 	g_object_unref ((calitem));
3702 
3703 	return FALSE;
3704 }
3705 
3706 /* Sets a callback to use to get the current time. This is useful if the
3707  * application needs to use its own timezone data rather than rely on the
3708  * Unix timezone. */
3709 void
3710 e_calendar_item_set_get_time_callback (ECalendarItem *calitem,
3711                                        ECalendarItemGetTimeCallback cb,
3712                                        gpointer data,
3713                                        GDestroyNotify destroy)
3714 {
3715 	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
3716 
3717 	if (calitem->time_callback_data && calitem->time_callback_destroy)
3718 		(*calitem->time_callback_destroy) (calitem->time_callback_data);
3719 
3720 	calitem->time_callback = cb;
3721 	calitem->time_callback_data = data;
3722 	calitem->time_callback_destroy = destroy;
3723 }