]> Pileus Git - ~andy/gtk/blob - gtk/gtkcalendar.c
b7f3b5606a3751c3a5084c4a4f78002ed16e8859
[~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)          (& (widget)->style->fg[gtk_widget_get_state (widget)])
206 #define HEADER_BG_COLOR(widget)          (& (widget)->style->bg[gtk_widget_get_state (widget)])
207 #define SELECTED_BG_COLOR(widget)        (& (widget)->style->base[gtk_widget_has_focus (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE])
208 #define SELECTED_FG_COLOR(widget)        (& (widget)->style->text[gtk_widget_has_focus (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE])
209 #define NORMAL_DAY_COLOR(widget)         (& (widget)->style->text[gtk_widget_get_state (widget)])
210 #define PREV_MONTH_COLOR(widget)         (& (widget)->style->mid[gtk_widget_get_state (widget)])
211 #define NEXT_MONTH_COLOR(widget)         (& (widget)->style->mid[gtk_widget_get_state (widget)])
212 #define MARKED_COLOR(widget)             (& (widget)->style->text[gtk_widget_get_state (widget)])
213 #define BACKGROUND_COLOR(widget)         (& (widget)->style->base[gtk_widget_get_state (widget)])
214 #define HIGHLIGHT_BACK_COLOR(widget)     (& (widget)->style->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   gboolean year_left;
1262
1263   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) 
1264     year_left = priv->year_before;
1265   else
1266     year_left = !priv->year_before;
1267
1268   rect->y = 3;
1269   rect->width = priv->arrow_width;
1270   rect->height = priv->header_h - 7;
1271
1272   switch (arrow)
1273     {
1274     case ARROW_MONTH_LEFT:
1275       if (year_left) 
1276         rect->x = (widget->allocation.width - 2 * widget->style->xthickness
1277                    - (3 + 2*priv->arrow_width + priv->max_month_width));
1278       else
1279         rect->x = 3;
1280       break;
1281     case ARROW_MONTH_RIGHT:
1282       if (year_left) 
1283         rect->x = (widget->allocation.width - 2 * widget->style->xthickness 
1284                    - 3 - priv->arrow_width);
1285       else
1286         rect->x = (priv->arrow_width + priv->max_month_width);
1287       break;
1288     case ARROW_YEAR_LEFT:
1289       if (year_left) 
1290         rect->x = 3;
1291       else
1292         rect->x = (widget->allocation.width - 2 * widget->style->xthickness
1293                    - (3 + 2*priv->arrow_width + priv->max_year_width));
1294       break;
1295     case ARROW_YEAR_RIGHT:
1296       if (year_left) 
1297         rect->x = (priv->arrow_width + priv->max_year_width);
1298       else
1299         rect->x = (widget->allocation.width - 2 * widget->style->xthickness 
1300                    - 3 - priv->arrow_width);
1301       break;
1302     }
1303 }
1304
1305 static void
1306 calendar_day_rectangle (GtkCalendar  *calendar,
1307                         gint          row,
1308                         gint          col,
1309                         GdkRectangle *rect)
1310 {
1311   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1312
1313   rect->x = calendar_left_x_for_column (calendar, col);
1314   rect->y = calendar_top_y_for_row (calendar, row);
1315   rect->height = calendar_row_height (calendar);
1316   rect->width = priv->day_width;
1317 }
1318
1319 static void
1320 calendar_set_month_prev (GtkCalendar *calendar)
1321 {
1322   GtkCalendarPrivate *priv = calendar->priv;
1323   gint month_len;
1324   
1325   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
1326     return;
1327
1328   if (priv->month == 0)
1329     {
1330       priv->month = 11;
1331       priv->year--;
1332     }
1333   else
1334     priv->month--;
1335
1336   month_len = month_length[leap (priv->year)][priv->month + 1];
1337
1338   calendar_compute_days (calendar);
1339   
1340   g_signal_emit (calendar,
1341                  gtk_calendar_signals[PREV_MONTH_SIGNAL],
1342                  0);
1343   g_signal_emit (calendar,
1344                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
1345                  0);
1346
1347   if (month_len < priv->selected_day)
1348     {
1349       priv->selected_day = 0;
1350       gtk_calendar_select_day (calendar, month_len);
1351     }
1352   else
1353     {
1354       if (priv->selected_day < 0)
1355         priv->selected_day = priv->selected_day + 1 + month_length[leap (priv->year)][priv->month + 1];
1356       gtk_calendar_select_day (calendar, priv->selected_day);
1357     }
1358
1359   calendar_queue_refresh (calendar);
1360 }
1361
1362 \f
1363 /****************************************
1364  *           Basic object methods       *
1365  ****************************************/
1366
1367 static void
1368 gtk_calendar_finalize (GObject *object)
1369 {
1370   G_OBJECT_CLASS (gtk_calendar_parent_class)->finalize (object);
1371 }
1372
1373 static void
1374 gtk_calendar_destroy (GtkObject *object)
1375 {
1376   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (object);
1377
1378   calendar_stop_spinning (GTK_CALENDAR (object));
1379
1380   /* Call the destroy function for the extra display callback: */
1381   if (priv->detail_func_destroy && priv->detail_func_user_data)
1382     {
1383       priv->detail_func_destroy (priv->detail_func_user_data);
1384       priv->detail_func_user_data = NULL;
1385       priv->detail_func_destroy = NULL;
1386     }
1387
1388   GTK_OBJECT_CLASS (gtk_calendar_parent_class)->destroy (object);
1389 }
1390
1391
1392 static void
1393 calendar_set_display_option (GtkCalendar              *calendar,
1394                              GtkCalendarDisplayOptions flag,
1395                              gboolean                  setting)
1396 {
1397   GtkCalendarPrivate *priv = calendar->priv;
1398   GtkCalendarDisplayOptions flags;
1399
1400   if (setting)
1401     flags = priv->display_flags | flag;
1402   else
1403     flags = priv->display_flags & ~flag;
1404   gtk_calendar_set_display_options (calendar, flags);
1405 }
1406
1407 static gboolean
1408 calendar_get_display_option (GtkCalendar              *calendar,
1409                              GtkCalendarDisplayOptions flag)
1410 {
1411   GtkCalendarPrivate *priv = calendar->priv;
1412
1413   return (priv->display_flags & flag) != 0;
1414 }
1415
1416 static void 
1417 gtk_calendar_set_property (GObject      *object,
1418                            guint         prop_id,
1419                            const GValue *value,
1420                            GParamSpec   *pspec)
1421 {
1422   GtkCalendar *calendar = GTK_CALENDAR (object);
1423   GtkCalendarPrivate *priv = calendar->priv;
1424
1425   switch (prop_id) 
1426     {
1427     case PROP_YEAR:
1428       gtk_calendar_select_month (calendar,
1429                                  priv->month,
1430                                  g_value_get_int (value));
1431       break;
1432     case PROP_MONTH:
1433       gtk_calendar_select_month (calendar,
1434                                  g_value_get_int (value),
1435                                  priv->year);
1436       break;
1437     case PROP_DAY:
1438       gtk_calendar_select_day (calendar,
1439                                g_value_get_int (value));
1440       break;
1441     case PROP_SHOW_HEADING:
1442       calendar_set_display_option (calendar,
1443                                    GTK_CALENDAR_SHOW_HEADING,
1444                                    g_value_get_boolean (value));
1445       break;
1446     case PROP_SHOW_DAY_NAMES:
1447       calendar_set_display_option (calendar,
1448                                    GTK_CALENDAR_SHOW_DAY_NAMES,
1449                                    g_value_get_boolean (value));
1450       break;
1451     case PROP_NO_MONTH_CHANGE:
1452       calendar_set_display_option (calendar,
1453                                    GTK_CALENDAR_NO_MONTH_CHANGE,
1454                                    g_value_get_boolean (value));
1455       break;
1456     case PROP_SHOW_WEEK_NUMBERS:
1457       calendar_set_display_option (calendar,
1458                                    GTK_CALENDAR_SHOW_WEEK_NUMBERS,
1459                                    g_value_get_boolean (value));
1460       break;
1461     case PROP_SHOW_DETAILS:
1462       calendar_set_display_option (calendar,
1463                                    GTK_CALENDAR_SHOW_DETAILS,
1464                                    g_value_get_boolean (value));
1465       break;
1466     case PROP_DETAIL_WIDTH_CHARS:
1467       gtk_calendar_set_detail_width_chars (calendar,
1468                                            g_value_get_int (value));
1469       break;
1470     case PROP_DETAIL_HEIGHT_ROWS:
1471       gtk_calendar_set_detail_height_rows (calendar,
1472                                            g_value_get_int (value));
1473       break;
1474     default:
1475       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1476       break;
1477     }
1478 }
1479
1480 static void 
1481 gtk_calendar_get_property (GObject      *object,
1482                            guint         prop_id,
1483                            GValue       *value,
1484                            GParamSpec   *pspec)
1485 {
1486   GtkCalendar *calendar = GTK_CALENDAR (object);
1487   GtkCalendarPrivate *priv = calendar->priv;
1488
1489   switch (prop_id) 
1490     {
1491     case PROP_YEAR:
1492       g_value_set_int (value, priv->year);
1493       break;
1494     case PROP_MONTH:
1495       g_value_set_int (value, priv->month);
1496       break;
1497     case PROP_DAY:
1498       g_value_set_int (value, priv->selected_day);
1499       break;
1500     case PROP_SHOW_HEADING:
1501       g_value_set_boolean (value, calendar_get_display_option (calendar,
1502                                                                GTK_CALENDAR_SHOW_HEADING));
1503       break;
1504     case PROP_SHOW_DAY_NAMES:
1505       g_value_set_boolean (value, calendar_get_display_option (calendar,
1506                                                                GTK_CALENDAR_SHOW_DAY_NAMES));
1507       break;
1508     case PROP_NO_MONTH_CHANGE:
1509       g_value_set_boolean (value, calendar_get_display_option (calendar,
1510                                                                GTK_CALENDAR_NO_MONTH_CHANGE));
1511       break;
1512     case PROP_SHOW_WEEK_NUMBERS:
1513       g_value_set_boolean (value, calendar_get_display_option (calendar,
1514                                                                GTK_CALENDAR_SHOW_WEEK_NUMBERS));
1515       break;
1516     case PROP_SHOW_DETAILS:
1517       g_value_set_boolean (value, calendar_get_display_option (calendar,
1518                                                                GTK_CALENDAR_SHOW_DETAILS));
1519       break;
1520     case PROP_DETAIL_WIDTH_CHARS:
1521       g_value_set_int (value, priv->detail_width_chars);
1522       break;
1523     case PROP_DETAIL_HEIGHT_ROWS:
1524       g_value_set_int (value, priv->detail_height_rows);
1525       break;
1526     default:
1527       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1528       break;
1529     }
1530 }
1531
1532 \f
1533 /****************************************
1534  *             Realization              *
1535  ****************************************/
1536
1537 static void
1538 calendar_realize_arrows (GtkCalendar *calendar)
1539 {
1540   GtkWidget *widget = GTK_WIDGET (calendar);
1541   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1542   GdkWindowAttr attributes;
1543   gint attributes_mask;
1544   gint i;
1545   
1546   /* Arrow windows ------------------------------------- */
1547   if (! (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
1548       && (priv->display_flags & GTK_CALENDAR_SHOW_HEADING))
1549     {
1550       attributes.wclass = GDK_INPUT_OUTPUT;
1551       attributes.window_type = GDK_WINDOW_CHILD;
1552       attributes.visual = gtk_widget_get_visual (widget);
1553       attributes.colormap = gtk_widget_get_colormap (widget);
1554       attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK
1555                                | GDK_BUTTON_PRESS_MASK  | GDK_BUTTON_RELEASE_MASK
1556                                | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1557       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1558       for (i = 0; i < 4; i++)
1559         {
1560           GdkRectangle rect;
1561           calendar_arrow_rectangle (calendar, i, &rect);
1562           
1563           attributes.x = rect.x;
1564           attributes.y = rect.y;
1565           attributes.width = rect.width;
1566           attributes.height = rect.height;
1567           priv->arrow_win[i] = gdk_window_new (priv->header_win,
1568                                                &attributes, 
1569                                                attributes_mask);
1570           if (gtk_widget_is_sensitive (widget))
1571             priv->arrow_state[i] = GTK_STATE_NORMAL;
1572           else 
1573             priv->arrow_state[i] = GTK_STATE_INSENSITIVE;
1574           gdk_window_set_background (priv->arrow_win[i],
1575                                      HEADER_BG_COLOR (GTK_WIDGET (calendar)));
1576           gdk_window_show (priv->arrow_win[i]);
1577           gdk_window_set_user_data (priv->arrow_win[i], widget);
1578         }
1579     }
1580   else
1581     {
1582       for (i = 0; i < 4; i++)
1583         priv->arrow_win[i] = NULL;
1584     }
1585 }
1586
1587 static void
1588 calendar_realize_header (GtkCalendar *calendar)
1589 {
1590   GtkWidget *widget = GTK_WIDGET (calendar);
1591   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1592   GdkWindowAttr attributes;
1593   gint attributes_mask;
1594   
1595   /* Header window ------------------------------------- */
1596   if (priv->display_flags & GTK_CALENDAR_SHOW_HEADING)
1597     {
1598       attributes.wclass = GDK_INPUT_OUTPUT;
1599       attributes.window_type = GDK_WINDOW_CHILD;
1600       attributes.visual = gtk_widget_get_visual (widget);
1601       attributes.colormap = gtk_widget_get_colormap (widget);
1602       attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
1603       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1604       attributes.x = widget->style->xthickness;
1605       attributes.y = widget->style->ythickness;
1606       attributes.width = widget->allocation.width - 2 * attributes.x;
1607       attributes.height = priv->header_h;
1608       priv->header_win = gdk_window_new (widget->window,
1609                                          &attributes, attributes_mask);
1610       
1611       gdk_window_set_background (priv->header_win,
1612                                  HEADER_BG_COLOR (GTK_WIDGET (calendar)));
1613       gdk_window_show (priv->header_win);
1614       gdk_window_set_user_data (priv->header_win, widget);
1615       
1616     }
1617   else
1618     {
1619       priv->header_win = NULL;
1620     }
1621   calendar_realize_arrows (calendar);
1622 }
1623
1624 static gint
1625 calendar_get_inner_border (GtkCalendar *calendar)
1626 {
1627   gint inner_border;
1628
1629   gtk_widget_style_get (GTK_WIDGET (calendar),
1630                         "inner-border", &inner_border,
1631                         NULL);
1632
1633   return inner_border;
1634 }
1635
1636 static gint
1637 calendar_get_xsep (GtkCalendar *calendar)
1638 {
1639   gint xsep;
1640
1641   gtk_widget_style_get (GTK_WIDGET (calendar),
1642                         "horizontal-separation", &xsep,
1643                         NULL);
1644
1645   return xsep;
1646 }
1647
1648 static gint
1649 calendar_get_ysep (GtkCalendar *calendar)
1650 {
1651   gint ysep;
1652
1653   gtk_widget_style_get (GTK_WIDGET (calendar),
1654                         "vertical-separation", &ysep,
1655                         NULL);
1656
1657   return ysep;
1658 }
1659
1660 static void
1661 calendar_realize_day_names (GtkCalendar *calendar)
1662 {
1663   GtkWidget *widget = GTK_WIDGET (calendar);
1664   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1665   GdkWindowAttr attributes;
1666   gint attributes_mask;
1667   gint inner_border = calendar_get_inner_border (calendar);
1668
1669   /* Day names  window --------------------------------- */
1670   if ( priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
1671     {
1672       attributes.wclass = GDK_INPUT_OUTPUT;
1673       attributes.window_type = GDK_WINDOW_CHILD;
1674       attributes.visual = gtk_widget_get_visual (widget);
1675       attributes.colormap = gtk_widget_get_colormap (widget);
1676       attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
1677       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1678       attributes.x = (widget->style->xthickness + inner_border);
1679       attributes.y = priv->header_h + (widget->style->ythickness 
1680                                            + inner_border);
1681       attributes.width = (widget->allocation.width 
1682                           - (widget->style->xthickness + inner_border) 
1683                           * 2);
1684       attributes.height = priv->day_name_h;
1685       priv->day_name_win = gdk_window_new (widget->window,
1686                                            &attributes, 
1687                                            attributes_mask);
1688       gdk_window_set_background (priv->day_name_win, 
1689                                  BACKGROUND_COLOR ( GTK_WIDGET ( calendar)));
1690       gdk_window_show (priv->day_name_win);
1691       gdk_window_set_user_data (priv->day_name_win, widget);
1692     }
1693   else
1694     {
1695       priv->day_name_win = NULL;
1696     }
1697 }
1698
1699 static void
1700 calendar_realize_week_numbers (GtkCalendar *calendar)
1701 {
1702   GtkWidget *widget = GTK_WIDGET (calendar);
1703   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1704   GdkWindowAttr attributes;
1705   gint attributes_mask;
1706   gint inner_border = calendar_get_inner_border (calendar);
1707
1708   /* Week number window -------------------------------- */
1709   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
1710     {
1711       attributes.wclass = GDK_INPUT_OUTPUT;
1712       attributes.window_type = GDK_WINDOW_CHILD;
1713       attributes.visual = gtk_widget_get_visual (widget);
1714       attributes.colormap = gtk_widget_get_colormap (widget);
1715       attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
1716       
1717       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1718       if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) 
1719         attributes.x = widget->style->xthickness + inner_border;
1720       else 
1721         attributes.x = widget->allocation.width - priv->week_width - (widget->style->xthickness + inner_border);
1722       attributes.y = (priv->header_h + priv->day_name_h 
1723                       + (widget->style->ythickness + inner_border));
1724       attributes.width = priv->week_width;
1725       attributes.height = priv->main_h;
1726       priv->week_win = gdk_window_new (widget->window,
1727                                        &attributes, attributes_mask);
1728       gdk_window_set_background (priv->week_win,  
1729                                  BACKGROUND_COLOR (GTK_WIDGET (calendar)));
1730       gdk_window_show (priv->week_win);
1731       gdk_window_set_user_data (priv->week_win, widget);
1732     } 
1733   else
1734     {
1735       priv->week_win = NULL;
1736     }
1737 }
1738
1739 static void
1740 gtk_calendar_realize (GtkWidget *widget)
1741 {
1742   GtkCalendar *calendar = GTK_CALENDAR (widget);
1743   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1744   GdkWindowAttr attributes;
1745   gint attributes_mask;
1746   gint inner_border = calendar_get_inner_border (calendar);
1747
1748   gtk_widget_set_realized (widget, TRUE);
1749   
1750   attributes.x = widget->allocation.x;
1751   attributes.y = widget->allocation.y;
1752   attributes.width = widget->allocation.width;
1753   attributes.height = widget->allocation.height;
1754   attributes.wclass = GDK_INPUT_OUTPUT;
1755   attributes.window_type = GDK_WINDOW_CHILD;
1756   attributes.event_mask =  (gtk_widget_get_events (widget) 
1757                             | GDK_EXPOSURE_MASK |GDK_KEY_PRESS_MASK | GDK_SCROLL_MASK);
1758   attributes.visual = gtk_widget_get_visual (widget);
1759   attributes.colormap = gtk_widget_get_colormap (widget);
1760   
1761   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1762   widget->window = gdk_window_new (widget->parent->window,
1763                                    &attributes, attributes_mask);
1764   
1765   widget->style = gtk_style_attach (widget->style, widget->window);
1766   
1767   /* Header window ------------------------------------- */
1768   calendar_realize_header (calendar);
1769   /* Day names  window --------------------------------- */
1770   calendar_realize_day_names (calendar);
1771   /* Week number window -------------------------------- */
1772   calendar_realize_week_numbers (calendar);
1773   /* Main Window --------------------------------------  */
1774   attributes.event_mask =  (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK
1775                             | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1776                             | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
1777   
1778   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) 
1779     attributes.x = priv->week_width + (widget->style->ythickness + inner_border);
1780   else
1781     attributes.x = widget->style->ythickness + inner_border;
1782
1783   attributes.y = (priv->header_h + priv->day_name_h 
1784                   + (widget->style->ythickness + inner_border));
1785   attributes.width = (widget->allocation.width - attributes.x 
1786                       - (widget->style->xthickness + inner_border));
1787   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1788     attributes.width -= priv->week_width;
1789
1790   attributes.height = priv->main_h;
1791   priv->main_win = gdk_window_new (widget->window,
1792                                    &attributes, attributes_mask);
1793   gdk_window_set_background (priv->main_win, 
1794                              BACKGROUND_COLOR ( GTK_WIDGET ( calendar)));
1795   gdk_window_show (priv->main_win);
1796   gdk_window_set_user_data (priv->main_win, widget);
1797   gdk_window_set_background (widget->window, BACKGROUND_COLOR (widget));
1798   gdk_window_show (widget->window);
1799   gdk_window_set_user_data (widget->window, widget);
1800 }
1801
1802 static void
1803 gtk_calendar_unrealize (GtkWidget *widget)
1804 {
1805   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1806   gint i;
1807   
1808   if (priv->header_win)
1809     {
1810       for (i = 0; i < 4; i++)
1811         {
1812           if (priv->arrow_win[i])
1813             {
1814               gdk_window_set_user_data (priv->arrow_win[i], NULL);
1815               gdk_window_destroy (priv->arrow_win[i]);
1816               priv->arrow_win[i] = NULL;
1817             }
1818         }
1819       gdk_window_set_user_data (priv->header_win, NULL);
1820       gdk_window_destroy (priv->header_win);
1821       priv->header_win = NULL;
1822     }
1823   
1824   if (priv->week_win)
1825     {
1826       gdk_window_set_user_data (priv->week_win, NULL);
1827       gdk_window_destroy (priv->week_win);
1828       priv->week_win = NULL;      
1829     }
1830   
1831   if (priv->main_win)
1832     {
1833       gdk_window_set_user_data (priv->main_win, NULL);
1834       gdk_window_destroy (priv->main_win);
1835       priv->main_win = NULL;      
1836     }
1837   if (priv->day_name_win)
1838     {
1839       gdk_window_set_user_data (priv->day_name_win, NULL);
1840       gdk_window_destroy (priv->day_name_win);
1841       priv->day_name_win = NULL;      
1842     }
1843
1844   GTK_WIDGET_CLASS (gtk_calendar_parent_class)->unrealize (widget);
1845 }
1846
1847 static gchar*
1848 gtk_calendar_get_detail (GtkCalendar *calendar,
1849                          gint         row,
1850                          gint         column)
1851 {
1852   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
1853   gint year, month;
1854
1855   if (priv->detail_func == NULL)
1856     return NULL;
1857
1858   year = priv->year;
1859   month = priv->month + priv->day_month[row][column] - MONTH_CURRENT;
1860
1861   if (month < 0)
1862     {
1863       month += 12;
1864       year -= 1;
1865     }
1866   else if (month > 11)
1867     {
1868       month -= 12;
1869       year += 1;
1870     }
1871
1872   return priv->detail_func (calendar,
1873                             year, month,
1874                             priv->day[row][column],
1875                             priv->detail_func_user_data);
1876 }
1877
1878 static gboolean
1879 gtk_calendar_query_tooltip (GtkWidget  *widget,
1880                             gint        x,
1881                             gint        y,
1882                             gboolean    keyboard_mode,
1883                             GtkTooltip *tooltip)
1884 {
1885   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1886   GtkCalendar *calendar = GTK_CALENDAR (widget);
1887   gchar *detail = NULL;
1888   GdkRectangle day_rect;
1889
1890   if (priv->main_win)
1891     {
1892       gint x0, y0, row, col;
1893
1894       gdk_window_get_position (priv->main_win, &x0, &y0);
1895       col = calendar_column_from_x (calendar, x - x0);
1896       row = calendar_row_from_y (calendar, y - y0);
1897
1898       if (col != -1 && row != -1 &&
1899           (0 != (priv->detail_overflow[row] & (1 << col)) ||
1900            0 == (priv->display_flags & GTK_CALENDAR_SHOW_DETAILS)))
1901         {
1902           detail = gtk_calendar_get_detail (calendar, row, col);
1903           calendar_day_rectangle (calendar, row, col, &day_rect);
1904
1905           day_rect.x += x0;
1906           day_rect.y += y0;
1907         }
1908     }
1909
1910   if (detail)
1911     {
1912       gtk_tooltip_set_tip_area (tooltip, &day_rect);
1913       gtk_tooltip_set_markup (tooltip, detail);
1914
1915       g_free (detail);
1916
1917       return TRUE;
1918     }
1919
1920   if (GTK_WIDGET_CLASS (gtk_calendar_parent_class)->query_tooltip)
1921     return GTK_WIDGET_CLASS (gtk_calendar_parent_class)->query_tooltip (widget, x, y, keyboard_mode, tooltip);
1922
1923   return FALSE;
1924 }
1925
1926 \f
1927 /****************************************
1928  *       Size Request and Allocate      *
1929  ****************************************/
1930
1931 static void
1932 gtk_calendar_size_request (GtkWidget      *widget,
1933                            GtkRequisition *requisition)
1934 {
1935   GtkCalendar *calendar = GTK_CALENDAR (widget);
1936   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
1937   PangoLayout *layout;
1938   PangoRectangle logical_rect;
1939
1940   gint height;
1941   gint i, r, c;
1942   gint calendar_margin = CALENDAR_MARGIN;
1943   gint header_width, main_width;
1944   gint max_header_height = 0;
1945   gint focus_width;
1946   gint focus_padding;
1947   gint max_detail_height;
1948   gint inner_border = calendar_get_inner_border (calendar);
1949   gint calendar_ysep = calendar_get_ysep (calendar);
1950   gint calendar_xsep = calendar_get_xsep (calendar);
1951
1952   gtk_widget_style_get (GTK_WIDGET (widget),
1953                         "focus-line-width", &focus_width,
1954                         "focus-padding", &focus_padding,
1955                         NULL);
1956
1957   layout = gtk_widget_create_pango_layout (widget, NULL);
1958   
1959   /*
1960    * Calculate the requisition  width for the widget.
1961    */
1962   
1963   /* Header width */
1964   
1965   if (priv->display_flags & GTK_CALENDAR_SHOW_HEADING)
1966     {
1967       priv->max_month_width = 0;
1968       for (i = 0; i < 12; i++)
1969         {
1970           pango_layout_set_text (layout, default_monthname[i], -1);
1971           pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1972           priv->max_month_width = MAX (priv->max_month_width,
1973                                                logical_rect.width + 8);
1974           max_header_height = MAX (max_header_height, logical_rect.height); 
1975         }
1976
1977       priv->max_year_width = 0;
1978       /* Translators:  This is a text measurement template.
1979        * Translate it to the widest year text
1980        *
1981        * If you don't understand this, leave it as "2000"
1982        */
1983       pango_layout_set_text (layout, C_("year measurement template", "2000"), -1);        
1984       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
1985       priv->max_year_width = MAX (priv->max_year_width,
1986                                   logical_rect.width + 8);
1987       max_header_height = MAX (max_header_height, logical_rect.height); 
1988     } 
1989   else 
1990     {
1991       priv->max_month_width = 0;
1992       priv->max_year_width = 0;
1993     }
1994   
1995   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
1996     header_width = (priv->max_month_width 
1997                     + priv->max_year_width
1998                     + 3 * 3);
1999   else
2000     header_width = (priv->max_month_width 
2001                     + priv->max_year_width
2002                     + 4 * priv->arrow_width + 3 * 3);
2003
2004   /* Mainwindow labels width */
2005   
2006   priv->max_day_char_width = 0;
2007   priv->max_day_char_ascent = 0;
2008   priv->max_day_char_descent = 0;
2009   priv->min_day_width = 0;
2010
2011   for (i = 0; i < 9; i++)
2012     {
2013       gchar buffer[32];
2014       g_snprintf (buffer, sizeof (buffer), C_("calendar:day:digits", "%d"), i * 11);
2015       pango_layout_set_text (layout, buffer, -1);         
2016       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2017       priv->min_day_width = MAX (priv->min_day_width,
2018                                          logical_rect.width);
2019
2020       priv->max_day_char_ascent = MAX (priv->max_day_char_ascent,
2021                                                PANGO_ASCENT (logical_rect));
2022       priv->max_day_char_descent = MAX (priv->max_day_char_descent, 
2023                                                 PANGO_DESCENT (logical_rect));
2024     }
2025   
2026   priv->max_label_char_ascent = 0;
2027   priv->max_label_char_descent = 0;
2028   if (priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
2029     for (i = 0; i < 7; i++)
2030       {
2031         pango_layout_set_text (layout, default_abbreviated_dayname[i], -1);
2032         pango_layout_line_get_pixel_extents (pango_layout_get_lines_readonly (layout)->data, NULL, &logical_rect);
2033
2034         priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
2035         priv->max_label_char_ascent = MAX (priv->max_label_char_ascent,
2036                                                    PANGO_ASCENT (logical_rect));
2037         priv->max_label_char_descent = MAX (priv->max_label_char_descent, 
2038                                                     PANGO_DESCENT (logical_rect));
2039       }
2040   
2041   priv->max_week_char_width = 0;
2042   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
2043     for (i = 0; i < 9; i++)
2044       {
2045         gchar buffer[32];
2046         g_snprintf (buffer, sizeof (buffer), C_("calendar:week:digits", "%d"), i * 11);
2047         pango_layout_set_text (layout, buffer, -1);       
2048         pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2049         priv->max_week_char_width = MAX (priv->max_week_char_width,
2050                                            logical_rect.width / 2);
2051       }
2052   
2053   /* Calculate detail extents. Do this as late as possible since
2054    * pango_layout_set_markup is called which alters font settings. */
2055   max_detail_height = 0;
2056
2057   if (priv->detail_func && (priv->display_flags & GTK_CALENDAR_SHOW_DETAILS))
2058     {
2059       gchar *markup, *tail;
2060
2061       if (priv->detail_width_chars || priv->detail_height_rows)
2062         {
2063           gint rows = MAX (1, priv->detail_height_rows) - 1;
2064           gsize len = priv->detail_width_chars + rows + 16;
2065
2066           markup = tail = g_alloca (len);
2067
2068           memcpy (tail,     "<small>", 7);
2069           tail += 7;
2070
2071           memset (tail, 'm', priv->detail_width_chars);
2072           tail += priv->detail_width_chars;
2073
2074           memset (tail, '\n', rows);
2075           tail += rows;
2076
2077           memcpy (tail,     "</small>", 9);
2078           tail += 9;
2079
2080           g_assert (len == (tail - markup));
2081
2082           pango_layout_set_markup (layout, markup, -1);
2083           pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2084
2085           if (priv->detail_width_chars)
2086             priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
2087           if (priv->detail_height_rows)
2088             max_detail_height = MAX (max_detail_height, logical_rect.height);
2089         }
2090
2091       if (!priv->detail_width_chars || !priv->detail_height_rows)
2092         for (r = 0; r < 6; r++)
2093           for (c = 0; c < 7; c++)
2094             {
2095               gchar *detail = gtk_calendar_get_detail (calendar, r, c);
2096
2097               if (detail)
2098                 {
2099                   markup = g_strconcat ("<small>", detail, "</small>", NULL);
2100                   pango_layout_set_markup (layout, markup, -1);
2101
2102                   if (priv->detail_width_chars)
2103                     {
2104                       pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
2105                       pango_layout_set_width (layout, PANGO_SCALE * priv->min_day_width);
2106                     }
2107
2108                   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2109
2110                   if (!priv->detail_width_chars)
2111                     priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
2112                   if (!priv->detail_height_rows)
2113                     max_detail_height = MAX (max_detail_height, logical_rect.height);
2114
2115                   g_free (markup);
2116                   g_free (detail);
2117                 }
2118             }
2119     }
2120
2121   /* We add one to max_day_char_width to be able to make the marked day "bold" */
2122   priv->max_day_char_width = priv->min_day_width / 2 + 1;
2123
2124   main_width = (7 * (priv->min_day_width + (focus_padding + focus_width) * 2) + (DAY_XSEP * 6) + CALENDAR_MARGIN * 2
2125                 + (priv->max_week_char_width
2126                    ? priv->max_week_char_width * 2 + (focus_padding + focus_width) * 2 + calendar_xsep * 2
2127                    : 0));
2128   
2129   
2130   requisition->width = MAX (header_width, main_width + inner_border * 2) + widget->style->xthickness * 2;
2131   
2132   /*
2133    * Calculate the requisition height for the widget.
2134    */
2135   
2136   if (priv->display_flags & GTK_CALENDAR_SHOW_HEADING)
2137     {
2138       priv->header_h = (max_header_height + calendar_ysep * 2);
2139     }
2140   else
2141     {
2142       priv->header_h = 0;
2143     }
2144   
2145   if (priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
2146     {
2147       priv->day_name_h = (priv->max_label_char_ascent
2148                                   + priv->max_label_char_descent
2149                                   + 2 * (focus_padding + focus_width) + calendar_margin);
2150       calendar_margin = calendar_ysep;
2151     } 
2152   else
2153     {
2154       priv->day_name_h = 0;
2155     }
2156
2157   priv->main_h = (CALENDAR_MARGIN + calendar_margin
2158                           + 6 * (priv->max_day_char_ascent
2159                                  + priv->max_day_char_descent 
2160                                  + max_detail_height
2161                                  + 2 * (focus_padding + focus_width))
2162                           + DAY_YSEP * 5);
2163   
2164   height = (priv->header_h + priv->day_name_h 
2165             + priv->main_h);
2166   
2167   requisition->height = height + (widget->style->ythickness + inner_border) * 2;
2168
2169   g_object_unref (layout);
2170 }
2171
2172 static void
2173 gtk_calendar_size_allocate (GtkWidget     *widget,
2174                             GtkAllocation *allocation)
2175 {
2176   GtkCalendar *calendar = GTK_CALENDAR (widget);
2177   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2178   gint xthickness = widget->style->xthickness;
2179   gint ythickness = widget->style->xthickness;
2180   guint i;
2181   gint inner_border = calendar_get_inner_border (calendar);
2182   gint calendar_xsep = calendar_get_xsep (calendar);
2183
2184   widget->allocation = *allocation;
2185     
2186   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
2187     {
2188       priv->day_width = (priv->min_day_width
2189                          * ((allocation->width - (xthickness + inner_border) * 2
2190                              - (CALENDAR_MARGIN * 2) -  (DAY_XSEP * 6) - calendar_xsep * 2))
2191                          / (7 * priv->min_day_width + priv->max_week_char_width * 2));
2192       priv->week_width = ((allocation->width - (xthickness + inner_border) * 2
2193                            - (CALENDAR_MARGIN * 2) - (DAY_XSEP * 6) - calendar_xsep * 2 )
2194                           - priv->day_width * 7 + CALENDAR_MARGIN + calendar_xsep);
2195     } 
2196   else 
2197     {
2198       priv->day_width = (allocation->width
2199                          - (xthickness + inner_border) * 2
2200                          - (CALENDAR_MARGIN * 2)
2201                          - (DAY_XSEP * 6))/7;
2202       priv->week_width = 0;
2203     }
2204   
2205   if (gtk_widget_get_realized (widget))
2206     {
2207       gdk_window_move_resize (widget->window,
2208                               allocation->x, allocation->y,
2209                               allocation->width, allocation->height);
2210       if (priv->header_win)
2211         gdk_window_move_resize (priv->header_win,
2212                                 xthickness, ythickness,
2213                                 allocation->width - 2 * xthickness, priv->header_h);
2214
2215       for (i = 0 ; i < 4 ; i++)
2216         {
2217           if (priv->arrow_win[i])
2218             {
2219               GdkRectangle rect;
2220               calendar_arrow_rectangle (calendar, i, &rect);
2221           
2222               gdk_window_move_resize (priv->arrow_win[i],
2223                                       rect.x, rect.y, rect.width, rect.height);
2224             }
2225         }
2226       
2227       if (priv->day_name_win)
2228         gdk_window_move_resize (priv->day_name_win,
2229                                 xthickness + inner_border,
2230                                 priv->header_h + (widget->style->ythickness + inner_border),
2231                                 allocation->width - (xthickness + inner_border) * 2,
2232                                 priv->day_name_h);
2233       if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) 
2234         {
2235           if (priv->week_win)
2236             gdk_window_move_resize (priv->week_win,
2237                                     (xthickness + inner_border),
2238                                     priv->header_h + priv->day_name_h
2239                                     + (widget->style->ythickness + inner_border),
2240                                     priv->week_width,
2241                                     priv->main_h);
2242           gdk_window_move_resize (priv->main_win,
2243                                   priv->week_width + (xthickness + inner_border),
2244                                   priv->header_h + priv->day_name_h
2245                                   + (widget->style->ythickness + inner_border),
2246                                   allocation->width 
2247                                   - priv->week_width 
2248                                   - (xthickness + inner_border) * 2,
2249                                   priv->main_h);
2250         }
2251       else 
2252         {
2253           gdk_window_move_resize (priv->main_win,
2254                                   (xthickness + inner_border),
2255                                   priv->header_h + priv->day_name_h
2256                                   + (widget->style->ythickness + inner_border),
2257                                   allocation->width 
2258                                   - priv->week_width 
2259                                   - (xthickness + inner_border) * 2,
2260                                   priv->main_h);
2261           if (priv->week_win)
2262             gdk_window_move_resize (priv->week_win,
2263                                     allocation->width 
2264                                     - priv->week_width 
2265                                     - (xthickness + inner_border),
2266                                     priv->header_h + priv->day_name_h
2267                                     + (widget->style->ythickness + inner_border),
2268                                     priv->week_width,
2269                                     priv->main_h);
2270         }
2271     }
2272 }
2273
2274 \f
2275 /****************************************
2276  *              Repainting              *
2277  ****************************************/
2278
2279 static void
2280 calendar_paint_header (GtkCalendar *calendar)
2281 {
2282   GtkWidget *widget = GTK_WIDGET (calendar);
2283   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2284   cairo_t *cr;
2285   char buffer[255];
2286   int x, y;
2287   gint header_width;
2288   gint max_month_width;
2289   gint max_year_width;
2290   PangoLayout *layout;
2291   PangoRectangle logical_rect;
2292   gboolean year_left;
2293   time_t tmp_time;
2294   struct tm *tm;
2295   gchar *str;
2296
2297   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) 
2298     year_left = priv->year_before;
2299   else
2300     year_left = !priv->year_before;
2301
2302   cr = gdk_cairo_create (priv->header_win);
2303   
2304   header_width = widget->allocation.width - 2 * widget->style->xthickness;
2305   
2306   max_month_width = priv->max_month_width;
2307   max_year_width = priv->max_year_width;
2308   
2309   gtk_paint_shadow (widget->style, priv->header_win,
2310                     GTK_STATE_NORMAL, GTK_SHADOW_OUT,
2311                     NULL, widget, "calendar",
2312                     0, 0, header_width, priv->header_h);
2313
2314   tmp_time = 1;  /* Jan 1 1970, 00:00:01 UTC */
2315   tm = gmtime (&tmp_time);
2316   tm->tm_year = priv->year - 1900;
2317
2318   /* Translators: This dictates how the year is displayed in
2319    * gtkcalendar widget.  See strftime() manual for the format.
2320    * Use only ASCII in the translation.
2321    *
2322    * Also look for the msgid "2000".
2323    * Translate that entry to a year with the widest output of this
2324    * msgid.
2325    *
2326    * "%Y" is appropriate for most locales.
2327    */
2328   strftime (buffer, sizeof (buffer), C_("calendar year format", "%Y"), tm);
2329   str = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
2330   layout = gtk_widget_create_pango_layout (widget, str);
2331   g_free (str);
2332   
2333   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2334   
2335   /* Draw title */
2336   y = (priv->header_h - logical_rect.height) / 2;
2337   
2338   /* Draw year and its arrows */
2339   
2340   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
2341     if (year_left)
2342       x = 3 + (max_year_width - logical_rect.width)/2;
2343     else
2344       x = header_width - (3 + max_year_width
2345                           - (max_year_width - logical_rect.width)/2);
2346   else
2347     if (year_left)
2348       x = 3 + priv->arrow_width + (max_year_width - logical_rect.width)/2;
2349     else
2350       x = header_width - (3 + priv->arrow_width + max_year_width
2351                           - (max_year_width - logical_rect.width)/2);
2352   
2353
2354   gdk_cairo_set_source_color (cr, HEADER_FG_COLOR (GTK_WIDGET (calendar)));
2355   cairo_move_to (cr, x, y);
2356   pango_cairo_show_layout (cr, layout);
2357   
2358   /* Draw month */
2359   g_snprintf (buffer, sizeof (buffer), "%s", default_monthname[priv->month]);
2360   pango_layout_set_text (layout, buffer, -1);
2361   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2362
2363   if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
2364     if (year_left)
2365       x = header_width - (3 + max_month_width
2366                           - (max_month_width - logical_rect.width)/2);      
2367     else
2368     x = 3 + (max_month_width - logical_rect.width) / 2;
2369   else
2370     if (year_left)
2371       x = header_width - (3 + priv->arrow_width + max_month_width
2372                           - (max_month_width - logical_rect.width)/2);
2373     else
2374     x = 3 + priv->arrow_width + (max_month_width - logical_rect.width)/2;
2375
2376   cairo_move_to (cr, x, y);
2377   pango_cairo_show_layout (cr, layout);
2378
2379   g_object_unref (layout);
2380   cairo_destroy (cr);
2381 }
2382
2383 static void
2384 calendar_paint_day_names (GtkCalendar *calendar)
2385 {
2386   GtkWidget *widget = GTK_WIDGET (calendar);
2387   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2388   cairo_t *cr;
2389   char buffer[255];
2390   int day,i;
2391   int day_width, cal_width;
2392   int day_wid_sep;
2393   PangoLayout *layout;
2394   PangoRectangle logical_rect;
2395   gint focus_padding;
2396   gint focus_width;
2397   gint calendar_ysep = calendar_get_ysep (calendar);
2398   gint calendar_xsep = calendar_get_xsep (calendar);
2399
2400   cr = gdk_cairo_create (priv->day_name_win);
2401   
2402   gtk_widget_style_get (GTK_WIDGET (widget),
2403                         "focus-line-width", &focus_width,
2404                         "focus-padding", &focus_padding,
2405                         NULL);
2406   
2407   day_width = priv->day_width;
2408   cal_width = widget->allocation.width;
2409   day_wid_sep = day_width + DAY_XSEP;
2410   
2411   /*
2412    * Draw rectangles as inverted background for the labels.
2413    */
2414
2415   gdk_cairo_set_source_color (cr, SELECTED_BG_COLOR (widget));
2416   cairo_rectangle (cr,
2417                    CALENDAR_MARGIN, CALENDAR_MARGIN,
2418                    cal_width-CALENDAR_MARGIN * 2,
2419                    priv->day_name_h - CALENDAR_MARGIN);
2420   cairo_fill (cr);
2421   
2422   if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
2423     {
2424       cairo_rectangle (cr, 
2425                        CALENDAR_MARGIN,
2426                        priv->day_name_h - calendar_ysep,
2427                        priv->week_width - calendar_ysep - CALENDAR_MARGIN,
2428                        calendar_ysep);
2429       cairo_fill (cr);
2430     }
2431   
2432   /*
2433    * Write the labels
2434    */
2435
2436   layout = gtk_widget_create_pango_layout (widget, NULL);
2437
2438   gdk_cairo_set_source_color (cr, SELECTED_FG_COLOR (widget));
2439   for (i = 0; i < 7; i++)
2440     {
2441       if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
2442         day = 6 - i;
2443       else
2444         day = i;
2445       day = (day + priv->week_start) % 7;
2446       g_snprintf (buffer, sizeof (buffer), "%s", default_abbreviated_dayname[day]);
2447
2448       pango_layout_set_text (layout, buffer, -1);
2449       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2450
2451       cairo_move_to (cr, 
2452                      (CALENDAR_MARGIN +
2453                       + (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR ?
2454                          (priv->week_width + (priv->week_width ? calendar_xsep : 0))
2455                          : 0)
2456                       + day_wid_sep * i
2457                       + (day_width - logical_rect.width)/2),
2458                      CALENDAR_MARGIN + focus_width + focus_padding + logical_rect.y);
2459       pango_cairo_show_layout (cr, layout);
2460     }
2461   
2462   g_object_unref (layout);
2463   cairo_destroy (cr);
2464 }
2465
2466 static void
2467 calendar_paint_week_numbers (GtkCalendar *calendar)
2468 {
2469   GtkWidget *widget = GTK_WIDGET (calendar);
2470   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2471   cairo_t *cr;
2472
2473   guint week = 0, year;
2474   gint row, x_loc, y_loc;
2475   gint day_height;
2476   char buffer[32];
2477   PangoLayout *layout;
2478   PangoRectangle logical_rect;
2479   gint focus_padding;
2480   gint focus_width;
2481   gint calendar_xsep = calendar_get_xsep (calendar);
2482
2483   cr = gdk_cairo_create (priv->week_win);
2484   
2485   gtk_widget_style_get (GTK_WIDGET (widget),
2486                         "focus-line-width", &focus_width,
2487                         "focus-padding", &focus_padding,
2488                         NULL);
2489   
2490   /*
2491    * Draw a rectangle as inverted background for the labels.
2492    */
2493
2494   gdk_cairo_set_source_color (cr, SELECTED_BG_COLOR (widget));
2495   if (priv->day_name_win)
2496     cairo_rectangle (cr, 
2497                      CALENDAR_MARGIN,
2498                      0,
2499                      priv->week_width - CALENDAR_MARGIN,
2500                      priv->main_h - CALENDAR_MARGIN);
2501   else
2502     cairo_rectangle (cr,
2503                      CALENDAR_MARGIN,
2504                      CALENDAR_MARGIN,
2505                      priv->week_width - CALENDAR_MARGIN,
2506                      priv->main_h - 2 * CALENDAR_MARGIN);
2507   cairo_fill (cr);
2508   
2509   /*
2510    * Write the labels
2511    */
2512   
2513   layout = gtk_widget_create_pango_layout (widget, NULL);
2514   
2515   gdk_cairo_set_source_color (cr, SELECTED_FG_COLOR (widget));
2516   day_height = calendar_row_height (calendar);
2517   for (row = 0; row < 6; row++)
2518     {
2519       gboolean result;
2520       
2521       year = priv->year;
2522       if (priv->day[row][6] < 15 && row > 3 && priv->month == 11)
2523         year++;
2524
2525       result = week_of_year (&week, &year,              
2526                              ((priv->day[row][6] < 15 && row > 3 ? 1 : 0)
2527                               + priv->month) % 12 + 1, priv->day[row][6]);
2528       g_return_if_fail (result);
2529
2530       /* Translators: this defines whether the week numbers should use
2531        * localized digits or the ones used in English (0123...).
2532        *
2533        * Translate to "%Id" if you want to use localized digits, or
2534        * translate to "%d" otherwise.
2535        *
2536        * Note that translating this doesn't guarantee that you get localized
2537        * digits. That needs support from your system and locale definition
2538        * too.
2539        */
2540       g_snprintf (buffer, sizeof (buffer), C_("calendar:week:digits", "%d"), week);
2541       pango_layout_set_text (layout, buffer, -1);
2542       pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2543
2544       y_loc = calendar_top_y_for_row (calendar, row) + (day_height - logical_rect.height) / 2;
2545
2546       x_loc = (priv->week_width
2547                - logical_rect.width
2548                - calendar_xsep - focus_padding - focus_width);
2549
2550       cairo_move_to (cr, x_loc, y_loc);
2551       pango_cairo_show_layout (cr, layout);
2552     }
2553   
2554   g_object_unref (layout);
2555   cairo_destroy (cr);
2556 }
2557
2558 static void
2559 calendar_invalidate_day_num (GtkCalendar *calendar,
2560                              gint         day)
2561 {
2562   GtkCalendarPrivate *priv = calendar->priv;
2563   gint r, c, row, col;
2564   
2565   row = -1;
2566   col = -1;
2567   for (r = 0; r < 6; r++)
2568     for (c = 0; c < 7; c++)
2569       if (priv->day_month[r][c] == MONTH_CURRENT &&
2570           priv->day[r][c] == day)
2571         {
2572           row = r;
2573           col = c;
2574         }
2575   
2576   g_return_if_fail (row != -1);
2577   g_return_if_fail (col != -1);
2578   
2579   calendar_invalidate_day (calendar, row, col);
2580 }
2581
2582 static void
2583 calendar_invalidate_day (GtkCalendar *calendar,
2584                          gint         row,
2585                          gint         col)
2586 {
2587   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2588
2589   if (priv->main_win)
2590     {
2591       GdkRectangle day_rect;
2592       
2593       calendar_day_rectangle (calendar, row, col, &day_rect);
2594       gdk_window_invalidate_rect (priv->main_win, &day_rect, FALSE);
2595     }
2596 }
2597
2598 static gboolean
2599 is_color_attribute (PangoAttribute *attribute,
2600                     gpointer        data)
2601 {
2602   return (attribute->klass->type == PANGO_ATTR_FOREGROUND ||
2603           attribute->klass->type == PANGO_ATTR_BACKGROUND);
2604 }
2605
2606 static void
2607 calendar_paint_day (GtkCalendar *calendar,
2608                     gint             row,
2609                     gint             col)
2610 {
2611   GtkWidget *widget = GTK_WIDGET (calendar);
2612   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2613   cairo_t *cr;
2614   GdkColor *text_color;
2615   gchar *detail;
2616   gchar buffer[32];
2617   gint day;
2618   gint x_loc, y_loc;
2619   GdkRectangle day_rect;
2620
2621   PangoLayout *layout;
2622   PangoRectangle logical_rect;
2623   gboolean overflow = FALSE;
2624   gboolean show_details;
2625
2626   g_return_if_fail (row < 6);
2627   g_return_if_fail (col < 7);
2628
2629   cr = gdk_cairo_create (priv->main_win);
2630
2631   day = priv->day[row][col];
2632   show_details = (priv->display_flags & GTK_CALENDAR_SHOW_DETAILS);
2633
2634   calendar_day_rectangle (calendar, row, col, &day_rect);
2635   
2636   if (priv->day_month[row][col] == MONTH_PREV)
2637     {
2638       text_color = PREV_MONTH_COLOR (widget);
2639     } 
2640   else if (priv->day_month[row][col] == MONTH_NEXT)
2641     {
2642       text_color =  NEXT_MONTH_COLOR (widget);
2643     } 
2644   else 
2645     {
2646 #if 0      
2647       if (priv->highlight_row == row && priv->highlight_col == col)
2648         {
2649           cairo_set_source_color (cr, HIGHLIGHT_BG_COLOR (widget));
2650           gdk_cairo_rectangle (cr, &day_rect);
2651           cairo_fill (cr);
2652         }
2653 #endif     
2654       if (priv->selected_day == day)
2655         {
2656           gdk_cairo_set_source_color (cr, SELECTED_BG_COLOR (widget));
2657           gdk_cairo_rectangle (cr, &day_rect);
2658           cairo_fill (cr);
2659         }
2660       if (priv->selected_day == day)
2661         text_color = SELECTED_FG_COLOR (widget);
2662       else if (priv->marked_date[day-1])
2663         text_color = MARKED_COLOR (widget);
2664       else
2665         text_color = NORMAL_DAY_COLOR (widget);
2666     }
2667
2668   /* Translators: this defines whether the day numbers should use
2669    * localized digits or the ones used in English (0123...).
2670    *
2671    * Translate to "%Id" if you want to use localized digits, or
2672    * translate to "%d" otherwise.
2673    *
2674    * Note that translating this doesn't guarantee that you get localized
2675    * digits. That needs support from your system and locale definition
2676    * too.
2677    */
2678   g_snprintf (buffer, sizeof (buffer), C_("calendar:day:digits", "%d"), day);
2679
2680   /* Get extra information to show, if any: */
2681
2682   detail = gtk_calendar_get_detail (calendar, row, col);
2683
2684   layout = gtk_widget_create_pango_layout (widget, buffer);
2685   pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
2686   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
2687   
2688   x_loc = day_rect.x + (day_rect.width - logical_rect.width) / 2;
2689   y_loc = day_rect.y;
2690
2691   gdk_cairo_set_source_color (cr, text_color);
2692   cairo_move_to (cr, x_loc, y_loc);
2693   pango_cairo_show_layout (cr, layout);
2694
2695   if (priv->day_month[row][col] == MONTH_CURRENT &&
2696      (priv->marked_date[day-1] || (detail && !show_details)))
2697     {
2698       cairo_move_to (cr, x_loc - 1, y_loc);
2699       pango_cairo_show_layout (cr, layout);
2700     }
2701
2702   y_loc += priv->max_day_char_descent;
2703
2704   if (priv->detail_func && show_details)
2705     {
2706       cairo_save (cr);
2707
2708       if (priv->selected_day == day)
2709         gdk_cairo_set_source_color (cr, &widget->style->text[GTK_STATE_ACTIVE]);
2710       else if (priv->day_month[row][col] == MONTH_CURRENT)
2711         gdk_cairo_set_source_color (cr, &widget->style->base[GTK_STATE_ACTIVE]);
2712       else
2713         gdk_cairo_set_source_color (cr, &widget->style->base[GTK_STATE_INSENSITIVE]);
2714
2715       cairo_set_line_width (cr, 1);
2716       cairo_move_to (cr, day_rect.x + 2, y_loc + 0.5);
2717       cairo_line_to (cr, day_rect.x + day_rect.width - 2, y_loc + 0.5);
2718       cairo_stroke (cr);
2719
2720       cairo_restore (cr);
2721
2722       y_loc += 2;
2723     }
2724
2725   if (detail && show_details)
2726     {
2727       gchar *markup = g_strconcat ("<small>", detail, "</small>", NULL);
2728       pango_layout_set_markup (layout, markup, -1);
2729       g_free (markup);
2730
2731       if (day == priv->selected_day)
2732         {
2733           /* Stripping colors as they conflict with selection marking. */
2734
2735           PangoAttrList *attrs = pango_layout_get_attributes (layout);
2736           PangoAttrList *colors = NULL;
2737
2738           if (attrs)
2739             colors = pango_attr_list_filter (attrs, is_color_attribute, NULL);
2740           if (colors)
2741             pango_attr_list_unref (colors);
2742         }
2743
2744       pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
2745       pango_layout_set_width (layout, PANGO_SCALE * day_rect.width);
2746
2747       if (priv->detail_height_rows)
2748         {
2749           gint dy = day_rect.height - (y_loc - day_rect.y);
2750           pango_layout_set_height (layout, PANGO_SCALE * dy);
2751           pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
2752         }
2753
2754       cairo_move_to (cr, day_rect.x, y_loc);
2755       pango_cairo_show_layout (cr, layout);
2756     }
2757
2758   if (gtk_widget_has_focus (widget)
2759       && priv->focus_row == row && priv->focus_col == col)
2760     {
2761       GtkStateType state;
2762
2763       if (priv->selected_day == day)
2764         state = gtk_widget_has_focus (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE;
2765       else
2766         state = GTK_STATE_NORMAL;
2767       
2768       gtk_paint_focus (widget->style, 
2769                        priv->main_win,
2770                        state,
2771                        NULL, widget, "calendar-day",
2772                        day_rect.x,     day_rect.y, 
2773                        day_rect.width, day_rect.height);
2774     }
2775
2776   if (overflow)
2777     priv->detail_overflow[row] |= (1 << col);
2778   else
2779     priv->detail_overflow[row] &= ~(1 << col);
2780
2781   g_object_unref (layout);
2782   cairo_destroy (cr);
2783   g_free (detail);
2784 }
2785
2786 static void
2787 calendar_paint_main (GtkCalendar *calendar)
2788 {
2789   gint row, col;
2790   
2791   for (col = 0; col < 7; col++)
2792     for (row = 0; row < 6; row++)
2793       calendar_paint_day (calendar, row, col);
2794 }
2795
2796 static void
2797 calendar_invalidate_arrow (GtkCalendar *calendar,
2798                            guint        arrow)
2799 {
2800   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2801   GdkWindow *window;
2802   
2803   window = priv->arrow_win[arrow];
2804   if (window)
2805     gdk_window_invalidate_rect (window, NULL, FALSE);
2806 }
2807
2808 static void
2809 calendar_paint_arrow (GtkCalendar *calendar,
2810                       guint            arrow)
2811 {
2812   GtkWidget *widget = GTK_WIDGET (calendar);
2813   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2814   GdkWindow *window;
2815   
2816   window = priv->arrow_win[arrow];
2817   if (window)
2818     {
2819       cairo_t *cr = gdk_cairo_create (window);
2820       gint width, height;
2821       gint state;
2822         
2823       state = priv->arrow_state[arrow];
2824
2825       gdk_cairo_set_source_color (cr, &widget->style->bg[state]);
2826       cairo_paint (cr);
2827       cairo_destroy (cr);
2828       
2829       gdk_drawable_get_size (window, &width, &height);
2830       if (arrow == ARROW_MONTH_LEFT || arrow == ARROW_YEAR_LEFT)
2831         gtk_paint_arrow (widget->style, window, state, 
2832                          GTK_SHADOW_OUT, NULL, widget, "calendar",
2833                          GTK_ARROW_LEFT, TRUE, 
2834                          width/2 - 3, height/2 - 4, 8, 8);
2835       else 
2836         gtk_paint_arrow (widget->style, window, state, 
2837                          GTK_SHADOW_OUT, NULL, widget, "calendar",
2838                          GTK_ARROW_RIGHT, TRUE, 
2839                          width/2 - 4, height/2 - 4, 8, 8);
2840     }
2841 }
2842
2843 static gboolean
2844 gtk_calendar_expose (GtkWidget      *widget,
2845                      GdkEventExpose *event)
2846 {
2847   GtkCalendar *calendar = GTK_CALENDAR (widget);
2848   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
2849   int i;
2850
2851   if (gtk_widget_is_drawable (widget))
2852     {
2853       if (event->window == priv->main_win)
2854         calendar_paint_main (calendar);
2855       
2856       if (event->window == priv->header_win)
2857         calendar_paint_header (calendar);
2858
2859       for (i = 0; i < 4; i++)
2860         if (event->window == priv->arrow_win[i])
2861           calendar_paint_arrow (calendar, i);
2862       
2863       if (event->window == priv->day_name_win)
2864         calendar_paint_day_names (calendar);
2865       
2866       if (event->window == priv->week_win)
2867         calendar_paint_week_numbers (calendar);
2868       if (event->window == widget->window)
2869         {
2870           gtk_paint_shadow (widget->style, widget->window, gtk_widget_get_state (widget),
2871                             GTK_SHADOW_IN, NULL, widget, "calendar",
2872                             0, 0, widget->allocation.width, widget->allocation.height);
2873         }
2874     }
2875   
2876   return FALSE;
2877 }
2878
2879 \f
2880 /****************************************
2881  *           Mouse handling             *
2882  ****************************************/
2883
2884 static void
2885 calendar_arrow_action (GtkCalendar *calendar,
2886                        guint        arrow)
2887 {
2888   switch (arrow)
2889     {
2890     case ARROW_YEAR_LEFT:
2891       calendar_set_year_prev (calendar);
2892       break;
2893     case ARROW_YEAR_RIGHT:
2894       calendar_set_year_next (calendar);
2895       break;
2896     case ARROW_MONTH_LEFT:
2897       calendar_set_month_prev (calendar);
2898       break;
2899     case ARROW_MONTH_RIGHT:
2900       calendar_set_month_next (calendar);
2901       break;
2902     default:;
2903       /* do nothing */
2904     }
2905 }
2906
2907 static gboolean
2908 calendar_timer (gpointer data)
2909 {
2910   GtkCalendar *calendar = data;
2911   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2912   gboolean retval = FALSE;
2913   
2914   if (priv->timer)
2915     {
2916       calendar_arrow_action (calendar, priv->click_child);
2917
2918       if (priv->need_timer)
2919         {
2920           GtkSettings *settings;
2921           guint        timeout;
2922
2923           settings = gtk_widget_get_settings (GTK_WIDGET (calendar));
2924           g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
2925
2926           priv->need_timer = FALSE;
2927           priv->timer = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE,
2928                                             timeout * SCROLL_DELAY_FACTOR,
2929                                             (GSourceFunc) calendar_timer,
2930                                             (gpointer) calendar, NULL);
2931         }
2932       else 
2933         retval = TRUE;
2934     }
2935
2936   return retval;
2937 }
2938
2939 static void
2940 calendar_start_spinning (GtkCalendar *calendar,
2941                          gint         click_child)
2942 {
2943   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2944
2945   priv->click_child = click_child;
2946   
2947   if (!priv->timer)
2948     {
2949       GtkSettings *settings;
2950       guint        timeout;
2951
2952       settings = gtk_widget_get_settings (GTK_WIDGET (calendar));
2953       g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
2954
2955       priv->need_timer = TRUE;
2956       priv->timer = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT_IDLE,
2957                                         timeout,
2958                                         (GSourceFunc) calendar_timer,
2959                                         (gpointer) calendar, NULL);
2960     }
2961 }
2962
2963 static void
2964 calendar_stop_spinning (GtkCalendar *calendar)
2965 {
2966   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2967
2968   if (priv->timer)
2969     {
2970       g_source_remove (priv->timer);
2971       priv->timer = 0;
2972       priv->need_timer = FALSE;
2973     }
2974 }
2975
2976 static void
2977 calendar_main_button_press (GtkCalendar    *calendar,
2978                             GdkEventButton *event)
2979 {
2980   GtkWidget *widget = GTK_WIDGET (calendar);
2981   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
2982   gint x, y;
2983   gint row, col;
2984   gint day_month;
2985   gint day;
2986   
2987   x = (gint) (event->x);
2988   y = (gint) (event->y);
2989   
2990   row = calendar_row_from_y (calendar, y);
2991   col = calendar_column_from_x (calendar, x);
2992
2993   /* If row or column isn't found, just return. */
2994   if (row == -1 || col == -1)
2995     return;
2996   
2997   day_month = priv->day_month[row][col];
2998
2999   if (event->type == GDK_BUTTON_PRESS)
3000     {
3001       day = priv->day[row][col];
3002       
3003       if (day_month == MONTH_PREV)
3004         calendar_set_month_prev (calendar);
3005       else if (day_month == MONTH_NEXT)
3006         calendar_set_month_next (calendar);
3007       
3008       if (!gtk_widget_has_focus (widget))
3009         gtk_widget_grab_focus (widget);
3010           
3011       if (event->button == 1) 
3012         {
3013           priv->in_drag = 1;
3014           priv->drag_start_x = x;
3015           priv->drag_start_y = y;
3016         }
3017
3018       calendar_select_and_focus_day (calendar, day);
3019     }
3020   else if (event->type == GDK_2BUTTON_PRESS)
3021     {
3022       priv->in_drag = 0;
3023       if (day_month == MONTH_CURRENT)
3024         g_signal_emit (calendar,
3025                        gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL],
3026                        0);
3027     }
3028 }
3029
3030 static gboolean
3031 gtk_calendar_button_press (GtkWidget      *widget,
3032                            GdkEventButton *event)
3033 {
3034   GtkCalendar *calendar = GTK_CALENDAR (widget);
3035   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3036   gint arrow = -1;
3037   
3038   if (event->window == priv->main_win)
3039     calendar_main_button_press (calendar, event);
3040
3041   if (!gtk_widget_has_focus (widget))
3042     gtk_widget_grab_focus (widget);
3043
3044   for (arrow = ARROW_YEAR_LEFT; arrow <= ARROW_MONTH_RIGHT; arrow++)
3045     {
3046       if (event->window == priv->arrow_win[arrow])
3047         {
3048           
3049           /* only call the action on single click, not double */
3050           if (event->type == GDK_BUTTON_PRESS)
3051             {
3052               if (event->button == 1)
3053                 calendar_start_spinning (calendar, arrow);
3054
3055               calendar_arrow_action (calendar, arrow);        
3056             }
3057
3058           return TRUE;
3059         }
3060     }
3061
3062   return FALSE;
3063 }
3064
3065 static gboolean
3066 gtk_calendar_button_release (GtkWidget    *widget,
3067                              GdkEventButton *event)
3068 {
3069   GtkCalendar *calendar = GTK_CALENDAR (widget);
3070   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3071
3072   if (event->button == 1) 
3073     {
3074       calendar_stop_spinning (calendar);
3075
3076       if (priv->in_drag)
3077         priv->in_drag = 0;
3078     }
3079
3080   return TRUE;
3081 }
3082
3083 static gboolean
3084 gtk_calendar_motion_notify (GtkWidget      *widget,
3085                             GdkEventMotion *event)
3086 {
3087   GtkCalendar *calendar = GTK_CALENDAR (widget);
3088   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3089   gint event_x, event_y;
3090   gint row, col;
3091   gint old_row, old_col;
3092   
3093   event_x = (gint) (event->x);
3094   event_y = (gint) (event->y);
3095   
3096   if (event->window == priv->main_win)
3097     {
3098       
3099       if (priv->in_drag) 
3100         {
3101           if (gtk_drag_check_threshold (widget,
3102                                         priv->drag_start_x, priv->drag_start_y,
3103                                         event->x, event->y))
3104             {
3105               GdkDragContext *context;
3106               GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
3107               gtk_target_list_add_text_targets (target_list, 0);
3108               context = gtk_drag_begin (widget, target_list, GDK_ACTION_COPY,
3109                                         1, (GdkEvent *)event);
3110
3111           
3112               priv->in_drag = 0;
3113               
3114               gtk_target_list_unref (target_list);
3115               gtk_drag_set_icon_default (context);
3116             }
3117         }
3118       else 
3119         {
3120           row = calendar_row_from_y (calendar, event_y);
3121           col = calendar_column_from_x (calendar, event_x);
3122           
3123           if (row != priv->highlight_row || priv->highlight_col != col)
3124             {
3125               old_row = priv->highlight_row;
3126               old_col = priv->highlight_col;
3127               if (old_row > -1 && old_col > -1)
3128                 {
3129                   priv->highlight_row = -1;
3130                   priv->highlight_col = -1;
3131                   calendar_invalidate_day (calendar, old_row, old_col);
3132                 }
3133               
3134               priv->highlight_row = row;
3135               priv->highlight_col = col;
3136               
3137               if (row > -1 && col > -1)
3138                 calendar_invalidate_day (calendar, row, col);
3139             }
3140         }
3141     }
3142   return TRUE;
3143 }
3144
3145 static gboolean
3146 gtk_calendar_enter_notify (GtkWidget        *widget,
3147                            GdkEventCrossing *event)
3148 {
3149   GtkCalendar *calendar = GTK_CALENDAR (widget);
3150   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3151   
3152   if (event->window == priv->arrow_win[ARROW_MONTH_LEFT])
3153     {
3154       priv->arrow_state[ARROW_MONTH_LEFT] = GTK_STATE_PRELIGHT;
3155       calendar_invalidate_arrow (calendar, ARROW_MONTH_LEFT);
3156     }
3157   
3158   if (event->window == priv->arrow_win[ARROW_MONTH_RIGHT])
3159     {
3160       priv->arrow_state[ARROW_MONTH_RIGHT] = GTK_STATE_PRELIGHT;
3161       calendar_invalidate_arrow (calendar, ARROW_MONTH_RIGHT);
3162     }
3163   
3164   if (event->window == priv->arrow_win[ARROW_YEAR_LEFT])
3165     {
3166       priv->arrow_state[ARROW_YEAR_LEFT] = GTK_STATE_PRELIGHT;
3167       calendar_invalidate_arrow (calendar, ARROW_YEAR_LEFT);
3168     }
3169   
3170   if (event->window == priv->arrow_win[ARROW_YEAR_RIGHT])
3171     {
3172       priv->arrow_state[ARROW_YEAR_RIGHT] = GTK_STATE_PRELIGHT;
3173       calendar_invalidate_arrow (calendar, ARROW_YEAR_RIGHT);
3174     }
3175   
3176   return TRUE;
3177 }
3178
3179 static gboolean
3180 gtk_calendar_leave_notify (GtkWidget        *widget,
3181                            GdkEventCrossing *event)
3182 {
3183   GtkCalendar *calendar = GTK_CALENDAR (widget);
3184   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3185   gint row;
3186   gint col;
3187   
3188   if (event->window == priv->main_win)
3189     {
3190       row = priv->highlight_row;
3191       col = priv->highlight_col;
3192       priv->highlight_row = -1;
3193       priv->highlight_col = -1;
3194       if (row > -1 && col > -1)
3195         calendar_invalidate_day (calendar, row, col);
3196     }
3197   
3198   if (event->window == priv->arrow_win[ARROW_MONTH_LEFT])
3199     {
3200       priv->arrow_state[ARROW_MONTH_LEFT] = GTK_STATE_NORMAL;
3201       calendar_invalidate_arrow (calendar, ARROW_MONTH_LEFT);
3202     }
3203   
3204   if (event->window == priv->arrow_win[ARROW_MONTH_RIGHT])
3205     {
3206       priv->arrow_state[ARROW_MONTH_RIGHT] = GTK_STATE_NORMAL;
3207       calendar_invalidate_arrow (calendar, ARROW_MONTH_RIGHT);
3208     }
3209   
3210   if (event->window == priv->arrow_win[ARROW_YEAR_LEFT])
3211     {
3212       priv->arrow_state[ARROW_YEAR_LEFT] = GTK_STATE_NORMAL;
3213       calendar_invalidate_arrow (calendar, ARROW_YEAR_LEFT);
3214     }
3215   
3216   if (event->window == priv->arrow_win[ARROW_YEAR_RIGHT])
3217     {
3218       priv->arrow_state[ARROW_YEAR_RIGHT] = GTK_STATE_NORMAL;
3219       calendar_invalidate_arrow (calendar, ARROW_YEAR_RIGHT);
3220     }
3221   
3222   return TRUE;
3223 }
3224
3225 static gboolean
3226 gtk_calendar_scroll (GtkWidget      *widget,
3227                      GdkEventScroll *event)
3228 {
3229   GtkCalendar *calendar = GTK_CALENDAR (widget);
3230
3231   if (event->direction == GDK_SCROLL_UP) 
3232     {
3233       if (!gtk_widget_has_focus (widget))
3234         gtk_widget_grab_focus (widget);
3235       calendar_set_month_prev (calendar);
3236     }
3237   else if (event->direction == GDK_SCROLL_DOWN) 
3238     {
3239       if (!gtk_widget_has_focus (widget))
3240         gtk_widget_grab_focus (widget);
3241       calendar_set_month_next (calendar);
3242     }
3243   else
3244     return FALSE;
3245
3246   return TRUE;
3247 }
3248
3249 \f
3250 /****************************************
3251  *             Key handling              *
3252  ****************************************/
3253
3254 static void 
3255 move_focus (GtkCalendar *calendar, 
3256             gint         direction)
3257 {
3258   GtkCalendarPrivate *priv = calendar->priv;
3259   GtkTextDirection text_dir = gtk_widget_get_direction (GTK_WIDGET (calendar));
3260  
3261   if ((text_dir == GTK_TEXT_DIR_LTR && direction == -1) ||
3262       (text_dir == GTK_TEXT_DIR_RTL && direction == 1)) 
3263     {
3264       if (priv->focus_col > 0)
3265           priv->focus_col--;
3266       else if (priv->focus_row > 0)
3267         {
3268           priv->focus_col = 6;
3269           priv->focus_row--;
3270         }
3271
3272       if (priv->focus_col < 0)
3273         priv->focus_col = 6;
3274       if (priv->focus_row < 0)
3275         priv->focus_row = 5;
3276     }
3277   else 
3278     {
3279       if (priv->focus_col < 6)
3280         priv->focus_col++;
3281       else if (priv->focus_row < 5)
3282         {
3283           priv->focus_col = 0;
3284           priv->focus_row++;
3285         }
3286
3287       if (priv->focus_col < 0)
3288         priv->focus_col = 0;
3289       if (priv->focus_row < 0)
3290         priv->focus_row = 0;
3291     }
3292 }
3293
3294 static gboolean
3295 gtk_calendar_key_press (GtkWidget   *widget,
3296                         GdkEventKey *event)
3297 {
3298   GtkCalendar *calendar = GTK_CALENDAR (widget);
3299   GtkCalendarPrivate *priv = calendar->priv;
3300   gint return_val;
3301   gint old_focus_row;
3302   gint old_focus_col;
3303   gint row, col, day;
3304
3305   return_val = FALSE;
3306
3307   old_focus_row = priv->focus_row;
3308   old_focus_col = priv->focus_col;
3309
3310   switch (event->keyval)
3311     {
3312     case GDK_KP_Left:
3313     case GDK_Left:
3314       return_val = TRUE;
3315       if (event->state & GDK_CONTROL_MASK)
3316         calendar_set_month_prev (calendar);
3317       else
3318         {
3319           move_focus (calendar, -1);
3320           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3321           calendar_invalidate_day (calendar, priv->focus_row,
3322                                    priv->focus_col);
3323         }
3324       break;
3325     case GDK_KP_Right:
3326     case GDK_Right:
3327       return_val = TRUE;
3328       if (event->state & GDK_CONTROL_MASK)
3329         calendar_set_month_next (calendar);
3330       else
3331         {
3332           move_focus (calendar, 1);
3333           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3334           calendar_invalidate_day (calendar, priv->focus_row,
3335                                    priv->focus_col);
3336         }
3337       break;
3338     case GDK_KP_Up:
3339     case GDK_Up:
3340       return_val = TRUE;
3341       if (event->state & GDK_CONTROL_MASK)
3342         calendar_set_year_prev (calendar);
3343       else
3344         {
3345           if (priv->focus_row > 0)
3346             priv->focus_row--;
3347           if (priv->focus_row < 0)
3348             priv->focus_row = 5;
3349           if (priv->focus_col < 0)
3350             priv->focus_col = 6;
3351           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3352           calendar_invalidate_day (calendar, priv->focus_row,
3353                                    priv->focus_col);
3354         }
3355       break;
3356     case GDK_KP_Down:
3357     case GDK_Down:
3358       return_val = TRUE;
3359       if (event->state & GDK_CONTROL_MASK)
3360         calendar_set_year_next (calendar);
3361       else
3362         {
3363           if (priv->focus_row < 5)
3364             priv->focus_row++;
3365           if (priv->focus_col < 0)
3366             priv->focus_col = 0;
3367           calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
3368           calendar_invalidate_day (calendar, priv->focus_row,
3369                                    priv->focus_col);
3370         }
3371       break;
3372     case GDK_KP_Space:
3373     case GDK_space:
3374       row = priv->focus_row;
3375       col = priv->focus_col;
3376       
3377       if (row > -1 && col > -1)
3378         {
3379           return_val = TRUE;
3380
3381           day = priv->day[row][col];
3382           if (priv->day_month[row][col] == MONTH_PREV)
3383             calendar_set_month_prev (calendar);
3384           else if (priv->day_month[row][col] == MONTH_NEXT)
3385             calendar_set_month_next (calendar);
3386
3387           calendar_select_and_focus_day (calendar, day);
3388         }
3389     }   
3390   
3391   return return_val;
3392 }
3393
3394 \f
3395 /****************************************
3396  *           Misc widget methods        *
3397  ****************************************/
3398
3399 static void
3400 calendar_set_background (GtkWidget *widget)
3401 {
3402   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3403   gint i;
3404   
3405   if (gtk_widget_get_realized (widget))
3406     {
3407       for (i = 0; i < 4; i++)
3408         {
3409           if (priv->arrow_win[i])
3410             gdk_window_set_background (priv->arrow_win[i], 
3411                                        HEADER_BG_COLOR (widget));
3412         }
3413       if (priv->header_win)
3414         gdk_window_set_background (priv->header_win, 
3415                                    HEADER_BG_COLOR (widget));
3416       if (priv->day_name_win)
3417         gdk_window_set_background (priv->day_name_win, 
3418                                    BACKGROUND_COLOR (widget));
3419       if (priv->week_win)
3420         gdk_window_set_background (priv->week_win,
3421                                    BACKGROUND_COLOR (widget));
3422       if (priv->main_win)
3423         gdk_window_set_background (priv->main_win,
3424                                    BACKGROUND_COLOR (widget));
3425       if (widget->window)
3426         gdk_window_set_background (widget->window,
3427                                    BACKGROUND_COLOR (widget)); 
3428     }
3429 }
3430
3431 static void
3432 gtk_calendar_style_set (GtkWidget *widget,
3433                         GtkStyle  *previous_style)
3434 {
3435   if (previous_style && gtk_widget_get_realized (widget))
3436     calendar_set_background (widget);
3437 }
3438
3439 static void
3440 gtk_calendar_state_changed (GtkWidget      *widget,
3441                             GtkStateType    previous_state)
3442 {
3443   GtkCalendar *calendar = GTK_CALENDAR (widget);
3444   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3445   int i;
3446   
3447   if (!gtk_widget_is_sensitive (widget))
3448     {
3449       priv->in_drag = 0;
3450       calendar_stop_spinning (calendar);    
3451     }
3452
3453   for (i = 0; i < 4; i++)
3454     if (gtk_widget_is_sensitive (widget))
3455       priv->arrow_state[i] = GTK_STATE_NORMAL;
3456     else 
3457       priv->arrow_state[i] = GTK_STATE_INSENSITIVE;
3458   
3459   calendar_set_background (widget);
3460 }
3461
3462 static void
3463 gtk_calendar_grab_notify (GtkWidget *widget,
3464                           gboolean   was_grabbed)
3465 {
3466   if (!was_grabbed)
3467     calendar_stop_spinning (GTK_CALENDAR (widget));
3468 }
3469
3470 static gboolean
3471 gtk_calendar_focus_out (GtkWidget     *widget,
3472                         GdkEventFocus *event)
3473 {
3474   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3475   GtkCalendar *calendar = GTK_CALENDAR (widget);
3476
3477   calendar_queue_refresh (calendar);
3478   calendar_stop_spinning (calendar);
3479   
3480   priv->in_drag = 0; 
3481
3482   return FALSE;
3483 }
3484
3485 \f
3486 /****************************************
3487  *          Drag and Drop               *
3488  ****************************************/
3489
3490 static void
3491 gtk_calendar_drag_data_get (GtkWidget        *widget,
3492                             GdkDragContext   *context,
3493                             GtkSelectionData *selection_data,
3494                             guint             info,
3495                             guint             time)
3496 {
3497   GtkCalendar *calendar = GTK_CALENDAR (widget);
3498   GtkCalendarPrivate *priv = calendar->priv;
3499   GDate *date;
3500   gchar str[128];
3501   gsize len;
3502
3503   date = g_date_new_dmy (priv->selected_day, priv->month + 1, priv->year);
3504   len = g_date_strftime (str, 127, "%x", date);
3505   gtk_selection_data_set_text (selection_data, str, len);
3506   
3507   g_free (date);
3508 }
3509
3510 /* Get/set whether drag_motion requested the drag data and
3511  * drag_data_received should thus not actually insert the data,
3512  * since the data doesn't result from a drop.
3513  */
3514 static void
3515 set_status_pending (GdkDragContext *context,
3516                     GdkDragAction   suggested_action)
3517 {
3518   g_object_set_data (G_OBJECT (context),
3519                      I_("gtk-calendar-status-pending"),
3520                      GINT_TO_POINTER (suggested_action));
3521 }
3522
3523 static GdkDragAction
3524 get_status_pending (GdkDragContext *context)
3525 {
3526   return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (context),
3527                                              "gtk-calendar-status-pending"));
3528 }
3529
3530 static void
3531 gtk_calendar_drag_leave (GtkWidget      *widget,
3532                          GdkDragContext *context,
3533                          guint           time)
3534 {
3535   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3536
3537   priv->drag_highlight = 0;
3538   gtk_drag_unhighlight (widget);
3539   
3540 }
3541
3542 static gboolean
3543 gtk_calendar_drag_motion (GtkWidget      *widget,
3544                           GdkDragContext *context,
3545                           gint            x,
3546                           gint            y,
3547                           guint           time)
3548 {
3549   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
3550   GdkAtom target;
3551   
3552   if (!priv->drag_highlight) 
3553     {
3554       priv->drag_highlight = 1;
3555       gtk_drag_highlight (widget);
3556     }
3557   
3558   target = gtk_drag_dest_find_target (widget, context, NULL);
3559   if (target == GDK_NONE || context->suggested_action == 0)
3560     gdk_drag_status (context, 0, time);
3561   else
3562     {
3563       set_status_pending (context, context->suggested_action);
3564       gtk_drag_get_data (widget, context, target, time);
3565     }
3566   
3567   return TRUE;
3568 }
3569
3570 static gboolean
3571 gtk_calendar_drag_drop (GtkWidget      *widget,
3572                         GdkDragContext *context,
3573                         gint            x,
3574                         gint            y,
3575                         guint           time)
3576 {
3577   GdkAtom target;
3578
3579   target = gtk_drag_dest_find_target (widget, context, NULL);  
3580   if (target != GDK_NONE)
3581     {
3582       gtk_drag_get_data (widget, context, 
3583                          target, 
3584                          time);
3585       return TRUE;
3586     }
3587
3588   return FALSE;
3589 }
3590
3591 static void
3592 gtk_calendar_drag_data_received (GtkWidget        *widget,
3593                                  GdkDragContext   *context,
3594                                  gint              x,
3595                                  gint              y,
3596                                  GtkSelectionData *selection_data,
3597                                  guint             info,
3598                                  guint             time)
3599 {
3600   GtkCalendar *calendar = GTK_CALENDAR (widget);
3601   GtkCalendarPrivate *priv = calendar->priv;
3602   guint day, month, year;
3603   gchar *str;
3604   GDate *date;
3605   GdkDragAction suggested_action;
3606
3607   suggested_action = get_status_pending (context);
3608
3609   if (suggested_action) 
3610     {
3611       set_status_pending (context, 0);
3612      
3613       /* We are getting this data due to a request in drag_motion,
3614        * rather than due to a request in drag_drop, so we are just
3615        * supposed to call drag_status, not actually paste in the
3616        * data.
3617        */
3618       str = (gchar*) gtk_selection_data_get_text (selection_data);
3619
3620       if (str) 
3621         {
3622           date = g_date_new ();
3623           g_date_set_parse (date, str);
3624           if (!g_date_valid (date)) 
3625               suggested_action = 0;
3626           g_date_free (date);
3627           g_free (str);
3628         }
3629       else
3630         suggested_action = 0;
3631
3632       gdk_drag_status (context, suggested_action, time);
3633
3634       return;
3635     }
3636
3637   date = g_date_new ();
3638   str = (gchar*) gtk_selection_data_get_text (selection_data);
3639   if (str) 
3640     {
3641       g_date_set_parse (date, str);
3642       g_free (str);
3643     }
3644   
3645   if (!g_date_valid (date)) 
3646     {
3647       g_warning ("Received invalid date data\n");
3648       g_date_free (date);       
3649       gtk_drag_finish (context, FALSE, FALSE, time);
3650       return;
3651     }
3652
3653   day = g_date_get_day (date);
3654   month = g_date_get_month (date);
3655   year = g_date_get_year (date);
3656   g_date_free (date);   
3657
3658   gtk_drag_finish (context, TRUE, FALSE, time);
3659
3660   
3661   g_object_freeze_notify (G_OBJECT (calendar));
3662   if (!(priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
3663       && (priv->display_flags & GTK_CALENDAR_SHOW_HEADING))
3664     gtk_calendar_select_month (calendar, month - 1, year);
3665   gtk_calendar_select_day (calendar, day);
3666   g_object_thaw_notify (G_OBJECT (calendar));  
3667 }
3668
3669 \f
3670 /****************************************
3671  *              Public API              *
3672  ****************************************/
3673
3674 /**
3675  * gtk_calendar_new:
3676  * 
3677  * Creates a new calendar, with the current date being selected. 
3678  * 
3679  * Return value: a newly #GtkCalendar widget
3680  **/
3681 GtkWidget*
3682 gtk_calendar_new (void)
3683 {
3684   return g_object_new (GTK_TYPE_CALENDAR, NULL);
3685 }
3686
3687 /**
3688  * gtk_calendar_get_display_options:
3689  * @calendar: a #GtkCalendar
3690  * 
3691  * Returns the current display options of @calendar. 
3692  * 
3693  * Return value: the display options.
3694  *
3695  * Since: 2.4
3696  **/
3697 GtkCalendarDisplayOptions 
3698 gtk_calendar_get_display_options (GtkCalendar         *calendar)
3699 {
3700   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
3701
3702   return calendar->priv->display_flags;
3703 }
3704
3705 /**
3706  * gtk_calendar_set_display_options:
3707  * @calendar: a #GtkCalendar
3708  * @flags: the display options to set
3709  * 
3710  * Sets display options (whether to display the heading and the month  
3711  * headings).
3712  *
3713  * Since: 2.4
3714  **/
3715 void
3716 gtk_calendar_set_display_options (GtkCalendar          *calendar,
3717                                   GtkCalendarDisplayOptions flags)
3718 {
3719   GtkWidget *widget = GTK_WIDGET (calendar);
3720   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
3721   gint resize = 0;
3722   gint i;
3723   GtkCalendarDisplayOptions old_flags;
3724   
3725   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3726   
3727   old_flags = priv->display_flags;
3728   
3729   if (gtk_widget_get_realized (widget))
3730     {
3731       if ((flags ^ priv->display_flags) & GTK_CALENDAR_NO_MONTH_CHANGE)
3732         {
3733           resize ++;
3734           if (! (flags & GTK_CALENDAR_NO_MONTH_CHANGE)
3735               && (priv->header_win))
3736             {
3737               priv->display_flags &= ~GTK_CALENDAR_NO_MONTH_CHANGE;
3738               calendar_realize_arrows (calendar);
3739             }
3740           else
3741             {
3742               for (i = 0; i < 4; i++)
3743                 {
3744                   if (priv->arrow_win[i])
3745                     {
3746                       gdk_window_set_user_data (priv->arrow_win[i], 
3747                                                 NULL);
3748                       gdk_window_destroy (priv->arrow_win[i]);
3749                       priv->arrow_win[i] = NULL;
3750                     }
3751                 }
3752             }
3753         }
3754       
3755       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_HEADING)
3756         {
3757           resize++;
3758           
3759           if (flags & GTK_CALENDAR_SHOW_HEADING)
3760             {
3761               priv->display_flags |= GTK_CALENDAR_SHOW_HEADING;
3762               calendar_realize_header (calendar);
3763             }
3764           else
3765             {
3766               for (i = 0; i < 4; i++)
3767                 {
3768                   if (priv->arrow_win[i])
3769                     {
3770                       gdk_window_set_user_data (priv->arrow_win[i], 
3771                                                 NULL);
3772                       gdk_window_destroy (priv->arrow_win[i]);
3773                       priv->arrow_win[i] = NULL;
3774                     }
3775                 }
3776               gdk_window_set_user_data (priv->header_win, NULL);
3777               gdk_window_destroy (priv->header_win);
3778               priv->header_win = NULL;
3779             }
3780         }
3781       
3782       
3783       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DAY_NAMES)
3784         {
3785           resize++;
3786           
3787           if (flags & GTK_CALENDAR_SHOW_DAY_NAMES)
3788             {
3789               priv->display_flags |= GTK_CALENDAR_SHOW_DAY_NAMES;
3790               calendar_realize_day_names (calendar);
3791             }
3792           else
3793             {
3794               gdk_window_set_user_data (priv->day_name_win, NULL);
3795               gdk_window_destroy (priv->day_name_win);
3796               priv->day_name_win = NULL;
3797             }
3798         }
3799       
3800       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3801         {
3802           resize++;
3803           
3804           if (flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3805             {
3806               priv->display_flags |= GTK_CALENDAR_SHOW_WEEK_NUMBERS;
3807               calendar_realize_week_numbers (calendar);
3808             }
3809           else
3810             {
3811               gdk_window_set_user_data (priv->week_win, NULL);
3812               gdk_window_destroy (priv->week_win);
3813               priv->week_win = NULL;
3814             }
3815         }
3816
3817       if ((flags ^ priv->display_flags) & GTK_CALENDAR_WEEK_START_MONDAY)
3818         g_warning ("GTK_CALENDAR_WEEK_START_MONDAY is ignored; the first day of the week is determined from the locale");
3819       
3820       if ((flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DETAILS)
3821         resize++;
3822
3823       priv->display_flags = flags;
3824       if (resize)
3825         gtk_widget_queue_resize (GTK_WIDGET (calendar));
3826       
3827     } 
3828   else
3829     priv->display_flags = flags;
3830   
3831   g_object_freeze_notify (G_OBJECT (calendar));
3832   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_HEADING)
3833     g_object_notify (G_OBJECT (calendar), "show-heading");
3834   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_DAY_NAMES)
3835     g_object_notify (G_OBJECT (calendar), "show-day-names");
3836   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_NO_MONTH_CHANGE)
3837     g_object_notify (G_OBJECT (calendar), "no-month-change");
3838   if ((old_flags ^ priv->display_flags) & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
3839     g_object_notify (G_OBJECT (calendar), "show-week-numbers");
3840   g_object_thaw_notify (G_OBJECT (calendar));
3841 }
3842
3843 /**
3844  * gtk_calendar_select_month:
3845  * @calendar: a #GtkCalendar
3846  * @month: a month number between 0 and 11.
3847  * @year: the year the month is in.
3848  *
3849  * Shifts the calendar to a different month.
3850  **/
3851 void
3852 gtk_calendar_select_month (GtkCalendar *calendar,
3853                            guint        month,
3854                            guint        year)
3855 {
3856   GtkCalendarPrivate *priv;
3857
3858   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3859   g_return_if_fail (month <= 11);
3860
3861   priv = calendar->priv;
3862
3863   priv->month = month;
3864   priv->year  = year;
3865
3866   calendar_compute_days (calendar);
3867   calendar_queue_refresh (calendar);
3868
3869   g_object_freeze_notify (G_OBJECT (calendar));
3870   g_object_notify (G_OBJECT (calendar), "month");
3871   g_object_notify (G_OBJECT (calendar), "year");
3872   g_object_thaw_notify (G_OBJECT (calendar));
3873
3874   g_signal_emit (calendar,
3875                  gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
3876                  0);
3877 }
3878
3879 /**
3880  * gtk_calendar_select_day:
3881  * @calendar: a #GtkCalendar.
3882  * @day: the day number between 1 and 31, or 0 to unselect 
3883  *   the currently selected day.
3884  * 
3885  * Selects a day from the current month.
3886  **/
3887 void
3888 gtk_calendar_select_day (GtkCalendar *calendar,
3889                          guint        day)
3890 {
3891   GtkCalendarPrivate *priv;
3892
3893   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3894   g_return_if_fail (day <= 31);
3895
3896   priv = calendar->priv;
3897
3898   /* Deselect the old day */
3899   if (priv->selected_day > 0)
3900     {
3901       gint selected_day;
3902       
3903       selected_day = priv->selected_day;
3904       priv->selected_day = 0;
3905       if (gtk_widget_is_drawable (GTK_WIDGET (calendar)))
3906         calendar_invalidate_day_num (calendar, selected_day);
3907     }
3908   
3909   priv->selected_day = day;
3910   
3911   /* Select the new day */
3912   if (day != 0)
3913     {
3914       if (gtk_widget_is_drawable (GTK_WIDGET (calendar)))
3915         calendar_invalidate_day_num (calendar, day);
3916     }
3917   
3918   g_object_notify (G_OBJECT (calendar), "day");
3919
3920   g_signal_emit (calendar,
3921                  gtk_calendar_signals[DAY_SELECTED_SIGNAL],
3922                  0);
3923 }
3924
3925 /**
3926  * gtk_calendar_clear_marks:
3927  * @calendar: a #GtkCalendar
3928  * 
3929  * Remove all visual markers.
3930  **/
3931 void
3932 gtk_calendar_clear_marks (GtkCalendar *calendar)
3933 {
3934   GtkCalendarPrivate *priv;
3935   guint day;
3936
3937   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3938
3939   priv = calendar->priv;
3940
3941   for (day = 0; day < 31; day++)
3942     {
3943       priv->marked_date[day] = FALSE;
3944     }
3945
3946   priv->num_marked_dates = 0;
3947   calendar_queue_refresh (calendar);
3948 }
3949
3950 /**
3951  * gtk_calendar_mark_day:
3952  * @calendar: a #GtkCalendar
3953  * @day: the day number to mark between 1 and 31.
3954  *
3955  * Places a visual marker on a particular day.
3956  */
3957 void
3958 gtk_calendar_mark_day (GtkCalendar *calendar,
3959                        guint        day)
3960 {
3961   GtkCalendarPrivate *priv;
3962
3963   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3964
3965   priv = calendar->priv;
3966
3967   if (day >= 1 && day <= 31 && !priv->marked_date[day-1])
3968     {
3969       priv->marked_date[day - 1] = TRUE;
3970       priv->num_marked_dates++;
3971       calendar_invalidate_day_num (calendar, day);
3972     }
3973 }
3974
3975 /**
3976  * gtk_calendar_unmark_day:
3977  * @calendar: a #GtkCalendar.
3978  * @day: the day number to unmark between 1 and 31.
3979  *
3980  * Removes the visual marker from a particular day.
3981  */
3982 void
3983 gtk_calendar_unmark_day (GtkCalendar *calendar,
3984                          guint        day)
3985 {
3986   GtkCalendarPrivate *priv;
3987
3988   g_return_if_fail (GTK_IS_CALENDAR (calendar));
3989
3990   priv = calendar->priv;
3991
3992   if (day >= 1 && day <= 31 && priv->marked_date[day-1])
3993     {
3994       priv->marked_date[day - 1] = FALSE;
3995       priv->num_marked_dates--;
3996       calendar_invalidate_day_num (calendar, day);
3997     }
3998 }
3999
4000 /**
4001  * gtk_calendar_get_date:
4002  * @calendar: a #GtkCalendar
4003  * @year: (allow-none): location to store the year number, or %NULL
4004  * @month: (allow-none): location to store the month number (between 0 and 11), or %NULL
4005  * @day: (allow-none): location to store the day number (between 1 and 31), or %NULL
4006  * 
4007  * Obtains the selected date from a #GtkCalendar.
4008  **/
4009 void
4010 gtk_calendar_get_date (GtkCalendar *calendar,
4011                        guint       *year,
4012                        guint       *month,
4013                        guint       *day)
4014 {
4015   GtkCalendarPrivate *priv;
4016
4017   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4018
4019   priv = calendar->priv;
4020
4021   if (year)
4022     *year = priv->year;
4023
4024   if (month)
4025     *month = priv->month;
4026
4027   if (day)
4028     *day = priv->selected_day;
4029 }
4030
4031 /**
4032  * gtk_calendar_set_detail_func:
4033  * @calendar: a #GtkCalendar.
4034  * @func: a function providing details for each day.
4035  * @data: data to pass to @func invokations.
4036  * @destroy: a function for releasing @data.
4037  *
4038  * Installs a function which provides Pango markup with detail information
4039  * for each day. Examples for such details are holidays or appointments. That
4040  * information is shown below each day when #GtkCalendar:show-details is set.
4041  * A tooltip containing with full detail information is provided, if the entire
4042  * text should not fit into the details area, or if #GtkCalendar:show-details
4043  * is not set.
4044  *
4045  * The size of the details area can be restricted by setting the
4046  * #GtkCalendar:detail-width-chars and #GtkCalendar:detail-height-rows
4047  * properties.
4048  *
4049  * Since: 2.14
4050  */
4051 void
4052 gtk_calendar_set_detail_func (GtkCalendar           *calendar,
4053                               GtkCalendarDetailFunc  func,
4054                               gpointer               data,
4055                               GDestroyNotify         destroy)
4056 {
4057   GtkCalendarPrivate *priv;
4058
4059   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4060
4061   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
4062
4063   if (priv->detail_func_destroy)
4064     priv->detail_func_destroy (priv->detail_func_user_data);
4065
4066   priv->detail_func = func;
4067   priv->detail_func_user_data = data;
4068   priv->detail_func_destroy = destroy;
4069
4070   gtk_widget_set_has_tooltip (GTK_WIDGET (calendar),
4071                               NULL != priv->detail_func);
4072   gtk_widget_queue_resize (GTK_WIDGET (calendar));
4073 }
4074
4075 /**
4076  * gtk_calendar_set_detail_width_chars:
4077  * @calendar: a #GtkCalendar.
4078  * @chars: detail width in characters.
4079  *
4080  * Updates the width of detail cells.
4081  * See #GtkCalendar:detail-width-chars.
4082  *
4083  * Since: 2.14
4084  */
4085 void
4086 gtk_calendar_set_detail_width_chars (GtkCalendar *calendar,
4087                                      gint         chars)
4088 {
4089   GtkCalendarPrivate *priv;
4090
4091   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4092
4093   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
4094
4095   if (chars != priv->detail_width_chars)
4096     {
4097       priv->detail_width_chars = chars;
4098       g_object_notify (G_OBJECT (calendar), "detail-width-chars");
4099       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
4100     }
4101 }
4102
4103 /**
4104  * gtk_calendar_set_detail_height_rows:
4105  * @calendar: a #GtkCalendar.
4106  * @rows: detail height in rows.
4107  *
4108  * Updates the height of detail cells.
4109  * See #GtkCalendar:detail-height-rows.
4110  *
4111  * Since: 2.14
4112  */
4113 void
4114 gtk_calendar_set_detail_height_rows (GtkCalendar *calendar,
4115                                      gint         rows)
4116 {
4117   GtkCalendarPrivate *priv;
4118
4119   g_return_if_fail (GTK_IS_CALENDAR (calendar));
4120
4121   priv = GTK_CALENDAR_GET_PRIVATE (calendar);
4122
4123   if (rows != priv->detail_height_rows)
4124     {
4125       priv->detail_height_rows = rows;
4126       g_object_notify (G_OBJECT (calendar), "detail-height-rows");
4127       gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
4128     }
4129 }
4130
4131 /**
4132  * gtk_calendar_get_detail_width_chars:
4133  * @calendar: a #GtkCalendar.
4134  *
4135  * Queries the width of detail cells, in characters.
4136  * See #GtkCalendar:detail-width-chars.
4137  *
4138  * Since: 2.14
4139  *
4140  * Return value: The width of detail cells, in characters.
4141  */
4142 gint
4143 gtk_calendar_get_detail_width_chars (GtkCalendar *calendar)
4144 {
4145   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
4146   return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_width_chars;
4147 }
4148
4149 /**
4150  * gtk_calendar_get_detail_height_rows:
4151  * @calendar: a #GtkCalendar.
4152  *
4153  * Queries the height of detail cells, in rows.
4154  * See #GtkCalendar:detail-width-chars.
4155  *
4156  * Since: 2.14
4157  *
4158  * Return value: The height of detail cells, in rows.
4159  */
4160 gint
4161 gtk_calendar_get_detail_height_rows (GtkCalendar *calendar)
4162 {
4163   g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
4164   return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_height_rows;
4165 }