Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / e-util / e-calendar-item.c
blob6708e8be0441fcf89bfbcf97e37db43c876c0874
1 /*
2 * ECalendarItem - canvas item displaying a calendar.
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 * Authors:
17 * Damon Chaplin <damon@ximian.com>
18 * Bolian Yin <bolian.yin@sun.com>
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
23 #include "evolution-config.h"
25 #include <libebackend/libebackend.h>
27 #include "e-calendar-item.h"
29 #include <time.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkkeysyms.h>
34 #include <glib/gi18n.h>
36 #include "ea-widgets.h"
37 #include "e-misc-utils.h"
38 #include "e-util-enumtypes.h"
40 static const gint e_calendar_item_days_in_month[12] = {
41 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
44 #define DAYS_IN_MONTH(year, month) \
45 e_calendar_item_days_in_month[month] + (((month) == 1 \
46 && ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))) ? 1 : 0)
48 static void e_calendar_item_dispose (GObject *object);
49 static void e_calendar_item_get_property (GObject *object,
50 guint property_id,
51 GValue *value,
52 GParamSpec *pspec);
53 static void e_calendar_item_set_property (GObject *object,
54 guint property_id,
55 const GValue *value,
56 GParamSpec *pspec);
57 static void e_calendar_item_realize (GnomeCanvasItem *item);
58 static void e_calendar_item_unmap (GnomeCanvasItem *item);
59 static void e_calendar_item_update (GnomeCanvasItem *item,
60 const cairo_matrix_t *i2c,
61 gint flags);
62 static void e_calendar_item_draw (GnomeCanvasItem *item,
63 cairo_t *cr,
64 gint x,
65 gint y,
66 gint width,
67 gint height);
68 static void e_calendar_item_draw_month (ECalendarItem *calitem,
69 cairo_t *cr,
70 gint x,
71 gint y,
72 gint width,
73 gint height,
74 gint row,
75 gint col);
76 static void e_calendar_item_draw_day_numbers
77 (ECalendarItem *calitem,
78 cairo_t *cr,
79 gint width,
80 gint height,
81 gint row,
82 gint col,
83 gint year,
84 gint month,
85 GDateWeekday start_weekday,
86 gint cells_x,
87 gint cells_y);
88 static GnomeCanvasItem *e_calendar_item_point (GnomeCanvasItem *item,
89 gdouble x,
90 gdouble y,
91 gint cx,
92 gint cy);
93 static void e_calendar_item_stop_selecting (ECalendarItem *calitem,
94 guint32 time);
95 static void e_calendar_item_selection_add_days
96 (ECalendarItem *calitem,
97 gint n_days,
98 gboolean multi_selection);
99 static gint e_calendar_item_key_press_event (ECalendarItem *item,
100 GdkEvent *event);
101 static gint e_calendar_item_event (GnomeCanvasItem *item,
102 GdkEvent *event);
103 static void e_calendar_item_bounds (GnomeCanvasItem *item,
104 gdouble *x1,
105 gdouble *y1,
106 gdouble *x2,
107 gdouble *y2);
109 static gboolean e_calendar_item_button_press (ECalendarItem *calitem,
110 GdkEvent *event);
111 static gboolean e_calendar_item_button_release (ECalendarItem *calitem,
112 GdkEvent *event);
113 static gboolean e_calendar_item_motion (ECalendarItem *calitem,
114 GdkEvent *event);
116 static gboolean e_calendar_item_convert_position_to_day
117 (ECalendarItem *calitem,
118 gint x,
119 gint y,
120 gboolean round_empty_positions,
121 gint *month_offset,
122 gint *day,
123 gboolean *entire_week);
124 static void e_calendar_item_get_month_info (ECalendarItem *calitem,
125 gint row,
126 gint col,
127 gint *first_day_offset,
128 gint *days_in_month,
129 gint *days_in_prev_month);
130 static void e_calendar_item_recalc_sizes (ECalendarItem *calitem);
132 static void e_calendar_item_get_day_style (ECalendarItem *calitem,
133 gint year,
134 gint month,
135 gint day,
136 gint day_style,
137 gboolean today,
138 gboolean prev_or_next_month,
139 gboolean selected,
140 gboolean has_focus,
141 gboolean drop_target,
142 GdkColor **bg_color,
143 GdkColor **fg_color,
144 GdkColor **box_color,
145 gboolean *bold,
146 gboolean *italic,
147 GdkColor *local_bg_color,
148 GdkColor *local_fg_color);
149 static void e_calendar_item_check_selection_end
150 (ECalendarItem *calitem,
151 gint start_month,
152 gint start_day,
153 gint *end_month,
154 gint *end_day);
155 static void e_calendar_item_check_selection_start
156 (ECalendarItem *calitem,
157 gint *start_month,
158 gint *start_day,
159 gint end_month,
160 gint end_day);
161 static void e_calendar_item_add_days_to_selection
162 (ECalendarItem *calitem,
163 gint days);
164 static void e_calendar_item_round_up_selection
165 (ECalendarItem *calitem,
166 gint *month_offset,
167 gint *day);
168 static void e_calendar_item_round_down_selection
169 (ECalendarItem *calitem,
170 gint *month_offset,
171 gint *day);
172 static gint e_calendar_item_get_inclusive_days
173 (ECalendarItem *calitem,
174 gint start_month_offset,
175 gint start_day,
176 gint end_month_offset,
177 gint end_day);
178 static void e_calendar_item_ensure_valid_day
179 (ECalendarItem *calitem,
180 gint *month_offset,
181 gint *day);
182 static gboolean e_calendar_item_ensure_days_visible
183 (ECalendarItem *calitem,
184 gint start_year,
185 gint start_month,
186 gint start_day,
187 gint end_year,
188 gint end_month,
189 gint end_day,
190 gboolean emission);
191 static void e_calendar_item_show_popup_menu (ECalendarItem *calitem,
192 GdkEvent *button_event,
193 gint month_offset);
194 static void e_calendar_item_on_menu_item_activate
195 (GtkWidget *menuitem,
196 ECalendarItem *calitem);
197 static void e_calendar_item_date_range_changed
198 (ECalendarItem *calitem);
199 static void e_calendar_item_queue_signal_emission
200 (ECalendarItem *calitem);
201 static gboolean e_calendar_item_signal_emission_idle_cb
202 (gpointer data);
203 static void e_calendar_item_set_selection_if_emission
204 (ECalendarItem *calitem,
205 const GDate *start_date,
206 const GDate *end_date,
207 gboolean emission);
208 static void e_calendar_item_set_first_month_with_emit
209 (ECalendarItem *calitem,
210 gint year,
211 gint month,
212 gboolean emit_date_range_moved);
214 /* Our arguments. */
215 enum {
216 PROP_0,
217 PROP_YEAR,
218 PROP_MONTH,
219 PROP_X1,
220 PROP_Y1,
221 PROP_X2,
222 PROP_Y2,
223 PROP_FONT_DESC,
224 PROP_WEEK_NUMBER_FONT,
225 PROP_WEEK_NUMBER_FONT_DESC,
226 PROP_ROW_HEIGHT,
227 PROP_COLUMN_WIDTH,
228 PROP_MINIMUM_ROWS,
229 PROP_MINIMUM_COLUMNS,
230 PROP_MAXIMUM_ROWS,
231 PROP_MAXIMUM_COLUMNS,
232 PROP_WEEK_START_DAY,
233 PROP_SHOW_WEEK_NUMBERS,
234 PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
235 PROP_MAXIMUM_DAYS_SELECTED,
236 PROP_DAYS_TO_START_WEEK_SELECTION,
237 PROP_MOVE_SELECTION_WHEN_MOVING,
238 PROP_PRESERVE_DAY_WHEN_MOVING,
239 PROP_DISPLAY_POPUP
242 enum {
243 DATE_RANGE_CHANGED,
244 DATE_RANGE_MOVED,
245 SELECTION_CHANGED,
246 SELECTION_PREVIEW_CHANGED,
247 MONTH_WIDTH_CHANGED,
248 LAST_SIGNAL
251 static guint e_calendar_item_signals[LAST_SIGNAL] = { 0 };
253 G_DEFINE_TYPE_WITH_CODE (
254 ECalendarItem,
255 e_calendar_item,
256 GNOME_TYPE_CANVAS_ITEM,
257 G_IMPLEMENT_INTERFACE (
258 E_TYPE_EXTENSIBLE, NULL))
260 static void
261 e_calendar_item_class_init (ECalendarItemClass *class)
263 GObjectClass *object_class;
264 GnomeCanvasItemClass *item_class;
266 object_class = G_OBJECT_CLASS (class);
267 object_class->dispose = e_calendar_item_dispose;
268 object_class->get_property = e_calendar_item_get_property;
269 object_class->set_property = e_calendar_item_set_property;
271 item_class = GNOME_CANVAS_ITEM_CLASS (class);
272 item_class->realize = e_calendar_item_realize;
273 item_class->unmap = e_calendar_item_unmap;
274 item_class->update = e_calendar_item_update;
275 item_class->draw = e_calendar_item_draw;
276 item_class->point = e_calendar_item_point;
277 item_class->event = e_calendar_item_event;
278 item_class->bounds = e_calendar_item_bounds;
280 class->date_range_changed = NULL;
281 class->selection_changed = NULL;
282 class->selection_preview_changed = NULL;
284 g_object_class_install_property (
285 object_class,
286 PROP_YEAR,
287 g_param_spec_int (
288 "year",
289 NULL,
290 NULL,
291 G_MININT,
292 G_MAXINT,
294 G_PARAM_READWRITE));
296 g_object_class_install_property (
297 object_class,
298 PROP_MONTH,
299 g_param_spec_int (
300 "month",
301 NULL,
302 NULL,
303 G_MININT,
304 G_MAXINT,
306 G_PARAM_READWRITE));
308 g_object_class_install_property (
309 object_class,
310 PROP_X1,
311 g_param_spec_double (
312 "x1",
313 NULL,
314 NULL,
315 -G_MAXDOUBLE,
316 G_MAXDOUBLE,
318 G_PARAM_READWRITE));
320 g_object_class_install_property (
321 object_class,
322 PROP_Y1,
323 g_param_spec_double (
324 "y1",
325 NULL,
326 NULL,
327 -G_MAXDOUBLE,
328 G_MAXDOUBLE,
330 G_PARAM_READWRITE));
332 g_object_class_install_property (
333 object_class,
334 PROP_X2,
335 g_param_spec_double (
336 "x2",
337 NULL,
338 NULL,
339 -G_MAXDOUBLE,
340 G_MAXDOUBLE,
342 G_PARAM_READWRITE));
344 g_object_class_install_property (
345 object_class,
346 PROP_Y2,
347 g_param_spec_double (
348 "y2",
349 NULL,
350 NULL,
351 -G_MAXDOUBLE,
352 G_MAXDOUBLE,
354 G_PARAM_READWRITE));
356 g_object_class_install_property (
357 object_class,
358 PROP_FONT_DESC,
359 g_param_spec_boxed (
360 "font_desc",
361 NULL,
362 NULL,
363 PANGO_TYPE_FONT_DESCRIPTION,
364 G_PARAM_READWRITE));
366 g_object_class_install_property (
367 object_class,
368 PROP_WEEK_NUMBER_FONT_DESC,
369 g_param_spec_boxed (
370 "week_number_font_desc",
371 NULL,
372 NULL,
373 PANGO_TYPE_FONT_DESCRIPTION,
374 G_PARAM_READWRITE));
376 g_object_class_install_property (
377 object_class,
378 PROP_ROW_HEIGHT,
379 g_param_spec_int (
380 "row_height",
381 NULL,
382 NULL,
383 G_MININT,
384 G_MAXINT,
386 G_PARAM_READABLE));
388 g_object_class_install_property (
389 object_class,
390 PROP_COLUMN_WIDTH,
391 g_param_spec_int (
392 "column_width",
393 NULL,
394 NULL,
395 G_MININT,
396 G_MAXINT,
398 G_PARAM_READABLE));
400 g_object_class_install_property (
401 object_class,
402 PROP_MINIMUM_ROWS,
403 g_param_spec_int (
404 "minimum_rows",
405 NULL,
406 NULL,
407 G_MININT,
408 G_MAXINT,
410 G_PARAM_READWRITE));
412 g_object_class_install_property (
413 object_class,
414 PROP_MINIMUM_COLUMNS,
415 g_param_spec_int (
416 "minimum_columns",
417 NULL,
418 NULL,
419 G_MININT,
420 G_MAXINT,
422 G_PARAM_READWRITE));
424 g_object_class_install_property (
425 object_class,
426 PROP_MAXIMUM_ROWS,
427 g_param_spec_int (
428 "maximum_rows",
429 NULL,
430 NULL,
431 G_MININT,
432 G_MAXINT,
434 G_PARAM_READWRITE));
436 g_object_class_install_property (
437 object_class,
438 PROP_MAXIMUM_COLUMNS,
439 g_param_spec_int (
440 "maximum_columns",
441 NULL,
442 NULL,
443 G_MININT,
444 G_MAXINT,
446 G_PARAM_READWRITE));
448 g_object_class_install_property (
449 object_class,
450 PROP_WEEK_START_DAY,
451 g_param_spec_enum (
452 "week-start-day",
453 NULL,
454 NULL,
455 E_TYPE_DATE_WEEKDAY,
456 G_DATE_MONDAY,
457 G_PARAM_READWRITE |
458 G_PARAM_STATIC_STRINGS));
460 g_object_class_install_property (
461 object_class,
462 PROP_SHOW_WEEK_NUMBERS,
463 g_param_spec_boolean (
464 "show_week_numbers",
465 NULL,
466 NULL,
467 TRUE,
468 G_PARAM_READWRITE));
470 g_object_class_install_property (
471 object_class,
472 PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
473 g_param_spec_boolean (
474 "keep_wdays_on_weeknum_click",
475 NULL,
476 NULL,
477 FALSE,
478 G_PARAM_READWRITE));
480 g_object_class_install_property (
481 object_class,
482 PROP_MAXIMUM_DAYS_SELECTED,
483 g_param_spec_int (
484 "maximum_days_selected",
485 NULL,
486 NULL,
487 G_MININT,
488 G_MAXINT,
490 G_PARAM_READWRITE));
492 g_object_class_install_property (
493 object_class,
494 PROP_DAYS_TO_START_WEEK_SELECTION,
495 g_param_spec_int (
496 "days_to_start_week_selection",
497 NULL,
498 NULL,
499 G_MININT,
500 G_MAXINT,
502 G_PARAM_READWRITE));
504 g_object_class_install_property (
505 object_class,
506 PROP_MOVE_SELECTION_WHEN_MOVING,
507 g_param_spec_boolean (
508 "move_selection_when_moving",
509 NULL,
510 NULL,
511 TRUE,
512 G_PARAM_READWRITE));
514 g_object_class_install_property (
515 object_class,
516 PROP_PRESERVE_DAY_WHEN_MOVING,
517 g_param_spec_boolean (
518 "preserve_day_when_moving",
519 NULL,
520 NULL,
521 TRUE,
522 G_PARAM_READWRITE));
524 g_object_class_install_property (
525 object_class,
526 PROP_DISPLAY_POPUP,
527 g_param_spec_boolean (
528 "display_popup",
529 NULL,
530 NULL,
531 TRUE,
532 G_PARAM_READWRITE));
534 e_calendar_item_signals[DATE_RANGE_CHANGED] = g_signal_new (
535 "date_range_changed",
536 G_TYPE_FROM_CLASS (object_class),
537 G_SIGNAL_RUN_FIRST,
538 G_STRUCT_OFFSET (ECalendarItemClass, date_range_changed),
539 NULL, NULL,
540 g_cclosure_marshal_VOID__VOID,
541 G_TYPE_NONE, 0);
543 /* Invoked when a user changes date range, by pressing month/year
544 arrows or any similar way, but not when selecting a day in the calendar. */
545 e_calendar_item_signals[DATE_RANGE_MOVED] = g_signal_new (
546 "date-range-moved",
547 G_TYPE_FROM_CLASS (object_class),
548 G_SIGNAL_RUN_FIRST,
549 0, NULL, NULL,
550 g_cclosure_marshal_VOID__VOID,
551 G_TYPE_NONE, 0);
553 e_calendar_item_signals[SELECTION_CHANGED] = g_signal_new (
554 "selection_changed",
555 G_TYPE_FROM_CLASS (object_class),
556 G_SIGNAL_RUN_FIRST,
557 G_STRUCT_OFFSET (ECalendarItemClass, selection_changed),
558 NULL, NULL,
559 g_cclosure_marshal_VOID__VOID,
560 G_TYPE_NONE, 0);
562 e_calendar_item_signals[SELECTION_PREVIEW_CHANGED] = g_signal_new (
563 "selection_preview_changed",
564 G_TYPE_FROM_CLASS (object_class),
565 G_SIGNAL_RUN_LAST,
566 G_STRUCT_OFFSET (ECalendarItemClass, selection_preview_changed),
567 NULL, NULL,
568 g_cclosure_marshal_VOID__VOID,
569 G_TYPE_NONE, 0);
571 e_calendar_item_signals[MONTH_WIDTH_CHANGED] = g_signal_new (
572 "month-width-changed",
573 G_TYPE_FROM_CLASS (object_class),
574 G_SIGNAL_RUN_LAST,
575 0 /* G_STRUCT_OFFSET (ECalendarItemClass, month_width_changed) */,
576 NULL, NULL,
577 g_cclosure_marshal_VOID__VOID,
578 G_TYPE_NONE, 0);
580 e_calendar_item_a11y_init ();
583 static void
584 e_calendar_item_init (ECalendarItem *calitem)
586 struct tm *tmp_tm;
587 time_t t;
589 /* Set the default time to the current month. */
590 t = time (NULL);
591 tmp_tm = localtime (&t);
592 calitem->year = tmp_tm->tm_year + 1900;
593 calitem->month = tmp_tm->tm_mon;
595 calitem->styles = NULL;
597 calitem->min_cols = 1;
598 calitem->min_rows = 1;
599 calitem->max_cols = -1;
600 calitem->max_rows = -1;
602 calitem->rows = 0;
603 calitem->cols = 0;
605 calitem->show_week_numbers = FALSE;
606 calitem->keep_wdays_on_weeknum_click = FALSE;
607 calitem->week_start_day = G_DATE_MONDAY;
608 calitem->expand = TRUE;
609 calitem->max_days_selected = 1;
610 calitem->days_to_start_week_selection = -1;
611 calitem->move_selection_when_moving = TRUE;
612 calitem->preserve_day_when_moving = FALSE;
613 calitem->display_popup = TRUE;
615 calitem->x1 = 0.0;
616 calitem->y1 = 0.0;
617 calitem->x2 = 0.0;
618 calitem->y2 = 0.0;
620 calitem->selecting = FALSE;
621 calitem->selecting_axis = NULL;
623 calitem->selection_set = FALSE;
625 calitem->selection_changed = FALSE;
626 calitem->date_range_changed = FALSE;
628 calitem->style_callback = NULL;
629 calitem->style_callback_data = NULL;
630 calitem->style_callback_destroy = NULL;
632 calitem->time_callback = NULL;
633 calitem->time_callback_data = NULL;
634 calitem->time_callback_destroy = NULL;
636 calitem->signal_emission_idle_id = 0;
639 static void
640 e_calendar_item_dispose (GObject *object)
642 ECalendarItem *calitem;
644 calitem = E_CALENDAR_ITEM (object);
646 e_calendar_item_set_style_callback (calitem, NULL, NULL, NULL);
647 e_calendar_item_set_get_time_callback (calitem, NULL, NULL, NULL);
649 if (calitem->styles) {
650 g_free (calitem->styles);
651 calitem->styles = NULL;
654 if (calitem->signal_emission_idle_id > 0) {
655 g_source_remove (calitem->signal_emission_idle_id);
656 calitem->signal_emission_idle_id = -1;
659 if (calitem->font_desc) {
660 pango_font_description_free (calitem->font_desc);
661 calitem->font_desc = NULL;
664 if (calitem->week_number_font_desc) {
665 pango_font_description_free (calitem->week_number_font_desc);
666 calitem->week_number_font_desc = NULL;
669 if (calitem->selecting_axis)
670 g_free (calitem->selecting_axis);
672 G_OBJECT_CLASS (e_calendar_item_parent_class)->dispose (object);
675 static void
676 e_calendar_item_get_property (GObject *object,
677 guint property_id,
678 GValue *value,
679 GParamSpec *pspec)
681 ECalendarItem *calitem;
683 calitem = E_CALENDAR_ITEM (object);
685 switch (property_id) {
686 case PROP_YEAR:
687 g_value_set_int (value, calitem->year);
688 return;
689 case PROP_MONTH:
690 g_value_set_int (value, calitem->month);
691 return;
692 case PROP_X1:
693 g_value_set_double (value, calitem->x1);
694 return;
695 case PROP_Y1:
696 g_value_set_double (value, calitem->y1);
697 return;
698 case PROP_X2:
699 g_value_set_double (value, calitem->x2);
700 return;
701 case PROP_Y2:
702 g_value_set_double (value, calitem->y2);
703 return;
704 case PROP_FONT_DESC:
705 g_value_set_boxed (value, calitem->font_desc);
706 return;
707 case PROP_WEEK_NUMBER_FONT_DESC:
708 g_value_set_boxed (value, calitem->week_number_font_desc);
709 return;
710 case PROP_ROW_HEIGHT:
711 e_calendar_item_recalc_sizes (calitem);
712 g_value_set_int (value, calitem->min_month_height);
713 return;
714 case PROP_COLUMN_WIDTH:
715 e_calendar_item_recalc_sizes (calitem);
716 g_value_set_int (value, calitem->min_month_width);
717 return;
718 case PROP_MINIMUM_ROWS:
719 g_value_set_int (value, calitem->min_rows);
720 return;
721 case PROP_MINIMUM_COLUMNS:
722 g_value_set_int (value, calitem->min_cols);
723 return;
724 case PROP_MAXIMUM_ROWS:
725 g_value_set_int (value, calitem->max_rows);
726 return;
727 case PROP_MAXIMUM_COLUMNS:
728 g_value_set_int (value, calitem->max_cols);
729 return;
730 case PROP_WEEK_START_DAY:
731 g_value_set_enum (value, calitem->week_start_day);
732 return;
733 case PROP_SHOW_WEEK_NUMBERS:
734 g_value_set_boolean (value, calitem->show_week_numbers);
735 return;
736 case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
737 g_value_set_boolean (value, calitem->keep_wdays_on_weeknum_click);
738 return;
739 case PROP_MAXIMUM_DAYS_SELECTED:
740 g_value_set_int (value, e_calendar_item_get_max_days_sel (calitem));
741 return;
742 case PROP_DAYS_TO_START_WEEK_SELECTION:
743 g_value_set_int (value, e_calendar_item_get_days_start_week_sel (calitem));
744 return;
745 case PROP_MOVE_SELECTION_WHEN_MOVING:
746 g_value_set_boolean (value, calitem->move_selection_when_moving);
747 return;
748 case PROP_PRESERVE_DAY_WHEN_MOVING:
749 g_value_set_boolean (value, calitem->preserve_day_when_moving);
750 return;
751 case PROP_DISPLAY_POPUP:
752 g_value_set_boolean (value, e_calendar_item_get_display_popup (calitem));
753 return;
756 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
759 static void
760 e_calendar_item_set_property (GObject *object,
761 guint property_id,
762 const GValue *value,
763 GParamSpec *pspec)
765 GnomeCanvasItem *item;
766 ECalendarItem *calitem;
767 PangoFontDescription *font_desc;
768 gdouble dvalue;
769 gint ivalue;
770 gboolean bvalue;
772 item = GNOME_CANVAS_ITEM (object);
773 calitem = E_CALENDAR_ITEM (object);
775 switch (property_id) {
776 case PROP_YEAR:
777 ivalue = g_value_get_int (value);
778 e_calendar_item_set_first_month (
779 calitem, ivalue, calitem->month);
780 return;
781 case PROP_MONTH:
782 ivalue = g_value_get_int (value);
783 e_calendar_item_set_first_month (
784 calitem, calitem->year, ivalue);
785 return;
786 case PROP_X1:
787 dvalue = g_value_get_double (value);
788 if (calitem->x1 != dvalue) {
789 calitem->x1 = dvalue;
790 gnome_canvas_item_request_update (item);
792 return;
793 case PROP_Y1:
794 dvalue = g_value_get_double (value);
795 if (calitem->y1 != dvalue) {
796 calitem->y1 = dvalue;
797 gnome_canvas_item_request_update (item);
799 return;
800 case PROP_X2:
801 dvalue = g_value_get_double (value);
802 if (calitem->x2 != dvalue) {
803 calitem->x2 = dvalue;
804 gnome_canvas_item_request_update (item);
806 return;
807 case PROP_Y2:
808 dvalue = g_value_get_double (value);
809 if (calitem->y2 != dvalue) {
810 calitem->y2 = dvalue;
811 gnome_canvas_item_request_update (item);
813 return;
814 case PROP_FONT_DESC:
815 font_desc = g_value_get_boxed (value);
816 if (calitem->font_desc)
817 pango_font_description_free (calitem->font_desc);
818 calitem->font_desc = pango_font_description_copy (font_desc);
819 gnome_canvas_item_request_update (item);
820 return;
821 case PROP_WEEK_NUMBER_FONT_DESC:
822 font_desc = g_value_get_boxed (value);
823 if (calitem->week_number_font_desc)
824 pango_font_description_free (calitem->week_number_font_desc);
825 calitem->week_number_font_desc = pango_font_description_copy (font_desc);
826 gnome_canvas_item_request_update (item);
827 return;
828 case PROP_MINIMUM_ROWS:
829 ivalue = g_value_get_int (value);
830 ivalue = MAX (1, ivalue);
831 if (calitem->min_rows != ivalue) {
832 calitem->min_rows = ivalue;
833 gnome_canvas_item_request_update (item);
835 return;
836 case PROP_MINIMUM_COLUMNS:
837 ivalue = g_value_get_int (value);
838 ivalue = MAX (1, ivalue);
839 if (calitem->min_cols != ivalue) {
840 calitem->min_cols = ivalue;
841 gnome_canvas_item_request_update (item);
843 return;
844 case PROP_MAXIMUM_ROWS:
845 ivalue = g_value_get_int (value);
846 if (calitem->max_rows != ivalue) {
847 calitem->max_rows = ivalue;
848 gnome_canvas_item_request_update (item);
850 return;
851 case PROP_MAXIMUM_COLUMNS:
852 ivalue = g_value_get_int (value);
853 if (calitem->max_cols != ivalue) {
854 calitem->max_cols = ivalue;
855 gnome_canvas_item_request_update (item);
857 return;
858 case PROP_WEEK_START_DAY:
859 ivalue = g_value_get_enum (value);
860 if (calitem->week_start_day != ivalue) {
861 calitem->week_start_day = ivalue;
862 gnome_canvas_item_request_update (item);
864 return;
865 case PROP_SHOW_WEEK_NUMBERS:
866 bvalue = g_value_get_boolean (value);
867 if (calitem->show_week_numbers != bvalue) {
868 calitem->show_week_numbers = bvalue;
869 gnome_canvas_item_request_update (item);
871 return;
872 case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
873 calitem->keep_wdays_on_weeknum_click = g_value_get_boolean (value);
874 return;
875 case PROP_MAXIMUM_DAYS_SELECTED:
876 ivalue = g_value_get_int (value);
877 e_calendar_item_set_max_days_sel (calitem, ivalue);
878 return;
879 case PROP_DAYS_TO_START_WEEK_SELECTION:
880 ivalue = g_value_get_int (value);
881 e_calendar_item_set_days_start_week_sel (calitem, ivalue);
882 return;
883 case PROP_MOVE_SELECTION_WHEN_MOVING:
884 bvalue = g_value_get_boolean (value);
885 calitem->move_selection_when_moving = bvalue;
886 return;
887 case PROP_PRESERVE_DAY_WHEN_MOVING:
888 bvalue = g_value_get_boolean (value);
889 calitem->preserve_day_when_moving = bvalue;
890 return;
891 case PROP_DISPLAY_POPUP:
892 bvalue = g_value_get_boolean (value);
893 e_calendar_item_set_display_popup (calitem, bvalue);
894 return;
897 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
900 static void
901 e_calendar_item_realize (GnomeCanvasItem *item)
903 ECalendarItem *calitem;
905 if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize)
906 (* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize) (item);
908 calitem = E_CALENDAR_ITEM (item);
910 e_calendar_item_style_updated (GTK_WIDGET (item->canvas), calitem);
912 e_extensible_load_extensions (E_EXTENSIBLE (calitem));
915 static void
916 e_calendar_item_unmap (GnomeCanvasItem *item)
918 ECalendarItem *calitem;
920 calitem = E_CALENDAR_ITEM (item);
922 if (calitem->selecting) {
923 gnome_canvas_item_ungrab (item, GDK_CURRENT_TIME);
924 calitem->selecting = FALSE;
927 if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap)
928 (* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap) (item);
931 static void
932 e_calendar_item_update (GnomeCanvasItem *item,
933 const cairo_matrix_t *i2c,
934 gint flags)
936 GnomeCanvasItemClass *item_class;
937 ECalendarItem *calitem;
938 gint char_height, width, height, space, space_per_cal, space_per_cell;
939 gint rows, cols, xthickness, ythickness, old_month_width;
940 PangoContext *pango_context;
941 PangoFontMetrics *font_metrics;
942 GtkStyleContext *style_context;
943 GtkBorder padding;
945 item_class = GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class);
946 if (item_class->update != NULL)
947 item_class->update (item, i2c, flags);
949 calitem = E_CALENDAR_ITEM (item);
950 style_context = gtk_widget_get_style_context (GTK_WIDGET (item->canvas));
951 gtk_style_context_get_padding (style_context, gtk_style_context_get_state (style_context), &padding);
952 xthickness = padding.left;
953 ythickness = padding.top;
955 item->x1 = calitem->x1;
956 item->y1 = calitem->y1;
957 item->x2 = calitem->x2 >= calitem->x1 ? calitem->x2 : calitem->x1;
958 item->y2 = calitem->y2 >= calitem->y1 ? calitem->y2 : calitem->y1;
960 /* Set up Pango prerequisites */
961 pango_context = gtk_widget_get_pango_context (GTK_WIDGET (item->canvas));
962 font_metrics = pango_context_get_metrics (
963 pango_context, NULL,
964 pango_context_get_language (pango_context));
967 * Calculate the new layout of the calendar.
970 /* Make sure the minimum row width & cell height and the widths of
971 * all the digits and characters are up to date. */
972 e_calendar_item_recalc_sizes (calitem);
974 /* Calculate how many rows & cols we can fit in. */
975 width = item->x2 - item->x1;
976 height = item->y2 - item->y1;
978 width -= xthickness * 2;
979 height -= ythickness * 2;
981 if (calitem->min_month_height == 0)
982 rows = 1;
983 else
984 rows = height / calitem->min_month_height;
985 rows = MAX (rows, calitem->min_rows);
986 if (calitem->max_rows > 0)
987 rows = MIN (rows, calitem->max_rows);
989 if (calitem->min_month_width == 0)
990 cols = 1;
991 else
992 cols = width / calitem->min_month_width;
993 cols = MAX (cols, calitem->min_cols);
994 if (calitem->max_cols > 0)
995 cols = MIN (cols, calitem->max_cols);
997 if (rows != calitem->rows || cols != calitem->cols)
998 e_calendar_item_date_range_changed (calitem);
1000 calitem->rows = rows;
1001 calitem->cols = cols;
1003 /* Split up the empty space according to the configuration.
1004 * If the calendar is set to expand, we divide the space between the
1005 * cells and the spaces around the calendar, otherwise we place the
1006 * calendars in the center of the available area. */
1008 char_height =
1009 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1010 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
1012 old_month_width = calitem->month_width;
1013 calitem->month_width = calitem->min_month_width;
1014 calitem->month_height = calitem->min_month_height;
1015 calitem->cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
1016 + E_CALENDAR_ITEM_MIN_CELL_XPAD;
1017 calitem->cell_height = char_height
1018 + E_CALENDAR_ITEM_MIN_CELL_YPAD;
1019 calitem->month_tpad = 0;
1020 calitem->month_bpad = 0;
1021 calitem->month_lpad = 0;
1022 calitem->month_rpad = 0;
1024 space = height - calitem->rows * calitem->month_height;
1025 if (space > 0) {
1026 space_per_cal = space / calitem->rows;
1027 calitem->month_height += space_per_cal;
1029 if (calitem->expand) {
1030 space_per_cell = space_per_cal / E_CALENDAR_ROWS_PER_MONTH;
1031 calitem->cell_height += space_per_cell;
1032 space_per_cal -= space_per_cell * E_CALENDAR_ROWS_PER_MONTH;
1035 calitem->month_tpad = space_per_cal / 2;
1036 calitem->month_bpad = space_per_cal - calitem->month_tpad;
1039 space = width - calitem->cols * calitem->month_width;
1040 if (space > 0) {
1041 space_per_cal = space / calitem->cols;
1042 calitem->month_width += space_per_cal;
1043 space -= space_per_cal * calitem->cols;
1045 if (calitem->expand) {
1046 space_per_cell = space_per_cal / E_CALENDAR_COLS_PER_MONTH;
1047 calitem->cell_width += space_per_cell;
1048 space_per_cal -= space_per_cell * E_CALENDAR_COLS_PER_MONTH;
1051 calitem->month_lpad = space_per_cal / 2;
1052 calitem->month_rpad = space_per_cal - calitem->month_lpad;
1055 space = MAX (0, space);
1056 calitem->x_offset = space / 2;
1058 gnome_canvas_request_redraw (
1059 item->canvas, item->x1, item->y1,
1060 item->x2, item->y2);
1062 pango_font_metrics_unref (font_metrics);
1064 if (old_month_width != calitem->month_width) {
1065 g_signal_emit (calitem, e_calendar_item_signals[MONTH_WIDTH_CHANGED], 0, NULL);
1070 * DRAWING ROUTINES - functions to paint the canvas item.
1072 static void
1073 e_calendar_item_draw (GnomeCanvasItem *canvas_item,
1074 cairo_t *cr,
1075 gint x,
1076 gint y,
1077 gint width,
1078 gint height)
1080 ECalendarItem *calitem;
1081 GtkWidget *widget;
1082 GtkStyleContext *style_context;
1083 gint char_height, row, col, row_y, bar_height;
1084 PangoContext *pango_context;
1085 PangoFontMetrics *font_metrics;
1086 GdkRGBA bg_color;
1087 GtkBorder border;
1089 #if 0
1090 g_print (
1091 "In e_calendar_item_draw %i,%i %ix%i\n",
1092 x, y, width, height);
1093 #endif
1094 calitem = E_CALENDAR_ITEM (canvas_item);
1096 widget = GTK_WIDGET (canvas_item->canvas);
1097 style_context = gtk_widget_get_style_context (widget);
1099 /* Set up Pango prerequisites */
1100 pango_context = gtk_widget_get_pango_context (
1101 GTK_WIDGET (canvas_item->canvas));
1102 /* It's OK when the calitem->font_desc is NUL, then the currently set font is used */
1103 font_metrics = pango_context_get_metrics (
1104 pango_context, calitem->font_desc,
1105 pango_context_get_language (pango_context));
1107 char_height =
1108 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1109 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
1111 e_utils_get_theme_color (widget, "theme_bg_color", E_UTILS_DEFAULT_THEME_BG_COLOR, &bg_color);
1113 gtk_style_context_get_border (style_context, gtk_style_context_get_state (style_context), &border);
1115 /* Clear the entire background. */
1116 cairo_save (cr);
1117 gdk_cairo_set_source_rgba (cr, &bg_color);
1118 cairo_rectangle (
1119 cr, calitem->x1 - x, calitem->y1 - y,
1120 calitem->x2 - calitem->x1 + 1,
1121 calitem->y2 - calitem->y1 + 1);
1122 cairo_fill (cr);
1123 cairo_restore (cr);
1125 row_y = canvas_item->y1 + border.top;
1126 bar_height =
1127 border.top + border.bottom +
1128 E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height +
1129 E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME;
1131 for (row = 0; row < calitem->rows; row++) {
1132 /* Draw the background for the title bars and the shadow around
1133 * it, and the vertical lines between columns. */
1135 cairo_save (cr);
1136 gdk_cairo_set_source_rgba (cr, &bg_color);
1137 cairo_rectangle (
1138 cr, calitem->x1 + border.left - x,
1139 row_y - y,
1140 calitem->x2 - calitem->x1 + 1 -
1141 (border.left + border.right),
1142 bar_height);
1143 cairo_fill (cr);
1144 cairo_restore (cr);
1146 gtk_style_context_save (style_context);
1147 gtk_style_context_add_class (
1148 style_context, GTK_STYLE_CLASS_HEADER);
1149 cairo_save (cr);
1150 gtk_render_frame (
1151 style_context, cr,
1152 (gdouble) calitem->x1 + border.left - x,
1153 (gdouble) row_y - y,
1154 (gdouble) calitem->x2 - calitem->x1 + 1 -
1155 (border.left + border.right),
1156 (gdouble) bar_height);
1157 cairo_restore (cr);
1158 gtk_style_context_restore (style_context);
1160 for (col = 0; col < calitem->cols; col++) {
1161 e_calendar_item_draw_month (
1162 calitem, cr, x, y,
1163 width, height, row, col);
1166 row_y += calitem->month_height;
1169 /* Draw the shadow around the entire item. */
1170 gtk_style_context_save (style_context);
1171 gtk_style_context_add_class (
1172 style_context, GTK_STYLE_CLASS_ENTRY);
1173 cairo_save (cr);
1174 gtk_render_frame (
1175 style_context, cr,
1176 (gdouble) calitem->x1 - x,
1177 (gdouble) calitem->y1 - y,
1178 (gdouble) calitem->x2 - calitem->x1 + 1,
1179 (gdouble) calitem->y2 - calitem->y1 + 1);
1180 cairo_restore (cr);
1181 gtk_style_context_restore (style_context);
1183 pango_font_metrics_unref (font_metrics);
1186 static void
1187 layout_set_day_text (ECalendarItem *calitem,
1188 PangoLayout *layout,
1189 GDateWeekday weekday)
1191 const gchar *abbr_name;
1193 abbr_name = e_get_weekday_name (weekday, TRUE);
1194 pango_layout_set_text (layout, abbr_name, -1);
1197 static void
1198 e_calendar_item_draw_month (ECalendarItem *calitem,
1199 cairo_t *cr,
1200 gint x,
1201 gint y,
1202 gint width,
1203 gint height,
1204 gint row,
1205 gint col)
1207 GnomeCanvasItem *item;
1208 GtkWidget *widget;
1209 struct tm tmp_tm;
1210 GdkRectangle clip_rect;
1211 GDateWeekday start_weekday;
1212 gint char_height, xthickness, ythickness;
1213 gint year, month;
1214 gint month_x, month_y, month_w, month_h;
1215 gint min_x, max_x, text_x, text_y;
1216 gint day, cells_x, cells_y, min_cell_width, text_width, arrow_button_size;
1217 gint clip_width, clip_height;
1218 gchar buffer[64];
1219 GDateWeekday weekday;
1220 PangoContext *pango_context;
1221 PangoFontMetrics *font_metrics;
1222 PangoLayout *layout;
1223 GtkStyleContext *style_context;
1224 GtkBorder padding;
1225 PangoFontDescription *font_desc;
1226 GdkRGBA rgba;
1228 #if 0
1229 g_print (
1230 "In e_calendar_item_draw_month: %i,%i %ix%i row:%i col:%i\n",
1231 x, y, width, height, row, col);
1232 #endif
1233 item = GNOME_CANVAS_ITEM (calitem);
1234 widget = GTK_WIDGET (item->canvas);
1236 /* Set up Pango prerequisites */
1237 font_desc = calitem->font_desc;
1238 pango_context = gtk_widget_get_pango_context (widget);
1239 font_metrics = pango_context_get_metrics (
1240 pango_context, font_desc,
1241 pango_context_get_language (pango_context));
1242 if (!font_desc)
1243 font_desc = pango_context_get_font_description (pango_context);
1244 font_desc = pango_font_description_copy (font_desc);
1246 char_height =
1247 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1248 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
1249 style_context = gtk_widget_get_style_context (widget);
1250 gtk_style_context_get_padding (style_context, gtk_style_context_get_state (style_context), &padding);
1251 xthickness = padding.left;
1252 ythickness = padding.top;
1253 arrow_button_size =
1254 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
1255 + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
1256 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
1257 + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
1258 + 2 * xthickness;
1260 pango_font_metrics_unref (font_metrics);
1262 /* Calculate the top-left position of the entire month display. */
1263 month_x = item->x1 + xthickness + calitem->x_offset
1264 + ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1265 ? (calitem->cols - 1 - col) : col) * calitem->month_width - x;
1266 month_w = item->x2 - item->x1 - xthickness * 2;
1267 month_w = MIN (month_w, calitem->month_width);
1268 month_y = item->y1 + ythickness + row * calitem->month_height - y;
1269 month_h = item->y2 - item->y1 - ythickness * 2;
1270 month_h = MIN (month_h, calitem->month_height);
1272 /* Just return if the month is outside the given area. */
1273 if (month_x >= width || month_x + calitem->month_width <= 0
1274 || month_y >= height || month_y + calitem->month_height <= 0) {
1275 pango_font_description_free (font_desc);
1276 return;
1279 month = calitem->month + row * calitem->cols + col;
1280 year = calitem->year + month / 12;
1281 month %= 12;
1283 /* Draw the month name & year, with clipping. Note that the top row
1284 * needs extra space around it for the buttons. */
1286 layout = gtk_widget_create_pango_layout (widget, NULL);
1288 if (row == 0 && col == 0)
1289 min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON;
1290 else
1291 min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME;
1293 max_x = month_w;
1294 if (row == 0 && col == 0)
1295 max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON;
1296 else
1297 max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME;
1299 text_y = month_y + padding.top
1300 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME;
1301 clip_rect.x = month_x + min_x;
1302 clip_rect.x = MAX (0, clip_rect.x);
1303 clip_rect.y = MAX (0, text_y);
1305 memset (&tmp_tm, 0, sizeof (tmp_tm));
1306 tmp_tm.tm_year = year - 1900;
1307 tmp_tm.tm_mon = month;
1308 tmp_tm.tm_mday = 1;
1309 tmp_tm.tm_isdst = -1;
1310 mktime (&tmp_tm);
1312 start_weekday = e_weekday_from_tm_wday (tmp_tm.tm_wday);
1314 if (month_x + max_x - clip_rect.x > 0) {
1315 cairo_save (cr);
1317 clip_rect.width = month_x + max_x - clip_rect.x;
1318 clip_rect.height = text_y + char_height - clip_rect.y;
1319 gdk_cairo_rectangle (cr, &clip_rect);
1320 cairo_clip (cr);
1322 e_utils_get_theme_color (widget, "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &rgba);
1323 gdk_cairo_set_source_rgba (cr, &rgba);
1325 if (row == 0 && col == 0) {
1326 PangoLayout *layout_yr;
1327 gchar buffer_yr[64];
1328 gdouble max_width;
1330 layout_yr = gtk_widget_create_pango_layout (widget, NULL);
1332 /* This is a strftime() format. %B = Month name. */
1333 e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);
1334 /* This is a strftime() format. %Y = Year. */
1335 e_utf8_strftime (buffer_yr, sizeof (buffer_yr), C_("CalItem", "%Y"), &tmp_tm);
1337 pango_layout_set_font_description (layout, font_desc);
1338 pango_layout_set_text (layout, buffer, -1);
1340 pango_layout_set_font_description (layout_yr, font_desc);
1341 pango_layout_set_text (layout_yr, buffer_yr, -1);
1343 if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL) {
1344 max_width = calitem->max_month_name_width;
1345 pango_layout_get_pixel_size (layout, &text_width, NULL);
1347 cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
1348 pango_cairo_show_layout (cr, layout);
1350 max_width = calitem->max_digit_width * 5;
1351 pango_layout_get_pixel_size (layout_yr, &text_width, NULL);
1353 cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
1354 pango_cairo_show_layout (cr, layout_yr);
1355 } else {
1356 max_width = calitem->max_digit_width * 5;
1357 pango_layout_get_pixel_size (layout_yr, &text_width, NULL);
1359 cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
1360 pango_cairo_show_layout (cr, layout_yr);
1362 max_width = calitem->max_month_name_width;
1363 pango_layout_get_pixel_size (layout, &text_width, NULL);
1365 cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
1366 pango_cairo_show_layout (cr, layout);
1369 g_object_unref (layout_yr);
1370 } else {
1371 /* This is a strftime() format. %B = Month name, %Y = Year. */
1372 e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B %Y"), &tmp_tm);
1374 pango_layout_set_font_description (layout, font_desc);
1375 pango_layout_set_text (layout, buffer, -1);
1377 /* Ideally we place the text centered in the month, but we
1378 * won't go to the left of the minimum x position. */
1379 pango_layout_get_pixel_size (layout, &text_width, NULL);
1380 text_x = (calitem->month_width - text_width) / 2;
1381 text_x = MAX (min_x, text_x);
1383 cairo_move_to (cr, month_x + text_x, text_y);
1384 pango_cairo_show_layout (cr, layout);
1387 cairo_restore (cr);
1390 /* Set the clip rectangle for the main month display. */
1391 clip_rect.x = MAX (0, month_x);
1392 clip_rect.y = MAX (0, month_y);
1393 clip_width = month_x + month_w - clip_rect.x;
1394 clip_height = month_y + month_h - clip_rect.y;
1396 if (clip_width <= 0 || clip_height <= 0) {
1397 g_object_unref (layout);
1398 pango_font_description_free (font_desc);
1399 return;
1402 clip_rect.width = clip_width;
1403 clip_rect.height = clip_height;
1405 cairo_save (cr);
1407 gdk_cairo_rectangle (cr, &clip_rect);
1408 cairo_clip (cr);
1410 /* Draw the day initials across the top of the month. */
1411 min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
1412 + E_CALENDAR_ITEM_MIN_CELL_XPAD;
1414 cells_x = month_x + E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad
1415 + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
1416 if (calitem->show_week_numbers)
1417 cells_x += calitem->max_week_number_digit_width * 2
1418 + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
1419 text_x = cells_x + calitem->cell_width
1420 - (calitem->cell_width - min_cell_width) / 2;
1421 text_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
1422 text_y = month_y + ythickness * 2
1423 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
1424 + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
1425 + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad;
1427 cells_y = text_y + char_height
1428 + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
1429 + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
1431 cairo_save (cr);
1432 e_utils_get_theme_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &rgba);
1433 gdk_cairo_set_source_rgba (cr, &rgba);
1434 cairo_rectangle (
1435 cr, cells_x ,
1436 text_y - E_CALENDAR_ITEM_YPAD_ABOVE_CELLS - 1,
1437 calitem->cell_width * 7 , cells_y - text_y);
1438 cairo_fill (cr);
1439 cairo_restore (cr);
1441 weekday = calitem->week_start_day;
1442 pango_layout_set_font_description (layout, font_desc);
1443 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1444 text_x += (7 - 1) * calitem->cell_width;
1445 e_utils_get_theme_color (widget, "theme_text_color,theme_fg_color", E_UTILS_DEFAULT_THEME_TEXT_COLOR, &rgba);
1446 gdk_cairo_set_source_rgba (cr, &rgba);
1447 for (day = 0; day < 7; day++) {
1448 cairo_save (cr);
1449 layout_set_day_text (calitem, layout, weekday);
1450 cairo_move_to (
1452 text_x - calitem->day_widths[weekday],
1453 text_y);
1454 pango_cairo_show_layout (cr, layout);
1455 text_x += (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1456 ? -calitem->cell_width : calitem->cell_width;
1457 cairo_restore (cr);
1459 weekday = e_weekday_get_next (weekday);
1462 /* Draw the rectangle around the week number. */
1463 if (calitem->show_week_numbers) {
1464 cairo_save (cr);
1465 e_utils_get_theme_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &rgba);
1466 gdk_cairo_set_source_rgba (cr, &rgba);
1467 cairo_rectangle (
1468 cr, cells_x, cells_y - (cells_y - text_y + 2) ,
1469 -20, E_CALENDAR_ROWS_PER_MONTH * calitem->cell_height + 18);
1470 cairo_fill (cr);
1471 cairo_restore (cr);
1474 e_calendar_item_draw_day_numbers (
1475 calitem, cr, width, height, row, col,
1476 year, month, start_weekday, cells_x, cells_y);
1478 g_object_unref (layout);
1479 cairo_restore (cr);
1480 pango_font_description_free (font_desc);
1483 static const gchar *
1484 get_digit_fomat (void)
1487 #ifdef HAVE_GNU_GET_LIBC_VERSION
1488 #include <gnu/libc-version.h>
1490 const gchar *libc_version = gnu_get_libc_version ();
1491 gchar **split = g_strsplit (libc_version, ".", -1);
1492 gint major = 0;
1493 gint minor = 0;
1494 gint revision = 0;
1496 major = atoi (split[0]);
1497 minor = atoi (split[1]);
1499 if (g_strv_length (split) > 2)
1500 revision = atoi (split[2]);
1501 g_strfreev (split);
1503 if (major > 2 || minor > 2 || (minor == 2 && revision > 2)) {
1504 return "%Id";
1506 #endif
1508 return "%d";
1511 static void
1512 e_calendar_item_draw_day_numbers (ECalendarItem *calitem,
1513 cairo_t *cr,
1514 gint width,
1515 gint height,
1516 gint row,
1517 gint col,
1518 gint year,
1519 gint month,
1520 GDateWeekday start_weekday,
1521 gint cells_x,
1522 gint cells_y)
1524 GnomeCanvasItem *item;
1525 GtkWidget *widget;
1526 PangoFontDescription *font_desc;
1527 GdkColor *bg_color, *fg_color, *box_color;
1528 GdkRGBA rgba;
1529 struct tm today_tm;
1530 time_t t;
1531 gint char_height, min_cell_width, min_cell_height;
1532 gint day_num, drow, dcol, day_x, day_y;
1533 gint text_x, text_y;
1534 gint num_chars, digit;
1535 gint week_num, mon, days_from_week_start;
1536 gint years[3], months[3], days_in_month[3];
1537 gboolean today, selected, has_focus, drop_target = FALSE;
1538 gboolean bold, italic, draw_day, finished = FALSE;
1539 gint today_year, today_month, today_mday, month_offset;
1540 gchar buffer[64];
1541 gint day_style = 0;
1542 PangoContext *pango_context;
1543 PangoFontMetrics *font_metrics;
1544 PangoLayout *layout;
1546 item = GNOME_CANVAS_ITEM (calitem);
1547 widget = GTK_WIDGET (item->canvas);
1549 /* Set up Pango prerequisites */
1550 font_desc = calitem->font_desc;
1552 pango_context = gtk_widget_get_pango_context (widget);
1553 font_metrics = pango_context_get_metrics (
1554 pango_context, font_desc,
1555 pango_context_get_language (pango_context));
1556 if (!font_desc)
1557 font_desc = pango_context_get_font_description (pango_context);
1558 font_desc = pango_font_description_copy (font_desc);
1560 char_height =
1561 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
1562 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
1564 min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
1565 + E_CALENDAR_ITEM_MIN_CELL_XPAD;
1566 min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;
1568 layout = gtk_widget_create_pango_layout (GTK_WIDGET (widget), NULL);
1570 /* Calculate the number of days in the previous, current, and next
1571 * months. */
1572 years[0] = years[1] = years[2] = year;
1573 months[0] = month - 1;
1574 months[1] = month;
1575 months[2] = month + 1;
1576 if (months[0] == -1) {
1577 months[0] = 11;
1578 years[0]--;
1580 if (months[2] == 12) {
1581 months[2] = 0;
1582 years[2]++;
1585 days_in_month[0] = DAYS_IN_MONTH (years[0], months[0]);
1586 days_in_month[1] = DAYS_IN_MONTH (years[1], months[1]);
1587 days_in_month[2] = DAYS_IN_MONTH (years[2], months[2]);
1589 /* Mon 0 is the previous month, which we may show the end of. Mon 1 is
1590 * the current month, and mon 2 is the next month. */
1591 mon = 0;
1593 month_offset = row * calitem->cols + col - 1;
1594 day_num = days_in_month[0];
1595 days_from_week_start = e_weekday_get_days_between (
1596 calitem->week_start_day, start_weekday);
1597 /* For the top-left month we show the end of the previous month, and
1598 * if the new month starts on the first day of the week we show a
1599 * complete week from the previous month. */
1600 if (days_from_week_start == 0) {
1601 if (row == 0 && col == 0) {
1602 day_num -= 6;
1603 } else {
1604 mon++;
1605 month_offset++;
1606 day_num = 1;
1608 } else {
1609 day_num -= days_from_week_start - 1;
1612 /* Get today's date, so we can highlight it. */
1613 if (calitem->time_callback) {
1614 today_tm = calitem->time_callback (
1615 calitem, calitem->time_callback_data);
1616 } else {
1617 t = time (NULL);
1618 today_tm = *localtime (&t);
1620 today_year = today_tm.tm_year + 1900;
1621 today_month = today_tm.tm_mon;
1622 today_mday = today_tm.tm_mday;
1624 /* We usually skip the last days of the previous month (mon = 0),
1625 * except for the top-left month displayed. */
1626 draw_day = (mon == 1 || (row == 0 && col == 0));
1628 for (drow = 0; drow < 6; drow++) {
1629 /* Draw the week number. */
1630 if (calitem->show_week_numbers) {
1631 week_num = e_calendar_item_get_week_number (
1632 calitem, day_num, months[mon], years[mon]);
1634 text_x = cells_x - E_CALENDAR_ITEM_XPAD_BEFORE_CELLS - 1
1635 - E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS;
1636 text_y = cells_y + drow * calitem->cell_height +
1637 + (calitem->cell_height - min_cell_height + 1) / 2;
1639 num_chars = 0;
1640 if (week_num >= 10) {
1641 digit = week_num / 10;
1642 text_x -= calitem->week_number_digit_widths[digit];
1643 num_chars += sprintf (
1644 &buffer[num_chars],
1645 get_digit_fomat (), digit);
1648 digit = week_num % 10;
1649 text_x -= calitem->week_number_digit_widths[digit] + 6;
1650 num_chars += sprintf (
1651 &buffer[num_chars],
1652 get_digit_fomat (), digit);
1654 cairo_save (cr);
1655 e_utils_get_theme_color (widget, "theme_text_color,theme_fg_color", E_UTILS_DEFAULT_THEME_TEXT_COLOR, &rgba);
1656 gdk_cairo_set_source_rgba (cr, &rgba);
1657 pango_layout_set_font_description (layout, font_desc);
1658 pango_layout_set_text (layout, buffer, num_chars);
1659 cairo_move_to (cr, text_x, text_y);
1660 pango_cairo_update_layout (cr, layout);
1661 pango_cairo_show_layout (cr, layout);
1662 cairo_restore (cr);
1665 for (dcol = 0; dcol < 7; dcol++) {
1666 if (draw_day) {
1667 GdkColor local_bg_color, local_fg_color;
1669 day_x = cells_x +
1670 ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1671 ? 7 - 1 - dcol : dcol) * calitem->cell_width;
1673 day_y = cells_y + drow * calitem->cell_height;
1675 today = years[mon] == today_year
1676 && months[mon] == today_month
1677 && day_num == today_mday;
1679 selected = calitem->selection_set
1680 && (calitem->selection_start_month_offset < month_offset
1681 || (calitem->selection_start_month_offset == month_offset
1682 && calitem->selection_start_day <= day_num))
1683 && (calitem->selection_end_month_offset > month_offset
1684 || (calitem->selection_end_month_offset == month_offset
1685 && calitem->selection_end_day >= day_num));
1687 if (calitem->styles)
1688 day_style = calitem->styles[(month_offset + 1) * 32 + day_num];
1690 /* Get the colors & style to use for the day.*/
1691 if ((gtk_widget_has_focus (GTK_WIDGET (item->canvas))) &&
1692 item->canvas->focused_item == item)
1693 has_focus = TRUE;
1694 else
1695 has_focus = FALSE;
1697 bold = FALSE;
1698 italic = FALSE;
1700 if (calitem->style_callback)
1701 calitem->style_callback (
1702 calitem,
1703 years[mon],
1704 months[mon],
1705 day_num,
1706 day_style,
1707 today,
1708 mon != 1,
1709 selected,
1710 has_focus,
1711 drop_target,
1712 &bg_color,
1713 &fg_color,
1714 &box_color,
1715 &bold,
1716 &italic,
1717 calitem->style_callback_data);
1718 else
1719 e_calendar_item_get_day_style (
1720 calitem,
1721 years[mon],
1722 months[mon],
1723 day_num,
1724 day_style,
1725 today,
1726 mon != 1,
1727 selected,
1728 has_focus,
1729 drop_target,
1730 &bg_color,
1731 &fg_color,
1732 &box_color,
1733 &bold,
1734 &italic,
1735 &local_bg_color,
1736 &local_fg_color);
1738 /* Draw the background, if set. */
1739 if (bg_color) {
1740 cairo_save (cr);
1741 gdk_cairo_set_source_color (cr, bg_color);
1742 cairo_rectangle (
1743 cr, day_x , day_y,
1744 calitem->cell_width,
1745 calitem->cell_height);
1746 cairo_fill (cr);
1747 cairo_restore (cr);
1750 /* Draw the box, if set. */
1751 if (box_color) {
1752 cairo_save (cr);
1753 gdk_cairo_set_source_color (cr, box_color);
1754 cairo_rectangle (
1755 cr, day_x , day_y,
1756 calitem->cell_width - 1,
1757 calitem->cell_height - 1);
1758 cairo_stroke (cr);
1759 cairo_restore (cr);
1762 /* Draw the 1- or 2-digit day number. */
1763 day_x += calitem->cell_width -
1764 (calitem->cell_width -
1765 min_cell_width) / 2;
1766 day_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
1767 day_y += (calitem->cell_height - min_cell_height + 1) / 2;
1768 day_y += E_CALENDAR_ITEM_MIN_CELL_YPAD / 2;
1770 num_chars = 0;
1771 if (day_num >= 10) {
1772 digit = day_num / 10;
1773 day_x -= calitem->digit_widths[digit];
1774 num_chars += sprintf (
1775 &buffer[num_chars],
1776 get_digit_fomat (), digit);
1779 digit = day_num % 10;
1780 day_x -= calitem->digit_widths[digit];
1781 num_chars += sprintf (
1782 &buffer[num_chars],
1783 get_digit_fomat (), digit);
1785 cairo_save (cr);
1786 if (fg_color) {
1787 gdk_cairo_set_source_color (
1788 cr, fg_color);
1789 } else {
1790 e_utils_get_theme_color (widget, "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &rgba);
1791 gdk_cairo_set_source_rgba (cr, &rgba);
1794 if (bold) {
1795 pango_font_description_set_weight (
1796 font_desc, PANGO_WEIGHT_BOLD);
1797 } else {
1798 pango_font_description_set_weight (
1799 font_desc, PANGO_WEIGHT_NORMAL);
1802 if (italic) {
1803 pango_font_description_set_style (
1804 font_desc, PANGO_STYLE_ITALIC);
1805 } else {
1806 pango_font_description_set_style (
1807 font_desc, PANGO_STYLE_NORMAL);
1810 pango_layout_set_font_description (layout, font_desc);
1811 pango_layout_set_text (layout, buffer, num_chars);
1812 cairo_move_to (cr, day_x, day_y);
1813 pango_cairo_update_layout (cr, layout);
1814 pango_cairo_show_layout (cr, layout);
1815 cairo_restore (cr);
1818 /* See if we've reached the end of a month. */
1819 if (day_num == days_in_month[mon]) {
1820 month_offset++;
1821 mon++;
1822 /* We only draw the start of the next month
1823 * for the bottom-right month displayed. */
1824 if (mon == 2 && (row != calitem->rows - 1
1825 || col != calitem->cols - 1)) {
1826 /* Set a flag so we exit the loop. */
1827 finished = TRUE;
1828 break;
1830 day_num = 1;
1831 draw_day = TRUE;
1832 } else {
1833 day_num++;
1837 /* Exit the loop if the flag is set. */
1838 if (finished)
1839 break;
1842 /* Reset pango weight and style */
1843 pango_font_description_set_weight (font_desc, PANGO_WEIGHT_NORMAL);
1844 pango_font_description_set_style (font_desc, PANGO_STYLE_NORMAL);
1846 g_object_unref (layout);
1848 pango_font_metrics_unref (font_metrics);
1849 pango_font_description_free (font_desc);
1852 gint
1853 e_calendar_item_get_week_number (ECalendarItem *calitem,
1854 gint day,
1855 gint month,
1856 gint year)
1858 GDate date;
1859 GDateWeekday weekday;
1860 guint yearday;
1861 gint week_num;
1863 g_date_clear (&date, 1);
1864 g_date_set_dmy (&date, day, month + 1, year);
1866 weekday = g_date_get_weekday (&date);
1868 if (g_date_valid_weekday (weekday)) {
1869 guint days_between;
1871 /* We want always point to nearest Monday as the first day
1872 * of the week regardless of the calendar's week_start_day. */
1873 if (weekday >= G_DATE_THURSDAY) {
1874 days_between = e_weekday_get_days_between (
1875 weekday, G_DATE_MONDAY);
1876 g_date_add_days (&date, days_between);
1877 } else {
1878 days_between = e_weekday_get_days_between (
1879 G_DATE_MONDAY, weekday);
1880 g_date_subtract_days (&date, days_between);
1884 /* Calculate the day of the year, from 0 to 365. */
1885 yearday = g_date_get_day_of_year (&date) - 1;
1887 /* If the week starts on or after 29th December, it is week 1 of the
1888 * next year, since there are 4 days in the next year. */
1889 if (g_date_get_month (&date) == 12 && g_date_get_day (&date) >= 29)
1890 return 1;
1892 /* Calculate the week number, from 0. */
1893 week_num = yearday / 7;
1895 /* If the first week starts on or after Jan 5th, then we need to add
1896 * 1 since the previous week will really be the first week. */
1897 if (yearday % 7 >= 4)
1898 week_num++;
1900 /* Add 1 so week numbers are from 1 to 53. */
1901 return week_num + 1;
1904 /* This is supposed to return the nearest item the the point and the distance.
1905 * Since we are the only item we just return ourself and 0 for the distance.
1906 * This is needed so that we get button/motion events. */
1907 static GnomeCanvasItem *
1908 e_calendar_item_point (GnomeCanvasItem *item,
1909 gdouble x,
1910 gdouble y,
1911 gint cx,
1912 gint cy)
1914 return item;
1917 static void
1918 e_calendar_item_stop_selecting (ECalendarItem *calitem,
1919 guint32 time)
1921 if (!calitem->selecting)
1922 return;
1924 gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (calitem), time);
1926 calitem->selecting = FALSE;
1928 /* If the user selects the grayed dates before the first month or
1929 * after the last month, we move backwards or forwards one month.
1930 * The set_month () call should take care of updating the selection. */
1931 if (calitem->selection_end_month_offset == -1)
1932 e_calendar_item_set_first_month_with_emit (
1933 calitem, calitem->year,
1934 calitem->month - 1, FALSE);
1935 else if (calitem->selection_start_month_offset == calitem->rows * calitem->cols)
1936 e_calendar_item_set_first_month_with_emit (
1937 calitem, calitem->year,
1938 calitem->month + 1, FALSE);
1940 calitem->selection_changed = TRUE;
1941 if (calitem->selecting_axis) {
1942 g_free (calitem->selecting_axis);
1943 calitem->selecting_axis = NULL;
1946 e_calendar_item_queue_signal_emission (calitem);
1947 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
1950 static void
1951 e_calendar_item_selection_add_days (ECalendarItem *calitem,
1952 gint n_days,
1953 gboolean multi_selection)
1955 GDate gdate_start, gdate_end;
1957 g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
1959 if (!e_calendar_item_get_selection (calitem, &gdate_start, &gdate_end)) {
1960 /* We set the date to the first day of the month */
1961 g_date_set_dmy (&gdate_start, 1, calitem->month + 1, calitem->year);
1962 gdate_end = gdate_start;
1965 if (multi_selection && calitem->max_days_selected > 1) {
1966 gint days_between;
1968 days_between = g_date_days_between (&gdate_start, &gdate_end);
1969 if (!calitem->selecting_axis) {
1970 calitem->selecting_axis = g_new (GDate, 1);
1971 *(calitem->selecting_axis) = gdate_start;
1973 if ((days_between != 0 &&
1974 g_date_compare (calitem->selecting_axis, &gdate_end) == 0) ||
1975 (days_between == 0 && n_days < 0)) {
1976 if (days_between - n_days > calitem->max_days_selected - 1)
1977 n_days = days_between + 1 - calitem->max_days_selected;
1978 g_date_add_days (&gdate_start, n_days);
1980 else {
1981 if (days_between + n_days > calitem->max_days_selected - 1)
1982 n_days = calitem->max_days_selected - 1 - days_between;
1983 g_date_add_days (&gdate_end, n_days);
1986 if (g_date_compare (&gdate_end, &gdate_start) < 0) {
1987 GDate tmp_date;
1988 tmp_date = gdate_start;
1989 gdate_start = gdate_end;
1990 gdate_end = tmp_date;
1993 else {
1994 /* clear "selecting_axis", it is only for mulit-selecting */
1995 if (calitem->selecting_axis) {
1996 g_free (calitem->selecting_axis);
1997 calitem->selecting_axis = NULL;
1999 g_date_add_days (&gdate_start, n_days);
2000 gdate_end = gdate_start;
2003 calitem->selecting = TRUE;
2005 e_calendar_item_set_selection_if_emission (
2006 calitem, &gdate_start, &gdate_end, FALSE);
2008 g_signal_emit_by_name (calitem, "selection_preview_changed");
2011 static gint
2012 e_calendar_item_key_press_event (ECalendarItem *calitem,
2013 GdkEvent *event)
2015 guint keyval = event->key.keyval;
2016 gboolean is_rtl;
2017 gboolean multi_selection;
2019 if (event->key.state & GDK_CONTROL_MASK ||
2020 event->key.state & GDK_MOD1_MASK)
2021 return FALSE;
2023 is_rtl = gtk_widget_get_direction (GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas)) == GTK_TEXT_DIR_RTL;
2024 multi_selection = event->key.state & GDK_SHIFT_MASK;
2026 switch (keyval) {
2027 case GDK_KEY_Up:
2028 e_calendar_item_selection_add_days (
2029 calitem, -7,
2030 multi_selection);
2031 break;
2032 case GDK_KEY_Down:
2033 e_calendar_item_selection_add_days (
2034 calitem, 7,
2035 multi_selection);
2036 break;
2037 case GDK_KEY_Left:
2038 e_calendar_item_selection_add_days (
2039 calitem, is_rtl ? 1 : -1,
2040 multi_selection);
2041 break;
2042 case GDK_KEY_Right:
2043 e_calendar_item_selection_add_days (
2044 calitem, is_rtl ? -1 : 1,
2045 multi_selection);
2046 break;
2047 case GDK_KEY_space:
2048 case GDK_KEY_Return:
2049 e_calendar_item_stop_selecting (calitem, event->key.time);
2050 break;
2051 default:
2052 return FALSE;
2054 return TRUE;
2057 static gint
2058 e_calendar_item_event (GnomeCanvasItem *item,
2059 GdkEvent *event)
2061 ECalendarItem *calitem;
2063 calitem = E_CALENDAR_ITEM (item);
2065 switch (event->type) {
2066 case GDK_BUTTON_PRESS:
2067 return e_calendar_item_button_press (calitem, event);
2068 case GDK_BUTTON_RELEASE:
2069 return e_calendar_item_button_release (calitem, event);
2070 case GDK_MOTION_NOTIFY:
2071 return e_calendar_item_motion (calitem, event);
2072 case GDK_FOCUS_CHANGE:
2073 gnome_canvas_item_request_update (item);
2074 return FALSE;
2075 case GDK_KEY_PRESS:
2076 return e_calendar_item_key_press_event (calitem, event);
2077 default:
2078 break;
2081 return FALSE;
2084 static void
2085 e_calendar_item_bounds (GnomeCanvasItem *item,
2086 gdouble *x1,
2087 gdouble *y1,
2088 gdouble *x2,
2089 gdouble *y2)
2091 ECalendarItem *calitem;
2093 g_return_if_fail (E_IS_CALENDAR_ITEM (item));
2095 calitem = E_CALENDAR_ITEM (item);
2096 *x1 = calitem->x1;
2097 *y1 = calitem->y1;
2098 *x2 = calitem->x2;
2099 *y2 = calitem->y2;
2102 /* This checks if any fonts have changed, and if so it recalculates the
2103 * text sizes and the minimum month size. */
2104 static void
2105 e_calendar_item_recalc_sizes (ECalendarItem *calitem)
2107 GnomeCanvasItem *canvas_item;
2108 gint max_day_width, digit, max_digit_width, max_week_number_digit_width;
2109 gint char_height, width, min_cell_width, min_cell_height;
2110 gchar buffer[64];
2111 struct tm tmp_tm;
2112 PangoFontDescription *font_desc, *wkfont_desc;
2113 PangoContext *pango_context;
2114 PangoFontMetrics *font_metrics;
2115 PangoLayout *layout;
2116 GDateWeekday weekday;
2117 GtkWidget *widget;
2118 GtkStyleContext *style_context;
2119 GtkBorder padding;
2121 canvas_item = GNOME_CANVAS_ITEM (calitem);
2122 widget = GTK_WIDGET (canvas_item->canvas);
2123 style_context = gtk_widget_get_style_context (widget);
2124 gtk_style_context_get_padding (style_context, gtk_style_context_get_state (style_context), &padding);
2126 /* Set up Pango prerequisites */
2127 font_desc = calitem->font_desc;
2128 wkfont_desc = calitem->week_number_font_desc;
2130 pango_context = gtk_widget_create_pango_context (
2131 GTK_WIDGET (canvas_item->canvas));
2132 font_metrics = pango_context_get_metrics (
2133 pango_context, font_desc,
2134 pango_context_get_language (pango_context));
2135 if (!font_desc)
2136 font_desc = pango_context_get_font_description (pango_context);
2137 font_desc = pango_font_description_copy (font_desc);
2138 layout = pango_layout_new (pango_context);
2140 char_height =
2141 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
2142 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
2144 max_day_width = 0;
2145 for (weekday = G_DATE_MONDAY; weekday <= G_DATE_SUNDAY; weekday++) {
2146 layout_set_day_text (calitem, layout, weekday);
2147 pango_layout_get_pixel_size (layout, &width, NULL);
2149 calitem->day_widths[weekday] = width;
2150 max_day_width = MAX (max_day_width, width);
2152 calitem->max_day_width = max_day_width;
2154 max_digit_width = 0;
2155 max_week_number_digit_width = 0;
2156 for (digit = 0; digit < 10; digit++) {
2157 gchar locale_digit[5];
2158 gint locale_digit_len;
2160 locale_digit_len = sprintf (locale_digit, get_digit_fomat (), digit);
2162 pango_layout_set_text (layout, locale_digit, locale_digit_len);
2163 pango_layout_get_pixel_size (layout, &width, NULL);
2165 calitem->digit_widths[digit] = width;
2166 max_digit_width = MAX (max_digit_width, width);
2168 if (wkfont_desc) {
2169 pango_context_set_font_description (pango_context, wkfont_desc);
2170 pango_layout_context_changed (layout);
2172 pango_layout_set_text (layout, locale_digit, locale_digit_len);
2173 pango_layout_get_pixel_size (layout, &width, NULL);
2175 calitem->week_number_digit_widths[digit] = width;
2176 max_week_number_digit_width = MAX (max_week_number_digit_width, width);
2178 pango_context_set_font_description (pango_context, font_desc);
2179 pango_layout_context_changed (layout);
2180 } else {
2181 calitem->week_number_digit_widths[digit] = width;
2182 max_week_number_digit_width = max_digit_width;
2185 calitem->max_digit_width = max_digit_width;
2186 calitem->max_week_number_digit_width = max_week_number_digit_width;
2188 min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
2189 + E_CALENDAR_ITEM_MIN_CELL_XPAD;
2190 min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;
2192 calitem->min_month_width = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS
2193 + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS + min_cell_width * 7
2194 + E_CALENDAR_ITEM_XPAD_AFTER_CELLS;
2195 if (calitem->show_week_numbers) {
2196 calitem->min_month_width += calitem->max_week_number_digit_width * 2
2197 + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
2200 calitem->min_month_height = padding.top * 2
2201 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height
2202 + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + 1
2203 + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS
2204 + char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
2205 + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS + min_cell_height * 6
2206 + E_CALENDAR_ITEM_YPAD_BELOW_CELLS;
2208 calitem->max_month_name_width = 50;
2209 memset (&tmp_tm, 0, sizeof (tmp_tm));
2210 tmp_tm.tm_year = 2000 - 100;
2211 tmp_tm.tm_mday = 1;
2212 tmp_tm.tm_isdst = -1;
2213 for (tmp_tm.tm_mon = 0; tmp_tm.tm_mon < 12; tmp_tm.tm_mon++) {
2214 mktime (&tmp_tm);
2216 e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);
2218 pango_layout_set_text (layout, buffer, -1);
2219 pango_layout_get_pixel_size (layout, &width, NULL);
2221 if (width > calitem->max_month_name_width)
2222 calitem->max_month_name_width = width;
2225 g_object_unref (layout);
2226 g_object_unref (pango_context);
2227 pango_font_metrics_unref (font_metrics);
2228 pango_font_description_free (font_desc);
2231 static void
2232 e_calendar_item_get_day_style (ECalendarItem *calitem,
2233 gint year,
2234 gint month,
2235 gint day,
2236 gint day_style,
2237 gboolean today,
2238 gboolean prev_or_next_month,
2239 gboolean selected,
2240 gboolean has_focus,
2241 gboolean drop_target,
2242 GdkColor **bg_color,
2243 GdkColor **fg_color,
2244 GdkColor **box_color,
2245 gboolean *bold,
2246 gboolean *italic,
2247 GdkColor *local_bg_color,
2248 GdkColor *local_fg_color)
2250 GtkWidget *widget;
2252 widget = GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas);
2254 *bg_color = NULL;
2255 *fg_color = NULL;
2256 *box_color = NULL;
2258 *bold = (day_style & E_CALENDAR_ITEM_MARK_BOLD) ==
2259 E_CALENDAR_ITEM_MARK_BOLD;
2260 *italic = (day_style & E_CALENDAR_ITEM_MARK_ITALIC) ==
2261 E_CALENDAR_ITEM_MARK_ITALIC;
2263 if (today)
2264 *box_color = &calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX];
2266 if (prev_or_next_month) {
2267 *fg_color = local_fg_color;
2268 e_utils_get_theme_color_color (widget, "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, local_fg_color);
2271 if (selected) {
2272 *bg_color = local_bg_color;
2273 *fg_color = local_fg_color;
2275 if (has_focus) {
2276 e_utils_get_theme_color_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, local_bg_color);
2277 e_utils_get_theme_color_color (widget, "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, local_fg_color);
2278 } else {
2279 GdkColor base_bg;
2281 e_utils_get_theme_color_color (widget, "theme_unfocused_selected_bg_color,theme_selected_bg_color", E_UTILS_DEFAULT_THEME_UNFOCUSED_SELECTED_BG_COLOR, local_bg_color);
2282 e_utils_get_theme_color_color (widget, "theme_unfocused_selected_fg_color,theme_selected_fg_color", E_UTILS_DEFAULT_THEME_UNFOCUSED_SELECTED_FG_COLOR, local_fg_color);
2284 e_utils_get_theme_color_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &base_bg);
2286 if (local_bg_color->red == base_bg.red &&
2287 local_bg_color->green == base_bg.green &&
2288 local_bg_color->blue == base_bg.blue) {
2289 e_utils_get_theme_color_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, local_bg_color);
2290 e_utils_get_theme_color_color (widget, "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, local_fg_color);
2296 static gboolean
2297 e_calendar_item_button_press (ECalendarItem *calitem,
2298 GdkEvent *button_event)
2300 GdkGrabStatus grab_status;
2301 GdkDevice *event_device;
2302 guint event_button = 0;
2303 guint32 event_time;
2304 gdouble event_x_win = 0;
2305 gdouble event_y_win = 0;
2306 gint month_offset, day, add_days = 0;
2307 gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;
2309 gdk_event_get_button (button_event, &event_button);
2310 gdk_event_get_coords (button_event, &event_x_win, &event_y_win);
2311 event_device = gdk_event_get_device (button_event);
2312 event_time = gdk_event_get_time (button_event);
2314 if (event_button == 4)
2315 e_calendar_item_set_first_month_with_emit (
2316 calitem, calitem->year,
2317 calitem->month - 1, TRUE);
2318 else if (event_button == 5)
2319 e_calendar_item_set_first_month_with_emit (
2320 calitem, calitem->year,
2321 calitem->month + 1, TRUE);
2323 if (!e_calendar_item_convert_position_to_day (calitem,
2324 event_x_win,
2325 event_y_win,
2326 TRUE,
2327 &month_offset, &day,
2328 &all_week))
2329 return FALSE;
2331 if (event_button == 3 && day == -1
2332 && e_calendar_item_get_display_popup (calitem)) {
2333 e_calendar_item_show_popup_menu (
2334 calitem, button_event, month_offset);
2335 return TRUE;
2338 if (event_button != 1 || day == -1)
2339 return FALSE;
2341 if (calitem->max_days_selected < 1)
2342 return TRUE;
2344 grab_status = gnome_canvas_item_grab (
2345 GNOME_CANVAS_ITEM (calitem),
2346 GDK_POINTER_MOTION_MASK |
2347 GDK_BUTTON_RELEASE_MASK,
2348 NULL,
2349 event_device,
2350 event_time);
2352 if (grab_status != GDK_GRAB_SUCCESS)
2353 return FALSE;
2355 if (all_week && calitem->keep_wdays_on_weeknum_click) {
2356 gint tmp_start_moff, tmp_start_day;
2358 tmp_start_moff = calitem->selection_start_month_offset;
2359 tmp_start_day = calitem->selection_start_day;
2360 e_calendar_item_round_down_selection (
2361 calitem, &tmp_start_moff, &tmp_start_day);
2363 e_calendar_item_round_down_selection (calitem, &month_offset, &day);
2364 month_offset += calitem->selection_start_month_offset - tmp_start_moff;
2365 day += calitem->selection_start_day - tmp_start_day;
2367 /* keep same count of days selected */
2368 add_days = e_calendar_item_get_inclusive_days (
2369 calitem,
2370 calitem->selection_start_month_offset,
2371 calitem->selection_start_day,
2372 calitem->selection_end_month_offset,
2373 calitem->selection_end_day) - 1;
2376 calitem->selection_set = TRUE;
2377 calitem->selection_start_month_offset = month_offset;
2378 calitem->selection_start_day = day;
2379 calitem->selection_end_month_offset = month_offset;
2380 calitem->selection_end_day = day;
2382 if (add_days > 0)
2383 e_calendar_item_add_days_to_selection (calitem, add_days);
2385 calitem->selection_real_start_month_offset = month_offset;
2386 calitem->selection_real_start_day = day;
2388 calitem->selection_from_full_week = FALSE;
2389 calitem->selecting = TRUE;
2390 calitem->selection_dragging_end = TRUE;
2392 if (all_week && !calitem->keep_wdays_on_weeknum_click) {
2393 calitem->selection_from_full_week = TRUE;
2394 round_up_end = TRUE;
2397 if (calitem->days_to_start_week_selection == 1) {
2398 round_down_start = TRUE;
2399 round_up_end = TRUE;
2402 /* Don't round up or down if we can't select a week or more,
2403 * or when keeping week days. */
2404 if (calitem->max_days_selected < 7 ||
2405 (all_week && calitem->keep_wdays_on_weeknum_click)) {
2406 round_down_start = FALSE;
2407 round_up_end = FALSE;
2410 if (round_up_end)
2411 e_calendar_item_round_up_selection (
2412 calitem, &calitem->selection_end_month_offset,
2413 &calitem->selection_end_day);
2415 if (round_down_start)
2416 e_calendar_item_round_down_selection (
2417 calitem, &calitem->selection_start_month_offset,
2418 &calitem->selection_start_day);
2420 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
2422 return TRUE;
2425 static gboolean
2426 e_calendar_item_button_release (ECalendarItem *calitem,
2427 GdkEvent *button_event)
2429 guint32 event_time;
2431 event_time = gdk_event_get_time (button_event);
2432 e_calendar_item_stop_selecting (calitem, event_time);
2434 return FALSE;
2437 static gboolean
2438 e_calendar_item_motion (ECalendarItem *calitem,
2439 GdkEvent *event)
2441 gint start_month, start_day, end_month, end_day, month_offset, day;
2442 gint tmp_month, tmp_day, days_in_selection;
2443 gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;
2445 if (!calitem->selecting)
2446 return FALSE;
2448 if (!e_calendar_item_convert_position_to_day (calitem,
2449 event->button.x,
2450 event->button.y,
2451 TRUE,
2452 &month_offset, &day,
2453 &all_week))
2454 return FALSE;
2456 if (day == -1)
2457 return FALSE;
2459 if (calitem->selection_dragging_end) {
2460 start_month = calitem->selection_real_start_month_offset;
2461 start_day = calitem->selection_real_start_day;
2462 end_month = month_offset;
2463 end_day = day;
2464 } else {
2465 start_month = month_offset;
2466 start_day = day;
2467 end_month = calitem->selection_real_start_month_offset;
2468 end_day = calitem->selection_real_start_day;
2471 if (start_month > end_month || (start_month == end_month
2472 && start_day > end_day)) {
2473 tmp_month = start_month;
2474 tmp_day = start_day;
2475 start_month = end_month;
2476 start_day = end_day;
2477 end_month = tmp_month;
2478 end_day = tmp_day;
2480 calitem->selection_dragging_end =
2481 !calitem->selection_dragging_end;
2484 if (calitem->days_to_start_week_selection > 0) {
2485 days_in_selection = e_calendar_item_get_inclusive_days (
2486 calitem, start_month, start_day, end_month, end_day);
2487 if (days_in_selection >= calitem->days_to_start_week_selection) {
2488 round_down_start = TRUE;
2489 round_up_end = TRUE;
2493 /* If we are over a week number and we are dragging the end of the
2494 * selection, we round up to the end of this week. */
2495 if (all_week && calitem->selection_dragging_end)
2496 round_up_end = TRUE;
2498 /* If the selection was started from a week number and we are dragging
2499 * the start of the selection, we need to round up the end to include
2500 * all of the original week selected. */
2501 if (calitem->selection_from_full_week
2502 && !calitem->selection_dragging_end)
2503 round_up_end = TRUE;
2505 /* Don't round up or down if we can't select a week or more. */
2506 if (calitem->max_days_selected < 7) {
2507 round_down_start = FALSE;
2508 round_up_end = FALSE;
2511 if (round_up_end)
2512 e_calendar_item_round_up_selection (
2513 calitem, &end_month,
2514 &end_day);
2515 if (round_down_start)
2516 e_calendar_item_round_down_selection (
2517 calitem, &start_month,
2518 &start_day);
2520 /* Check we don't go over the maximum number of days to select. */
2521 if (calitem->selection_dragging_end) {
2522 e_calendar_item_check_selection_end (
2523 calitem,
2524 start_month,
2525 start_day,
2526 &end_month,
2527 &end_day);
2528 } else {
2529 e_calendar_item_check_selection_start (
2530 calitem,
2531 &start_month,
2532 &start_day,
2533 end_month,
2534 end_day);
2537 if (start_month == calitem->selection_start_month_offset
2538 && start_day == calitem->selection_start_day
2539 && end_month == calitem->selection_end_month_offset
2540 && end_day == calitem->selection_end_day)
2541 return FALSE;
2543 calitem->selection_start_month_offset = start_month;
2544 calitem->selection_start_day = start_day;
2545 calitem->selection_end_month_offset = end_month;
2546 calitem->selection_end_day = end_day;
2548 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
2550 return TRUE;
2553 static void
2554 e_calendar_item_check_selection_end (ECalendarItem *calitem,
2555 gint start_month,
2556 gint start_day,
2557 gint *end_month,
2558 gint *end_day)
2560 gint year, month, max_month, max_day, days_in_month;
2562 if (calitem->max_days_selected <= 0)
2563 return;
2565 year = calitem->year;
2566 month = calitem->month + start_month;
2567 e_calendar_item_normalize_date (calitem, &year, &month);
2569 max_month = start_month;
2570 max_day = start_day + calitem->max_days_selected - 1;
2572 for (;;) {
2573 days_in_month = DAYS_IN_MONTH (year, month);
2574 if (max_day <= days_in_month)
2575 break;
2576 max_month++;
2577 month++;
2578 if (month == 12) {
2579 year++;
2580 month = 0;
2582 max_day -= days_in_month;
2585 if (*end_month > max_month) {
2586 *end_month = max_month;
2587 *end_day = max_day;
2588 } else if (*end_month == max_month && *end_day > max_day) {
2589 *end_day = max_day;
2593 static void
2594 e_calendar_item_check_selection_start (ECalendarItem *calitem,
2595 gint *start_month,
2596 gint *start_day,
2597 gint end_month,
2598 gint end_day)
2600 gint year, month, min_month, min_day, days_in_month;
2602 if (calitem->max_days_selected <= 0)
2603 return;
2605 year = calitem->year;
2606 month = calitem->month + end_month;
2607 e_calendar_item_normalize_date (calitem, &year, &month);
2609 min_month = end_month;
2610 min_day = end_day - calitem->max_days_selected + 1;
2612 while (min_day <= 0) {
2613 min_month--;
2614 month--;
2615 if (month == -1) {
2616 year--;
2617 month = 11;
2619 days_in_month = DAYS_IN_MONTH (year, month);
2620 min_day += days_in_month;
2623 if (*start_month < min_month) {
2624 *start_month = min_month;
2625 *start_day = min_day;
2626 } else if (*start_month == min_month && *start_day < min_day) {
2627 *start_day = min_day;
2631 /* Converts a position within the item to a month & day.
2632 * The month returned is 0 for the top-left month displayed.
2633 * If the position is over the month heading -1 is returned for the day.
2634 * If the position is over a week number the first day of the week is returned
2635 * and entire_week is set to TRUE.
2636 * It returns FALSE if the position is completely outside all months. */
2637 static gboolean
2638 e_calendar_item_convert_position_to_day (ECalendarItem *calitem,
2639 gint event_x,
2640 gint event_y,
2641 gboolean round_empty_positions,
2642 gint *month_offset,
2643 gint *day,
2644 gboolean *entire_week)
2646 GnomeCanvasItem *item;
2647 GtkWidget *widget;
2648 GtkStyleContext *style_context;
2649 GtkBorder padding;
2650 gint xthickness, ythickness, char_height;
2651 gint x, y, row, col, cells_x, cells_y, day_row, day_col;
2652 gint first_day_offset, days_in_month, days_in_prev_month;
2653 gint week_num_x1, week_num_x2;
2654 PangoContext *pango_context;
2655 PangoFontMetrics *font_metrics;
2657 item = GNOME_CANVAS_ITEM (calitem);
2658 widget = GTK_WIDGET (item->canvas);
2659 style_context = gtk_widget_get_style_context (widget);
2660 gtk_style_context_get_padding (style_context, gtk_style_context_get_state (style_context), &padding);
2662 pango_context = gtk_widget_create_pango_context (widget);
2663 font_metrics = pango_context_get_metrics (
2664 pango_context, calitem->font_desc,
2665 pango_context_get_language (pango_context));
2667 char_height =
2668 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
2669 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
2670 xthickness = padding.left;
2671 ythickness = padding.top;
2673 pango_font_metrics_unref (font_metrics);
2674 g_object_unref (pango_context);
2676 *entire_week = FALSE;
2678 x = event_x - xthickness - calitem->x_offset;
2679 y = event_y - ythickness;
2681 if (x < 0 || y < 0)
2682 return FALSE;
2684 row = y / calitem->month_height;
2685 col = x / calitem->month_width;
2687 if (row >= calitem->rows || col >= calitem->cols)
2688 return FALSE;
2689 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
2690 col = calitem->cols - 1 - col;
2692 *month_offset = row * calitem->cols + col;
2694 x = x % calitem->month_width;
2695 y = y % calitem->month_height;
2697 if (y < ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
2698 + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME) {
2699 *day = -1;
2700 return TRUE;
2703 cells_y = ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
2704 + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
2705 + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad
2706 + char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
2707 + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
2708 y -= cells_y;
2709 if (y < 0)
2710 return FALSE;
2711 day_row = y / calitem->cell_height;
2712 if (day_row >= E_CALENDAR_ROWS_PER_MONTH)
2713 return FALSE;
2715 week_num_x1 = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad;
2717 if (calitem->show_week_numbers) {
2718 week_num_x2 = week_num_x1
2719 + calitem->max_week_number_digit_width * 2;
2720 if (x >= week_num_x1 && x < week_num_x2)
2721 *entire_week = TRUE;
2722 cells_x = week_num_x2 + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
2723 } else {
2724 cells_x = week_num_x1;
2727 if (*entire_week) {
2728 day_col = 0;
2729 } else {
2730 cells_x += E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
2731 x -= cells_x;
2732 if (x < 0)
2733 return FALSE;
2734 day_col = x / calitem->cell_width;
2735 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
2736 day_col = E_CALENDAR_COLS_PER_MONTH - 1 - day_col;
2737 if (day_col >= E_CALENDAR_COLS_PER_MONTH)
2738 return FALSE;
2741 *day = day_row * E_CALENDAR_COLS_PER_MONTH + day_col;
2743 e_calendar_item_get_month_info (
2744 calitem, row, col, &first_day_offset,
2745 &days_in_month, &days_in_prev_month);
2746 if (*day < first_day_offset) {
2747 if (*entire_week || (row == 0 && col == 0)) {
2748 (*month_offset)--;
2749 *day = days_in_prev_month + 1 - first_day_offset
2750 + *day;
2751 return TRUE;
2752 } else if (round_empty_positions) {
2753 *day = first_day_offset;
2754 } else {
2755 return FALSE;
2759 *day -= first_day_offset - 1;
2761 if (*day > days_in_month) {
2762 if (row == calitem->rows - 1 && col == calitem->cols - 1) {
2763 (*month_offset)++;
2764 *day -= days_in_month;
2765 return TRUE;
2766 } else if (round_empty_positions) {
2767 *day = days_in_month;
2768 } else {
2769 return FALSE;
2773 return TRUE;
2776 static void
2777 e_calendar_item_get_month_info (ECalendarItem *calitem,
2778 gint row,
2779 gint col,
2780 gint *first_day_offset,
2781 gint *days_in_month,
2782 gint *days_in_prev_month)
2784 GDateWeekday start_weekday;
2785 gint year, month, first_day_of_month;
2786 struct tm tmp_tm = { 0 };
2788 month = calitem->month + row * calitem->cols + col;
2789 year = calitem->year + month / 12;
2790 month = month % 12;
2792 *days_in_month = DAYS_IN_MONTH (year, month);
2793 if (month == 0)
2794 *days_in_prev_month = DAYS_IN_MONTH (year - 1, 11);
2795 else
2796 *days_in_prev_month = DAYS_IN_MONTH (year, month - 1);
2798 tmp_tm.tm_year = year - 1900;
2799 tmp_tm.tm_mon = month;
2800 tmp_tm.tm_mday = 1;
2801 tmp_tm.tm_isdst = -1;
2802 mktime (&tmp_tm);
2804 start_weekday = e_weekday_from_tm_wday (tmp_tm.tm_wday);
2806 first_day_of_month = e_weekday_get_days_between (
2807 calitem->week_start_day, start_weekday);
2809 if (row == 0 && col == 0 && first_day_of_month == 0)
2810 *first_day_offset = 7;
2811 else
2812 *first_day_offset = first_day_of_month;
2815 void
2816 e_calendar_item_get_first_month (ECalendarItem *calitem,
2817 gint *year,
2818 gint *month)
2820 *year = calitem->year;
2821 *month = calitem->month;
2824 gboolean
2825 e_calendar_item_convert_position_to_date (ECalendarItem *calitem,
2826 gint event_x,
2827 gint event_y,
2828 GDate *date)
2830 gint month_offset = -1;
2831 gint day = -1, dday, dmonth, dyear;
2832 gboolean entire_week = FALSE;
2834 g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), FALSE);
2835 g_return_val_if_fail (date != NULL, FALSE);
2837 if (calitem->rows == 0 || calitem->cols == 0)
2838 return FALSE;
2840 if (!e_calendar_item_convert_position_to_day (calitem, event_x, event_y, FALSE, &month_offset, &day, &entire_week) ||
2841 day < 0 || entire_week)
2842 return FALSE;
2844 dyear = calitem->year;
2845 dmonth = calitem->month + month_offset;
2846 e_calendar_item_normalize_date (calitem, &dyear, &dmonth);
2847 dday = day;
2849 g_date_set_dmy (date, dday, dmonth + 1, dyear);
2851 return g_date_valid (date);
2854 static void
2855 e_calendar_item_preserve_day_selection (ECalendarItem *calitem,
2856 gint selected_day,
2857 gint *month_offset,
2858 gint *day)
2860 gint year, month, weekday, days, days_in_month;
2861 struct tm tmp_tm = { 0 };
2863 year = calitem->year;
2864 month = calitem->month + *month_offset;
2865 e_calendar_item_normalize_date (calitem, &year, &month);
2867 tmp_tm.tm_year = year - 1900;
2868 tmp_tm.tm_mon = month;
2869 tmp_tm.tm_mday = *day;
2870 tmp_tm.tm_isdst = -1;
2871 mktime (&tmp_tm);
2873 /* Convert to 0 (Monday) to 6 (Sunday). */
2874 weekday = (tmp_tm.tm_wday + 6) % 7;
2876 /* Calculate how many days to the start of the row. */
2877 days = (weekday + 7 - selected_day) % 7;
2879 *day -= days;
2880 if (*day <= 0) {
2881 month--;
2882 if (month == -1) {
2883 year--;
2884 month = 11;
2886 days_in_month = DAYS_IN_MONTH (year, month);
2887 (*month_offset)--;
2888 *day += days_in_month;
2892 /* This also handles values of month < 0 or > 11 by updating the year. */
2893 static void
2894 e_calendar_item_set_first_month_with_emit (ECalendarItem *calitem,
2895 gint year,
2896 gint month,
2897 gboolean emit_date_range_moved)
2899 gint new_year, new_month, months_diff, num_months;
2900 gint old_days_in_selection, new_days_in_selection;
2902 new_year = year;
2903 new_month = month;
2904 e_calendar_item_normalize_date (calitem, &new_year, &new_month);
2906 if (calitem->year == new_year && calitem->month == new_month)
2907 return;
2909 /* Update the selection. */
2910 num_months = calitem->rows * calitem->cols;
2911 months_diff = (new_year - calitem->year) * 12
2912 + new_month - calitem->month;
2914 if (calitem->selection_set) {
2915 if (!calitem->move_selection_when_moving
2916 || (calitem->selection_start_month_offset - months_diff >= 0
2917 && calitem->selection_end_month_offset - months_diff < num_months)) {
2918 calitem->selection_start_month_offset -= months_diff;
2919 calitem->selection_end_month_offset -= months_diff;
2920 calitem->selection_real_start_month_offset -= months_diff;
2922 calitem->year = new_year;
2923 calitem->month = new_month;
2924 } else {
2925 gint selected_day;
2926 struct tm tmp_tm = { 0 };
2928 old_days_in_selection = e_calendar_item_get_inclusive_days (
2929 calitem,
2930 calitem->selection_start_month_offset,
2931 calitem->selection_start_day,
2932 calitem->selection_end_month_offset,
2933 calitem->selection_end_day);
2935 /* Calculate the currently selected day */
2936 tmp_tm.tm_year = calitem->year - 1900;
2937 tmp_tm.tm_mon = calitem->month + calitem->selection_start_month_offset;
2938 tmp_tm.tm_mday = calitem->selection_start_day;
2939 tmp_tm.tm_isdst = -1;
2940 mktime (&tmp_tm);
2942 selected_day = (tmp_tm.tm_wday + 6) % 7;
2944 /* Make sure the selection will be displayed. */
2945 if (calitem->selection_start_month_offset < 0
2946 || calitem->selection_start_month_offset >= num_months) {
2947 calitem->selection_end_month_offset -=
2948 calitem->selection_start_month_offset;
2949 calitem->selection_start_month_offset = 0;
2952 /* We want to ensure that the same number of days are
2953 * selected after we have moved the selection. */
2954 calitem->year = new_year;
2955 calitem->month = new_month;
2957 e_calendar_item_ensure_valid_day (
2958 calitem, &calitem->selection_start_month_offset,
2959 &calitem->selection_start_day);
2960 e_calendar_item_ensure_valid_day (
2961 calitem, &calitem->selection_end_month_offset,
2962 &calitem->selection_end_day);
2964 if (calitem->preserve_day_when_moving) {
2965 e_calendar_item_preserve_day_selection (
2966 calitem, selected_day,
2967 &calitem->selection_start_month_offset,
2968 &calitem->selection_start_day);
2971 new_days_in_selection = e_calendar_item_get_inclusive_days (
2972 calitem,
2973 calitem->selection_start_month_offset,
2974 calitem->selection_start_day,
2975 calitem->selection_end_month_offset,
2976 calitem->selection_end_day);
2978 if (old_days_in_selection != new_days_in_selection)
2979 e_calendar_item_add_days_to_selection (
2980 calitem, old_days_in_selection -
2981 new_days_in_selection);
2983 /* Flag that we need to emit the "selection_changed"
2984 * signal. We don't want to emit it here since setting
2985 * the "year" and "month" args would result in 2
2986 * signals emitted. */
2987 calitem->selection_changed = TRUE;
2989 } else {
2990 calitem->year = new_year;
2991 calitem->month = new_month;
2994 e_calendar_item_date_range_changed (calitem);
2995 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
2997 if (emit_date_range_moved)
2998 g_signal_emit (calitem, e_calendar_item_signals[DATE_RANGE_MOVED], 0);
3001 /* This also handles values of month < 0 or > 11 by updating the year. */
3002 void
3003 e_calendar_item_set_first_month (ECalendarItem *calitem,
3004 gint year,
3005 gint month)
3007 e_calendar_item_set_first_month_with_emit (calitem, year, month, TRUE);
3010 /* Get the maximum number of days selectable */
3011 gint
3012 e_calendar_item_get_max_days_sel (ECalendarItem *calitem)
3014 return calitem->max_days_selected;
3017 /* Set the maximum number of days selectable */
3018 void
3019 e_calendar_item_set_max_days_sel (ECalendarItem *calitem,
3020 gint days)
3022 calitem->max_days_selected = MAX (0, days);
3023 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3026 /* Get the maximum number of days before whole weeks are selected */
3027 gint
3028 e_calendar_item_get_days_start_week_sel (ECalendarItem *calitem)
3030 return calitem->days_to_start_week_selection;
3033 /* Set the maximum number of days before whole weeks are selected */
3034 void
3035 e_calendar_item_set_days_start_week_sel (ECalendarItem *calitem,
3036 gint days)
3038 calitem->days_to_start_week_selection = days;
3041 gboolean
3042 e_calendar_item_get_display_popup (ECalendarItem *calitem)
3044 return calitem->display_popup;
3047 void
3048 e_calendar_item_set_display_popup (ECalendarItem *calitem,
3049 gboolean display)
3051 calitem->display_popup = display;
3054 /* This will make sure that the given year & month are valid, i.e. if month
3055 * is < 0 or > 11 the year and month will be updated accordingly. */
3056 void
3057 e_calendar_item_normalize_date (ECalendarItem *calitem,
3058 gint *year,
3059 gint *month)
3061 if (*month >= 0) {
3062 *year += *month / 12;
3063 *month = *month % 12;
3064 } else {
3065 *year += *month / 12 - 1;
3066 *month = *month % 12;
3067 if (*month != 0)
3068 *month += 12;
3072 /* Adds or subtracts days from the selection. It is used when we switch months
3073 * and the selection extends past the end of a month but we want to keep the
3074 * number of days selected the same. days should not be more than 30. */
3075 static void
3076 e_calendar_item_add_days_to_selection (ECalendarItem *calitem,
3077 gint days)
3079 gint year, month, days_in_month;
3081 year = calitem->year;
3082 month = calitem->month + calitem->selection_end_month_offset;
3083 e_calendar_item_normalize_date (calitem, &year, &month);
3085 calitem->selection_end_day += days;
3086 if (calitem->selection_end_day <= 0) {
3087 month--;
3088 e_calendar_item_normalize_date (calitem, &year, &month);
3089 calitem->selection_end_month_offset--;
3090 calitem->selection_end_day += DAYS_IN_MONTH (year, month);
3091 } else {
3092 days_in_month = DAYS_IN_MONTH (year, month);
3093 if (calitem->selection_end_day > days_in_month) {
3094 calitem->selection_end_month_offset++;
3095 calitem->selection_end_day -= days_in_month;
3100 /* Gets the range of dates actually shown. Months are 0 to 11.
3101 * This also includes the last days of the previous month and the first days
3102 * of the following month, which are normally shown in gray.
3103 * It returns FALSE if no dates are currently shown. */
3104 gboolean
3105 e_calendar_item_get_date_range (ECalendarItem *calitem,
3106 gint *start_year,
3107 gint *start_month,
3108 gint *start_day,
3109 gint *end_year,
3110 gint *end_month,
3111 gint *end_day)
3113 gint first_day_offset, days_in_month, days_in_prev_month;
3115 if (calitem->rows == 0 || calitem->cols == 0)
3116 return FALSE;
3118 /* Calculate the first day shown. This will be one of the greyed-out
3119 * days before the first full month begins. */
3120 e_calendar_item_get_month_info (
3121 calitem, 0, 0, &first_day_offset,
3122 &days_in_month, &days_in_prev_month);
3123 *start_year = calitem->year;
3124 *start_month = calitem->month - 1;
3125 if (*start_month == -1) {
3126 (*start_year)--;
3127 *start_month = 11;
3129 *start_day = days_in_prev_month + 1 - first_day_offset;
3131 /* Calculate the last day shown. This will be one of the greyed-out
3132 * days after the last full month ends. */
3133 e_calendar_item_get_month_info (
3134 calitem, calitem->rows - 1,
3135 calitem->cols - 1, &first_day_offset,
3136 &days_in_month, &days_in_prev_month);
3137 *end_month = calitem->month + calitem->rows * calitem->cols;
3138 *end_year = calitem->year + *end_month / 12;
3139 *end_month %= 12;
3140 *end_day = E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH
3141 - first_day_offset - days_in_month;
3143 return TRUE;
3146 /* Simple way to mark days so they appear bold.
3147 * A more flexible interface may be added later. */
3148 void
3149 e_calendar_item_clear_marks (ECalendarItem *calitem)
3151 GnomeCanvasItem *item;
3153 item = GNOME_CANVAS_ITEM (calitem);
3155 g_free (calitem->styles);
3156 calitem->styles = NULL;
3158 gnome_canvas_request_redraw (
3159 item->canvas, item->x1, item->y1,
3160 item->x2, item->y2);
3163 /* add_day_style - whether bit-or with the actual style or change the style fully */
3164 void
3165 e_calendar_item_mark_day (ECalendarItem *calitem,
3166 gint year,
3167 gint month,
3168 gint day,
3169 guint8 day_style,
3170 gboolean add_day_style)
3172 gint month_offset;
3173 gint index;
3175 month_offset = (year - calitem->year) * 12 + month - calitem->month;
3176 if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
3177 return;
3179 if (!calitem->styles)
3180 calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);
3182 index = (month_offset + 1) * 32 + day;
3183 calitem->styles[index] = day_style |
3184 (add_day_style ? calitem->styles[index] : 0);
3186 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3189 void
3190 e_calendar_item_mark_days (ECalendarItem *calitem,
3191 gint start_year,
3192 gint start_month,
3193 gint start_day,
3194 gint end_year,
3195 gint end_month,
3196 gint end_day,
3197 guint8 day_style,
3198 gboolean add_day_style)
3200 gint month_offset, end_month_offset, day;
3202 month_offset = (start_year - calitem->year) * 12 + start_month
3203 - calitem->month;
3204 day = start_day;
3205 if (month_offset > calitem->rows * calitem->cols)
3206 return;
3207 if (month_offset < -1) {
3208 month_offset = -1;
3209 day = 1;
3212 end_month_offset = (end_year - calitem->year) * 12 + end_month
3213 - calitem->month;
3214 if (end_month_offset < -1)
3215 return;
3216 if (end_month_offset > calitem->rows * calitem->cols) {
3217 end_month_offset = calitem->rows * calitem->cols;
3218 end_day = 31;
3221 if (month_offset > end_month_offset)
3222 return;
3224 if (!calitem->styles)
3225 calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);
3227 for (;;) {
3228 gint index;
3230 if (month_offset == end_month_offset && day > end_day)
3231 break;
3233 if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
3234 g_warning ("Bad month offset: %i\n", month_offset);
3235 if (day < 1 || day > 31)
3236 g_warning ("Bad day: %i\n", day);
3238 #if 0
3239 g_print ("Marking Month:%i Day:%i\n", month_offset, day);
3240 #endif
3241 index = (month_offset + 1) * 32 + day;
3242 calitem->styles[index] = day_style |
3243 (add_day_style ? calitem->styles[index] : 0);
3245 day++;
3246 if (day == 32) {
3247 month_offset++;
3248 day = 1;
3249 if (month_offset > end_month_offset)
3250 break;
3254 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3257 /* Rounds up the given day to the end of the week. */
3258 static void
3259 e_calendar_item_round_up_selection (ECalendarItem *calitem,
3260 gint *month_offset,
3261 gint *day)
3263 GDateWeekday weekday;
3264 gint year, month, days, days_in_month;
3265 struct tm tmp_tm = { 0 };
3267 year = calitem->year;
3268 month = calitem->month + *month_offset;
3269 e_calendar_item_normalize_date (calitem, &year, &month);
3271 tmp_tm.tm_year = year - 1900;
3272 tmp_tm.tm_mon = month;
3273 tmp_tm.tm_mday = *day;
3274 tmp_tm.tm_isdst = -1;
3275 mktime (&tmp_tm);
3277 /* Calculate how many days to the end of the row. */
3278 weekday = e_weekday_from_tm_wday (tmp_tm.tm_wday);
3279 days = e_weekday_get_days_between (weekday, calitem->week_start_day);
3281 *day += days;
3282 days_in_month = DAYS_IN_MONTH (year, month);
3283 if (*day > days_in_month) {
3284 (*month_offset)++;
3285 *day -= days_in_month;
3289 /* Rounds down the given day to the start of the week. */
3290 static void
3291 e_calendar_item_round_down_selection (ECalendarItem *calitem,
3292 gint *month_offset,
3293 gint *day)
3295 GDateWeekday weekday;
3296 gint year, month, days, days_in_month;
3297 struct tm tmp_tm = { 0 };
3299 year = calitem->year;
3300 month = calitem->month + *month_offset;
3301 e_calendar_item_normalize_date (calitem, &year, &month);
3303 tmp_tm.tm_year = year - 1900;
3304 tmp_tm.tm_mon = month;
3305 tmp_tm.tm_mday = *day;
3306 tmp_tm.tm_isdst = -1;
3307 mktime (&tmp_tm);
3309 /* Calculate how many days to the start of the row. */
3310 weekday = e_weekday_from_tm_wday (tmp_tm.tm_wday);
3311 days = e_weekday_get_days_between (weekday, calitem->week_start_day);
3313 *day -= days;
3314 if (*day <= 0) {
3315 month--;
3316 if (month == -1) {
3317 year--;
3318 month = 11;
3320 days_in_month = DAYS_IN_MONTH (year, month);
3321 (*month_offset)--;
3322 *day += days_in_month;
3326 static gint
3327 e_calendar_item_get_inclusive_days (ECalendarItem *calitem,
3328 gint start_month_offset,
3329 gint start_day,
3330 gint end_month_offset,
3331 gint end_day)
3333 gint start_year, start_month, end_year, end_month, days = 0;
3335 start_year = calitem->year;
3336 start_month = calitem->month + start_month_offset;
3337 e_calendar_item_normalize_date (calitem, &start_year, &start_month);
3339 end_year = calitem->year;
3340 end_month = calitem->month + end_month_offset;
3341 e_calendar_item_normalize_date (calitem, &end_year, &end_month);
3343 while (start_year < end_year || start_month < end_month) {
3344 days += DAYS_IN_MONTH (start_year, start_month);
3345 start_month++;
3346 if (start_month == 12) {
3347 start_year++;
3348 start_month = 0;
3352 days += end_day - start_day + 1;
3354 return days;
3357 /* If the day is off the end of the month it is set to the last day of the
3358 * month. */
3359 static void
3360 e_calendar_item_ensure_valid_day (ECalendarItem *calitem,
3361 gint *month_offset,
3362 gint *day)
3364 gint year, month, days_in_month;
3366 year = calitem->year;
3367 month = calitem->month + *month_offset;
3368 e_calendar_item_normalize_date (calitem, &year, &month);
3370 days_in_month = DAYS_IN_MONTH (year, month);
3371 if (*day > days_in_month)
3372 *day = days_in_month;
3375 gboolean
3376 e_calendar_item_get_selection (ECalendarItem *calitem,
3377 GDate *start_date,
3378 GDate *end_date)
3380 gint start_year, start_month, start_day;
3381 gint end_year, end_month, end_day;
3383 g_date_clear (start_date, 1);
3384 g_date_clear (end_date, 1);
3386 if (!calitem->selection_set)
3387 return FALSE;
3389 start_year = calitem->year;
3390 start_month = calitem->month + calitem->selection_start_month_offset;
3391 e_calendar_item_normalize_date (calitem, &start_year, &start_month);
3392 start_day = calitem->selection_start_day;
3394 end_year = calitem->year;
3395 end_month = calitem->month + calitem->selection_end_month_offset;
3396 e_calendar_item_normalize_date (calitem, &end_year, &end_month);
3397 end_day = calitem->selection_end_day;
3399 g_date_set_dmy (start_date, start_day, start_month + 1, start_year);
3400 g_date_set_dmy (end_date, end_day, end_month + 1, end_year);
3402 return TRUE;
3405 static void
3406 e_calendar_item_set_selection_if_emission (ECalendarItem *calitem,
3407 const GDate *start_date,
3408 const GDate *end_date,
3409 gboolean emission)
3411 gint start_year, start_month, start_day;
3412 gint end_year, end_month, end_day;
3413 gint new_start_month_offset, new_start_day;
3414 gint new_end_month_offset, new_end_day;
3415 gboolean need_update;
3417 g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
3419 /* If start_date is NULL, we clear the selection without changing the
3420 * month shown. */
3421 if (start_date == NULL) {
3422 calitem->selection_set = FALSE;
3423 calitem->selection_changed = TRUE;
3424 e_calendar_item_queue_signal_emission (calitem);
3425 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3426 return;
3429 if (end_date == NULL)
3430 end_date = start_date;
3432 g_return_if_fail (g_date_compare (start_date, end_date) <= 0);
3434 start_year = g_date_get_year (start_date);
3435 start_month = g_date_get_month (start_date) - 1;
3436 start_day = g_date_get_day (start_date);
3437 end_year = g_date_get_year (end_date);
3438 end_month = g_date_get_month (end_date) - 1;
3439 end_day = g_date_get_day (end_date);
3441 need_update = e_calendar_item_ensure_days_visible (
3442 calitem,
3443 start_year,
3444 start_month,
3445 start_day,
3446 end_year,
3447 end_month,
3448 end_day,
3449 emission);
3451 new_start_month_offset = (start_year - calitem->year) * 12
3452 + start_month - calitem->month;
3453 new_start_day = start_day;
3455 /* This may go outside the visible months, but we don't care. */
3456 new_end_month_offset = (end_year - calitem->year) * 12
3457 + end_month - calitem->month;
3458 new_end_day = end_day;
3460 if (!calitem->selection_set
3461 || calitem->selection_start_month_offset != new_start_month_offset
3462 || calitem->selection_start_day != new_start_day
3463 || calitem->selection_end_month_offset != new_end_month_offset
3464 || calitem->selection_end_day != new_end_day) {
3465 need_update = TRUE;
3466 if (emission) {
3467 calitem->selection_changed = TRUE;
3468 e_calendar_item_queue_signal_emission (calitem);
3470 calitem->selection_set = TRUE;
3471 calitem->selection_start_month_offset = new_start_month_offset;
3472 calitem->selection_start_day = new_start_day;
3473 calitem->selection_end_month_offset = new_end_month_offset;
3474 calitem->selection_end_day = new_end_day;
3476 calitem->selection_real_start_month_offset = new_start_month_offset;
3477 calitem->selection_real_start_day = new_start_day;
3478 calitem->selection_from_full_week = FALSE;
3481 if (need_update) {
3482 g_signal_emit (
3483 calitem,
3484 e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
3485 gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
3489 void
3490 e_calendar_item_style_updated (GtkWidget *widget,
3491 ECalendarItem *calitem)
3493 GdkRGBA unfocused_selected_bg, selected_bg, fg, base_bg;
3495 e_utils_get_theme_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &selected_bg);
3496 e_utils_get_theme_color (widget, "theme_unfocused_selected_bg_color,theme_selected_bg_color", E_UTILS_DEFAULT_THEME_UNFOCUSED_SELECTED_BG_COLOR, &unfocused_selected_bg);
3497 e_utils_get_theme_color (widget, "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &fg);
3498 e_utils_get_theme_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &base_bg);
3500 if (gdk_rgba_equal (&selected_bg, &unfocused_selected_bg))
3501 e_utils_get_theme_color (widget, "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, &selected_bg);
3503 e_rgba_to_color (&selected_bg, &calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX]);
3504 e_rgba_to_color (&base_bg, &calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_FG]);
3505 e_rgba_to_color (&unfocused_selected_bg, &calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG_FOCUSED]);
3506 e_rgba_to_color (&fg, &calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG]);
3507 e_rgba_to_color (&fg, &calitem->colors[E_CALENDAR_ITEM_COLOR_PREV_OR_NEXT_MONTH_FG]);
3509 e_calendar_item_recalc_sizes (calitem);
3512 void
3513 e_calendar_item_set_selection (ECalendarItem *calitem,
3514 const GDate *start_date,
3515 const GDate *end_date)
3517 GDate current_start_date, current_end_date;
3519 /* If the user is in the middle of a selection, we must abort it. */
3520 if (calitem->selecting) {
3521 gnome_canvas_item_ungrab (
3522 GNOME_CANVAS_ITEM (calitem),
3523 GDK_CURRENT_TIME);
3524 calitem->selecting = FALSE;
3527 if (e_calendar_item_get_selection (calitem, &current_start_date, &current_end_date)) {
3528 /* No change, no need to recalculate anything */
3529 if (start_date && end_date && g_date_valid (start_date) && g_date_valid (end_date) &&
3530 g_date_compare (start_date, &current_start_date) == 0 &&
3531 g_date_compare (end_date, &current_end_date) == 0)
3532 return;
3535 e_calendar_item_set_selection_if_emission (calitem,
3536 start_date, end_date,
3537 TRUE);
3540 /* This tries to ensure that the given time range is visible. If the range
3541 * given is longer than we can show, only the start of it will be visible.
3542 * Note that this will not update the selection. That should be done somewhere
3543 * else. It returns TRUE if the visible range has been changed. */
3544 static gboolean
3545 e_calendar_item_ensure_days_visible (ECalendarItem *calitem,
3546 gint start_year,
3547 gint start_month,
3548 gint start_day,
3549 gint end_year,
3550 gint end_month,
3551 gint end_day,
3552 gboolean emission)
3554 gint current_end_year, current_end_month;
3555 gint months_shown;
3556 gint first_day_offset, days_in_month, days_in_prev_month;
3557 gboolean need_update = FALSE;
3559 months_shown = calitem->rows * calitem->cols;
3561 /* Calculate the range of months currently displayed. */
3562 current_end_year = calitem->year;
3563 current_end_month = calitem->month + months_shown - 1;
3564 e_calendar_item_normalize_date (
3565 calitem, &current_end_year,
3566 &current_end_month);
3568 /* Try to ensure that the end month is shown. */
3569 if ((end_year == current_end_year + 1 &&
3570 current_end_month == 11 && end_month == 0) ||
3571 (end_year == current_end_year && end_month == current_end_month + 1)) {
3572 /* See if the end of the selection will fit in the
3573 * leftover days of the month after the last one shown. */
3574 calitem->month += (months_shown - 1);
3575 e_calendar_item_normalize_date (
3576 calitem, &calitem->year,
3577 &calitem->month);
3579 e_calendar_item_get_month_info (
3580 calitem, 0, 0,
3581 &first_day_offset,
3582 &days_in_month,
3583 &days_in_prev_month);
3585 if (end_day >= E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH -
3586 first_day_offset - days_in_month) {
3587 need_update = TRUE;
3589 calitem->year = end_year;
3590 calitem->month = end_month - months_shown + 1;
3591 } else {
3592 calitem->month -= (months_shown - 1);
3595 e_calendar_item_normalize_date (
3596 calitem, &calitem->year,
3597 &calitem->month);
3599 else if (end_year > current_end_year ||
3600 (end_year == current_end_year && end_month > current_end_month)) {
3601 /* The selection will definitely not fit in the leftover days
3602 * of the month after the last one shown. */
3603 need_update = TRUE;
3605 calitem->year = end_year;
3606 calitem->month = end_month - months_shown + 1;
3608 e_calendar_item_normalize_date (
3609 calitem, &calitem->year,
3610 &calitem->month);
3613 /* Now try to ensure that the start month is shown. We do this after
3614 * the end month so that the start month will always be shown. */
3615 if (start_year < calitem->year
3616 || (start_year == calitem->year
3617 && start_month < calitem->month)) {
3618 need_update = TRUE;
3620 /* First we see if the start of the selection will fit in the
3621 * leftover days of the month before the first one shown. */
3622 calitem->year = start_year;
3623 calitem->month = start_month + 1;
3624 e_calendar_item_normalize_date (
3625 calitem, &calitem->year,
3626 &calitem->month);
3628 e_calendar_item_get_month_info (
3629 calitem, 0, 0,
3630 &first_day_offset,
3631 &days_in_month,
3632 &days_in_prev_month);
3634 if (start_day <= days_in_prev_month - first_day_offset) {
3635 calitem->year = start_year;
3636 calitem->month = start_month;
3640 if (need_update && emission)
3641 e_calendar_item_date_range_changed (calitem);
3643 return need_update;
3646 static void
3647 e_calendar_item_show_popup_menu (ECalendarItem *calitem,
3648 GdkEvent *button_event,
3649 gint month_offset)
3651 GtkWidget *menu, *submenu, *menuitem, *label;
3652 GtkWidget *canvas_widget;
3653 gint year, month;
3654 const gchar *name;
3655 gchar buffer[64];
3657 menu = gtk_menu_new ();
3659 for (year = calitem->year - 2; year <= calitem->year + 2; year++) {
3660 g_snprintf (buffer, 64, "%i", year);
3661 menuitem = gtk_menu_item_new_with_label (buffer);
3662 gtk_widget_show (menuitem);
3663 gtk_container_add (GTK_CONTAINER (menu), menuitem);
3665 submenu = gtk_menu_new ();
3666 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
3668 g_object_set_data (
3669 G_OBJECT (submenu), "year",
3670 GINT_TO_POINTER (year));
3671 g_object_set_data (
3672 G_OBJECT (submenu), "month_offset",
3673 GINT_TO_POINTER (month_offset));
3675 for (month = 0; month < 12; month++) {
3676 name = e_get_month_name (month + 1, FALSE);
3678 menuitem = gtk_menu_item_new ();
3679 gtk_widget_show (menuitem);
3680 gtk_container_add (GTK_CONTAINER (submenu), menuitem);
3682 label = gtk_label_new (name);
3683 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
3684 gtk_widget_show (label);
3685 gtk_container_add (GTK_CONTAINER (menuitem), label);
3687 g_object_set_data (
3688 G_OBJECT (menuitem), "month",
3689 GINT_TO_POINTER (month));
3691 g_signal_connect (
3692 menuitem, "activate",
3693 G_CALLBACK (e_calendar_item_on_menu_item_activate),
3694 calitem);
3698 g_signal_connect (
3699 menu, "deactivate",
3700 G_CALLBACK (gtk_menu_detach), NULL);
3702 canvas_widget = GTK_WIDGET (calitem->canvas_item.canvas);
3703 gtk_menu_attach_to_widget (GTK_MENU (menu), canvas_widget, NULL);
3704 gtk_menu_popup_at_pointer (GTK_MENU (menu), button_event);
3707 static void
3708 e_calendar_item_on_menu_item_activate (GtkWidget *menuitem,
3709 ECalendarItem *calitem)
3711 GtkWidget *parent;
3712 gint year, month_offset, month;
3713 gpointer data;
3715 parent = gtk_widget_get_parent (menuitem);
3716 data = g_object_get_data (G_OBJECT (parent), "year");
3717 year = GPOINTER_TO_INT (data);
3719 parent = gtk_widget_get_parent (menuitem);
3720 data = g_object_get_data (G_OBJECT (parent), "month_offset");
3721 month_offset = GPOINTER_TO_INT (data);
3723 data = g_object_get_data (G_OBJECT (menuitem), "month");
3724 month = GPOINTER_TO_INT (data);
3726 month -= month_offset;
3727 e_calendar_item_normalize_date (calitem, &year, &month);
3728 e_calendar_item_set_first_month_with_emit (calitem, year, month, TRUE);
3731 /* Sets the function to call to get the colors to use for a particular day. */
3732 void
3733 e_calendar_item_set_style_callback (ECalendarItem *calitem,
3734 ECalendarItemStyleCallback cb,
3735 gpointer data,
3736 GDestroyNotify destroy)
3738 g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
3740 if (calitem->style_callback_data && calitem->style_callback_destroy)
3741 (*calitem->style_callback_destroy) (calitem->style_callback_data);
3743 calitem->style_callback = cb;
3744 calitem->style_callback_data = data;
3745 calitem->style_callback_destroy = destroy;
3748 static void
3749 e_calendar_item_date_range_changed (ECalendarItem *calitem)
3751 g_free (calitem->styles);
3752 calitem->styles = NULL;
3753 calitem->date_range_changed = TRUE;
3754 e_calendar_item_queue_signal_emission (calitem);
3757 static void
3758 e_calendar_item_queue_signal_emission (ECalendarItem *calitem)
3760 if (calitem->signal_emission_idle_id == 0) {
3761 calitem->signal_emission_idle_id = g_idle_add_full (
3762 G_PRIORITY_HIGH, (GSourceFunc)
3763 e_calendar_item_signal_emission_idle_cb,
3764 calitem, NULL);
3768 static gboolean
3769 e_calendar_item_signal_emission_idle_cb (gpointer data)
3771 ECalendarItem *calitem;
3773 g_return_val_if_fail (E_IS_CALENDAR_ITEM (data), FALSE);
3775 calitem = E_CALENDAR_ITEM (data);
3777 calitem->signal_emission_idle_id = 0;
3779 /* We ref the calitem & check in case it gets destroyed, since we
3780 * were getting a free memory write here. */
3781 g_object_ref ((calitem));
3783 if (calitem->date_range_changed) {
3784 calitem->date_range_changed = FALSE;
3785 g_signal_emit (calitem, e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
3788 if (calitem->selection_changed) {
3789 calitem->selection_changed = FALSE;
3790 g_signal_emit (calitem, e_calendar_item_signals[SELECTION_CHANGED], 0);
3793 g_object_unref ((calitem));
3795 return FALSE;
3798 /* Sets a callback to use to get the current time. This is useful if the
3799 * application needs to use its own timezone data rather than rely on the
3800 * Unix timezone. */
3801 void
3802 e_calendar_item_set_get_time_callback (ECalendarItem *calitem,
3803 ECalendarItemGetTimeCallback cb,
3804 gpointer data,
3805 GDestroyNotify destroy)
3807 g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
3809 if (calitem->time_callback_data && calitem->time_callback_destroy)
3810 (*calitem->time_callback_destroy) (calitem->time_callback_data);
3812 calitem->time_callback = cb;
3813 calitem->time_callback_data = data;
3814 calitem->time_callback_destroy = destroy;