gtkcalendar.c 94.3 KB
Newer Older
1 2 3 4
/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * GTK Calendar Widget
Matthias Clasen's avatar
2.5.5  
Matthias Clasen committed
5
 * Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson and Mattias Groenlund
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
21 22 23 24
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

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

32
#include <config.h>
33 34

#ifdef HAVE_SYS_TIME_H
35
#include <sys/time.h>
36
#endif
37
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
38 39
#include <langinfo.h>
#endif
40 41 42
#include <string.h>
#include <stdlib.h>
#include <time.h>
43 44 45 46 47 48 49

#include <glib.h>

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

50
#include <glib/gprintf.h>
Manish Singh's avatar
Manish Singh committed
51

52
#undef GTK_DISABLE_DEPRECATED
53
#include "gtkcalendar.h"
54 55
#define GTK_DISABLE_DEPRECATED

56
#include "gtkdnd.h"
Matthias Clasen's avatar
Matthias Clasen committed
57
#include "gtkintl.h"
58 59
#include "gtkmain.h"
#include "gtkmarshalers.h"
60
#include "gtkprivate.h"
61
#include "gtkintl.h"
62
#include "gdk/gdkkeysyms.h"
63
#include "gtkalias.h"
64 65

/***************************************************************************/
66 67 68 69
/* The following date routines are taken from the lib_date package. 
 * They have been minimally edited to avoid conflict with types defined
 * in win32 headers.
 */
70

71
static const guint month_length[2][13] =
72
{
73 74
  { 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 }
75 76
};

77
static const guint days_in_months[2][14] =
78
{
79 80
  { 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 }
81 82
};

83 84 85 86 87
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,
			       guint year2, guint mm2, guint dd2);
static guint  weeks_in_year(guint year);
88

89 90
static gboolean 
leap (guint year)
91
{
92
  return((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
93 94
}

95 96
static guint 
day_of_week (guint year, guint mm, guint dd)
97
{
98
  glong  days;
99 100 101
  
  days = calc_days(year, mm, dd);
  if (days > 0L)
102
    {
103 104 105
      days--;
      days %= 7L;
      days++;
106
    }
107
  return( (guint) days );
108 109
}

110
static guint weeks_in_year(guint year)
111
{
112
  return(52 + ((day_of_week(year,1,1)==4) || (day_of_week(year,12,31)==4)));
113 114
}

115 116
static gboolean 
check_date(guint year, guint mm, guint dd)
117
{
118 119 120 121
  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;
122 123
}

124 125
static guint 
week_number(guint year, guint mm, guint dd)
126
{
127
  guint first;
128 129
  
  first = day_of_week(year,1,1) - 1;
130
  return( (guint) ( (dates_difference(year,1,1, year,mm,dd) + first) / 7L ) +
131
	  (first < 4) );
132 133
}

134 135
static glong 
year_to_days(guint year)
136
{
137
  return( year * 365L + (year / 4) - (year / 100) + (year / 400) );
138 139 140
}


141 142
static glong 
calc_days(guint year, guint mm, guint dd)
143
{
144
  gboolean lp;
145 146
  
  if (year < 1) return(0L);
147 148
  if ((mm < 1) || (mm > 12)) return(0L);
  if ((dd < 1) || (dd > month_length[(lp = leap(year))][mm])) return(0L);
149
  return( year_to_days(--year) + days_in_months[lp][mm] + dd );
150 151
}

152 153
static gboolean 
week_of_year(guint *week, guint *year, guint mm, guint dd)
154
{
155 156 157 158 159 160 161 162 163 164
  if (check_date(*year,mm,dd))
    {
      *week = week_number(*year,mm,dd);
      if (*week == 0) 
	*week = weeks_in_year(--(*year));
      else if (*week > weeks_in_year(*year))
	{
	  *week = 1;
	  (*year)++;
	}
165
      return TRUE;
166
    }
167
  return FALSE;
168 169
}

170 171 172
static glong 
dates_difference(guint year1, guint mm1, guint dd1,
		 guint year2, guint mm2, guint dd2)
173
{
174
  return( calc_days(year2, mm2, dd2) - calc_days(year1, mm1, dd1) );
175 176
}

177
/*** END OF lib_date routines ********************************************/
178

179
/* Spacing around day/week headers and main area, inside those windows */
180
#define CALENDAR_MARGIN		 0
181 182 183
/* Spacing around day/week headers and main area, outside those windows */
#define INNER_BORDER		 4
/* Separation between day headers and main area */
184
#define CALENDAR_YSEP		 4
185
/* Separation between week headers and main area */
186
#define CALENDAR_XSEP		 4
187

188
#define DAY_XSEP		 0 /* not really good for small calendar */
189 190 191
#define DAY_YSEP		 0 /* not really good for small calendar */

/* Color usage */
192 193
#define HEADER_FG_COLOR(widget)		 (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
#define HEADER_BG_COLOR(widget)		 (& (widget)->style->bg[GTK_WIDGET_STATE (widget)])
194 195
#define SELECTED_BG_COLOR(widget)	 (& (widget)->style->base[GTK_WIDGET_HAS_FOCUS (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE])
#define SELECTED_FG_COLOR(widget)	 (& (widget)->style->text[GTK_WIDGET_HAS_FOCUS (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE])
196 197 198 199 200 201
#define NORMAL_DAY_COLOR(widget)	 (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
#define PREV_MONTH_COLOR(widget)	 (& (widget)->style->mid[GTK_WIDGET_STATE (widget)])
#define NEXT_MONTH_COLOR(widget)	 (& (widget)->style->mid[GTK_WIDGET_STATE (widget)])
#define MARKED_COLOR(widget)		 (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
#define BACKGROUND_COLOR(widget)	 (& (widget)->style->base[GTK_WIDGET_STATE (widget)])
#define HIGHLIGHT_BACK_COLOR(widget)	 (& (widget)->style->mid[GTK_WIDGET_STATE (widget)])
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226

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

Matthias Clasen's avatar
Matthias Clasen committed
227 228 229 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,
  PROP_LAST
};

240
static guint gtk_calendar_signals[LAST_SIGNAL] = { 0 };
241

242
struct _GtkCalendarPrivate
243 244 245 246 247 248 249
{
  GdkWindow *header_win;
  GdkWindow *day_name_win;
  GdkWindow *main_win;
  GdkWindow *week_win;
  GdkWindow *arrow_win[4];

250 251 252
  guint header_h;
  guint day_name_h;
  guint main_h;
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270

  guint	     arrow_state[4];
  guint	     arrow_width;
  guint	     max_month_width;
  guint	     max_year_width;
  
  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;
  
  /* flags */
271 272
  guint year_before : 1;

273 274
  guint need_timer  : 1;

275 276 277
  guint in_drag : 1;
  guint drag_highlight : 1;

278 279
  guint32 timer;
  gint click_child;
280 281

  gint week_start;
282 283 284

  gint drag_start_x;
  gint drag_start_y;
285 286
};

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
#define GTK_CALENDAR_GET_PRIVATE(widget)  (GTK_CALENDAR (widget)->priv)

static void gtk_calendar_finalize     (GObject      *calendar);
static void gtk_calendar_destroy      (GtkObject    *calendar);
static void gtk_calendar_set_property (GObject      *object,
				       guint         prop_id,
				       const GValue *value,
				       GParamSpec   *pspec);
static void gtk_calendar_get_property (GObject      *object,
				       guint         prop_id,
				       GValue       *value,
				       GParamSpec   *pspec);

static void     gtk_calendar_realize        (GtkWidget        *widget);
static void     gtk_calendar_unrealize      (GtkWidget        *widget);
static void     gtk_calendar_size_request   (GtkWidget        *widget,
					     GtkRequisition   *requisition);
static void     gtk_calendar_size_allocate  (GtkWidget        *widget,
					     GtkAllocation    *allocation);
static gboolean gtk_calendar_expose         (GtkWidget        *widget,
					     GdkEventExpose   *event);
static gboolean gtk_calendar_button_press   (GtkWidget        *widget,
					     GdkEventButton   *event);
static gboolean gtk_calendar_button_release (GtkWidget        *widget,
					     GdkEventButton   *event);
static gboolean gtk_calendar_motion_notify  (GtkWidget        *widget,
					     GdkEventMotion   *event);
static gboolean gtk_calendar_enter_notify   (GtkWidget        *widget,
					     GdkEventCrossing *event);
static gboolean gtk_calendar_leave_notify   (GtkWidget        *widget,
					     GdkEventCrossing *event);
static gboolean gtk_calendar_scroll         (GtkWidget        *widget,
					     GdkEventScroll   *event);
static gboolean gtk_calendar_key_press      (GtkWidget        *widget,
					     GdkEventKey      *event);
static gboolean gtk_calendar_focus_out      (GtkWidget        *widget,
					     GdkEventFocus    *event);
static void     gtk_calendar_grab_notify    (GtkWidget        *widget,
					     gboolean          was_grabbed);
static void     gtk_calendar_state_changed  (GtkWidget        *widget,
					     GtkStateType      previous_state);
static void     gtk_calendar_style_set      (GtkWidget        *widget,
					     GtkStyle         *previous_style);

static void     gtk_calendar_drag_data_get      (GtkWidget        *widget,
						 GdkDragContext   *context,
						 GtkSelectionData *selection_data,
						 guint             info,
						 guint             time);
static void     gtk_calendar_drag_data_received (GtkWidget        *widget,
						 GdkDragContext   *context,
						 gint              x,
						 gint              y,
						 GtkSelectionData *selection_data,
						 guint             info,
						 guint             time);
static gboolean gtk_calendar_drag_motion        (GtkWidget        *widget,
						 GdkDragContext   *context,
						 gint              x,
						 gint              y,
						 guint             time);
static void     gtk_calendar_drag_leave         (GtkWidget        *widget,
						 GdkDragContext   *context,
						 guint             time);
static gboolean gtk_calendar_drag_drop          (GtkWidget        *widget,
						 GdkDragContext   *context,
						 gint              x,
						 gint              y,
						 guint             time);

static void calendar_start_spinning (GtkCalendar *calendar,
				     gint         click_child);
static void calendar_stop_spinning  (GtkCalendar *calendar);

static void calendar_invalidate_day     (GtkCalendar *widget,
					 gint       row,
					 gint       col);
static void calendar_invalidate_day_num (GtkCalendar *widget,
					 gint       day);
static void calendar_invalidate_arrow   (GtkCalendar *widget,
					 guint      arrow);

static void calendar_compute_days      (GtkCalendar *calendar);
370
     
371 372 373
static char    *default_abbreviated_dayname[7];
static char    *default_monthname[12];

374
G_DEFINE_TYPE (GtkCalendar, gtk_calendar, GTK_TYPE_WIDGET)
375 376 377 378

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
379
  GObjectClass   *gobject_class;
380
  GtkObjectClass   *object_class;
381
  GtkWidgetClass *widget_class;
382 383

  gobject_class = (GObjectClass*)  class;
384
  object_class = (GtkObjectClass*)  class;
385
  widget_class = (GtkWidgetClass*) class;
386
  
Matthias Clasen's avatar
Matthias Clasen committed
387 388
  gobject_class->set_property = gtk_calendar_set_property;
  gobject_class->get_property = gtk_calendar_get_property;
389
  gobject_class->finalize = gtk_calendar_finalize;
390

391 392
  object_class->destroy = gtk_calendar_destroy;

393 394 395 396 397 398
  widget_class->realize = gtk_calendar_realize;
  widget_class->unrealize = gtk_calendar_unrealize;
  widget_class->expose_event = gtk_calendar_expose;
  widget_class->size_request = gtk_calendar_size_request;
  widget_class->size_allocate = gtk_calendar_size_allocate;
  widget_class->button_press_event = gtk_calendar_button_press;
399
  widget_class->button_release_event = gtk_calendar_button_release;
400 401 402 403
  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;
404
  widget_class->scroll_event = gtk_calendar_scroll;
405 406
  widget_class->style_set = gtk_calendar_style_set;
  widget_class->state_changed = gtk_calendar_state_changed;
407
  widget_class->grab_notify = gtk_calendar_grab_notify;
408
  widget_class->focus_out_event = gtk_calendar_focus_out;
409 410 411 412 413 414

  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;
415
  
Matthias Clasen's avatar
Matthias Clasen committed
416 417 418
  g_object_class_install_property (gobject_class,
                                   PROP_YEAR,
                                   g_param_spec_int ("year",
419 420
						     P_("Year"),
						     P_("The selected year"),
Matthias Clasen's avatar
Matthias Clasen committed
421
						     0, G_MAXINT, 0,
422
						     GTK_PARAM_READWRITE));
Matthias Clasen's avatar
Matthias Clasen committed
423 424 425
  g_object_class_install_property (gobject_class,
                                   PROP_MONTH,
                                   g_param_spec_int ("month",
426 427
						     P_("Month"),
						     P_("The selected month (as a number between 0 and 11)"),
Matthias Clasen's avatar
Matthias Clasen committed
428
						     0, 11, 0,
429
						     GTK_PARAM_READWRITE));
Matthias Clasen's avatar
Matthias Clasen committed
430 431 432
  g_object_class_install_property (gobject_class,
                                   PROP_DAY,
                                   g_param_spec_int ("day",
433 434
						     P_("Day"),
						     P_("The selected day (as a number between 1 and 31, or 0 to unselect the currently selected day)"),
Matthias Clasen's avatar
Matthias Clasen committed
435
						     0, 31, 0,
436
						     GTK_PARAM_READWRITE));
437 438 439 440 441 442 443 444

/**
 * GtkCalendar:show-heading:
 *
 * Determines whether a heading is displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
445 446
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_HEADING,
447
                                   g_param_spec_boolean ("show-heading",
448 449
							 P_("Show Heading"),
							 P_("If TRUE, a heading is displayed"),
Matthias Clasen's avatar
Matthias Clasen committed
450
							 TRUE,
451
							 GTK_PARAM_READWRITE));
452 453 454 455 456 457 458 459

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

/**
 * GtkCalendar:show-week-numbers:
 *
 * Determines whether week numbers are displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
489 490
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_WEEK_NUMBERS,
491
                                   g_param_spec_boolean ("show-week-numbers",
492 493
							 P_("Show Week Numbers"),
							 P_("If TRUE, week numbers are displayed"),
Matthias Clasen's avatar
Matthias Clasen committed
494
							 FALSE,
495
							 GTK_PARAM_READWRITE));
496

497
  gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
Matthias Clasen's avatar
Matthias Clasen committed
498
    g_signal_new (I_("month_changed"),
Manish Singh's avatar
Manish Singh committed
499 500 501 502 503 504
		  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);
505
  gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
Matthias Clasen's avatar
Matthias Clasen committed
506
    g_signal_new (I_("day_selected"),
Manish Singh's avatar
Manish Singh committed
507 508 509 510 511 512
		  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);
513
  gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
Matthias Clasen's avatar
Matthias Clasen committed
514
    g_signal_new (I_("day_selected_double_click"),
Manish Singh's avatar
Manish Singh committed
515 516 517 518 519 520
		  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);
521
  gtk_calendar_signals[PREV_MONTH_SIGNAL] =
Matthias Clasen's avatar
Matthias Clasen committed
522
    g_signal_new (I_("prev_month"),
Manish Singh's avatar
Manish Singh committed
523 524 525 526 527 528
		  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);
529
  gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
Matthias Clasen's avatar
Matthias Clasen committed
530
    g_signal_new (I_("next_month"),
Manish Singh's avatar
Manish Singh committed
531 532 533 534 535 536
		  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);
537
  gtk_calendar_signals[PREV_YEAR_SIGNAL] =
Matthias Clasen's avatar
Matthias Clasen committed
538
    g_signal_new (I_("prev_year"),
Manish Singh's avatar
Manish Singh committed
539 540 541 542 543 544
		  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);
545
  gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
Matthias Clasen's avatar
Matthias Clasen committed
546
    g_signal_new (I_("next_year"),
Manish Singh's avatar
Manish Singh committed
547 548 549 550 551 552
		  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);
553 554
  
  g_type_class_add_private (gobject_class, sizeof (GtkCalendarPrivate));
555 556 557 558 559
}

static void
gtk_calendar_init (GtkCalendar *calendar)
{
560
  GtkWidget *widget = GTK_WIDGET (calendar);
561 562 563 564
  time_t secs;
  struct tm *tm;
  gint i;
  char buffer[255];
565 566 567
#ifdef G_OS_WIN32
  wchar_t wbuffer[100];
#else
568
  time_t tmp_time;
569
#endif
570
  GtkCalendarPrivate *priv;
571
  gchar *year_before;
572
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
573 574 575
  gchar *langinfo;
  gint week_1stday = 0;
  gint first_weekday = 1;
576
  guint week_origin;
577 578 579
#else
  gchar *week_start;
#endif
580

581 582 583 584
  priv = calendar->priv = G_TYPE_INSTANCE_GET_PRIVATE (calendar,
						       GTK_TYPE_CALENDAR,
						       GtkCalendarPrivate);

585
  GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
586
  
587 588 589
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
590
#ifndef G_OS_WIN32
591 592
	tmp_time= (i+3)*86400;
	strftime ( buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
593
	default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
#else
	if (G_WIN32_HAVE_WIDECHAR_API ())
	  {
	    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);
	  }
	else
	  {
	    if (!GetLocaleInfoA (GetThreadLocale (),
				 (LOCALE_SABBREVDAYNAME1 + (i+6)%7) | LOCALE_USE_CP_ACP,
				 buffer, G_N_ELEMENTS (buffer)))
	      default_abbreviated_dayname[i] = g_strdup_printf ("(%d)", i);
	    else
	      default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
	  }
#endif
613
      }
614
  
615 616 617
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
618
#ifndef G_OS_WIN32
619 620
	tmp_time=i*2764800;
	strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
621
	default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
#else
	if (G_WIN32_HAVE_WIDECHAR_API ())
	  {
	    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);
	  }
	else
	  {
	    if (!GetLocaleInfoA (GetThreadLocale (),
				 (LOCALE_SMONTHNAME1 + i) | LOCALE_USE_CP_ACP,
				 buffer, G_N_ELEMENTS (buffer)))
	      default_monthname[i] = g_strdup_printf ("(%d)", i);
	    else
	      default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
	  }
#endif
641
      }
642
  
643 644 645 646 647
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
  calendar->month = tm->tm_mon;
  calendar->year  = 1900 + tm->tm_year;
648

649 650
  for (i=0;i<31;i++)
    calendar->marked_date[i] = FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
651
  calendar->num_marked_dates = 0;
652
  calendar->selected_day = tm->tm_mday;
653
  
654 655
  calendar->display_flags = ( GTK_CALENDAR_SHOW_HEADING | 
			      GTK_CALENDAR_SHOW_DAY_NAMES );
656
  
657 658
  calendar->highlight_row = -1;
  calendar->highlight_col = -1;
659
  
660 661 662
  calendar->focus_row = -1;
  calendar->focus_col = -1;

663 664 665 666
  priv->max_year_width = 0;
  priv->max_month_width = 0;
  priv->max_day_char_width = 0;
  priv->max_week_char_width = 0;
667

668 669 670 671
  priv->max_day_char_ascent = 0;
  priv->max_day_char_descent = 0;
  priv->max_label_char_ascent = 0;
  priv->max_label_char_descent = 0;
672

673
  priv->arrow_width = 10;
674

675 676 677
  priv->need_timer = 0;
  priv->timer = 0;
  priv->click_child = -1;
678

679 680
  priv->in_drag = 0;
  priv->drag_highlight = 0;
681

682 683
  gtk_drag_dest_set (widget, 0, NULL, 0, GDK_ACTION_COPY);
  gtk_drag_dest_add_text_targets (widget);
684

685
  priv->year_before = 0;
686 687 688 689 690 691 692 693 694 695 696 697

  /* 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.
   *
   * Note that this flipping is in top the text direction flipping,
   * so if you have a default text direction of RTL and YM, then
   * the year will appear on the right.
   */
  year_before = _("calendar:MY");
  if (strcmp (year_before, "calendar:YM") == 0)
698
    priv->year_before = 1;
699 700
  else if (strcmp (year_before, "calendar:MY") != 0)
    g_warning ("Whoever translated calendar:MY did so wrongly.\n");
701

702
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
703 704 705 706 707 708 709 710
  langinfo = nl_langinfo (_NL_TIME_FIRST_WEEKDAY);
  first_weekday = langinfo[0];
  langinfo = nl_langinfo (_NL_TIME_WEEK_1STDAY);
  week_origin = GPOINTER_TO_INT (langinfo);
  if (week_origin == 19971130) /* Sunday */
    week_1stday = 0;
  else if (week_origin == 19971201) /* Monday */
    week_1stday = 1;
711 712 713
  else
    g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.\n");

714
  priv->week_start = (week_1stday + first_weekday - 1) % 7;
715
#else
716 717 718 719 720 721 722
  /* 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.
   */  
  week_start = _("calendar:week_start:0");

  if (strncmp (week_start, "calendar:week_start:", 20) == 0)
723
    priv->week_start = *(week_start + 20) - '0';
724
  else 
725
    priv->week_start = -1;
726
  
727
  if (priv->week_start < 0 || priv->week_start > 6)
728 729
    {
      g_warning ("Whoever translated calendar:week_start:0 did so wrongly.\n");
730
      priv->week_start = 0;
731
    }
732
#endif
733 734

  calendar_compute_days (calendar);
735 736
}

737 738 739 740

/****************************************
 *          Utility Functions           *
 ****************************************/
741 742

static void
743
calendar_set_month_next (GtkCalendar *calendar)
744 745
{
  gint month_len;
746
  
747
  g_return_if_fail (GTK_IS_WIDGET (calendar));
748
  
749 750
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
751 752
  
  
753 754 755 756 757 758 759
  if (calendar->month == 11)
    {
      calendar->month = 0;
      calendar->year++;
    } 
  else 
    calendar->month++;
760
  
761
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
762 763 764 765 766 767
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_MONTH_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
768
  
769
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
770
  
771 772 773 774 775 776 777
  if (month_len < calendar->selected_day)
    {
      calendar->selected_day = 0;
      gtk_calendar_select_day (calendar, month_len);
    }
  else
    gtk_calendar_select_day (calendar, calendar->selected_day);
778 779

  gtk_widget_queue_draw (GTK_WIDGET (calendar));
780 781 782
}

static void
783
calendar_set_year_prev (GtkCalendar *calendar)
784 785
{
  gint month_len;
786
  
787
  g_return_if_fail (GTK_IS_WIDGET (calendar));
788
  
789
  calendar->year--;
790
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
791 792 793 794 795 796
  g_signal_emit (calendar,
		 gtk_calendar_signals[PREV_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
797
  
798
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
799
  
800 801 802 803 804 805 806
  if (month_len < calendar->selected_day)
    {
      calendar->selected_day = 0;
      gtk_calendar_select_day (calendar, month_len);
    }
  else
    gtk_calendar_select_day (calendar, calendar->selected_day);
807
  
808
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
809 810 811
}

static void
812
calendar_set_year_next (GtkCalendar *calendar)
813 814
{
  gint month_len;
815
  
816
  g_return_if_fail (GTK_IS_WIDGET (calendar));
817
  
818
  calendar->year++;
819
  calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
820 821 822 823 824 825
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
826
  
827
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
828
  
829 830 831 832 833 834 835
  if (month_len < calendar->selected_day)
    {
      calendar->selected_day = 0;
      gtk_calendar_select_day (calendar, month_len);
    }
  else
    gtk_calendar_select_day (calendar, calendar->selected_day);
836
  
837
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
838 839 840
}

static void
841
calendar_compute_days (GtkCalendar *calendar)
842
{
843 844 845 846 847 848 849 850
  GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (GTK_WIDGET (calendar));
  gint month;
  gint year;
  gint ndays_in_month;
  gint ndays_in_prev_month;
  gint first_day;
  gint row;
  gint col;
851
  gint day;
852 853 854 855 856

  g_return_if_fail (GTK_IS_CALENDAR (calendar));

  year = calendar->year;
  month = calendar->month + 1;
857
  
858
  ndays_in_month = month_length[leap (year)][month];
859
  
860 861
  first_day = day_of_week (year, month, 1);
  first_day = (first_day + 7 - priv->week_start) % 7;
862
  
863 864 865 866 867 868
  /* 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;
869
  
870 871
  row = 0;
  if (first_day > 0)
872
    {
873 874 875 876 877 878 879 880 881 882 883 884 885 886
      for (col = 0; col < first_day; col++)
	{
	  calendar->day[row][col] = day;
	  calendar->day_month[row][col] = MONTH_PREV;
	  day++;
	}
    }
  
  /* Compute days of current month */
  col = first_day;
  for (day = 1; day <= ndays_in_month; day++)
    {
      calendar->day[row][col] = day;
      calendar->day_month[row][col] = MONTH_CURRENT;
887
      
888 889
      col++;
      if (col == 7)
890
	{
891 892
	  row++;
	  col = 0;
893
	}
894
    }
895 896 897 898
  
  /* Compute days of next month */
  day = 1;
  for (; row <= 5; row++)
899
    {
900 901 902 903 904 905 906
      for (; col <= 6; col++)
	{
	  calendar->day[row][col] = day;
	  calendar->day_month[row][col] = MONTH_NEXT;
	  day++;
	}
      col = 0;
907 908 909 910
    }
}

static void
911 912
calendar_select_and_focus_day (GtkCalendar *calendar,
			       guint        day)
913
{
914 915 916 917
  gint old_focus_row = calendar->focus_row;
  gint old_focus_col = calendar->focus_col;
  gint row;
  gint col;
918
  
919 920 921 922