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