]> Pileus Git - ~andy/gtk/blob - gtk/gtkcalendar.c
gtkcalendar: Remove unused variable
[~andy/gtk] / gtk / gtkcalendar.c
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  * lib_date routines
8  * Copyright (c) 1995, 1996, 1997, 1998 by Steffen Beyer
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free
22  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  */
24
25 /*
26  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
27  * file for a list of people on the GTK+ Team.  See the ChangeLog
28  * files for a list of changes.  These files are distributed with
29  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
30  */
31
32 /**
33  * SECTION:gtkcalendar
34  * @Short_description: Displays a calendar and allows the user to select a date
35  * @Title: GtkCalendar
36  *
37  * #GtkCalendar is a widget that displays a Gregorian calendar, one month
38  * at a time. It can be created with gtk_calendar_new().
39  *
40  * The month and year currently displayed can be altered with
41  * gtk_calendar_select_month(). The exact day can be selected from the
42  * displayed month using gtk_calendar_select_day().
43  *
44  * To place a visual marker on a particular day, use gtk_calendar_mark_day()
45  * and to remove the marker, gtk_calendar_unmark_day(). Alternative, all
46  * marks can be cleared with gtk_calendar_clear_marks().
47  *
48  * The way in which the calendar itself is displayed can be altered using
49  * gtk_calendar_set_display_options().
50  *
51  * The selected date can be retrieved from a #GtkCalendar using
52  * gtk_calendar_get_date().
53  *
54  * Users should be aware that, although the Gregorian calendar is the legal
55  * calendar in most countries, it was adopted progressively between 1582 and
56  * 1929. Display before these dates is likely to be historically incorrect.
57  */
58
59 #include "config.h"
60
61 #ifdef HAVE_SYS_TIME_H
62 #include <sys/time.h>
63 #endif
64 #ifdef HAVE__NL_TIME_FIRST_WEEKDAY
65 #include <langinfo.h>
66 #endif
67 #include <string.h>
68 #include <stdlib.h>
69 #include <time.h>
70
71 #include <glib.h>
72
73 #ifdef G_OS_WIN32
74 #include <windows.h>
75 #endif
76
77 #include "gtkcalendar.h"
78 #include "gtkdnd.h"
79 #include "gtkintl.h"
80 #include "gtkmain.h"
81 #include "gtkmarshalers.h"
82 #include "gtktooltip.h"
83 #include "gtkprivate.h"
84
85 /***************************************************************************/
86 /* The following date routines are taken from the lib_date package.
87  * They have been minimally edited to avoid conflict with types defined
88  * in win32 headers.
89  */
90
91 static const guint month_length[2][13] =
92 {
93   { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
94   { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
95 };
96
97 static const guint days_in_months[2][14] =
98 {
99   { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
100   { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
101 };
102
103 static glong  calc_days(guint year, guint mm, guint dd);
104 static guint  day_of_week(guint year, guint mm, guint dd);
105 static glong  dates_difference(guint year1, guint mm1, guint dd1,
106                                guint year2, guint mm2, guint dd2);
107 static guint  weeks_in_year(guint year);
108
109 static gboolean
110 leap (guint year)
111 {
112   return((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
113 }
114
115 static guint
116 day_of_week (guint year, guint mm, guint dd)
117 {
118   glong  days;
119
120   days = calc_days(year, mm, dd);
121   if (days > 0L)
122     {
123       days--;
124       days %= 7L;
125       days++;
126     }
127   return( (guint) days );
128 }
129
130 static guint weeks_in_year(guint year)
131 {
132   return(52 + ((day_of_week(year,1,1)==4) || (day_of_week(year,12,31)==4)));
133 }
134
135 static gboolean
136 check_date(guint year, guint mm, guint dd)
137 {
138   if (year < 1) return FALSE;
139   if ((mm < 1) || (mm > 12)) return FALSE;
140   if ((dd < 1) || (dd > month_length[leap(year)][mm])) return FALSE;
141   return TRUE;
142 }
143
144 static guint
145 week_number(guint year, guint mm, guint dd)
146 {
147   guint first;
148
149   first = day_of_week(year,1,1) - 1;
150   return( (guint) ( (dates_difference(year,1,1, year,mm,dd) + first) / 7L ) +
151           (first < 4) );
152 }
153
154 static glong
155 year_to_days(guint year)
156 {
157   return( year * 365L + (year / 4) - (year / 100) + (year / 400) );
158 }
159
160
161 static glong
162 calc_days(guint year, guint mm, guint dd)
163 {
164   gboolean lp;
165
166   if (year < 1) return(0L);
167   if ((mm < 1) || (mm > 12)) return(0L);
168   if ((dd < 1) || (dd > month_length[(lp = leap(year))][mm])) return(0L);
169   return( year_to_days(--year) + days_in_months[lp][mm] + dd );
170 }
171
172 static gboolean
173 week_of_year(guint *week, guint *year, guint mm, guint dd)
174 {
175   if (check_date(*year,mm,dd))
176     {
177       *week = week_number(*year,mm,dd);
178       if (*week == 0)
179         *week = weeks_in_year(--(*year));
180       else if (*week > weeks_in_year(*year))
181         {
182           *week = 1;
183           (*year)++;
184         }
185       return TRUE;
186     }
187   return FALSE;
188 }
189
190 static glong
191 dates_difference(guint year1, guint mm1, guint dd1,
192                  guint year2, guint mm2, guint dd2)
193 {
194   return( calc_days(year2, mm2, dd2) - calc_days(year1, mm1, dd1) );
195 }
196
197 /*** END OF lib_date routines ********************************************/
198
199 /* Spacing around day/week headers and main area, inside those windows */
200 #define CALENDAR_MARGIN          0
201
202 #define DAY_XSEP                 0 /* not really good for small calendar */
203 #define DAY_YSEP                 0 /* not really good for small calendar */
204
205 #define SCROLL_DELAY_FACTOR      5
206
207 enum {
208   ARROW_YEAR_LEFT,
209   ARROW_YEAR_RIGHT,
210   ARROW_MONTH_LEFT,
211   ARROW_MONTH_RIGHT
212 };
213
214 enum {
215   MONTH_PREV,
216   MONTH_CURRENT,
217   MONTH_NEXT
218 };
219
220 enum {
221   MONTH_CHANGED_SIGNAL,
222   DAY_SELECTED_SIGNAL,
223   DAY_SELECTED_DOUBLE_CLICK_SIGNAL,
224   PREV_MONTH_SIGNAL,
225   NEXT_MONTH_SIGNAL,
226   PREV_YEAR_SIGNAL,
227   NEXT_YEAR_SIGNAL,
228   LAST_SIGNAL
229 };
230
231 enum
232 {
233   PROP_0,
234   PROP_YEAR,
235   PROP_MONTH,
236   PROP_DAY,
237   PROP_SHOW_HEADING,
238   PROP_SHOW_DAY_NAMES,
239   PROP_NO_MONTH_CHANGE,
240   PROP_SHOW_WEEK_NUMBERS,
241   PROP_SHOW_DETAILS,
242   PROP_DETAIL_WIDTH_CHARS,
243   PROP_DETAIL_HEIGHT_ROWS
244 };
245
246 static guint gtk_calendar_signals[LAST_SIGNAL] = { 0 };
247
248 struct _GtkCalendarPrivate
249 {
250   GtkCalendarDisplayOptions display_flags;
251
252   GdkWindow *main_win;
253   GdkWindow *arrow_win[4];
254
255   gchar grow_space [32];
256
257   gint  month;
258   gint  year;
259   gint  selected_day;
260
261   gint  day_month[6][7];
262   gint  day[6][7];
263
264   gint  num_marked_dates;
265   gint  marked_date[31];
266
267   gint  focus_row;
268   gint  focus_col;
269
270   guint header_h;
271   guint day_name_h;
272   guint main_h;
273
274   guint arrow_state[4];
275   guint arrow_width;
276   guint max_month_width;
277   guint max_year_width;
278
279   guint day_width;
280   guint week_width;
281
282   guint min_day_width;
283   guint max_day_char_width;
284   guint max_day_char_ascent;
285   guint max_day_char_descent;
286   guint max_label_char_ascent;
287   guint max_label_char_descent;
288   guint max_week_char_width;
289
290   /* flags */
291   guint year_before : 1;
292
293   guint need_timer  : 1;
294
295   guint in_drag : 1;
296   guint drag_highlight : 1;
297
298   guint32 timer;
299   gint click_child;
300
301   gint week_start;
302
303   gint drag_start_x;
304   gint drag_start_y;
305
306   /* Optional callback, used to display extra information for each day. */
307   GtkCalendarDetailFunc detail_func;
308   gpointer              detail_func_user_data;
309   GDestroyNotify        detail_func_destroy;
310
311   /* Size requistion for details provided by the hook. */
312   gint detail_height_rows;
313   gint detail_width_chars;
314   gint detail_overflow[6];
315 };
316
317 #define GTK_CALENDAR_GET_PRIVATE(widget)  (GTK_CALENDAR (widget)->priv)
318
319 static void gtk_calendar_finalize     (GObject      *calendar);
320 static void gtk_calendar_destroy      (GtkWidget    *widget);
321 static void gtk_calendar_set_property (GObject      *object,
322                                        guint         prop_id,
323                                        const GValue *value,
324                                        GParamSpec   *pspec);
325 static void gtk_calendar_get_property (GObject      *object,
326                                        guint         prop_id,
327                                        GValue       *value,
328                                        GParamSpec   *pspec);
329
330 static void     gtk_calendar_realize        (GtkWidget        *widget);
331 static void     gtk_calendar_unrealize      (GtkWidget        *widget);
332 static void     gtk_calendar_map            (GtkWidget        *widget);
333 static void     gtk_calendar_unmap          (GtkWidget        *widget);
334 static void     gtk_calendar_get_preferred_width  (GtkWidget   *widget,
335                                                    gint        *minimum,
336                                                    gint        *natural);
337 static void     gtk_calendar_get_preferred_height (GtkWidget   *widget,
338                                                    gint        *minimum,
339                                                    gint        *natural);
340 static void     gtk_calendar_size_allocate  (GtkWidget        *widget,
341                                              GtkAllocation    *allocation);
342 static gboolean gtk_calendar_draw           (GtkWidget        *widget,
343                                              cairo_t          *cr);
344 static gboolean gtk_calendar_button_press   (GtkWidget        *widget,
345                                              GdkEventButton   *event);
346 static gboolean gtk_calendar_button_release (GtkWidget        *widget,
347                                              GdkEventButton   *event);
348 static gboolean gtk_calendar_motion_notify  (GtkWidget        *widget,
349                                              GdkEventMotion   *event);
350 static gboolean gtk_calendar_enter_notify   (GtkWidget        *widget,
351                                              GdkEventCrossing *event);
352 static gboolean gtk_calendar_leave_notify   (GtkWidget        *widget,
353                                              GdkEventCrossing *event);
354 static gboolean gtk_calendar_scroll         (GtkWidget        *widget,
355                                              GdkEventScroll   *event);
356 static gboolean gtk_calendar_key_press      (GtkWidget        *widget,
357                                              GdkEventKey      *event);
358 static gboolean gtk_calendar_focus_out      (GtkWidget        *widget,
359                                              GdkEventFocus    *event);
360 static void     gtk_calendar_grab_notify    (GtkWidget        *widget,
361                                              gboolean          was_grabbed);
362 static void     gtk_calendar_state_flags_changed  (GtkWidget     *widget,
363                                                    GtkStateFlags  previous_state);
364 static gboolean gtk_calendar_query_tooltip  (GtkWidget        *widget,
365                                              gint              x,
366                                              gint              y,
367                                              gboolean          keyboard_mode,
368                                              GtkTooltip       *tooltip);
369
370 static void     gtk_calendar_drag_data_get      (GtkWidget        *widget,
371                                                  GdkDragContext   *context,
372                                                  GtkSelectionData *selection_data,
373                                                  guint             info,
374                                                  guint             time);
375 static void     gtk_calendar_drag_data_received (GtkWidget        *widget,
376                                                  GdkDragContext   *context,
377                                                  gint              x,
378                                                  gint              y,
379                                                  GtkSelectionData *selection_data,
380                                                  guint             info,
381                                                  guint             time);
382 static gboolean gtk_calendar_drag_motion        (GtkWidget        *widget,
383                                                  GdkDragContext   *context,
384                                                  gint              x,
385                                                  gint              y,
386                                                  guint             time);
387 static void     gtk_calendar_drag_leave         (GtkWidget        *widget,
388                                                  GdkDragContext   *context,
389                                                  guint             time);
390 static gboolean gtk_calendar_drag_drop          (GtkWidget        *widget,
391                                                  GdkDragContext   *context,
392                                                  gint              x,
393                                                  gint              y,
394                                                  guint             time);
395
396 static void calendar_start_spinning (GtkCalendar *calendar,
397                                      gint         click_child);
398 static void calendar_stop_spinning  (GtkCalendar *calendar);
399
400 static void calendar_invalidate_day     (GtkCalendar *widget,
401                                          gint       row,
402                                          gint       col);
403 static void calendar_invalidate_day_num (GtkCalendar *widget,
404                                          gint       day);
405 static void calendar_invalidate_arrow   (GtkCalendar *widget,
406                                          guint      arrow);
407
408 static void calendar_compute_days      (GtkCalendar *calendar);
409 static gint calendar_get_xsep          (GtkCalendar *calendar);
410 static gint calendar_get_ysep          (GtkCalendar *calendar);
411 static gint calendar_get_inner_border  (GtkCalendar *calendar);
412
413 static char    *default_abbreviated_dayname[7];
414 static char    *default_monthname[12];
415
416 G_DEFINE_TYPE (GtkCalendar, gtk_calendar, GTK_TYPE_WIDGET)
417
418 static void
419 gtk_calendar_class_init (GtkCalendarClass *class)
420 {
421   GObjectClass   *gobject_class;
422   GtkWidgetClass *widget_class;
423
424   gobject_class = (GObjectClass*)  class;
425   widget_class = (GtkWidgetClass*) class;
426
427   gobject_class->set_property = gtk_calendar_set_property;
428   gobject_class->get_property = gtk_calendar_get_property;
429   gobject_class->finalize = gtk_calendar_finalize;
430
431   widget_class->destroy = gtk_calendar_destroy;
432   widget_class->realize = gtk_calendar_realize;
433   widget_class->unrealize = gtk_calendar_unrealize;
434   widget_class->map = gtk_calendar_map;
435   widget_class->unmap = gtk_calendar_unmap;
436   widget_class->draw = gtk_calendar_draw;
437   widget_class->get_preferred_width = gtk_calendar_get_preferred_width;
438   widget_class->get_preferred_height = gtk_calendar_get_preferred_height;
439   widget_class->size_allocate = gtk_calendar_size_allocate;
440   widget_class->button_press_event = gtk_calendar_button_press;
441   widget_class->button_release_event = gtk_calendar_button_release;
442   widget_class->motion_notify_event = gtk_calendar_motion_notify;
443   widget_class->enter_notify_event = gtk_calendar_enter_notify;
444   widget_class->leave_notify_event = gtk_calendar_leave_notify;
445   widget_class->key_press_event = gtk_calendar_key_press;
446   widget_class->scroll_event = gtk_calendar_scroll;
447   widget_class->state_flags_changed = gtk_calendar_state_flags_changed;
448   widget_class->grab_notify = gtk_calendar_grab_notify;
449   widget_class->focus_out_event = gtk_calendar_focus_out;
450   widget_class->query_tooltip = gtk_calendar_query_tooltip;
451
452   widget_class->drag_data_get = gtk_calendar_drag_data_get;
453   widget_class->drag_motion = gtk_calendar_drag_motion;
454   widget_class->drag_leave = gtk_calendar_drag_leave;
455   widget_class->drag_drop = gtk_calendar_drag_drop;
456   widget_class->drag_data_received = gtk_calendar_drag_data_received;
457
458   /**
459    * GtkCalendar:year:
460    *
461    * The selected year.
462    * This property gets initially set to the current year.
463    */
464   g_object_class_install_property (gobject_class,
465                                    PROP_YEAR,
466                                    g_param_spec_int ("year",
467                                                      P_("Year"),
468                                                      P_("The selected year"),
469                                                      0, G_MAXINT >> 9, 0,
470                                                      GTK_PARAM_READWRITE));
471
472   /**
473    * GtkCalendar:month:
474    *
475    * The selected month (as a number between 0 and 11).
476    * This property gets initially set to the current month.
477    */
478   g_object_class_install_property (gobject_class,
479                                    PROP_MONTH,
480                                    g_param_spec_int ("month",
481                                                      P_("Month"),
482                                                      P_("The selected month (as a number between 0 and 11)"),
483                                                      0, 11, 0,
484                                                      GTK_PARAM_READWRITE));
485
486   /**
487    * GtkCalendar:day:
488    *
489    * The selected day (as a number between 1 and 31, or 0
490    * to unselect the currently selected day).
491    * This property gets initially set to the current day.
492    */
493   g_object_class_install_property (gobject_class,
494                                    PROP_DAY,
495                                    g_param_spec_int ("day",
496                                                      P_("Day"),
497                                                      P_("The selected day (as a number between 1 and 31, or 0 to unselect the currently selected day)"),
498                                                      0, 31, 0,
499                                                      GTK_PARAM_READWRITE));
500
501 /**
502  * GtkCalendar:show-heading:
503  *
504  * Determines whether a heading is displayed.
505  *
506  * Since: 2.4
507  */
508   g_object_class_install_property (gobject_class,
509                                    PROP_SHOW_HEADING,
510                                    g_param_spec_boolean ("show-heading",
511                                                          P_("Show Heading"),
512                                                          P_("If TRUE, a heading is displayed"),
513                                                          TRUE,
514                                                          GTK_PARAM_READWRITE));
515
516 /**
517  * GtkCalendar:show-day-names:
518  *
519  * Determines whether day names are displayed.
520  *
521  * Since: 2.4
522  */
523   g_object_class_install_property (gobject_class,
524                                    PROP_SHOW_DAY_NAMES,
525                                    g_param_spec_boolean ("show-day-names",
526                                                          P_("Show Day Names"),
527                                                          P_("If TRUE, day names are displayed"),
528                                                          TRUE,
529                                                          GTK_PARAM_READWRITE));
530 /**
531  * GtkCalendar:no-month-change:
532  *
533  * Determines whether the selected month can be changed.
534  *
535  * Since: 2.4
536  */
537   g_object_class_install_property (gobject_class,
538                                    PROP_NO_MONTH_CHANGE,
539                                    g_param_spec_boolean ("no-month-change",
540                                                          P_("No Month Change"),
541                                                          P_("If TRUE, the selected month cannot be changed"),
542                                                          FALSE,
543                                                          GTK_PARAM_READWRITE));
544
545 /**
546  * GtkCalendar:show-week-numbers:
547  *
548  * Determines whether week numbers are displayed.
549  *
550  * Since: 2.4
551  */
552   g_object_class_install_property (gobject_class,
553                                    PROP_SHOW_WEEK_NUMBERS,
554                                    g_param_spec_boolean ("show-week-numbers",
555                                                          P_("Show Week Numbers"),
556                                                          P_("If TRUE, week numbers are displayed"),
557                                                          FALSE,
558                                                          GTK_PARAM_READWRITE));
559
560 /**
561  * GtkCalendar:detail-width-chars:
562  *
563  * Width of a detail cell, in characters.
564  * A value of 0 allows any width. See gtk_calendar_set_detail_func().
565  *
566  * Since: 2.14
567  */
568   g_object_class_install_property (gobject_class,
569                                    PROP_DETAIL_WIDTH_CHARS,
570                                    g_param_spec_int ("detail-width-chars",
571                                                      P_("Details Width"),
572                                                      P_("Details width in characters"),
573                                                      0, 127, 0,
574                                                      GTK_PARAM_READWRITE));
575
576 /**
577  * GtkCalendar:detail-height-rows:
578  *
579  * Height of a detail cell, in rows.
580  * A value of 0 allows any width. See gtk_calendar_set_detail_func().
581  *
582  * Since: 2.14
583  */
584   g_object_class_install_property (gobject_class,
585                                    PROP_DETAIL_HEIGHT_ROWS,
586                                    g_param_spec_int ("detail-height-rows",
587                                                      P_("Details Height"),
588                                                      P_("Details height in rows"),
589                                                      0, 127, 0,
590                                                      GTK_PARAM_READWRITE));
591
592 /**
593  * GtkCalendar:show-details:
594  *
595  * Determines whether details are shown directly in the widget, or if they are
596  * available only as tooltip. When this property is set days with details are
597  * marked.
598  *
599  * Since: 2.14
600  */
601   g_object_class_install_property (gobject_class,
602                                    PROP_SHOW_DETAILS,
603                                    g_param_spec_boolean ("show-details",
604                                                          P_("Show Details"),
605                                                          P_("If TRUE, details are shown"),
606                                                          TRUE,
607                                                          GTK_PARAM_READWRITE));
608
609
610   /**
611    * GtkCalendar:inner-border
612    *
613    * The spacing around the day/week headers and main area.
614    */
615   gtk_widget_class_install_style_property (widget_class,
616                                            g_param_spec_int ("inner-border",
617                                                              P_("Inner border"),
618                                                              P_("Inner border space"),
619                                                              0, G_MAXINT, 4,
620                                                              GTK_PARAM_READABLE));
621
622   /**
623    * GtkCalndar:vertical-separation
624    *
625    * Separation between day headers and main area.
626    */
627   gtk_widget_class_install_style_property (widget_class,
628                                            g_param_spec_int ("vertical-separation",
629                                                              P_("Vertical separation"),
630                                                              P_("Space between day headers and main area"),
631                                                              0, G_MAXINT, 4,
632                                                              GTK_PARAM_READABLE));
633
634   /**
635    * GtkCalendar:horizontal-separation
636    *
637    * Separation between week headers and main area.
638    */
639   gtk_widget_class_install_style_property (widget_class,
640                                            g_param_spec_int ("horizontal-separation",
641                                                              P_("Horizontal separation"),
642                                                              P_("Space between week headers and main area"),
643                                                              0, G_MAXINT, 4,
644                                                              GTK_PARAM_READABLE));
645
646   /**
647    * GtkCalendar::month-changed:
648    * @calendar: the object which received the signal.
649    *
650    * Emitted when the user clicks a button to change the selected month on a
651    * calendar.
652    */
653   gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
654     g_signal_new (I_("month-changed"),
655                   G_OBJECT_CLASS_TYPE (gobject_class),
656                   G_SIGNAL_RUN_FIRST,
657                   G_STRUCT_OFFSET (GtkCalendarClass, month_changed),
658                   NULL, NULL,
659                   _gtk_marshal_VOID__VOID,
660                   G_TYPE_NONE, 0);
661
662   /**
663    * GtkCalendar::day-selected:
664    * @calendar: the object which received the signal.
665    *
666    * Emitted when the user selects a day.
667    */
668   gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
669     g_signal_new (I_("day-selected"),
670                   G_OBJECT_CLASS_TYPE (gobject_class),
671                   G_SIGNAL_RUN_FIRST,
672                   G_STRUCT_OFFSET (GtkCalendarClass, day_selected),
673                   NULL, NULL,
674                   _gtk_marshal_VOID__VOID,
675                   G_TYPE_NONE, 0);
676
677   /**
678    * GtkCalendar::day-selected-double-click:
679    * @calendar: the object which received the signal.
680    *
681    * Emitted when the user double-clicks a day.
682    */
683   gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
684     g_signal_new (I_("day-selected-double-click"),
685                   G_OBJECT_CLASS_TYPE (gobject_class),
686                   G_SIGNAL_RUN_FIRST,
687                   G_STRUCT_OFFSET (GtkCalendarClass, day_selected_double_click),
688                   NULL, NULL,
689                   _gtk_marshal_VOID__VOID,
690                   G_TYPE_NONE, 0);
691
692   /**
693    * GtkCalendar::prev-month:
694    * @calendar: the object which received the signal.
695    *
696    * Emitted when the user switched to the previous month.
697    */
698   gtk_calendar_signals[PREV_MONTH_SIGNAL] =
699     g_signal_new (I_("prev-month"),
700                   G_OBJECT_CLASS_TYPE (gobject_class),
701                   G_SIGNAL_RUN_FIRST,
702                   G_STRUCT_OFFSET (GtkCalendarClass, prev_month),
703                   NULL, NULL,
704                   _gtk_marshal_VOID__VOID,
705                   G_TYPE_NONE, 0);
706
707   /**
708    * GtkCalendar::next-month:
709    * @calendar: the object which received the signal.
710    *
711    * Emitted when the user switched to the next month.
712    */
713   gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
714     g_signal_new (I_("next-month"),
715                   G_OBJECT_CLASS_TYPE (gobject_class),
716                   G_SIGNAL_RUN_FIRST,
717                   G_STRUCT_OFFSET (GtkCalendarClass, next_month),
718                   NULL, NULL,
719                   _gtk_marshal_VOID__VOID,
720                   G_TYPE_NONE, 0);
721
722   /**
723    * GtkCalendar::prev-year:
724    * @calendar: the object which received the signal.
725    *
726    * Emitted when user switched to the previous year.
727    */
728   gtk_calendar_signals[PREV_YEAR_SIGNAL] =
729     g_signal_new (I_("prev-year"),
730                   G_OBJECT_CLASS_TYPE (gobject_class),
731                   G_SIGNAL_RUN_FIRST,
732                   G_STRUCT_OFFSET (GtkCalendarClass, prev_year),
733                   NULL, NULL,
734                   _gtk_marshal_VOID__VOID,
735                   G_TYPE_NONE, 0);
736
737   /**
738    * GtkCalendar::next-year:
739    * @calendar: the object which received the signal.
740    *
741    * Emitted when user switched to the next year.
742    */
743   gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
744     g_signal_new (I_("next-year"),
745                   G_OBJECT_CLASS_TYPE (gobject_class),
746                   G_SIGNAL_RUN_FIRST,
747                   G_STRUCT_OFFSET (GtkCalendarClass, next_year),
748                   NULL, NULL,
749                   _gtk_marshal_VOID__VOID,
750                   G_TYPE_NONE, 0);
751
752   g_type_class_add_private (gobject_class, sizeof (GtkCalendarPrivate));
753 }
754
755 static void
756 gtk_calendar_init (GtkCalendar *calendar)
757 {
758   GtkWidget *widget = GTK_WIDGET (calendar);
759   time_t secs;
760   struct tm *tm;
761   gint i;
762 #ifdef G_OS_WIN32
763   wchar_t wbuffer[100];
764 #else
765   char buffer[255];
766   time_t tmp_time;
767 #endif
768   GtkCalendarPrivate *priv;
769   gchar *year_before;
770 #ifdef HAVE__NL_TIME_FIRST_WEEKDAY
771   union { unsigned int word; char *string; } langinfo;
772   gint week_1stday = 0;
773   gint first_weekday = 1;
774   guint week_origin;
775 #else
776   gchar *week_start;
777 #endif
778
779   priv = calendar->priv = G_TYPE_INSTANCE_GET_PRIVATE (calendar,
780                                                        GTK_TYPE_CALENDAR,
781                                                        GtkCalendarPrivate);
782
783   gtk_widget_set_can_focus (widget, TRUE);
784   gtk_widget_set_has_window (widget, FALSE);
785
786   if (!default_abbreviated_dayname[0])
787     for (i=0; i<7; i++)
788       {
789 #ifndef G_OS_WIN32
790         tmp_time= (i+3)*86400;
791         strftime ( buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
792         default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
793 #else
794         if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SABBREVDAYNAME1 + (i+6)%7,
795                              wbuffer, G_N_ELEMENTS (wbuffer)))
796           default_abbreviated_dayname[i] = g_strdup_printf ("(%d)", i);
797         else
798           default_abbreviated_dayname[i] = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
799 #endif
800       }
801
802   if (!default_monthname[0])
803     for (i=0; i<12; i++)
804       {
805 #ifndef G_OS_WIN32
806         tmp_time=i*2764800;
807         strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
808         default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
809 #else
810         if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SMONTHNAME1 + i,
811                              wbuffer, G_N_ELEMENTS (wbuffer)))
812           default_monthname[i] = g_strdup_printf ("(%d)", i);
813         else
814           default_monthname[i] = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
815 #endif
816       }
817
818   /* Set defaults */
819   secs = time (NULL);
820   tm = localtime (&secs);
821   priv->month = tm->tm_mon;
822   priv->year  = 1900 + tm->tm_year;
823
824   for (i=0;i<31;i++)
825     priv->marked_date[i] = FALSE;
826   priv->num_marked_dates = 0;
827   priv->selected_day = tm->tm_mday;
828
829   priv->display_flags = (GTK_CALENDAR_SHOW_HEADING |
830                              GTK_CALENDAR_SHOW_DAY_NAMES |
831                              GTK_CALENDAR_SHOW_DETAILS);
832
833   priv->focus_row = -1;
834   priv->focus_col = -1;
835
836   priv->max_year_width = 0;
837   priv->max_month_width = 0;
838   priv->max_day_char_width = 0;
839   priv->max_week_char_width = 0;
840
841   priv->max_day_char_ascent = 0;
842   priv->max_day_char_descent = 0;
843   priv->max_label_char_ascent = 0;
844   priv->max_label_char_descent = 0;
845
846   priv->arrow_width = 10;
847
848   priv->need_timer = 0;
849   priv->timer = 0;
850   priv->click_child = -1;
851
852   priv->in_drag = 0;
853   priv->drag_highlight = 0;
854
855   gtk_drag_dest_set (widget, 0, NULL, 0, GDK_ACTION_COPY);
856   gtk_drag_dest_add_text_targets (widget);
857
858   priv->year_before = 0;
859
860   /* Translate to calendar:YM if you want years to be displayed
861    * before months; otherwise translate to calendar:MY.
862    * Do *not* translate it to anything else, if it
863    * it isn't calendar:YM or calendar:MY it will not work.
864    *
865    * Note that the ordering described here is logical order, which is
866    * further influenced by BIDI ordering. Thus, if you have a default
867    * text direction of RTL and specify "calendar:YM", then the year
868    * will appear to the right of the month.
869    */
870   year_before = _("calendar:MY");
871   if (strcmp (year_before, "calendar:YM") == 0)
872     priv->year_before = 1;
873   else if (strcmp (year_before, "calendar:MY") != 0)
874     g_warning ("Whoever translated calendar:MY did so wrongly.\n");
875
876 #ifdef G_OS_WIN32
877   priv->week_start = 0;
878   week_start = NULL;
879
880   if (GetLocaleInfoW (GetThreadLocale (), LOCALE_IFIRSTDAYOFWEEK,
881                       wbuffer, G_N_ELEMENTS (wbuffer)))
882     week_start = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
883
884   if (week_start != NULL)
885     {
886       priv->week_start = (week_start[0] - '0' + 1) % 7;
887       g_free(week_start);
888     }
889 #else
890 #ifdef HAVE__NL_TIME_FIRST_WEEKDAY
891   langinfo.string = nl_langinfo (_NL_TIME_FIRST_WEEKDAY);
892   first_weekday = langinfo.string[0];
893   langinfo.string = nl_langinfo (_NL_TIME_WEEK_1STDAY);
894   week_origin = langinfo.word;
895   if (week_origin == 19971130) /* Sunday */
896     week_1stday = 0;
897   else if (week_origin == 19971201) /* Monday */
898     week_1stday = 1;
899   else
900     g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.\n");
901
902   priv->week_start = (week_1stday + first_weekday - 1) % 7;
903 #else
904   /* Translate to calendar:week_start:0 if you want Sunday to be the
905    * first day of the week to calendar:week_start:1 if you want Monday
906    * to be the first day of the week, and so on.
907    */
908   week_start = _("calendar:week_start:0");
909
910   if (strncmp (week_start, "calendar:week_start:", 20) == 0)
911     priv->week_start = *(week_start + 20) - '0';
912   else
913     priv->week_start = -1;
914
915   if (priv->week_start < 0 || priv->week_start > 6)
916     {
917       g_warning ("Whoever translated calendar:week_start:0 did so wrongly.\n");
918       priv->week_start = 0;
919     }
920 #endif
921 #endif
922
923   calendar_compute_days (calendar);
924 }
925
926 \f
927 /****************************************
928  *          Utility Functions           *
929  ****************************************/
930
931 static void
932 calendar_queue_refresh (GtkCalendar *calendar)
933 {
934   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
935
936   if (!(priv->detail_func) ||
937       !(priv->display_flags & GTK_CALENDAR_SHOW_DETAILS) ||
938        (priv->detail_width_chars && priv->detail_height_rows))
939     gtk_widget_queue_draw (GTK_WIDGET (calendar));
940   else
941     gtk_widget_queue_resize (GTK_WIDGET (calendar));
942 }
943
944 static void
945 calendar_set_month_next (GtkCalendar *calendar)
946 {
947   gint month_len;
948   GtkCalendarPrivate *priv = calendar->priv;
949
950   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
951     return;
952
953   if (priv->month == 11)
954     {
955       priv->month = 0;
956       priv->year++;
957     }
958   else
959     priv->month++;
960
961   calendar_compute_days (calendar);
962   g_signal_emit (calendar,
963                  gtk_calendar_signals[NEXT_MONTH_SIGNAL],
964                  0);
965   g_signal_emit (calendar,
966                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
967                  0);
968
969   month_len = month_length[leap (priv->year)][priv->month + 1];
970
971   if (month_len < priv->selected_day)
972     {
973       priv->selected_day = 0;
974       gtk_calendar_select_day (calendar, month_len);
975     }
976   else
977     gtk_calendar_select_day (calendar, priv->selected_day);
978
979   calendar_queue_refresh (calendar);
980 }
981
982 static void
983 calendar_set_year_prev (GtkCalendar *calendar)
984 {
985   GtkCalendarPrivate *priv = calendar->priv;
986   gint month_len;
987
988   priv->year--;
989   calendar_compute_days (calendar);
990   g_signal_emit (calendar,
991                  gtk_calendar_signals[PREV_YEAR_SIGNAL],
992                  0);
993   g_signal_emit (calendar,
994                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
995                  0);
996
997   month_len = month_length[leap (priv->year)][priv->month + 1];
998
999   if (month_len < priv->selected_day)
1000     {
1001       priv->selected_day = 0;
1002       gtk_calendar_select_day (calendar, month_len);
1003     }
1004   else
1005     gtk_calendar_select_day (calendar, priv->selected_day);
1006
1007   calendar_queue_refresh (calendar);
1008 }
1009
1010 static void
1011 calendar_set_year_next (GtkCalendar *calendar)
1012 {
1013   GtkCalendarPrivate *priv = calendar->priv;
1014   gint month_len;
1015
1016   priv->year++;
1017   calendar_compute_days (calendar);
1018   g_signal_emit (calendar,
1019                  gtk_calendar_signals[NEXT_YEAR_SIGNAL],
1020                  0);
1021   g_signal_emit (calendar,
1022                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
1023                  0);
1024
1025   month_len = month_length[leap (priv->year)][priv->month + 1];
1026
1027   if (month_len < priv->selected_day)
1028     {
1029       priv->selected_day = 0;
1030       gtk_calendar_select_day (calendar, month_len);
1031     }
1032   else
1033     gtk_calendar_select_day (calendar, priv->selected_day);
1034
1035   calendar_queue_refresh (calendar);
1036 }
1037
1038 static void
1039 calendar_compute_days (GtkCalendar *calendar)
1040 {
1041   GtkCalendarPrivate *priv = calendar->priv;
1042   gint month;
1043   gint year;
1044   gint ndays_in_month;
1045   gint ndays_in_prev_month;
1046   gint first_day;
1047   gint row;
1048   gint col;
1049   gint day;
1050
1051   year = priv->year;
1052   month = priv->month + 1;
1053
1054   ndays_in_month = month_length[leap (year)][month];
1055
1056   first_day = day_of_week (year, month, 1);
1057   first_day = (first_day + 7 - priv->week_start) % 7;
1058
1059   /* Compute days of previous month */
1060   if (month > 1)
1061     ndays_in_prev_month = month_length[leap (year)][month-1];
1062   else
1063     ndays_in_prev_month = month_length[leap (year)][12];
1064   day = ndays_in_prev_month - first_day + 1;
1065
1066   row = 0;
1067   if (first_day > 0)
1068     {
1069       for (col = 0; col < first_day; col++)
1070         {
1071           priv->day[row][col] = day;
1072           priv->day_month[row][col] = MONTH_PREV;
1073           day++;
1074         }
1075     }
1076
1077   /* Compute days of current month */
1078   col = first_day;
1079   for (day = 1; day <= ndays_in_month; day++)
1080     {
1081       priv->day[row][col] = day;
1082       priv->day_month[row][col] = MONTH_CURRENT;
1083
1084       col++;
1085       if (col == 7)
1086         {
1087           row++;
1088           col = 0;
1089         }
1090     }
1091
1092   /* Compute days of next month */
1093   day = 1;
1094   for (; row <= 5; row++)
1095     {
1096       for (; col <= 6; col++)
1097         {
1098           priv->day[row][col] = day;
1099           priv->day_month[row][col] = MONTH_NEXT;
1100           day++;
1101         }
1102       col = 0;
1103     }
1104 }
1105
1106 static void
1107 calendar_select_and_focus_day (GtkCalendar *calendar,
1108                                guint        day)
1109 {
1110   GtkCalendarPrivate *priv = calendar->priv;
1111   gint old_focus_row = priv->focus_row;
1112   gint old_focus_col = priv->focus_col;
1113   gint row;
1114   gint col;
1115
1116   for (row = 0; row < 6; row ++)
1117     for (col = 0; col < 7; col++)
1118       {
1119         if (priv->day_month[row][col] == MONTH_CURRENT
1120             && priv->day[row][col] == day)
1121           {
1122             priv->focus_row = row;
1123             priv->focus_col = col;
1124           }
1125       }
1126
1127   if (old_focus_row != -1 && old_focus_col != -1)
1128     calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
1129
1130   gtk_calendar_select_day (calendar, day);
1131 }
1132
1133 \f
1134 /****************************************
1135  *     Layout computation utilities     *
1136  ****************************************/
1137
1138 static gint
1139 calendar_row_height (GtkCalendar *calendar)
1140 {
1141   GtkCalendarPrivate *priv = calendar->priv;
1142
1143   return (GTK_CALENDAR_GET_PRIVATE (calendar)->main_h - CALENDAR_MARGIN
1144           - ((priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
1145              ? calendar_get_ysep (calendar) : CALENDAR_MARGIN)) / 6;
1146 }
1147
1148
1149 /* calendar_left_x_for_column: returns the x coordinate
1150  * for the left of the column */
1151 static gint
1152 calendar_left_x_for_column (GtkCalendar *calendar,
1153                             gint         column)
1154 {
1155   GtkCalendarPrivate *priv = calendar->priv;
1156   gint width;
1157   gint x_left;
1158   gint week_width;
1159   gint calendar_xsep = calendar_get_xsep (calendar);
1160   GtkStyleContext *context;
1161   GtkStateFlags state;
1162   gint inner_border = calendar_get_inner_border (calendar);
1163   GtkBorder padding;
1164
1165   context = gtk_widget_get_style_context (GTK_WIDGET (calendar));
1166   state = gtk_widget_get_state_flags (GTK_WIDGET (calendar));
1167   gtk_style_context_get_padding (context, state, &padding);
1168
1169   week_width = priv->week_width + padding.left + inner_border;
1170
1171   if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
1172     {
1173       column = 6 - column;
1174       week_width = 0;
1175     }
1176
1177   width = GTK_CALENDAR_GET_PRIVATE (calendar)->day_width;
1178   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
1179     x_left = week_width + calendar_xsep + (width + DAY_XSEP) * column;
1180   else
1181     x_left = week_width + CALENDAR_MARGIN + (width + DAY_XSEP) * column;
1182
1183   return x_left;
1184 }
1185
1186 /* column_from_x: returns the column 0-6 that the
1187  * x pixel of the xwindow is in */
1188 static gint
1189 calendar_column_from_x (GtkCalendar *calendar,
1190                         gint         event_x)
1191 {
1192   gint c, column;
1193   gint x_left, x_right;
1194
1195   column = -1;
1196
1197   for (c = 0; c < 7; c++)
1198     {
1199       x_left = calendar_left_x_for_column (calendar, c);
1200       x_right = x_left + GTK_CALENDAR_GET_PRIVATE (calendar)->day_width;
1201
1202       if (event_x >= x_left && event_x < x_right)
1203         {
1204           column = c;
1205           break;
1206         }
1207     }
1208
1209   return column;
1210 }
1211
1212 /* calendar_top_y_for_row: returns the y coordinate
1213  * for the top of the row */
1214 static gint
1215 calendar_top_y_for_row (GtkCalendar *calendar,
1216                         gint         row)
1217 {
1218   GtkStyleContext *context;
1219   GtkAllocation allocation;
1220   gint inner_border = calendar_get_inner_border (calendar);
1221   GtkStateFlags state;
1222   GtkBorder padding;
1223
1224   gtk_widget_get_allocation (GTK_WIDGET (calendar), &allocation);
1225   context = gtk_widget_get_style_context (GTK_WIDGET (calendar));
1226   state = gtk_widget_get_state_flags (GTK_WIDGET (calendar));
1227
1228   gtk_style_context_get_padding (context, state, &padding);
1229
1230   return  allocation.height
1231           - padding.top - inner_border
1232           - (CALENDAR_MARGIN + (6 - row)
1233              * calendar_row_height (calendar));
1234 }
1235
1236 /* row_from_y: returns the row 0-5 that the
1237  * y pixel of the xwindow is in */
1238 static gint
1239 calendar_row_from_y (GtkCalendar *calendar,
1240                      gint         event_y)
1241 {
1242   gint r, row;
1243   gint height;
1244   gint y_top, y_bottom;
1245
1246   height = calendar_row_height (calendar);
1247   row = -1;
1248
1249   for (r = 0; r < 6; r++)
1250     {
1251       y_top = calendar_top_y_for_row (calendar, r);
1252       y_bottom = y_top + height;
1253
1254       if (event_y >= y_top && event_y < y_bottom)
1255         {
1256           row = r;
1257           break;
1258         }
1259     }
1260
1261   return row;
1262 }
1263
1264 static void
1265 calendar_arrow_rectangle (GtkCalendar  *calendar,
1266                           guint         arrow,
1267                           GdkRectangle *rect)
1268 {
1269   GtkWidget *widget = GTK_WIDGET (calendar);
1270   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1271   GtkAllocation allocation;
1272   GtkStyleContext *context;
1273   GtkStateFlags state;
1274   GtkBorder padding;
1275   gboolean year_left;
1276
1277   gtk_widget_get_allocation (widget, &allocation);
1278   context = gtk_widget_get_style_context (widget);
1279   state = gtk_widget_get_state_flags (widget);
1280
1281   gtk_style_context_get_padding (context, state, &padding);
1282
1283   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
1284     year_left = priv->year_before;
1285   else
1286     year_left = !priv->year_before;
1287
1288   rect->y = 3;
1289   rect->width = priv->arrow_width;
1290   rect->height = priv->header_h - 7;
1291
1292   switch (arrow)
1293     {
1294     case ARROW_MONTH_LEFT:
1295       if (year_left)
1296         rect->x = (allocation.width - padding.left - padding.right
1297                    - (3 + 2 * priv->arrow_width + priv->max_month_width));
1298       else
1299         rect->x = 3;
1300       break;
1301     case ARROW_MONTH_RIGHT:
1302       if (year_left)
1303         rect->x = (allocation.width - padding.left - padding.right
1304                    - 3 - priv->arrow_width);
1305       else
1306         rect->x = (priv->arrow_width + priv->max_month_width);
1307       break;
1308     case ARROW_YEAR_LEFT:
1309       if (year_left)
1310         rect->x = 3;
1311       else
1312         rect->x = (allocation.width - padding.left - padding.right
1313                    - (3 + 2 * priv->arrow_width + priv->max_year_width));
1314       break;
1315     case ARROW_YEAR_RIGHT:
1316       if (year_left)
1317         rect->x = (priv->arrow_width + priv->max_year_width);
1318       else
1319         rect->x = (allocation.width - padding.left - padding.right
1320                    - 3 - priv->arrow_width);
1321       break;
1322     }
1323
1324   rect->x += padding.left;
1325   rect->y += padding.top;
1326 }
1327
1328 static void
1329 calendar_day_rectangle (GtkCalendar  *calendar,
1330                         gint          row,
1331                         gint          col,
1332                         GdkRectangle *rect)
1333 {
1334   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1335
1336   rect->x = calendar_left_x_for_column (calendar, col);
1337   rect->y = calendar_top_y_for_row (calendar, row);
1338   rect->height = calendar_row_height (calendar);
1339   rect->width = priv->day_width;
1340 }
1341
1342 static void
1343 calendar_set_month_prev (GtkCalendar *calendar)
1344 {
1345   GtkCalendarPrivate *priv = calendar->priv;
1346   gint month_len;
1347
1348   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
1349     return;
1350
1351   if (priv->month == 0)
1352     {
1353       priv->month = 11;
1354       priv->year--;
1355     }
1356   else
1357     priv->month--;
1358
1359   month_len = month_length[leap (priv->year)][priv->month + 1];
1360
1361   calendar_compute_days (calendar);
1362
1363   g_signal_emit (calendar,
1364                  gtk_calendar_signals[PREV_MONTH_SIGNAL],
1365                  0);
1366   g_signal_emit (calendar,
1367                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
1368                  0);
1369
1370   if (month_len < priv->selected_day)
1371     {
1372       priv->selected_day = 0;
1373       gtk_calendar_select_day (calendar, month_len);
1374     }
1375   else
1376     {
1377       if (priv->selected_day < 0)
1378         priv->selected_day = priv->selected_day + 1 + month_length[leap (priv->year)][priv->month + 1];
1379       gtk_calendar_select_day (calendar, priv->selected_day);
1380     }
1381
1382   calendar_queue_refresh (calendar);
1383 }
1384
1385 \f
1386 /****************************************
1387  *           Basic object methods       *
1388  ****************************************/
1389
1390 static void
1391 gtk_calendar_finalize (GObject *object)
1392 {
1393   G_OBJECT_CLASS (gtk_calendar_parent_class)->finalize (object);
1394 }
1395
1396 static void
1397 gtk_calendar_destroy (GtkWidget *widget)
1398 {
1399   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1400
1401   calendar_stop_spinning (GTK_CALENDAR (widget));
1402
1403   /* Call the destroy function for the extra display callback: */
1404   if (priv->detail_func_destroy && priv->detail_func_user_data)
1405     {
1406       priv->detail_func_destroy (priv->detail_func_user_data);
1407       priv->detail_func_user_data = NULL;
1408       priv->detail_func_destroy = NULL;
1409     }
1410
1411   GTK_WIDGET_CLASS (gtk_calendar_parent_class)->destroy (widget);
1412 }
1413
1414
1415 static void
1416 calendar_set_display_option (GtkCalendar              *calendar,
1417                              GtkCalendarDisplayOptions flag,
1418                              gboolean                  setting)
1419 {
1420   GtkCalendarPrivate *priv = calendar->priv;
1421   GtkCalendarDisplayOptions flags;
1422
1423   if (setting)
1424     flags = priv->display_flags | flag;
1425   else
1426     flags = priv->display_flags & ~flag;
1427   gtk_calendar_set_display_options (calendar, flags);
1428 }
1429
1430 static gboolean
1431 calendar_get_display_option (GtkCalendar              *calendar,
1432                              GtkCalendarDisplayOptions flag)
1433 {
1434   GtkCalendarPrivate *priv = calendar->priv;
1435
1436   return (priv->display_flags & flag) != 0;
1437 }
1438
1439 static void
1440 gtk_calendar_set_property (GObject      *object,
1441                            guint         prop_id,
1442                            const GValue *value,
1443                            GParamSpec   *pspec)
1444 {
1445   GtkCalendar *calendar = GTK_CALENDAR (object);
1446   GtkCalendarPrivate *priv = calendar->priv;
1447
1448   switch (prop_id)
1449     {
1450     case PROP_YEAR:
1451       gtk_calendar_select_month (calendar,
1452                                  priv->month,
1453                                  g_value_get_int (value));
1454       break;
1455     case PROP_MONTH:
1456       gtk_calendar_select_month (calendar,
1457                                  g_value_get_int (value),
1458                                  priv->year);
1459       break;
1460     case PROP_DAY:
1461       gtk_calendar_select_day (calendar,
1462                                g_value_get_int (value));
1463       break;
1464     case PROP_SHOW_HEADING:
1465       calendar_set_display_option (calendar,
1466                                    GTK_CALENDAR_SHOW_HEADING,
1467                                    g_value_get_boolean (value));
1468       break;
1469     case PROP_SHOW_DAY_NAMES:
1470       calendar_set_display_option (calendar,
1471                                    GTK_CALENDAR_SHOW_DAY_NAMES,
1472                                    g_value_get_boolean (value));
1473       break;
1474     case PROP_NO_MONTH_CHANGE:
1475       calendar_set_display_option (calendar,
1476                                    GTK_CALENDAR_NO_MONTH_CHANGE,
1477                                    g_value_get_boolean (value));
1478       break;
1479     case PROP_SHOW_WEEK_NUMBERS:
1480       calendar_set_display_option (calendar,
1481                                    GTK_CALENDAR_SHOW_WEEK_NUMBERS,
1482                                    g_value_get_boolean (value));
1483       break;
1484     case PROP_SHOW_DETAILS:
1485       calendar_set_display_option (calendar,
1486                                    GTK_CALENDAR_SHOW_DETAILS,
1487                                    g_value_get_boolean (value));
1488       break;
1489     case PROP_DETAIL_WIDTH_CHARS:
1490       gtk_calendar_set_detail_width_chars (calendar,
1491                                            g_value_get_int (value));
1492       break;
1493     case PROP_DETAIL_HEIGHT_ROWS:
1494       gtk_calendar_set_detail_height_rows (calendar,
1495                                            g_value_get_int (value));
1496       break;
1497     default:
1498       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1499       break;
1500     }
1501 }
1502
1503 static void
1504 gtk_calendar_get_property (GObject      *object,
1505                            guint         prop_id,
1506                            GValue       *value,
1507                            GParamSpec   *pspec)
1508 {
1509   GtkCalendar *calendar = GTK_CALENDAR (object);
1510   GtkCalendarPrivate *priv = calendar->priv;
1511
1512   switch (prop_id)
1513     {
1514     case PROP_YEAR:
1515       g_value_set_int (value, priv->year);
1516       break;
1517     case PROP_MONTH:
1518       g_value_set_int (value, priv->month);
1519       break;
1520     case PROP_DAY:
1521       g_value_set_int (value, priv->selected_day);
1522       break;
1523     case PROP_SHOW_HEADING:
1524       g_value_set_boolean (value, calendar_get_display_option (calendar,
1525                                                                GTK_CALENDAR_SHOW_HEADING));
1526       break;
1527     case PROP_SHOW_DAY_NAMES:
1528       g_value_set_boolean (value, calendar_get_display_option (calendar,
1529                                                                GTK_CALENDAR_SHOW_DAY_NAMES));
1530       break;
1531     case PROP_NO_MONTH_CHANGE:
1532       g_value_set_boolean (value, calendar_get_display_option (calendar,
1533                                                                GTK_CALENDAR_NO_MONTH_CHANGE));
1534       break;
1535     case PROP_SHOW_WEEK_NUMBERS:
1536       g_value_set_boolean (value, calendar_get_display_option (calendar,
1537                                                                GTK_CALENDAR_SHOW_WEEK_NUMBERS));
1538       break;
1539     case PROP_SHOW_DETAILS:
1540       g_value_set_boolean (value, calendar_get_display_option (calendar,
1541                                                                GTK_CALENDAR_SHOW_DETAILS));
1542       break;
1543     case PROP_DETAIL_WIDTH_CHARS:
1544       g_value_set_int (value, priv->detail_width_chars);
1545       break;
1546     case PROP_DETAIL_HEIGHT_ROWS:
1547       g_value_set_int (value, priv->detail_height_rows);
1548       break;
1549     default:
1550       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1551       break;
1552     }
1553 }
1554
1555 \f
1556 /****************************************
1557  *             Realization              *
1558  ****************************************/
1559
1560 static void
1561 calendar_realize_arrows (GtkCalendar *calendar)
1562 {
1563   GtkWidget *widget = GTK_WIDGET (calendar);
1564   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1565   GdkWindowAttr attributes;
1566   gint attributes_mask;
1567   gint i;
1568   GtkAllocation allocation;
1569
1570   if (! (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
1571       && (priv->display_flags & GTK_CALENDAR_SHOW_HEADING))
1572     {
1573       gtk_widget_get_allocation (widget, &allocation);
1574
1575       attributes.wclass = GDK_INPUT_ONLY;
1576       attributes.window_type = GDK_WINDOW_CHILD;
1577       attributes.event_mask = (gtk_widget_get_events (widget)
1578                                | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1579                                | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1580       attributes_mask = GDK_WA_X | GDK_WA_Y;
1581       for (i = 0; i < 4; i++)
1582         {
1583           GdkRectangle rect;
1584           calendar_arrow_rectangle (calendar, i, &rect);
1585
1586           attributes.x = allocation.x + rect.x;
1587           attributes.y = allocation.y + rect.y;
1588           attributes.width = rect.width;
1589           attributes.height = rect.height;
1590           priv->arrow_win[i] = gdk_window_new (gtk_widget_get_window (widget),
1591                                                &attributes,
1592                                                attributes_mask);
1593
1594           if (!gtk_widget_is_sensitive (widget))
1595             priv->arrow_state[i] = GTK_STATE_FLAG_INSENSITIVE;
1596
1597           gdk_window_set_user_data (priv->arrow_win[i], widget);
1598         }
1599     }
1600   else
1601     {
1602       for (i = 0; i < 4; i++)
1603         priv->arrow_win[i] = NULL;
1604     }
1605 }
1606
1607 static void
1608 calendar_unrealize_arrows (GtkCalendar *calendar)
1609 {
1610   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1611   gint i;
1612
1613   for (i = 0; i < 4; i++)
1614     {
1615       if (priv->arrow_win[i])
1616         {
1617           gdk_window_set_user_data (priv->arrow_win[i], NULL);
1618           gdk_window_destroy (priv->arrow_win[i]);
1619           priv->arrow_win[i] = NULL;
1620         }
1621     }
1622
1623 }
1624
1625 static gint
1626 calendar_get_inner_border (GtkCalendar *calendar)
1627 {
1628   gint inner_border;
1629
1630   gtk_widget_style_get (GTK_WIDGET (calendar),
1631                         "inner-border", &inner_border,
1632                         NULL);
1633
1634   return inner_border;
1635 }
1636
1637 static gint
1638 calendar_get_xsep (GtkCalendar *calendar)
1639 {
1640   gint xsep;
1641
1642   gtk_widget_style_get (GTK_WIDGET (calendar),
1643                         "horizontal-separation", &xsep,
1644                         NULL);
1645
1646   return xsep;
1647 }
1648
1649 static gint
1650 calendar_get_ysep (GtkCalendar *calendar)
1651 {
1652   gint ysep;
1653
1654   gtk_widget_style_get (GTK_WIDGET (calendar),
1655                         "vertical-separation", &ysep,
1656                         NULL);
1657
1658   return ysep;
1659 }
1660
1661 static void
1662 gtk_calendar_realize (GtkWidget *widget)
1663 {
1664   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1665   GdkWindowAttr attributes;
1666   gint attributes_mask;
1667   gint inner_border = calendar_get_inner_border (GTK_CALENDAR (widget));
1668   GtkStyleContext *context;
1669   GtkAllocation allocation;
1670   GtkStateFlags state = 0;
1671   GtkBorder padding;
1672
1673   context = gtk_widget_get_style_context (widget);
1674   state = gtk_widget_get_state_flags (widget);
1675   gtk_style_context_get_padding (context, state, &padding);
1676
1677   gtk_widget_get_allocation (widget, &allocation);
1678
1679   GTK_WIDGET_CLASS (gtk_calendar_parent_class)->realize (widget);
1680
1681   attributes.wclass = GDK_INPUT_ONLY;
1682   attributes.window_type = GDK_WINDOW_CHILD;
1683   attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK
1684                            | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1685                            | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
1686
1687   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
1688     attributes.x = priv->week_width + padding.left + inner_border;
1689   else
1690     attributes.x = padding.left + inner_border;
1691
1692   attributes.y = priv->header_h + priv->day_name_h + padding.top + inner_border;
1693   attributes.width = allocation.width - attributes.x - (padding.right + inner_border);
1694
1695   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1696     attributes.width -= priv->week_width;
1697
1698   attributes.height = priv->main_h;
1699   attributes_mask = GDK_WA_X | GDK_WA_Y;
1700
1701   attributes.x += allocation.x;
1702   attributes.y += allocation.y;
1703
1704   priv->main_win = gdk_window_new (gtk_widget_get_window (widget),
1705                                    &attributes, attributes_mask);
1706   gdk_window_set_user_data (priv->main_win, widget);
1707
1708   calendar_realize_arrows (GTK_CALENDAR (widget));
1709 }
1710
1711 static void
1712 gtk_calendar_unrealize (GtkWidget *widget)
1713 {
1714   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1715
1716   calendar_unrealize_arrows (GTK_CALENDAR (widget));
1717
1718   if (priv->main_win)
1719     {
1720       gdk_window_set_user_data (priv->main_win, NULL);
1721       gdk_window_destroy (priv->main_win);
1722       priv->main_win = NULL;
1723     }
1724
1725   GTK_WIDGET_CLASS (gtk_calendar_parent_class)->unrealize (widget);
1726 }
1727
1728 static void
1729 calendar_map_arrows (GtkCalendar *calendar)
1730 {
1731   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1732   gint i;
1733
1734   for (i = 0; i < 4; i++)
1735     {
1736       if (priv->arrow_win[i])
1737         gdk_window_show (priv->arrow_win[i]);
1738     }
1739 }
1740
1741 static void
1742 calendar_unmap_arrows (GtkCalendar *calendar)
1743 {
1744   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1745   gint i;
1746
1747   for (i = 0; i < 4; i++)
1748     {
1749       if (priv->arrow_win[i])
1750         gdk_window_hide (priv->arrow_win[i]);
1751     }
1752 }
1753
1754 static void
1755 gtk_calendar_map (GtkWidget *widget)
1756 {
1757   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1758
1759   GTK_WIDGET_CLASS (gtk_calendar_parent_class)->map (widget);
1760
1761   gdk_window_show (priv->main_win);
1762
1763   calendar_map_arrows (GTK_CALENDAR (widget));
1764 }
1765
1766 static void
1767 gtk_calendar_unmap (GtkWidget *widget)
1768 {
1769   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1770
1771   calendar_unmap_arrows (GTK_CALENDAR (widget));
1772
1773   gdk_window_hide (priv->main_win);
1774
1775   GTK_WIDGET_CLASS (gtk_calendar_parent_class)->unmap (widget);
1776 }
1777
1778 static gchar*
1779 gtk_calendar_get_detail (GtkCalendar *calendar,
1780                          gint         row,
1781                          gint         column)
1782 {
1783   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1784   gint year, month;
1785
1786   if (priv->detail_func == NULL)
1787     return NULL;
1788
1789   year = priv->year;
1790   month = priv->month + priv->day_month[row][column] - MONTH_CURRENT;
1791
1792   if (month < 0)
1793     {
1794       month += 12;
1795       year -= 1;
1796     }
1797   else if (month > 11)
1798     {
1799       month -= 12;
1800       year += 1;
1801     }
1802
1803   return priv->detail_func (calendar,
1804                             year, month,
1805                             priv->day[row][column],
1806                             priv->detail_func_user_data);
1807 }
1808
1809 static gboolean
1810 gtk_calendar_query_tooltip (GtkWidget  *widget,
1811                             gint        x,
1812                             gint        y,
1813                             gboolean    keyboard_mode,
1814                             GtkTooltip *tooltip)
1815 {
1816   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1817   GtkCalendar *calendar = GTK_CALENDAR (widget);
1818   gchar *detail = NULL;
1819   GdkRectangle day_rect;
1820   gint row, col;
1821
1822   col = calendar_column_from_x (calendar, x);
1823   row = calendar_row_from_y (calendar, y);
1824
1825   if (col != -1 && row != -1 &&
1826       (0 != (priv->detail_overflow[row] & (1 << col)) ||
1827       0 == (priv->display_flags & GTK_CALENDAR_SHOW_DETAILS)))
1828     {
1829       detail = gtk_calendar_get_detail (calendar, row, col);
1830       calendar_day_rectangle (calendar, row, col, &day_rect);
1831     }
1832
1833   if (detail)
1834     {
1835       gtk_tooltip_set_tip_area (tooltip, &day_rect);
1836       gtk_tooltip_set_markup (tooltip, detail);
1837
1838       g_free (detail);
1839
1840       return TRUE;
1841     }
1842
1843   if (GTK_WIDGET_CLASS (gtk_calendar_parent_class)->query_tooltip)
1844     return GTK_WIDGET_CLASS (gtk_calendar_parent_class)->query_tooltip (widget, x, y, keyboard_mode, tooltip);
1845
1846   return FALSE;
1847 }
1848
1849 \f
1850 /****************************************
1851  *       Size Request and Allocate      *
1852  ****************************************/
1853
1854 static void
1855 gtk_calendar_size_request (GtkWidget      *widget,
1856                            GtkRequisition *requisition)
1857 {
1858   GtkCalendar *calendar = GTK_CALENDAR (widget);
1859   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1860   GtkStyleContext *context;
1861   GtkStateFlags state;
1862   GtkBorder padding;
1863   PangoLayout *layout;
1864   PangoRectangle logical_rect;
1865
1866   gint height;
1867   gint i, r, c;
1868   gint calendar_margin = CALENDAR_MARGIN;
1869   gint header_width, main_width;
1870   gint max_header_height = 0;
1871   gint focus_width;
1872   gint focus_padding;
1873   gint max_detail_height;
1874   gint inner_border = calendar_get_inner_border (calendar);
1875   gint calendar_ysep = calendar_get_ysep (calendar);
1876   gint calendar_xsep = calendar_get_xsep (calendar);
1877
1878   gtk_widget_style_get (GTK_WIDGET (widget),
1879                         "focus-line-width", &focus_width,
1880                         "focus-padding", &focus_padding,
1881                         NULL);
1882
1883   layout = gtk_widget_create_pango_layout (widget, NULL);
1884
1885   /*
1886    * Calculate the requisition  width for the widget.
1887    */
1888
1889   /* Header width */
1890
1891   if (priv->display_flags & GTK_CALENDAR_SHOW_HEADING)
1892     {
1893       priv->max_month_width = 0;
1894       for (i = 0; i < 12; i++)
1895         {
1896           pango_layout_set_text (layout, default_monthname[i], -1);
1897           pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1898           priv->max_month_width = MAX (priv->max_month_width,
1899                                                logical_rect.width + 8);
1900           max_header_height = MAX (max_header_height, logical_rect.height);
1901         }
1902
1903       priv->max_year_width = 0;
1904       /* Translators:  This is a text measurement template.
1905        * Translate it to the widest year text
1906        *
1907        * If you don't understand this, leave it as "2000"
1908        */
1909       pango_layout_set_text (layout, C_("year measurement template", "2000"), -1);
1910       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1911       priv->max_year_width = MAX (priv->max_year_width,
1912                                   logical_rect.width + 8);
1913       max_header_height = MAX (max_header_height, logical_rect.height);
1914     }
1915   else
1916     {
1917       priv->max_month_width = 0;
1918       priv->max_year_width = 0;
1919     }
1920
1921   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
1922     header_width = (priv->max_month_width
1923                     + priv->max_year_width
1924                     + 3 * 3);
1925   else
1926     header_width = (priv->max_month_width
1927                     + priv->max_year_width
1928                     + 4 * priv->arrow_width + 3 * 3);
1929
1930   /* Mainwindow labels width */
1931
1932   priv->max_day_char_width = 0;
1933   priv->max_day_char_ascent = 0;
1934   priv->max_day_char_descent = 0;
1935   priv->min_day_width = 0;
1936
1937   for (i = 0; i < 9; i++)
1938     {
1939       gchar buffer[32];
1940       g_snprintf (buffer, sizeof (buffer), C_("calendar:day:digits", "%d"), i * 11);
1941       pango_layout_set_text (layout, buffer, -1);
1942       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1943       priv->min_day_width = MAX (priv->min_day_width,
1944                                          logical_rect.width);
1945
1946       priv->max_day_char_ascent = MAX (priv->max_day_char_ascent,
1947                                                PANGO_ASCENT (logical_rect));
1948       priv->max_day_char_descent = MAX (priv->max_day_char_descent,
1949                                                 PANGO_DESCENT (logical_rect));
1950     }
1951
1952   priv->max_label_char_ascent = 0;
1953   priv->max_label_char_descent = 0;
1954   if (priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
1955     for (i = 0; i < 7; i++)
1956       {
1957         pango_layout_set_text (layout, default_abbreviated_dayname[i], -1);
1958         pango_layout_line_get_pixel_extents (pango_layout_get_lines_readonly (layout)->data, NULL, &logical_rect);
1959
1960         priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
1961         priv->max_label_char_ascent = MAX (priv->max_label_char_ascent,
1962                                                    PANGO_ASCENT (logical_rect));
1963         priv->max_label_char_descent = MAX (priv->max_label_char_descent,
1964                                                     PANGO_DESCENT (logical_rect));
1965       }
1966
1967   priv->max_week_char_width = 0;
1968   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
1969     for (i = 0; i < 9; i++)
1970       {
1971         gchar buffer[32];
1972         g_snprintf (buffer, sizeof (buffer), C_("calendar:week:digits", "%d"), i * 11);
1973         pango_layout_set_text (layout, buffer, -1);
1974         pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1975         priv->max_week_char_width = MAX (priv->max_week_char_width,
1976                                            logical_rect.width / 2);
1977       }
1978
1979   /* Calculate detail extents. Do this as late as possible since
1980    * pango_layout_set_markup is called which alters font settings. */
1981   max_detail_height = 0;
1982
1983   if (priv->detail_func && (priv->display_flags & GTK_CALENDAR_SHOW_DETAILS))
1984     {
1985       gchar *markup, *tail;
1986
1987       if (priv->detail_width_chars || priv->detail_height_rows)
1988         {
1989           gint rows = MAX (1, priv->detail_height_rows) - 1;
1990           gsize len = priv->detail_width_chars + rows + 16;
1991
1992           markup = tail = g_alloca (len);
1993
1994           memcpy (tail,     "<small>", 7);
1995           tail += 7;
1996
1997           memset (tail, 'm', priv->detail_width_chars);
1998           tail += priv->detail_width_chars;
1999
2000           memset (tail, '\n', rows);
2001           tail += rows;
2002
2003           memcpy (tail,     "</small>", 9);
2004           tail += 9;
2005
2006           g_assert (len == (tail - markup));
2007
2008           pango_layout_set_markup (layout, markup, -1);
2009           pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2010
2011           if (priv->detail_width_chars)
2012             priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
2013           if (priv->detail_height_rows)
2014             max_detail_height = MAX (max_detail_height, logical_rect.height);
2015         }
2016
2017       if (!priv->detail_width_chars || !priv->detail_height_rows)
2018         for (r = 0; r < 6; r++)
2019           for (c = 0; c < 7; c++)
2020             {
2021               gchar *detail = gtk_calendar_get_detail (calendar, r, c);
2022
2023               if (detail)
2024                 {
2025                   markup = g_strconcat ("<small>", detail, "</small>", NULL);
2026                   pango_layout_set_markup (layout, markup, -1);
2027
2028                   if (priv->detail_width_chars)
2029                     {
2030                       pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
2031                       pango_layout_set_width (layout, PANGO_SCALE * priv->min_day_width);
2032                     }
2033
2034                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2035
2036                   if (!priv->detail_width_chars)
2037                     priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
2038                   if (!priv->detail_height_rows)
2039                     max_detail_height = MAX (max_detail_height, logical_rect.height);
2040
2041                   g_free (markup);
2042                   g_free (detail);
2043                 }
2044             }
2045     }
2046
2047   /* We add one to max_day_char_width to be able to make the marked day "bold" */
2048   priv->max_day_char_width = priv->min_day_width / 2 + 1;
2049
2050   main_width = (7 * (priv->min_day_width + (focus_padding + focus_width) * 2) + (DAY_XSEP * 6) + CALENDAR_MARGIN * 2
2051                 + (priv->max_week_char_width
2052                    ? priv->max_week_char_width * 2 + (focus_padding + focus_width) * 2 + calendar_xsep * 2
2053                    : 0));
2054
2055   context = gtk_widget_get_style_context (widget);
2056   state = gtk_widget_get_state_flags (widget);
2057   gtk_style_context_get_padding (context, state, &padding);
2058
2059   requisition->width = MAX (header_width, main_width + inner_border * 2) + padding.left + padding.right;
2060
2061   /*
2062    * Calculate the requisition height for the widget.
2063    */
2064
2065   if (priv->display_flags & GTK_CALENDAR_SHOW_HEADING)
2066     {
2067       priv->header_h = (max_header_height + calendar_ysep * 2);
2068     }
2069   else
2070     {
2071       priv->header_h = 0;
2072     }
2073
2074   if (priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
2075     {
2076       priv->day_name_h = (priv->max_label_char_ascent
2077                                   + priv->max_label_char_descent
2078                                   + 2 * (focus_padding + focus_width) + calendar_margin);
2079       calendar_margin = calendar_ysep;
2080     }
2081   else
2082     {
2083       priv->day_name_h = 0;
2084     }
2085
2086   priv->main_h = (CALENDAR_MARGIN + calendar_margin
2087                           + 6 * (priv->max_day_char_ascent
2088                                  + priv->max_day_char_descent
2089                                  + max_detail_height
2090                                  + 2 * (focus_padding + focus_width))
2091                           + DAY_YSEP * 5);
2092
2093   height = priv->header_h + priv->day_name_h + priv->main_h;
2094
2095   requisition->height = height + padding.top + padding.bottom + (inner_border * 2);
2096
2097   g_object_unref (layout);
2098 }
2099
2100 static void
2101 gtk_calendar_get_preferred_width (GtkWidget *widget,
2102                                   gint      *minimum,
2103                                   gint      *natural)
2104 {
2105   GtkRequisition requisition;
2106
2107   gtk_calendar_size_request (widget, &requisition);
2108
2109   *minimum = *natural = requisition.width;
2110 }
2111
2112 static void
2113 gtk_calendar_get_preferred_height (GtkWidget *widget,
2114                                    gint      *minimum,
2115                                    gint      *natural)
2116 {
2117   GtkRequisition requisition;
2118
2119   gtk_calendar_size_request (widget, &requisition);
2120
2121   *minimum = *natural = requisition.height;
2122 }
2123
2124 static void
2125 gtk_calendar_size_allocate (GtkWidget     *widget,
2126                             GtkAllocation *allocation)
2127 {
2128   GtkCalendar *calendar = GTK_CALENDAR (widget);
2129   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2130   GtkStyleContext *context;
2131   GtkStateFlags state;
2132   GtkBorder padding;
2133   guint i;
2134   gint inner_border = calendar_get_inner_border (calendar);
2135   gint calendar_xsep = calendar_get_xsep (calendar);
2136
2137   context = gtk_widget_get_style_context (widget);
2138   state = gtk_widget_get_state_flags (widget);
2139   gtk_style_context_get_padding (context, state, &padding);
2140
2141   gtk_widget_set_allocation (widget, allocation);
2142
2143   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
2144     {
2145       priv->day_width = (priv->min_day_width
2146                          * ((allocation->width - (inner_border * 2) - padding.left - padding.right
2147                              - (CALENDAR_MARGIN * 2) -  (DAY_XSEP * 6) - calendar_xsep * 2))
2148                          / (7 * priv->min_day_width + priv->max_week_char_width * 2));
2149       priv->week_width = ((allocation->width - (inner_border * 2) - padding.left - padding.right
2150                            - (CALENDAR_MARGIN * 2) - (DAY_XSEP * 6) - calendar_xsep * 2 )
2151                           - priv->day_width * 7 + CALENDAR_MARGIN + calendar_xsep);
2152     }
2153   else
2154     {
2155       priv->day_width = (allocation->width
2156                          - (inner_border * 2)
2157                          - padding.left - padding.right
2158                          - (CALENDAR_MARGIN * 2)
2159                          - (DAY_XSEP * 6))/7;
2160       priv->week_width = 0;
2161     }
2162
2163   if (gtk_widget_get_realized (widget))
2164     {
2165       if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
2166         gdk_window_move_resize (priv->main_win,
2167                                 allocation->x
2168                                  + priv->week_width + padding.left + inner_border,
2169                                 allocation->y
2170                                  + priv->header_h + priv->day_name_h
2171                                  + (padding.top + inner_border),
2172                                 allocation->width - priv->week_width
2173                                 - (inner_border * 2) - padding.left - padding.right,
2174                                 priv->main_h);
2175       else
2176         gdk_window_move_resize (priv->main_win,
2177                                 allocation->x
2178                                  + padding.left + inner_border,
2179                                 allocation->y
2180                                  + priv->header_h + priv->day_name_h
2181                                  + padding.top + inner_border,
2182                                 allocation->width - priv->week_width
2183                                 - (inner_border * 2) - padding.left - padding.right,
2184                                 priv->main_h);
2185
2186       for (i = 0 ; i < 4 ; i++)
2187         {
2188           if (priv->arrow_win[i])
2189             {
2190               GdkRectangle rect;
2191               calendar_arrow_rectangle (calendar, i, &rect);
2192
2193               gdk_window_move_resize (priv->arrow_win[i],
2194                                       allocation->x + rect.x,
2195                                       allocation->y + rect.y,
2196                                       rect.width, rect.height);
2197             }
2198         }
2199     }
2200 }
2201
2202 \f
2203 /****************************************
2204  *              Repainting              *
2205  ****************************************/
2206
2207 static void
2208 calendar_paint_header (GtkCalendar *calendar, cairo_t *cr)
2209 {
2210   GtkWidget *widget = GTK_WIDGET (calendar);
2211   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2212   GtkAllocation allocation;
2213   GtkStyleContext *context;
2214   GtkStateFlags state;
2215   GtkBorder padding;
2216   char buffer[255];
2217   gint x, y;
2218   gint header_width;
2219   gint max_month_width;
2220   gint max_year_width;
2221   PangoLayout *layout;
2222   PangoRectangle logical_rect;
2223   gboolean year_left;
2224   time_t tmp_time;
2225   struct tm *tm;
2226   gchar *str;
2227
2228   context = gtk_widget_get_style_context (widget);
2229   state = gtk_widget_get_state_flags (widget);
2230   gtk_style_context_get_padding (context, state, &padding);
2231
2232   cairo_save (cr);
2233   cairo_translate (cr, padding.left, padding.top);
2234
2235   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
2236     year_left = priv->year_before;
2237   else
2238     year_left = !priv->year_before;
2239
2240   gtk_widget_get_allocation (widget, &allocation);
2241
2242   header_width = allocation.width - padding.left - padding.right;
2243
2244   max_month_width = priv->max_month_width;
2245   max_year_width = priv->max_year_width;
2246
2247   gtk_style_context_save (context);
2248   gtk_style_context_add_class (context, GTK_STYLE_CLASS_HEADER);
2249
2250   gtk_render_background (context, cr, 0, 0, header_width, priv->header_h);
2251   gtk_render_frame (context, cr, 0, 0, header_width, priv->header_h);
2252
2253   tmp_time = 1;  /* Jan 1 1970, 00:00:01 UTC */
2254   tm = gmtime (&tmp_time);
2255   tm->tm_year = priv->year - 1900;
2256
2257   /* Translators: This dictates how the year is displayed in
2258    * gtkcalendar widget.  See strftime() manual for the format.
2259    * Use only ASCII in the translation.
2260    *
2261    * Also look for the msgid "2000".
2262    * Translate that entry to a year with the widest output of this
2263    * msgid.
2264    *
2265    * "%Y" is appropriate for most locales.
2266    */
2267   strftime (buffer, sizeof (buffer), C_("calendar year format", "%Y"), tm);
2268   str = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
2269   layout = gtk_widget_create_pango_layout (widget, str);
2270   g_free (str);
2271
2272   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2273
2274   /* Draw title */
2275   y = (priv->header_h - logical_rect.height) / 2;
2276
2277   /* Draw year and its arrows */
2278
2279   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
2280     if (year_left)
2281       x = 3 + (max_year_width - logical_rect.width)/2;
2282     else
2283       x = header_width - (3 + max_year_width
2284                           - (max_year_width - logical_rect.width)/2);
2285   else
2286     if (year_left)
2287       x = 3 + priv->arrow_width + (max_year_width - logical_rect.width)/2;
2288     else
2289       x = header_width - (3 + priv->arrow_width + max_year_width
2290                           - (max_year_width - logical_rect.width)/2);
2291
2292   gtk_render_layout (context, cr, x, y, layout);
2293
2294   /* Draw month */
2295   g_snprintf (buffer, sizeof (buffer), "%s", default_monthname[priv->month]);
2296   pango_layout_set_text (layout, buffer, -1);
2297   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2298
2299   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
2300     if (year_left)
2301       x = header_width - (3 + max_month_width
2302                           - (max_month_width - logical_rect.width)/2);
2303     else
2304     x = 3 + (max_month_width - logical_rect.width) / 2;
2305   else
2306     if (year_left)
2307       x = header_width - (3 + priv->arrow_width + max_month_width
2308                           - (max_month_width - logical_rect.width)/2);
2309     else
2310     x = 3 + priv->arrow_width + (max_month_width - logical_rect.width)/2;
2311
2312   gtk_render_layout (context, cr, x, y, layout);
2313   g_object_unref (layout);
2314
2315   gtk_style_context_restore (context);
2316   cairo_restore (cr);
2317 }
2318
2319 static void
2320 calendar_paint_day_names (GtkCalendar *calendar,
2321                           cairo_t     *cr)
2322 {
2323   GtkWidget *widget = GTK_WIDGET (calendar);
2324   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2325   GtkStyleContext *context;
2326   GtkStateFlags state;
2327   GtkBorder padding;
2328   GtkAllocation allocation;
2329   char buffer[255];
2330   int day,i;
2331   int day_width, cal_width;
2332   int day_wid_sep;
2333   PangoLayout *layout;
2334   PangoRectangle logical_rect;
2335   gint focus_padding;
2336   gint focus_width;
2337   gint calendar_ysep = calendar_get_ysep (calendar);
2338   gint calendar_xsep = calendar_get_xsep (calendar);
2339   gint inner_border = calendar_get_inner_border (calendar);
2340
2341   context = gtk_widget_get_style_context (widget);
2342   state = gtk_widget_get_state_flags (widget);
2343   gtk_style_context_get_padding (context, state, &padding);
2344
2345   cairo_save (cr);
2346
2347   cairo_translate (cr,
2348                    padding.left + inner_border,
2349                    priv->header_h + padding.top + inner_border);
2350
2351   gtk_widget_style_get (GTK_WIDGET (widget),
2352                         "focus-line-width", &focus_width,
2353                         "focus-padding", &focus_padding,
2354                         NULL);
2355
2356   gtk_widget_get_allocation (widget, &allocation);
2357
2358   day_width = priv->day_width;
2359   cal_width = allocation.width - (inner_border * 2) - padding.left - padding.right;
2360   day_wid_sep = day_width + DAY_XSEP;
2361
2362   /*
2363    * Draw rectangles as inverted background for the labels.
2364    */
2365
2366   gtk_style_context_save (context);
2367   gtk_style_context_add_class (context, GTK_STYLE_CLASS_HIGHLIGHT);
2368
2369   gtk_render_background (context, cr,
2370                          CALENDAR_MARGIN, CALENDAR_MARGIN,
2371                          cal_width - CALENDAR_MARGIN * 2,
2372                          priv->day_name_h - CALENDAR_MARGIN);
2373
2374   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
2375     gtk_render_background (context, cr,
2376                            CALENDAR_MARGIN,
2377                            priv->day_name_h - calendar_ysep,
2378                            priv->week_width - calendar_ysep - CALENDAR_MARGIN,
2379                            calendar_ysep);
2380
2381   /*
2382    * Write the labels
2383    */
2384   layout = gtk_widget_create_pango_layout (widget, NULL);
2385
2386   for (i = 0; i < 7; i++)
2387     {
2388       if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
2389         day = 6 - i;
2390       else
2391         day = i;
2392       day = (day + priv->week_start) % 7;
2393       g_snprintf (buffer, sizeof (buffer), "%s", default_abbreviated_dayname[day]);
2394
2395       pango_layout_set_text (layout, buffer, -1);
2396       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2397
2398       gtk_render_layout (context, cr,
2399                          (CALENDAR_MARGIN +
2400                           + (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR ?
2401                              (priv->week_width + (priv->week_width ? calendar_xsep : 0))
2402                              : 0)
2403                           + day_wid_sep * i
2404                           + (day_width - logical_rect.width)/2),
2405                          CALENDAR_MARGIN + focus_width + focus_padding + logical_rect.y,
2406                          layout);
2407     }
2408
2409   g_object_unref (layout);
2410
2411   gtk_style_context_restore (context);
2412   cairo_restore (cr);
2413 }
2414
2415 static void
2416 calendar_paint_week_numbers (GtkCalendar *calendar,
2417                              cairo_t     *cr)
2418 {
2419   GtkWidget *widget = GTK_WIDGET (calendar);
2420   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2421   GtkStyleContext *context;
2422   GtkStateFlags state;
2423   GtkBorder padding;
2424   guint week = 0, year;
2425   gint row, x_loc, y_loc;
2426   gint day_height;
2427   char buffer[32];
2428   PangoLayout *layout;
2429   PangoRectangle logical_rect;
2430   gint focus_padding;
2431   gint focus_width;
2432   gint calendar_xsep = calendar_get_xsep (calendar);
2433   gint inner_border = calendar_get_inner_border (calendar);
2434   gint x, y;
2435
2436   context = gtk_widget_get_style_context (widget);
2437   state = gtk_widget_get_state_flags (widget);
2438   gtk_style_context_get_padding (context, state, &padding);
2439
2440   cairo_save (cr);
2441
2442   y = priv->header_h + priv->day_name_h + (padding.top + inner_border);
2443   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
2444     x = padding.left + inner_border;
2445   else
2446     x = gtk_widget_get_allocated_width (widget) - priv->week_width - (padding.right + inner_border);
2447
2448   gtk_widget_style_get (GTK_WIDGET (widget),
2449                         "focus-line-width", &focus_width,
2450                         "focus-padding", &focus_padding,
2451                         NULL);
2452
2453   gtk_style_context_save (context);
2454   gtk_style_context_add_class (context, GTK_STYLE_CLASS_HIGHLIGHT);
2455
2456   if (priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
2457     gtk_render_background (context, cr,
2458                            x + CALENDAR_MARGIN, y,
2459                            priv->week_width - CALENDAR_MARGIN,
2460                            priv->main_h - CALENDAR_MARGIN);
2461   else
2462     gtk_render_background (context, cr,
2463                            x + CALENDAR_MARGIN,
2464                            y + CALENDAR_MARGIN,
2465                            priv->week_width - CALENDAR_MARGIN,
2466                            priv->main_h - 2 * CALENDAR_MARGIN);
2467
2468   /*
2469    * Write the labels
2470    */
2471
2472   layout = gtk_widget_create_pango_layout (widget, NULL);
2473   day_height = calendar_row_height (calendar);
2474
2475   for (row = 0; row < 6; row++)
2476     {
2477       gboolean result;
2478
2479       year = priv->year;
2480       if (priv->day[row][6] < 15 && row > 3 && priv->month == 11)
2481         year++;
2482
2483       result = week_of_year (&week, &year,
2484                              ((priv->day[row][6] < 15 && row > 3 ? 1 : 0)
2485                               + priv->month) % 12 + 1, priv->day[row][6]);
2486       g_return_if_fail (result);
2487
2488       /* Translators: this defines whether the week numbers should use
2489        * localized digits or the ones used in English (0123...).
2490        *
2491        * Translate to "%Id" if you want to use localized digits, or
2492        * translate to "%d" otherwise.
2493        *
2494        * Note that translating this doesn't guarantee that you get localized
2495        * digits. That needs support from your system and locale definition
2496        * too.
2497        */
2498       g_snprintf (buffer, sizeof (buffer), C_("calendar:week:digits", "%d"), week);
2499       pango_layout_set_text (layout, buffer, -1);
2500       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2501
2502       y_loc = calendar_top_y_for_row (calendar, row) + (day_height - logical_rect.height) / 2;
2503
2504       x_loc = x + (priv->week_width
2505                    - logical_rect.width
2506                    - calendar_xsep - focus_padding - focus_width);
2507
2508       gtk_render_layout (context, cr, x_loc, y_loc, layout);
2509     }
2510
2511   g_object_unref (layout);
2512
2513   gtk_style_context_restore (context);
2514   cairo_restore (cr);
2515 }
2516
2517 static void
2518 calendar_invalidate_day_num (GtkCalendar *calendar,
2519                              gint         day)
2520 {
2521   GtkCalendarPrivate *priv = calendar->priv;
2522   gint r, c, row, col;
2523
2524   row = -1;
2525   col = -1;
2526   for (r = 0; r < 6; r++)
2527     for (c = 0; c < 7; c++)
2528       if (priv->day_month[r][c] == MONTH_CURRENT &&
2529           priv->day[r][c] == day)
2530         {
2531           row = r;
2532           col = c;
2533         }
2534
2535   g_return_if_fail (row != -1);
2536   g_return_if_fail (col != -1);
2537
2538   calendar_invalidate_day (calendar, row, col);
2539 }
2540
2541 static void
2542 calendar_invalidate_day (GtkCalendar *calendar,
2543                          gint         row,
2544                          gint         col)
2545 {
2546   GdkRectangle day_rect;
2547   GtkAllocation allocation;
2548
2549   gtk_widget_get_allocation (GTK_WIDGET (calendar), &allocation);
2550   calendar_day_rectangle (calendar, row, col, &day_rect);
2551   gtk_widget_queue_draw_area (GTK_WIDGET (calendar),
2552                               allocation.x + day_rect.x,
2553                               allocation.y + day_rect.y,
2554                               day_rect.width, day_rect.height);
2555 }
2556
2557 static gboolean
2558 is_color_attribute (PangoAttribute *attribute,
2559                     gpointer        data)
2560 {
2561   return (attribute->klass->type == PANGO_ATTR_FOREGROUND ||
2562           attribute->klass->type == PANGO_ATTR_BACKGROUND);
2563 }
2564
2565 static void
2566 calendar_paint_day (GtkCalendar *calendar,
2567                     cairo_t     *cr,
2568                     gint         row,
2569                     gint         col)
2570 {
2571   GtkWidget *widget = GTK_WIDGET (calendar);
2572   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2573   GtkStyleContext *context;
2574   GtkStateFlags state = 0;
2575   gchar *detail;
2576   gchar buffer[32];
2577   gint day;
2578   gint x_loc, y_loc;
2579   GdkRectangle day_rect;
2580
2581   PangoLayout *layout;
2582   PangoRectangle logical_rect;
2583   gboolean overflow = FALSE;
2584   gboolean show_details;
2585
2586   g_return_if_fail (row < 6);
2587   g_return_if_fail (col < 7);
2588
2589   context = gtk_widget_get_style_context (widget);
2590
2591   day = priv->day[row][col];
2592   show_details = (priv->display_flags & GTK_CALENDAR_SHOW_DETAILS);
2593
2594   calendar_day_rectangle (calendar, row, col, &day_rect);
2595
2596   gtk_style_context_save (context);
2597
2598   if (!gtk_widget_get_sensitive (widget))
2599     state |= GTK_STATE_FLAG_INSENSITIVE;
2600   else
2601     {
2602       if (gtk_widget_has_focus (widget))
2603         state |= GTK_STATE_FLAG_FOCUSED;
2604
2605       if (priv->day_month[row][col] == MONTH_PREV ||
2606           priv->day_month[row][col] == MONTH_NEXT)
2607         state |= GTK_STATE_FLAG_INCONSISTENT;
2608       else
2609         {
2610           if (priv->marked_date[day-1])
2611             state |= GTK_STATE_FLAG_ACTIVE;
2612
2613           if (priv->selected_day == day)
2614             {
2615               state |= GTK_STATE_FLAG_SELECTED;
2616
2617               gtk_style_context_set_state (context, state);
2618               gtk_render_background (context, cr,
2619                                      day_rect.x, day_rect.y,
2620                                      day_rect.width, day_rect.height);
2621             }
2622         }
2623     }
2624
2625   gtk_style_context_set_state (context, state);
2626
2627   /* Translators: this defines whether the day numbers should use
2628    * localized digits or the ones used in English (0123...).
2629    *
2630    * Translate to "%Id" if you want to use localized digits, or
2631    * translate to "%d" otherwise.
2632    *
2633    * Note that translating this doesn't guarantee that you get localized
2634    * digits. That needs support from your system and locale definition
2635    * too.
2636    */
2637   g_snprintf (buffer, sizeof (buffer), C_("calendar:day:digits", "%d"), day);
2638
2639   /* Get extra information to show, if any: */
2640
2641   detail = gtk_calendar_get_detail (calendar, row, col);
2642
2643   layout = gtk_widget_create_pango_layout (widget, buffer);
2644   pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
2645   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2646
2647   x_loc = day_rect.x + (day_rect.width - logical_rect.width) / 2;
2648   y_loc = day_rect.y;
2649
2650   gtk_render_layout (context, cr, x_loc, y_loc, layout);
2651
2652   if (priv->day_month[row][col] == MONTH_CURRENT &&
2653      (priv->marked_date[day-1] || (detail && !show_details)))
2654     gtk_render_layout (context, cr, x_loc - 1, y_loc, layout);
2655
2656   y_loc += priv->max_day_char_descent;
2657
2658   if (priv->detail_func && show_details)
2659     {
2660       GdkRGBA color;
2661
2662       cairo_save (cr);
2663
2664       gtk_style_context_get_color (context, state, &color);
2665       gdk_cairo_set_source_rgba (cr, &color);
2666
2667       cairo_set_line_width (cr, 1);
2668       cairo_move_to (cr, day_rect.x + 2, y_loc + 0.5);
2669       cairo_line_to (cr, day_rect.x + day_rect.width - 2, y_loc + 0.5);
2670       cairo_stroke (cr);
2671
2672       cairo_restore (cr);
2673
2674       y_loc += 2;
2675     }
2676
2677   if (detail && show_details)
2678     {
2679       gchar *markup = g_strconcat ("<small>", detail, "</small>", NULL);
2680       pango_layout_set_markup (layout, markup, -1);
2681       g_free (markup);
2682
2683       if (day == priv->selected_day)
2684         {
2685           /* Stripping colors as they conflict with selection marking. */
2686
2687           PangoAttrList *attrs = pango_layout_get_attributes (layout);
2688           PangoAttrList *colors = NULL;
2689
2690           if (attrs)
2691             colors = pango_attr_list_filter (attrs, is_color_attribute, NULL);
2692           if (colors)
2693             pango_attr_list_unref (colors);
2694         }
2695
2696       pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
2697       pango_layout_set_width (layout, PANGO_SCALE * day_rect.width);
2698
2699       if (priv->detail_height_rows)
2700         {
2701           gint dy = day_rect.height - (y_loc - day_rect.y);
2702           pango_layout_set_height (layout, PANGO_SCALE * dy);
2703           pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
2704         }
2705
2706       cairo_move_to (cr, day_rect.x, y_loc);
2707       pango_cairo_show_layout (cr, layout);
2708     }
2709
2710   if (gtk_widget_has_focus (widget)
2711       && priv->focus_row == row && priv->focus_col == col)
2712     gtk_render_focus (context, cr,
2713                       day_rect.x, day_rect.y,
2714                       day_rect.width, day_rect.height);
2715
2716   if (overflow)
2717     priv->detail_overflow[row] |= (1 << col);
2718   else
2719     priv->detail_overflow[row] &= ~(1 << col);
2720
2721   gtk_style_context_restore (context);
2722   g_object_unref (layout);
2723   g_free (detail);
2724 }
2725
2726 static void
2727 calendar_paint_main (GtkCalendar *calendar,
2728                      cairo_t     *cr)
2729 {
2730   gint row, col;
2731
2732   cairo_save (cr);
2733
2734   for (col = 0; col < 7; col++)
2735     for (row = 0; row < 6; row++)
2736       calendar_paint_day (calendar, cr, row, col);
2737
2738   cairo_restore (cr);
2739 }
2740
2741 static void
2742 calendar_invalidate_arrow (GtkCalendar *calendar,
2743                            guint        arrow)
2744 {
2745   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2746
2747   if (priv->display_flags & GTK_CALENDAR_SHOW_HEADING &&
2748       priv->arrow_win[arrow])
2749     {
2750       GdkRectangle rect;
2751       GtkAllocation allocation;
2752
2753       calendar_arrow_rectangle (calendar, arrow, &rect);
2754       gtk_widget_get_allocation (GTK_WIDGET (calendar), &allocation);
2755       gtk_widget_queue_draw_area (GTK_WIDGET (calendar),
2756                                   allocation.x + rect.x,
2757                                   allocation.y + rect.y,
2758                                   rect.width, rect.height);
2759     }
2760 }
2761
2762 static void
2763 calendar_paint_arrow (GtkCalendar *calendar,
2764                       cairo_t     *cr,
2765                       guint        arrow)
2766 {
2767   GtkWidget *widget = GTK_WIDGET (calendar);
2768   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2769   GtkStyleContext *context;
2770   GtkStateFlags state;
2771   GdkRectangle rect;
2772   gdouble angle;
2773
2774   if (!priv->arrow_win[arrow])
2775     return;
2776
2777   calendar_arrow_rectangle (calendar, arrow, &rect);
2778
2779   cairo_save (cr);
2780
2781   context = gtk_widget_get_style_context (widget);
2782   state = priv->arrow_state[arrow];
2783
2784   gtk_style_context_save (context);
2785   gtk_style_context_set_state (context, state);
2786   gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
2787
2788   gtk_render_background (context, cr,
2789                          rect.x, rect.y,
2790                          rect.width, rect.height);
2791
2792   if (arrow == ARROW_MONTH_LEFT || arrow == ARROW_YEAR_LEFT)
2793     angle = 3 * (G_PI / 2);
2794   else
2795     angle = G_PI / 2;
2796
2797   gtk_render_arrow (context, cr, angle,
2798                     rect.x + (rect.width - 8) / 2,
2799                     rect.y + (rect.height - 8) / 2,
2800                     8);
2801
2802   gtk_style_context_restore (context);
2803   cairo_restore (cr);
2804 }
2805
2806 static gboolean
2807 gtk_calendar_draw (GtkWidget *widget,
2808                    cairo_t   *cr)
2809 {
2810   GtkCalendar *calendar = GTK_CALENDAR (widget);
2811   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2812   int i;
2813
2814   if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget)))
2815     {
2816       GtkStyleContext *context;
2817
2818       context = gtk_widget_get_style_context (widget);
2819
2820       gtk_style_context_save (context);
2821       gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
2822
2823       gtk_render_background (context, cr, 0, 0,
2824                              gtk_widget_get_allocated_width (widget),
2825                              gtk_widget_get_allocated_height (widget));
2826       gtk_render_frame (context, cr, 0, 0,
2827                         gtk_widget_get_allocated_width (widget),
2828                         gtk_widget_get_allocated_height (widget));
2829
2830       gtk_style_context_restore (context);
2831     }
2832
2833   calendar_paint_main (calendar, cr);
2834
2835   if (priv->display_flags & GTK_CALENDAR_SHOW_HEADING)
2836     {
2837       calendar_paint_header (calendar, cr);
2838       for (i = 0; i < 4; i++)
2839         calendar_paint_arrow (calendar, cr, i);
2840     }
2841
2842   if (priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
2843     calendar_paint_day_names (calendar, cr);
2844
2845   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
2846     calendar_paint_week_numbers (calendar, cr);
2847
2848   return FALSE;
2849 }
2850
2851
2852 /****************************************
2853  *           Mouse handling             *
2854  ****************************************/
2855
2856 static void
2857 calendar_arrow_action (GtkCalendar *calendar,
2858                        guint        arrow)
2859 {
2860   switch (arrow)
2861     {
2862     case ARROW_YEAR_LEFT:
2863       calendar_set_year_prev (calendar);
2864       break;
2865     case ARROW_YEAR_RIGHT:
2866       calendar_set_year_next (calendar);
2867       break;
2868     case ARROW_MONTH_LEFT:
2869       calendar_set_month_prev (calendar);
2870       break;
2871     case ARROW_MONTH_RIGHT:
2872       calendar_set_month_next (calendar);
2873       break;
2874     default:;
2875       /* do nothing */
2876     }
2877 }
2878
2879 static gboolean
2880 calendar_timer (gpointer data)
2881 {
2882   GtkCalendar *calendar = data;
2883   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2884   gboolean retval = FALSE;
2885
2886   if (priv->timer)
2887     {
2888       calendar_arrow_action (calendar, priv->click_child);
2889
2890       if (priv->need_timer)
2891         {
2892           GtkSettings *settings;
2893           guint        timeout;
2894
2895           settings = gtk_widget_get_settings (GTK_WIDGET (calendar));
2896           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
2897
2898           priv->need_timer = FALSE;
2899           priv->timer = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE,
2900                                             timeout * SCROLL_DELAY_FACTOR,
2901                                             (GSourceFunc) calendar_timer,
2902                                             (gpointer) calendar, NULL);
2903         }
2904       else
2905         retval = TRUE;
2906     }
2907
2908   return retval;
2909 }
2910
2911 static void
2912 calendar_start_spinning (GtkCalendar *calendar,
2913                          gint         click_child)
2914 {
2915   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2916
2917   priv->click_child = click_child;
2918
2919   if (!priv->timer)
2920     {
2921       GtkSettings *settings;
2922       guint        timeout;
2923
2924       settings = gtk_widget_get_settings (GTK_WIDGET (calendar));
2925       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
2926
2927       priv->need_timer = TRUE;
2928       priv->timer = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE,
2929                                         timeout,
2930                                         (GSourceFunc) calendar_timer,
2931                                         (gpointer) calendar, NULL);
2932     }
2933 }
2934
2935 static void
2936 calendar_stop_spinning (GtkCalendar *calendar)
2937 {
2938   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2939
2940   if (priv->timer)
2941     {
2942       g_source_remove (priv->timer);
2943       priv->timer = 0;
2944       priv->need_timer = FALSE;
2945     }
2946 }
2947
2948 static void
2949 calendar_main_button_press (GtkCalendar    *calendar,
2950                             GdkEventButton *event)
2951 {
2952   GtkWidget *widget = GTK_WIDGET (calendar);
2953   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2954   gint x, y;
2955   gint win_x, win_y;
2956   gint row, col;
2957   gint day_month;
2958   gint day;
2959   GtkAllocation allocation;
2960
2961   x = (gint) (event->x);
2962   y = (gint) (event->y);
2963
2964   gdk_window_get_position (priv->main_win, &win_x, &win_y);
2965   gtk_widget_get_allocation (widget, &allocation);
2966
2967   row = calendar_row_from_y (calendar, y + win_y - allocation.y);
2968   col = calendar_column_from_x (calendar, x + win_x - allocation.x);
2969
2970   /* If row or column isn't found, just return. */
2971   if (row == -1 || col == -1)
2972     return;
2973
2974   day_month = priv->day_month[row][col];
2975
2976   if (event->type == GDK_BUTTON_PRESS)
2977     {
2978       day = priv->day[row][col];
2979
2980       if (day_month == MONTH_PREV)
2981         calendar_set_month_prev (calendar);
2982       else if (day_month == MONTH_NEXT)
2983         calendar_set_month_next (calendar);
2984
2985       if (!gtk_widget_has_focus (widget))
2986         gtk_widget_grab_focus (widget);
2987
2988       if (event->button == 1)
2989         {
2990           priv->in_drag = 1;
2991           priv->drag_start_x = x;
2992           priv->drag_start_y = y;
2993         }
2994
2995       calendar_select_and_focus_day (calendar, day);
2996     }
2997   else if (event->type == GDK_2BUTTON_PRESS)
2998     {
2999       priv->in_drag = 0;
3000       if (day_month == MONTH_CURRENT)
3001         g_signal_emit (calendar,
3002                        gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL],
3003                        0);
3004     }
3005 }
3006
3007 static gboolean
3008 gtk_calendar_button_press (GtkWidget      *widget,
3009                            GdkEventButton *event)
3010 {
3011   GtkCalendar *calendar = GTK_CALENDAR (widget);
3012   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3013   gint arrow = -1;
3014
3015   if (event->window == priv->main_win)
3016     calendar_main_button_press (calendar, event);
3017
3018   if (!gtk_widget_has_focus (widget))
3019     gtk_widget_grab_focus (widget);
3020
3021   for (arrow = ARROW_YEAR_LEFT; arrow <= ARROW_MONTH_RIGHT; arrow++)
3022     {
3023       if (event->window == priv->arrow_win[arrow])
3024         {
3025
3026           /* only call the action on single click, not double */
3027           if (event->type == GDK_BUTTON_PRESS)
3028             {
3029               if (event->button == 1)
3030                 calendar_start_spinning (calendar, arrow);
3031
3032               calendar_arrow_action (calendar, arrow);
3033             }
3034
3035           return TRUE;
3036         }
3037     }
3038
3039   return FALSE;
3040 }
3041
3042 static gboolean
3043 gtk_calendar_button_release (GtkWidget    *widget,
3044                              GdkEventButton *event)
3045 {
3046   GtkCalendar *calendar = GTK_CALENDAR (widget);
3047   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3048
3049   if (event->button == 1)
3050     {
3051       calendar_stop_spinning (calendar);
3052
3053       if (priv->in_drag)
3054         priv->in_drag = 0;
3055     }
3056
3057   return TRUE;
3058 }
3059
3060 static gboolean
3061 gtk_calendar_motion_notify (GtkWidget      *widget,
3062                             GdkEventMotion *event)
3063 {
3064   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3065
3066   if (priv->in_drag)
3067     {
3068       if (gtk_drag_check_threshold (widget,
3069                                     priv->drag_start_x, priv->drag_start_y,
3070                                     event->x, event->y))
3071         {
3072           GdkDragContext *context;
3073           GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
3074           gtk_target_list_add_text_targets (target_list, 0);
3075           context = gtk_drag_begin (widget, target_list, GDK_ACTION_COPY,
3076                                     1, (GdkEvent *)event);
3077
3078           priv->in_drag = 0;
3079           gtk_target_list_unref (target_list);
3080           gtk_drag_set_icon_default (context);
3081         }
3082     }
3083
3084   return TRUE;
3085 }
3086
3087 static gboolean
3088 gtk_calendar_enter_notify (GtkWidget        *widget,
3089                            GdkEventCrossing *event)
3090 {
3091   GtkCalendar *calendar = GTK_CALENDAR (widget);
3092   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3093
3094   if (event->window == priv->arrow_win[ARROW_MONTH_LEFT])
3095     {
3096       priv->arrow_state[ARROW_MONTH_LEFT] |= GTK_STATE_FLAG_PRELIGHT;
3097       calendar_invalidate_arrow (calendar, ARROW_MONTH_LEFT);
3098     }
3099
3100   if (event->window == priv->arrow_win[ARROW_MONTH_RIGHT])
3101     {
3102       priv->arrow_state[ARROW_MONTH_RIGHT] |= GTK_STATE_FLAG_PRELIGHT;
3103       calendar_invalidate_arrow (calendar, ARROW_MONTH_RIGHT);
3104     }
3105
3106   if (event->window == priv->arrow_win[ARROW_YEAR_LEFT])
3107     {
3108       priv->arrow_state[ARROW_YEAR_LEFT] |= GTK_STATE_FLAG_PRELIGHT;
3109       calendar_invalidate_arrow (calendar, ARROW_YEAR_LEFT);
3110     }
3111
3112   if (event->window == priv->arrow_win[ARROW_YEAR_RIGHT])
3113     {
3114       priv->arrow_state[ARROW_YEAR_RIGHT] |= GTK_STATE_FLAG_PRELIGHT;
3115       calendar_invalidate_arrow (calendar, ARROW_YEAR_RIGHT);
3116     }
3117
3118   return TRUE;
3119 }
3120
3121 static gboolean
3122 gtk_calendar_leave_notify (GtkWidget        *widget,
3123                            GdkEventCrossing *event)
3124 {
3125   GtkCalendar *calendar = GTK_CALENDAR (widget);
3126   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3127
3128   if (event->window == priv->arrow_win[ARROW_MONTH_LEFT])
3129     {
3130       priv->arrow_state[ARROW_MONTH_LEFT] &= ~(GTK_STATE_FLAG_PRELIGHT);
3131       calendar_invalidate_arrow (calendar, ARROW_MONTH_LEFT);
3132     }
3133
3134   if (event->window == priv->arrow_win[ARROW_MONTH_RIGHT])
3135     {
3136       priv->arrow_state[ARROW_MONTH_RIGHT] &= ~(GTK_STATE_FLAG_PRELIGHT);
3137       calendar_invalidate_arrow (calendar, ARROW_MONTH_RIGHT);
3138     }
3139
3140   if (event->window == priv->arrow_win[ARROW_YEAR_LEFT])
3141     {
3142       priv->arrow_state[ARROW_YEAR_LEFT] &= ~(GTK_STATE_FLAG_PRELIGHT);
3143       calendar_invalidate_arrow (calendar, ARROW_YEAR_LEFT);
3144     }
3145
3146   if (event->window == priv->arrow_win[ARROW_YEAR_RIGHT])
3147     {
3148       priv->arrow_state[ARROW_YEAR_RIGHT] &= ~(GTK_STATE_FLAG_PRELIGHT);
3149       calendar_invalidate_arrow (calendar, ARROW_YEAR_RIGHT);
3150     }
3151
3152   return TRUE;
3153 }
3154
3155 static gboolean
3156 gtk_calendar_scroll (GtkWidget      *widget,
3157                      GdkEventScroll *event)
3158 {
3159   GtkCalendar *calendar = GTK_CALENDAR (widget);
3160
3161   if (event->direction == GDK_SCROLL_UP)
3162     {
3163       if (!gtk_widget_has_focus (widget))
3164         gtk_widget_grab_focus (widget);
3165       calendar_set_month_prev (calendar);
3166     }
3167   else if (event->direction == GDK_SCROLL_DOWN)
3168     {
3169       if (!gtk_widget_has_focus (widget))
3170         gtk_widget_grab_focus (widget);
3171       calendar_set_month_next (calendar);
3172     }
3173   else
3174     return FALSE;
3175
3176   return TRUE;
3177 }
3178
3179 \f
3180 /****************************************
3181  *             Key handling              *
3182  ****************************************/
3183
3184 static void
3185 move_focus (GtkCalendar *calendar,
3186             gint         direction)
3187 {
3188   GtkCalendarPrivate *priv = calendar->priv;
3189   GtkTextDirection text_dir = gtk_widget_get_direction (GTK_WIDGET (calendar));
3190
3191   if ((text_dir == GTK_TEXT_DIR_LTR && direction == -1) ||
3192       (text_dir == GTK_TEXT_DIR_RTL && direction == 1))
3193     {
3194       if (priv->focus_col > 0)
3195           priv->focus_col--;
3196       else if (priv->focus_row > 0)
3197         {
3198           priv->focus_col = 6;
3199           priv->focus_row--;
3200         }
3201
3202       if (priv->focus_col < 0)
3203         priv->focus_col = 6;
3204       if (priv->focus_row < 0)
3205         priv->focus_row = 5;
3206     }
3207   else
3208     {
3209       if (priv->focus_col < 6)
3210         priv->focus_col++;
3211       else if (priv->focus_row < 5)
3212         {
3213           priv->focus_col = 0;
3214           priv->focus_row++;
3215         }
3216
3217       if (priv->focus_col < 0)
3218         priv->focus_col = 0;
3219       if (priv->focus_row < 0)
3220         priv->focus_row = 0;
3221     }
3222 }
3223
3224 static gboolean
3225 gtk_calendar_key_press (GtkWidget   *widget,
3226                         GdkEventKey *event)
3227 {
3228   GtkCalendar *calendar = GTK_CALENDAR (widget);
3229   GtkCalendarPrivate *priv = calendar->priv;
3230   gint return_val;
3231   gint old_focus_row;
3232   gint old_focus_col;
3233   gint row, col, day;
3234
3235   return_val = FALSE;
3236
3237   old_focus_row = priv->focus_row;
3238   old_focus_col = priv->focus_col;
3239
3240   switch (event->keyval)
3241     {
3242     case GDK_KEY_KP_Left:
3243     case GDK_KEY_Left:
3244       return_val = TRUE;
3245       if (event->state & GDK_CONTROL_MASK)
3246         calendar_set_month_prev (calendar);
3247       else
3248         {
3249           move_focus (calendar, -1);
3250           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3251           calendar_invalidate_day (calendar, priv->focus_row,
3252                                    priv->focus_col);
3253         }
3254       break;
3255     case GDK_KEY_KP_Right:
3256     case GDK_KEY_Right:
3257       return_val = TRUE;
3258       if (event->state & GDK_CONTROL_MASK)
3259         calendar_set_month_next (calendar);
3260       else
3261         {
3262           move_focus (calendar, 1);
3263           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3264           calendar_invalidate_day (calendar, priv->focus_row,
3265                                    priv->focus_col);
3266         }
3267       break;
3268     case GDK_KEY_KP_Up:
3269     case GDK_KEY_Up:
3270       return_val = TRUE;
3271       if (event->state & GDK_CONTROL_MASK)
3272         calendar_set_year_prev (calendar);
3273       else
3274         {
3275           if (priv->focus_row > 0)
3276             priv->focus_row--;
3277           if (priv->focus_row < 0)
3278             priv->focus_row = 5;
3279           if (priv->focus_col < 0)
3280             priv->focus_col = 6;
3281           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3282           calendar_invalidate_day (calendar, priv->focus_row,
3283                                    priv->focus_col);
3284         }
3285       break;
3286     case GDK_KEY_KP_Down:
3287     case GDK_KEY_Down:
3288       return_val = TRUE;
3289       if (event->state & GDK_CONTROL_MASK)
3290         calendar_set_year_next (calendar);
3291       else
3292         {
3293           if (priv->focus_row < 5)
3294             priv->focus_row++;
3295           if (priv->focus_col < 0)
3296             priv->focus_col = 0;
3297           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3298           calendar_invalidate_day (calendar, priv->focus_row,
3299                                    priv->focus_col);
3300         }
3301       break;
3302     case GDK_KEY_KP_Space:
3303     case GDK_KEY_space:
3304       row = priv->focus_row;
3305       col = priv->focus_col;
3306
3307       if (row > -1 && col > -1)
3308         {
3309           return_val = TRUE;
3310
3311           day = priv->day[row][col];
3312           if (priv->day_month[row][col] == MONTH_PREV)
3313             calendar_set_month_prev (calendar);
3314           else if (priv->day_month[row][col] == MONTH_NEXT)
3315             calendar_set_month_next (calendar);
3316
3317           calendar_select_and_focus_day (calendar, day);
3318         }
3319     }
3320
3321   return return_val;
3322 }
3323
3324 \f
3325 /****************************************
3326  *           Misc widget methods        *
3327  ****************************************/
3328
3329 static void
3330 gtk_calendar_state_flags_changed (GtkWidget     *widget,
3331                                   GtkStateFlags  previous_state)
3332 {
3333   GtkCalendar *calendar = GTK_CALENDAR (widget);
3334   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3335   int i;
3336
3337   if (!gtk_widget_is_sensitive (widget))
3338     {
3339       priv->in_drag = 0;
3340       calendar_stop_spinning (calendar);
3341     }
3342
3343   for (i = 0; i < 4; i++)
3344     if (gtk_widget_is_sensitive (widget))
3345       priv->arrow_state[i] &= ~(GTK_STATE_FLAG_INSENSITIVE);
3346     else
3347       priv->arrow_state[i] |= GTK_STATE_FLAG_INSENSITIVE;
3348 }
3349
3350 static void
3351 gtk_calendar_grab_notify (GtkWidget *widget,
3352                           gboolean   was_grabbed)
3353 {
3354   if (!was_grabbed)
3355     calendar_stop_spinning (GTK_CALENDAR (widget));
3356 }
3357
3358 static gboolean
3359 gtk_calendar_focus_out (GtkWidget     *widget,
3360                         GdkEventFocus *event)
3361 {
3362   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3363   GtkCalendar *calendar = GTK_CALENDAR (widget);
3364
3365   calendar_queue_refresh (calendar);
3366   calendar_stop_spinning (calendar);
3367
3368   priv->in_drag = 0;
3369
3370   return FALSE;
3371 }
3372
3373 \f
3374 /****************************************
3375  *          Drag and Drop               *
3376  ****************************************/
3377
3378 static void
3379 gtk_calendar_drag_data_get (GtkWidget        *widget,
3380                             GdkDragContext   *context,
3381                             GtkSelectionData *selection_data,
3382                             guint             info,
3383                             guint             time)
3384 {
3385   GtkCalendar *calendar = GTK_CALENDAR (widget);
3386   GtkCalendarPrivate *priv = calendar->priv;
3387   GDate *date;
3388   gchar str[128];
3389   gsize len;
3390
3391   date = g_date_new_dmy (priv->selected_day, priv->month + 1, priv->year);
3392   len = g_date_strftime (str, 127, "%x", date);
3393   gtk_selection_data_set_text (selection_data, str, len);
3394
3395   g_free (date);
3396 }
3397
3398 /* Get/set whether drag_motion requested the drag data and
3399  * drag_data_received should thus not actually insert the data,
3400  * since the data doesn't result from a drop.
3401  */
3402 static void
3403 set_status_pending (GdkDragContext *context,
3404                     GdkDragAction   suggested_action)
3405 {
3406   g_object_set_data (G_OBJECT (context),
3407                      I_("gtk-calendar-status-pending"),
3408                      GINT_TO_POINTER (suggested_action));
3409 }
3410
3411 static GdkDragAction
3412 get_status_pending (GdkDragContext *context)
3413 {
3414   return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (context),
3415                                              "gtk-calendar-status-pending"));
3416 }
3417
3418 static void
3419 gtk_calendar_drag_leave (GtkWidget      *widget,
3420                          GdkDragContext *context,
3421                          guint           time)
3422 {
3423   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3424
3425   priv->drag_highlight = 0;
3426   gtk_drag_unhighlight (widget);
3427
3428 }
3429
3430 static gboolean
3431 gtk_calendar_drag_motion (GtkWidget      *widget,
3432                           GdkDragContext *context,
3433                           gint            x,
3434                           gint            y,
3435                           guint           time)
3436 {
3437   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3438   GdkAtom target;
3439
3440   if (!priv->drag_highlight)
3441     {
3442       priv->drag_highlight = 1;
3443       gtk_drag_highlight (widget);
3444     }
3445
3446   target = gtk_drag_dest_find_target (widget, context, NULL);
3447   if (target == GDK_NONE || gdk_drag_context_get_suggested_action (context) == 0)
3448     gdk_drag_status (context, 0, time);
3449   else
3450     {
3451       set_status_pending (context, gdk_drag_context_get_suggested_action (context));
3452       gtk_drag_get_data (widget, context, target, time);
3453     }
3454
3455   return TRUE;
3456 }
3457
3458 static gboolean
3459 gtk_calendar_drag_drop (GtkWidget      *widget,
3460                         GdkDragContext *context,
3461                         gint            x,
3462                         gint            y,
3463                         guint           time)
3464 {
3465   GdkAtom target;
3466
3467   target = gtk_drag_dest_find_target (widget, context, NULL);
3468   if (target != GDK_NONE)
3469     {
3470       gtk_drag_get_data (widget, context,
3471                          target,
3472                          time);
3473       return TRUE;
3474     }
3475
3476   return FALSE;
3477 }
3478
3479 static void
3480 gtk_calendar_drag_data_received (GtkWidget        *widget,
3481                                  GdkDragContext   *context,
3482                                  gint              x,
3483                                  gint              y,
3484                                  GtkSelectionData *selection_data,
3485                                  guint             info,
3486                                  guint             time)
3487 {
3488   GtkCalendar *calendar = GTK_CALENDAR (widget);
3489   GtkCalendarPrivate *priv = calendar->priv;
3490   guint day, month, year;
3491   gchar *str;
3492   GDate *date;
3493   GdkDragAction suggested_action;
3494
3495   suggested_action = get_status_pending (context);
3496
3497   if (suggested_action)
3498     {
3499       set_status_pending (context, 0);
3500
3501       /* We are getting this data due to a request in drag_motion,
3502        * rather than due to a request in drag_drop, so we are just
3503        * supposed to call drag_status, not actually paste in the
3504        * data.
3505        */
3506       str = (gchar*) gtk_selection_data_get_text (selection_data);
3507
3508       if (str)
3509         {
3510           date = g_date_new ();
3511           g_date_set_parse (date, str);
3512           if (!g_date_valid (date))
3513               suggested_action = 0;
3514           g_date_free (date);
3515           g_free (str);
3516         }
3517       else
3518         suggested_action = 0;
3519
3520       gdk_drag_status (context, suggested_action, time);
3521
3522       return;
3523     }
3524
3525   date = g_date_new ();
3526   str = (gchar*) gtk_selection_data_get_text (selection_data);
3527   if (str)
3528     {
3529       g_date_set_parse (date, str);
3530       g_free (str);
3531     }
3532
3533   if (!g_date_valid (date))
3534     {
3535       g_warning ("Received invalid date data\n");
3536       g_date_free (date);
3537       gtk_drag_finish (context, FALSE, FALSE, time);
3538       return;
3539     }
3540
3541   day = g_date_get_day (date);
3542   month = g_date_get_month (date);
3543   year = g_date_get_year (date);
3544   g_date_free (date);
3545
3546   gtk_drag_finish (context, TRUE, FALSE, time);
3547
3548
3549   g_object_freeze_notify (G_OBJECT (calendar));
3550   if (!(priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
3551       && (priv->display_flags & GTK_CALENDAR_SHOW_HEADING))
3552     gtk_calendar_select_month (calendar, month - 1, year);
3553   gtk_calendar_select_day (calendar, day);
3554   g_object_thaw_notify (G_OBJECT (calendar));
3555 }
3556
3557 \f
3558 /****************************************
3559  *              Public API              *
3560  ****************************************/
3561
3562 /**
3563  * gtk_calendar_new:
3564  *
3565  * Creates a new calendar, with the current date being selected.
3566  *
3567  * Return value: a newly #GtkCalendar widget
3568  **/
3569 GtkWidget*
3570 gtk_calendar_new (void)
3571 {
3572   return g_object_new (GTK_TYPE_CALENDAR, NULL);
3573 }
3574
3575 /**
3576  * gtk_calendar_get_display_options:
3577  * @calendar: a #GtkCalendar
3578  *
3579  * Returns the current display options of @calendar.
3580  *
3581  * Return value: the display options.
3582  *
3583  * Since: 2.4
3584  **/
3585 GtkCalendarDisplayOptions
3586 gtk_calendar_get_display_options (GtkCalendar         *calendar)
3587 {
3588   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
3589
3590   return calendar->priv->display_flags;
3591 }
3592
3593 /**
3594  * gtk_calendar_set_display_options:
3595  * @calendar: a #GtkCalendar
3596  * @flags: the display options to set
3597  *
3598  * Sets display options (whether to display the heading and the month
3599  * headings).
3600  *
3601  * Since: 2.4
3602  **/
3603 void
3604 gtk_calendar_set_display_options (GtkCalendar          *calendar,
3605                                   GtkCalendarDisplayOptions flags)
3606 {
3607   GtkWidget *widget = GTK_WIDGET (calendar);
3608   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3609   gint resize = 0;
3610   GtkCalendarDisplayOptions old_flags;
3611
3612   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3613
3614   old_flags = priv->display_flags;
3615
3616   if (gtk_widget_get_realized (widget))
3617     {
3618       if ((flags ^ priv->display_flags) & GTK_CALENDAR_NO_MONTH_CHANGE)
3619         {
3620           resize ++;
3621           if (! (flags & GTK_CALENDAR_NO_MONTH_CHANGE)
3622               && (priv->display_flags & GTK_CALENDAR_SHOW_HEADING))
3623             {
3624               priv->display_flags &= ~GTK_CALENDAR_NO_MONTH_CHANGE;
3625               calendar_realize_arrows (calendar);
3626               if (gtk_widget_get_mapped (widget))
3627                 calendar_map_arrows (calendar);
3628             }
3629           else
3630             {
3631               calendar_unrealize_arrows (calendar);
3632             }
3633         }
3634
3635       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_HEADING)
3636         {
3637           resize++;
3638
3639           if (flags & GTK_CALENDAR_SHOW_HEADING)
3640             {
3641               priv->display_flags |= GTK_CALENDAR_SHOW_HEADING;
3642               calendar_realize_arrows (calendar);
3643               if (gtk_widget_get_mapped (widget))
3644                 calendar_map_arrows (calendar);
3645             }
3646           else
3647             {
3648               calendar_unrealize_arrows (calendar);
3649             }
3650         }
3651
3652       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DAY_NAMES)
3653         {
3654           resize++;
3655
3656           if (flags & GTK_CALENDAR_SHOW_DAY_NAMES)
3657             priv->display_flags |= GTK_CALENDAR_SHOW_DAY_NAMES;
3658         }
3659
3660       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3661         {
3662           resize++;
3663
3664           if (flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3665             priv->display_flags |= GTK_CALENDAR_SHOW_WEEK_NUMBERS;
3666         }
3667
3668       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DETAILS)
3669         resize++;
3670
3671       priv->display_flags = flags;
3672       if (resize)
3673         gtk_widget_queue_resize (GTK_WIDGET (calendar));
3674     }
3675   else
3676     priv->display_flags = flags;
3677
3678   g_object_freeze_notify (G_OBJECT (calendar));
3679   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_HEADING)
3680     g_object_notify (G_OBJECT (calendar), "show-heading");
3681   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DAY_NAMES)
3682     g_object_notify (G_OBJECT (calendar), "show-day-names");
3683   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_NO_MONTH_CHANGE)
3684     g_object_notify (G_OBJECT (calendar), "no-month-change");
3685   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3686     g_object_notify (G_OBJECT (calendar), "show-week-numbers");
3687   g_object_thaw_notify (G_OBJECT (calendar));
3688 }
3689
3690 /**
3691  * gtk_calendar_select_month:
3692  * @calendar: a #GtkCalendar
3693  * @month: a month number between 0 and 11.
3694  * @year: the year the month is in.
3695  *
3696  * Shifts the calendar to a different month.
3697  **/
3698 void
3699 gtk_calendar_select_month (GtkCalendar *calendar,
3700                            guint        month,
3701                            guint        year)
3702 {
3703   GtkCalendarPrivate *priv;
3704
3705   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3706   g_return_if_fail (month <= 11);
3707
3708   priv = calendar->priv;
3709
3710   priv->month = month;
3711   priv->year  = year;
3712
3713   calendar_compute_days (calendar);
3714   calendar_queue_refresh (calendar);
3715
3716   g_object_freeze_notify (G_OBJECT (calendar));
3717   g_object_notify (G_OBJECT (calendar), "month");
3718   g_object_notify (G_OBJECT (calendar), "year");
3719   g_object_thaw_notify (G_OBJECT (calendar));
3720
3721   g_signal_emit (calendar,
3722                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
3723                  0);
3724 }
3725
3726 /**
3727  * gtk_calendar_select_day:
3728  * @calendar: a #GtkCalendar.
3729  * @day: the day number between 1 and 31, or 0 to unselect
3730  *   the currently selected day.
3731  *
3732  * Selects a day from the current month.
3733  **/
3734 void
3735 gtk_calendar_select_day (GtkCalendar *calendar,
3736                          guint        day)
3737 {
3738   GtkCalendarPrivate *priv;
3739
3740   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3741   g_return_if_fail (day <= 31);
3742
3743   priv = calendar->priv;
3744
3745   /* Deselect the old day */
3746   if (priv->selected_day > 0)
3747     {
3748       gint selected_day;
3749
3750       selected_day = priv->selected_day;
3751       priv->selected_day = 0;
3752       if (gtk_widget_is_drawable (GTK_WIDGET (calendar)))
3753         calendar_invalidate_day_num (calendar, selected_day);
3754     }
3755
3756   priv->selected_day = day;
3757
3758   /* Select the new day */
3759   if (day != 0)
3760     {
3761       if (gtk_widget_is_drawable (GTK_WIDGET (calendar)))
3762         calendar_invalidate_day_num (calendar, day);
3763     }
3764
3765   g_object_notify (G_OBJECT (calendar), "day");
3766
3767   g_signal_emit (calendar,
3768                  gtk_calendar_signals[DAY_SELECTED_SIGNAL],
3769                  0);
3770 }
3771
3772 /**
3773  * gtk_calendar_clear_marks:
3774  * @calendar: a #GtkCalendar
3775  *
3776  * Remove all visual markers.
3777  **/
3778 void
3779 gtk_calendar_clear_marks (GtkCalendar *calendar)
3780 {
3781   GtkCalendarPrivate *priv;
3782   guint day;
3783
3784   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3785
3786   priv = calendar->priv;
3787
3788   for (day = 0; day < 31; day++)
3789     {
3790       priv->marked_date[day] = FALSE;
3791     }
3792
3793   priv->num_marked_dates = 0;
3794   calendar_queue_refresh (calendar);
3795 }
3796
3797 /**
3798  * gtk_calendar_mark_day:
3799  * @calendar: a #GtkCalendar
3800  * @day: the day number to mark between 1 and 31.
3801  *
3802  * Places a visual marker on a particular day.
3803  */
3804 void
3805 gtk_calendar_mark_day (GtkCalendar *calendar,
3806                        guint        day)
3807 {
3808   GtkCalendarPrivate *priv;
3809
3810   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3811
3812   priv = calendar->priv;
3813
3814   if (day >= 1 && day <= 31 && !priv->marked_date[day-1])
3815     {
3816       priv->marked_date[day - 1] = TRUE;
3817       priv->num_marked_dates++;
3818       calendar_invalidate_day_num (calendar, day);
3819     }
3820 }
3821
3822 /**
3823  * gtk_calendar_get_day_is_marked:
3824  * @calendar: a #GtkCalendar
3825  * @day: the day number between 1 and 31.
3826  *
3827  * Returns if the @day of the @calendar is already marked.
3828  *
3829  * Returns: whether the day is marked.
3830  *
3831  * Since: 3.0
3832  */
3833 gboolean
3834 gtk_calendar_get_day_is_marked (GtkCalendar *calendar,
3835                                 guint        day)
3836 {
3837   GtkCalendarPrivate *priv;
3838
3839   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), FALSE);
3840
3841   priv = calendar->priv;
3842
3843   if (day >= 1 && day <= 31)
3844     return priv->marked_date[day - 1];
3845
3846   return FALSE;
3847 }
3848
3849 /**
3850  * gtk_calendar_unmark_day:
3851  * @calendar: a #GtkCalendar.
3852  * @day: the day number to unmark between 1 and 31.
3853  *
3854  * Removes the visual marker from a particular day.
3855  */
3856 void
3857 gtk_calendar_unmark_day (GtkCalendar *calendar,
3858                          guint        day)
3859 {
3860   GtkCalendarPrivate *priv;
3861
3862   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3863
3864   priv = calendar->priv;
3865
3866   if (day >= 1 && day <= 31 && priv->marked_date[day-1])
3867     {
3868       priv->marked_date[day - 1] = FALSE;
3869       priv->num_marked_dates--;
3870       calendar_invalidate_day_num (calendar, day);
3871     }
3872 }
3873
3874 /**
3875  * gtk_calendar_get_date:
3876  * @calendar: a #GtkCalendar
3877  * @year: (out) (allow-none): location to store the year as a decimal
3878  *     number (e.g. 2011), or %NULL
3879  * @month: (out) (allow-none): location to store the month number
3880  *     (between 0 and 11), or %NULL
3881  * @day: (out) (allow-none): location to store the day number (between
3882  *     1 and 31), or %NULL
3883  *
3884  * Obtains the selected date from a #GtkCalendar.
3885  */
3886 void
3887 gtk_calendar_get_date (GtkCalendar *calendar,
3888                        guint       *year,
3889                        guint       *month,
3890                        guint       *day)
3891 {
3892   GtkCalendarPrivate *priv;
3893
3894   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3895
3896   priv = calendar->priv;
3897
3898   if (year)
3899     *year = priv->year;
3900
3901   if (month)
3902     *month = priv->month;
3903
3904   if (day)
3905     *day = priv->selected_day;
3906 }
3907
3908 /**
3909  * gtk_calendar_set_detail_func:
3910  * @calendar: a #GtkCalendar.
3911  * @func: a function providing details for each day.
3912  * @data: data to pass to @func invokations.
3913  * @destroy: a function for releasing @data.
3914  *
3915  * Installs a function which provides Pango markup with detail information
3916  * for each day. Examples for such details are holidays or appointments. That
3917  * information is shown below each day when #GtkCalendar:show-details is set.
3918  * A tooltip containing with full detail information is provided, if the entire
3919  * text should not fit into the details area, or if #GtkCalendar:show-details
3920  * is not set.
3921  *
3922  * The size of the details area can be restricted by setting the
3923  * #GtkCalendar:detail-width-chars and #GtkCalendar:detail-height-rows
3924  * properties.
3925  *
3926  * Since: 2.14
3927  */
3928 void
3929 gtk_calendar_set_detail_func (GtkCalendar           *calendar,
3930                               GtkCalendarDetailFunc  func,
3931                               gpointer               data,
3932                               GDestroyNotify         destroy)
3933 {
3934   GtkCalendarPrivate *priv;
3935
3936   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3937
3938   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3939
3940   if (priv->detail_func_destroy)
3941     priv->detail_func_destroy (priv->detail_func_user_data);
3942
3943   priv->detail_func = func;
3944   priv->detail_func_user_data = data;
3945   priv->detail_func_destroy = destroy;
3946
3947   gtk_widget_set_has_tooltip (GTK_WIDGET (calendar),
3948                               NULL != priv->detail_func);
3949   gtk_widget_queue_resize (GTK_WIDGET (calendar));
3950 }
3951
3952 /**
3953  * gtk_calendar_set_detail_width_chars:
3954  * @calendar: a #GtkCalendar.
3955  * @chars: detail width in characters.
3956  *
3957  * Updates the width of detail cells.
3958  * See #GtkCalendar:detail-width-chars.
3959  *
3960  * Since: 2.14
3961  */
3962 void
3963 gtk_calendar_set_detail_width_chars (GtkCalendar *calendar,
3964                                      gint         chars)
3965 {
3966   GtkCalendarPrivate *priv;
3967
3968   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3969
3970   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3971
3972   if (chars != priv->detail_width_chars)
3973     {
3974       priv->detail_width_chars = chars;
3975       g_object_notify (G_OBJECT (calendar), "detail-width-chars");
3976       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
3977     }
3978 }
3979
3980 /**
3981  * gtk_calendar_set_detail_height_rows:
3982  * @calendar: a #GtkCalendar.
3983  * @rows: detail height in rows.
3984  *
3985  * Updates the height of detail cells.
3986  * See #GtkCalendar:detail-height-rows.
3987  *
3988  * Since: 2.14
3989  */
3990 void
3991 gtk_calendar_set_detail_height_rows (GtkCalendar *calendar,
3992                                      gint         rows)
3993 {
3994   GtkCalendarPrivate *priv;
3995
3996   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3997
3998   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3999
4000   if (rows != priv->detail_height_rows)
4001     {
4002       priv->detail_height_rows = rows;
4003       g_object_notify (G_OBJECT (calendar), "detail-height-rows");
4004       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
4005     }
4006 }
4007
4008 /**
4009  * gtk_calendar_get_detail_width_chars:
4010  * @calendar: a #GtkCalendar.
4011  *
4012  * Queries the width of detail cells, in characters.
4013  * See #GtkCalendar:detail-width-chars.
4014  *
4015  * Since: 2.14
4016  *
4017  * Return value: The width of detail cells, in characters.
4018  */
4019 gint
4020 gtk_calendar_get_detail_width_chars (GtkCalendar *calendar)
4021 {
4022   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
4023   return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_width_chars;
4024 }
4025
4026 /**
4027  * gtk_calendar_get_detail_height_rows:
4028  * @calendar: a #GtkCalendar.
4029  *
4030  * Queries the height of detail cells, in rows.
4031  * See #GtkCalendar:detail-width-chars.
4032  *
4033  * Since: 2.14
4034  *
4035  * Return value: The height of detail cells, in rows.
4036  */
4037 gint
4038 gtk_calendar_get_detail_height_rows (GtkCalendar *calendar)
4039 {
4040   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
4041   return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_height_rows;
4042 }