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