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