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