gtkcalendar.c 120 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
Matthias Clasen committed
5
 * Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson and Mattias Groenlund
6
 *
7 8 9 10
 * lib_date routines
 * Copyright (c) 1995, 1996, 1997, 1998 by Steffen Beyer
 *
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12 13 14 15 16
 * 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
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
Javier Jardón's avatar
Javier Jardón committed
21
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
22 23
 */

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

31 32 33 34 35
/**
 * SECTION:gtkcalendar
 * @Short_description: Displays a calendar and allows the user to select a date
 * @Title: GtkCalendar
 *
36 37
 * #GtkCalendar is a widget that displays a Gregorian calendar, one month
 * at a time. It can be created with gtk_calendar_new().
38 39
 *
 * The month and year currently displayed can be altered with
40 41
 * gtk_calendar_select_month(). The exact day can be selected from the
 * displayed month using gtk_calendar_select_day().
42
 *
43 44 45
 * 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().
46 47 48 49 50 51
 *
 * 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().
52 53 54 55
 *
 * 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.
56 57
 */

58
#include "config.h"
59 60

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

#include <glib.h>

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

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

/***************************************************************************/
85
/* The following date routines are taken from the lib_date package.
86 87 88
 * They have been minimally edited to avoid conflict with types defined
 * in win32 headers.
 */
89

90
static const guint month_length[2][13] =
91
{
92 93
  { 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 }
94 95
};

96
static const guint days_in_months[2][14] =
97
{
98 99
  { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
  { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
100 101
};

102 103 104
static glong  calc_days(guint year, guint mm, guint dd);
static guint  day_of_week(guint year, guint mm, guint dd);
static glong  dates_difference(guint year1, guint mm1, guint dd1,
105
                               guint year2, guint mm2, guint dd2);
106
static guint  weeks_in_year(guint year);
107

108
static gboolean
109
leap (guint year)
110
{
111
  return((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
112 113
}

114
static guint
115
day_of_week (guint year, guint mm, guint dd)
116
{
117
  glong  days;
118

119 120
  days = calc_days(year, mm, dd);
  if (days > 0L)
121
    {
122 123 124
      days--;
      days %= 7L;
      days++;
125
    }
126
  return( (guint) days );
127 128
}

129
static guint weeks_in_year(guint year)
130
{
131
  return(52 + ((day_of_week(year,1,1)==4) || (day_of_week(year,12,31)==4)));
132 133
}

134
static gboolean
135
check_date(guint year, guint mm, guint dd)
136
{
137 138 139 140
  if (year < 1) return FALSE;
  if ((mm < 1) || (mm > 12)) return FALSE;
  if ((dd < 1) || (dd > month_length[leap(year)][mm])) return FALSE;
  return TRUE;
141 142
}

143
static guint
144
week_number(guint year, guint mm, guint dd)
145
{
146
  guint first;
147

148
  first = day_of_week(year,1,1) - 1;
149
  return( (guint) ( (dates_difference(year,1,1, year,mm,dd) + first) / 7L ) +
150
          (first < 4) );
151 152
}

153
static glong
154
year_to_days(guint year)
155
{
156
  return( year * 365L + (year / 4) - (year / 100) + (year / 400) );
157 158 159
}


160
static glong
161
calc_days(guint year, guint mm, guint dd)
162
{
163
  gboolean lp;
164

165
  if (year < 1) return(0L);
166 167
  if ((mm < 1) || (mm > 12)) return(0L);
  if ((dd < 1) || (dd > month_length[(lp = leap(year))][mm])) return(0L);
168
  return( year_to_days(--year) + days_in_months[lp][mm] + dd );
169 170
}

171
static gboolean
172
week_of_year(guint *week, guint *year, guint mm, guint dd)
173
{
174 175 176
  if (check_date(*year,mm,dd))
    {
      *week = week_number(*year,mm,dd);
177 178
      if (*week == 0)
        *week = weeks_in_year(--(*year));
179
      else if (*week > weeks_in_year(*year))
180 181 182 183
        {
          *week = 1;
          (*year)++;
        }
184
      return TRUE;
185
    }
186
  return FALSE;
187 188
}

189
static glong
190
dates_difference(guint year1, guint mm1, guint dd1,
191
                 guint year2, guint mm2, guint dd2)
192
{
193
  return( calc_days(year2, mm2, dd2) - calc_days(year1, mm1, dd1) );
194 195
}

196
/*** END OF lib_date routines ********************************************/
197

198
/* Spacing around day/week headers and main area, inside those windows */
199
#define CALENDAR_MARGIN          0
200

201 202
#define DAY_XSEP                 0 /* not really good for small calendar */
#define DAY_YSEP                 0 /* not really good for small calendar */
203

204 205
#define SCROLL_DELAY_FACTOR      5

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
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
};

230 231 232 233 234 235 236 237 238 239
enum
{
  PROP_0,
  PROP_YEAR,
  PROP_MONTH,
  PROP_DAY,
  PROP_SHOW_HEADING,
  PROP_SHOW_DAY_NAMES,
  PROP_NO_MONTH_CHANGE,
  PROP_SHOW_WEEK_NUMBERS,
240
  PROP_SHOW_DETAILS,
241
  PROP_DETAIL_WIDTH_CHARS,
242
  PROP_DETAIL_HEIGHT_ROWS
243 244
};

245
static guint gtk_calendar_signals[LAST_SIGNAL] = { 0 };
246

247
struct _GtkCalendarPrivate
248
{
249 250
  GtkCalendarDisplayOptions display_flags;

251 252 253
  GdkWindow *main_win;
  GdkWindow *arrow_win[4];

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
  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;

269 270 271
  guint header_h;
  guint day_name_h;
  guint main_h;
272

273
  guint arrow_prelight : 4;
274 275 276
  guint arrow_width;
  guint max_month_width;
  guint max_year_width;
277

278 279 280 281 282 283 284 285 286 287
  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;
288

289
  /* flags */
290 291
  guint year_before : 1;

292 293
  guint need_timer  : 1;

294 295 296
  guint in_drag : 1;
  guint drag_highlight : 1;

297 298
  guint32 timer;
  gint click_child;
299 300

  gint week_start;
301 302 303

  gint drag_start_x;
  gint drag_start_y;
304 305 306 307 308 309 310 311 312

  /* 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;
313
  gint detail_overflow[6];
314 315
};

316 317 318
#define GTK_CALENDAR_GET_PRIVATE(widget)  (GTK_CALENDAR (widget)->priv)

static void gtk_calendar_finalize     (GObject      *calendar);
319
static void gtk_calendar_destroy      (GtkWidget    *widget);
320
static void gtk_calendar_set_property (GObject      *object,
321 322 323
                                       guint         prop_id,
                                       const GValue *value,
                                       GParamSpec   *pspec);
324
static void gtk_calendar_get_property (GObject      *object,
325 326 327
                                       guint         prop_id,
                                       GValue       *value,
                                       GParamSpec   *pspec);
328 329 330

static void     gtk_calendar_realize        (GtkWidget        *widget);
static void     gtk_calendar_unrealize      (GtkWidget        *widget);
331 332
static void     gtk_calendar_map            (GtkWidget        *widget);
static void     gtk_calendar_unmap          (GtkWidget        *widget);
333 334 335 336 337 338
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);
339
static void     gtk_calendar_size_allocate  (GtkWidget        *widget,
340
                                             GtkAllocation    *allocation);
341 342
static gboolean gtk_calendar_draw           (GtkWidget        *widget,
                                             cairo_t          *cr);
343
static gboolean gtk_calendar_button_press   (GtkWidget        *widget,
344
                                             GdkEventButton   *event);
345
static gboolean gtk_calendar_button_release (GtkWidget        *widget,
346
                                             GdkEventButton   *event);
347
static gboolean gtk_calendar_motion_notify  (GtkWidget        *widget,
348
                                             GdkEventMotion   *event);
349
static gboolean gtk_calendar_enter_notify   (GtkWidget        *widget,
350
                                             GdkEventCrossing *event);
351
static gboolean gtk_calendar_leave_notify   (GtkWidget        *widget,
352
                                             GdkEventCrossing *event);
353
static gboolean gtk_calendar_scroll         (GtkWidget        *widget,
354
                                             GdkEventScroll   *event);
355
static gboolean gtk_calendar_key_press      (GtkWidget        *widget,
356
                                             GdkEventKey      *event);
357
static gboolean gtk_calendar_focus_out      (GtkWidget        *widget,
358
                                             GdkEventFocus    *event);
359
static void     gtk_calendar_grab_notify    (GtkWidget        *widget,
360
                                             gboolean          was_grabbed);
361 362
static void     gtk_calendar_state_flags_changed  (GtkWidget     *widget,
                                                   GtkStateFlags  previous_state);
363
static gboolean gtk_calendar_query_tooltip  (GtkWidget        *widget,
364 365 366 367
                                             gint              x,
                                             gint              y,
                                             gboolean          keyboard_mode,
                                             GtkTooltip       *tooltip);
368 369

static void     gtk_calendar_drag_data_get      (GtkWidget        *widget,
370 371 372 373
                                                 GdkDragContext   *context,
                                                 GtkSelectionData *selection_data,
                                                 guint             info,
                                                 guint             time);
374
static void     gtk_calendar_drag_data_received (GtkWidget        *widget,
375 376 377 378 379 380
                                                 GdkDragContext   *context,
                                                 gint              x,
                                                 gint              y,
                                                 GtkSelectionData *selection_data,
                                                 guint             info,
                                                 guint             time);
381
static gboolean gtk_calendar_drag_motion        (GtkWidget        *widget,
382 383 384 385
                                                 GdkDragContext   *context,
                                                 gint              x,
                                                 gint              y,
                                                 guint             time);
386
static void     gtk_calendar_drag_leave         (GtkWidget        *widget,
387 388
                                                 GdkDragContext   *context,
                                                 guint             time);
389
static gboolean gtk_calendar_drag_drop          (GtkWidget        *widget,
390 391 392 393
                                                 GdkDragContext   *context,
                                                 gint              x,
                                                 gint              y,
                                                 guint             time);
394

Matthias Clasen's avatar
Matthias Clasen committed
395

396
static void calendar_start_spinning (GtkCalendar *calendar,
397
                                     gint         click_child);
398 399 400
static void calendar_stop_spinning  (GtkCalendar *calendar);

static void calendar_invalidate_day     (GtkCalendar *widget,
401 402
                                         gint       row,
                                         gint       col);
403
static void calendar_invalidate_day_num (GtkCalendar *widget,
404
                                         gint       day);
405
static void calendar_invalidate_arrow   (GtkCalendar *widget,
406
                                         guint      arrow);
407 408

static void calendar_compute_days      (GtkCalendar *calendar);
409 410
static gint calendar_get_xsep          (GtkCalendar *calendar);
static gint calendar_get_ysep          (GtkCalendar *calendar);
411
static gint calendar_get_inner_border  (GtkCalendar *calendar);
412

413 414 415
static char    *default_abbreviated_dayname[7];
static char    *default_monthname[12];

416
G_DEFINE_TYPE (GtkCalendar, gtk_calendar, GTK_TYPE_WIDGET)
417 418 419 420

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
421
  GObjectClass   *gobject_class;
422
  GtkWidgetClass *widget_class;
423 424

  gobject_class = (GObjectClass*)  class;
425
  widget_class = (GtkWidgetClass*) class;
426

427 428
  gobject_class->set_property = gtk_calendar_set_property;
  gobject_class->get_property = gtk_calendar_get_property;
429
  gobject_class->finalize = gtk_calendar_finalize;
430

431
  widget_class->destroy = gtk_calendar_destroy;
432 433
  widget_class->realize = gtk_calendar_realize;
  widget_class->unrealize = gtk_calendar_unrealize;
434 435
  widget_class->map = gtk_calendar_map;
  widget_class->unmap = gtk_calendar_unmap;
436
  widget_class->draw = gtk_calendar_draw;
437 438
  widget_class->get_preferred_width = gtk_calendar_get_preferred_width;
  widget_class->get_preferred_height = gtk_calendar_get_preferred_height;
439 440
  widget_class->size_allocate = gtk_calendar_size_allocate;
  widget_class->button_press_event = gtk_calendar_button_press;
441
  widget_class->button_release_event = gtk_calendar_button_release;
442 443 444 445
  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;
446
  widget_class->scroll_event = gtk_calendar_scroll;
447
  widget_class->state_flags_changed = gtk_calendar_state_flags_changed;
448
  widget_class->grab_notify = gtk_calendar_grab_notify;
449
  widget_class->focus_out_event = gtk_calendar_focus_out;
450
  widget_class->query_tooltip = gtk_calendar_query_tooltip;
451 452 453 454 455 456

  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;
457

458
  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_CALENDAR);
Matthias Clasen's avatar
Matthias Clasen committed
459

460 461 462
  /**
   * GtkCalendar:year:
   *
463
   * The selected year.
464
   * This property gets initially set to the current year.
465
   */
466 467 468
  g_object_class_install_property (gobject_class,
                                   PROP_YEAR,
                                   g_param_spec_int ("year",
469 470 471 472
                                                     P_("Year"),
                                                     P_("The selected year"),
                                                     0, G_MAXINT >> 9, 0,
                                                     GTK_PARAM_READWRITE));
473 474 475 476

  /**
   * GtkCalendar:month:
   *
477
   * The selected month (as a number between 0 and 11).
478 479
   * This property gets initially set to the current month.
   */
480 481 482
  g_object_class_install_property (gobject_class,
                                   PROP_MONTH,
                                   g_param_spec_int ("month",
483 484 485 486
                                                     P_("Month"),
                                                     P_("The selected month (as a number between 0 and 11)"),
                                                     0, 11, 0,
                                                     GTK_PARAM_READWRITE));
487 488 489 490

  /**
   * GtkCalendar:day:
   *
491
   * The selected day (as a number between 1 and 31, or 0
492 493 494
   * to unselect the currently selected day).
   * This property gets initially set to the current day.
   */
495 496 497
  g_object_class_install_property (gobject_class,
                                   PROP_DAY,
                                   g_param_spec_int ("day",
498 499 500 501
                                                     P_("Day"),
                                                     P_("The selected day (as a number between 1 and 31, or 0 to unselect the currently selected day)"),
                                                     0, 31, 0,
                                                     GTK_PARAM_READWRITE));
502 503 504 505 506 507 508 509

/**
 * GtkCalendar:show-heading:
 *
 * Determines whether a heading is displayed.
 *
 * Since: 2.4
 */
510 511
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_HEADING,
512
                                   g_param_spec_boolean ("show-heading",
513 514 515 516
                                                         P_("Show Heading"),
                                                         P_("If TRUE, a heading is displayed"),
                                                         TRUE,
                                                         GTK_PARAM_READWRITE));
517 518 519 520 521 522 523 524

/**
 * GtkCalendar:show-day-names:
 *
 * Determines whether day names are displayed.
 *
 * Since: 2.4
 */
525 526
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_DAY_NAMES,
527
                                   g_param_spec_boolean ("show-day-names",
528 529 530 531
                                                         P_("Show Day Names"),
                                                         P_("If TRUE, day names are displayed"),
                                                         TRUE,
                                                         GTK_PARAM_READWRITE));
532 533 534 535 536 537 538
/**
 * GtkCalendar:no-month-change:
 *
 * Determines whether the selected month can be changed.
 *
 * Since: 2.4
 */
539 540
  g_object_class_install_property (gobject_class,
                                   PROP_NO_MONTH_CHANGE,
541
                                   g_param_spec_boolean ("no-month-change",
542 543 544 545
                                                         P_("No Month Change"),
                                                         P_("If TRUE, the selected month cannot be changed"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));
546 547 548 549 550 551 552 553

/**
 * GtkCalendar:show-week-numbers:
 *
 * Determines whether week numbers are displayed.
 *
 * Since: 2.4
 */
554 555
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_WEEK_NUMBERS,
556
                                   g_param_spec_boolean ("show-week-numbers",
557 558 559 560
                                                         P_("Show Week Numbers"),
                                                         P_("If TRUE, week numbers are displayed"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));
561

562 563 564 565 566 567
/**
 * GtkCalendar:detail-width-chars:
 *
 * Width of a detail cell, in characters.
 * A value of 0 allows any width. See gtk_calendar_set_detail_func().
 *
568
 * Since: 2.14
569 570 571 572
 */
  g_object_class_install_property (gobject_class,
                                   PROP_DETAIL_WIDTH_CHARS,
                                   g_param_spec_int ("detail-width-chars",
573 574 575 576
                                                     P_("Details Width"),
                                                     P_("Details width in characters"),
                                                     0, 127, 0,
                                                     GTK_PARAM_READWRITE));
577 578 579 580 581 582 583

/**
 * GtkCalendar:detail-height-rows:
 *
 * Height of a detail cell, in rows.
 * A value of 0 allows any width. See gtk_calendar_set_detail_func().
 *
584
 * Since: 2.14
585 586 587 588
 */
  g_object_class_install_property (gobject_class,
                                   PROP_DETAIL_HEIGHT_ROWS,
                                   g_param_spec_int ("detail-height-rows",
589 590 591 592
                                                     P_("Details Height"),
                                                     P_("Details height in rows"),
                                                     0, 127, 0,
                                                     GTK_PARAM_READWRITE));
593

594 595 596 597 598 599 600
/**
 * 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.
 *
601
 * Since: 2.14
602 603 604 605
 */
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_DETAILS,
                                   g_param_spec_boolean ("show-details",
606 607 608 609
                                                         P_("Show Details"),
                                                         P_("If TRUE, details are shown"),
                                                         TRUE,
                                                         GTK_PARAM_READWRITE));
610

611 612

  /**
613
   * GtkCalendar:inner-border:
614 615 616 617 618 619 620 621 622 623 624
   *
   * 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));

  /**
625
   * GtkCalndar:vertical-separation:
626 627 628 629 630 631 632 633 634 635 636
   *
   * 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));

  /**
637
   * GtkCalendar:horizontal-separation:
638 639 640 641 642 643 644 645 646 647
   *
   * 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));

648 649 650 651 652 653 654
  /**
   * 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.
   */
655
  gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
656
    g_signal_new (I_("month-changed"),
657 658 659 660 661 662
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, month_changed),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
663 664 665 666 667 668 669

  /**
   * GtkCalendar::day-selected:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user selects a day.
   */
670
  gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
671
    g_signal_new (I_("day-selected"),
672 673 674 675 676 677
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, day_selected),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
678 679 680 681 682 683 684

  /**
   * GtkCalendar::day-selected-double-click:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user double-clicks a day.
   */
685
  gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
686
    g_signal_new (I_("day-selected-double-click"),
687 688 689 690 691 692
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, day_selected_double_click),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
693 694 695 696 697 698 699

  /**
   * GtkCalendar::prev-month:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user switched to the previous month.
   */
700
  gtk_calendar_signals[PREV_MONTH_SIGNAL] =
701
    g_signal_new (I_("prev-month"),
702 703 704 705 706 707
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, prev_month),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
708 709 710 711 712 713 714

  /**
   * GtkCalendar::next-month:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user switched to the next month.
   */
715
  gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
716
    g_signal_new (I_("next-month"),
717 718 719 720 721 722
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, next_month),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
723 724 725 726 727 728 729

  /**
   * GtkCalendar::prev-year:
   * @calendar: the object which received the signal.
   *
   * Emitted when user switched to the previous year.
   */
730
  gtk_calendar_signals[PREV_YEAR_SIGNAL] =
731
    g_signal_new (I_("prev-year"),
732 733 734 735 736 737
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, prev_year),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
738 739 740 741 742 743 744

  /**
   * GtkCalendar::next-year:
   * @calendar: the object which received the signal.
   *
   * Emitted when user switched to the next year.
   */
745
  gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
746
    g_signal_new (I_("next-year"),
747 748 749 750 751 752 753
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, next_year),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

754
  g_type_class_add_private (gobject_class, sizeof (GtkCalendarPrivate));
755 756 757 758 759
}

static void
gtk_calendar_init (GtkCalendar *calendar)
{
760
  GtkWidget *widget = GTK_WIDGET (calendar);
761 762 763
  time_t secs;
  struct tm *tm;
  gint i;
764 765 766
#ifdef G_OS_WIN32
  wchar_t wbuffer[100];
#else
767
  char buffer[255];
768
  time_t tmp_time;
769
#endif
770
  GtkCalendarPrivate *priv;
771
  gchar *year_before;
772
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
773
  union { unsigned int word; char *string; } langinfo;
774 775
  gint week_1stday = 0;
  gint first_weekday = 1;
776
  guint week_origin;
777 778 779
#else
  gchar *week_start;
#endif
780

781
  priv = calendar->priv = G_TYPE_INSTANCE_GET_PRIVATE (calendar,
782 783
                                                       GTK_TYPE_CALENDAR,
                                                       GtkCalendarPrivate);
784

785
  gtk_widget_set_can_focus (widget, TRUE);
786
  gtk_widget_set_has_window (widget, FALSE);
787

788 789 790
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
791
#ifndef G_OS_WIN32
792 793 794
        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);
795
#else
796 797 798 799 800
        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);
801
#endif
802
      }
803

804 805 806
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
807
#ifndef G_OS_WIN32
808 809 810
        tmp_time=i*2764800;
        strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
        default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
811
#else
812 813 814 815 816
        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);
817
#endif
818
      }
819

820 821 822
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
823 824
  priv->month = tm->tm_mon;
  priv->year  = 1900 + tm->tm_year;
825

826
  for (i=0;i<31;i++)
827 828 829
    priv->marked_date[i] = FALSE;
  priv->num_marked_dates = 0;
  priv->selected_day = tm->tm_mday;
830

831
  priv->display_flags = (GTK_CALENDAR_SHOW_HEADING |
832 833 834
                             GTK_CALENDAR_SHOW_DAY_NAMES |
                             GTK_CALENDAR_SHOW_DETAILS);

835 836
  priv->focus_row = -1;
  priv->focus_col = -1;
837

838 839 840 841
  priv->max_year_width = 0;
  priv->max_month_width = 0;
  priv->max_day_char_width = 0;
  priv->max_week_char_width = 0;
842

843 844 845 846
  priv->max_day_char_ascent = 0;
  priv->max_day_char_descent = 0;
  priv->max_label_char_ascent = 0;
  priv->max_label_char_descent = 0;
847

848
  priv->arrow_width = 10;
849

850 851 852
  priv->need_timer = 0;
  priv->timer = 0;
  priv->click_child = -1;
853

854 855
  priv->in_drag = 0;
  priv->drag_highlight = 0;
856

857 858
  gtk_drag_dest_set (widget, 0, NULL, 0, GDK_ACTION_COPY);
  gtk_drag_dest_add_text_targets (widget);
859

860
  priv->year_before = 0;
861 862 863 864 865 866

  /* 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.
   *
867 868 869 870
   * 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.
871 872 873
   */
  year_before = _("calendar:MY");
  if (strcmp (year_before, "calendar:YM") == 0)
874
    priv->year_before = 1;
875 876
  else if (strcmp (year_before, "calendar:MY") != 0)
    g_warning ("Whoever translated calendar:MY did so wrongly.\n");
877

878
#ifdef G_OS_WIN32
Tor Lillqvist's avatar
Tor Lillqvist committed
879 880
  priv->week_start = 0;
  week_start = NULL;
881

Tor Lillqvist's avatar
Tor Lillqvist committed
882
  if (GetLocaleInfoW (GetThreadLocale (), LOCALE_IFIRSTDAYOFWEEK,
883
                      wbuffer, G_N_ELEMENTS (wbuffer)))
Tor Lillqvist's avatar
Tor Lillqvist committed
884
    week_start = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
885

Tor Lillqvist's avatar
Tor Lillqvist committed
886
  if (week_start != NULL)
887
    {
Tor Lillqvist's avatar
Tor Lillqvist committed
888 889 890 891
      priv->week_start = (week_start[0] - '0' + 1) % 7;
      g_free(week_start);
    }
#else
892
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
893 894 895 896
  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;
897 898 899 900
  if (week_origin == 19971130) /* Sunday */
    week_1stday = 0;
  else if (week_origin == 19971201) /* Monday */
    week_1stday = 1;
901 902 903
  else
    g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.\n");

904
  priv->week_start = (week_1stday + first_weekday - 1) % 7;
905
#else
906 907 908
  /* 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.
909
   */
910 911 912
  week_start = _("calendar:week_start:0");

  if (strncmp (week_start, "calendar:week_start:", 20) == 0)
913
    priv->week_start = *(week_start + 20) - '0';
914
  else
915
    priv->week_start = -1;
916

917
  if (priv->week_start < 0 || priv->week_start > 6)
918 919
    {
      g_warning ("Whoever translated calendar:week_start:0 did so wrongly.\n");
920
      priv->week_start = 0;
921
    }
922
#endif
923
#endif
924 925

  calendar_compute_days (calendar);
926 927
}

928 929 930 931

/****************************************
 *          Utility Functions           *
 ****************************************/
932

933 934 935 936 937
static void
calendar_queue_refresh (GtkCalendar *calendar)
{
  GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);

938
  if (!(priv->detail_func) ||
939
      !(priv->display_flags & GTK_CALENDAR_SHOW_DETAILS) ||
940
       (priv->detail_width_chars && priv->detail_height_rows))
941 942 943 944 945
    gtk_widget_queue_draw (GTK_WIDGET (calendar));
  else
    gtk_widget_queue_resize (GTK_WIDGET (calendar));
}

946
static void
947
calendar_set_month_next (GtkCalendar *calendar)
948 949
{
  gint month_len;
950
  GtkCalendarPrivate *priv = calendar->priv;
951

952
  if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
953
    return;
954 955

  if (priv->month == 11)
956
    {
957 958 959 960 961 962
      priv->month = 0;
      priv->year++;
    }
  else
    priv->month++;

963
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
964
  g_signal_emit (calendar,
965 966
                 gtk_calendar_signals[NEXT_MONTH_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
967
  g_signal_emit (calendar,
968 969
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
970 971 972 973

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

  if (month_len < priv->selected_day)
974
    {
975
      priv->selected_day = 0;
976 977 978
      gtk_calendar_select_day (calendar, month_len);
    }
  else
979
    gtk_calendar_select_day (calendar, priv->selected_day);
980

981
  calendar_queue_refresh (calendar);
982 983 984
}

static void
985
calendar_set_year_prev (GtkCalendar *calendar)
986
{
987
  GtkCalendarPrivate *priv = calendar->priv;
988
  gint month_len;
989

990
  priv->year--;
991
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
992
  g_signal_emit (calendar,
993 994
                 gtk_calendar_signals[PREV_YEAR_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
995
  g_signal_emit (calendar,
996 997
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
998 999 1000 1001

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

  if (month_len < priv->selected_day)
1002
    {
1003
      priv->selected_day = 0;
1004 1005 1006
      gtk_calendar_select_day (calendar, month_len);
    }
  else
1007 1008
    gtk_calendar_select_day (calendar, priv->selected_day);

1009
  calendar_queue_refresh (calendar);
1010 1011 1012
}

static void
1013
calendar_set_year_next (GtkCalendar *calendar)
1014
{
1015
  GtkCalendarPrivate *priv = calendar->priv;
1016
  gint month_len;
1017

1018
  priv->year++;
1019
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
1020
  g_signal_emit (calendar,
1021 1022
                 gtk_calendar_signals[NEXT_YEAR_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
1023
  g_signal_emit (calendar,
1024 1025
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
1026 1027 1028 1029

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

  if (month_len < priv->selected_day)
1030
    {
1031
      priv->selected_day = 0;
1032 1033 1034
      gtk_calendar_select_day (calendar, month_len);
    }
  else
1035 1036
    gtk_calendar_select_day (calendar, priv->selected_day);

1037
  calendar_queue_refresh (calendar);
1038 1039 1040
}

static void
1041
calendar_compute_days (GtkCalendar *calendar)
1042
{
1043
  GtkCalendarPrivate *priv = calendar->priv;
1044 1045 1046 1047 1048 1049 1050
  gint month;
  gint year;
  gint ndays_in_month;
  gint ndays_in_prev_month;
  gint first_day;
  gint row;
  gint col;
1051
  gint day;
1052

1053 1054 1055
  year = priv->year;
  month = priv->month + 1;

1056
  ndays_in_month = month_length[leap (year)][month];
1057

1058 1059
  first_day = day_of_week (year, month, 1);
  first_day = (first_day + 7 - priv->week_start) % 7;
1060

1061 1062 1063 1064 1065 1066
  /* Compute days of previous month */
  if (month > 1)
    ndays_in_prev_month = month_length[leap (year)][month-1];
  else
    ndays_in_prev_month = month_length[leap (year)][12];
  day = ndays_in_prev_month - first_day + 1;
1067

1068 1069
  row = 0;
  if (first_day > 0)
1070
    {
1071
      for (col = 0; col < first_day; col++)
1072 1073 1074 1075 1076
        {
          priv->day[row][col] = day;
          priv->day_month[row][col] = MONTH_PREV;
          day++;
        }
1077
    }
1078

1079 1080 1081 1082
  /* Compute days of current month */
  col = first_day;
  for (day = 1; day <= ndays_in_month; day++)
    {
1083 1084 1085
      priv->day[row][col] = day;
      priv->day_month[row][col] = MONTH_CURRENT;

1086 1087
      col++;
      if (col == 7)
1088 1089 1090 1091
        {
          row++;
          col = 0;
        }
1092
    }
1093

1094 1095 1096
  /* Compute days of next month */
  day = 1;
  for (; row <= 5; row++)
1097
    {
1098
      for (; col <= 6; col++)
1099 1100 1101 1102 1103
        {
          priv->day[row][col] = day;
          priv->day_month[row][col] = MONTH_NEXT;
          day++;
        }
1104
      col = 0;
1105 1106 1107 1108
    }
}

static void
1109
calendar_select_and_focus_day (GtkCalendar *calendar,
1110
                               guint        day)
1111
{
1112 1113 1114
  GtkCalendarPrivate *priv = calendar->priv;
  gint old_focus_row = priv->focus_row;
  gint old_focus_col = priv->focus_col;
1115 1116
  gint row;
  gint col;
1117

1118 1119 1120
  for (row = 0; row < 6; row ++)
    for (col = 0; col < 7; col++)
      {
1121 1122 1123 1124 1125 1126
        if (priv->day_month[row][col] == MONTH_CURRENT
            && priv->day[row][col] == day)
          {
            priv->focus_row = row;
            priv->focus_col = col;
          }
1127 1128 1129 1130
      }

  if (old_focus_row != -1 && old_focus_col != -1)
    calendar_invalidate_day (calendar, old_focus_row, old_focus_col);
1131

1132 1133
  gtk_calendar_select_day (calendar, day);
}
1134

1135 1136 1137 1138 1139 1140 1141 1142

/****************************************
 *     Layout computation utilities     *
 ****************************************/

static gint
calendar_row_height (GtkCalendar *calendar)
{
1143 1144
  GtkCalendarPrivate *priv = calendar->priv;

1145
  return (GTK_CALENDAR_GET_PRIVATE (calendar)->main_h - CALENDAR_MARGIN
1146 1147
          - ((priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
             ? calendar_get_ysep (calendar) : CALENDAR_MARGIN)) / 6;
1148 1149
}

1150 1151 1152 1153 1154

/* calendar_left_x_for_column: returns the x coordinate
 * for the left of the column */
static gint
calendar_left_x_for_column (GtkCalendar *calendar,
1155
                            gint         column)
1156
{
1157
  GtkCalendarPrivate *priv = calendar->priv;
1158 1159
  gint width;
  gint x_left;
1160
  gint week_width;
1161
  gint calendar_xsep = calendar_get_xsep (calendar);
1162 1163
  GtkStyleContext *context;
  GtkStateFlags state;
1164
  gint inner_border = calendar_get_inner_border (calendar);
1165
  GtkBorder padding;
1166

1167 1168 1169
  context = gtk_widget_get_style_context (GTK_WIDGET (calendar));
  state = gtk_widget_get_state_flags (GTK_WIDGET (calendar));
  gtk_style_context_get_padding (context, state, &padding);
1170

1171
  week_width = priv->week_width + padding.left + inner_border;
1172

1173
  if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
1174 1175 1176 1177
    {
      column = 6 - column;
      week_width = 0;
    }
1178 1179

  width = GTK_CALENDAR_GET_PRIVATE (calendar)->day_width;
1180
  if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
1181
    x_left = week_width + calendar_xsep + (width + DAY_XSEP) * column;
1182
  else
1183
    x_left = week_width + CALENDAR_MARGIN + (width + DAY_XSEP) * column;
1184