gtkcalendar.c 82.8 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"
Manish Singh's avatar
Manish Singh committed
43
#include "gtkmarshalers.h"
Matthias Clasen's avatar
Matthias Clasen committed
44
#include "gtkintl.h"
45
46
47
48
49
50
51
#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.  */

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

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

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

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

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,
75
				N_int year2, N_int mm2, N_int dd2);
76
77
78
79
80
static N_int   weeks_in_year(N_int year);

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

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

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

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

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

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


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

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

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

166
/*** END OF lib_date routines ********************************************/
167

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

177
#define DAY_XSEP		 0 /* not really good for small calendar */
178
179
180
#define DAY_YSEP		 0 /* not really good for small calendar */

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

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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
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_WEEK_START_MONDAY,
  PROP_LAST
};

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

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

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

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

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

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

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

276
277
static void gtk_calendar_class_init	(GtkCalendarClass *class);
static void gtk_calendar_init		(GtkCalendar *calendar);
278
static void gtk_calendar_finalize	(GObject *calendar);
Matthias Clasen's avatar
Matthias Clasen committed
279
280
281
282
283
284
285
286
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);
287
288
289
290
291
292
293
294
295
296
297
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);
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);
static void gtk_calendar_state_changed	(GtkWidget *widget,
308
					 GtkStateType previous_state);
309
static void gtk_calendar_style_set	(GtkWidget *widget,
310
					 GtkStyle  *previous_style);
311
312
313
314
315
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);

316
317
static void gtk_calendar_select_and_focus_day (GtkCalendar *calendar,
					       guint        day);
318
319
320
321
322
323
324
325
326
327
328
329
330

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);
331
332
333
334

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

Manish Singh's avatar
Manish Singh committed
335
GType
336
gtk_calendar_get_type (void)
337
{
Manish Singh's avatar
Manish Singh committed
338
  static GType calendar_type = 0;
339
  
340
341
  if (!calendar_type)
    {
342
      static const GTypeInfo calendar_info =
343
344
      {
	sizeof (GtkCalendarClass),
Manish Singh's avatar
Manish Singh committed
345
346
	NULL,		/* base_init */
	NULL,		/* base_finalize */
347
	(GClassInitFunc) gtk_calendar_class_init,
Manish Singh's avatar
Manish Singh committed
348
349
	NULL,		/* class_finalize */
	NULL,		/* class_data */
350
	sizeof (GtkCalendar),
Manish Singh's avatar
Manish Singh committed
351
	0,		/* n_preallocs */
352
	(GInstanceInitFunc) gtk_calendar_init,
353
      };
354

Manish Singh's avatar
Manish Singh committed
355
356
      calendar_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkCalendar",
					      &calendar_info, 0);
357
    }
358
  
359
360
361
362
363
364
  return calendar_type;
}

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
365
  GObjectClass   *gobject_class;
366
  GtkWidgetClass *widget_class;
367
368

  gobject_class = (GObjectClass*)  class;
369
  widget_class = (GtkWidgetClass*) class;
370
  
Manish Singh's avatar
Manish Singh committed
371
  parent_class = g_type_class_peek_parent (class);
372
  
Matthias Clasen's avatar
Matthias Clasen committed
373
374
  gobject_class->set_property = gtk_calendar_set_property;
  gobject_class->get_property = gtk_calendar_get_property;
375
  gobject_class->finalize = gtk_calendar_finalize;
376

377
378
379
380
381
382
383
384
385
386
387
388
  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;
  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;
  widget_class->style_set = gtk_calendar_style_set;
  widget_class->state_changed = gtk_calendar_state_changed;
389
  
390
391
392
393
394
395
396
397
  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
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
  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));
  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));
  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));
  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));
  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));
  g_object_class_install_property (gobject_class,
                                   PROP_WEEK_START_MONDAY,
                                   g_param_spec_boolean ("week_start_monday",
							 _("Week Start Monday"),
							 _("If TRUE, Monday is displayed as the first day of the week"),
							 FALSE,
							 G_PARAM_READWRITE));


456
  gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
457
458
459
460
461
462
463
    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);
464
  gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
465
466
467
468
469
470
471
    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);
472
  gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
473
474
475
476
477
478
479
    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);
480
  gtk_calendar_signals[PREV_MONTH_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
481
482
483
484
485
486
487
    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);
488
  gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
489
490
491
492
493
494
495
    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);
496
  gtk_calendar_signals[PREV_YEAR_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
497
498
499
500
501
502
503
    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);
504
  gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
Manish Singh's avatar
Manish Singh committed
505
506
507
508
509
510
511
    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);
512
513
514
515
516
517
518
519
520
521
522
}

static void
gtk_calendar_init (GtkCalendar *calendar)
{
  time_t secs;
  struct tm *tm;
  gint i;
  char buffer[255];
  time_t tmp_time;
  GtkWidget *widget;
523
  GtkCalendarPrivateData *private_data;
524
  
525
526
  widget = GTK_WIDGET (calendar);
  GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
527
  
Manish Singh's avatar
Manish Singh committed
528
  calendar->private_data = g_malloc (sizeof (GtkCalendarPrivateData));
529
530
  private_data = GTK_CALENDAR_PRIVATE_DATA (calendar);

531
532
533
534
535
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
	tmp_time= (i+3)*86400;
	strftime ( buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
536
	default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
537
      }
538
  
539
540
541
542
543
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
	tmp_time=i*2764800;
	strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
544
	default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL);
545
      }
546
  
547
548
549
550
551
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
  calendar->month = tm->tm_mon;
  calendar->year  = 1900 + tm->tm_year;
552
  
553
554
  for (i=0;i<31;i++)
    calendar->marked_date[i] = FALSE;
555
  calendar->num_marked_dates = 0;
556
  calendar->selected_day = tm->tm_mday;
557
  
558
559
  calendar->display_flags = ( GTK_CALENDAR_SHOW_HEADING | 
			      GTK_CALENDAR_SHOW_DAY_NAMES );
560
  
561
562
  calendar->highlight_row = -1;
  calendar->highlight_col = -1;
563
  
564
565
566
567
  calendar->focus_row = -1;
  calendar->focus_col = -1;
  calendar->xor_gc = NULL;

568
569
570
571
572
573
574
575
576
577
578
  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;
579
580

  private_data->freeze_count = 0;
581
  
582
583
584
585
  private_data->dirty_header = 0;
  private_data->dirty_day_names = 0;
  private_data->dirty_week = 0;
  private_data->dirty_main = 0;
586
587
588
}

GtkWidget*
589
gtk_calendar_new (void)
590
{
Manish Singh's avatar
Manish Singh committed
591
  return g_object_new (GTK_TYPE_CALENDAR, NULL);
592
593
594
595
596
}

/* column_from_x: returns the column 0-6 that the
 * x pixel of the xwindow is in */
static gint
597
598
column_from_x (GtkCalendar *calendar,
	       gint	    event_x)
599
600
601
{
  gint c, column;
  gint x_left, x_right;
602
  
603
  column = -1;
604
  
605
606
607
  for (c = 0; c < 7; c++)
    {
      x_left = left_x_for_column (calendar, c);
608
      x_right = x_left + GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
609
      
Owen Taylor's avatar
Owen Taylor committed
610
      if (event_x >= x_left && event_x < x_right)
611
612
613
614
615
	{
	  column = c;
	  break;
	}
    }
616
  
617
618
619
620
621
622
  return column;
}

static gint
row_height (GtkCalendar *calendar)
{
623
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h - CALENDAR_MARGIN
624
625
626
627
628
629
630
631
	  - ((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
632
633
row_from_y (GtkCalendar *calendar,
	    gint	 event_y)
634
635
636
637
{
  gint r, row;
  gint height;
  gint y_top, y_bottom;
638
  
639
640
  height = row_height (calendar);
  row = -1;
641
  
642
643
644
645
  for (r = 0; r < 6; r++)
    {
      y_top = top_y_for_row (calendar, r);
      y_bottom = y_top + height;
646
      
Owen Taylor's avatar
Owen Taylor committed
647
      if (event_y >= y_top && event_y < y_bottom)
648
649
650
651
652
	{
	  row = r;
	  break;
	}
    }
653
  
654
655
656
657
658
659
  return row;
}

/* left_x_for_column: returns the x coordinate
 * for the left of the column */
static gint
660
661
left_x_for_column (GtkCalendar *calendar,
		   gint		column)
662
663
664
{
  gint width;
  gint x_left;
665
  
666
667
668
  if (gtk_widget_get_direction (GTK_WIDGET (calendar)) == GTK_TEXT_DIR_RTL)
    column = 6 - column;

669
  width = GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
670
  if (calendar->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
671
    x_left = CALENDAR_XSEP + (width + DAY_XSEP) * column;
672
673
  else
    x_left = CALENDAR_MARGIN + (width + DAY_XSEP) * column;
674
  
675
676
677
678
679
680
  return x_left;
}

/* top_y_for_row: returns the y coordinate
 * for the top of the row */
static gint
681
682
top_y_for_row (GtkCalendar *calendar,
	       gint	    row)
683
{
684
  
685
686
687
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h 
	  - (CALENDAR_MARGIN + (6 - row)
	     * row_height (calendar)));
688
689
690
691
692
}

/* 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
693
694
695
696
697
draw_arrow_right (GdkWindow *window,
		  GdkGC	    *gc,
		  gint	     x,
		  gint	     y,
		  gint	     size)
698
699
{
  gint i;
700
  
701
702
703
704
705
706
707
708
709
710
711
712
713
  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
714
715
716
717
718
draw_arrow_left (GdkWindow *window,
		 GdkGC	   *gc,
		 gint	    x,
		 gint	    y,
		 gint	    size)
719
720
{
  gint i;
721
  
722
723
724
725
726
727
728
729
730
731
732
733
734
735
  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;
736
  
737
738
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
739
  
740
741
742
743
744
745
746
  if (calendar->month == 0)
    {
      calendar->month = 11;
      calendar->year--;
    } 
  else 
    calendar->month--;
747
  
748
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
749
  
750
751
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
752
  
Manish Singh's avatar
Manish Singh committed
753
754
755
756
757
758
  g_signal_emit (calendar,
		 gtk_calendar_signals[PREV_MONTH_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
759
  
760
761
762
763
764
765
766
767
768
  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];
769
      gtk_calendar_select_day (calendar, calendar->selected_day);
770
    }
771
772

  gtk_widget_queue_draw (GTK_WIDGET (calendar));
773
774
775
776
777
778
779
780
  gtk_calendar_thaw (calendar);
}


static void
gtk_calendar_set_month_next (GtkCalendar *calendar)
{
  gint month_len;
781
  
782
  g_return_if_fail (GTK_IS_WIDGET (calendar));
783
  
784
785
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
786
787
  
  
788
789
790
791
792
793
794
  if (calendar->month == 11)
    {
      calendar->month = 0;
      calendar->year++;
    } 
  else 
    calendar->month++;
795
  
796
797
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
798
799
800
801
802
803
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_MONTH_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
804
  
805
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
806
  
807
808
809
810
811
812
813
  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);
814
815

  gtk_widget_queue_draw (GTK_WIDGET (calendar));
816
817
818
819
820
821
822
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_prev (GtkCalendar *calendar)
{
  gint month_len;
823
  
824
  g_return_if_fail (GTK_IS_WIDGET (calendar));
825
  
826
827
828
  calendar->year--;
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
829
830
831
832
833
834
  g_signal_emit (calendar,
		 gtk_calendar_signals[PREV_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
835
  
836
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
837
  
838
839
840
841
842
843
844
  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);
845
  
846
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
847
848
849
850
851
852
853
854
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_next (GtkCalendar *calendar)
{
  gint month_len;
  GtkWidget *widget;
855
  
856
  g_return_if_fail (GTK_IS_WIDGET (calendar));
857
  
858
  widget = GTK_WIDGET (calendar);
859
  
860
  gtk_calendar_freeze (calendar);
861
  
862
863
  calendar->year++;
  gtk_calendar_compute_days (calendar);
Manish Singh's avatar
Manish Singh committed
864
865
866
867
868
869
  g_signal_emit (calendar,
		 gtk_calendar_signals[NEXT_YEAR_SIGNAL],
		 0);
  g_signal_emit (calendar,
		 gtk_calendar_signals[MONTH_CHANGED_SIGNAL],
		 0);
870
  
871
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
872
  
873
874
875
876
877
878
879
  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);
880
  
881
  gtk_widget_queue_draw (GTK_WIDGET (calendar));
882
883
884
885
  gtk_calendar_thaw (calendar);
}

static void
886
gtk_calendar_main_button (GtkWidget	 *widget,
887
888
889
890
891
892
			  GdkEventButton *event)
{
  GtkCalendar *calendar;
  gint x, y;
  gint row, col;
  gint day_month;
893
  gint day;
894
  
895
  calendar = GTK_CALENDAR (widget);
896
  
897
898
  x = (gint) (event->x);
  y = (gint) (event->y);
899
  
900
901
  row = row_from_y (calendar, y);
  col = column_from_x (calendar, x);
Owen Taylor's avatar
Owen Taylor committed
902
903
904
905

  /* If row or column isn't found, just return. */
  if (row == -1 || col == -1)
    return;
906
  
907
  day_month = calendar->day_month[row][col];
908
909

  if (event->type == GDK_BUTTON_PRESS)
910
    {
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
      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
927
928
929
	g_signal_emit (calendar,
		       gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL],
		       0);
930
931
932
933
934
935
936
    }
}

static void
gtk_calendar_realize_arrows (GtkWidget *widget)
{
  GtkCalendar *calendar;
937
  GtkCalendarPrivateData *private_data;
938
939
940
  GdkWindowAttr attributes;
  gint attributes_mask;
  gint i;
941
  
942
  g_return_if_fail (GTK_IS_CALENDAR (widget));
943
  
944
  calendar = GTK_CALENDAR (widget);
945
946
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

947
948
949
950
951
952
953
954
955
956
957
958
959
  /* Arrow windows ------------------------------------- */
  if (! (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
      && (calendar->display_flags & GTK_CALENDAR_SHOW_HEADING))
    {
      attributes.wclass = GDK_INPUT_OUTPUT;
      attributes.window_type = GDK_WINDOW_CHILD;
      attributes.visual = gtk_widget_get_visual (widget);
      attributes.colormap = gtk_widget_get_colormap (widget);
      attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK
			       | GDK_BUTTON_PRESS_MASK	| GDK_BUTTON_RELEASE_MASK
			       | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
      attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
      attributes.y = 3;
960
961
      attributes.width = private_data->arrow_width;
      attributes.height = private_data->header_h - 7;
962
963
964
965
966
967
968
969
      for (i = 0; i < 4; i++)
	{
	  switch (i)
	    {
	    case ARROW_MONTH_LEFT:
	      attributes.x = 3;
	      break;
	    case ARROW_MONTH_RIGHT:
970
971
	      attributes.x = (private_data->arrow_width 
			      + private_data->max_month_width);
972
973
	      break;
	    case ARROW_YEAR_LEFT:
974
	      attributes.x = (widget->allocation.width - 2 * widget->style->xthickness
975
976
			      - (3 + 2*private_data->arrow_width 
				 + private_data->max_year_width));
977
978
	      break;
	    case ARROW_YEAR_RIGHT:
979
	      attributes.x = (widget->allocation.width - 2 * widget->style->xthickness 
980
			      - 3 - private_data->arrow_width);
981
982
	      break;
	    }
983
984
985
986
987
	  private_data->arrow_win[i] = gdk_window_new (private_data->header_win,
						       &attributes, 
						       attributes_mask);
	  private_data->arrow_state[i] = GTK_STATE_NORMAL;
	  gdk_window_set_background (private_data->arrow_win[i],
988
				     HEADER_BG_COLOR (GTK_WIDGET (calendar)));
989
990
	  gdk_window_show (private_data->arrow_win[i]);
	  gdk_window_set_user_data (private_data->arrow_win[i], widget);
991
992
993
	}
    }
  else
994
995
996
997
    {
      for (i = 0; i < 4; i++)
	private_data->arrow_win[i] = NULL;
    }
998
999
1000
1001
1002
1003
}

static void
gtk_calendar_realize_header (GtkWidget *widget)
{
  GtkCalendar *calendar;
1004
  GtkCalendarPrivateData *private_data;
1005
1006
  GdkWindowAttr attributes;
  gint attributes_mask;
1007
  
1008
  g_return_if_fail (GTK_IS_CALENDAR (widget));
1009
  
1010
  calendar = GTK_CALENDAR (widget);
1011
1012
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

1013
1014
1015
1016
1017
1018
1019
1020
1021
  /* Header window ------------------------------------- */
  if (calendar->display_flags & GTK_CALENDAR_SHOW_HEADING)
    {
      attributes.wclass = GDK_INPUT_OUTPUT;
      attributes.window_type = GDK_WINDOW_CHILD;
      attributes.visual = gtk_widget_get_visual (widget);
      attributes.colormap = gtk_widget_get_colormap (widget);
      attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
      attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1022
1023
      attributes.x = widget->style->xthickness;
      attributes.y = widget->style->ythickness;
1024
1025
      attributes.width = widget->allocation.width - 2 * attributes.x;
      attributes.height = private_data->header_h - 2 * attributes.y;
1026
      private_data->header_win = gdk_window_new (widget->window,
1027
					     &attributes, attributes_mask);
1028
      
1029
      gdk_window_set_background (private_data->header_win,
1030
				 HEADER_BG_COLOR (GTK_WIDGET (calendar)));
1031
1032
      gdk_window_show (private_data->header_win);
      gdk_window_set_user_data (private_data->header_win, widget);
1033
      
1034
1035
    }
  else
1036
1037
1038
    {
      private_data->header_win = NULL;
    }
1039
1040
1041
1042
1043
1044
1045
  gtk_calendar_realize_arrows (widget);
}

static void
gtk_calendar_realize_day_names (GtkWidget *widget)
{
  GtkCalendar *calendar;
1046
  GtkCalendarPrivateData *private_data;
1047
1048
  GdkWindowAttr attributes;
  gint attributes_mask;
1049
  
1050
  g_return_if_fail (GTK_IS_CALENDAR (widget));
1051
  
1052
  calendar = GTK_CALENDAR (widget);
1053
1054
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

1055
  /* Day names	window --------------------------------- */
1056
1057
1058
1059
1060
1061
1062
1063
  if ( calendar->display_flags & GTK_CALENDAR_SHOW_DAY_NAMES)
    {
      attributes.wclass = GDK_INPUT_OUTPUT;
      attributes.window_type = GDK_WINDOW_CHILD;
      attributes.visual = gtk_widget_get_visual (widget);
      attributes.colormap = gtk_widget_get_colormap (widget);
      attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
      attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1064
1065
      attributes.x = (widget->style->xthickness + INNER_BORDER);
      attributes.y = private_data->header_h + (widget->style->ythickness 
1066
1067
					   + INNER_BORDER);
      attributes.width = (widget->allocation.width 
1068
			  - (widget->style->xthickness + INNER_BORDER) 
1069
1070
1071
1072
1073
1074
1075
1076
1077
			  * 2);
      attributes.height = private_data->day_name_h;
      private_data->day_name_win = gdk_window_new (widget->window,
						   &attributes, 
						   attributes_mask);
      gdk_window_set_background (private_data->day_name_win, 
				 BACKGROUND_COLOR ( GTK_WIDGET ( calendar)));
      gdk_window_show (private_data->day_name_win);
      gdk_window_set_user_data (private_data->day_name_win, widget);
1078
1079
    }
  else
1080
1081
1082
    {
      private_data->day_name_win = NULL;
    }
1083
1084
1085
1086
1087
1088
}

static void
gtk_calendar_realize_week_numbers (GtkWidget *widget)
{
  GtkCalendar *calendar;
1089
  GtkCalendarPrivateData *private_data;
1090
1091
  GdkWindowAttr attributes;
  gint attributes_mask;
1092
  
1093
  g_return_if_fail (GTK_IS_CALENDAR (widget));
1094
  
1095
  calendar = GTK_CALENDAR (widget);
1096
1097
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

1098
1099
1100
1101
1102
1103
1104
1105
  /* Week number window -------------------------------- */
  if (calendar->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
    {
      attributes.wclass = GDK_INPUT_OUTPUT;
      attributes.window_type = GDK_WINDOW_CHILD;
      attributes.visual = gtk_widget_get_visual (widget);
      attributes.colormap = gtk_widget_get_colormap (widget);
      attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
1106
      
1107
      attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1108
      attributes.x = widget->style->xthickness + INNER_BORDER;
1109
      attributes.y = (private_data->header_h + private_data->day_name_h 
1110
		      + (widget->style->ythickness + INNER_BORDER));
1111
1112
1113
1114
1115
1116
1117
1118
      attributes.width = private_data->week_width;
      attributes.height = private_data->main_h;
      private_data->week_win = gdk_window_new (widget->window,
					       &attributes, attributes_mask);
      gdk_window_set_background (private_data->week_win,  
				 BACKGROUND_COLOR (GTK_WIDGET (calendar)));
      gdk_window_show (private_data->week_win);
      gdk_window_set_user_data (private_data->week_win, widget);
1119
1120
    } 
  else
1121
1122
1123
    {
      private_data->week_win = NULL;
    }
1124
1125
1126
1127
1128
1129
}

static void
gtk_calendar_realize (GtkWidget *widget)
{
  GtkCalendar *calendar;
1130
  GtkCalendarPrivateData *private_data;
1131
1132
  GdkWindowAttr attributes;
  gint attributes_mask;
1133
1134
  GdkGCValues values;

1135
  calendar = GTK_CALENDAR (widget);
1136
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);
1137
  
1138
1139
  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
  gtk_calendar_compute_days (calendar);
1140
  
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.event_mask =  (gtk_widget_get_events (widget) 
			    | GDK_EXPOSURE_MASK |GDK_KEY_PRESS_MASK);
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (widget);
1151
  
1152
1153
1154
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
  widget->window = gdk_window_new (widget->parent->window,
				   &attributes, attributes_mask);
1155
  
1156
  widget->style = gtk_style_attach (widget->style, widget->window);
1157
  
1158
1159
  /* Header window ------------------------------------- */
  gtk_calendar_realize_header (widget);
1160
  /* Day names	window --------------------------------- */
1161
1162
1163
  gtk_calendar_realize_day_names (widget);
  /* Week number window -------------------------------- */
  gtk_calendar_realize_week_numbers (widget);
1164
  /* Main Window --------------------------------------	 */
1165
1166
1167
  attributes.event_mask =  (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK
			    | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
			    | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
1168
  
1169
  attributes.x = private_data->week_width + (widget->style->ythickness + INNER_BORDER);
1170
  attributes.y = (private_data->header_h + private_data->day_name_h 
1171
		  + (widget->style->ythickness + INNER_BORDER));
1172
  attributes.width = (widget->allocation.width - attributes.x 
1173
		      - (widget->style->xthickness + INNER_BORDER));
1174
1175