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