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