gtkcalendar.c 118 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3 4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * GTK Calendar Widget
Matthias Clasen's avatar
2.5.5  
Matthias Clasen committed
5
 * Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson and Mattias Groenlund
Matthias Clasen's avatar
Matthias Clasen committed
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9 10 11 12 13
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Matthias Clasen's avatar
Matthias Clasen committed
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
Matthias Clasen's avatar
Matthias Clasen committed
18
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 20
 */

21
/*
Owen Taylor's avatar
Owen Taylor committed
22
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23 24
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
Matthias Clasen's avatar
Matthias Clasen committed
25
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
26 27
 */

28 29 30 31 32
/**
 * SECTION:gtkcalendar
 * @Short_description: Displays a calendar and allows the user to select a date
 * @Title: GtkCalendar
 *
33 34
 * #GtkCalendar is a widget that displays a Gregorian calendar, one month
 * at a time. It can be created with gtk_calendar_new().
35 36
 *
 * The month and year currently displayed can be altered with
37 38
 * gtk_calendar_select_month(). The exact day can be selected from the
 * displayed month using gtk_calendar_select_day().
39
 *
40 41 42
 * To place a visual marker on a particular day, use gtk_calendar_mark_day()
 * and to remove the marker, gtk_calendar_unmark_day(). Alternative, all
 * marks can be cleared with gtk_calendar_clear_marks().
43 44 45 46 47 48
 *
 * The way in which the calendar itself is displayed can be altered using
 * gtk_calendar_set_display_options().
 *
 * The selected date can be retrieved from a #GtkCalendar using
 * gtk_calendar_get_date().
49
 *
50 51 52 53
 * Users should be aware that, although the Gregorian calendar is the
 * legal calendar in most countries, it was adopted progressively
 * between 1582 and 1929. Display before these dates is likely to be
 * historically incorrect.
54 55
 */

56
#include "config.h"
57 58

#ifdef HAVE_SYS_TIME_H
59
#include <sys/time.h>
60
#endif
61
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
62 63
#include <langinfo.h>
#endif
64 65 66
#include <string.h>
#include <stdlib.h>
#include <time.h>
67 68 69 70 71 72 73

#include <glib.h>

#ifdef G_OS_WIN32
#include <windows.h>
#endif

74
#include "gtkcalendar.h"
75
#include "gtkdnd.h"
76
#include "gtkdragdest.h"
77
#include "gtkintl.h"
78 79
#include "gtkmain.h"
#include "gtkmarshalers.h"
80
#include "gtktooltip.h"
81
#include "gtkprivate.h"
82
#include "gtkrender.h"
83

84 85 86
#define TIMEOUT_INITIAL  500
#define TIMEOUT_REPEAT    50

87
static const guint month_length[2][13] =
88
{
89 90
  { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
  { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
91 92
};

Matthias Clasen's avatar
Matthias Clasen committed
93
static gboolean
94
leap (guint year)
95
{
96
  return ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
97 98
}

Matthias Clasen's avatar
Matthias Clasen committed
99
static guint
100
day_of_week (guint year, guint mm, guint dd)
101
{
102 103
  GDateTime *dt;
  guint days;
104

105
  dt = g_date_time_new_local (year, mm, dd, 1, 1, 1);
106 107 108
  if (dt == NULL)
    return 0;

109 110
  days = g_date_time_get_day_of_week (dt);
  g_date_time_unref (dt);
111

112
  return days;
113 114
}

Matthias Clasen's avatar
Matthias Clasen committed
115
static guint
116
week_of_year (guint year, guint mm, guint dd)
117
{
118 119
  GDateTime *dt;
  guint week;
120

121
  dt = g_date_time_new_local (year, mm, dd, 1, 1, 1);
122 123 124
  if (dt == NULL)
    return 1;

125 126
  week = g_date_time_get_week_of_year (dt);
  g_date_time_unref (dt);
127

128
  return week;
129 130
}

131
/* Spacing around day/week headers and main area, inside those windows */
Matthias Clasen's avatar
Matthias Clasen committed
132
#define CALENDAR_MARGIN          0
133

Matthias Clasen's avatar
Matthias Clasen committed
134 135
#define DAY_XSEP                 0 /* not really good for small calendar */
#define DAY_YSEP                 0 /* not really good for small calendar */
136

137 138
#define SCROLL_DELAY_FACTOR      5

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
enum {
  ARROW_YEAR_LEFT,
  ARROW_YEAR_RIGHT,
  ARROW_MONTH_LEFT,
  ARROW_MONTH_RIGHT
};

enum {
  MONTH_PREV,
  MONTH_CURRENT,
  MONTH_NEXT
};

enum {
  MONTH_CHANGED_SIGNAL,
  DAY_SELECTED_SIGNAL,
  DAY_SELECTED_DOUBLE_CLICK_SIGNAL,
  PREV_MONTH_SIGNAL,
  NEXT_MONTH_SIGNAL,
  PREV_YEAR_SIGNAL,
  NEXT_YEAR_SIGNAL,
  LAST_SIGNAL
};

163 164 165 166 167 168 169 170 171 172
enum
{
  PROP_0,
  PROP_YEAR,
  PROP_MONTH,
  PROP_DAY,
  PROP_SHOW_HEADING,
  PROP_SHOW_DAY_NAMES,
  PROP_NO_MONTH_CHANGE,
  PROP_SHOW_WEEK_NUMBERS,
173
  PROP_SHOW_DETAILS,
174
  PROP_DETAIL_WIDTH_CHARS,
175
  PROP_DETAIL_HEIGHT_ROWS
176 177
};

178
static guint gtk_calendar_signals[LAST_SIGNAL] = { 0 };
179

180
struct _GtkCalendarPrivate
181
{
182 183
  GtkCalendarDisplayOptions display_flags;

184 185 186
  GdkWindow *main_win;
  GdkWindow *arrow_win[4];

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
  gchar grow_space [32];

  gint  month;
  gint  year;
  gint  selected_day;

  gint  day_month[6][7];
  gint  day[6][7];

  gint  num_marked_dates;
  gint  marked_date[31];

  gint  focus_row;
  gint  focus_col;

202 203 204
  guint header_h;
  guint day_name_h;
  guint main_h;
205

206
  guint arrow_prelight : 4;
Matthias Clasen's avatar
Matthias Clasen committed
207 208 209
  guint arrow_width;
  guint max_month_width;
  guint max_year_width;
210

211 212 213 214 215 216 217 218 219 220
  guint day_width;
  guint week_width;

  guint min_day_width;
  guint max_day_char_width;
  guint max_day_char_ascent;
  guint max_day_char_descent;
  guint max_label_char_ascent;
  guint max_label_char_descent;
  guint max_week_char_width;
Matthias Clasen's avatar
Matthias Clasen committed
221

222
  /* flags */
223 224
  guint year_before : 1;

225 226
  guint need_timer  : 1;

227 228 229
  guint in_drag : 1;
  guint drag_highlight : 1;

230 231
  guint32 timer;
  gint click_child;
232 233

  gint week_start;
234 235 236

  gint drag_start_x;
  gint drag_start_y;
237 238 239 240 241 242 243 244 245

  /* Optional callback, used to display extra information for each day. */
  GtkCalendarDetailFunc detail_func;
  gpointer              detail_func_user_data;
  GDestroyNotify        detail_func_destroy;

  /* Size requistion for details provided by the hook. */
  gint detail_height_rows;
  gint detail_width_chars;
246
  gint detail_overflow[6];
247 248
};

249
static void gtk_calendar_finalize     (GObject      *calendar);
250
static void gtk_calendar_destroy      (GtkWidget    *widget);
251
static void gtk_calendar_set_property (GObject      *object,
Matthias Clasen's avatar
Matthias Clasen committed
252 253 254
                                       guint         prop_id,
                                       const GValue *value,
                                       GParamSpec   *pspec);
255
static void gtk_calendar_get_property (GObject      *object,
Matthias Clasen's avatar
Matthias Clasen committed
256 257 258
                                       guint         prop_id,
                                       GValue       *value,
                                       GParamSpec   *pspec);
259 260 261

static void     gtk_calendar_realize        (GtkWidget        *widget);
static void     gtk_calendar_unrealize      (GtkWidget        *widget);
262 263
static void     gtk_calendar_map            (GtkWidget        *widget);
static void     gtk_calendar_unmap          (GtkWidget        *widget);
264 265 266 267 268 269
static void     gtk_calendar_get_preferred_width  (GtkWidget   *widget,
                                                   gint        *minimum,
                                                   gint        *natural);
static void     gtk_calendar_get_preferred_height (GtkWidget   *widget,
                                                   gint        *minimum,
                                                   gint        *natural);
270
static void     gtk_calendar_size_allocate  (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
271
                                             GtkAllocation    *allocation);
Benjamin Otte's avatar
Benjamin Otte committed
272 273
static gboolean gtk_calendar_draw           (GtkWidget        *widget,
                                             cairo_t          *cr);
274
static gboolean gtk_calendar_button_press   (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
275
                                             GdkEventButton   *event);
276
static gboolean gtk_calendar_button_release (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
277
                                             GdkEventButton   *event);
278
static gboolean gtk_calendar_motion_notify  (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
279
                                             GdkEventMotion   *event);
280
static gboolean gtk_calendar_enter_notify   (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
281
                                             GdkEventCrossing *event);
282
static gboolean gtk_calendar_leave_notify   (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
283
                                             GdkEventCrossing *event);
284
static gboolean gtk_calendar_scroll         (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
285
                                             GdkEventScroll   *event);
286
static gboolean gtk_calendar_key_press      (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
287
                                             GdkEventKey      *event);
288
static gboolean gtk_calendar_focus_out      (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
289
                                             GdkEventFocus    *event);
290
static void     gtk_calendar_grab_notify    (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
291
                                             gboolean          was_grabbed);
292 293
static void     gtk_calendar_state_flags_changed  (GtkWidget     *widget,
                                                   GtkStateFlags  previous_state);
294
static gboolean gtk_calendar_query_tooltip  (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
295 296 297 298
                                             gint              x,
                                             gint              y,
                                             gboolean          keyboard_mode,
                                             GtkTooltip       *tooltip);
299 300

static void     gtk_calendar_drag_data_get      (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
301 302 303 304
                                                 GdkDragContext   *context,
                                                 GtkSelectionData *selection_data,
                                                 guint             info,
                                                 guint             time);
305
static void     gtk_calendar_drag_data_received (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
306 307 308 309 310 311
                                                 GdkDragContext   *context,
                                                 gint              x,
                                                 gint              y,
                                                 GtkSelectionData *selection_data,
                                                 guint             info,
                                                 guint             time);
312
static gboolean gtk_calendar_drag_motion        (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
313 314 315 316
                                                 GdkDragContext   *context,
                                                 gint              x,
                                                 gint              y,
                                                 guint             time);
317
static void     gtk_calendar_drag_leave         (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
318 319
                                                 GdkDragContext   *context,
                                                 guint             time);
320
static gboolean gtk_calendar_drag_drop          (GtkWidget        *widget,
Matthias Clasen's avatar
Matthias Clasen committed
321 322 323 324
                                                 GdkDragContext   *context,
                                                 gint              x,
                                                 gint              y,
                                                 guint             time);
325

Matthias Clasen's avatar
Matthias Clasen committed
326

327
static void calendar_start_spinning (GtkCalendar *calendar,
Matthias Clasen's avatar
Matthias Clasen committed
328
                                     gint         click_child);
329 330 331
static void calendar_stop_spinning  (GtkCalendar *calendar);

static void calendar_invalidate_day     (GtkCalendar *widget,
Matthias Clasen's avatar
Matthias Clasen committed
332 333
                                         gint       row,
                                         gint       col);
334
static void calendar_invalidate_day_num (GtkCalendar *widget,
Matthias Clasen's avatar
Matthias Clasen committed
335
                                         gint       day);
336
static void calendar_invalidate_arrow   (GtkCalendar *widget,
Matthias Clasen's avatar
Matthias Clasen committed
337
                                         guint      arrow);
338 339

static void calendar_compute_days      (GtkCalendar *calendar);
340 341
static gint calendar_get_xsep          (GtkCalendar *calendar);
static gint calendar_get_ysep          (GtkCalendar *calendar);
342
static gint calendar_get_inner_border  (GtkCalendar *calendar);
343

344 345 346
static char    *default_abbreviated_dayname[7];
static char    *default_monthname[12];

347
G_DEFINE_TYPE_WITH_PRIVATE (GtkCalendar, gtk_calendar, GTK_TYPE_WIDGET)
348 349 350 351

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
352
  GObjectClass   *gobject_class;
353
  GtkWidgetClass *widget_class;
354 355

  gobject_class = (GObjectClass*)  class;
356
  widget_class = (GtkWidgetClass*) class;
Matthias Clasen's avatar
Matthias Clasen committed
357

358 359
  gobject_class->set_property = gtk_calendar_set_property;
  gobject_class->get_property = gtk_calendar_get_property;
360
  gobject_class->finalize = gtk_calendar_finalize;
361

362
  widget_class->destroy = gtk_calendar_destroy;
363 364
  widget_class->realize = gtk_calendar_realize;
  widget_class->unrealize = gtk_calendar_unrealize;
365 366
  widget_class->map = gtk_calendar_map;
  widget_class->unmap = gtk_calendar_unmap;
Benjamin Otte's avatar
Benjamin Otte committed
367
  widget_class->draw = gtk_calendar_draw;
368 369
  widget_class->get_preferred_width = gtk_calendar_get_preferred_width;
  widget_class->get_preferred_height = gtk_calendar_get_preferred_height;
370 371
  widget_class->size_allocate = gtk_calendar_size_allocate;
  widget_class->button_press_event = gtk_calendar_button_press;
372
  widget_class->button_release_event = gtk_calendar_button_release;
373 374 375 376
  widget_class->motion_notify_event = gtk_calendar_motion_notify;
  widget_class->enter_notify_event = gtk_calendar_enter_notify;
  widget_class->leave_notify_event = gtk_calendar_leave_notify;
  widget_class->key_press_event = gtk_calendar_key_press;
377
  widget_class->scroll_event = gtk_calendar_scroll;
378
  widget_class->state_flags_changed = gtk_calendar_state_flags_changed;
379
  widget_class->grab_notify = gtk_calendar_grab_notify;
380
  widget_class->focus_out_event = gtk_calendar_focus_out;
381
  widget_class->query_tooltip = gtk_calendar_query_tooltip;
382 383 384 385 386 387

  widget_class->drag_data_get = gtk_calendar_drag_data_get;
  widget_class->drag_motion = gtk_calendar_drag_motion;
  widget_class->drag_leave = gtk_calendar_drag_leave;
  widget_class->drag_drop = gtk_calendar_drag_drop;
  widget_class->drag_data_received = gtk_calendar_drag_data_received;
Matthias Clasen's avatar
Matthias Clasen committed
388

389 390 391
  /**
   * GtkCalendar:year:
   *
Matthias Clasen's avatar
Matthias Clasen committed
392
   * The selected year.
393
   * This property gets initially set to the current year.
Matthias Clasen's avatar
Matthias Clasen committed
394
   */
395 396 397
  g_object_class_install_property (gobject_class,
                                   PROP_YEAR,
                                   g_param_spec_int ("year",
Matthias Clasen's avatar
Matthias Clasen committed
398 399 400
                                                     P_("Year"),
                                                     P_("The selected year"),
                                                     0, G_MAXINT >> 9, 0,
401
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
402 403 404 405

  /**
   * GtkCalendar:month:
   *
Matthias Clasen's avatar
Matthias Clasen committed
406
   * The selected month (as a number between 0 and 11).
407 408
   * This property gets initially set to the current month.
   */
409 410 411
  g_object_class_install_property (gobject_class,
                                   PROP_MONTH,
                                   g_param_spec_int ("month",
Matthias Clasen's avatar
Matthias Clasen committed
412 413 414
                                                     P_("Month"),
                                                     P_("The selected month (as a number between 0 and 11)"),
                                                     0, 11, 0,
415
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
416 417 418 419

  /**
   * GtkCalendar:day:
   *
Matthias Clasen's avatar
Matthias Clasen committed
420
   * The selected day (as a number between 1 and 31, or 0
421 422 423
   * to unselect the currently selected day).
   * This property gets initially set to the current day.
   */
424 425 426
  g_object_class_install_property (gobject_class,
                                   PROP_DAY,
                                   g_param_spec_int ("day",
Matthias Clasen's avatar
Matthias Clasen committed
427 428 429
                                                     P_("Day"),
                                                     P_("The selected day (as a number between 1 and 31, or 0 to unselect the currently selected day)"),
                                                     0, 31, 0,
430
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
431 432 433 434 435 436 437 438

/**
 * GtkCalendar:show-heading:
 *
 * Determines whether a heading is displayed.
 *
 * Since: 2.4
 */
439 440
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_HEADING,
441
                                   g_param_spec_boolean ("show-heading",
Matthias Clasen's avatar
Matthias Clasen committed
442 443 444
                                                         P_("Show Heading"),
                                                         P_("If TRUE, a heading is displayed"),
                                                         TRUE,
445
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
446 447 448 449 450 451 452 453

/**
 * GtkCalendar:show-day-names:
 *
 * Determines whether day names are displayed.
 *
 * Since: 2.4
 */
454 455
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_DAY_NAMES,
456
                                   g_param_spec_boolean ("show-day-names",
Matthias Clasen's avatar
Matthias Clasen committed
457 458 459
                                                         P_("Show Day Names"),
                                                         P_("If TRUE, day names are displayed"),
                                                         TRUE,
460
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
461 462 463 464 465 466 467
/**
 * GtkCalendar:no-month-change:
 *
 * Determines whether the selected month can be changed.
 *
 * Since: 2.4
 */
468 469
  g_object_class_install_property (gobject_class,
                                   PROP_NO_MONTH_CHANGE,
470
                                   g_param_spec_boolean ("no-month-change",
Matthias Clasen's avatar
Matthias Clasen committed
471 472 473
                                                         P_("No Month Change"),
                                                         P_("If TRUE, the selected month cannot be changed"),
                                                         FALSE,
474
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
475 476 477 478 479 480 481 482

/**
 * GtkCalendar:show-week-numbers:
 *
 * Determines whether week numbers are displayed.
 *
 * Since: 2.4
 */
483 484
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_WEEK_NUMBERS,
485
                                   g_param_spec_boolean ("show-week-numbers",
Matthias Clasen's avatar
Matthias Clasen committed
486 487 488
                                                         P_("Show Week Numbers"),
                                                         P_("If TRUE, week numbers are displayed"),
                                                         FALSE,
489
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
490

491 492 493 494 495 496
/**
 * GtkCalendar:detail-width-chars:
 *
 * Width of a detail cell, in characters.
 * A value of 0 allows any width. See gtk_calendar_set_detail_func().
 *
497
 * Since: 2.14
498 499 500 501
 */
  g_object_class_install_property (gobject_class,
                                   PROP_DETAIL_WIDTH_CHARS,
                                   g_param_spec_int ("detail-width-chars",
Matthias Clasen's avatar
Matthias Clasen committed
502 503 504
                                                     P_("Details Width"),
                                                     P_("Details width in characters"),
                                                     0, 127, 0,
505
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
506 507 508 509 510 511 512

/**
 * GtkCalendar:detail-height-rows:
 *
 * Height of a detail cell, in rows.
 * A value of 0 allows any width. See gtk_calendar_set_detail_func().
 *
513
 * Since: 2.14
514 515 516 517
 */
  g_object_class_install_property (gobject_class,
                                   PROP_DETAIL_HEIGHT_ROWS,
                                   g_param_spec_int ("detail-height-rows",
Matthias Clasen's avatar
Matthias Clasen committed
518 519 520
                                                     P_("Details Height"),
                                                     P_("Details height in rows"),
                                                     0, 127, 0,
521
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
522

523 524 525 526 527 528 529
/**
 * GtkCalendar:show-details:
 *
 * Determines whether details are shown directly in the widget, or if they are
 * available only as tooltip. When this property is set days with details are
 * marked.
 *
530
 * Since: 2.14
531 532 533 534
 */
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_DETAILS,
                                   g_param_spec_boolean ("show-details",
Matthias Clasen's avatar
Matthias Clasen committed
535 536 537
                                                         P_("Show Details"),
                                                         P_("If TRUE, details are shown"),
                                                         TRUE,
538
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
539

540 541

  /**
Matthias Clasen's avatar
Matthias Clasen committed
542
   * GtkCalendar:inner-border:
543 544 545 546 547 548 549 550 551 552 553
   *
   * The spacing around the day/week headers and main area.
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("inner-border",
                                                             P_("Inner border"),
                                                             P_("Inner border space"),
                                                             0, G_MAXINT, 4,
                                                             GTK_PARAM_READABLE));

  /**
Matthias Clasen's avatar
Matthias Clasen committed
554
   * GtkCalndar:vertical-separation:
555 556 557 558 559 560 561 562 563 564 565
   *
   * Separation between day headers and main area.
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("vertical-separation",
                                                             P_("Vertical separation"),
                                                             P_("Space between day headers and main area"),
                                                             0, G_MAXINT, 4,
                                                             GTK_PARAM_READABLE));

  /**
Matthias Clasen's avatar
Matthias Clasen committed
566
   * GtkCalendar:horizontal-separation:
567 568 569 570 571 572 573 574 575 576
   *
   * Separation between week headers and main area.
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("horizontal-separation",
                                                             P_("Horizontal separation"),
                                                             P_("Space between week headers and main area"),
                                                             0, G_MAXINT, 4,
                                                             GTK_PARAM_READABLE));

577 578 579 580 581 582 583
  /**
   * GtkCalendar::month-changed:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user clicks a button to change the selected month on a
   * calendar.
   */
584
  gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
585
    g_signal_new (I_("month-changed"),
Matthias Clasen's avatar
Matthias Clasen committed
586 587 588 589
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, month_changed),
                  NULL, NULL,
590
                  NULL,
Matthias Clasen's avatar
Matthias Clasen committed
591
                  G_TYPE_NONE, 0);
592 593 594 595 596 597 598

  /**
   * GtkCalendar::day-selected:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user selects a day.
   */
599
  gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
600
    g_signal_new (I_("day-selected"),
Matthias Clasen's avatar
Matthias Clasen committed
601 602 603 604
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, day_selected),
                  NULL, NULL,
605
                  NULL,
Matthias Clasen's avatar
Matthias Clasen committed
606
                  G_TYPE_NONE, 0);
607 608 609 610 611 612 613

  /**
   * GtkCalendar::day-selected-double-click:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user double-clicks a day.
   */
614
  gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
615
    g_signal_new (I_("day-selected-double-click"),
Matthias Clasen's avatar
Matthias Clasen committed
616 617 618 619
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, day_selected_double_click),
                  NULL, NULL,
620
                  NULL,
Matthias Clasen's avatar
Matthias Clasen committed
621
                  G_TYPE_NONE, 0);
622 623 624 625 626 627 628

  /**
   * GtkCalendar::prev-month:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user switched to the previous month.
   */
629
  gtk_calendar_signals[PREV_MONTH_SIGNAL] =
630
    g_signal_new (I_("prev-month"),
Matthias Clasen's avatar
Matthias Clasen committed
631 632 633 634
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, prev_month),
                  NULL, NULL,
635
                  NULL,
Matthias Clasen's avatar
Matthias Clasen committed
636
                  G_TYPE_NONE, 0);
637 638 639 640 641 642 643

  /**
   * GtkCalendar::next-month:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user switched to the next month.
   */
644
  gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
645
    g_signal_new (I_("next-month"),
Matthias Clasen's avatar
Matthias Clasen committed
646 647 648 649
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, next_month),
                  NULL, NULL,
650
                  NULL,
Matthias Clasen's avatar
Matthias Clasen committed
651
                  G_TYPE_NONE, 0);
652 653 654 655 656 657 658

  /**
   * GtkCalendar::prev-year:
   * @calendar: the object which received the signal.
   *
   * Emitted when user switched to the previous year.
   */
659
  gtk_calendar_signals[PREV_YEAR_SIGNAL] =
660
    g_signal_new (I_("prev-year"),
Matthias Clasen's avatar
Matthias Clasen committed
661 662 663 664
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, prev_year),
                  NULL, NULL,
665
                  NULL,
Matthias Clasen's avatar
Matthias Clasen committed
666
                  G_TYPE_NONE, 0);
667 668 669 670 671 672 673

  /**
   * GtkCalendar::next-year:
   * @calendar: the object which received the signal.
   *
   * Emitted when user switched to the next year.
   */
674
  gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
675
    g_signal_new (I_("next-year"),
Matthias Clasen's avatar
Matthias Clasen committed
676 677 678 679
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, next_year),
                  NULL, NULL,
680
                  NULL,
Matthias Clasen's avatar
Matthias Clasen committed
681
                  G_TYPE_NONE, 0);
682 683 684

  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_CALENDAR);
  gtk_widget_class_set_css_name (widget_class, "calendar");
685 686 687 688 689
}

static void
gtk_calendar_init (GtkCalendar *calendar)
{
690
  GtkWidget *widget = GTK_WIDGET (calendar);
691 692 693
  time_t secs;
  struct tm *tm;
  gint i;
694 695 696
#ifdef G_OS_WIN32
  wchar_t wbuffer[100];
#else
697
  char buffer[255];
698
  time_t tmp_time;
699
#endif
700
  GtkCalendarPrivate *priv;
701
  gchar *year_before;
702
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
703
  union { unsigned int word; char *string; } langinfo;
704 705
  gint week_1stday = 0;
  gint first_weekday = 1;
706
  guint week_origin;
707 708 709
#else
  gchar *week_start;
#endif
710

711
  priv = calendar->priv = gtk_calendar_get_instance_private (calendar);
712

713
  gtk_widget_set_can_focus (widget, TRUE);
714
  gtk_widget_set_has_window (widget, FALSE);
Matthias Clasen's avatar
Matthias Clasen committed
715

716 717 718
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
719
#ifndef G_OS_WIN32
Matthias Clasen's avatar
Matthias Clasen committed
720 721 722
        tmp_time= (i+3)*86400;
        strftime ( buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
        default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
723
#else
Matthias Clasen's avatar
Matthias Clasen committed
724 725 726 727 728
        if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SABBREVDAYNAME1 + (i+6)%7,
                             wbuffer, G_N_ELEMENTS (wbuffer)))
          default_abbreviated_dayname[i] = g_strdup_printf ("(%d)", i);
        else
          default_abbreviated_dayname[i] = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
729
#endif
730
      }
Matthias Clasen's avatar
Matthias Clasen committed
731

732 733 734
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
735
#ifndef G_OS_WIN32
Matthias Clasen's avatar
Matthias Clasen committed
736 737 738
        tmp_time=i*2764800;
        strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
        default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
739
#else
Matthias Clasen's avatar
Matthias Clasen committed
740 741 742 743 744
        if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SMONTHNAME1 + i,
                             wbuffer, G_N_ELEMENTS (wbuffer)))
          default_monthname[i] = g_strdup_printf ("(%d)", i);
        else
          default_monthname[i] = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
745
#endif
746
      }
Matthias Clasen's avatar
Matthias Clasen committed
747

748 749 750
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
751 752
  priv->month = tm->tm_mon;
  priv->year  = 1900 + tm->tm_year;
753

754
  for (i=0;i<31;i++)
755 756 757
    priv->marked_date[i] = FALSE;
  priv->num_marked_dates = 0;
  priv->selected_day = tm->tm_mday;
Matthias Clasen's avatar
Matthias Clasen committed
758

759
  priv->display_flags = (GTK_CALENDAR_SHOW_HEADING |
Matthias Clasen's avatar
Matthias Clasen committed
760 761 762
                             GTK_CALENDAR_SHOW_DAY_NAMES |
                             GTK_CALENDAR_SHOW_DETAILS);

763 764
  priv->focus_row = -1;
  priv->focus_col = -1;
765

766 767 768 769
  priv->max_year_width = 0;
  priv->max_month_width = 0;
  priv->max_day_char_width = 0;
  priv->max_week_char_width = 0;
770

771 772 773 774
  priv->max_day_char_ascent = 0;
  priv->max_day_char_descent = 0;
  priv->max_label_char_ascent = 0;
  priv->max_label_char_descent = 0;
775

776
  priv->arrow_width = 10;
777

778 779 780
  priv->need_timer = 0;
  priv->timer = 0;
  priv->click_child = -1;
781

782 783
  priv->in_drag = 0;
  priv->drag_highlight = 0;
784

785 786
  gtk_drag_dest_set (widget, 0, NULL, 0, GDK_ACTION_COPY);
  gtk_drag_dest_add_text_targets (widget);
787

788
  priv->year_before = 0;
789 790 791 792 793 794

  /* Translate to calendar:YM if you want years to be displayed
   * before months; otherwise translate to calendar:MY.
   * Do *not* translate it to anything else, if it
   * it isn't calendar:YM or calendar:MY it will not work.
   *
795 796 797 798
   * Note that the ordering described here is logical order, which is
   * further influenced by BIDI ordering. Thus, if you have a default
   * text direction of RTL and specify "calendar:YM", then the year
   * will appear to the right of the month.
799 800 801
   */
  year_before = _("calendar:MY");
  if (strcmp (year_before, "calendar:YM") == 0)
802
    priv->year_before = 1;
803
  else if (strcmp (year_before, "calendar:MY") != 0)
804
    g_warning ("Whoever translated calendar:MY did so wrongly.");
805

806
#ifdef G_OS_WIN32
Tor Lillqvist's avatar
Tor Lillqvist committed
807 808
  priv->week_start = 0;
  week_start = NULL;
809

Tor Lillqvist's avatar
Tor Lillqvist committed
810
  if (GetLocaleInfoW (GetThreadLocale (), LOCALE_IFIRSTDAYOFWEEK,
Matthias Clasen's avatar
Matthias Clasen committed
811
                      wbuffer, G_N_ELEMENTS (wbuffer)))
Tor Lillqvist's avatar
Tor Lillqvist committed
812
    week_start = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
813

Tor Lillqvist's avatar
Tor Lillqvist committed
814
  if (week_start != NULL)
815
    {
Tor Lillqvist's avatar
Tor Lillqvist committed
816 817 818 819
      priv->week_start = (week_start[0] - '0' + 1) % 7;
      g_free(week_start);
    }
#else
820
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
821 822 823 824
  langinfo.string = nl_langinfo (_NL_TIME_FIRST_WEEKDAY);
  first_weekday = langinfo.string[0];
  langinfo.string = nl_langinfo (_NL_TIME_WEEK_1STDAY);
  week_origin = langinfo.word;
825 826 827 828
  if (week_origin == 19971130) /* Sunday */
    week_1stday = 0;
  else if (week_origin == 19971201) /* Monday */
    week_1stday = 1;
829
  else
830
    g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.");
831

832
  priv->week_start = (week_1stday + first_weekday - 1) % 7;
833
#else
834 835 836
  /* Translate to calendar:week_start:0 if you want Sunday to be the
   * first day of the week to calendar:week_start:1 if you want Monday
   * to be the first day of the week, and so on.
Matthias Clasen's avatar
Matthias Clasen committed
837
   */
838 839 840
  week_start = _("calendar:week_start:0");

  if (strncmp (week_start, "calendar:week_start:", 20) == 0)
841
    priv->week_start = *(week_start + 20) - '0';
Matthias Clasen's avatar
Matthias Clasen committed
842
  else
843
    priv->week_start = -1;
Matthias Clasen's avatar
Matthias Clasen committed
844

845
  if (priv->week_start < 0 || priv->week_start > 6)
846
    {
847
      g_warning ("Whoever translated calendar:week_start:0 did so wrongly.");
848
      priv->week_start = 0;
849
    }
850
#endif
851
#endif
852 853

  calendar_compute_days (calendar);
854 855
}

856 857 858 859

/****************************************
 *          Utility Functions           *
 ****************************************/
860

861 862 863
static void
calendar_queue_refresh (GtkCalendar *calendar)
{
864
  GtkCalendarPrivate *priv = calendar->priv;
865

866
  if (!(priv->detail_func) ||
867
      !(priv->display_flags & GTK_CALENDAR_SHOW_DETAILS) ||
868
       (priv->detail_width_chars && priv->detail_height_rows))
869 870 871 872 873
    gtk_widget_queue_draw (GTK_WIDGET (calendar));
  else
    gtk_widget_queue_resize (GTK_WIDGET (calendar));
}

874
static void
875
calendar_set_month_next (GtkCalendar *calendar)
876 877
{
  gint month_len;
878
  GtkCalendarPrivate *priv = calendar->priv;
879

880
  if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
881
    return;
882 883

  if (priv->month == 11)
884
    {
885 886 887 888 889 890
      priv->month = 0;
      priv->year++;
    }
  else
    priv->month++;

891
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
892
  g_signal_emit (calendar,
Matthias Clasen's avatar
Matthias Clasen committed
893 894
                 gtk_calendar_signals[NEXT_MONTH_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
895
  g_signal_emit (calendar,
Matthias Clasen's avatar
Matthias Clasen committed
896 897
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
898 899 900 901

  month_len = month_length[leap (priv->year)][priv->month + 1];

  if (month_len < priv->selected_day)
902
    {
903
      priv->selected_day = 0;
904 905 906
      gtk_calendar_select_day (calendar, month_len);
    }
  else
907
    gtk_calendar_select_day (calendar, priv->selected_day);
908

909
  calendar_queue_refresh (calendar);
910 911 912
}

static void
913
calendar_set_year_prev (GtkCalendar *calendar)
914
{
915
  GtkCalendarPrivate *priv = calendar->priv;
916
  gint month_len;
917

918
  priv->year--;
919
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
920
  g_signal_emit (calendar,
Matthias Clasen's avatar
Matthias Clasen committed
921 922
                 gtk_calendar_signals[PREV_YEAR_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
923
  g_signal_emit (calendar,
Matthias Clasen's avatar
Matthias Clasen committed
924 925
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
926 927 928 929

  month_len = month_length[leap (priv->year)][priv->month + 1];

  if (month_len < priv->selected_day)
930
    {
931
      priv->selected_day = 0;
932 933 934
      gtk_calendar_select_day (calendar, month_len);
    }
  else
935 936
    gtk_calendar_select_day (calendar, priv->selected_day);

937
  calendar_queue_refresh (calendar);
938 939 940
}

static void
941
calendar_set_year_next (GtkCalendar *calendar)
942
{
943
  GtkCalendarPrivate *priv = calendar->priv;
944
  gint month_len;
945

946
  priv->year++;
947
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
948
  g_signal_emit (calendar,
Matthias Clasen's avatar
Matthias Clasen committed
949 950
                 gtk_calendar_signals[NEXT_YEAR_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
951
  g_signal_emit (calendar,
Matthias Clasen's avatar
Matthias Clasen committed
952 953
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
954 955 956 957

  month_len = month_length[leap (priv->year)][priv->month + 1];

  if (month_len < priv->selected_day)
958
    {
959
      priv->selected_day = 0;
960 961 962
      gtk_calendar_select_day (calendar, month_len);
    }
  else
963 964
    gtk_calendar_select_day (calendar, priv->selected_day);

965
  calendar_queue_refresh (calendar);
966 967 968
}

static void
969
calendar_compute_days (GtkCalendar *calendar)
970
{
971
  GtkCalendarPrivate *priv = calendar->priv;
972 973 974 975 976 977 978
  gint month;
  gint year;
  gint ndays_in_month;
  gint ndays_in_prev_month;
  gint first_day;
  gint row;
  gint col;
979
  gint day;
980

981 982 983
  year = priv->year;
  month = priv->month + 1;

984
  ndays_in_month = month_length[leap (year)][month];
985

986 987
  first_day = day_of_week (year, month, 1);
  first_day = (first_day + 7 - priv->week_start) % 7;
988 989
  if (first_day == 0)
    first_day = 7;
990

991 992
  /* Compute days of previous month */
  if (month > 1)
993
    ndays_in_prev_month = month_length[leap (year)][month - 1];
994
  else
995
    ndays_in_prev_month = month_length[leap (year - 1)][12];
996
  day = ndays_in_prev_month - first_day+ 1;
Matthias Clasen's avatar
Matthias Clasen committed
997

998
  for (col = 0; col < first_day; col++)
999
    {