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

No issues found

  1 /*
  2  * This program is free software; you can redistribute it and/or
  3  * modify it under the terms of the GNU Lesser General Public
  4  * License as published by the Free Software Foundation; either
  5  * version 2 of the License, or (at your option) version 3.
  6  *
  7  * This program is distributed in the hope that it will be useful,
  8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 10  * Lesser General Public License for more details.
 11  *
 12  * You should have received a copy of the GNU Lesser General Public
 13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 14  *
 15  *
 16  * Authors:
 17  *		Damon Chaplin <damon@ximian.com>
 18  *		Bolian Yin <bolian.yin@sun.com>
 19  *
 20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 21  *
 22  */
 23 
 24 /*
 25  * ECalendar - displays a table of monthly calendars, allowing highlighting
 26  * and selection of one or more days. Like GtkCalendar with more features.
 27  * Most of the functionality is in the ECalendarItem canvas item, though
 28  * we also add GnomeCanvasWidget buttons to go to the previous/next month and
 29  * to got to the current day.
 30  */
 31 
 32 #ifdef HAVE_CONFIG_H
 33 #include <config.h>
 34 #endif
 35 
 36 #include "e-calendar.h"
 37 
 38 #include <gtk/gtk.h>
 39 #include <libgnomecanvas/gnome-canvas-widget.h>
 40 #include <glib/gi18n.h>
 41 
 42 #define E_CALENDAR_SMALL_FONT_PTSIZE 6
 43 
 44 #define E_CALENDAR_SMALL_FONT	\
 45 	"-adobe-utopia-regular-r-normal-*-*-100-*-*-p-*-iso8859-*"
 46 #define E_CALENDAR_SMALL_FONT_FALLBACK	\
 47 	"-adobe-helvetica-medium-r-normal-*-*-80-*-*-p-*-iso8859-*"
 48 
 49 /* The space between the arrow buttons and the edge of the widget. */
 50 #define E_CALENDAR_ARROW_BUTTON_X_PAD	2
 51 #define E_CALENDAR_ARROW_BUTTON_Y_PAD	0
 52 
 53 /* Vertical padding. The padding above the button includes the space for the
 54  * horizontal line. */
 55 #define	E_CALENDAR_YPAD_ABOVE_LOWER_BUTTONS		4
 56 #define	E_CALENDAR_YPAD_BELOW_LOWER_BUTTONS		3
 57 
 58 /* Horizontal padding inside & between buttons. */
 59 #define E_CALENDAR_IXPAD_BUTTONS			4
 60 #define E_CALENDAR_XPAD_BUTTONS				8
 61 
 62 /* The time between steps when the prev/next buttons is pressed, in 1/1000ths
 63  * of a second, and the number of timeouts we skip before we start
 64  * automatically moving back/forward. */
 65 #define E_CALENDAR_AUTO_MOVE_TIMEOUT		150
 66 #define E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY	2
 67 
 68 static void e_calendar_dispose		(GObject	*object);
 69 static void e_calendar_realize		(GtkWidget	*widget);
 70 static void e_calendar_style_set	(GtkWidget	*widget,
 71 					 GtkStyle	*previous_style);
 72 static void e_calendar_get_preferred_width (GtkWidget *widget,
 73 					    gint      *minimal_width,
 74 					    gint      *natural_width);
 75 static void e_calendar_get_preferred_height (GtkWidget *widget,
 76 					     gint      *minimal_height,
 77 					     gint      *natural_height);
 78 static void e_calendar_size_allocate	(GtkWidget	*widget,
 79 					 GtkAllocation	*allocation);
 80 static gint e_calendar_drag_motion	(GtkWidget      *widget,
 81 					 GdkDragContext *context,
 82 					 gint            x,
 83 					 gint            y,
 84 					 guint           time);
 85 static void e_calendar_drag_leave	(GtkWidget      *widget,
 86 					 GdkDragContext *context,
 87 					 guint           time);
 88 static gboolean e_calendar_button_has_focus (ECalendar	*cal);
 89 static gboolean e_calendar_focus (GtkWidget *widget,
 90 				  GtkDirectionType direction);
 91 
 92 static void e_calendar_on_prev_pressed	(ECalendar	*cal);
 93 static void e_calendar_on_prev_released	(ECalendar	*cal);
 94 static void e_calendar_on_prev_clicked  (ECalendar      *cal);
 95 static void e_calendar_on_next_pressed	(ECalendar	*cal);
 96 static void e_calendar_on_next_released	(ECalendar	*cal);
 97 static void e_calendar_on_next_clicked  (ECalendar      *cal);
 98 static void e_calendar_on_prev_year_pressed  (ECalendar *cal);
 99 static void e_calendar_on_prev_year_released (ECalendar *cal);
100 static void e_calendar_on_prev_year_clicked  (ECalendar *cal);
101 static void e_calendar_on_next_year_pressed  (ECalendar *cal);
102 static void e_calendar_on_next_year_released (ECalendar *cal);
103 static void e_calendar_on_next_year_clicked  (ECalendar *cal);
104 
105 static void	e_calendar_start_auto_move	(ECalendar *cal,
106 						 gboolean moving_forward);
107 static gboolean e_calendar_auto_move_handler	(gpointer data);
108 static void e_calendar_start_auto_move_year	(ECalendar *cal,
109 						 gboolean moving_forward);
110 static gboolean e_calendar_auto_move_year_handler (gpointer data);
111 static void e_calendar_stop_auto_move		(ECalendar *cal);
112 
113 G_DEFINE_TYPE (
114 	ECalendar,
115 	e_calendar,
116 	E_TYPE_CANVAS)
117 
118 static void
119 e_calendar_class_init (ECalendarClass *class)
120 {
121 	GObjectClass   *object_class;
122 	GtkWidgetClass *widget_class;
123 
124 	object_class = (GObjectClass *) class;
125 	widget_class = (GtkWidgetClass *) class;
126 
127 	object_class->dispose = e_calendar_dispose;
128 
129 	widget_class->realize		   = e_calendar_realize;
130 	widget_class->style_set		   = e_calendar_style_set;
131 	widget_class->get_preferred_width  = e_calendar_get_preferred_width;
132 	widget_class->get_preferred_height = e_calendar_get_preferred_height;
133 	widget_class->size_allocate	   = e_calendar_size_allocate;
134 	widget_class->drag_motion	   = e_calendar_drag_motion;
135 	widget_class->drag_leave	   = e_calendar_drag_leave;
136 	widget_class->focus                = e_calendar_focus;
137 }
138 
139 static void
140 e_calendar_init (ECalendar *cal)
141 {
142 	GnomeCanvasGroup *canvas_group;
143 	PangoFontDescription *small_font_desc;
144 	GtkWidget *button, *pixmap;
145 	AtkObject *a11y;
146 
147 	/* Create the small font. */
148 
149 	small_font_desc = pango_font_description_copy (
150 		gtk_widget_get_style (GTK_WIDGET (cal))->font_desc);
151 	pango_font_description_set_size (
152 		small_font_desc,
153 		E_CALENDAR_SMALL_FONT_PTSIZE * PANGO_SCALE);
154 
155 	canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (cal)->root);
156 
157 	cal->calitem = E_CALENDAR_ITEM (
158 		gnome_canvas_item_new (
159 			canvas_group,
160 			e_calendar_item_get_type (),
161 			"week_number_font_desc", small_font_desc,
162 			NULL));
163 
164 	pango_font_description_free (small_font_desc);
165 
166 	/* Create the arrow buttons to move to the previous/next month. */
167 	button = gtk_button_new ();
168 	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
169 	gtk_widget_show (button);
170 	g_signal_connect_swapped (
171 		button, "pressed",
172 		G_CALLBACK (e_calendar_on_prev_pressed), cal);
173 	g_signal_connect_swapped (
174 		button, "released",
175 		G_CALLBACK (e_calendar_on_prev_released), cal);
176 	g_signal_connect_swapped (
177 		button, "clicked",
178 		G_CALLBACK (e_calendar_on_prev_clicked), cal);
179 
180 	pixmap = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
181 	gtk_widget_show (pixmap);
182 	gtk_container_add (GTK_CONTAINER (button), pixmap);
183 
184 	cal->prev_item = gnome_canvas_item_new (
185 		canvas_group,
186 		gnome_canvas_widget_get_type (),
187 		"widget", button,
188 		NULL);
189 	a11y = gtk_widget_get_accessible (button);
190 	atk_object_set_name (a11y, _("Previous month"));
191 
192 	button = gtk_button_new ();
193 	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
194 	gtk_widget_show (button);
195 	g_signal_connect_swapped (
196 		button, "pressed",
197 		G_CALLBACK (e_calendar_on_next_pressed), cal);
198 	g_signal_connect_swapped (
199 		button, "released",
200 		G_CALLBACK (e_calendar_on_next_released), cal);
201 	g_signal_connect_swapped (
202 		button, "clicked",
203 		G_CALLBACK (e_calendar_on_next_clicked), cal);
204 
205 	pixmap = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
206 	gtk_widget_show (pixmap);
207 	gtk_container_add (GTK_CONTAINER (button), pixmap);
208 
209 	cal->next_item = gnome_canvas_item_new (
210 		canvas_group,
211 		gnome_canvas_widget_get_type (),
212 		"widget", button,
213 		NULL);
214 	a11y = gtk_widget_get_accessible (button);
215 	atk_object_set_name (a11y, _("Next month"));
216 
217 	/* Create the arrow buttons to move to the previous/next year. */
218 	button = gtk_button_new ();
219 	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
220 	gtk_widget_show (button);
221 	g_signal_connect_swapped (
222 		button, "pressed",
223 		G_CALLBACK (e_calendar_on_prev_year_pressed), cal);
224 	g_signal_connect_swapped (
225 		button, "released",
226 		G_CALLBACK (e_calendar_on_prev_year_released), cal);
227 	g_signal_connect_swapped (
228 		button, "clicked",
229 		G_CALLBACK (e_calendar_on_prev_year_clicked), cal);
230 
231 	pixmap = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
232 	gtk_widget_show (pixmap);
233 	gtk_container_add (GTK_CONTAINER (button), pixmap);
234 
235 	cal->prev_item_year = gnome_canvas_item_new (
236 		canvas_group,
237 		gnome_canvas_widget_get_type (),
238 		"widget", button,
239 		NULL);
240 	a11y = gtk_widget_get_accessible (button);
241 	atk_object_set_name (a11y, _("Previous year"));
242 
243 	button = gtk_button_new ();
244 	gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
245 	gtk_widget_show (button);
246 	g_signal_connect_swapped (
247 		button, "pressed",
248 		G_CALLBACK (e_calendar_on_next_year_pressed), cal);
249 	g_signal_connect_swapped (
250 		button, "released",
251 		G_CALLBACK (e_calendar_on_next_year_released), cal);
252 	g_signal_connect_swapped (
253 		button, "clicked",
254 		G_CALLBACK (e_calendar_on_next_year_clicked), cal);
255 
256 	pixmap = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
257 	gtk_widget_show (pixmap);
258 	gtk_container_add (GTK_CONTAINER (button), pixmap);
259 
260 	cal->next_item_year = gnome_canvas_item_new (
261 		canvas_group,
262 		gnome_canvas_widget_get_type (),
263 		"widget", button,
264 		NULL);
265 	a11y = gtk_widget_get_accessible (button);
266 	atk_object_set_name (a11y, _("Next year"));
267 
268 	cal->min_rows = 1;
269 	cal->min_cols = 1;
270 	cal->max_rows = -1;
271 	cal->max_cols = -1;
272 
273 	cal->timeout_id = 0;
274 }
275 
276 /**
277  * e_calendar_new:
278  * @Returns: a new #ECalendar.
279  *
280  * Creates a new #ECalendar.
281  **/
282 GtkWidget *
283 e_calendar_new (void)
284 {
285 	GtkWidget *cal;
286 	AtkObject *a11y;
287 
288 	cal = g_object_new (e_calendar_get_type (), NULL);
289 	a11y = gtk_widget_get_accessible (cal);
290 	atk_object_set_name (a11y, _("Month Calendar"));
291 
292 	return cal;
293 }
294 
295 static void
296 e_calendar_dispose (GObject *object)
297 {
298 	ECalendar *cal;
299 
300 	g_return_if_fail (object != NULL);
301 	g_return_if_fail (E_IS_CALENDAR (object));
302 
303 	cal = E_CALENDAR (object);
304 
305 	if (cal->timeout_id != 0) {
306 		g_source_remove (cal->timeout_id);
307 		cal->timeout_id = 0;
308 	}
309 
310 	/* Chain up to parent's dispose() method. */
311 	G_OBJECT_CLASS (e_calendar_parent_class)->dispose (object);
312 }
313 
314 static void
315 e_calendar_realize (GtkWidget *widget)
316 {
317 	GtkStyle *style;
318 	GdkWindow *window;
319 
320 	(*GTK_WIDGET_CLASS (e_calendar_parent_class)->realize) (widget);
321 
322 	/* Set the background of the canvas window to the normal color,
323 	 * or the arrow buttons are not displayed properly. */
324 	style = gtk_widget_get_style (widget);
325 	window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
326 	gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
327 }
328 
329 static void
330 e_calendar_style_set (GtkWidget *widget,
331                       GtkStyle *previous_style)
332 {
333 	ECalendar *e_calendar;
334 
335 	e_calendar = E_CALENDAR (widget);
336 	if (GTK_WIDGET_CLASS (e_calendar_parent_class)->style_set)
337 		(*GTK_WIDGET_CLASS (e_calendar_parent_class)->style_set) (widget,
338 							       previous_style);
339 
340 	/* Set the background of the canvas window to the normal color,
341 	 * or the arrow buttons are not displayed properly. */
342 	if (gtk_widget_get_realized (widget)) {
343 		GtkStyle *style;
344 		GdkWindow *window;
345 
346 		style = gtk_widget_get_style (widget);
347 		window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
348 		gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
349 	}
350 	e_calendar_item_style_set (widget, e_calendar->calitem);
351 }
352 
353 static void
354 e_calendar_get_preferred_width (GtkWidget *widget,
355                                 gint *minimum,
356                                 gint *natural)
357 {
358 	ECalendar *cal;
359 	GtkStyle *style;
360 	gint col_width;
361 
362 	cal = E_CALENDAR (widget);
363 	style = gtk_widget_get_style (GTK_WIDGET (cal));
364 
365 	g_object_get ((cal->calitem), "column_width", &col_width, NULL);
366 
367 	*minimum = *natural = col_width * cal->min_cols + style->xthickness * 2;
368 }
369 
370 static void
371 e_calendar_get_preferred_height (GtkWidget *widget,
372                                  gint *minimum,
373                                  gint *natural)
374 {
375 	ECalendar *cal;
376 	GtkStyle *style;
377 	gint row_height;
378 
379 	cal = E_CALENDAR (widget);
380 	style = gtk_widget_get_style (GTK_WIDGET (cal));
381 
382 	g_object_get ((cal->calitem), "row_height", &row_height, NULL);
383 
384 	*minimum = *natural = row_height * cal->min_rows + style->ythickness * 2;
385 }
386 
387 static void
388 e_calendar_size_allocate (GtkWidget *widget,
389                           GtkAllocation *allocation)
390 {
391 	ECalendar *cal;
392 	GtkStyle *style;
393 	GtkAllocation old_allocation;
394 	PangoFontDescription *font_desc;
395 	PangoContext *pango_context;
396 	PangoFontMetrics *font_metrics;
397 	gdouble old_x2, old_y2, new_x2, new_y2;
398 	gdouble xthickness, ythickness, arrow_button_size, current_x, month_width;
399 	gboolean is_rtl;
400 
401 	cal = E_CALENDAR (widget);
402 	style = gtk_widget_get_style (widget);
403 	xthickness = style->xthickness;
404 	ythickness = style->ythickness;
405 
406 	(*GTK_WIDGET_CLASS (e_calendar_parent_class)->size_allocate) (widget, allocation);
407 
408 	/* Set up Pango prerequisites */
409 	font_desc = gtk_widget_get_style (widget)->font_desc;
410 	pango_context = gtk_widget_get_pango_context (widget);
411 	font_metrics = pango_context_get_metrics (
412 		pango_context, font_desc,
413 		pango_context_get_language (pango_context));
414 
415 	/* Set the scroll region to its allocated size, if changed. */
416 	gnome_canvas_get_scroll_region (
417 		GNOME_CANVAS (cal),
418 		NULL, NULL, &old_x2, &old_y2);
419 	gtk_widget_get_allocation (widget, &old_allocation);
420 	new_x2 = old_allocation.width - 1;
421 	new_y2 = old_allocation.height - 1;
422 	if (old_x2 != new_x2 || old_y2 != new_y2)
423 		gnome_canvas_set_scroll_region (
424 			GNOME_CANVAS (cal),
425 			0, 0, new_x2, new_y2);
426 
427 	/* Take off space for line & buttons if shown. */
428 	gnome_canvas_item_set (
429 		GNOME_CANVAS_ITEM (cal->calitem),
430 		"x1", 0.0,
431 		"y1", 0.0,
432 		"x2", new_x2,
433 		"y2", new_y2,
434 		NULL);
435 
436 	if (cal->calitem->month_width > 0)
437 		month_width = cal->calitem->month_width;
438 	else
439 		month_width = new_x2;
440 	month_width -= E_CALENDAR_ITEM_MIN_CELL_XPAD + E_CALENDAR_ARROW_BUTTON_X_PAD;
441 
442 	/* Position the arrow buttons. */
443 	arrow_button_size =
444 		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
445 		+ PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
446 		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
447 		+ E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
448 		- E_CALENDAR_ARROW_BUTTON_Y_PAD * 2 - 2;
449 
450 	is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
451 	current_x = is_rtl ?
452 		(month_width - 2 * xthickness - E_CALENDAR_ARROW_BUTTON_X_PAD - arrow_button_size) :
453 		(xthickness);
454 
455 	gnome_canvas_item_set (
456 		cal->prev_item,
457 		"x", current_x,
458 		"y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
459 		"width", arrow_button_size,
460 		"height", arrow_button_size,
461 		NULL);
462 
463 	current_x += (is_rtl ? -1.0 : +1.0) * (cal->calitem->max_month_name_width - xthickness + 2 * arrow_button_size);
464 
465 	gnome_canvas_item_set (
466 		cal->next_item,
467 		"x", current_x,
468 		"y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
469 		"width", arrow_button_size,
470 		"height", arrow_button_size,
471 		NULL);
472 
473 	current_x = is_rtl ?
474 		(xthickness) :
475 		(month_width - 2 * xthickness - E_CALENDAR_ARROW_BUTTON_X_PAD - arrow_button_size);
476 
477 	gnome_canvas_item_set (
478 		cal->next_item_year,
479 		"x", current_x,
480 		"y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
481 		"width", arrow_button_size,
482 		"height", arrow_button_size,
483 		NULL);
484 
485 	current_x += (is_rtl ? +1.0 : -1.0) * (cal->calitem->max_digit_width * 5 - xthickness + 2 * arrow_button_size);
486 
487 	gnome_canvas_item_set (
488 		cal->prev_item_year,
489 		"x", current_x,
490 		"y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
491 		"width", arrow_button_size,
492 		"height", arrow_button_size,
493 		NULL);
494 
495 	pango_font_metrics_unref (font_metrics);
496 }
497 
498 void
499 e_calendar_set_minimum_size (ECalendar *cal,
500                              gint rows,
501                              gint cols)
502 {
503 	g_return_if_fail (E_IS_CALENDAR (cal));
504 
505 	cal->min_rows = rows;
506 	cal->min_cols = cols;
507 
508 	gnome_canvas_item_set (
509 		GNOME_CANVAS_ITEM (cal->calitem),
510 		"minimum_rows", rows,
511 		"minimum_columns", cols,
512 		NULL);
513 
514 	gtk_widget_queue_resize (GTK_WIDGET (cal));
515 }
516 
517 void
518 e_calendar_set_maximum_size (ECalendar *cal,
519                              gint rows,
520                              gint cols)
521 {
522 	g_return_if_fail (E_IS_CALENDAR (cal));
523 
524 	cal->max_rows = rows;
525 	cal->max_cols = cols;
526 
527 	gnome_canvas_item_set (
528 		GNOME_CANVAS_ITEM (cal->calitem),
529 		"maximum_rows", rows,
530 		"maximum_columns", cols,
531 		NULL);
532 
533 	gtk_widget_queue_resize (GTK_WIDGET (cal));
534 }
535 
536 /* Returns the border size on each side of the month displays. */
537 void
538 e_calendar_get_border_size (ECalendar *cal,
539                             gint *top,
540                             gint *bottom,
541                             gint *left,
542                             gint *right)
543 {
544 	GtkStyle *style;
545 
546 	g_return_if_fail (E_IS_CALENDAR (cal));
547 
548 	style = gtk_widget_get_style (GTK_WIDGET (cal));
549 
550 	if (style) {
551 		*top    = style->ythickness;
552 		*bottom = style->ythickness;
553 		*left   = style->xthickness;
554 		*right  = style->xthickness;
555 	} else {
556 		*top = *bottom = *left = *right = 0;
557 	}
558 }
559 
560 static void
561 e_calendar_on_prev_pressed (ECalendar *cal)
562 {
563 	e_calendar_start_auto_move (cal, FALSE);
564 }
565 
566 static void
567 e_calendar_on_next_pressed (ECalendar *cal)
568 {
569 	e_calendar_start_auto_move (cal, TRUE);
570 }
571 
572 static void
573 e_calendar_on_prev_year_pressed (ECalendar *cal)
574 {
575 	e_calendar_start_auto_move_year (cal, FALSE);
576 }
577 
578 static void
579 e_calendar_on_next_year_pressed (ECalendar *cal)
580 {
581 	e_calendar_start_auto_move_year (cal, TRUE);
582 }
583 
584 static void
585 e_calendar_start_auto_move (ECalendar *cal,
586                             gboolean moving_forward)
587 {
588 	if (cal->timeout_id == 0) {
589 		cal->timeout_id = g_timeout_add (
590 			E_CALENDAR_AUTO_MOVE_TIMEOUT,
591 			e_calendar_auto_move_handler,
592 			cal);
593 	}
594 	cal->timeout_delay = E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY;
595 	cal->moving_forward = moving_forward;
596 
597 }
598 
599 static void
600 e_calendar_start_auto_move_year (ECalendar *cal,
601                                  gboolean moving_forward)
602 {
603 	if (cal->timeout_id == 0) {
604 		cal->timeout_id = g_timeout_add (
605 			E_CALENDAR_AUTO_MOVE_TIMEOUT,
606 			e_calendar_auto_move_year_handler,
607 			cal);
608 	}
609 	cal->timeout_delay = E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY;
610 	cal->moving_forward = moving_forward;
611 }
612 
613 static gboolean
614 e_calendar_auto_move_year_handler (gpointer data)
615 {
616 	ECalendar *cal;
617 	ECalendarItem *calitem;
618 	gint offset;
619 
620 	g_return_val_if_fail (E_IS_CALENDAR (data), FALSE);
621 
622 	cal = E_CALENDAR (data);
623 	calitem = cal->calitem;
624 
625 	if (cal->timeout_delay > 0) {
626 		cal->timeout_delay--;
627 	} else {
628 		offset = cal->moving_forward ? 12 : -12;
629 		e_calendar_item_set_first_month (
630 			calitem, calitem->year,
631 			calitem->month + offset);
632 	}
633 
634 	return TRUE;
635 }
636 
637 static gboolean
638 e_calendar_auto_move_handler (gpointer data)
639 {
640 	ECalendar *cal;
641 	ECalendarItem *calitem;
642 	gint offset;
643 
644 	g_return_val_if_fail (E_IS_CALENDAR (data), FALSE);
645 
646 	cal = E_CALENDAR (data);
647 	calitem = cal->calitem;
648 
649 	if (cal->timeout_delay > 0) {
650 		cal->timeout_delay--;
651 	} else {
652 		offset = cal->moving_forward ? 1 : -1;
653 		e_calendar_item_set_first_month (
654 			calitem, calitem->year,
655 			calitem->month + offset);
656 	}
657 
658 	return TRUE;
659 }
660 
661 static void
662 e_calendar_on_prev_released (ECalendar *cal)
663 {
664 	e_calendar_stop_auto_move (cal);
665 }
666 
667 static void
668 e_calendar_on_next_released (ECalendar *cal)
669 {
670 	e_calendar_stop_auto_move (cal);
671 }
672 
673 static void
674 e_calendar_on_prev_year_released (ECalendar *cal)
675 {
676 	e_calendar_stop_auto_move (cal);
677 }
678 
679 static void
680 e_calendar_on_next_year_released (ECalendar *cal)
681 {
682 	e_calendar_stop_auto_move (cal);
683 }
684 
685 static void
686 e_calendar_stop_auto_move (ECalendar *cal)
687 {
688 	if (cal->timeout_id != 0) {
689 		g_source_remove (cal->timeout_id);
690 		cal->timeout_id = 0;
691 	}
692 }
693 
694 static void
695 e_calendar_on_prev_clicked (ECalendar *cal)
696 {
697 	e_calendar_item_set_first_month (
698 		cal->calitem, cal->calitem->year,
699 		cal->calitem->month - 1);
700 }
701 
702 static void
703 e_calendar_on_next_clicked (ECalendar *cal)
704 {
705 	e_calendar_item_set_first_month (
706 		cal->calitem, cal->calitem->year,
707 		cal->calitem->month + 1);
708 }
709 
710 static void
711 e_calendar_on_prev_year_clicked (ECalendar *cal)
712 {
713 	e_calendar_item_set_first_month (
714 		cal->calitem, cal->calitem->year,
715 		cal->calitem->month - 12);
716 }
717 
718 static void
719 e_calendar_on_next_year_clicked (ECalendar *cal)
720 {
721 	e_calendar_item_set_first_month (
722 		cal->calitem, cal->calitem->year,
723 		cal->calitem->month + 12);
724 }
725 
726 static gint
727 e_calendar_drag_motion (GtkWidget *widget,
728                         GdkDragContext *context,
729                         gint x,
730                         gint y,
731                         guint time)
732 {
733 	return FALSE;
734 }
735 
736 static void
737 e_calendar_drag_leave (GtkWidget *widget,
738                        GdkDragContext *context,
739                        guint time)
740 {
741 }
742 
743 static gboolean
744 e_calendar_button_has_focus (ECalendar *cal)
745 {
746 	GtkWidget *prev_widget, *next_widget;
747 	gboolean ret_val;
748 
749 	g_return_val_if_fail (E_IS_CALENDAR (cal), FALSE);
750 
751 	prev_widget = GNOME_CANVAS_WIDGET (cal->prev_item)->widget;
752 	next_widget = GNOME_CANVAS_WIDGET (cal->next_item)->widget;
753 	ret_val = gtk_widget_has_focus (prev_widget) ||
754 		gtk_widget_has_focus (next_widget);
755 	return ret_val;
756 }
757 
758 static gboolean
759 e_calendar_focus (GtkWidget *widget,
760                   GtkDirectionType direction)
761 {
762 #define E_CALENDAR_FOCUS_CHILDREN_NUM 5
763 	ECalendar *cal;
764 	GnomeCanvas *canvas;
765 	GnomeCanvasItem *children[E_CALENDAR_FOCUS_CHILDREN_NUM];
766 	gint focused_index = -1;
767 	gint index;
768 
769 	g_return_val_if_fail (widget != NULL, FALSE);
770 	g_return_val_if_fail (E_IS_CALENDAR (widget), FALSE);
771 	cal = E_CALENDAR (widget);
772 	canvas = GNOME_CANVAS (widget);
773 
774 	if (!gtk_widget_get_can_focus (widget))
775 		return FALSE;
776 
777 	children[0] = GNOME_CANVAS_ITEM (cal->calitem);
778 	children[1] = cal->prev_item;
779 	children[2] = cal->next_item;
780 	children[3] = cal->prev_item_year;
781 	children[4] = cal->next_item_year;
782 
783 	/* get current focused item, if e-calendar has had focus */
784 	if (gtk_widget_has_focus (widget) || e_calendar_button_has_focus (cal))
785 		for (index = 0; index < E_CALENDAR_FOCUS_CHILDREN_NUM; ++index) {
786 			if (canvas->focused_item == NULL)
787 				break;
788 
789 			if (children[index] == canvas->focused_item) {
790 				focused_index = index;
791 				break;
792 			}
793 		}
794 
795 	if (focused_index == -1)
796 		if (direction == GTK_DIR_TAB_FORWARD)
797 			focused_index = 0;
798 		else
799 			focused_index = E_CALENDAR_FOCUS_CHILDREN_NUM - 1;
800 	else
801 		if (direction == GTK_DIR_TAB_FORWARD)
802 			++focused_index;
803 		else
804 			--focused_index;
805 
806 	if (focused_index < 0 ||
807 	    focused_index >= E_CALENDAR_FOCUS_CHILDREN_NUM)
808 		/* move out of e-calendar */
809 		return FALSE;
810 	gnome_canvas_item_grab_focus (children[focused_index]);
811 	if (GNOME_IS_CANVAS_WIDGET (children[focused_index])) {
812 		widget = GNOME_CANVAS_WIDGET (children[focused_index])->widget;
813 		gtk_widget_grab_focus (widget);
814 	}
815 	return TRUE;
816 }
817 
818 void
819 e_calendar_set_focusable (ECalendar *cal,
820                           gboolean focusable)
821 {
822 	GtkWidget *widget;
823 	GtkWidget *prev_widget, *next_widget;
824 	GtkWidget *toplevel;
825 
826 	g_return_if_fail (E_IS_CALENDAR (cal));
827 
828 	widget = GTK_WIDGET (cal);
829 	prev_widget = GNOME_CANVAS_WIDGET (cal->prev_item)->widget;
830 	next_widget = GNOME_CANVAS_WIDGET (cal->next_item)->widget;
831 
832 	if (focusable) {
833 		gtk_widget_set_can_focus (widget, TRUE);
834 		gtk_widget_set_can_focus (prev_widget, TRUE);
835 		gtk_widget_set_can_focus (next_widget, TRUE);
836 	}
837 	else {
838 		if (gtk_widget_has_focus (GTK_WIDGET (cal)) ||
839 		    e_calendar_button_has_focus (cal)) {
840 			toplevel = gtk_widget_get_toplevel (widget);
841 			if (toplevel)
842 				gtk_widget_grab_focus (toplevel);
843 		}
844 		gtk_widget_set_can_focus (widget, FALSE);
845 		gtk_widget_set_can_focus (prev_widget, FALSE);
846 		gtk_widget_set_can_focus (next_widget, FALSE);
847 	}
848 }