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