1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * GTK Calendar Widget
5 * Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson and Mattias Groenlund
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21/*
22 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
23 * file for a list of people on the GTK+ Team. See the ChangeLog
24 * files for a list of changes. These files are distributed with
25 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
26 */
27
28/**
29 * GtkCalendar:
30 *
31 * `GtkCalendar` is a widget that displays a Gregorian calendar, one month
32 * at a time.
33 *
34 * ![An example GtkCalendar](calendar.png)
35 *
36 * A `GtkCalendar` can be created with [ctor@Gtk.Calendar.new].
37 *
38 * The date that is currently displayed can be altered with
39 * [method@Gtk.Calendar.select_day].
40 *
41 * To place a visual marker on a particular day, use
42 * [method@Gtk.Calendar.mark_day] and to remove the marker,
43 * [method@Gtk.Calendar.unmark_day]. Alternative, all
44 * marks can be cleared with [method@Gtk.Calendar.clear_marks].
45 *
46 * The selected date can be retrieved from a `GtkCalendar` using
47 * [method@Gtk.Calendar.get_date].
48 *
49 * Users should be aware that, although the Gregorian calendar is the
50 * legal calendar in most countries, it was adopted progressively
51 * between 1582 and 1929. Display before these dates is likely to be
52 * historically incorrect.
53 *
54 * # CSS nodes
55 *
56 * ```
57 * calendar.view
58 * ├── header
59 * │ ├── button
60 * │ ├── stack.month
61 * │ ├── button
62 * │ ├── button
63 * │ ├── label.year
64 * │ ╰── button
65 * ╰── grid
66 * ╰── label[.day-name][.week-number][.day-number][.other-month][.today]
67 * ```
68 *
69 * `GtkCalendar` has a main node with name calendar. It contains a subnode
70 * called header containing the widgets for switching between years and months.
71 *
72 * The grid subnode contains all day labels, including week numbers on the left
73 * (marked with the .week-number css class) and day names on top (marked with the
74 * .day-name css class).
75 *
76 * Day labels that belong to the previous or next month get the .other-month
77 * style class. The label of the current day get the .today style class.
78 *
79 * Marked day labels get the :selected state assigned.
80 */
81
82#include "config.h"
83
84#ifdef HAVE_SYS_TIME_H
85#include <sys/time.h>
86#endif
87#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
88#include <langinfo.h>
89#endif
90#include <string.h>
91#include <stdlib.h>
92#include <time.h>
93
94#include <glib.h>
95
96#ifdef G_OS_WIN32
97#include <windows.h>
98#endif
99
100#include "gtkcalendar.h"
101#include "gtkdroptarget.h"
102#include "gtkintl.h"
103#include "gtkmain.h"
104#include "gtkmarshalers.h"
105#include "gtktooltip.h"
106#include "gtkprivate.h"
107#include "gtkrendericonprivate.h"
108#include "gtksnapshot.h"
109#include "gtkwidgetprivate.h"
110#include "gtkgestureclick.h"
111#include "gtkgesturedrag.h"
112#include "gtkeventcontrollerscroll.h"
113#include "gtkeventcontrollerkey.h"
114#include "gtkeventcontrollerfocus.h"
115#include "gtkdragsource.h"
116#include "gtknative.h"
117#include "gtkicontheme.h"
118#include "gtkdragicon.h"
119#include "gtkbutton.h"
120#include "gtkbox.h"
121#include "gtkboxlayout.h"
122#include "gtkorientable.h"
123#include "gtklabel.h"
124#include "gtkstack.h"
125#include "gtkgrid.h"
126
127static const guint month_length[2][13] =
128{
129 { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
130 { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
131};
132
133static gboolean
134leap (guint year)
135{
136 return ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
137}
138
139static guint
140day_of_week (guint year, guint mm, guint dd)
141{
142 GDateTime *dt;
143 guint days;
144
145 dt = g_date_time_new_local (year, month: mm, day: dd, hour: 1, minute: 1, seconds: 1);
146 if (dt == NULL)
147 return 0;
148
149 days = g_date_time_get_day_of_week (datetime: dt);
150 g_date_time_unref (datetime: dt);
151
152 return days;
153}
154
155static guint
156week_of_year (guint year, guint mm, guint dd)
157{
158 GDateTime *dt;
159 guint week;
160
161 dt = g_date_time_new_local (year, month: mm, day: dd, hour: 1, minute: 1, seconds: 1);
162 if (dt == NULL)
163 return 1;
164
165 week = g_date_time_get_week_of_year (datetime: dt);
166 g_date_time_unref (datetime: dt);
167
168 return week;
169}
170
171enum {
172 MONTH_PREV,
173 MONTH_CURRENT,
174 MONTH_NEXT
175};
176
177enum {
178 DAY_SELECTED_SIGNAL,
179 PREV_MONTH_SIGNAL,
180 NEXT_MONTH_SIGNAL,
181 PREV_YEAR_SIGNAL,
182 NEXT_YEAR_SIGNAL,
183 LAST_SIGNAL
184};
185
186enum
187{
188 PROP_0,
189 PROP_YEAR,
190 PROP_MONTH,
191 PROP_DAY,
192 PROP_SHOW_HEADING,
193 PROP_SHOW_DAY_NAMES,
194 PROP_SHOW_WEEK_NUMBERS,
195};
196
197static guint gtk_calendar_signals[LAST_SIGNAL] = { 0 };
198
199typedef struct _GtkCalendarClass GtkCalendarClass;
200typedef struct _GtkCalendarPrivate GtkCalendarPrivate;
201
202struct _GtkCalendar
203{
204 GtkWidget widget;
205
206 guint show_week_numbers : 1;
207 guint show_heading : 1;
208 guint show_day_names : 1;
209 guint year_before : 1;
210
211 GtkWidget *header_box;
212 GtkWidget *year_label;
213 GtkWidget *month_name_stack;
214 GtkWidget *arrow_widgets[4];
215
216 GtkWidget *grid;
217 GtkWidget *day_name_labels[7];
218 GtkWidget *week_number_labels[6];
219 GtkWidget *day_number_labels[6][7];
220
221 GDateTime *date;
222
223 int day_month[6][7];
224 int day[6][7];
225
226 int num_marked_dates;
227 int marked_date[31];
228
229 int focus_row;
230 int focus_col;
231
232 int week_start;
233};
234
235struct _GtkCalendarClass
236{
237 GtkWidgetClass parent_class;
238
239 void (* day_selected) (GtkCalendar *calendar);
240 void (* prev_month) (GtkCalendar *calendar);
241 void (* next_month) (GtkCalendar *calendar);
242 void (* prev_year) (GtkCalendar *calendar);
243 void (* next_year) (GtkCalendar *calendar);
244};
245
246static void gtk_calendar_set_property (GObject *object,
247 guint prop_id,
248 const GValue *value,
249 GParamSpec *pspec);
250static void gtk_calendar_get_property (GObject *object,
251 guint prop_id,
252 GValue *value,
253 GParamSpec *pspec);
254
255static void gtk_calendar_button_press (GtkGestureClick *gesture,
256 int n_press,
257 double x,
258 double y,
259 gpointer user_data);
260static gboolean gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *controller,
261 guint keyval,
262 guint keycode,
263 GdkModifierType state,
264 GtkWidget *widget);
265static void gtk_calendar_focus_controller_focus (GtkEventController *controller,
266 GtkWidget *widget);
267
268static void calendar_invalidate_day (GtkCalendar *widget,
269 int row,
270 int col);
271static void calendar_invalidate_day_num (GtkCalendar *widget,
272 int day);
273
274static gboolean gtk_calendar_scroll_controller_scroll (GtkEventControllerScroll *scroll,
275 double dx,
276 double dy,
277 GtkWidget *widget);
278
279static void calendar_set_month_prev (GtkCalendar *calendar);
280static void calendar_set_month_next (GtkCalendar *calendar);
281static void calendar_set_year_prev (GtkCalendar *calendar);
282static void calendar_set_year_next (GtkCalendar *calendar);
283
284
285static char *default_abbreviated_dayname[7];
286static char *default_monthname[12];
287
288G_DEFINE_TYPE (GtkCalendar, gtk_calendar, GTK_TYPE_WIDGET)
289
290static void
291gtk_calendar_drag_notify_value (GtkDropTarget *target,
292 GParamSpec **pspec,
293 GtkCalendar *calendar)
294{
295 GDate *date;
296 const GValue *value;
297
298 value = gtk_drop_target_get_value (self: target);
299 if (value == NULL)
300 return;
301
302 date = g_date_new ();
303 g_date_set_parse (date, str: g_value_get_string (value));
304 if (!g_date_valid (date))
305 gtk_drop_target_reject (self: target);
306 g_date_free (date);
307}
308
309static gboolean
310gtk_calendar_drag_drop (GtkDropTarget *dest,
311 const GValue *value,
312 double x,
313 double y,
314 GtkCalendar *calendar)
315{
316 GDate *date;
317 GDateTime *datetime;
318
319 date = g_date_new ();
320 g_date_set_parse (date, str: g_value_get_string (value));
321
322 if (!g_date_valid (date))
323 {
324 g_warning ("Received invalid date data");
325 g_date_free (date);
326 return FALSE;
327 }
328
329 datetime = g_date_time_new_local (year: g_date_get_year (date),
330 month: g_date_get_month (date),
331 day: g_date_get_day (date),
332 hour: 0, minute: 0, seconds: 0);
333 g_date_free (date);
334
335 gtk_calendar_select_day (self: calendar, date: datetime);
336 g_date_time_unref (datetime);
337
338 return TRUE;
339}
340
341static void
342gtk_calendar_dispose (GObject *object)
343{
344 GtkCalendar *calendar = GTK_CALENDAR (object);
345
346 g_clear_pointer (&calendar->date, g_date_time_unref);
347 g_clear_pointer (&calendar->header_box, gtk_widget_unparent);
348 g_clear_pointer (&calendar->grid, gtk_widget_unparent);
349
350 G_OBJECT_CLASS (gtk_calendar_parent_class)->dispose (object);
351}
352
353static void
354gtk_calendar_class_init (GtkCalendarClass *class)
355{
356 GObjectClass *gobject_class;
357 GtkWidgetClass *widget_class;
358
359 gobject_class = (GObjectClass*) class;
360 widget_class = (GtkWidgetClass*) class;
361
362 gobject_class->dispose = gtk_calendar_dispose;
363 gobject_class->set_property = gtk_calendar_set_property;
364 gobject_class->get_property = gtk_calendar_get_property;
365
366 /**
367 * GtkCalendar:year:
368 *
369 * The selected year.
370 *
371 * This property gets initially set to the current year.
372 */
373 g_object_class_install_property (oclass: gobject_class,
374 property_id: PROP_YEAR,
375 pspec: g_param_spec_int (name: "year",
376 P_("Year"),
377 P_("The selected year"),
378 minimum: 1, maximum: 9999, default_value: 1,
379 flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
380
381 /**
382 * GtkCalendar:month:
383 *
384 * The selected month (as a number between 0 and 11).
385 *
386 * This property gets initially set to the current month.
387 */
388 g_object_class_install_property (oclass: gobject_class,
389 property_id: PROP_MONTH,
390 pspec: g_param_spec_int (name: "month",
391 P_("Month"),
392 P_("The selected month (as a number between 0 and 11)"),
393 minimum: 0, maximum: 11, default_value: 0,
394 flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
395
396 /**
397 * GtkCalendar:day:
398 *
399 * The selected day (as a number between 1 and 31).
400 */
401 g_object_class_install_property (oclass: gobject_class,
402 property_id: PROP_DAY,
403 pspec: g_param_spec_int (name: "day",
404 P_("Day"),
405 P_("The selected day (as a number between 1 and 31)"),
406 minimum: 1, maximum: 31, default_value: 1,
407 flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
408
409 /**
410 * GtkCalendar:show-heading: (attributes org.gtk.Property.get=gtk_calendar_get_show_heading org.gtk.Property.set=gtk_calendar_set_show_heading)
411 *
412 * Determines whether a heading is displayed.
413 */
414 g_object_class_install_property (oclass: gobject_class,
415 property_id: PROP_SHOW_HEADING,
416 pspec: g_param_spec_boolean (name: "show-heading",
417 P_("Show Heading"),
418 P_("If TRUE, a heading is displayed"),
419 TRUE,
420 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
421
422 /**
423 * GtkCalendar:show-day-names: (attributes org.gtk.Property.get=gtk_calendar_get_show_day_names org.gtk.Property.set=gtk_calendar_set_show_day_names)
424 *
425 * Determines whether day names are displayed.
426 */
427 g_object_class_install_property (oclass: gobject_class,
428 property_id: PROP_SHOW_DAY_NAMES,
429 pspec: g_param_spec_boolean (name: "show-day-names",
430 P_("Show Day Names"),
431 P_("If TRUE, day names are displayed"),
432 TRUE,
433 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
434 /**
435 * GtkCalendar:show-week-numbers: (attributes org.gtk.Property.get=gtk_calendar_get_show_week_numbers org.gtk.Property.set=gtk_calendar_set_show_week_numbers)
436 *
437 * Determines whether week numbers are displayed.
438 */
439 g_object_class_install_property (oclass: gobject_class,
440 property_id: PROP_SHOW_WEEK_NUMBERS,
441 pspec: g_param_spec_boolean (name: "show-week-numbers",
442 P_("Show Week Numbers"),
443 P_("If TRUE, week numbers are displayed"),
444 FALSE,
445 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
446
447 /**
448 * GtkCalendar::day-selected:
449 * @calendar: the object which received the signal.
450 *
451 * Emitted when the user selects a day.
452 */
453 gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
454 g_signal_new (I_("day-selected"),
455 G_OBJECT_CLASS_TYPE (gobject_class),
456 signal_flags: G_SIGNAL_RUN_FIRST,
457 G_STRUCT_OFFSET (GtkCalendarClass, day_selected),
458 NULL, NULL,
459 NULL,
460 G_TYPE_NONE, n_params: 0);
461
462 /**
463 * GtkCalendar::prev-month:
464 * @calendar: the object which received the signal.
465 *
466 * Emitted when the user switched to the previous month.
467 */
468 gtk_calendar_signals[PREV_MONTH_SIGNAL] =
469 g_signal_new (I_("prev-month"),
470 G_OBJECT_CLASS_TYPE (gobject_class),
471 signal_flags: G_SIGNAL_RUN_FIRST,
472 G_STRUCT_OFFSET (GtkCalendarClass, prev_month),
473 NULL, NULL,
474 NULL,
475 G_TYPE_NONE, n_params: 0);
476
477 /**
478 * GtkCalendar::next-month:
479 * @calendar: the object which received the signal.
480 *
481 * Emitted when the user switched to the next month.
482 */
483 gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
484 g_signal_new (I_("next-month"),
485 G_OBJECT_CLASS_TYPE (gobject_class),
486 signal_flags: G_SIGNAL_RUN_FIRST,
487 G_STRUCT_OFFSET (GtkCalendarClass, next_month),
488 NULL, NULL,
489 NULL,
490 G_TYPE_NONE, n_params: 0);
491
492 /**
493 * GtkCalendar::prev-year:
494 * @calendar: the object which received the signal.
495 *
496 * Emitted when user switched to the previous year.
497 */
498 gtk_calendar_signals[PREV_YEAR_SIGNAL] =
499 g_signal_new (I_("prev-year"),
500 G_OBJECT_CLASS_TYPE (gobject_class),
501 signal_flags: G_SIGNAL_RUN_FIRST,
502 G_STRUCT_OFFSET (GtkCalendarClass, prev_year),
503 NULL, NULL,
504 NULL,
505 G_TYPE_NONE, n_params: 0);
506
507 /**
508 * GtkCalendar::next-year:
509 * @calendar: the object which received the signal.
510 *
511 * Emitted when user switched to the next year.
512 */
513 gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
514 g_signal_new (I_("next-year"),
515 G_OBJECT_CLASS_TYPE (gobject_class),
516 signal_flags: G_SIGNAL_RUN_FIRST,
517 G_STRUCT_OFFSET (GtkCalendarClass, next_year),
518 NULL, NULL,
519 NULL,
520 G_TYPE_NONE, n_params: 0);
521
522 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
523 gtk_widget_class_set_css_name (widget_class, I_("calendar"));
524}
525
526static GdkContentProvider *
527gtk_calendar_drag_prepare (GtkDragSource *source,
528 double x,
529 double y,
530 GtkCalendar *calendar)
531{
532 GDate *date;
533 char str[128];
534
535 date = g_date_new_dmy (day: g_date_time_get_day_of_month (datetime: calendar->date),
536 month: g_date_time_get_month (datetime: calendar->date),
537 year: g_date_time_get_year (datetime: calendar->date));
538 g_date_strftime (s: str, slen: 127, format: "%x", date);
539 g_free (mem: date);
540
541 return gdk_content_provider_new_typed (G_TYPE_STRING, str);
542}
543
544#pragma GCC diagnostic push
545#pragma GCC diagnostic ignored "-Wformat-nonliteral"
546
547static void
548gtk_calendar_init (GtkCalendar *calendar)
549{
550 GtkWidget *widget = GTK_WIDGET (calendar);
551 GtkEventController *controller;
552 GtkGesture *gesture;
553 GtkDragSource *source;
554 GtkDropTarget *target;
555 int i;
556#ifdef G_OS_WIN32
557 wchar_t wbuffer[100];
558#else
559 static const char *month_format = NULL;
560 char buffer[255];
561 time_t tmp_time;
562#endif
563 char *year_before;
564#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
565 union { unsigned int word; char *string; } langinfo;
566 int week_1stday = 0;
567 int first_weekday = 1;
568 guint week_origin;
569#else
570 char *week_start;
571#endif
572 int min_year_width;
573 GDateTime *now;
574
575 gtk_widget_set_focusable (widget, TRUE);
576
577 gtk_widget_add_css_class (GTK_WIDGET (calendar), css_class: "view");
578
579 calendar->header_box = g_object_new (GTK_TYPE_BOX,
580 first_property_name: "css-name", "header",
581 NULL);
582 calendar->year_label = gtk_label_new (str: "");
583 gtk_widget_add_css_class (widget: calendar->year_label, css_class: "year");
584 calendar->month_name_stack = gtk_stack_new ();
585 gtk_widget_add_css_class (widget: calendar->month_name_stack, css_class: "month");
586 calendar->arrow_widgets[0] = gtk_button_new_from_icon_name (icon_name: "pan-start-symbolic");
587 g_signal_connect_swapped (calendar->arrow_widgets[0], "clicked", G_CALLBACK (calendar_set_month_prev), calendar);
588 calendar->arrow_widgets[1] = gtk_button_new_from_icon_name (icon_name: "pan-end-symbolic");
589 g_signal_connect_swapped (calendar->arrow_widgets[1], "clicked", G_CALLBACK (calendar_set_month_next), calendar);
590 gtk_widget_set_hexpand (widget: calendar->arrow_widgets[1], TRUE);
591 gtk_widget_set_halign (widget: calendar->arrow_widgets[1], align: GTK_ALIGN_START);
592 calendar->arrow_widgets[2] = gtk_button_new_from_icon_name (icon_name: "pan-start-symbolic");
593 g_signal_connect_swapped (calendar->arrow_widgets[2], "clicked", G_CALLBACK (calendar_set_year_prev), calendar);
594 calendar->arrow_widgets[3] = gtk_button_new_from_icon_name (icon_name: "pan-end-symbolic");
595 g_signal_connect_swapped (calendar->arrow_widgets[3], "clicked", G_CALLBACK (calendar_set_year_next), calendar);
596
597 gtk_box_append (GTK_BOX (calendar->header_box), child: calendar->arrow_widgets[0]);
598 gtk_box_append (GTK_BOX (calendar->header_box), child: calendar->month_name_stack);
599 gtk_box_append (GTK_BOX (calendar->header_box), child: calendar->arrow_widgets[1]);
600 gtk_box_append (GTK_BOX (calendar->header_box), child: calendar->arrow_widgets[2]);
601 gtk_box_append (GTK_BOX (calendar->header_box), child: calendar->year_label);
602 gtk_box_append (GTK_BOX (calendar->header_box), child: calendar->arrow_widgets[3]);
603
604 gtk_widget_set_parent (widget: calendar->header_box, GTK_WIDGET (calendar));
605
606 gesture = gtk_gesture_click_new ();
607 g_signal_connect (gesture, "pressed", G_CALLBACK (gtk_calendar_button_press), calendar);
608 gtk_widget_add_controller (GTK_WIDGET (calendar), GTK_EVENT_CONTROLLER (gesture));
609
610 source = gtk_drag_source_new ();
611 g_signal_connect (source, "prepare", G_CALLBACK (gtk_calendar_drag_prepare), calendar);
612 gtk_widget_add_controller (GTK_WIDGET (calendar), GTK_EVENT_CONTROLLER (source));
613
614 controller =
615 gtk_event_controller_scroll_new (flags: GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
616 GTK_EVENT_CONTROLLER_SCROLL_DISCRETE);
617 g_signal_connect (controller, "scroll",
618 G_CALLBACK (gtk_calendar_scroll_controller_scroll),
619 calendar);
620 gtk_widget_add_controller (GTK_WIDGET (calendar), controller);
621
622 controller = gtk_event_controller_key_new ();
623 g_signal_connect (controller, "key-pressed",
624 G_CALLBACK (gtk_calendar_key_controller_key_pressed),
625 calendar);
626 gtk_widget_add_controller (GTK_WIDGET (calendar), controller);
627 controller = gtk_event_controller_focus_new ();
628 g_signal_connect (controller, "enter",
629 G_CALLBACK (gtk_calendar_focus_controller_focus),
630 calendar);
631 g_signal_connect (controller, "leave",
632 G_CALLBACK (gtk_calendar_focus_controller_focus),
633 calendar);
634 gtk_widget_add_controller (GTK_WIDGET (calendar), controller);
635
636#ifdef G_OS_WIN32
637 calendar->week_start = 0;
638 week_start = NULL;
639
640 if (GetLocaleInfoW (GetThreadLocale (), LOCALE_IFIRSTDAYOFWEEK,
641 wbuffer, G_N_ELEMENTS (wbuffer)))
642 week_start = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
643
644 if (week_start != NULL)
645 {
646 calendar->week_start = (week_start[0] - '0' + 1) % 7;
647 g_free(week_start);
648 }
649#else
650#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
651 langinfo.string = nl_langinfo (item: _NL_TIME_FIRST_WEEKDAY);
652 first_weekday = langinfo.string[0];
653 langinfo.string = nl_langinfo (item: _NL_TIME_WEEK_1STDAY);
654 week_origin = langinfo.word;
655 if (week_origin == 19971130) /* Sunday */
656 week_1stday = 0;
657 else if (week_origin == 19971201) /* Monday */
658 week_1stday = 1;
659 else
660 g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.");
661
662 calendar->week_start = (week_1stday + first_weekday - 1) % 7;
663#else
664 /* Translate to calendar:week_start:0 if you want Sunday to be the
665 * first day of the week to calendar:week_start:1 if you want Monday
666 * to be the first day of the week, and so on.
667 */
668 week_start = _("calendar:week_start:0");
669
670 if (strncmp (week_start, "calendar:week_start:", 20) == 0)
671 calendar->week_start = *(week_start + 20) - '0';
672 else
673 calendar->week_start = -1;
674
675 if (calendar->week_start < 0 || calendar->week_start > 6)
676 {
677 g_warning ("Whoever translated calendar:week_start:0 did so wrongly.");
678 calendar->week_start = 0;
679 }
680#endif
681#endif
682
683 if (!default_abbreviated_dayname[0])
684 for (i=0; i<7; i++)
685 {
686#ifndef G_OS_WIN32
687 tmp_time= (i+3)*86400; /* epoch was a Thursday, so add 3 days for Sunday */
688 strftime (s: buffer, maxsize: sizeof (buffer), format: "%a", tp: gmtime (timer: &tmp_time));
689 default_abbreviated_dayname[i] = g_locale_to_utf8 (opsysstring: buffer, len: -1, NULL, NULL, NULL);
690#else
691 if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SABBREVDAYNAME1 + (i+6)%7,
692 wbuffer, G_N_ELEMENTS (wbuffer)))
693 default_abbreviated_dayname[i] = g_strdup_printf ("(%d)", i);
694 else
695 default_abbreviated_dayname[i] = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
696#endif
697 }
698
699 if (!default_monthname[0])
700 for (i=0; i<12; i++)
701 {
702#ifndef G_OS_WIN32
703 tmp_time=i*2764800;
704 if (G_UNLIKELY (month_format == NULL))
705 {
706 buffer[0] = '\0';
707 month_format = "%OB";
708 strftime (s: buffer, maxsize: sizeof (buffer), format: month_format, tp: gmtime (timer: &tmp_time));
709 /* "%OB" is not supported in Linux with glibc < 2.27 */
710 if (!strcmp (s1: buffer, s2: "%OB") || !strcmp (s1: buffer, s2: "OB") || !strcmp (s1: buffer, s2: ""))
711 {
712 month_format = "%B";
713 strftime (s: buffer, maxsize: sizeof (buffer), format: month_format, tp: gmtime (timer: &tmp_time));
714 }
715 }
716 else
717 strftime (s: buffer, maxsize: sizeof (buffer), format: month_format, tp: gmtime (timer: &tmp_time));
718
719 default_monthname[i] = g_locale_to_utf8 (opsysstring: buffer, len: -1, NULL, NULL, NULL);
720#else
721 if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SMONTHNAME1 + i,
722 wbuffer, G_N_ELEMENTS (wbuffer)))
723 default_monthname[i] = g_strdup_printf ("(%d)", i);
724 else
725 default_monthname[i] = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
726#endif
727 }
728
729 for (i = 0; i < 12; i ++)
730 {
731 GtkWidget *month_label = gtk_label_new (str: default_monthname[i]);
732
733 gtk_stack_add_named (GTK_STACK (calendar->month_name_stack), child: month_label, name: default_monthname[i]);
734 }
735
736 calendar->grid = gtk_grid_new ();
737 gtk_grid_set_row_homogeneous (GTK_GRID (calendar->grid), TRUE);
738 gtk_grid_set_column_homogeneous (GTK_GRID (calendar->grid), TRUE);
739 /* Day name labels */
740 for (i = 0; i < 7; i ++)
741 {
742 int day;
743 GtkWidget *label;
744
745 day = (i + calendar->week_start) % 7;
746 label = gtk_label_new (str: default_abbreviated_dayname[day]);
747
748 gtk_widget_set_hexpand (widget: label, TRUE);
749 gtk_widget_set_vexpand (widget: label, TRUE);
750 gtk_widget_add_css_class (widget: label, css_class: "day-name");
751 gtk_grid_attach (GTK_GRID (calendar->grid), child: label, column: 1 + i, row: 0, width: 1, height: 1);
752
753 calendar->day_name_labels[i] = label;
754 }
755
756 /* Week number labels */
757 for (i = 0; i < 6; i ++)
758 {
759 GtkWidget *label = gtk_label_new (str: "");
760
761 gtk_widget_set_hexpand (widget: label, TRUE);
762 gtk_widget_set_vexpand (widget: label, TRUE);
763 gtk_widget_add_css_class (widget: label, css_class: "week-number");
764 gtk_grid_attach (GTK_GRID (calendar->grid), child: label, column: 0, row: 1 + i, width: 1, height: 1);
765
766 calendar->week_number_labels[i] = label;
767 gtk_widget_hide (widget: label);
768 }
769
770 {
771 int x, y;
772
773 for (y = 0; y < 6; y ++)
774 for (x = 0; x < 7; x ++)
775 {
776 GtkWidget *label = gtk_label_new (str: "");
777
778 gtk_widget_set_hexpand (widget: label, TRUE);
779 gtk_widget_set_vexpand (widget: label, TRUE);
780 gtk_widget_add_css_class (widget: label, css_class: "day-number");
781 gtk_grid_attach (GTK_GRID (calendar->grid), child: label, column: 1 + x, row: 1 + y, width: 1, height: 1);
782
783 calendar->day_number_labels[y][x] = label;
784 }
785 }
786
787 gtk_widget_set_hexpand (widget: calendar->grid, TRUE);
788 gtk_widget_set_vexpand (widget: calendar->grid, TRUE);
789 gtk_widget_set_parent (widget: calendar->grid, GTK_WIDGET (calendar));
790
791 for (i=0;i<31;i++)
792 calendar->marked_date[i] = FALSE;
793 calendar->num_marked_dates = 0;
794
795 calendar->show_heading = TRUE;
796 calendar->show_day_names = TRUE;
797
798 calendar->focus_row = -1;
799 calendar->focus_col = -1;
800
801 target = gtk_drop_target_new (G_TYPE_STRING, actions: GDK_ACTION_COPY);
802 gtk_drop_target_set_preload (self: target, TRUE);
803 g_signal_connect (target, "notify::value", G_CALLBACK (gtk_calendar_drag_notify_value), calendar);
804 g_signal_connect (target, "drop", G_CALLBACK (gtk_calendar_drag_drop), calendar);
805 gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (target));
806
807 calendar->year_before = 0;
808
809 /* Translate to calendar:YM if you want years to be displayed
810 * before months; otherwise translate to calendar:MY.
811 * Do *not* translate it to anything else, if it
812 * it isn't calendar:YM or calendar:MY it will not work.
813 *
814 * Note that the ordering described here is logical order, which is
815 * further influenced by BIDI ordering. Thus, if you have a default
816 * text direction of RTL and specify "calendar:YM", then the year
817 * will appear to the right of the month.
818 */
819 year_before = _("calendar:MY");
820 if (strcmp (s1: year_before, s2: "calendar:YM") == 0)
821 calendar->year_before = 1;
822 else if (strcmp (s1: year_before, s2: "calendar:MY") != 0)
823 g_warning ("Whoever translated calendar:MY did so wrongly.");
824
825 gtk_orientable_set_orientation (GTK_ORIENTABLE (gtk_widget_get_layout_manager (GTK_WIDGET (calendar))),
826 orientation: GTK_ORIENTATION_VERTICAL);
827
828 /* Select current day */
829 calendar->date = g_date_time_new_from_unix_local (t: 0);
830 now = g_date_time_new_now_local ();
831 gtk_calendar_select_day (self: calendar, date: now);
832 g_date_time_unref (datetime: now);
833
834 /* We just initialized the year label, now add some space to it so
835 * changing the year does not increase the calendar width */
836 gtk_widget_measure (widget: calendar->year_label, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
837 minimum: &min_year_width, NULL, NULL, NULL);
838 gtk_widget_set_size_request (widget: calendar->year_label, width: min_year_width + 10, height: -1);
839}
840
841#pragma GCC diagnostic pop
842
843static void
844calendar_queue_refresh (GtkCalendar *calendar)
845{
846 gtk_widget_queue_resize (GTK_WIDGET (calendar));
847}
848
849static void
850calendar_set_month_prev (GtkCalendar *calendar)
851{
852 GDateTime *new_date;
853
854 new_date = g_date_time_add_months (datetime: calendar->date, months: -1);
855
856 gtk_calendar_select_day (self: calendar, date: new_date);
857 g_date_time_unref (datetime: new_date);
858
859 g_signal_emit (instance: calendar, signal_id: gtk_calendar_signals[PREV_MONTH_SIGNAL], detail: 0);
860}
861
862static void
863calendar_set_month_next (GtkCalendar *calendar)
864{
865 GDateTime *new_date;
866
867 new_date = g_date_time_add_months (datetime: calendar->date, months: 1);
868
869 gtk_calendar_select_day (self: calendar, date: new_date);
870 g_date_time_unref (datetime: new_date);
871
872 g_signal_emit (instance: calendar, signal_id: gtk_calendar_signals[NEXT_MONTH_SIGNAL], detail: 0);
873}
874
875static void
876calendar_set_year_prev (GtkCalendar *calendar)
877{
878 GDateTime *new_date;
879
880 new_date = g_date_time_add_years (datetime: calendar->date, years: -1);
881
882 gtk_calendar_select_day (self: calendar, date: new_date);
883 g_date_time_unref (datetime: new_date);
884
885 g_signal_emit (instance: calendar, signal_id: gtk_calendar_signals[PREV_YEAR_SIGNAL], detail: 0);
886}
887
888static void
889calendar_set_year_next (GtkCalendar *calendar)
890{
891 GDateTime *new_date;
892
893 new_date = g_date_time_add_years (datetime: calendar->date, years: 1);
894
895 gtk_calendar_select_day (self: calendar, date: new_date);
896 g_date_time_unref (datetime: new_date);
897
898 g_signal_emit (instance: calendar, signal_id: gtk_calendar_signals[NEXT_YEAR_SIGNAL], detail: 0);
899}
900
901static void
902calendar_compute_days (GtkCalendar *calendar)
903{
904 const int month = g_date_time_get_month (datetime: calendar->date);
905 const int year = g_date_time_get_year (datetime: calendar->date);
906 int ndays_in_month;
907 int ndays_in_prev_month;
908 int first_day;
909 int row;
910 int col;
911 int day;
912
913 ndays_in_month = month_length[leap (year)][month];
914
915 first_day = day_of_week (year, mm: month, dd: 1);
916 first_day = (first_day + 7 - calendar->week_start) % 7;
917 if (first_day == 0)
918 first_day = 7;
919
920 /* Compute days of previous month */
921 if (month > 1)
922 ndays_in_prev_month = month_length[leap (year)][month - 1];
923 else
924 ndays_in_prev_month = month_length[leap (year: year - 1)][12];
925 day = ndays_in_prev_month - first_day+ 1;
926
927 for (col = 0; col < first_day; col++)
928 {
929 calendar->day[0][col] = day;
930 calendar->day_month[0][col] = MONTH_PREV;
931 day++;
932 }
933
934 /* Compute days of current month */
935 row = first_day / 7;
936 col = first_day % 7;
937 for (day = 1; day <= ndays_in_month; day++)
938 {
939 calendar->day[row][col] = day;
940 calendar->day_month[row][col] = MONTH_CURRENT;
941
942 col++;
943 if (col == 7)
944 {
945 row++;
946 col = 0;
947 }
948 }
949
950 /* Compute days of next month */
951 day = 1;
952 for (; row <= 5; row++)
953 {
954 for (; col <= 6; col++)
955 {
956 calendar->day[row][col] = day;
957 calendar->day_month[row][col] = MONTH_NEXT;
958 day++;
959 }
960 col = 0;
961 }
962}
963
964static void
965calendar_select_and_focus_day (GtkCalendar *calendar,
966 int day)
967{
968 GDateTime *new_date;
969 int row;
970 int col;
971
972 for (row = 0; row < 6; row ++)
973 for (col = 0; col < 7; col++)
974 {
975 if (calendar->day_month[row][col] == MONTH_CURRENT &&
976 calendar->day[row][col] == day)
977 {
978 calendar->focus_row = row;
979 calendar->focus_col = col;
980 break;
981 }
982 }
983
984 new_date = g_date_time_new_local (year: g_date_time_get_year (datetime: calendar->date),
985 month: g_date_time_get_month (datetime: calendar->date),
986 day,
987 hour: 0, minute: 0, seconds: 0);
988
989 gtk_calendar_select_day (self: calendar, date: new_date);
990 g_date_time_unref (datetime: new_date);
991}
992
993static void
994gtk_calendar_set_property (GObject *object,
995 guint prop_id,
996 const GValue *value,
997 GParamSpec *pspec)
998{
999 GtkCalendar *calendar = GTK_CALENDAR (object);
1000 GDateTime *date;
1001
1002 switch (prop_id)
1003 {
1004 case PROP_YEAR:
1005 date = g_date_time_new_local (year: g_value_get_int (value),
1006 month: g_date_time_get_month (datetime: calendar->date),
1007 day: g_date_time_get_day_of_month (datetime: calendar->date),
1008 hour: 0, minute: 0, seconds: 0);
1009 if (date)
1010 {
1011 gtk_calendar_select_day (self: calendar, date);
1012 g_date_time_unref (datetime: date);
1013 }
1014 break;
1015 case PROP_MONTH:
1016 date = g_date_time_new_local (year: g_date_time_get_year (datetime: calendar->date),
1017 month: g_value_get_int (value) + 1,
1018 day: g_date_time_get_day_of_month (datetime: calendar->date),
1019 hour: 0, minute: 0, seconds: 0);
1020 if (date)
1021 {
1022 gtk_calendar_select_day (self: calendar, date);
1023 g_date_time_unref (datetime: date);
1024 }
1025 break;
1026 case PROP_DAY:
1027 date = g_date_time_new_local (year: g_date_time_get_year (datetime: calendar->date),
1028 month: g_date_time_get_month (datetime: calendar->date),
1029 day: g_value_get_int (value),
1030 hour: 0, minute: 0, seconds: 0);
1031 if (date)
1032 {
1033 gtk_calendar_select_day (self: calendar, date);
1034 g_date_time_unref (datetime: date);
1035 }
1036 break;
1037 case PROP_SHOW_HEADING:
1038 gtk_calendar_set_show_heading (self: calendar, value: g_value_get_boolean (value));
1039 break;
1040 case PROP_SHOW_DAY_NAMES:
1041 gtk_calendar_set_show_day_names (self: calendar, value: g_value_get_boolean (value));
1042 break;
1043 case PROP_SHOW_WEEK_NUMBERS:
1044 gtk_calendar_set_show_week_numbers (self: calendar, value: g_value_get_boolean (value));
1045 break;
1046 default:
1047 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1048 break;
1049 }
1050}
1051
1052static void
1053gtk_calendar_get_property (GObject *object,
1054 guint prop_id,
1055 GValue *value,
1056 GParamSpec *pspec)
1057{
1058 GtkCalendar *calendar = GTK_CALENDAR (object);
1059
1060 switch (prop_id)
1061 {
1062 case PROP_YEAR:
1063 g_value_set_int (value, v_int: g_date_time_get_year (datetime: calendar->date));
1064 break;
1065 case PROP_MONTH:
1066 g_value_set_int (value, v_int: g_date_time_get_month (datetime: calendar->date) - 1);
1067 break;
1068 case PROP_DAY:
1069 g_value_set_int (value, v_int: g_date_time_get_day_of_month (datetime: calendar->date));
1070 break;
1071 case PROP_SHOW_HEADING:
1072 g_value_set_boolean (value, v_boolean: gtk_calendar_get_show_heading (self: calendar));
1073 break;
1074 case PROP_SHOW_DAY_NAMES:
1075 g_value_set_boolean (value, v_boolean: gtk_calendar_get_show_day_names (self: calendar));
1076 break;
1077 case PROP_SHOW_WEEK_NUMBERS:
1078 g_value_set_boolean (value, v_boolean: gtk_calendar_get_show_week_numbers (self: calendar));
1079 break;
1080 default:
1081 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1082 break;
1083 }
1084}
1085
1086static void
1087calendar_invalidate_day_num (GtkCalendar *calendar,
1088 int day)
1089{
1090 gtk_widget_queue_draw (GTK_WIDGET (calendar));
1091}
1092
1093static void
1094calendar_invalidate_day (GtkCalendar *calendar,
1095 int row,
1096 int col)
1097{
1098 gtk_widget_queue_draw (GTK_WIDGET (calendar));
1099}
1100
1101static void
1102gtk_calendar_button_press (GtkGestureClick *gesture,
1103 int n_press,
1104 double x,
1105 double y,
1106 gpointer user_data)
1107{
1108 GtkCalendar *calendar = user_data;
1109 GtkWidget *widget = GTK_WIDGET (calendar);
1110 GtkWidget *label;
1111 int row = -1, col = -1;
1112 int ix, iy;
1113 int day_month;
1114 int day;
1115
1116 label = gtk_widget_pick (widget, x, y, flags: GTK_PICK_DEFAULT);
1117 for (iy = 0; iy < 6; iy ++)
1118 for (ix = 0; ix < 7; ix ++)
1119 {
1120 if (label == calendar->day_number_labels[iy][ix])
1121 {
1122 row = iy;
1123 col = ix;
1124 }
1125 }
1126
1127 /* If row or column isn't found, just return. */
1128 if (row == -1 || col == -1)
1129 return;
1130
1131 day_month = calendar->day_month[row][col];
1132 day = calendar->day[row][col];
1133
1134 if (day_month == MONTH_PREV)
1135 calendar_set_month_prev (calendar);
1136 else if (day_month == MONTH_NEXT)
1137 calendar_set_month_next (calendar);
1138
1139 if (!gtk_widget_has_focus (widget))
1140 gtk_widget_grab_focus (widget);
1141
1142 calendar_select_and_focus_day (calendar, day);
1143}
1144
1145static gboolean
1146gtk_calendar_scroll_controller_scroll (GtkEventControllerScroll *scroll,
1147 double dx,
1148 double dy,
1149 GtkWidget *widget)
1150{
1151 GtkCalendar *calendar = GTK_CALENDAR (widget);
1152
1153 if (!gtk_widget_has_focus (widget))
1154 gtk_widget_grab_focus (widget);
1155
1156 if (dy < 0)
1157 calendar_set_month_prev (calendar);
1158 else if (dy > 0)
1159 calendar_set_month_next (calendar);
1160
1161 return GDK_EVENT_STOP;
1162}
1163
1164
1165/****************************************
1166 * Key handling *
1167 ****************************************/
1168
1169static void
1170move_focus (GtkCalendar *calendar,
1171 int direction,
1172 int updown)
1173{
1174 GtkTextDirection text_dir = gtk_widget_get_direction (GTK_WIDGET (calendar));
1175 int x, y;
1176
1177 if (updown == 1)
1178 {
1179 if (calendar->focus_row > 0)
1180 calendar->focus_row--;
1181 if (calendar->focus_row < 0)
1182 calendar->focus_row = 5;
1183 if (calendar->focus_col < 0)
1184 calendar->focus_col = 6;
1185 }
1186 else if (updown == -1)
1187 {
1188 if (calendar->focus_row < 5)
1189 calendar->focus_row++;
1190 if (calendar->focus_col < 0)
1191 calendar->focus_col = 0;
1192 }
1193 else if ((text_dir == GTK_TEXT_DIR_LTR && direction == -1) ||
1194 (text_dir == GTK_TEXT_DIR_RTL && direction == 1))
1195 {
1196 if (calendar->focus_col > 0)
1197 calendar->focus_col--;
1198 else if (calendar->focus_row > 0)
1199 {
1200 calendar->focus_col = 6;
1201 calendar->focus_row--;
1202 }
1203
1204 if (calendar->focus_col < 0)
1205 calendar->focus_col = 6;
1206 if (calendar->focus_row < 0)
1207 calendar->focus_row = 5;
1208 }
1209 else
1210 {
1211 if (calendar->focus_col < 6)
1212 calendar->focus_col++;
1213 else if (calendar->focus_row < 5)
1214 {
1215 calendar->focus_col = 0;
1216 calendar->focus_row++;
1217 }
1218
1219 if (calendar->focus_col < 0)
1220 calendar->focus_col = 0;
1221 if (calendar->focus_row < 0)
1222 calendar->focus_row = 0;
1223 }
1224
1225 for (y = 0; y < 6; y ++)
1226 for (x = 0; x < 7; x ++)
1227 {
1228 GtkWidget *label = calendar->day_number_labels[y][x];
1229
1230 if (calendar->focus_row == y && calendar->focus_col == x)
1231 gtk_widget_set_state_flags (widget: label, flags: GTK_STATE_FLAG_FOCUSED, FALSE);
1232 else
1233 gtk_widget_unset_state_flags (widget: label, flags: GTK_STATE_FLAG_FOCUSED);
1234 }
1235}
1236
1237static gboolean
1238gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *controller,
1239 guint keyval,
1240 guint keycode,
1241 GdkModifierType state,
1242 GtkWidget *widget)
1243{
1244 GtkCalendar *calendar = GTK_CALENDAR (widget);
1245 int return_val;
1246 int old_focus_row;
1247 int old_focus_col;
1248 int row, col, day;
1249
1250 return_val = FALSE;
1251
1252 old_focus_row = calendar->focus_row;
1253 old_focus_col = calendar->focus_col;
1254
1255 switch (keyval)
1256 {
1257 case GDK_KEY_KP_Left:
1258 case GDK_KEY_Left:
1259 return_val = TRUE;
1260 if (state & GDK_CONTROL_MASK)
1261 calendar_set_month_prev (calendar);
1262 else
1263 {
1264 move_focus (calendar, direction: -1, updown: 0);
1265 calendar_invalidate_day (calendar, row: old_focus_row, col: old_focus_col);
1266 calendar_invalidate_day (calendar, row: calendar->focus_row, col: calendar->focus_col);
1267 }
1268 break;
1269 case GDK_KEY_KP_Right:
1270 case GDK_KEY_Right:
1271 return_val = TRUE;
1272 if (state & GDK_CONTROL_MASK)
1273 calendar_set_month_next (calendar);
1274 else
1275 {
1276 move_focus (calendar, direction: 1, updown: 0);
1277 calendar_invalidate_day (calendar, row: old_focus_row, col: old_focus_col);
1278 calendar_invalidate_day (calendar, row: calendar->focus_row, col: calendar->focus_col);
1279 }
1280 break;
1281 case GDK_KEY_KP_Up:
1282 case GDK_KEY_Up:
1283 return_val = TRUE;
1284 if (state & GDK_CONTROL_MASK)
1285 calendar_set_year_prev (calendar);
1286 else
1287 {
1288 move_focus (calendar, direction: 0, updown: 1);
1289 calendar_invalidate_day (calendar, row: old_focus_row, col: old_focus_col);
1290 calendar_invalidate_day (calendar, row: calendar->focus_row, col: calendar->focus_col);
1291 }
1292 break;
1293 case GDK_KEY_KP_Down:
1294 case GDK_KEY_Down:
1295 return_val = TRUE;
1296 if (state & GDK_CONTROL_MASK)
1297 calendar_set_year_next (calendar);
1298 else
1299 {
1300 move_focus (calendar, direction: 0, updown: -1);
1301 calendar_invalidate_day (calendar, row: old_focus_row, col: old_focus_col);
1302 calendar_invalidate_day (calendar, row: calendar->focus_row, col: calendar->focus_col);
1303 }
1304 break;
1305 case GDK_KEY_KP_Space:
1306 case GDK_KEY_space:
1307 row = calendar->focus_row;
1308 col = calendar->focus_col;
1309
1310 if (row > -1 && col > -1)
1311 {
1312 return_val = TRUE;
1313
1314 day = calendar->day[row][col];
1315 if (calendar->day_month[row][col] == MONTH_PREV)
1316 calendar_set_month_prev (calendar);
1317 else if (calendar->day_month[row][col] == MONTH_NEXT)
1318 calendar_set_month_next (calendar);
1319
1320 calendar_select_and_focus_day (calendar, day);
1321 }
1322 break;
1323 default:
1324 break;
1325 }
1326
1327 return return_val;
1328}
1329
1330static void
1331gtk_calendar_focus_controller_focus (GtkEventController *controller,
1332 GtkWidget *widget)
1333{
1334 GtkCalendar *calendar = GTK_CALENDAR (widget);
1335
1336 calendar_queue_refresh (calendar);
1337}
1338
1339
1340/****************************************
1341 * Public API *
1342 ****************************************/
1343
1344/**
1345 * gtk_calendar_new:
1346 *
1347 * Creates a new calendar, with the current date being selected.
1348 *
1349 * Returns: a newly `GtkCalendar` widget
1350 */
1351GtkWidget*
1352gtk_calendar_new (void)
1353{
1354 return g_object_new (GTK_TYPE_CALENDAR, NULL);
1355}
1356
1357/**
1358 * gtk_calendar_select_day:
1359 * @self: a `GtkCalendar`.
1360 * @date: (transfer none): a `GDateTime` representing the day to select
1361 *
1362 * Switches to @date's year and month and select its day.
1363 */
1364void
1365gtk_calendar_select_day (GtkCalendar *calendar,
1366 GDateTime *date)
1367{
1368 GDateTime *today;
1369 int new_day, new_month, new_year;
1370 gboolean day_changed, month_changed, year_changed;
1371 char buffer[255];
1372 char *str;
1373 time_t tmp_time;
1374 struct tm *tm;
1375 int i;
1376 int x, y;
1377 int today_day;
1378
1379 g_return_if_fail (GTK_IS_CALENDAR (calendar));
1380 g_return_if_fail (date != NULL);
1381
1382 day_changed = g_date_time_get_day_of_month (datetime: calendar->date) != g_date_time_get_day_of_month (datetime: date);
1383 month_changed = g_date_time_get_month (datetime: calendar->date) != g_date_time_get_month (datetime: date);
1384 year_changed = g_date_time_get_year (datetime: calendar->date) != g_date_time_get_year (datetime: date);
1385
1386 if (!day_changed && !month_changed && !year_changed)
1387 return;
1388
1389 new_year = g_date_time_get_year (datetime: date);
1390 new_month = g_date_time_get_month (datetime: date);
1391 new_day = g_date_time_get_day_of_month (datetime: date);
1392
1393 g_date_time_unref (datetime: calendar->date);
1394 calendar->date = g_date_time_ref (datetime: date);
1395
1396 tmp_time = 1; /* Jan 1 1970, 00:00:01 UTC */
1397 tm = gmtime (timer: &tmp_time);
1398 tm->tm_year = new_year - 1900;
1399
1400 /* Translators: This dictates how the year is displayed in
1401 * gtkcalendar widget. See strftime() manual for the format.
1402 * Use only ASCII in the translation.
1403 *
1404 * "%Y" is appropriate for most locales.
1405 */
1406 strftime (s: buffer, maxsize: sizeof (buffer), C_("calendar year format", "%Y"), tp: tm);
1407 str = g_locale_to_utf8 (opsysstring: buffer, len: -1, NULL, NULL, NULL);
1408 gtk_label_set_label (GTK_LABEL (calendar->year_label), str);
1409 g_free (mem: str);
1410
1411 /* Update month */
1412
1413 calendar_compute_days (calendar);
1414 gtk_stack_set_visible_child_name (GTK_STACK (calendar->month_name_stack),
1415 name: default_monthname[new_month - 1]);
1416
1417 today = g_date_time_new_now_local ();
1418
1419 if (g_date_time_get_year (datetime: calendar->date) == g_date_time_get_year (datetime: today) &&
1420 g_date_time_get_month (datetime: calendar->date) == g_date_time_get_month (datetime: today))
1421 today_day = g_date_time_get_day_of_month (datetime: today);
1422 else
1423 today_day = -1;
1424
1425 g_date_time_unref (datetime: today);
1426
1427 /* Update day labels */
1428 for (y = 0; y < 6; y ++)
1429 for (x = 0; x < 7; x ++)
1430 {
1431 const int day = calendar->day[y][x];
1432 GtkWidget *label = calendar->day_number_labels[y][x];
1433 /* Translators: this defines whether the day numbers should use
1434 * localized digits or the ones used in English (0123...).
1435 *
1436 * Translate to "%Id" if you want to use localized digits, or
1437 * translate to "%d" otherwise.
1438 *
1439 * Note that translating this doesn't guarantee that you get localized
1440 * digits. That needs support from your system and locale definition
1441 * too.
1442 */
1443 g_snprintf (string: buffer, n: sizeof (buffer), C_("calendar:day:digits", "%d"), day);
1444
1445 gtk_label_set_label (GTK_LABEL (label), str: buffer);
1446
1447 if (calendar->day_month[y][x] == MONTH_PREV ||
1448 calendar->day_month[y][x] == MONTH_NEXT)
1449 gtk_widget_add_css_class (widget: label, css_class: "other-month");
1450 else
1451 gtk_widget_remove_css_class (widget: label, css_class: "other-month");
1452
1453 if (calendar->marked_date[day-1])
1454 gtk_widget_set_state_flags (widget: label, flags: GTK_STATE_FLAG_CHECKED, FALSE);
1455 else
1456 gtk_widget_unset_state_flags (widget: label, flags: GTK_STATE_FLAG_CHECKED);
1457
1458 if (new_day == day &&
1459 calendar->day_month[y][x] == MONTH_CURRENT)
1460 gtk_widget_set_state_flags (widget: label, flags: GTK_STATE_FLAG_SELECTED, FALSE);
1461 else
1462 gtk_widget_unset_state_flags (widget: label, flags: GTK_STATE_FLAG_SELECTED);
1463
1464 if (calendar->focus_row == y && calendar->focus_col == x)
1465 gtk_widget_set_state_flags (widget: label, flags: GTK_STATE_FLAG_FOCUSED, FALSE);
1466 else
1467 gtk_widget_unset_state_flags (widget: label, flags: GTK_STATE_FLAG_FOCUSED);
1468
1469 if (day == today_day &&
1470 calendar->day_month[y][x] == MONTH_CURRENT)
1471 gtk_widget_add_css_class (widget: label, css_class: "today");
1472 else
1473 gtk_widget_remove_css_class (widget: label, css_class: "today");
1474 }
1475
1476 /* Update week number labels.
1477 * We simply get the week number of calendar->date and add the others.
1478 * simple. */
1479 for (i = 0; i < 6; i ++)
1480 {
1481 int year = new_year;
1482 int month, week;
1483
1484 month = new_month + calendar->day_month[i][6] - MONTH_CURRENT;
1485
1486 if (month < 1)
1487 {
1488 month += 12;
1489 year -= 1;
1490 }
1491 else if (month > 12)
1492 {
1493 month -= 12;
1494 year += 1;
1495 }
1496
1497 week = week_of_year (year, mm: month, dd: calendar->day[i][6]);
1498
1499 /* Translators: this defines whether the week numbers should use
1500 * localized digits or the ones used in English (0123...).
1501 *
1502 * Translate to "%Id" if you want to use localized digits, or
1503 * translate to "%d" otherwise.
1504 * Note that translating this doesn't guarantee that you get localized
1505 * digits. That needs support from your system and locale definition
1506 * too. */
1507 g_snprintf (string: buffer, n: sizeof (buffer), C_("calendar:week:digits", "%d"), week);
1508
1509 gtk_label_set_label (GTK_LABEL (calendar->week_number_labels[i]), str: buffer);
1510 }
1511
1512 if (day_changed)
1513 {
1514 g_object_notify (G_OBJECT (calendar), property_name: "day");
1515 g_signal_emit (instance: calendar, signal_id: gtk_calendar_signals[DAY_SELECTED_SIGNAL], detail: 0);
1516 }
1517
1518 if (month_changed)
1519 g_object_notify (G_OBJECT (calendar), property_name: "month");
1520
1521 if (year_changed)
1522 g_object_notify (G_OBJECT (calendar), property_name: "year");
1523
1524}
1525
1526/**
1527 * gtk_calendar_clear_marks:
1528 * @calendar: a `GtkCalendar`
1529 *
1530 * Remove all visual markers.
1531 */
1532void
1533gtk_calendar_clear_marks (GtkCalendar *calendar)
1534{
1535 guint day;
1536
1537 g_return_if_fail (GTK_IS_CALENDAR (calendar));
1538
1539 for (day = 0; day < 31; day++)
1540 {
1541 calendar->marked_date[day] = FALSE;
1542 }
1543
1544 calendar->num_marked_dates = 0;
1545 calendar_queue_refresh (calendar);
1546}
1547
1548/**
1549 * gtk_calendar_mark_day:
1550 * @calendar: a `GtkCalendar`
1551 * @day: the day number to mark between 1 and 31.
1552 *
1553 * Places a visual marker on a particular day.
1554 */
1555void
1556gtk_calendar_mark_day (GtkCalendar *calendar,
1557 guint day)
1558{
1559 g_return_if_fail (GTK_IS_CALENDAR (calendar));
1560
1561 if (day >= 1 && day <= 31 && !calendar->marked_date[day-1])
1562 {
1563 calendar->marked_date[day - 1] = TRUE;
1564 calendar->num_marked_dates++;
1565 calendar_invalidate_day_num (calendar, day);
1566 }
1567}
1568
1569/**
1570 * gtk_calendar_get_day_is_marked:
1571 * @calendar: a `GtkCalendar`
1572 * @day: the day number between 1 and 31.
1573 *
1574 * Returns if the @day of the @calendar is already marked.
1575 *
1576 * Returns: whether the day is marked.
1577 */
1578gboolean
1579gtk_calendar_get_day_is_marked (GtkCalendar *calendar,
1580 guint day)
1581{
1582 g_return_val_if_fail (GTK_IS_CALENDAR (calendar), FALSE);
1583
1584 if (day >= 1 && day <= 31)
1585 return calendar->marked_date[day - 1];
1586
1587 return FALSE;
1588}
1589
1590/**
1591 * gtk_calendar_unmark_day:
1592 * @calendar: a `GtkCalendar`.
1593 * @day: the day number to unmark between 1 and 31.
1594 *
1595 * Removes the visual marker from a particular day.
1596 */
1597void
1598gtk_calendar_unmark_day (GtkCalendar *calendar,
1599 guint day)
1600{
1601 g_return_if_fail (GTK_IS_CALENDAR (calendar));
1602
1603 if (day >= 1 && day <= 31 && calendar->marked_date[day-1])
1604 {
1605 calendar->marked_date[day - 1] = FALSE;
1606 calendar->num_marked_dates--;
1607 calendar_invalidate_day_num (calendar, day);
1608 }
1609}
1610
1611/**
1612 * gtk_calendar_get_date:
1613 * @self: a `GtkCalendar`
1614 *
1615 * Returns a `GDateTime` representing the shown
1616 * year, month and the selected day.
1617 *
1618 * The returned date is in the local time zone.
1619 *
1620 * Returns: (transfer full): the `GDate` representing the shown date
1621 */
1622GDateTime *
1623gtk_calendar_get_date (GtkCalendar *self)
1624{
1625 g_return_val_if_fail (GTK_IS_CALENDAR (self), NULL);
1626
1627 return g_date_time_ref (datetime: self->date);
1628}
1629
1630/**
1631 * gtk_calendar_set_show_week_numbers: (attributes org.gtk.Method.set_property=show-week-numbers)
1632 * @self: a `GtkCalendar`
1633 * @value: whether to show week numbers on the left of the days
1634 *
1635 * Sets whether week numbers are shown in the calendar.
1636 */
1637void
1638gtk_calendar_set_show_week_numbers (GtkCalendar *self,
1639 gboolean value)
1640{
1641 int i;
1642
1643 g_return_if_fail (GTK_IS_CALENDAR (self));
1644
1645 if (self->show_week_numbers == value)
1646 return;
1647
1648 self->show_week_numbers = value;
1649
1650 for (i = 0; i < 6; i ++)
1651 gtk_widget_set_visible (widget: self->week_number_labels[i], visible: value);
1652
1653 g_object_notify (G_OBJECT (self), property_name: "show-week-numbers");
1654}
1655
1656/**
1657 * gtk_calendar_get_show_week_numbers: (attributes org.gtk.Method.get_property=show-week-numbers)
1658 * @self: a `GtkCalendar`
1659 *
1660 * Returns whether @self is showing week numbers right
1661 * now.
1662 *
1663 * This is the value of the [property@Gtk.Calendar:show-week-numbers]
1664 * property.
1665 *
1666 * Return: Whether the calendar is showing week numbers.
1667 */
1668gboolean
1669gtk_calendar_get_show_week_numbers (GtkCalendar *self)
1670{
1671 g_return_val_if_fail (GTK_IS_CALENDAR (self), FALSE);
1672
1673 return self->show_week_numbers;
1674}
1675
1676/**
1677 * gtk_calendar_set_show_heading: (attributes org.gtk.Method.set_property=show-heading)
1678 * @self: a `GtkCalendar`
1679 * @value: Whether to show the heading in the calendar
1680 *
1681 * Sets whether the calendar should show a heading.
1682 *
1683 * The heading contains the current year and month as well as
1684 * buttons for changing both.
1685 */
1686void
1687gtk_calendar_set_show_heading (GtkCalendar *self,
1688 gboolean value)
1689{
1690 g_return_if_fail (GTK_IS_CALENDAR (self));
1691
1692 if (self->show_heading == value)
1693 return;
1694
1695 self->show_heading = value;
1696
1697 gtk_widget_set_visible (widget: self->header_box, visible: value);
1698
1699 g_object_notify (G_OBJECT (self), property_name: "show-heading");
1700}
1701
1702/**
1703 * gtk_calendar_get_show_heading: (attributes org.gtk.Method.get_property=show-heading)
1704 * @self: a `GtkCalendar`
1705 *
1706 * Returns whether @self is currently showing the heading.
1707 *
1708 * This is the value of the [property@Gtk.Calendar:show-heading]
1709 * property.
1710 *
1711 * Return: Whether the calendar is showing a heading.
1712 */
1713gboolean
1714gtk_calendar_get_show_heading (GtkCalendar *self)
1715{
1716 g_return_val_if_fail (GTK_IS_CALENDAR (self), FALSE);
1717
1718 return self->show_heading;
1719}
1720
1721/**
1722 * gtk_calendar_set_show_day_names: (attributes org.gtk.Method.set_property=show-day-names)
1723 * @self: a `GtkCalendar`
1724 * @value: Whether to show day names above the day numbers
1725 *
1726 * Sets whether the calendar shows day names.
1727 */
1728void
1729gtk_calendar_set_show_day_names (GtkCalendar *self,
1730 gboolean value)
1731{
1732 int i;
1733
1734 g_return_if_fail (GTK_IS_CALENDAR (self));
1735
1736 if (self->show_day_names == value)
1737 return;
1738
1739 self->show_day_names = value;
1740
1741 for (i = 0; i < 7; i ++)
1742 gtk_widget_set_visible (widget: self->day_name_labels[i], visible: value);
1743
1744 g_object_notify (G_OBJECT (self), property_name: "show-day-names");
1745}
1746
1747/**
1748 * gtk_calendar_get_show_day_names: (attributes org.gtk.Method.get_property=show-day-names)
1749 * @self: a `GtkCalendar`
1750 *
1751 * Returns whether @self is currently showing the names
1752 * of the week days.
1753 *
1754 * This is the value of the [property@Gtk.Calendar:show-day-names]
1755 * property.
1756 *
1757 * Returns: Whether the calendar shows day names.
1758 */
1759gboolean
1760gtk_calendar_get_show_day_names (GtkCalendar *self)
1761{
1762 g_return_val_if_fail (GTK_IS_CALENDAR (self), FALSE);
1763
1764 return self->show_day_names;
1765}
1766

source code of gtk/gtk/gtkcalendar.c