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