gtkcalendar.c 90.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * GTK Calendar Widget
 * Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson and Mattias Grnlund
 * 
 * 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 33 34
#include "config.h"

#ifdef HAVE_SYS_TIME_H
35
#include <sys/time.h>
36
#endif
37 38 39
#include <string.h>
#include <stdlib.h>
#include <time.h>
40
#include <glib/gprintf.h>
Manish Singh's avatar
Manish Singh committed
41

42
#include "gtkcalendar.h"
Matthias Clasen's avatar
Matthias Clasen committed
43
#include "gtkintl.h"
44 45
#include "gtkmain.h"
#include "gtkmarshalers.h"
46
#include "gtkintl.h"
47 48 49 50 51 52 53
#include "gdk/gdkkeysyms.h"

/***************************************************************************/
/* The following date routines are taken from the lib_date package.  Keep
 * them seperate in case we want to update them if a newer lib_date comes
 * out with fixes.  */

54 55 56
typedef	 unsigned   int	    N_int;
typedef	 unsigned   long    N_long;
typedef	 signed	    long    Z_long;
57 58
typedef enum { false = FALSE , true = TRUE } boolean;

59 60
#define and	    &&	    /* logical (boolean) operators: lower case */
#define or	    ||
61

62
static const N_int month_length[2][13] =
63
{
64 65
  { 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 }
66 67
};

68
static const N_int days_in_months[2][14] =
69
{
70 71
  { 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 }
72 73 74 75 76
};

static Z_long  calc_days(N_int year, N_int mm, N_int dd);
static N_int   day_of_week(N_int year, N_int mm, N_int dd);
static Z_long  dates_difference(N_int year1, N_int mm1, N_int dd1,
77
				N_int year2, N_int mm2, N_int dd2);
78 79 80 81 82
static N_int   weeks_in_year(N_int year);

static boolean 
leap(N_int year)
{
83
  return((((year % 4) == 0) and ((year % 100) != 0)) or ((year % 400) == 0));
84 85 86 87 88
}

static N_int 
day_of_week(N_int year, N_int mm, N_int dd)
{
89 90 91 92
  Z_long  days;
  
  days = calc_days(year, mm, dd);
  if (days > 0L)
93
    {
94 95 96
      days--;
      days %= 7L;
      days++;
97
    }
98
  return( (N_int) days );
99 100 101 102
}

static N_int weeks_in_year(N_int year)
{
103
  return(52 + ((day_of_week(year,1,1)==4) or (day_of_week(year,12,31)==4)));
104 105 106 107 108
}

static boolean 
check_date(N_int year, N_int mm, N_int dd)
{
109 110 111 112
  if (year < 1) return(false);
  if ((mm < 1) or (mm > 12)) return(false);
  if ((dd < 1) or (dd > month_length[leap(year)][mm])) return(false);
  return(true);
113 114 115 116 117
}

static N_int 
week_number(N_int year, N_int mm, N_int dd)
{
118 119 120 121 122
  N_int first;
  
  first = day_of_week(year,1,1) - 1;
  return( (N_int) ( (dates_difference(year,1,1, year,mm,dd) + first) / 7L ) +
	  (first < 4) );
123 124 125 126 127
}

static Z_long 
year_to_days(N_int year)
{
128
  return( year * 365L + (year / 4) - (year / 100) + (year / 400) );
129 130 131 132 133 134
}


static Z_long 
calc_days(N_int year, N_int mm, N_int dd)
{
135 136 137 138 139 140
  boolean lp;
  
  if (year < 1) return(0L);
  if ((mm < 1) or (mm > 12)) return(0L);
  if ((dd < 1) or (dd > month_length[(lp = leap(year))][mm])) return(0L);
  return( year_to_days(--year) + days_in_months[lp][mm] + dd );
141 142 143 144 145
}

static boolean 
week_of_year(N_int *week, N_int *year, N_int mm, N_int dd)
{
146 147 148 149 150 151 152 153 154 155 156 157 158
  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)++;
	}
      return(true);
    }
  return(false);
159 160 161 162
}

static Z_long 
dates_difference(N_int year1, N_int mm1, N_int dd1,
163
		 N_int year2, N_int mm2, N_int dd2)
164
{
165
  return( calc_days(year2, mm2, dd2) - calc_days(year1, mm1, dd1) );
166 167
}

168
/*** END OF lib_date routines ********************************************/
169

170
/* Spacing around day/week headers and main area, inside those windows */
171
#define CALENDAR_MARGIN		 0
172 173 174
/* Spacing around day/week headers and main area, outside those windows */
#define INNER_BORDER		 4
/* Separation between day headers and main area */
175
#define CALENDAR_YSEP		 4
176
/* Separation between week headers and main area */
177
#define CALENDAR_XSEP		 4
178

179
#define DAY_XSEP		 0 /* not really good for small calendar */
180 181 182
#define DAY_YSEP		 0 /* not really good for small calendar */

/* Color usage */
183 184
#define HEADER_FG_COLOR(widget)		 (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
#define HEADER_BG_COLOR(widget)		 (& (widget)->style->bg[GTK_WIDGET_STATE (widget)])
185 186
#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])
187 188 189 190 191 192
#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)])
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

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
218 219 220 221 222 223 224 225 226 227 228 229 230
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
};

231 232
static gint gtk_calendar_signals[LAST_SIGNAL] = { 0 };

233 234 235 236 237 238 239 240 241 242 243
static GtkWidgetClass *parent_class = NULL;

typedef struct _GtkCalendarPrivateData GtkCalendarPrivateData;
struct _GtkCalendarPrivateData
{
  GdkWindow *header_win;
  GdkWindow *day_name_win;
  GdkWindow *main_win;
  GdkWindow *week_win;
  GdkWindow *arrow_win[4];

244 245 246
  guint header_h;
  guint day_name_h;
  guint main_h;
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263

  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;
  
264 265
  guint freeze_count;

266
  /* flags */
267 268 269 270
  guint dirty_header : 1;
  guint dirty_day_names : 1;
  guint dirty_main : 1;
  guint dirty_week : 1;
271

272 273
  guint year_before : 1;

274 275 276 277
  guint need_timer  : 1;

  guint32 timer;
  gint click_child;
278 279

  gint week_start;
280 281 282 283
};

#define GTK_CALENDAR_PRIVATE_DATA(widget)  (((GtkCalendarPrivateData*)(GTK_CALENDAR (widget)->private_data)))

284 285
typedef void (*GtkCalendarSignalDate) (GtkObject *object, guint arg1, guint arg2, guint arg3, gpointer data);

286 287
static void gtk_calendar_class_init	(GtkCalendarClass *class);
static void gtk_calendar_init		(GtkCalendar *calendar);
288
static void gtk_calendar_finalize	(GObject *calendar);
289
static void gtk_calendar_destroy	(GtkObject *calendar);
Matthias Clasen's avatar
Matthias Clasen committed
290 291 292 293 294 295 296 297
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);
298 299 300 301 302 303 304 305 306 307
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 gint gtk_calendar_expose		(GtkWidget *widget,
					 GdkEventExpose *event);
static gint gtk_calendar_button_press	(GtkWidget *widget,
					 GdkEventButton *event);
308 309
static gint gtk_calendar_button_release	(GtkWidget *widget,
					 GdkEventButton *event);
310 311 312 313 314 315 316 317 318 319
static void gtk_calendar_main_button	(GtkWidget *widget,
					 GdkEventButton *event);
static gint gtk_calendar_motion_notify	(GtkWidget *widget,
					 GdkEventMotion *event);
static gint gtk_calendar_enter_notify	(GtkWidget *widget,
					 GdkEventCrossing *event);
static gint gtk_calendar_leave_notify	(GtkWidget *widget,
					 GdkEventCrossing *event);
static gint gtk_calendar_key_press	(GtkWidget	   *widget,
					 GdkEventKey	   *event);
320 321
static gint gtk_calendar_scroll         (GtkWidget         *widget,
					 GdkEventScroll    *event);
322 323
static void gtk_calendar_grab_notify    (GtkWidget          *widget,
			 	         gboolean            was_grabbed);
324
static void gtk_calendar_state_changed	(GtkWidget *widget,
325
					 GtkStateType previous_state);
326
static void gtk_calendar_style_set	(GtkWidget *widget,
327
					 GtkStyle  *previous_style);
328 329 330 331 332
static void gtk_calendar_paint_header	    (GtkWidget *widget);
static void gtk_calendar_paint_day_names    (GtkWidget *widget);
static void gtk_calendar_paint_week_numbers (GtkWidget *widget);
static void gtk_calendar_paint_main	    (GtkWidget *widget);

333 334
static void gtk_calendar_select_and_focus_day (GtkCalendar *calendar,
					       guint        day);
335 336 337 338 339 340 341 342 343 344 345 346 347

static void gtk_calendar_paint_arrow	(GtkWidget    *widget,
					 guint	       arrow);
static void gtk_calendar_paint_day_num	(GtkWidget    *widget,
					 gint	       day);
static void gtk_calendar_paint_day	(GtkWidget    *widget,
					 gint	       row,
					 gint	       col);
static void gtk_calendar_compute_days	(GtkCalendar  *calendar);
static gint left_x_for_column		(GtkCalendar  *calendar,
					 gint	       column);
static gint top_y_for_row		(GtkCalendar  *calendar,
					 gint	       row);
348 349 350 351

static char    *default_abbreviated_dayname[7];
static char    *default_monthname[12];

Manish Singh's avatar
Manish Singh committed
352
GType
353
gtk_calendar_get_type (void)
354
{
Manish Singh's avatar
Manish Singh committed
355
  static GType calendar_type = 0;
356
  
357 358
  if (!calendar_type)
    {
359
      static const GTypeInfo calendar_info =
360 361
      {
	sizeof (GtkCalendarClass),
Manish Singh's avatar
Manish Singh committed
362 363
	NULL,		/* base_init */
	NULL,		/* base_finalize */
364
	(GClassInitFunc) gtk_calendar_class_init,
Manish Singh's avatar
Manish Singh committed
365 366
	NULL,		/* class_finalize */
	NULL,		/* class_data */
367
	sizeof (GtkCalendar),
Manish Singh's avatar
Manish Singh committed
368
	0,		/* n_preallocs */
369
	(GInstanceInitFunc) gtk_calendar_init,
370
      };
371

Manish Singh's avatar
Manish Singh committed
372 373
      calendar_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkCalendar",
					      &calendar_info, 0);
374
    }
375
  
376 377 378 379 380 381
  return calendar_type;
}

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
382
  GObjectClass   *gobject_class;
383
  GtkObjectClass   *object_class;
384
  GtkWidgetClass *widget_class;
385 386

  gobject_class = (GObjectClass*)  class;
387
  object_class = (GtkObjectClass*)  class;
388
  widget_class = (GtkWidgetClass*) class;
389
  
Manish Singh's avatar
Manish Singh committed
390
  parent_class = g_type_class_peek_parent (class);
391
  
Matthias Clasen's avatar
Matthias Clasen committed
392 393
  gobject_class->set_property = gtk_calendar_set_property;
  gobject_class->get_property = gtk_calendar_get_property;
394
  gobject_class->finalize = gtk_calendar_finalize;
395

396 397
  object_class->destroy = gtk_calendar_destroy;

398 399 400 401 402 403
  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;
404
  widget_class->button_release_event = gtk_calendar_button_release;
405 406 407 408
  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;
409
  widget_class->scroll_event = gtk_calendar_scroll;
410 411
  widget_class->style_set = gtk_calendar_style_set;
  widget_class->state_changed = gtk_calendar_state_changed;
412
  widget_class->grab_notify = gtk_calendar_grab_notify;
413
  
414 415 416 417 418 419 420 421
  class->month_changed = NULL;
  class->day_selected = NULL;
  class->day_selected_double_click = NULL;
  class->prev_month = NULL;
  class->next_month = NULL;
  class->prev_year = NULL;
  class->next_year = NULL;

Matthias Clasen's avatar
Matthias Clasen committed
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
  g_object_class_install_property (gobject_class,
                                   PROP_YEAR,
                                   g_param_spec_int ("year",
						     _("Year"),
						     _("The selected year"),
						     0, G_MAXINT, 0,
						     G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MONTH,
                                   g_param_spec_int ("month",
						     _("Month"),
						     _("The selected month (as a number between 0 and 11)"),
						     0, 11, 0,
						     G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_DAY,
                                   g_param_spec_int ("day",
						     _("Day"),
						     _("The selected day (as a number between 1 and 31, or 0 to unselect the currently selected day)"),
						     0, 31, 0,
						     G_PARAM_READWRITE));
443 444 445 446 447 448 449 450

/**
 * GtkCalendar:show-heading:
 *
 * Determines whether a heading is displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
451 452 453 454 455 456 457
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_HEADING,
                                   g_param_spec_boolean ("show_heading",
							 _("Show Heading"),
							 _("If TRUE, a heading is displayed"),
							 TRUE,
							 G_PARAM_READWRITE));
458 459 460 461 462 463 464 465

/**
 * GtkCalendar:show-day-names:
 *
 * Determines whether day names are displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
466 467 468 469 470 471 472
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_DAY_NAMES,
                                   g_param_spec_boolean ("show_day_names",
							 _("Show Day Names"),
							 _("If TRUE, day names are displayed"),
							 TRUE,
							 G_PARAM_READWRITE));
473 474 475 476 477 478 479
/**
 * GtkCalendar:no-month-change:
 *
 * Determines whether the selected month can be changed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
480 481 482 483 484 485 486
  g_object_class_install_property (gobject_class,
                                   PROP_NO_MONTH_CHANGE,
                                   g_param_spec_boolean ("no_month_change",
							 _("No Month Change"),
							 _("If TRUE, the selected month can not be changed"),
							 FALSE,
							 G_PARAM_READWRITE));
487 488 489 490 491 492 493 494

/**
 * GtkCalendar:show-week-numbers:
 *
 * Determines whether week numbers are displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
495 496 497 498 499 500 501
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_WEEK_NUMBERS,
                                   g_param_spec_boolean ("show_week_numbers",
							 _("Show Week Numbers"),
							 _("If TRUE, week numbers are displayed"),
							 FALSE,
							 G_PARAM_READWRITE));
502

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

static void
gtk_calendar_init (GtkCalendar *calendar)
{
  time_t secs;
  struct tm *tm;
  gint i;
  char buffer[255];
  time_t tmp_time;
  GtkWidget *widget;
570
  GtkCalendarPrivateData *private_data;
571
  gchar *year_before;
572
  gchar *week_start;
573
  
574 575
  widget = GTK_WIDGET (calendar);
  GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
576
  
Manish Singh's avatar
Manish Singh committed
577
  calendar->private_data = g_malloc (sizeof (GtkCalendarPrivateData));
578 579
  private_data = GTK_CALENDAR_PRIVATE_DATA (calendar);

580 581 582 583 584
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
	tmp_time= (i+3)*86400;
	strftime ( buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
585
	default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
586
      }
587
  
588 589 590 591 592
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
	tmp_time=i*2764800;
	strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
593
	default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
594
      }
595
  
596 597 598 599 600
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
  calendar->month = tm->tm_mon;
  calendar->year  = 1900 + tm->tm_year;
601
  
602 603
  for (i=0;i<31;i++)
    calendar->marked_date[i] = FALSE;
604
  calendar->num_marked_dates = 0;
605
  calendar->selected_day = tm->tm_mday;
606
  
607 608
  calendar->display_flags = ( GTK_CALENDAR_SHOW_HEADING | 
			      GTK_CALENDAR_SHOW_DAY_NAMES );
609
  
610 611
  calendar->highlight_row = -1;
  calendar->highlight_col = -1;
612
  
613 614 615 616
  calendar->focus_row = -1;
  calendar->focus_col = -1;
  calendar->xor_gc = NULL;

617 618 619 620 621 622 623 624 625 626 627
  private_data->max_year_width = 0;
  private_data->max_month_width = 0;
  private_data->max_day_char_width = 0;
  private_data->max_week_char_width = 0;

  private_data->max_day_char_ascent = 0;
  private_data->max_day_char_descent = 0;
  private_data->max_label_char_ascent = 0;
  private_data->max_label_char_descent = 0;

  private_data->arrow_width = 10;
628 629

  private_data->freeze_count = 0;
630
  
631 632 633 634
  private_data->dirty_header = 0;
  private_data->dirty_day_names = 0;
  private_data->dirty_week = 0;
  private_data->dirty_main = 0;
635
  
636 637 638
  private_data->need_timer = 0;
  private_data->timer = 0;
  private_data->click_child = -1;
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655

  private_data->year_before = 0;

  /* 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)
    private_data->year_before = 1;
  else if (strcmp (year_before, "calendar:MY") != 0)
    g_warning ("Whoever translated calendar:MY did so wrongly.\n");
656 657 658 659 660 661 662 663 664 665 666 667 668

  /* Translate to calendar:week_start:1 if you want Monday to be the
   * first day of the week; otherwise translate to calendar:week_start:0. 
   * Do *not* translate it to anything else, if it isn't calendar:week_start:1 
   * or calendar:week_start:0 it will not work.
   */
   week_start = _("calendar:week_start:0");
   if (strcmp (week_start, "calendar:week_start:1") == 0)
     private_data->week_start = 1;
   else if (strcmp (week_start, "calendar:week_start:0") == 0) 
     private_data->week_start = 0;
   else
     g_warning ("Whoever translated calendar:week_start:0 did so wrongly.\n");
669 670 671
}

GtkWidget*
672
gtk_calendar_new (void)
673
{
Manish Singh's avatar
Manish Singh committed
674
  return g_object_new (GTK_TYPE_CALENDAR, NULL);
675 676 677 678 679
}

/* column_from_x: returns the column 0-6 that the
 * x pixel of the xwindow is in */
static gint
680 681
column_from_x (GtkCalendar *calendar,
	       gint	    event_x)
682 683 684
{
  gint c, column;
  gint x_left, x_right;
685
  
686
  column = -1;
687
  
688 689 690
  for (c = 0; c < 7; c++)
    {
      x_left = left_x_for_column (calendar, c);
691
      x_right = x_left + GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
692
      
Owen Taylor's avatar
Owen Taylor committed
693
      if (event_x >= x_left && event_x < x_right)
694 695 696 697 698
	{
	  column = c;
	  break;
	}
    }
699
  
700 701 702 703 704 705
  return column;
}

static gint
row_height (GtkCalendar *calendar)
{
706
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h - CALENDAR_MARGIN
707 708 709 710 711 712 713 714
	  - ((calendar->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
	     ? CALENDAR_YSEP : CALENDAR_MARGIN)) / 6;
}


/* row_from_y: returns the row 0-5 that the
 * y pixel of the xwindow is in */
static gint
715 716
row_from_y (GtkCalendar *calendar,
	    gint	 event_y)
717 718 719 720
{
  gint r, row;
  gint height;
  gint y_top, y_bottom;
721
  
722 723
  height = row_height (calendar);
  row = -1;
724
  
725 726 727 728
  for (r = 0; r < 6; r++)
    {
      y_top = top_y_for_row (calendar, r);
      y_bottom = y_top + height;
729
      
Owen Taylor's avatar
Owen Taylor committed
730
      if (event_y >= y_top && event_y < y_bottom)
731 732 733 734 735
	{
	  row = r;
	  break;
	}
    }
736
  
737
  return row;
738
}/* left_x_for_column: returns the x coordinate
739 740
 * for the left of the column */
static gint
741 742
left_x_for_column (GtkCalendar *calendar,
		   gint		column)
743 744 745
{
  gint width;
  gint x_left;
746
  
747 748 749
  if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
    column = 6 - column;

750
  width = GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
751
  if (calendar->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
752
    x_left = CALENDAR_XSEP + (width + DAY_XSEP) * column;
753 754
  else
    x_left = CALENDAR_MARGIN + (width + DAY_XSEP) * column;
755
  
756 757 758 759 760 761
  return x_left;
}

/* top_y_for_row: returns the y coordinate
 * for the top of the row */
static gint
762 763
top_y_for_row (GtkCalendar *calendar,
	       gint	    row)
764
{
765
  
766 767 768
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h 
	  - (CALENDAR_MARGIN + (6 - row)
	     * row_height (calendar)));
769 770 771 772 773
}

/* This function should be done by the toolkit, but we don't like the
 * GTK arrows because they don't look good on this widget */
static void
774 775 776 777 778
draw_arrow_right (GdkWindow *window,
		  GdkGC	    *gc,
		  gint	     x,
		  gint	     y,
		  gint	     size)
779 780
{
  gint i;
781
  
782 783 784 785 786 787 788 789 790 791 792 793 794
  for (i = 0; i <= size / 2; i++)
    {
      gdk_draw_line (window, gc,
		     x + i,
		     y + i,
		     x + i,
		     y + size - i);
    }
}

/* This function should be done by the toolkit, but we don't like the
 * GTK arrows because they don't look good on this widget */
static void
795 796 797 798 799
draw_arrow_left (GdkWindow *window,
		 GdkGC	   *gc,
		 gint	    x,
		 gint	    y,
		 gint	    size)
800 801
{
  gint i;
802
  
803 804 805 806 807 808 809 810 811 812 813 814 815 816
  for (i = 0; i <= size / 2; i++)
    {
      gdk_draw_line (window, gc,
		     x + size/2 - i,
		     y + i,
		     x + size/2 - i,
		     y + size - i);
    }
}

static void
gtk_calendar_set_month_prev (GtkCalendar *calendar)
{
  gint month_len;
817
  
818 819
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
820
  
821 822 823 824 825 826 827
  if (calendar->month == 0)
    {
      calendar->month = 11;
      calendar->year--;
    } 
  else 
    calendar->month--;
828
  
829
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
830
  
831 832
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
833
  
Manish Singh's avatar
Manish Singh committed
834 835 836 837 838 839
  g_signal_emit (calendar,
		 gtk_calendar_signals[PREV_MONTH_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
840
  
841 842 843 844 845 846 847 848 849
  if (month_len < calendar->selected_day)
    {
      calendar->selected_day = 0;
      gtk_calendar_select_day (calendar, month_len);
    }
  else
    {
      if (calendar->selected_day < 0)
	calendar->selected_day = calendar->selected_day + 1 + month_length[leap (calendar->year)][calendar->month + 1];
850
      gtk_calendar_select_day (calendar, calendar->selected_day);
851
    }
852 853

  gtk_widget_queue_draw (GTK_WIDGET (calendar));
854 855 856 857 858 859 860 861
  gtk_calendar_thaw (calendar);
}


static void
gtk_calendar_set_month_next (GtkCalendar *calendar)
{
  gint month_len;
862
  
863
  g_return_if_fail (GTK_IS_WIDGET (calendar));
864
  
865 866
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
867 868
  
  
869 870 871 872 873 874 875
  if (calendar->month == 11)
    {
      calendar->month = 0;
      calendar->year++;
    } 
  else 
    calendar->month++;
876
  
877 878
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
879 880 881 882 883 884
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_MONTH_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
885
  
886
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
887
  
888 889 890 891 892 893 894
  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);
895 896

  gtk_widget_queue_draw (GTK_WIDGET (calendar));
897 898 899 900 901 902 903
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_prev (GtkCalendar *calendar)
{
  gint month_len;
904
  
905
  g_return_if_fail (GTK_IS_WIDGET (calendar));
906
  
907 908 909
  calendar->year--;
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
910 911 912 913 914 915
  g_signal_emit (calendar,
		 gtk_calendar_signals[PREV_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
916
  
917
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
918
  
919 920 921 922 923 924 925
  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);
926
  
927
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
928 929 930 931 932 933 934 935
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_next (GtkCalendar *calendar)
{
  gint month_len;
  GtkWidget *widget;
936
  
937
  g_return_if_fail (GTK_IS_WIDGET (calendar));
938
  
939
  widget = GTK_WIDGET (calendar);
940
  
941
  gtk_calendar_freeze (calendar);
942
  
943 944
  calendar->year++;
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
945 946 947 948 949 950
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
951
  
952
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
953
  
954 955 956 957 958 959 960
  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);
961
  
962
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
963 964 965 966
  gtk_calendar_thaw (calendar);
}

static void
967
gtk_calendar_main_button (GtkWidget	 *widget,
968 969 970 971 972 973
			  GdkEventButton *event)
{
  GtkCalendar *calendar;
  gint x, y;
  gint row, col;
  gint day_month;
974
  gint day;
975
  
976
  calendar = GTK_CALENDAR (widget);
977
  
978 979
  x = (gint) (event->x);
  y = (gint) (event->y);
980
  
981 982
  row = row_from_y (calendar, y);
  col = column_from_x (calendar, x);
Owen Taylor's avatar
Owen Taylor committed
983 984 985 986

  /* If row or column isn't found, just return. */
  if (row == -1 || col == -1)
    return;
987
  
988
  day_month = calendar->day_month[row][col];
989 990

  if (event->type == GDK_BUTTON_PRESS)
991
    {
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
      day = calendar->day[row][col];
      
      if (day_month == MONTH_PREV)
	gtk_calendar_set_month_prev (calendar);
      else if (day_month == MONTH_NEXT)
	gtk_calendar_set_month_next (calendar);
      
      if (!GTK_WIDGET_HAS_FOCUS (widget))
	gtk_widget_grab_focus (widget);
	  
      gtk_calendar_select_and_focus_day (calendar, day);

    }
  else if (event->type == GDK_2BUTTON_PRESS)
    {
      if (day_month == MONTH_CURRENT)
Manish Singh's avatar
Manish Singh committed
1008 1009 1010
	g_signal_emit (calendar,
		       gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL],
		       0);
1011 1012 1013 1014 1015 1016 1017
    }
}

static void
gtk_calendar_realize_arrows (GtkWidget *widget)
{
  GtkCalendar *calendar;
1018
  GtkCalendarPrivateData *private_data;
1019 1020 1021
  GdkWindowAttr attributes;
  gint attributes_mask;
  gint i;
1022
  gboolean year_left;
1023
  
1024
  g_return_if_fail (GTK_IS_CALENDAR (widget));