gtkcalendar.c 97.9 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"
43
#include "gtkdnd.h"
Matthias Clasen's avatar
Matthias Clasen committed
44
#include "gtkintl.h"
45 46
#include "gtkmain.h"
#include "gtkmarshalers.h"
47
#include "gtkintl.h"
48 49 50 51 52 53 54
#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.  */

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

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

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

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

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,
78
				N_int year2, N_int mm2, N_int dd2);
79 80 81 82 83
static N_int   weeks_in_year(N_int year);

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

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

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

static boolean 
check_date(N_int year, N_int mm, N_int dd)
{
110 111 112 113
  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);
114 115 116 117 118
}

static N_int 
week_number(N_int year, N_int mm, N_int dd)
{
119 120 121 122 123
  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) );
124 125 126 127 128
}

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


static Z_long 
calc_days(N_int year, N_int mm, N_int dd)
{
136 137 138 139 140 141
  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 );
142 143 144 145 146
}

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

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

169
/*** END OF lib_date routines ********************************************/
170

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

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

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

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

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

234 235 236 237 238 239 240 241 242 243 244
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];

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

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

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

273 274
  guint year_before : 1;

275 276
  guint need_timer  : 1;

277 278 279
  guint in_drag : 1;
  guint drag_highlight : 1;

280 281
  guint32 timer;
  gint click_child;
282 283

  gint week_start;
284 285 286

  gint drag_start_x;
  gint drag_start_y;
287 288 289 290
};

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

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

293 294
static void gtk_calendar_class_init	(GtkCalendarClass *class);
static void gtk_calendar_init		(GtkCalendar *calendar);
295
static void gtk_calendar_finalize	(GObject *calendar);
296
static void gtk_calendar_destroy	(GtkObject *calendar);
Matthias Clasen's avatar
Matthias Clasen committed
297 298 299 300 301 302 303 304
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);
305 306 307 308 309 310 311 312 313 314
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);
315 316
static gint gtk_calendar_button_release	(GtkWidget *widget,
					 GdkEventButton *event);
317 318 319 320 321 322 323 324 325 326
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);
327 328
static gint gtk_calendar_scroll         (GtkWidget         *widget,
					 GdkEventScroll    *event);
329 330
static void gtk_calendar_grab_notify    (GtkWidget          *widget,
			 	         gboolean            was_grabbed);
331
static void gtk_calendar_state_changed	(GtkWidget *widget,
332
					 GtkStateType previous_state);
333
static void gtk_calendar_style_set	(GtkWidget *widget,
334
					 GtkStyle  *previous_style);
335 336 337 338 339
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);

340 341
static void gtk_calendar_select_and_focus_day (GtkCalendar *calendar,
					       guint        day);
342 343 344 345 346 347 348 349 350 351 352 353 354

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);
355

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
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);
     
382 383 384
static char    *default_abbreviated_dayname[7];
static char    *default_monthname[12];

Manish Singh's avatar
Manish Singh committed
385
GType
386
gtk_calendar_get_type (void)
387
{
Manish Singh's avatar
Manish Singh committed
388
  static GType calendar_type = 0;
389
  
390 391
  if (!calendar_type)
    {
392
      static const GTypeInfo calendar_info =
393 394
      {
	sizeof (GtkCalendarClass),
Manish Singh's avatar
Manish Singh committed
395 396
	NULL,		/* base_init */
	NULL,		/* base_finalize */
397
	(GClassInitFunc) gtk_calendar_class_init,
Manish Singh's avatar
Manish Singh committed
398 399
	NULL,		/* class_finalize */
	NULL,		/* class_data */
400
	sizeof (GtkCalendar),
Manish Singh's avatar
Manish Singh committed
401
	0,		/* n_preallocs */
402
	(GInstanceInitFunc) gtk_calendar_init,
403
      };
404

Manish Singh's avatar
Manish Singh committed
405 406
      calendar_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkCalendar",
					      &calendar_info, 0);
407
    }
408
  
409 410 411 412 413 414
  return calendar_type;
}

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
415
  GObjectClass   *gobject_class;
416
  GtkObjectClass   *object_class;
417
  GtkWidgetClass *widget_class;
418 419

  gobject_class = (GObjectClass*)  class;
420
  object_class = (GtkObjectClass*)  class;
421
  widget_class = (GtkWidgetClass*) class;
422
  
Manish Singh's avatar
Manish Singh committed
423
  parent_class = g_type_class_peek_parent (class);
424
  
Matthias Clasen's avatar
Matthias Clasen committed
425 426
  gobject_class->set_property = gtk_calendar_set_property;
  gobject_class->get_property = gtk_calendar_get_property;
427
  gobject_class->finalize = gtk_calendar_finalize;
428

429 430
  object_class->destroy = gtk_calendar_destroy;

431 432 433 434 435 436
  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;
437
  widget_class->button_release_event = gtk_calendar_button_release;
438 439 440 441
  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;
442
  widget_class->scroll_event = gtk_calendar_scroll;
443 444
  widget_class->style_set = gtk_calendar_style_set;
  widget_class->state_changed = gtk_calendar_state_changed;
445
  widget_class->grab_notify = gtk_calendar_grab_notify;
446 447 448 449 450 451

  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;
452
  
453 454 455 456 457 458 459 460
  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
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
  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));
482 483 484 485 486 487 488 489

/**
 * GtkCalendar:show-heading:
 *
 * Determines whether a heading is displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
490 491 492 493 494 495 496
  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));
497 498 499 500 501 502 503 504

/**
 * GtkCalendar:show-day-names:
 *
 * Determines whether day names are displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
505 506 507 508 509 510 511
  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));
512 513 514 515 516 517 518
/**
 * GtkCalendar:no-month-change:
 *
 * Determines whether the selected month can be changed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
519 520 521 522 523 524 525
  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));
526 527 528 529 530 531 532 533

/**
 * GtkCalendar:show-week-numbers:
 *
 * Determines whether week numbers are displayed.
 *
 * Since: 2.4
 */
Matthias Clasen's avatar
Matthias Clasen committed
534 535 536 537 538 539 540
  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));
541

542
  gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
543 544 545 546 547 548 549
    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);
550
  gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
551 552 553 554 555 556 557
    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);
558
  gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
559 560 561 562 563 564 565
    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);
566
  gtk_calendar_signals[PREV_MONTH_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
567 568 569 570 571 572 573
    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);
574
  gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
575 576 577 578 579 580 581
    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);
582
  gtk_calendar_signals[PREV_YEAR_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
583 584 585 586 587 588 589
    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);
590
  gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
591 592 593 594 595 596 597
    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);
598 599
}

600 601 602 603 604 605 606 607 608 609 610
enum
{
  TARGET_TEXT,
};

static const GtkTargetEntry target_table[] = {
  { "TEXT",               0, TARGET_TEXT },
  { "STRING",             0, TARGET_TEXT },
  { "UTF8_STRING",        0, TARGET_TEXT },
};

611 612 613 614 615 616 617 618 619
static void
gtk_calendar_init (GtkCalendar *calendar)
{
  time_t secs;
  struct tm *tm;
  gint i;
  char buffer[255];
  time_t tmp_time;
  GtkWidget *widget;
620
  GtkCalendarPrivateData *private_data;
621
  gchar *year_before;
622
  gchar *week_start;
623
  
624 625
  widget = GTK_WIDGET (calendar);
  GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
626
  
Manish Singh's avatar
Manish Singh committed
627
  calendar->private_data = g_malloc (sizeof (GtkCalendarPrivateData));
628 629
  private_data = GTK_CALENDAR_PRIVATE_DATA (calendar);

630 631 632 633 634
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
	tmp_time= (i+3)*86400;
	strftime ( buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
635
	default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
636
      }
637
  
638 639 640 641 642
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
	tmp_time=i*2764800;
	strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
643
	default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
644
      }
645
  
646 647 648 649 650
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
  calendar->month = tm->tm_mon;
  calendar->year  = 1900 + tm->tm_year;
651
  
652 653
  for (i=0;i<31;i++)
    calendar->marked_date[i] = FALSE;
654
  calendar->num_marked_dates = 0;
655
  calendar->selected_day = tm->tm_mday;
656
  
657 658
  calendar->display_flags = ( GTK_CALENDAR_SHOW_HEADING | 
			      GTK_CALENDAR_SHOW_DAY_NAMES );
659
  
660 661
  calendar->highlight_row = -1;
  calendar->highlight_col = -1;
662
  
663 664 665 666
  calendar->focus_row = -1;
  calendar->focus_col = -1;
  calendar->xor_gc = NULL;

667 668 669 670 671 672 673 674 675 676 677
  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;
678 679

  private_data->freeze_count = 0;
680
  
681 682 683 684
  private_data->dirty_header = 0;
  private_data->dirty_day_names = 0;
  private_data->dirty_week = 0;
  private_data->dirty_main = 0;
685
  
686 687 688
  private_data->need_timer = 0;
  private_data->timer = 0;
  private_data->click_child = -1;
689

690 691 692 693 694 695 696 697
  private_data->in_drag = 0;
  private_data->drag_highlight = 0;

  gtk_drag_dest_set (widget,
		     0,
                     target_table, G_N_ELEMENTS (target_table),
                     GDK_ACTION_COPY);

698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
  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");
714 715 716 717 718 719 720 721 722 723 724 725 726

  /* 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");
727 728 729
}

GtkWidget*
730
gtk_calendar_new (void)
731
{
Manish Singh's avatar
Manish Singh committed
732
  return g_object_new (GTK_TYPE_CALENDAR, NULL);
733 734 735 736 737
}

/* column_from_x: returns the column 0-6 that the
 * x pixel of the xwindow is in */
static gint
738 739
column_from_x (GtkCalendar *calendar,
	       gint	    event_x)
740 741 742
{
  gint c, column;
  gint x_left, x_right;
743
  
744
  column = -1;
745
  
746 747 748
  for (c = 0; c < 7; c++)
    {
      x_left = left_x_for_column (calendar, c);
749
      x_right = x_left + GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
750
      
Owen Taylor's avatar
Owen Taylor committed
751
      if (event_x >= x_left && event_x < x_right)
752 753 754 755 756
	{
	  column = c;
	  break;
	}
    }
757
  
758 759 760 761 762 763
  return column;
}

static gint
row_height (GtkCalendar *calendar)
{
764
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h - CALENDAR_MARGIN
765 766 767 768 769 770 771 772
	  - ((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
773 774
row_from_y (GtkCalendar *calendar,
	    gint	 event_y)
775 776 777 778
{
  gint r, row;
  gint height;
  gint y_top, y_bottom;
779
  
780 781
  height = row_height (calendar);
  row = -1;
782
  
783 784 785 786
  for (r = 0; r < 6; r++)
    {
      y_top = top_y_for_row (calendar, r);
      y_bottom = y_top + height;
787
      
Owen Taylor's avatar
Owen Taylor committed
788
      if (event_y >= y_top && event_y < y_bottom)
789 790 791 792 793
	{
	  row = r;
	  break;
	}
    }
794
  
795
  return row;
796
}/* left_x_for_column: returns the x coordinate
797 798
 * for the left of the column */
static gint
799 800
left_x_for_column (GtkCalendar *calendar,
		   gint		column)
801 802 803
{
  gint width;
  gint x_left;
804
  
805 806 807
  if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
    column = 6 - column;

808
  width = GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
809
  if (calendar->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
810
    x_left = CALENDAR_XSEP + (width + DAY_XSEP) * column;
811 812
  else
    x_left = CALENDAR_MARGIN + (width + DAY_XSEP) * column;
813
  
814 815 816 817 818 819
  return x_left;
}

/* top_y_for_row: returns the y coordinate
 * for the top of the row */
static gint
820 821
top_y_for_row (GtkCalendar *calendar,
	       gint	    row)
822
{
823
  
824 825 826
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h 
	  - (CALENDAR_MARGIN + (6 - row)
	     * row_height (calendar)));
827 828 829 830 831
}

/* 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
832 833 834 835 836
draw_arrow_right (GdkWindow *window,
		  GdkGC	    *gc,
		  gint	     x,
		  gint	     y,
		  gint	     size)
837 838
{
  gint i;
839
  
840 841 842 843 844 845 846 847 848 849 850 851 852
  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
853 854 855 856 857
draw_arrow_left (GdkWindow *window,
		 GdkGC	   *gc,
		 gint	    x,
		 gint	    y,
		 gint	    size)
858 859
{
  gint i;
860
  
861 862 863 864 865 866 867 868 869 870 871 872 873 874
  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;
875
  
876 877
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
878
  
879 880 881 882 883 884 885
  if (calendar->month == 0)
    {
      calendar->month = 11;
      calendar->year--;
    } 
  else 
    calendar->month--;
886
  
887
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
888
  
889 890
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
891
  
Manish Singh's avatar
Manish Singh committed
892 893 894 895 896 897
  g_signal_emit (calendar,
		 gtk_calendar_signals[PREV_MONTH_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
898
  
899 900 901 902 903 904 905 906 907
  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];
908
      gtk_calendar_select_day (calendar, calendar->selected_day);
909
    }
910 911

  gtk_widget_queue_draw (GTK_WIDGET (calendar));
912 913 914 915 916 917 918 919
  gtk_calendar_thaw (calendar);
}


static void
gtk_calendar_set_month_next (GtkCalendar *calendar)
{
  gint month_len;
920
  
921
  g_return_if_fail (GTK_IS_WIDGET (calendar));
922
  
923 924
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
925 926
  
  
927 928 929 930 931 932 933
  if (calendar->month == 11)
    {
      calendar->month = 0;
      calendar->year++;
    } 
  else 
    calendar->month++;
934
  
935 936
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
937 938 939 940 941 942
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_MONTH_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
943
  
944
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
945
  
946 947 948 949 950 951 952
  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);
953 954

  gtk_widget_queue_draw (GTK_WIDGET (calendar));
955 956 957 958 959 960 961
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_prev (GtkCalendar *calendar)
{
  gint month_len;
962
  
963
  g_return_if_fail (GTK_IS_WIDGET (calendar));
964
  
965 966 967
  calendar->year--;
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
968 969 970 971 972 973
  g_signal_emit (calendar,
		 gtk_calendar_signals[PREV_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
974
  
975
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
976
  
977 978 979 980 981 982 983
  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);
984
  
985
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
986 987 988 989 990 991 992 993
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_next (GtkCalendar *calendar)
{
  gint month_len;
  GtkWidget *widget;
994
  
995
  g_return_if_fail (GTK_IS_WIDGET (calendar));
996
  
997
  widget = GTK_WIDGET (calendar);
998
  
999
  gtk_calendar_freeze (calendar);
1000
  
1001 1002
  calendar->year++;
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
1003 1004 1005 1006 1007 1008
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
1009
  
1010
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
1011
  
1012 1013 1014 1015 1016 1017 1018
  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);
1019
  
1020
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
1021 1022 1023 1024
  gtk_calendar_thaw (calendar);
}

static void
1025
gtk_calendar_main_button (GtkWidget	 *widget,
1026 1027 1028
			  GdkEventButton *event)
{
  GtkCalendar *calendar;
1029
  GtkCalendarPrivateData *private_data;
1030 1031 1032
  gint x, y;
  gint row, col;
  gint day_month;
1033
  gint day;
1034
  
1035
  calendar = GTK_CALENDAR (widget);