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