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