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