]> Pileus Git - ~andy/gtk/blob - gtk/gtkcalendar.c
calendar: Use gtk_cairo_transform_to_window()
[~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   gint 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   gtk_cairo_transform_to_window (cr, widget, priv->header_win);
2331
2332   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) 
2333     year_left = priv->year_before;
2334   else
2335     year_left = !priv->year_before;
2336
2337   gtk_widget_get_allocation (widget, &allocation);
2338   style = gtk_widget_get_style (widget);
2339
2340   header_width = allocation.width - 2 * style->xthickness;
2341
2342   max_month_width = priv->max_month_width;
2343   max_year_width = priv->max_year_width;
2344
2345   gtk_paint_shadow (style, cr,
2346                     GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2347                     widget, "calendar",
2348                     0, 0, header_width, priv->header_h);
2349
2350   tmp_time = 1;  /* Jan 1 1970, 00:00:01 UTC */
2351   tm = gmtime (&tmp_time);
2352   tm->tm_year = priv->year - 1900;
2353
2354   /* Translators: This dictates how the year is displayed in
2355    * gtkcalendar widget.  See strftime() manual for the format.
2356    * Use only ASCII in the translation.
2357    *
2358    * Also look for the msgid "2000".
2359    * Translate that entry to a year with the widest output of this
2360    * msgid.
2361    *
2362    * "%Y" is appropriate for most locales.
2363    */
2364   strftime (buffer, sizeof (buffer), C_("calendar year format", "%Y"), tm);
2365   str = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
2366   layout = gtk_widget_create_pango_layout (widget, str);
2367   g_free (str);
2368   
2369   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2370   
2371   /* Draw title */
2372   y = (priv->header_h - logical_rect.height) / 2;
2373   
2374   /* Draw year and its arrows */
2375   
2376   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
2377     if (year_left)
2378       x = 3 + (max_year_width - logical_rect.width)/2;
2379     else
2380       x = header_width - (3 + max_year_width
2381                           - (max_year_width - logical_rect.width)/2);
2382   else
2383     if (year_left)
2384       x = 3 + priv->arrow_width + (max_year_width - logical_rect.width)/2;
2385     else
2386       x = header_width - (3 + priv->arrow_width + max_year_width
2387                           - (max_year_width - logical_rect.width)/2);
2388   
2389
2390   gdk_cairo_set_source_color (cr, HEADER_FG_COLOR (GTK_WIDGET (calendar)));
2391   cairo_move_to (cr, x, y);
2392   pango_cairo_show_layout (cr, layout);
2393   
2394   /* Draw month */
2395   g_snprintf (buffer, sizeof (buffer), "%s", default_monthname[priv->month]);
2396   pango_layout_set_text (layout, buffer, -1);
2397   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2398
2399   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
2400     if (year_left)
2401       x = header_width - (3 + max_month_width
2402                           - (max_month_width - logical_rect.width)/2);      
2403     else
2404     x = 3 + (max_month_width - logical_rect.width) / 2;
2405   else
2406     if (year_left)
2407       x = header_width - (3 + priv->arrow_width + max_month_width
2408                           - (max_month_width - logical_rect.width)/2);
2409     else
2410     x = 3 + priv->arrow_width + (max_month_width - logical_rect.width)/2;
2411
2412   cairo_move_to (cr, x, y);
2413   pango_cairo_show_layout (cr, layout);
2414
2415   g_object_unref (layout);
2416
2417   cairo_restore (cr);
2418 }
2419
2420 static void
2421 calendar_paint_day_names (GtkCalendar *calendar,
2422                           cairo_t     *cr)
2423 {
2424   GtkWidget *widget = GTK_WIDGET (calendar);
2425   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2426   GtkAllocation allocation;
2427   char buffer[255];
2428   int day,i;
2429   int day_width, cal_width;
2430   int day_wid_sep;
2431   PangoLayout *layout;
2432   PangoRectangle logical_rect;
2433   gint focus_padding;
2434   gint focus_width;
2435   gint calendar_ysep = calendar_get_ysep (calendar);
2436   gint calendar_xsep = calendar_get_xsep (calendar);
2437
2438   cairo_save (cr);
2439   gtk_cairo_transform_to_window (cr, widget, priv->day_name_win);
2440
2441   gtk_widget_style_get (GTK_WIDGET (widget),
2442                         "focus-line-width", &focus_width,
2443                         "focus-padding", &focus_padding,
2444                         NULL);
2445
2446   gtk_widget_get_allocation (widget, &allocation);
2447
2448   day_width = priv->day_width;
2449   cal_width = allocation.width;
2450   day_wid_sep = day_width + DAY_XSEP;
2451   
2452   /*
2453    * Draw rectangles as inverted background for the labels.
2454    */
2455
2456   gdk_cairo_set_source_color (cr, SELECTED_BG_COLOR (widget));
2457   cairo_rectangle (cr,
2458                    CALENDAR_MARGIN, CALENDAR_MARGIN,
2459                    cal_width-CALENDAR_MARGIN * 2,
2460                    priv->day_name_h - CALENDAR_MARGIN);
2461   cairo_fill (cr);
2462   
2463   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
2464     {
2465       cairo_rectangle (cr, 
2466                        CALENDAR_MARGIN,
2467                        priv->day_name_h - calendar_ysep,
2468                        priv->week_width - calendar_ysep - CALENDAR_MARGIN,
2469                        calendar_ysep);
2470       cairo_fill (cr);
2471     }
2472   
2473   /*
2474    * Write the labels
2475    */
2476
2477   layout = gtk_widget_create_pango_layout (widget, NULL);
2478
2479   gdk_cairo_set_source_color (cr, SELECTED_FG_COLOR (widget));
2480   for (i = 0; i < 7; i++)
2481     {
2482       if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
2483         day = 6 - i;
2484       else
2485         day = i;
2486       day = (day + priv->week_start) % 7;
2487       g_snprintf (buffer, sizeof (buffer), "%s", default_abbreviated_dayname[day]);
2488
2489       pango_layout_set_text (layout, buffer, -1);
2490       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2491
2492       cairo_move_to (cr, 
2493                      (CALENDAR_MARGIN +
2494                       + (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR ?
2495                          (priv->week_width + (priv->week_width ? calendar_xsep : 0))
2496                          : 0)
2497                       + day_wid_sep * i
2498                       + (day_width - logical_rect.width)/2),
2499                      CALENDAR_MARGIN + focus_width + focus_padding + logical_rect.y);
2500       pango_cairo_show_layout (cr, layout);
2501     }
2502   
2503   g_object_unref (layout);
2504
2505   cairo_restore (cr);
2506 }
2507
2508 static void
2509 calendar_paint_week_numbers (GtkCalendar *calendar,
2510                              cairo_t     *cr)
2511 {
2512   GtkWidget *widget = GTK_WIDGET (calendar);
2513   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2514   guint week = 0, year;
2515   gint row, x_loc, y_loc;
2516   gint day_height;
2517   char buffer[32];
2518   PangoLayout *layout;
2519   PangoRectangle logical_rect;
2520   gint focus_padding;
2521   gint focus_width;
2522   gint calendar_xsep = calendar_get_xsep (calendar);
2523
2524   cairo_save (cr);
2525   gtk_cairo_transform_to_window (cr, widget, priv->week_win);
2526
2527   gtk_widget_style_get (GTK_WIDGET (widget),
2528                         "focus-line-width", &focus_width,
2529                         "focus-padding", &focus_padding,
2530                         NULL);
2531   
2532   /*
2533    * Draw a rectangle as inverted background for the labels.
2534    */
2535
2536   gdk_cairo_set_source_color (cr, SELECTED_BG_COLOR (widget));
2537   if (priv->day_name_win)
2538     cairo_rectangle (cr, 
2539                      CALENDAR_MARGIN,
2540                      0,
2541                      priv->week_width - CALENDAR_MARGIN,
2542                      priv->main_h - CALENDAR_MARGIN);
2543   else
2544     cairo_rectangle (cr,
2545                      CALENDAR_MARGIN,
2546                      CALENDAR_MARGIN,
2547                      priv->week_width - CALENDAR_MARGIN,
2548                      priv->main_h - 2 * CALENDAR_MARGIN);
2549   cairo_fill (cr);
2550   
2551   /*
2552    * Write the labels
2553    */
2554   
2555   layout = gtk_widget_create_pango_layout (widget, NULL);
2556   
2557   gdk_cairo_set_source_color (cr, SELECTED_FG_COLOR (widget));
2558   day_height = calendar_row_height (calendar);
2559   for (row = 0; row < 6; row++)
2560     {
2561       gboolean result;
2562       
2563       year = priv->year;
2564       if (priv->day[row][6] < 15 && row > 3 && priv->month == 11)
2565         year++;
2566
2567       result = week_of_year (&week, &year,              
2568                              ((priv->day[row][6] < 15 && row > 3 ? 1 : 0)
2569                               + priv->month) % 12 + 1, priv->day[row][6]);
2570       g_return_if_fail (result);
2571
2572       /* Translators: this defines whether the week numbers should use
2573        * localized digits or the ones used in English (0123...).
2574        *
2575        * Translate to "%Id" if you want to use localized digits, or
2576        * translate to "%d" otherwise.
2577        *
2578        * Note that translating this doesn't guarantee that you get localized
2579        * digits. That needs support from your system and locale definition
2580        * too.
2581        */
2582       g_snprintf (buffer, sizeof (buffer), C_("calendar:week:digits", "%d"), week);
2583       pango_layout_set_text (layout, buffer, -1);
2584       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2585
2586       y_loc = calendar_top_y_for_row (calendar, row) + (day_height - logical_rect.height) / 2;
2587
2588       x_loc = (priv->week_width
2589                - logical_rect.width
2590                - calendar_xsep - focus_padding - focus_width);
2591
2592       cairo_move_to (cr, x_loc, y_loc);
2593       pango_cairo_show_layout (cr, layout);
2594     }
2595   
2596   g_object_unref (layout);
2597
2598   cairo_restore (cr);
2599 }
2600
2601 static void
2602 calendar_invalidate_day_num (GtkCalendar *calendar,
2603                              gint         day)
2604 {
2605   GtkCalendarPrivate *priv = calendar->priv;
2606   gint r, c, row, col;
2607   
2608   row = -1;
2609   col = -1;
2610   for (r = 0; r < 6; r++)
2611     for (c = 0; c < 7; c++)
2612       if (priv->day_month[r][c] == MONTH_CURRENT &&
2613           priv->day[r][c] == day)
2614         {
2615           row = r;
2616           col = c;
2617         }
2618   
2619   g_return_if_fail (row != -1);
2620   g_return_if_fail (col != -1);
2621   
2622   calendar_invalidate_day (calendar, row, col);
2623 }
2624
2625 static void
2626 calendar_invalidate_day (GtkCalendar *calendar,
2627                          gint         row,
2628                          gint         col)
2629 {
2630   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2631
2632   if (priv->main_win)
2633     {
2634       GdkRectangle day_rect;
2635       
2636       calendar_day_rectangle (calendar, row, col, &day_rect);
2637       gdk_window_invalidate_rect (priv->main_win, &day_rect, FALSE);
2638     }
2639 }
2640
2641 static gboolean
2642 is_color_attribute (PangoAttribute *attribute,
2643                     gpointer        data)
2644 {
2645   return (attribute->klass->type == PANGO_ATTR_FOREGROUND ||
2646           attribute->klass->type == PANGO_ATTR_BACKGROUND);
2647 }
2648
2649 static void
2650 calendar_paint_day (GtkCalendar *calendar,
2651                     cairo_t     *cr,
2652                     gint         row,
2653                     gint         col)
2654 {
2655   GtkWidget *widget = GTK_WIDGET (calendar);
2656   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2657   GtkStyle *style;
2658   GdkColor *text_color;
2659   gchar *detail;
2660   gchar buffer[32];
2661   gint day;
2662   gint x_loc, y_loc;
2663   GdkRectangle day_rect;
2664
2665   PangoLayout *layout;
2666   PangoRectangle logical_rect;
2667   gboolean overflow = FALSE;
2668   gboolean show_details;
2669
2670   g_return_if_fail (row < 6);
2671   g_return_if_fail (col < 7);
2672
2673   style = gtk_widget_get_style (widget);
2674
2675   day = priv->day[row][col];
2676   show_details = (priv->display_flags & GTK_CALENDAR_SHOW_DETAILS);
2677
2678   calendar_day_rectangle (calendar, row, col, &day_rect);
2679   
2680   if (priv->day_month[row][col] == MONTH_PREV)
2681     {
2682       text_color = PREV_MONTH_COLOR (widget);
2683     } 
2684   else if (priv->day_month[row][col] == MONTH_NEXT)
2685     {
2686       text_color =  NEXT_MONTH_COLOR (widget);
2687     } 
2688   else 
2689     {
2690 #if 0      
2691       if (priv->highlight_row == row && priv->highlight_col == col)
2692         {
2693           cairo_set_source_color (cr, HIGHLIGHT_BG_COLOR (widget));
2694           gdk_cairo_rectangle (cr, &day_rect);
2695           cairo_fill (cr);
2696         }
2697 #endif     
2698       if (priv->selected_day == day)
2699         {
2700           gdk_cairo_set_source_color (cr, SELECTED_BG_COLOR (widget));
2701           gdk_cairo_rectangle (cr, &day_rect);
2702           cairo_fill (cr);
2703         }
2704       if (priv->selected_day == day)
2705         text_color = SELECTED_FG_COLOR (widget);
2706       else if (priv->marked_date[day-1])
2707         text_color = MARKED_COLOR (widget);
2708       else
2709         text_color = NORMAL_DAY_COLOR (widget);
2710     }
2711
2712   /* Translators: this defines whether the day numbers should use
2713    * localized digits or the ones used in English (0123...).
2714    *
2715    * Translate to "%Id" if you want to use localized digits, or
2716    * translate to "%d" otherwise.
2717    *
2718    * Note that translating this doesn't guarantee that you get localized
2719    * digits. That needs support from your system and locale definition
2720    * too.
2721    */
2722   g_snprintf (buffer, sizeof (buffer), C_("calendar:day:digits", "%d"), day);
2723
2724   /* Get extra information to show, if any: */
2725
2726   detail = gtk_calendar_get_detail (calendar, row, col);
2727
2728   layout = gtk_widget_create_pango_layout (widget, buffer);
2729   pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
2730   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2731   
2732   x_loc = day_rect.x + (day_rect.width - logical_rect.width) / 2;
2733   y_loc = day_rect.y;
2734
2735   gdk_cairo_set_source_color (cr, text_color);
2736   cairo_move_to (cr, x_loc, y_loc);
2737   pango_cairo_show_layout (cr, layout);
2738
2739   if (priv->day_month[row][col] == MONTH_CURRENT &&
2740      (priv->marked_date[day-1] || (detail && !show_details)))
2741     {
2742       cairo_move_to (cr, x_loc - 1, y_loc);
2743       pango_cairo_show_layout (cr, layout);
2744     }
2745
2746   y_loc += priv->max_day_char_descent;
2747
2748   if (priv->detail_func && show_details)
2749     {
2750       cairo_save (cr);
2751
2752       if (priv->selected_day == day)
2753         gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_ACTIVE]);
2754       else if (priv->day_month[row][col] == MONTH_CURRENT)
2755         gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_ACTIVE]);
2756       else
2757         gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_INSENSITIVE]);
2758
2759       cairo_set_line_width (cr, 1);
2760       cairo_move_to (cr, day_rect.x + 2, y_loc + 0.5);
2761       cairo_line_to (cr, day_rect.x + day_rect.width - 2, y_loc + 0.5);
2762       cairo_stroke (cr);
2763
2764       cairo_restore (cr);
2765
2766       y_loc += 2;
2767     }
2768
2769   if (detail && show_details)
2770     {
2771       gchar *markup = g_strconcat ("<small>", detail, "</small>", NULL);
2772       pango_layout_set_markup (layout, markup, -1);
2773       g_free (markup);
2774
2775       if (day == priv->selected_day)
2776         {
2777           /* Stripping colors as they conflict with selection marking. */
2778
2779           PangoAttrList *attrs = pango_layout_get_attributes (layout);
2780           PangoAttrList *colors = NULL;
2781
2782           if (attrs)
2783             colors = pango_attr_list_filter (attrs, is_color_attribute, NULL);
2784           if (colors)
2785             pango_attr_list_unref (colors);
2786         }
2787
2788       pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
2789       pango_layout_set_width (layout, PANGO_SCALE * day_rect.width);
2790
2791       if (priv->detail_height_rows)
2792         {
2793           gint dy = day_rect.height - (y_loc - day_rect.y);
2794           pango_layout_set_height (layout, PANGO_SCALE * dy);
2795           pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
2796         }
2797
2798       cairo_move_to (cr, day_rect.x, y_loc);
2799       pango_cairo_show_layout (cr, layout);
2800     }
2801
2802   if (gtk_widget_has_focus (widget)
2803       && priv->focus_row == row && priv->focus_col == col)
2804     {
2805       GtkStateType state;
2806
2807       if (priv->selected_day == day)
2808         state = gtk_widget_has_focus (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE;
2809       else
2810         state = GTK_STATE_NORMAL;
2811
2812       gtk_paint_focus (style, cr,
2813                        state, widget, "calendar-day",
2814                        day_rect.x,     day_rect.y, 
2815                        day_rect.width, day_rect.height);
2816     }
2817
2818   if (overflow)
2819     priv->detail_overflow[row] |= (1 << col);
2820   else
2821     priv->detail_overflow[row] &= ~(1 << col);
2822
2823   g_object_unref (layout);
2824   g_free (detail);
2825 }
2826
2827 static void
2828 calendar_paint_main (GtkCalendar *calendar,
2829                      cairo_t     *cr)
2830 {
2831   gint row, col;
2832   
2833   cairo_save (cr);
2834   gtk_cairo_transform_to_window (cr,
2835                                  GTK_WIDGET (calendar),
2836                                  calendar->priv->main_win);
2837
2838   for (col = 0; col < 7; col++)
2839     for (row = 0; row < 6; row++)
2840       calendar_paint_day (calendar, cr, row, col);
2841
2842   cairo_restore (cr);
2843 }
2844
2845 static void
2846 calendar_invalidate_arrow (GtkCalendar *calendar,
2847                            guint        arrow)
2848 {
2849   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2850   GdkWindow *window;
2851   
2852   window = priv->arrow_win[arrow];
2853   if (window)
2854     gdk_window_invalidate_rect (window, NULL, FALSE);
2855 }
2856
2857 static void
2858 calendar_paint_arrow (GtkCalendar *calendar,
2859                       cairo_t     *cr,
2860                       guint        arrow)
2861 {
2862   GtkWidget *widget = GTK_WIDGET (calendar);
2863   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2864   GdkWindow *window;
2865   
2866   cairo_save (cr);
2867   gtk_cairo_transform_to_window (cr, widget, priv->arrow_win[arrow]);
2868
2869   window = priv->arrow_win[arrow];
2870   if (window)
2871     {
2872       GtkStyle *style;
2873       gint state;
2874
2875       style = gtk_widget_get_style (widget);
2876       state = priv->arrow_state[arrow];
2877
2878       gdk_cairo_set_source_color (cr, &style->bg[state]);
2879       cairo_paint (cr);
2880       
2881       if (arrow == ARROW_MONTH_LEFT || arrow == ARROW_YEAR_LEFT)
2882         gtk_paint_arrow (style, cr, state,
2883                          GTK_SHADOW_OUT, widget, "calendar",
2884                          GTK_ARROW_LEFT, TRUE, 
2885                          gdk_window_get_width (window) / 2 - 3,
2886                          gdk_window_get_height (window) / 2 - 4,
2887                          8, 8);
2888       else 
2889         gtk_paint_arrow (style, cr, state,
2890                          GTK_SHADOW_OUT, widget, "calendar",
2891                          GTK_ARROW_RIGHT, TRUE, 
2892                          gdk_window_get_width (window) / 2 - 4,
2893                          gdk_window_get_height (window) / 2 - 4,
2894                          8, 8);
2895     }
2896
2897   cairo_restore (cr);
2898 }
2899
2900 static gboolean
2901 gtk_calendar_draw (GtkWidget *widget,
2902                    cairo_t   *cr)
2903 {
2904   GtkCalendar *calendar = GTK_CALENDAR (widget);
2905   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2906   int i;
2907
2908   if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget)))
2909     {
2910       gtk_paint_shadow (gtk_widget_get_style (widget), cr,
2911                         gtk_widget_get_state (widget), GTK_SHADOW_IN,
2912                         widget, "calendar",
2913                         0, 0,
2914                         gtk_widget_get_allocated_width (widget),
2915                         gtk_widget_get_allocated_height (widget));
2916     }
2917
2918   if (gtk_cairo_should_draw_window (cr, priv->main_win))
2919     calendar_paint_main (calendar, cr);
2920   
2921   if (priv->header_win &&
2922       gtk_cairo_should_draw_window (cr, priv->header_win))
2923     calendar_paint_header (calendar, cr);
2924
2925   for (i = 0; i < 4; i++)
2926     if (priv->arrow_win[i] &&
2927         gtk_cairo_should_draw_window (cr, priv->arrow_win[i]))
2928       calendar_paint_arrow (calendar, cr, i);
2929   
2930   if (priv->day_name_win &&
2931       gtk_cairo_should_draw_window (cr, priv->day_name_win))
2932     calendar_paint_day_names (calendar, cr);
2933   
2934   if (priv->week_win &&
2935       gtk_cairo_should_draw_window (cr, priv->week_win))
2936     calendar_paint_week_numbers (calendar, cr);
2937
2938   return FALSE;
2939 }
2940
2941
2942 /****************************************
2943  *           Mouse handling             *
2944  ****************************************/
2945
2946 static void
2947 calendar_arrow_action (GtkCalendar *calendar,
2948                        guint        arrow)
2949 {
2950   switch (arrow)
2951     {
2952     case ARROW_YEAR_LEFT:
2953       calendar_set_year_prev (calendar);
2954       break;
2955     case ARROW_YEAR_RIGHT:
2956       calendar_set_year_next (calendar);
2957       break;
2958     case ARROW_MONTH_LEFT:
2959       calendar_set_month_prev (calendar);
2960       break;
2961     case ARROW_MONTH_RIGHT:
2962       calendar_set_month_next (calendar);
2963       break;
2964     default:;
2965       /* do nothing */
2966     }
2967 }
2968
2969 static gboolean
2970 calendar_timer (gpointer data)
2971 {
2972   GtkCalendar *calendar = data;
2973   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2974   gboolean retval = FALSE;
2975   
2976   if (priv->timer)
2977     {
2978       calendar_arrow_action (calendar, priv->click_child);
2979
2980       if (priv->need_timer)
2981         {
2982           GtkSettings *settings;
2983           guint        timeout;
2984
2985           settings = gtk_widget_get_settings (GTK_WIDGET (calendar));
2986           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
2987
2988           priv->need_timer = FALSE;
2989           priv->timer = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE,
2990                                             timeout * SCROLL_DELAY_FACTOR,
2991                                             (GSourceFunc) calendar_timer,
2992                                             (gpointer) calendar, NULL);
2993         }
2994       else 
2995         retval = TRUE;
2996     }
2997
2998   return retval;
2999 }
3000
3001 static void
3002 calendar_start_spinning (GtkCalendar *calendar,
3003                          gint         click_child)
3004 {
3005   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3006
3007   priv->click_child = click_child;
3008   
3009   if (!priv->timer)
3010     {
3011       GtkSettings *settings;
3012       guint        timeout;
3013
3014       settings = gtk_widget_get_settings (GTK_WIDGET (calendar));
3015       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
3016
3017       priv->need_timer = TRUE;
3018       priv->timer = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE,
3019                                         timeout,
3020                                         (GSourceFunc) calendar_timer,
3021                                         (gpointer) calendar, NULL);
3022     }
3023 }
3024
3025 static void
3026 calendar_stop_spinning (GtkCalendar *calendar)
3027 {
3028   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3029
3030   if (priv->timer)
3031     {
3032       g_source_remove (priv->timer);
3033       priv->timer = 0;
3034       priv->need_timer = FALSE;
3035     }
3036 }
3037
3038 static void
3039 calendar_main_button_press (GtkCalendar    *calendar,
3040                             GdkEventButton *event)
3041 {
3042   GtkWidget *widget = GTK_WIDGET (calendar);
3043   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3044   gint x, y;
3045   gint row, col;
3046   gint day_month;
3047   gint day;
3048   
3049   x = (gint) (event->x);
3050   y = (gint) (event->y);
3051   
3052   row = calendar_row_from_y (calendar, y);
3053   col = calendar_column_from_x (calendar, x);
3054
3055   /* If row or column isn't found, just return. */
3056   if (row == -1 || col == -1)
3057     return;
3058   
3059   day_month = priv->day_month[row][col];
3060
3061   if (event->type == GDK_BUTTON_PRESS)
3062     {
3063       day = priv->day[row][col];
3064       
3065       if (day_month == MONTH_PREV)
3066         calendar_set_month_prev (calendar);
3067       else if (day_month == MONTH_NEXT)
3068         calendar_set_month_next (calendar);
3069       
3070       if (!gtk_widget_has_focus (widget))
3071         gtk_widget_grab_focus (widget);
3072           
3073       if (event->button == 1) 
3074         {
3075           priv->in_drag = 1;
3076           priv->drag_start_x = x;
3077           priv->drag_start_y = y;
3078         }
3079
3080       calendar_select_and_focus_day (calendar, day);
3081     }
3082   else if (event->type == GDK_2BUTTON_PRESS)
3083     {
3084       priv->in_drag = 0;
3085       if (day_month == MONTH_CURRENT)
3086         g_signal_emit (calendar,
3087                        gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL],
3088                        0);
3089     }
3090 }
3091
3092 static gboolean
3093 gtk_calendar_button_press (GtkWidget      *widget,
3094                            GdkEventButton *event)
3095 {
3096   GtkCalendar *calendar = GTK_CALENDAR (widget);
3097   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3098   gint arrow = -1;
3099   
3100   if (event->window == priv->main_win)
3101     calendar_main_button_press (calendar, event);
3102
3103   if (!gtk_widget_has_focus (widget))
3104     gtk_widget_grab_focus (widget);
3105
3106   for (arrow = ARROW_YEAR_LEFT; arrow <= ARROW_MONTH_RIGHT; arrow++)
3107     {
3108       if (event->window == priv->arrow_win[arrow])
3109         {
3110           
3111           /* only call the action on single click, not double */
3112           if (event->type == GDK_BUTTON_PRESS)
3113             {
3114               if (event->button == 1)
3115                 calendar_start_spinning (calendar, arrow);
3116
3117               calendar_arrow_action (calendar, arrow);        
3118             }
3119
3120           return TRUE;
3121         }
3122     }
3123
3124   return FALSE;
3125 }
3126
3127 static gboolean
3128 gtk_calendar_button_release (GtkWidget    *widget,
3129                              GdkEventButton *event)
3130 {
3131   GtkCalendar *calendar = GTK_CALENDAR (widget);
3132   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3133
3134   if (event->button == 1) 
3135     {
3136       calendar_stop_spinning (calendar);
3137
3138       if (priv->in_drag)
3139         priv->in_drag = 0;
3140     }
3141
3142   return TRUE;
3143 }
3144
3145 static gboolean
3146 gtk_calendar_motion_notify (GtkWidget      *widget,
3147                             GdkEventMotion *event)
3148 {
3149   GtkCalendar *calendar = GTK_CALENDAR (widget);
3150   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3151   gint event_x, event_y;
3152   gint row, col;
3153   gint old_row, old_col;
3154   
3155   event_x = (gint) (event->x);
3156   event_y = (gint) (event->y);
3157   
3158   if (event->window == priv->main_win)
3159     {
3160       
3161       if (priv->in_drag) 
3162         {
3163           if (gtk_drag_check_threshold (widget,
3164                                         priv->drag_start_x, priv->drag_start_y,
3165                                         event->x, event->y))
3166             {
3167               GdkDragContext *context;
3168               GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
3169               gtk_target_list_add_text_targets (target_list, 0);
3170               context = gtk_drag_begin (widget, target_list, GDK_ACTION_COPY,
3171                                         1, (GdkEvent *)event);
3172
3173           
3174               priv->in_drag = 0;
3175               
3176               gtk_target_list_unref (target_list);
3177               gtk_drag_set_icon_default (context);
3178             }
3179         }
3180       else 
3181         {
3182           row = calendar_row_from_y (calendar, event_y);
3183           col = calendar_column_from_x (calendar, event_x);
3184           
3185           if (row != priv->highlight_row || priv->highlight_col != col)
3186             {
3187               old_row = priv->highlight_row;
3188               old_col = priv->highlight_col;
3189               if (old_row > -1 && old_col > -1)
3190                 {
3191                   priv->highlight_row = -1;
3192                   priv->highlight_col = -1;
3193                   calendar_invalidate_day (calendar, old_row, old_col);
3194                 }
3195               
3196               priv->highlight_row = row;
3197               priv->highlight_col = col;
3198               
3199               if (row > -1 && col > -1)
3200                 calendar_invalidate_day (calendar, row, col);
3201             }
3202         }
3203     }
3204   return TRUE;
3205 }
3206
3207 static gboolean
3208 gtk_calendar_enter_notify (GtkWidget        *widget,
3209                            GdkEventCrossing *event)
3210 {
3211   GtkCalendar *calendar = GTK_CALENDAR (widget);
3212   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3213   
3214   if (event->window == priv->arrow_win[ARROW_MONTH_LEFT])
3215     {
3216       priv->arrow_state[ARROW_MONTH_LEFT] = GTK_STATE_PRELIGHT;
3217       calendar_invalidate_arrow (calendar, ARROW_MONTH_LEFT);
3218     }
3219   
3220   if (event->window == priv->arrow_win[ARROW_MONTH_RIGHT])
3221     {
3222       priv->arrow_state[ARROW_MONTH_RIGHT] = GTK_STATE_PRELIGHT;
3223       calendar_invalidate_arrow (calendar, ARROW_MONTH_RIGHT);
3224     }
3225   
3226   if (event->window == priv->arrow_win[ARROW_YEAR_LEFT])
3227     {
3228       priv->arrow_state[ARROW_YEAR_LEFT] = GTK_STATE_PRELIGHT;
3229       calendar_invalidate_arrow (calendar, ARROW_YEAR_LEFT);
3230     }
3231   
3232   if (event->window == priv->arrow_win[ARROW_YEAR_RIGHT])
3233     {
3234       priv->arrow_state[ARROW_YEAR_RIGHT] = GTK_STATE_PRELIGHT;
3235       calendar_invalidate_arrow (calendar, ARROW_YEAR_RIGHT);
3236     }
3237   
3238   return TRUE;
3239 }
3240
3241 static gboolean
3242 gtk_calendar_leave_notify (GtkWidget        *widget,
3243                            GdkEventCrossing *event)
3244 {
3245   GtkCalendar *calendar = GTK_CALENDAR (widget);
3246   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3247   gint row;
3248   gint col;
3249   
3250   if (event->window == priv->main_win)
3251     {
3252       row = priv->highlight_row;
3253       col = priv->highlight_col;
3254       priv->highlight_row = -1;
3255       priv->highlight_col = -1;
3256       if (row > -1 && col > -1)
3257         calendar_invalidate_day (calendar, row, col);
3258     }
3259   
3260   if (event->window == priv->arrow_win[ARROW_MONTH_LEFT])
3261     {
3262       priv->arrow_state[ARROW_MONTH_LEFT] = GTK_STATE_NORMAL;
3263       calendar_invalidate_arrow (calendar, ARROW_MONTH_LEFT);
3264     }
3265   
3266   if (event->window == priv->arrow_win[ARROW_MONTH_RIGHT])
3267     {
3268       priv->arrow_state[ARROW_MONTH_RIGHT] = GTK_STATE_NORMAL;
3269       calendar_invalidate_arrow (calendar, ARROW_MONTH_RIGHT);
3270     }
3271   
3272   if (event->window == priv->arrow_win[ARROW_YEAR_LEFT])
3273     {
3274       priv->arrow_state[ARROW_YEAR_LEFT] = GTK_STATE_NORMAL;
3275       calendar_invalidate_arrow (calendar, ARROW_YEAR_LEFT);
3276     }
3277   
3278   if (event->window == priv->arrow_win[ARROW_YEAR_RIGHT])
3279     {
3280       priv->arrow_state[ARROW_YEAR_RIGHT] = GTK_STATE_NORMAL;
3281       calendar_invalidate_arrow (calendar, ARROW_YEAR_RIGHT);
3282     }
3283   
3284   return TRUE;
3285 }
3286
3287 static gboolean
3288 gtk_calendar_scroll (GtkWidget      *widget,
3289                      GdkEventScroll *event)
3290 {
3291   GtkCalendar *calendar = GTK_CALENDAR (widget);
3292
3293   if (event->direction == GDK_SCROLL_UP) 
3294     {
3295       if (!gtk_widget_has_focus (widget))
3296         gtk_widget_grab_focus (widget);
3297       calendar_set_month_prev (calendar);
3298     }
3299   else if (event->direction == GDK_SCROLL_DOWN) 
3300     {
3301       if (!gtk_widget_has_focus (widget))
3302         gtk_widget_grab_focus (widget);
3303       calendar_set_month_next (calendar);
3304     }
3305   else
3306     return FALSE;
3307
3308   return TRUE;
3309 }
3310
3311 \f
3312 /****************************************
3313  *             Key handling              *
3314  ****************************************/
3315
3316 static void 
3317 move_focus (GtkCalendar *calendar, 
3318             gint         direction)
3319 {
3320   GtkCalendarPrivate *priv = calendar->priv;
3321   GtkTextDirection text_dir = gtk_widget_get_direction (GTK_WIDGET (calendar));
3322  
3323   if ((text_dir == GTK_TEXT_DIR_LTR && direction == -1) ||
3324       (text_dir == GTK_TEXT_DIR_RTL && direction == 1)) 
3325     {
3326       if (priv->focus_col > 0)
3327           priv->focus_col--;
3328       else if (priv->focus_row > 0)
3329         {
3330           priv->focus_col = 6;
3331           priv->focus_row--;
3332         }
3333
3334       if (priv->focus_col < 0)
3335         priv->focus_col = 6;
3336       if (priv->focus_row < 0)
3337         priv->focus_row = 5;
3338     }
3339   else 
3340     {
3341       if (priv->focus_col < 6)
3342         priv->focus_col++;
3343       else if (priv->focus_row < 5)
3344         {
3345           priv->focus_col = 0;
3346           priv->focus_row++;
3347         }
3348
3349       if (priv->focus_col < 0)
3350         priv->focus_col = 0;
3351       if (priv->focus_row < 0)
3352         priv->focus_row = 0;
3353     }
3354 }
3355
3356 static gboolean
3357 gtk_calendar_key_press (GtkWidget   *widget,
3358                         GdkEventKey *event)
3359 {
3360   GtkCalendar *calendar = GTK_CALENDAR (widget);
3361   GtkCalendarPrivate *priv = calendar->priv;
3362   gint return_val;
3363   gint old_focus_row;
3364   gint old_focus_col;
3365   gint row, col, day;
3366
3367   return_val = FALSE;
3368
3369   old_focus_row = priv->focus_row;
3370   old_focus_col = priv->focus_col;
3371
3372   switch (event->keyval)
3373     {
3374     case GDK_KEY_KP_Left:
3375     case GDK_KEY_Left:
3376       return_val = TRUE;
3377       if (event->state & GDK_CONTROL_MASK)
3378         calendar_set_month_prev (calendar);
3379       else
3380         {
3381           move_focus (calendar, -1);
3382           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3383           calendar_invalidate_day (calendar, priv->focus_row,
3384                                    priv->focus_col);
3385         }
3386       break;
3387     case GDK_KEY_KP_Right:
3388     case GDK_KEY_Right:
3389       return_val = TRUE;
3390       if (event->state & GDK_CONTROL_MASK)
3391         calendar_set_month_next (calendar);
3392       else
3393         {
3394           move_focus (calendar, 1);
3395           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3396           calendar_invalidate_day (calendar, priv->focus_row,
3397                                    priv->focus_col);
3398         }
3399       break;
3400     case GDK_KEY_KP_Up:
3401     case GDK_KEY_Up:
3402       return_val = TRUE;
3403       if (event->state & GDK_CONTROL_MASK)
3404         calendar_set_year_prev (calendar);
3405       else
3406         {
3407           if (priv->focus_row > 0)
3408             priv->focus_row--;
3409           if (priv->focus_row < 0)
3410             priv->focus_row = 5;
3411           if (priv->focus_col < 0)
3412             priv->focus_col = 6;
3413           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3414           calendar_invalidate_day (calendar, priv->focus_row,
3415                                    priv->focus_col);
3416         }
3417       break;
3418     case GDK_KEY_KP_Down:
3419     case GDK_KEY_Down:
3420       return_val = TRUE;
3421       if (event->state & GDK_CONTROL_MASK)
3422         calendar_set_year_next (calendar);
3423       else
3424         {
3425           if (priv->focus_row < 5)
3426             priv->focus_row++;
3427           if (priv->focus_col < 0)
3428             priv->focus_col = 0;
3429           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3430           calendar_invalidate_day (calendar, priv->focus_row,
3431                                    priv->focus_col);
3432         }
3433       break;
3434     case GDK_KEY_KP_Space:
3435     case GDK_KEY_space:
3436       row = priv->focus_row;
3437       col = priv->focus_col;
3438       
3439       if (row > -1 && col > -1)
3440         {
3441           return_val = TRUE;
3442
3443           day = priv->day[row][col];
3444           if (priv->day_month[row][col] == MONTH_PREV)
3445             calendar_set_month_prev (calendar);
3446           else if (priv->day_month[row][col] == MONTH_NEXT)
3447             calendar_set_month_next (calendar);
3448
3449           calendar_select_and_focus_day (calendar, day);
3450         }
3451     }   
3452   
3453   return return_val;
3454 }
3455
3456 \f
3457 /****************************************
3458  *           Misc widget methods        *
3459  ****************************************/
3460
3461 static void
3462 calendar_set_background (GtkWidget *widget)
3463 {
3464   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3465   GdkWindow *window;
3466   gint i;
3467   
3468   if (gtk_widget_get_realized (widget))
3469     {
3470       for (i = 0; i < 4; i++)
3471         {
3472           if (priv->arrow_win[i])
3473             gdk_window_set_background (priv->arrow_win[i], 
3474                                        HEADER_BG_COLOR (widget));
3475         }
3476       if (priv->header_win)
3477         gdk_window_set_background (priv->header_win, 
3478                                    HEADER_BG_COLOR (widget));
3479       if (priv->day_name_win)
3480         gdk_window_set_background (priv->day_name_win, 
3481                                    BACKGROUND_COLOR (widget));
3482       if (priv->week_win)
3483         gdk_window_set_background (priv->week_win,
3484                                    BACKGROUND_COLOR (widget));
3485       if (priv->main_win)
3486         gdk_window_set_background (priv->main_win,
3487                                    BACKGROUND_COLOR (widget));
3488
3489       window = gtk_widget_get_window (widget);
3490       if (window)
3491         gdk_window_set_background (window,
3492                                    BACKGROUND_COLOR (widget)); 
3493     }
3494 }
3495
3496 static void
3497 gtk_calendar_style_set (GtkWidget *widget,
3498                         GtkStyle  *previous_style)
3499 {
3500   if (previous_style && gtk_widget_get_realized (widget))
3501     calendar_set_background (widget);
3502 }
3503
3504 static void
3505 gtk_calendar_state_changed (GtkWidget      *widget,
3506                             GtkStateType    previous_state)
3507 {
3508   GtkCalendar *calendar = GTK_CALENDAR (widget);
3509   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3510   int i;
3511   
3512   if (!gtk_widget_is_sensitive (widget))
3513     {
3514       priv->in_drag = 0;
3515       calendar_stop_spinning (calendar);    
3516     }
3517
3518   for (i = 0; i < 4; i++)
3519     if (gtk_widget_is_sensitive (widget))
3520       priv->arrow_state[i] = GTK_STATE_NORMAL;
3521     else 
3522       priv->arrow_state[i] = GTK_STATE_INSENSITIVE;
3523   
3524   calendar_set_background (widget);
3525 }
3526
3527 static void
3528 gtk_calendar_grab_notify (GtkWidget *widget,
3529                           gboolean   was_grabbed)
3530 {
3531   if (!was_grabbed)
3532     calendar_stop_spinning (GTK_CALENDAR (widget));
3533 }
3534
3535 static gboolean
3536 gtk_calendar_focus_out (GtkWidget     *widget,
3537                         GdkEventFocus *event)
3538 {
3539   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3540   GtkCalendar *calendar = GTK_CALENDAR (widget);
3541
3542   calendar_queue_refresh (calendar);
3543   calendar_stop_spinning (calendar);
3544   
3545   priv->in_drag = 0; 
3546
3547   return FALSE;
3548 }
3549
3550 \f
3551 /****************************************
3552  *          Drag and Drop               *
3553  ****************************************/
3554
3555 static void
3556 gtk_calendar_drag_data_get (GtkWidget        *widget,
3557                             GdkDragContext   *context,
3558                             GtkSelectionData *selection_data,
3559                             guint             info,
3560                             guint             time)
3561 {
3562   GtkCalendar *calendar = GTK_CALENDAR (widget);
3563   GtkCalendarPrivate *priv = calendar->priv;
3564   GDate *date;
3565   gchar str[128];
3566   gsize len;
3567
3568   date = g_date_new_dmy (priv->selected_day, priv->month + 1, priv->year);
3569   len = g_date_strftime (str, 127, "%x", date);
3570   gtk_selection_data_set_text (selection_data, str, len);
3571   
3572   g_free (date);
3573 }
3574
3575 /* Get/set whether drag_motion requested the drag data and
3576  * drag_data_received should thus not actually insert the data,
3577  * since the data doesn't result from a drop.
3578  */
3579 static void
3580 set_status_pending (GdkDragContext *context,
3581                     GdkDragAction   suggested_action)
3582 {
3583   g_object_set_data (G_OBJECT (context),
3584                      I_("gtk-calendar-status-pending"),
3585                      GINT_TO_POINTER (suggested_action));
3586 }
3587
3588 static GdkDragAction
3589 get_status_pending (GdkDragContext *context)
3590 {
3591   return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (context),
3592                                              "gtk-calendar-status-pending"));
3593 }
3594
3595 static void
3596 gtk_calendar_drag_leave (GtkWidget      *widget,
3597                          GdkDragContext *context,
3598                          guint           time)
3599 {
3600   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3601
3602   priv->drag_highlight = 0;
3603   gtk_drag_unhighlight (widget);
3604   
3605 }
3606
3607 static gboolean
3608 gtk_calendar_drag_motion (GtkWidget      *widget,
3609                           GdkDragContext *context,
3610                           gint            x,
3611                           gint            y,
3612                           guint           time)
3613 {
3614   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3615   GdkAtom target;
3616   
3617   if (!priv->drag_highlight) 
3618     {
3619       priv->drag_highlight = 1;
3620       gtk_drag_highlight (widget);
3621     }
3622   
3623   target = gtk_drag_dest_find_target (widget, context, NULL);
3624   if (target == GDK_NONE || context->suggested_action == 0)
3625     gdk_drag_status (context, 0, time);
3626   else
3627     {
3628       set_status_pending (context, context->suggested_action);
3629       gtk_drag_get_data (widget, context, target, time);
3630     }
3631   
3632   return TRUE;
3633 }
3634
3635 static gboolean
3636 gtk_calendar_drag_drop (GtkWidget      *widget,
3637                         GdkDragContext *context,
3638                         gint            x,
3639                         gint            y,
3640                         guint           time)
3641 {
3642   GdkAtom target;
3643
3644   target = gtk_drag_dest_find_target (widget, context, NULL);  
3645   if (target != GDK_NONE)
3646     {
3647       gtk_drag_get_data (widget, context, 
3648                          target, 
3649                          time);
3650       return TRUE;
3651     }
3652
3653   return FALSE;
3654 }
3655
3656 static void
3657 gtk_calendar_drag_data_received (GtkWidget        *widget,
3658                                  GdkDragContext   *context,
3659                                  gint              x,
3660                                  gint              y,
3661                                  GtkSelectionData *selection_data,
3662                                  guint             info,
3663                                  guint             time)
3664 {
3665   GtkCalendar *calendar = GTK_CALENDAR (widget);
3666   GtkCalendarPrivate *priv = calendar->priv;
3667   guint day, month, year;
3668   gchar *str;
3669   GDate *date;
3670   GdkDragAction suggested_action;
3671
3672   suggested_action = get_status_pending (context);
3673
3674   if (suggested_action) 
3675     {
3676       set_status_pending (context, 0);
3677      
3678       /* We are getting this data due to a request in drag_motion,
3679        * rather than due to a request in drag_drop, so we are just
3680        * supposed to call drag_status, not actually paste in the
3681        * data.
3682        */
3683       str = (gchar*) gtk_selection_data_get_text (selection_data);
3684
3685       if (str) 
3686         {
3687           date = g_date_new ();
3688           g_date_set_parse (date, str);
3689           if (!g_date_valid (date)) 
3690               suggested_action = 0;
3691           g_date_free (date);
3692           g_free (str);
3693         }
3694       else
3695         suggested_action = 0;
3696
3697       gdk_drag_status (context, suggested_action, time);
3698
3699       return;
3700     }
3701
3702   date = g_date_new ();
3703   str = (gchar*) gtk_selection_data_get_text (selection_data);
3704   if (str) 
3705     {
3706       g_date_set_parse (date, str);
3707       g_free (str);
3708     }
3709   
3710   if (!g_date_valid (date)) 
3711     {
3712       g_warning ("Received invalid date data\n");
3713       g_date_free (date);       
3714       gtk_drag_finish (context, FALSE, FALSE, time);
3715       return;
3716     }
3717
3718   day = g_date_get_day (date);
3719   month = g_date_get_month (date);
3720   year = g_date_get_year (date);
3721   g_date_free (date);   
3722
3723   gtk_drag_finish (context, TRUE, FALSE, time);
3724
3725   
3726   g_object_freeze_notify (G_OBJECT (calendar));
3727   if (!(priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
3728       && (priv->display_flags & GTK_CALENDAR_SHOW_HEADING))
3729     gtk_calendar_select_month (calendar, month - 1, year);
3730   gtk_calendar_select_day (calendar, day);
3731   g_object_thaw_notify (G_OBJECT (calendar));  
3732 }
3733
3734 \f
3735 /****************************************
3736  *              Public API              *
3737  ****************************************/
3738
3739 /**
3740  * gtk_calendar_new:
3741  * 
3742  * Creates a new calendar, with the current date being selected. 
3743  * 
3744  * Return value: a newly #GtkCalendar widget
3745  **/
3746 GtkWidget*
3747 gtk_calendar_new (void)
3748 {
3749   return g_object_new (GTK_TYPE_CALENDAR, NULL);
3750 }
3751
3752 /**
3753  * gtk_calendar_get_display_options:
3754  * @calendar: a #GtkCalendar
3755  * 
3756  * Returns the current display options of @calendar. 
3757  * 
3758  * Return value: the display options.
3759  *
3760  * Since: 2.4
3761  **/
3762 GtkCalendarDisplayOptions 
3763 gtk_calendar_get_display_options (GtkCalendar         *calendar)
3764 {
3765   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
3766
3767   return calendar->priv->display_flags;
3768 }
3769
3770 /**
3771  * gtk_calendar_set_display_options:
3772  * @calendar: a #GtkCalendar
3773  * @flags: the display options to set
3774  * 
3775  * Sets display options (whether to display the heading and the month  
3776  * headings).
3777  *
3778  * Since: 2.4
3779  **/
3780 void
3781 gtk_calendar_set_display_options (GtkCalendar          *calendar,
3782                                   GtkCalendarDisplayOptions flags)
3783 {
3784   GtkWidget *widget = GTK_WIDGET (calendar);
3785   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3786   gint resize = 0;
3787   gint i;
3788   GtkCalendarDisplayOptions old_flags;
3789   
3790   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3791   
3792   old_flags = priv->display_flags;
3793   
3794   if (gtk_widget_get_realized (widget))
3795     {
3796       if ((flags ^ priv->display_flags) & GTK_CALENDAR_NO_MONTH_CHANGE)
3797         {
3798           resize ++;
3799           if (! (flags & GTK_CALENDAR_NO_MONTH_CHANGE)
3800               && (priv->header_win))
3801             {
3802               priv->display_flags &= ~GTK_CALENDAR_NO_MONTH_CHANGE;
3803               calendar_realize_arrows (calendar);
3804             }
3805           else
3806             {
3807               for (i = 0; i < 4; i++)
3808                 {
3809                   if (priv->arrow_win[i])
3810                     {
3811                       gdk_window_set_user_data (priv->arrow_win[i], 
3812                                                 NULL);
3813                       gdk_window_destroy (priv->arrow_win[i]);
3814                       priv->arrow_win[i] = NULL;
3815                     }
3816                 }
3817             }
3818         }
3819       
3820       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_HEADING)
3821         {
3822           resize++;
3823           
3824           if (flags & GTK_CALENDAR_SHOW_HEADING)
3825             {
3826               priv->display_flags |= GTK_CALENDAR_SHOW_HEADING;
3827               calendar_realize_header (calendar);
3828             }
3829           else
3830             {
3831               for (i = 0; i < 4; i++)
3832                 {
3833                   if (priv->arrow_win[i])
3834                     {
3835                       gdk_window_set_user_data (priv->arrow_win[i], 
3836                                                 NULL);
3837                       gdk_window_destroy (priv->arrow_win[i]);
3838                       priv->arrow_win[i] = NULL;
3839                     }
3840                 }
3841               gdk_window_set_user_data (priv->header_win, NULL);
3842               gdk_window_destroy (priv->header_win);
3843               priv->header_win = NULL;
3844             }
3845         }
3846       
3847       
3848       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DAY_NAMES)
3849         {
3850           resize++;
3851           
3852           if (flags & GTK_CALENDAR_SHOW_DAY_NAMES)
3853             {
3854               priv->display_flags |= GTK_CALENDAR_SHOW_DAY_NAMES;
3855               calendar_realize_day_names (calendar);
3856             }
3857           else
3858             {
3859               gdk_window_set_user_data (priv->day_name_win, NULL);
3860               gdk_window_destroy (priv->day_name_win);
3861               priv->day_name_win = NULL;
3862             }
3863         }
3864       
3865       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3866         {
3867           resize++;
3868           
3869           if (flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3870             {
3871               priv->display_flags |= GTK_CALENDAR_SHOW_WEEK_NUMBERS;
3872               calendar_realize_week_numbers (calendar);
3873             }
3874           else
3875             {
3876               gdk_window_set_user_data (priv->week_win, NULL);
3877               gdk_window_destroy (priv->week_win);
3878               priv->week_win = NULL;
3879             }
3880         }
3881
3882       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DETAILS)
3883         resize++;
3884
3885       priv->display_flags = flags;
3886       if (resize)
3887         gtk_widget_queue_resize (GTK_WIDGET (calendar));
3888     }
3889   else
3890     priv->display_flags = flags;
3891
3892   g_object_freeze_notify (G_OBJECT (calendar));
3893   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_HEADING)
3894     g_object_notify (G_OBJECT (calendar), "show-heading");
3895   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DAY_NAMES)
3896     g_object_notify (G_OBJECT (calendar), "show-day-names");
3897   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_NO_MONTH_CHANGE)
3898     g_object_notify (G_OBJECT (calendar), "no-month-change");
3899   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3900     g_object_notify (G_OBJECT (calendar), "show-week-numbers");
3901   g_object_thaw_notify (G_OBJECT (calendar));
3902 }
3903
3904 /**
3905  * gtk_calendar_select_month:
3906  * @calendar: a #GtkCalendar
3907  * @month: a month number between 0 and 11.
3908  * @year: the year the month is in.
3909  *
3910  * Shifts the calendar to a different month.
3911  **/
3912 void
3913 gtk_calendar_select_month (GtkCalendar *calendar,
3914                            guint        month,
3915                            guint        year)
3916 {
3917   GtkCalendarPrivate *priv;
3918
3919   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3920   g_return_if_fail (month <= 11);
3921
3922   priv = calendar->priv;
3923
3924   priv->month = month;
3925   priv->year  = year;
3926
3927   calendar_compute_days (calendar);
3928   calendar_queue_refresh (calendar);
3929
3930   g_object_freeze_notify (G_OBJECT (calendar));
3931   g_object_notify (G_OBJECT (calendar), "month");
3932   g_object_notify (G_OBJECT (calendar), "year");
3933   g_object_thaw_notify (G_OBJECT (calendar));
3934
3935   g_signal_emit (calendar,
3936                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
3937                  0);
3938 }
3939
3940 /**
3941  * gtk_calendar_select_day:
3942  * @calendar: a #GtkCalendar.
3943  * @day: the day number between 1 and 31, or 0 to unselect 
3944  *   the currently selected day.
3945  * 
3946  * Selects a day from the current month.
3947  **/
3948 void
3949 gtk_calendar_select_day (GtkCalendar *calendar,
3950                          guint        day)
3951 {
3952   GtkCalendarPrivate *priv;
3953
3954   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3955   g_return_if_fail (day <= 31);
3956
3957   priv = calendar->priv;
3958
3959   /* Deselect the old day */
3960   if (priv->selected_day > 0)
3961     {
3962       gint selected_day;
3963       
3964       selected_day = priv->selected_day;
3965       priv->selected_day = 0;
3966       if (gtk_widget_is_drawable (GTK_WIDGET (calendar)))
3967         calendar_invalidate_day_num (calendar, selected_day);
3968     }
3969   
3970   priv->selected_day = day;
3971   
3972   /* Select the new day */
3973   if (day != 0)
3974     {
3975       if (gtk_widget_is_drawable (GTK_WIDGET (calendar)))
3976         calendar_invalidate_day_num (calendar, day);
3977     }
3978   
3979   g_object_notify (G_OBJECT (calendar), "day");
3980
3981   g_signal_emit (calendar,
3982                  gtk_calendar_signals[DAY_SELECTED_SIGNAL],
3983                  0);
3984 }
3985
3986 /**
3987  * gtk_calendar_clear_marks:
3988  * @calendar: a #GtkCalendar
3989  * 
3990  * Remove all visual markers.
3991  **/
3992 void
3993 gtk_calendar_clear_marks (GtkCalendar *calendar)
3994 {
3995   GtkCalendarPrivate *priv;
3996   guint day;
3997
3998   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3999
4000   priv = calendar->priv;
4001
4002   for (day = 0; day < 31; day++)
4003     {
4004       priv->marked_date[day] = FALSE;
4005     }
4006
4007   priv->num_marked_dates = 0;
4008   calendar_queue_refresh (calendar);
4009 }
4010
4011 /**
4012  * gtk_calendar_mark_day:
4013  * @calendar: a #GtkCalendar
4014  * @day: the day number to mark between 1 and 31.
4015  *
4016  * Places a visual marker on a particular day.
4017  */
4018 void
4019 gtk_calendar_mark_day (GtkCalendar *calendar,
4020                        guint        day)
4021 {
4022   GtkCalendarPrivate *priv;
4023
4024   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4025
4026   priv = calendar->priv;
4027
4028   if (day >= 1 && day <= 31 && !priv->marked_date[day-1])
4029     {
4030       priv->marked_date[day - 1] = TRUE;
4031       priv->num_marked_dates++;
4032       calendar_invalidate_day_num (calendar, day);
4033     }
4034 }
4035
4036 /**
4037  * gtk_calendar_get_day_is_marked:
4038  * @calendar: a #GtkCalendar
4039  * @day: the day number between 1 and 31.
4040  *
4041  * Returns if the @day of the @calendar is already marked.
4042  *
4043  * Returns: whether the day is marked.
4044  *
4045  * Since: 3.0
4046  */
4047 gboolean
4048 gtk_calendar_get_day_is_marked (GtkCalendar *calendar,
4049                                 guint        day)
4050 {
4051   GtkCalendarPrivate *priv;
4052
4053   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), FALSE);
4054
4055   priv = calendar->priv;
4056
4057   if (day >= 1 && day <= 31)
4058     return priv->marked_date[day - 1];
4059
4060   return FALSE;
4061 }
4062
4063 /**
4064  * gtk_calendar_unmark_day:
4065  * @calendar: a #GtkCalendar.
4066  * @day: the day number to unmark between 1 and 31.
4067  *
4068  * Removes the visual marker from a particular day.
4069  */
4070 void
4071 gtk_calendar_unmark_day (GtkCalendar *calendar,
4072                          guint        day)
4073 {
4074   GtkCalendarPrivate *priv;
4075
4076   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4077
4078   priv = calendar->priv;
4079
4080   if (day >= 1 && day <= 31 && priv->marked_date[day-1])
4081     {
4082       priv->marked_date[day - 1] = FALSE;
4083       priv->num_marked_dates--;
4084       calendar_invalidate_day_num (calendar, day);
4085     }
4086 }
4087
4088 /**
4089  * gtk_calendar_get_date:
4090  * @calendar: a #GtkCalendar
4091  * @year: (allow-none): location to store the year number, or %NULL
4092  * @month: (allow-none): location to store the month number (between 0 and 11), or %NULL
4093  * @day: (allow-none): location to store the day number (between 1 and 31), or %NULL
4094  * 
4095  * Obtains the selected date from a #GtkCalendar.
4096  **/
4097 void
4098 gtk_calendar_get_date (GtkCalendar *calendar,
4099                        guint       *year,
4100                        guint       *month,
4101                        guint       *day)
4102 {
4103   GtkCalendarPrivate *priv;
4104
4105   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4106
4107   priv = calendar->priv;
4108
4109   if (year)
4110     *year = priv->year;
4111
4112   if (month)
4113     *month = priv->month;
4114
4115   if (day)
4116     *day = priv->selected_day;
4117 }
4118
4119 /**
4120  * gtk_calendar_set_detail_func:
4121  * @calendar: a #GtkCalendar.
4122  * @func: a function providing details for each day.
4123  * @data: data to pass to @func invokations.
4124  * @destroy: a function for releasing @data.
4125  *
4126  * Installs a function which provides Pango markup with detail information
4127  * for each day. Examples for such details are holidays or appointments. That
4128  * information is shown below each day when #GtkCalendar:show-details is set.
4129  * A tooltip containing with full detail information is provided, if the entire
4130  * text should not fit into the details area, or if #GtkCalendar:show-details
4131  * is not set.
4132  *
4133  * The size of the details area can be restricted by setting the
4134  * #GtkCalendar:detail-width-chars and #GtkCalendar:detail-height-rows
4135  * properties.
4136  *
4137  * Since: 2.14
4138  */
4139 void
4140 gtk_calendar_set_detail_func (GtkCalendar           *calendar,
4141                               GtkCalendarDetailFunc  func,
4142                               gpointer               data,
4143                               GDestroyNotify         destroy)
4144 {
4145   GtkCalendarPrivate *priv;
4146
4147   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4148
4149   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
4150
4151   if (priv->detail_func_destroy)
4152     priv->detail_func_destroy (priv->detail_func_user_data);
4153
4154   priv->detail_func = func;
4155   priv->detail_func_user_data = data;
4156   priv->detail_func_destroy = destroy;
4157
4158   gtk_widget_set_has_tooltip (GTK_WIDGET (calendar),
4159                               NULL != priv->detail_func);
4160   gtk_widget_queue_resize (GTK_WIDGET (calendar));
4161 }
4162
4163 /**
4164  * gtk_calendar_set_detail_width_chars:
4165  * @calendar: a #GtkCalendar.
4166  * @chars: detail width in characters.
4167  *
4168  * Updates the width of detail cells.
4169  * See #GtkCalendar:detail-width-chars.
4170  *
4171  * Since: 2.14
4172  */
4173 void
4174 gtk_calendar_set_detail_width_chars (GtkCalendar *calendar,
4175                                      gint         chars)
4176 {
4177   GtkCalendarPrivate *priv;
4178
4179   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4180
4181   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
4182
4183   if (chars != priv->detail_width_chars)
4184     {
4185       priv->detail_width_chars = chars;
4186       g_object_notify (G_OBJECT (calendar), "detail-width-chars");
4187       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
4188     }
4189 }
4190
4191 /**
4192  * gtk_calendar_set_detail_height_rows:
4193  * @calendar: a #GtkCalendar.
4194  * @rows: detail height in rows.
4195  *
4196  * Updates the height of detail cells.
4197  * See #GtkCalendar:detail-height-rows.
4198  *
4199  * Since: 2.14
4200  */
4201 void
4202 gtk_calendar_set_detail_height_rows (GtkCalendar *calendar,
4203                                      gint         rows)
4204 {
4205   GtkCalendarPrivate *priv;
4206
4207   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4208
4209   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
4210
4211   if (rows != priv->detail_height_rows)
4212     {
4213       priv->detail_height_rows = rows;
4214       g_object_notify (G_OBJECT (calendar), "detail-height-rows");
4215       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
4216     }
4217 }
4218
4219 /**
4220  * gtk_calendar_get_detail_width_chars:
4221  * @calendar: a #GtkCalendar.
4222  *
4223  * Queries the width of detail cells, in characters.
4224  * See #GtkCalendar:detail-width-chars.
4225  *
4226  * Since: 2.14
4227  *
4228  * Return value: The width of detail cells, in characters.
4229  */
4230 gint
4231 gtk_calendar_get_detail_width_chars (GtkCalendar *calendar)
4232 {
4233   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
4234   return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_width_chars;
4235 }
4236
4237 /**
4238  * gtk_calendar_get_detail_height_rows:
4239  * @calendar: a #GtkCalendar.
4240  *
4241  * Queries the height of detail cells, in rows.
4242  * See #GtkCalendar:detail-width-chars.
4243  *
4244  * Since: 2.14
4245  *
4246  * Return value: The height of detail cells, in rows.
4247  */
4248 gint
4249 gtk_calendar_get_detail_height_rows (GtkCalendar *calendar)
4250 {
4251   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
4252   return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_height_rows;
4253 }