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