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
Matthias Clasen committed
5
 * Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson and Mattias Groenlund
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
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
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 "gtkintl.h"
77 78
#include "gtkmain.h"
#include "gtkmarshalers.h"
79
#include "gtktooltip.h"
80
#include "gtkprivate.h"
81
#include "gtkrender.h"
82

83 84 85
#define TIMEOUT_INITIAL  500
#define TIMEOUT_REPEAT    50

86
static const guint month_length[2][13] =
87
{
88 89
  { 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 }
90 91
};

92
static gboolean
93
leap (guint year)
94
{
95
  return ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
96 97
}

98
static guint
99
day_of_week (guint year, guint mm, guint dd)
100
{
101 102
  GDateTime *dt;
  guint days;
103

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

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

111
  return days;
112 113
}

114
static guint
115
week_of_year (guint year, guint mm, guint dd)
116
{
117 118
  GDateTime *dt;
  guint week;
119

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

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

127
  return week;
128 129
}

130
/* Spacing around day/week headers and main area, inside those windows */
131
#define CALENDAR_MARGIN          0
132

133 134
#define DAY_XSEP                 0 /* not really good for small calendar */
#define DAY_YSEP                 0 /* not really good for small calendar */
135

136 137
#define SCROLL_DELAY_FACTOR      5

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
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
};

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

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

179
struct _GtkCalendarPrivate
180
{
181 182
  GtkCalendarDisplayOptions display_flags;

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

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
  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;

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

205
  guint arrow_prelight : 4;
206 207 208
  guint arrow_width;
  guint max_month_width;
  guint max_year_width;
209

210 211 212 213 214 215 216 217 218 219
  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;
220

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

224 225
  guint need_timer  : 1;

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

229 230
  guint32 timer;
  gint click_child;
231 232

  gint week_start;
233 234 235

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

  /* 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;
245
  gint detail_overflow[6];
246 247
};

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

static void     gtk_calendar_realize        (GtkWidget        *widget);
static void     gtk_calendar_unrealize      (GtkWidget        *widget);
261 262
static void     gtk_calendar_map            (GtkWidget        *widget);
static void     gtk_calendar_unmap          (GtkWidget        *widget);
263 264 265 266 267 268
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);
269
static void     gtk_calendar_size_allocate  (GtkWidget        *widget,
270
                                             GtkAllocation    *allocation);
271 272
static gboolean gtk_calendar_draw           (GtkWidget        *widget,
                                             cairo_t          *cr);
273
static gboolean gtk_calendar_button_press   (GtkWidget        *widget,
274
                                             GdkEventButton   *event);
275
static gboolean gtk_calendar_button_release (GtkWidget        *widget,
276
                                             GdkEventButton   *event);
277
static gboolean gtk_calendar_motion_notify  (GtkWidget        *widget,
278
                                             GdkEventMotion   *event);
279
static gboolean gtk_calendar_enter_notify   (GtkWidget        *widget,
280
                                             GdkEventCrossing *event);
281
static gboolean gtk_calendar_leave_notify   (GtkWidget        *widget,
282
                                             GdkEventCrossing *event);
283
static gboolean gtk_calendar_scroll         (GtkWidget        *widget,
284
                                             GdkEventScroll   *event);
285
static gboolean gtk_calendar_key_press      (GtkWidget        *widget,
286
                                             GdkEventKey      *event);
287
static gboolean gtk_calendar_focus_out      (GtkWidget        *widget,
288
                                             GdkEventFocus    *event);
289
static void     gtk_calendar_grab_notify    (GtkWidget        *widget,
290
                                             gboolean          was_grabbed);
291 292
static void     gtk_calendar_state_flags_changed  (GtkWidget     *widget,
                                                   GtkStateFlags  previous_state);
293
static gboolean gtk_calendar_query_tooltip  (GtkWidget        *widget,
294 295 296 297
                                             gint              x,
                                             gint              y,
                                             gboolean          keyboard_mode,
                                             GtkTooltip       *tooltip);
298 299

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

Matthias Clasen's avatar
Matthias Clasen committed
325

326
static void calendar_start_spinning (GtkCalendar *calendar,
327
                                     gint         click_child);
328 329 330
static void calendar_stop_spinning  (GtkCalendar *calendar);

static void calendar_invalidate_day     (GtkCalendar *widget,
331 332
                                         gint       row,
                                         gint       col);
333
static void calendar_invalidate_day_num (GtkCalendar *widget,
334
                                         gint       day);
335
static void calendar_invalidate_arrow   (GtkCalendar *widget,
336
                                         guint      arrow);
337 338

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

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

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

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

  gobject_class = (GObjectClass*)  class;
355
  widget_class = (GtkWidgetClass*) class;
356

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

361
  widget_class->destroy = gtk_calendar_destroy;
362 363
  widget_class->realize = gtk_calendar_realize;
  widget_class->unrealize = gtk_calendar_unrealize;
364 365
  widget_class->map = gtk_calendar_map;
  widget_class->unmap = gtk_calendar_unmap;
366
  widget_class->draw = gtk_calendar_draw;
367 368
  widget_class->get_preferred_width = gtk_calendar_get_preferred_width;
  widget_class->get_preferred_height = gtk_calendar_get_preferred_height;
369 370
  widget_class->size_allocate = gtk_calendar_size_allocate;
  widget_class->button_press_event = gtk_calendar_button_press;
371
  widget_class->button_release_event = gtk_calendar_button_release;
372 373 374 375
  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;
376
  widget_class->scroll_event = gtk_calendar_scroll;
377
  widget_class->state_flags_changed = gtk_calendar_state_flags_changed;
378
  widget_class->grab_notify = gtk_calendar_grab_notify;
379
  widget_class->focus_out_event = gtk_calendar_focus_out;
380
  widget_class->query_tooltip = gtk_calendar_query_tooltip;
381 382 383 384 385 386

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

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

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

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

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

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

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

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

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

522 523 524 525 526 527 528
/**
 * 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.
 *
529
 * Since: 2.14
530 531 532 533
 */
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_DETAILS,
                                   g_param_spec_boolean ("show-details",
534 535 536
                                                         P_("Show Details"),
                                                         P_("If TRUE, details are shown"),
                                                         TRUE,
537
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
538

539 540

  /**
541
   * GtkCalendar:inner-border:
542 543 544 545 546 547 548 549 550 551 552
   *
   * 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));

  /**
553
   * GtkCalndar:vertical-separation:
554 555 556 557 558 559 560 561 562 563 564
   *
   * 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));

  /**
565
   * GtkCalendar:horizontal-separation:
566 567 568 569 570 571 572 573 574 575
   *
   * 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));

576 577 578 579 580 581 582
  /**
   * 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.
   */
583
  gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
584
    g_signal_new (I_("month-changed"),
585 586 587 588 589 590
                  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);
591 592 593 594 595 596 597

  /**
   * GtkCalendar::day-selected:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user selects a day.
   */
598
  gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
599
    g_signal_new (I_("day-selected"),
600 601 602 603 604 605
                  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);
606 607 608 609 610 611 612

  /**
   * GtkCalendar::day-selected-double-click:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user double-clicks a day.
   */
613
  gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
614
    g_signal_new (I_("day-selected-double-click"),
615 616 617 618 619 620
                  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);
621 622 623 624 625 626 627

  /**
   * GtkCalendar::prev-month:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user switched to the previous month.
   */
628
  gtk_calendar_signals[PREV_MONTH_SIGNAL] =
629
    g_signal_new (I_("prev-month"),
630 631 632 633 634 635
                  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);
636 637 638 639 640 641 642

  /**
   * GtkCalendar::next-month:
   * @calendar: the object which received the signal.
   *
   * Emitted when the user switched to the next month.
   */
643
  gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
644
    g_signal_new (I_("next-month"),
645 646 647 648 649 650
                  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);
651 652 653 654 655 656 657

  /**
   * GtkCalendar::prev-year:
   * @calendar: the object which received the signal.
   *
   * Emitted when user switched to the previous year.
   */
658
  gtk_calendar_signals[PREV_YEAR_SIGNAL] =
659
    g_signal_new (I_("prev-year"),
660 661 662 663 664 665
                  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);
666 667 668 669 670 671 672

  /**
   * GtkCalendar::next-year:
   * @calendar: the object which received the signal.
   *
   * Emitted when user switched to the next year.
   */
673
  gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
674
    g_signal_new (I_("next-year"),
675 676 677 678 679 680
                  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);
681 682 683

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

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

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

712
  gtk_widget_set_can_focus (widget, TRUE);
713
  gtk_widget_set_has_window (widget, FALSE);
714

715 716 717
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
718
#ifndef G_OS_WIN32
719 720 721
        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);
722
#else
723 724 725 726 727
        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);
728
#endif
729
      }
730

731 732 733
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
734
#ifndef G_OS_WIN32
735 736 737
        tmp_time=i*2764800;
        strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
        default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
738
#else
739 740 741 742 743
        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);
744
#endif
745
      }
746

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

753
  for (i=0;i<31;i++)
754 755 756
    priv->marked_date[i] = FALSE;
  priv->num_marked_dates = 0;
  priv->selected_day = tm->tm_mday;
757

758
  priv->display_flags = (GTK_CALENDAR_SHOW_HEADING |
759 760 761
                             GTK_CALENDAR_SHOW_DAY_NAMES |
                             GTK_CALENDAR_SHOW_DETAILS);

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

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

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

775
  priv->arrow_width = 10;
776

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

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

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

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

  /* 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.
   *
794 795 796 797
   * 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.
798 799 800
   */
  year_before = _("calendar:MY");
  if (strcmp (year_before, "calendar:YM") == 0)
801
    priv->year_before = 1;
802 803
  else if (strcmp (year_before, "calendar:MY") != 0)
    g_warning ("Whoever translated calendar:MY did so wrongly.\n");
804

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

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

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

831
  priv->week_start = (week_1stday + first_weekday - 1) % 7;
832
#else
833 834 835
  /* 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.
836
   */
837 838 839
  week_start = _("calendar:week_start:0");

  if (strncmp (week_start, "calendar:week_start:", 20) == 0)
840
    priv->week_start = *(week_start + 20) - '0';
841
  else
842
    priv->week_start = -1;
843

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

  calendar_compute_days (calendar);
853 854
}

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

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

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

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

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

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

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

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

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

908
  calendar_queue_refresh (calendar);
909 910 911
}

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

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

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

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

936
  calendar_queue_refresh (calendar);
937 938 939
}

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

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

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

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

964
  calendar_queue_refresh (calendar);
965 966 967
}

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

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

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

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

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

997
  for (col = 0; col < first_day; col++)
998
    {
999 1000 1001
      priv->day[0][col] = day;
      priv->day_month[0][col] = MONTH_PREV;
      day++;
1002
    }
1003

1004
  /* Compute days of current month */
1005 1006
  row = first_day / 7;
  col = first_day % 7;
1007 1008
  for (day = 1; day <= ndays_in_month; day++)
    {
1009 1010 1011
      priv->day[row][col] = day;
      priv->day_month[row][col] = MONTH_CURRENT;

1012 1013
      col++;
      if (col == 7)
1014 1015 1016 1017
        {
          row++;
          col = 0;
        }
1018
    }
1019

1020 1021 1022
  /* Compute days of next month */
  day = 1;
  for (; row <= 5; row++)
1023
    {
1024
      for (; col <= 6; col++)
1025 1026 1027 1028 1029
        {
          priv->day[row][col] = day;
          priv->day_month[row][col] = MONTH_NEXT;
          day++;
        }
1030
      col = 0;
1031 1032 1033 1034
    }
}

static void
1035
calendar_select_and_focus_day (GtkCalendar *calendar,
1036
                               guint        day)
1037
{
1038 1039 1040
  GtkCalendarPrivate *priv = calendar->priv;
  gint old_focus_row = priv->focus_row;
  gint old_focus_col = priv->focus_col;
1041 1042
  gint row;
  gint col;
1043

1044 1045 1046
  for (row = 0; row < 6; row ++)
    for (col = 0; col < 7; col++)
      {
1047 1048 1049 1050 1051 1052
        if (priv->day_month[row][col] == MONTH_CURRENT
            && priv->day[row][col] == day)
          {
            priv->focus_row = row;
            priv->focus_col = col;
          }
1053 1054 1055 1056
      }

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

1058 1059
  gtk_calendar_select_day (calendar, day);
}
1060

1061 1062 1063 1064 1065 1066 1067 1068

/****************************************
 *     Layout computation utilities     *
 ****************************************/

static gint
calendar_row_height (GtkCalendar *calendar)
{
1069 1070
  GtkCalendarPrivate *priv = calendar->priv;

1071
  return (priv->main_h - CALENDAR_MARGIN
1072 1073
          - ((priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
             ? calendar_get_ysep (calendar) : CALENDAR_MARGIN)) / 6;
1074 1075
}

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088
static void
get_component_paddings (GtkCalendar *calendar,
                        GtkBorder   *padding,
                        GtkBorder   *day_padding,
                        GtkBorder   *day_name_padding,
                        GtkBorder   *week_padding)
{
  GtkStyleContext * context;
  GtkStateFlags state;
  GtkWidget *widget;

  widget = GTK_WIDGET (calendar);
  context = gtk_widget_get_style_context (widget);
1089
  state = gtk_style_context_get_state (context);
Cosimo Cecchi's avatar
Cosimo Cecchi committed
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117

  if (padding)
    gtk_style_context_get_padding (context, state, padding);

  if (day_padding)
    {
      gtk_style_context_save (context);
      gtk_style_context_add_class (context, "day-number");
      gtk_style_context_get_padding (context, state, day_padding);
      gtk_style_context_restore (context);
    }

  if (day_name_padding)
    {
      gtk_style_context_save (context);
      gtk_style_context_add_class (context, "day-name");
      gtk_style_context_get_padding (context, state, day_name_padding);
      gtk_style_context_restore (context);
    }

  if (week_padding)
    {
      gtk_style_context_save (context);
      gtk_style_context_add_class (context, "week-number");
      gtk_style_context_get_padding (context, state, week_padding);
      gtk_style_context_restore (context);
    }
}
1118 1119 1120 1121 1122

/* calendar_left_x_for_column: returns the x coordinate
 * for the left of the column */
static gint
calendar_left_x_for_column (GtkCalendar *calendar,
1123
                            gint         column)
1124
{
1125
  GtkCalendarPrivate *priv = calendar->priv;
1126 1127
  gint width;
  gint x_left;
1128
  gint week_width;
1129
  gint calendar_xsep = calendar_get_xsep (calendar);
1130
  gint inner_border = calendar_get_inner_border (calendar);
1131
  GtkBorder padding;
1132

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1133
  get_component_paddings (calendar, &padding, NULL, NULL, NULL);
1134

1135
  week_width = priv->week_width + padding.left + inner_border;
1136

1137
  if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
1138 1139 1140 1141
    {
      column = 6 - column;
      week_width = 0;
    }
1142

1143
  width = calendar->priv->day_width;
1144
  if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
1145
    x_left = week_width + calendar_xsep + (width + DAY_XSEP) * column;
1146
  else
1147
    x_left = week_width + CALENDAR_MARGIN + (width + DAY_XSEP) * column;
1148

1149 1150
  return x_left;
}
1151

1152 1153 1154 1155
/* column_from_x: returns the column 0-6 that the
 * x pixel of the xwindow is in */
static gint
calendar_column_from_x (GtkCalendar *calendar,
1156
                        gint         event_x)
1157 1158 1159
{
  gint c, column;
  gint x_left, x_right;
1160

1161
  column = -1;
1162

1163
  for (c = 0; c < 7; c++)
1164
    {
1165
      x_left = calendar_left_x_for_column (calendar, c);
1166
      x_right = x_left + calendar->priv->day_width;
1167

1168
      if (event_x >= x_left && event_x < x_right)
1169 1170 1171 1172
        {
          column = c;
          break;
        }
1173
    }
1174

1175
  return column;
1176 1177
}

1178 1179 1180 1181
/* calendar_top_y_for_row: returns the y coordinate
 * for the top of the row */
static gint
calendar_top_y_for_row (GtkCalendar *calendar,
1182
                        gint         row)
1183
{
1184
  GtkCalendarPrivate *priv = calendar->priv;
1185
  GtkBorder padding;
1186
  gint inner_border = calendar_get_inner_border (calendar);
1187

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1188
  get_component_paddings (calendar, &padding, NULL, NULL, NULL);
1189

1190 1191
  return priv->header_h + priv->day_name_h + padding.top + inner_border
         + row * calendar_row_height (calendar);
1192
}
1193

1194 1195 1196 1197
/* row_from_y: returns the row 0-5 that the
 * y pixel of the xwindow is in */
static gint
calendar_row_from_y (GtkCalendar *calendar,
1198
                     gint         event_y)
1199 1200 1201 1202
{
  gint r, row;
  gint height;
  gint y_top, y_bottom;