gtkcalendar.c 119 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 "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
};

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

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
}

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 */
132
#define CALENDAR_MARGIN          0
133

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;
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;
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,
252 253 254
                                       guint         prop_id,
                                       const GValue *value,
                                       GParamSpec   *pspec);
255
static void gtk_calendar_get_property (GObject      *object,
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,
271
                                             GtkAllocation    *allocation);
272 273
static gboolean gtk_calendar_draw           (GtkWidget        *widget,
                                             cairo_t          *cr);
274
static gboolean gtk_calendar_button_press   (GtkWidget        *widget,
275
                                             GdkEventButton   *event);
276
static gboolean gtk_calendar_button_release (GtkWidget        *widget,
277
                                             GdkEventButton   *event);
278
static gboolean gtk_calendar_motion_notify  (GtkWidget        *widget,
279
                                             GdkEventMotion   *event);
280
static gboolean gtk_calendar_enter_notify   (GtkWidget        *widget,
281
                                             GdkEventCrossing *event);
282
static gboolean gtk_calendar_leave_notify   (GtkWidget        *widget,
283
                                             GdkEventCrossing *event);
284
static gboolean gtk_calendar_scroll         (GtkWidget        *widget,
285
                                             GdkEventScroll   *event);
286
static gboolean gtk_calendar_key_press      (GtkWidget        *widget,
287
                                             GdkEventKey      *event);
288
static gboolean gtk_calendar_focus_out      (GtkWidget        *widget,
289
                                             GdkEventFocus    *event);
290
static void     gtk_calendar_grab_notify    (GtkWidget        *widget,
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,
295 296 297 298
                                             gint              x,
                                             gint              y,
                                             gboolean          keyboard_mode,
                                             GtkTooltip       *tooltip);
299 300

static void     gtk_calendar_drag_data_get      (GtkWidget        *widget,
301 302 303 304
                                                 GdkDragContext   *context,
                                                 GtkSelectionData *selection_data,
                                                 guint             info,
                                                 guint             time);
305
static void     gtk_calendar_drag_data_received (GtkWidget        *widget,
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,
313 314 315 316
                                                 GdkDragContext   *context,
                                                 gint              x,
                                                 gint              y,
                                                 guint             time);
317
static void     gtk_calendar_drag_leave         (GtkWidget        *widget,
318 319
                                                 GdkDragContext   *context,
                                                 guint             time);
320
static gboolean gtk_calendar_drag_drop          (GtkWidget        *widget,
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,
328
                                     gint         click_child);
329 330 331
static void calendar_stop_spinning  (GtkCalendar *calendar);

static void calendar_invalidate_day     (GtkCalendar *widget,
332 333
                                         gint       row,
                                         gint       col);
334
static void calendar_invalidate_day_num (GtkCalendar *widget,
335
                                         gint       day);
336
static void calendar_invalidate_arrow   (GtkCalendar *widget,
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;
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;
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;
388

389 390 391
  /**
   * GtkCalendar:year:
   *
392
   * The selected year.
393
   * This property gets initially set to the current year.
394
   */
395 396 397
  g_object_class_install_property (gobject_class,
                                   PROP_YEAR,
                                   g_param_spec_int ("year",
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:
   *
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",
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:
   *
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",
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",
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",
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",
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",
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",
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",
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",
535 536 537
                                                         P_("Show Details"),
                                                         P_("If TRUE, details are shown"),
                                                         TRUE,
538
                                                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
539

540 541

  /**
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));

  /**
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));

  /**
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"),
586 587 588 589
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, month_changed),
                  NULL, NULL,
590
                  NULL,
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"),
601 602 603 604
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, day_selected),
                  NULL, NULL,
605
                  NULL,
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"),
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,
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"),
631 632 633 634
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, prev_month),
                  NULL, NULL,
635
                  NULL,
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"),
646 647 648 649
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, next_month),
                  NULL, NULL,
650
                  NULL,
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"),
661 662 663 664
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, prev_year),
                  NULL, NULL,
665
                  NULL,
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"),
676 677 678 679
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkCalendarClass, next_year),
                  NULL, NULL,
680
                  NULL,
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
  static const char *month_format = NULL;
698
  char buffer[255];
699
  time_t tmp_time;
700
#endif
701
  GtkCalendarPrivate *priv;
702
  gchar *year_before;
703
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
704
  union { unsigned int word; char *string; } langinfo;
705 706
  gint week_1stday = 0;
  gint first_weekday = 1;
707
  guint week_origin;
708 709 710
#else
  gchar *week_start;
#endif
711

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

714
  gtk_widget_set_can_focus (widget, TRUE);
715
  gtk_widget_set_has_window (widget, FALSE);
716

717 718 719
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
720
#ifndef G_OS_WIN32
721
        tmp_time= (i+3)*86400;
722
        strftime (buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
723
        default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
724
#else
725 726 727 728 729
        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);
730
#endif
731
      }
732

733 734 735
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
736
#ifndef G_OS_WIN32
737
        tmp_time=i*2764800;
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
        if (G_UNLIKELY (month_format == NULL))
          {
            buffer[0] = '\0';
            month_format = "%OB";
            strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time));
            /* "%OB" is not supported in Linux with glibc < 2.27  */
            if (!strcmp (buffer, "%OB") || !strcmp (buffer, "OB") || !strcmp (buffer, ""))
              {
                month_format = "%B";
                strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time));
              }
          }
        else
          strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time));

753
        default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
754
#else
755 756 757 758 759
        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);
760
#endif
761
      }
762

763 764 765
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
766 767
  priv->month = tm->tm_mon;
  priv->year  = 1900 + tm->tm_year;
768

769
  for (i=0;i<31;i++)
770 771 772
    priv->marked_date[i] = FALSE;
  priv->num_marked_dates = 0;
  priv->selected_day = tm->tm_mday;
773

774
  priv->display_flags = (GTK_CALENDAR_SHOW_HEADING |
775 776 777
                             GTK_CALENDAR_SHOW_DAY_NAMES |
                             GTK_CALENDAR_SHOW_DETAILS);

778 779
  priv->focus_row = -1;
  priv->focus_col = -1;
780

781 782 783 784
  priv->max_year_width = 0;
  priv->max_month_width = 0;
  priv->max_day_char_width = 0;
  priv->max_week_char_width = 0;
785

786 787 788 789
  priv->max_day_char_ascent = 0;
  priv->max_day_char_descent = 0;
  priv->max_label_char_ascent = 0;
  priv->max_label_char_descent = 0;
790

791
  priv->arrow_width = 10;
792

793 794 795
  priv->need_timer = 0;
  priv->timer = 0;
  priv->click_child = -1;
796

797 798
  priv->in_drag = 0;
  priv->drag_highlight = 0;
799

800 801
  gtk_drag_dest_set (widget, 0, NULL, 0, GDK_ACTION_COPY);
  gtk_drag_dest_add_text_targets (widget);
802

803
  priv->year_before = 0;
804 805 806 807 808 809

  /* 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.
   *
810 811 812 813
   * 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.
814 815 816
   */
  year_before = _("calendar:MY");
  if (strcmp (year_before, "calendar:YM") == 0)
817
    priv->year_before = 1;
818
  else if (strcmp (year_before, "calendar:MY") != 0)
819
    g_warning ("Whoever translated calendar:MY did so wrongly.");
820

821
#ifdef G_OS_WIN32
Tor Lillqvist's avatar
Tor Lillqvist committed
822 823
  priv->week_start = 0;
  week_start = NULL;
824

Tor Lillqvist's avatar
Tor Lillqvist committed
825
  if (GetLocaleInfoW (GetThreadLocale (), LOCALE_IFIRSTDAYOFWEEK,
826
                      wbuffer, G_N_ELEMENTS (wbuffer)))
Tor Lillqvist's avatar
Tor Lillqvist committed
827
    week_start = g_utf16_to_utf8 (wbuffer, -1, NULL, NULL, NULL);
828

Tor Lillqvist's avatar
Tor Lillqvist committed
829
  if (week_start != NULL)
830
    {
Tor Lillqvist's avatar
Tor Lillqvist committed
831 832 833 834
      priv->week_start = (week_start[0] - '0' + 1) % 7;
      g_free(week_start);
    }
#else
835
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
836 837 838 839
  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;
840 841 842 843
  if (week_origin == 19971130) /* Sunday */
    week_1stday = 0;
  else if (week_origin == 19971201) /* Monday */
    week_1stday = 1;
844
  else
845
    g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.");
846

847
  priv->week_start = (week_1stday + first_weekday - 1) % 7;
848
#else
849 850 851
  /* 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.
852
   */
853 854 855
  week_start = _("calendar:week_start:0");

  if (strncmp (week_start, "calendar:week_start:", 20) == 0)
856
    priv->week_start = *(week_start + 20) - '0';
857
  else
858
    priv->week_start = -1;
859

860
  if (priv->week_start < 0 || priv->week_start > 6)
861
    {
862
      g_warning ("Whoever translated calendar:week_start:0 did so wrongly.");
863
      priv->week_start = 0;
864
    }
865
#endif
866
#endif
867 868

  calendar_compute_days (calendar);
869 870
}

871 872 873 874

/****************************************
 *          Utility Functions           *
 ****************************************/
875

876 877 878
static void
calendar_queue_refresh (GtkCalendar *calendar)
{
879
  GtkCalendarPrivate *priv = calendar->priv;
880

881
  if (!(priv->detail_func) ||
882
      !(priv->display_flags & GTK_CALENDAR_SHOW_DETAILS) ||
883
       (priv->detail_width_chars && priv->detail_height_rows))
884 885 886 887 888
    gtk_widget_queue_draw (GTK_WIDGET (calendar));
  else
    gtk_widget_queue_resize (GTK_WIDGET (calendar));
}

889
static void
890
calendar_set_month_next (GtkCalendar *calendar)
891 892
{
  gint month_len;
893
  GtkCalendarPrivate *priv = calendar->priv;
894

895
  if (priv->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
896
    return;
897 898

  if (priv->month == 11)
899
    {
900 901 902 903 904 905
      priv->month = 0;
      priv->year++;
    }
  else
    priv->month++;

906
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
907
  g_signal_emit (calendar,
908 909
                 gtk_calendar_signals[NEXT_MONTH_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
910
  g_signal_emit (calendar,
911 912
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
913 914 915 916

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

  if (month_len < priv->selected_day)
917
    {
918
      priv->selected_day = 0;
919 920 921
      gtk_calendar_select_day (calendar, month_len);
    }
  else
922
    gtk_calendar_select_day (calendar, priv->selected_day);
923

924
  calendar_queue_refresh (calendar);
925 926 927
}

static void
928
calendar_set_year_prev (GtkCalendar *calendar)
929
{
930
  GtkCalendarPrivate *priv = calendar->priv;
931
  gint month_len;
932

933
  priv->year--;
934
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
935
  g_signal_emit (calendar,
936 937
                 gtk_calendar_signals[PREV_YEAR_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
938
  g_signal_emit (calendar,
939 940
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
941 942 943 944

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

  if (month_len < priv->selected_day)
945
    {
946
      priv->selected_day = 0;
947 948 949
      gtk_calendar_select_day (calendar, month_len);
    }
  else
950 951
    gtk_calendar_select_day (calendar, priv->selected_day);

952
  calendar_queue_refresh (calendar);
953 954 955
}

static void
956
calendar_set_year_next (GtkCalendar *calendar)
957
{
958
  GtkCalendarPrivate *priv = calendar->priv;
959
  gint month_len;
960

961
  priv->year++;
962
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
963
  g_signal_emit (calendar,
964 965
                 gtk_calendar_signals[NEXT_YEAR_SIGNAL],
                 0);
Manish Singh's avatar
Manish Singh committed
966
  g_signal_emit (calendar,
967 968
                 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
                 0);
969 970 971 972

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

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

980
  calendar_queue_refresh (calendar);
981 982 983
}

static void
984
calendar_compute_days (GtkCalendar *calendar)
985
{
986
  GtkCalendarPrivate *priv = calendar->priv;
987 988 989 990 991 992 993
  gint month;
  gint year;
  gint ndays_in_month;
  gint ndays_in_prev_month;
  gint first_day;
  gint row;
  gint col;
994
  gint day;
995

996 997 998
  year = priv->year;
  month = priv->month + 1;

999
  ndays_in_month = month_length[leap (year)][month];
1000

1001 1002
  first_day = day_of_week (year, month, 1);
  first_day = (first_day + 7 - priv->week_start) % 7;
1003 1004
  if (first_day == 0)
    first_day = 7;
1005

1006 1007
  /* Compute days of previous month */
  if (month > 1)
1008
    ndays_in_prev_month = month_length[leap (year)][month - 1];
1009
  else
1010
    ndays_in_prev_month = month_length[leap (year - 1)][12];
1011
  day = ndays_in_prev_month - first_day+ 1;
1012

1013
  for (col = 0; col < first_day; col++)
1014
    {
1015 1016 1017
      priv->day[0][col] = day;
      priv->day_month[0][col] = MONTH_PREV;
      day++;
1018
    }
1019

1020
  /* Compute days of current month */
1021 1022
  row = first_day / 7;
  col = first_day % 7;
1023 1024
  for (day = 1; day <= ndays_in_month; day++)
    {
1025 1026 1027
      priv->day[row][col] = day;
      priv->day_month[row][col] = MONTH_CURRENT;

1028 1029
      col++;
      if (col == 7)
1030 1031 1032 1033
        {
          row++;
          col = 0;
        }
1034
    }
1035

1036 1037 1038
  /* Compute days of next month */
  day = 1;
  for (; row <= 5; row++)
1039
    {
1040
      for (; col <= 6; col++)
1041 1042 1043 1044 1045
        {
          priv->day[row][col] = day;
          priv->day_month[row][col] = MONTH_NEXT;
          day++;
        }
1046
      col = 0;
1047 1048 1049 1050
    }
}

static void
1051
calendar_select_and_focus_day (GtkCalendar *calendar,
1052
                               guint        day)
1053
{
1054 1055 1056
  GtkCalendarPrivate *priv = calendar->priv;
  gint old_focus_row = priv->focus_row;
  gint old_focus_col = priv->focus_col;
1057 1058
  gint row;
  gint col;
1059

1060 1061 1062
  for (row = 0; row < 6; row ++)
    for (col = 0; col < 7; col++)
      {
1063 1064 1065 1066 1067 1068
        if (priv->day_month[row][col] == MONTH_CURRENT
            && priv->day[row][col] == day)
          {
            priv->focus_row = row;
            priv->focus_col = col;
          }
1069 1070 1071 1072
      }

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

1074 1075
  gtk_calendar_select_day (calendar, day);
}
1076

1077 1078 1079 1080 1081 1082 1083 1084

/****************************************
 *     Layout computation utilities     *
 ****************************************/

static gint
calendar_row_height (GtkCalendar *calendar)
{
1085 1086
  GtkCalendarPrivate *priv = calendar->priv;

1087
  return (priv->main_h - CALENDAR_MARGIN
1088 1089
          - ((priv->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
             ? calendar_get_ysep (calendar) : CALENDAR_MARGIN)) / 6;
1090 1091
}

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
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);
1105
  state = gtk_style_context_get_state (context);
Cosimo Cecchi's avatar
Cosimo Cecchi committed
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133

  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);
    }
}
1134 1135 1136 1137 1138

/* calendar_left_x_for_column: returns the x coordinate
 * for the left of the column */
static gint
calendar_left_x_for_column (GtkCalendar *calendar,
1139
                            gint         column)
1140
{
1141
  GtkCalendarPrivate *priv = calendar->priv;
1142 1143
  gint width;
  gint x_left;
1144
  gint week_width;
1145
  gint calendar_xsep = calendar_get_xsep (calendar);
1146
  gint inner_border = calendar_get_inner_border (calendar);
1147
  GtkBorder padding;
1148

Cosimo Cecchi's avatar
Cosimo Cecchi committed
1149
  get_component_paddings (calendar, &padding, NULL, NULL, NULL);
1150

1151
  week_width = priv->week_width + padding.left + inner_border;
1152

1153
  if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
1154 1155 1156 1157
    {
      column = 6 - column;
      week_width = 0;
    }
1158

1159
  width = calendar->priv->day_width;
1160
  if (priv->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
1161
    x_left = week_width + calendar_xsep + (width + DAY_XSEP) * column;
1162
  else
1163
    x_left = week_width + CALENDAR_MARGIN + (width + DAY_XSEP) * column;
1164

1165 1166
  return x_left;
}
1167