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