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