gtkcalendar.c 78.1 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
40
41
42
43
44
45
46
47
48
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include "gtkcalendar.h"
#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.  */

49
50
51
typedef	 unsigned   int	    N_int;
typedef	 unsigned   long    N_long;
typedef	 signed	    long    Z_long;
52
53
typedef enum { false = FALSE , true = TRUE } boolean;

54
55
#define and	    &&	    /* logical (boolean) operators: lower case */
#define or	    ||
56

57
static const N_int month_length[2][13] =
58
{
59
60
  { 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 }
61
62
};

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

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,
72
				N_int year2, N_int mm2, N_int dd2);
73
74
75
76
77
static N_int   weeks_in_year(N_int year);

static boolean 
leap(N_int year)
{
78
  return((((year % 4) == 0) and ((year % 100) != 0)) or ((year % 400) == 0));
79
80
81
82
83
}

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

static N_int weeks_in_year(N_int year)
{
98
  return(52 + ((day_of_week(year,1,1)==4) or (day_of_week(year,12,31)==4)));
99
100
101
102
103
}

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

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

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


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

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

static Z_long 
dates_difference(N_int year1, N_int mm1, N_int dd1,
158
		 N_int year2, N_int mm2, N_int dd2)
159
{
160
  return( calc_days(year2, mm2, dd2) - calc_days(year1, mm1, dd1) );
161
162
}

163
/*** END OF lib_date routines ********************************************/
164

165
166
167
168
#define CALENDAR_MARGIN		 0
#define CALENDAR_YSEP		 4
#define CALENDAR_XSEP		 4
#define INNER_BORDER		 4
169
170
171

#define DAY_XPAD		 2
#define DAY_YPAD		 2
172
#define DAY_XSEP		 0 /* not really good for small calendar */
173
174
175
#define DAY_YSEP		 0 /* not really good for small calendar */

/* Color usage */
176
177
#define HEADER_FG_COLOR(widget)		 (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
#define HEADER_BG_COLOR(widget)		 (& (widget)->style->bg[GTK_WIDGET_STATE (widget)])
178
#define DAY_NAME_COLOR(widget)		 (& (widget)->style->bg[GTK_STATE_SELECTED])
179
180
#define NORMAL_DAY_COLOR(widget)	 (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
#define SELECTION_FOCUS_COLOR(widget)	 (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
181
#define SELECTION_NO_FOCUS_COLOR(widget) (& (widget)->style->mid[GTK_WIDGET_STATE (widget)])
182
183
184
185
186
187
#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 FOREGROUND_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)])
188
189
190
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
216
217
218

#define HEADER_FONT(widget) ((widget)->style->font)
#define LABEL_FONT(widget)   ((widget)->style->font)
#define DAY_FONT(widget)     ((widget)->style->font)

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

static gint gtk_calendar_signals[LAST_SIGNAL] = { 0 };

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

230
231
232
  guint header_h;
  guint day_name_h;
  guint main_h;
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

  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;
  
250
251
  guint freeze_count;

252
  /* flags */
253
254
255
256
  guint dirty_header : 1;
  guint dirty_day_names : 1;
  guint dirty_main : 1;
  guint dirty_week : 1;
257
258
259
260
};

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

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

263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
static void gtk_calendar_class_init	(GtkCalendarClass *class);
static void gtk_calendar_init		(GtkCalendar *calendar);
static void gtk_calendar_realize	(GtkWidget *widget);
static void gtk_calendar_unrealize	(GtkWidget *widget);
static void gtk_calendar_draw_focus	(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 gint gtk_calendar_focus_in	(GtkWidget *widget,
					 GdkEventFocus *event);
static gint gtk_calendar_focus_out	(GtkWidget *widget,
					 GdkEventFocus *event);
static void gtk_calendar_state_changed	(GtkWidget *widget,
291
					 GtkStateType previous_state);
292
static void gtk_calendar_style_set	(GtkWidget *widget,
293
					 GtkStyle  *previous_style);
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
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);


static void gtk_calendar_paint		(GtkWidget    *widget,
					 GdkRectangle *area);
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_draw		(GtkWidget    *widget,
					 GdkRectangle *area);
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);
316
317
318
319

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

320
321
GtkType
gtk_calendar_get_type (void)
322
{
323
324
  static GtkType calendar_type = 0;
  
325
326
  if (!calendar_type)
    {
327
      static const GTypeInfo calendar_info =
328
329
      {
	sizeof (GtkCalendarClass),
330
331
332
333
334
335
336
337
	NULL,           /* base_init */
	NULL,           /* base_finalize */
	(GClassInitFunc) gtk_calendar_class_init,
	NULL,           /* class_finalize */
	NULL,           /* class_data */
	sizeof (GtkCalendar),
	16,             /* n_preallocs */
	(GInstanceInitFunc) gtk_calendar_init,
338
      };
339
340

      calendar_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkCalendar", &calendar_info);
341
    }
342
  
343
344
345
346
347
348
349
350
  return calendar_type;
}

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
351
  
352
353
  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;
354
355
356
  
  parent_class = gtk_type_class (GTK_TYPE_WIDGET);
  
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
  widget_class->realize = gtk_calendar_realize;
  widget_class->unrealize = gtk_calendar_unrealize;
  widget_class->expose_event = gtk_calendar_expose;
  widget_class->draw = gtk_calendar_draw;
  widget_class->draw_focus = gtk_calendar_draw_focus;
  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->focus_in_event = gtk_calendar_focus_in;
  widget_class->focus_out_event = gtk_calendar_focus_out;
  widget_class->style_set = gtk_calendar_style_set;
  widget_class->state_changed = gtk_calendar_state_changed;
373
  
374
375
  gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
    gtk_signal_new ("month_changed",
376
		    GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class),
377
		    GTK_SIGNAL_OFFSET (GtkCalendarClass, month_changed),
378
379
380
		    gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
  gtk_calendar_signals[DAY_SELECTED_SIGNAL] =
    gtk_signal_new ("day_selected",
381
		    GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class),
382
		    GTK_SIGNAL_OFFSET (GtkCalendarClass, day_selected),
383
384
385
		    gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
  gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL] =
    gtk_signal_new ("day_selected_double_click",
386
		    GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class),
387
		    GTK_SIGNAL_OFFSET (GtkCalendarClass, day_selected_double_click),
388
389
390
		    gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
  gtk_calendar_signals[PREV_MONTH_SIGNAL] =
    gtk_signal_new ("prev_month",
391
		    GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class),
392
		    GTK_SIGNAL_OFFSET (GtkCalendarClass, prev_month),
393
394
395
		    gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
  gtk_calendar_signals[NEXT_MONTH_SIGNAL] =
    gtk_signal_new ("next_month",
396
		    GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class),
397
		    GTK_SIGNAL_OFFSET (GtkCalendarClass, next_month),
398
399
400
		    gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
  gtk_calendar_signals[PREV_YEAR_SIGNAL] =
    gtk_signal_new ("prev_year",
401
		    GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class),
402
		    GTK_SIGNAL_OFFSET (GtkCalendarClass, prev_year),
403
404
405
		    gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
  gtk_calendar_signals[NEXT_YEAR_SIGNAL] =
    gtk_signal_new ("next_year",
406
		    GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class),
407
		    GTK_SIGNAL_OFFSET (GtkCalendarClass, next_year),
408
		    gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
409
  
410
  gtk_object_class_add_signals (object_class, gtk_calendar_signals, LAST_SIGNAL);
411
412
413
414
415
416
417
418
  
  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;
419
420
421
422
423
424
425
426
427
428
429
}

static void
gtk_calendar_init (GtkCalendar *calendar)
{
  time_t secs;
  struct tm *tm;
  gint i;
  char buffer[255];
  time_t tmp_time;
  GtkWidget *widget;
430
  GtkCalendarPrivateData *private_data;
431
  
432
433
  widget = GTK_WIDGET (calendar);
  GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
434
  
435
436
437
  calendar->private_data = (gpointer) malloc (sizeof (GtkCalendarPrivateData));
  private_data = GTK_CALENDAR_PRIVATE_DATA (calendar);

438
439
440
441
442
443
444
  if (!default_abbreviated_dayname[0])
    for (i=0; i<7; i++)
      {
	tmp_time= (i+3)*86400;
	strftime ( buffer, sizeof (buffer), "%a", gmtime (&tmp_time));
	default_abbreviated_dayname[i] = g_strdup (buffer);
      }
445
  
446
447
448
449
450
451
452
  if (!default_monthname[0])
    for (i=0; i<12; i++)
      {
	tmp_time=i*2764800;
	strftime ( buffer, sizeof (buffer), "%B", gmtime (&tmp_time));
	default_monthname[i] = g_strdup (buffer);
      }
453
  
454
455
456
457
458
  /* Set defaults */
  secs = time (NULL);
  tm = localtime (&secs);
  calendar->month = tm->tm_mon;
  calendar->year  = 1900 + tm->tm_year;
459
  
460
461
  for (i=0;i<31;i++)
    calendar->marked_date[i] = FALSE;
462
  calendar->num_marked_dates = 0;
463
  calendar->selected_day = 1;
464
  
465
466
  calendar->display_flags = ( GTK_CALENDAR_SHOW_HEADING | 
			      GTK_CALENDAR_SHOW_DAY_NAMES );
467
  
468
469
  calendar->highlight_row = -1;
  calendar->highlight_col = -1;
470
  
471
472
473
474
  calendar->focus_row = -1;
  calendar->focus_col = -1;
  calendar->xor_gc = NULL;

475
476
477
478
479
480
481
482
483
484
485
  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;
486
487

  private_data->freeze_count = 0;
488
  
489
490
491
492
  private_data->dirty_header = 0;
  private_data->dirty_day_names = 0;
  private_data->dirty_week = 0;
  private_data->dirty_main = 0;
493
494
495
}

GtkWidget*
496
gtk_calendar_new (void)
497
{
498
  return GTK_WIDGET (gtk_type_new (GTK_TYPE_CALENDAR));
499
500
501
502
503
}

/* column_from_x: returns the column 0-6 that the
 * x pixel of the xwindow is in */
static gint
504
505
column_from_x (GtkCalendar *calendar,
	       gint	    event_x)
506
507
508
{
  gint c, column;
  gint x_left, x_right;
509
  
510
  column = -1;
511
  
512
513
514
  for (c = 0; c < 7; c++)
    {
      x_left = left_x_for_column (calendar, c);
515
      x_right = x_left + GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
516
      
Owen Taylor's avatar
Owen Taylor committed
517
      if (event_x >= x_left && event_x < x_right)
518
519
520
521
522
	{
	  column = c;
	  break;
	}
    }
523
  
524
525
526
527
528
529
  return column;
}

static gint
row_height (GtkCalendar *calendar)
{
530
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h - CALENDAR_MARGIN
531
532
533
534
535
536
537
538
	  - ((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
539
540
row_from_y (GtkCalendar *calendar,
	    gint	 event_y)
541
542
543
544
{
  gint r, row;
  gint height;
  gint y_top, y_bottom;
545
  
546
547
  height = row_height (calendar);
  row = -1;
548
  
549
550
551
552
  for (r = 0; r < 6; r++)
    {
      y_top = top_y_for_row (calendar, r);
      y_bottom = y_top + height;
553
      
Owen Taylor's avatar
Owen Taylor committed
554
      if (event_y >= y_top && event_y < y_bottom)
555
556
557
558
559
	{
	  row = r;
	  break;
	}
    }
560
  
561
562
563
564
565
566
  return row;
}

/* left_x_for_column: returns the x coordinate
 * for the left of the column */
static gint
567
568
left_x_for_column (GtkCalendar *calendar,
		   gint		column)
569
570
571
{
  gint width;
  gint x_left;
572
  
573
  width = GTK_CALENDAR_PRIVATE_DATA (calendar)->day_width;
574
575
576
577
  if (calendar->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
    x_left =  DAY_XSEP + (width + DAY_XSEP) * column;
  else
    x_left = CALENDAR_MARGIN + (width + DAY_XSEP) * column;
578
  
579
580
581
582
583
584
  return x_left;
}

/* top_y_for_row: returns the y coordinate
 * for the top of the row */
static gint
585
586
top_y_for_row (GtkCalendar *calendar,
	       gint	    row)
587
{
588
  
589
590
591
  return (GTK_CALENDAR_PRIVATE_DATA (calendar)->main_h 
	  - (CALENDAR_MARGIN + (6 - row)
	     * row_height (calendar)));
592
593
594
595
596
}

/* 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
597
598
599
600
601
draw_arrow_right (GdkWindow *window,
		  GdkGC	    *gc,
		  gint	     x,
		  gint	     y,
		  gint	     size)
602
603
{
  gint i;
604
  
605
606
607
608
609
610
611
612
613
614
615
616
617
  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
618
619
620
621
622
draw_arrow_left (GdkWindow *window,
		 GdkGC	   *gc,
		 gint	    x,
		 gint	    y,
		 gint	    size)
623
624
{
  gint i;
625
  
626
627
628
629
630
631
632
633
634
635
636
637
638
639
  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;
640
  
641
642
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
643
  
644
645
646
647
648
649
650
  if (calendar->month == 0)
    {
      calendar->month = 11;
      calendar->year--;
    } 
  else 
    calendar->month--;
651
  
652
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
653
  
654
655
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
656
  
657
658
659
660
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[PREV_MONTH_SIGNAL]);
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[MONTH_CHANGED_SIGNAL]);
661
  
662
663
664
665
666
667
668
669
670
  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];
671
      gtk_calendar_select_day (calendar, calendar->selected_day);
672
    }
673
  
674
675
676
677
678
679
680
681
682
  gtk_calendar_paint (GTK_WIDGET (calendar), NULL);
  gtk_calendar_thaw (calendar);
}


static void
gtk_calendar_set_month_next (GtkCalendar *calendar)
{
  gint month_len;
683
  
684
685
  g_return_if_fail (calendar != NULL);
  g_return_if_fail (GTK_IS_WIDGET (calendar));
686
  
687
688
  if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
    return;
689
690
  
  
691
692
693
694
695
696
697
  if (calendar->month == 11)
    {
      calendar->month = 0;
      calendar->year++;
    } 
  else 
    calendar->month++;
698
  
699
700
701
702
703
704
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[NEXT_MONTH_SIGNAL]);
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[MONTH_CHANGED_SIGNAL]);
705
  
706
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
707
  
708
709
710
711
712
713
714
  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);
715
  
716
717
718
719
720
721
722
723
  gtk_calendar_paint (GTK_WIDGET(calendar), NULL);
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_prev (GtkCalendar *calendar)
{
  gint month_len;
724
  
725
726
  g_return_if_fail (calendar != NULL);
  g_return_if_fail (GTK_IS_WIDGET (calendar));
727
  
728
729
730
731
732
733
734
  calendar->year--;
  gtk_calendar_freeze (calendar);
  gtk_calendar_compute_days (calendar);
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[PREV_YEAR_SIGNAL]);
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[MONTH_CHANGED_SIGNAL]);
735
  
736
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
737
  
738
739
740
741
742
743
744
  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);
745
  
746
747
748
749
750
751
752
753
754
  gtk_calendar_paint (GTK_WIDGET (calendar), NULL);
  gtk_calendar_thaw (calendar);
}

static void
gtk_calendar_set_year_next (GtkCalendar *calendar)
{
  gint month_len;
  GtkWidget *widget;
755
  
756
757
  g_return_if_fail (calendar != NULL);
  g_return_if_fail (GTK_IS_WIDGET (calendar));
758
  
759
  widget = GTK_WIDGET (calendar);
760
  
761
  gtk_calendar_freeze (calendar);
762
  
763
764
765
766
767
768
  calendar->year++;
  gtk_calendar_compute_days (calendar);
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[NEXT_YEAR_SIGNAL]);
  gtk_signal_emit (GTK_OBJECT (calendar),
		   gtk_calendar_signals[MONTH_CHANGED_SIGNAL]);
769
  
770
  month_len = month_length[leap (calendar->year)][calendar->month + 1];
771
  
772
773
774
775
776
777
778
  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);
779
  
780
781
782
783
784
  gtk_calendar_paint (GTK_WIDGET (calendar), NULL);
  gtk_calendar_thaw (calendar);
}

static void
785
gtk_calendar_main_button (GtkWidget	 *widget,
786
787
788
789
790
791
			  GdkEventButton *event)
{
  GtkCalendar *calendar;
  gint x, y;
  gint row, col;
  gint day_month;
792
  gint old_focus_row, old_focus_col;
793
  
794
  calendar = GTK_CALENDAR (widget);
795
  
796
797
  x = (gint) (event->x);
  y = (gint) (event->y);
798
  
799
800
  row = row_from_y (calendar, y);
  col = column_from_x (calendar, x);
Owen Taylor's avatar
Owen Taylor committed
801
802
803
804

  /* If row or column isn't found, just return. */
  if (row == -1 || col == -1)
    return;
805
  
806
  day_month = calendar->day_month[row][col];
807
  
808
809
810
811
812
813
814
815
816
  if (day_month == MONTH_CURRENT)
    {
      if (event->type == GDK_2BUTTON_PRESS)
	gtk_signal_emit (GTK_OBJECT (calendar),
			 gtk_calendar_signals[DAY_SELECTED_DOUBLE_CLICK_SIGNAL]);
      else
	{
	  if (!GTK_WIDGET_HAS_FOCUS (widget))
	    gtk_widget_grab_focus (widget);
817
818
819
820
821
	  old_focus_row = calendar->focus_row;
	  old_focus_col = calendar->focus_col;
	  calendar->focus_row = row;
	  calendar->focus_col = col;
	  gtk_calendar_paint_day (widget, old_focus_row, old_focus_col);
822
823
824
825
826
827
828
829
830
831
832
833
834
	  gtk_calendar_select_day (calendar, calendar->day[row][col]);
	}
    }
  else if (day_month == MONTH_PREV)
    gtk_calendar_set_month_prev (calendar);
  else if (day_month == MONTH_NEXT)
    gtk_calendar_set_month_next (calendar);
}

static void
gtk_calendar_realize_arrows (GtkWidget *widget)
{
  GtkCalendar *calendar;
835
  GtkCalendarPrivateData *private_data;
836
837
838
  GdkWindowAttr attributes;
  gint attributes_mask;
  gint i;
839
  
840
841
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CALENDAR (widget));
842
  
843
  calendar = GTK_CALENDAR (widget);
844
845
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

846
847
848
849
850
851
852
853
854
855
856
857
858
  /* 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;
859
860
      attributes.width = private_data->arrow_width;
      attributes.height = private_data->header_h - 7;
861
862
863
864
865
866
867
868
      for (i = 0; i < 4; i++)
	{
	  switch (i)
	    {
	    case ARROW_MONTH_LEFT:
	      attributes.x = 3;
	      break;
	    case ARROW_MONTH_RIGHT:
869
870
	      attributes.x = (private_data->arrow_width 
			      + private_data->max_month_width);
871
872
	      break;
	    case ARROW_YEAR_LEFT:
873
874
875
	      attributes.x = (widget->allocation.width - 4
			      - (3 + 2*private_data->arrow_width 
				 + private_data->max_year_width));
876
877
	      break;
	    case ARROW_YEAR_RIGHT:
878
879
	      attributes.x = (widget->allocation.width - 4 
			      - 3 - private_data->arrow_width);
880
881
	      break;
	    }
882
883
884
885
886
	  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],
887
				     HEADER_BG_COLOR (GTK_WIDGET (calendar)));
888
889
	  gdk_window_show (private_data->arrow_win[i]);
	  gdk_window_set_user_data (private_data->arrow_win[i], widget);
890
891
892
	}
    }
  else
893
894
895
896
    {
      for (i = 0; i < 4; i++)
	private_data->arrow_win[i] = NULL;
    }
897
898
899
900
901
902
}

static void
gtk_calendar_realize_header (GtkWidget *widget)
{
  GtkCalendar *calendar;
903
  GtkCalendarPrivateData *private_data;
904
905
  GdkWindowAttr attributes;
  gint attributes_mask;
906
  
907
908
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CALENDAR (widget));
909
  
910
  calendar = GTK_CALENDAR (widget);
911
912
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

913
914
915
916
917
918
919
920
921
  /* 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;
922
923
924
925
926
      attributes.x = 2;
      attributes.y = 2;
      attributes.width = widget->allocation.width - 4;
      attributes.height = private_data->header_h;
      private_data->header_win = gdk_window_new (widget->window,
927
					     &attributes, attributes_mask);
928
      
929
      gdk_window_set_background (private_data->header_win,
930
				 HEADER_BG_COLOR (GTK_WIDGET (calendar)));
931
932
      gdk_window_show (private_data->header_win);
      gdk_window_set_user_data (private_data->header_win, widget);
933
      
934
935
    }
  else
936
937
938
    {
      private_data->header_win = NULL;
    }
939
940
941
942
943
944
945
  gtk_calendar_realize_arrows (widget);
}

static void
gtk_calendar_realize_day_names (GtkWidget *widget)
{
  GtkCalendar *calendar;
946
  GtkCalendarPrivateData *private_data;
947
948
  GdkWindowAttr attributes;
  gint attributes_mask;
949
  
950
951
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CALENDAR (widget));
952
  
953
  calendar = GTK_CALENDAR (widget);
954
955
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

956
  /* Day names	window --------------------------------- */
957
958
959
960
961
962
963
964
  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;
965
966
      attributes.x = (widget->style->xthickness + INNER_BORDER);
      attributes.y = private_data->header_h + (widget->style->ythickness 
967
968
					   + INNER_BORDER);
      attributes.width = (widget->allocation.width 
969
			  - (widget->style->xthickness + INNER_BORDER) 
970
971
972
973
974
975
976
977
978
			  * 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);
979
980
    }
  else
981
982
983
    {
      private_data->day_name_win = NULL;
    }
984
985
986
987
988
989
}

static void
gtk_calendar_realize_week_numbers (GtkWidget *widget)
{
  GtkCalendar *calendar;
990
  GtkCalendarPrivateData *private_data;
991
992
  GdkWindowAttr attributes;
  gint attributes_mask;
993
  
994
995
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CALENDAR (widget));
996
  
997
  calendar = GTK_CALENDAR (widget);
998
999
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);

1000
1001
1002
1003
1004
1005
1006
1007
  /* 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;
1008
      
1009
      attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1010
      attributes.x = + (widget->style->xthickness + INNER_BORDER);
1011
      attributes.y = (private_data->header_h + private_data->day_name_h 
1012
		      + (widget->style->ythickness + INNER_BORDER));
1013
1014
1015
1016
1017
1018
1019
1020
      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);
1021
1022
    } 
  else
1023
1024
1025
    {
      private_data->week_win = NULL;
    }
1026
1027
1028
1029
1030
1031
}

static void
gtk_calendar_realize (GtkWidget *widget)
{
  GtkCalendar *calendar;
1032
  GtkCalendarPrivateData *private_data;
1033
1034
  GdkWindowAttr attributes;
  gint attributes_mask;
1035
1036
  GdkGCValues values;

1037
1038
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CALENDAR (widget));
1039
  
1040
  calendar = GTK_CALENDAR (widget);
1041
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);
1042
  
1043
1044
  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
  gtk_calendar_compute_days (calendar);
1045
  
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
  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);
1056
  
1057
1058
1059
  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);
1060
  
1061
  widget->style = gtk_style_attach (widget->style, widget->window);
1062
  
1063
1064
  /* Header window ------------------------------------- */
  gtk_calendar_realize_header (widget);
1065
  /* Day names	window --------------------------------- */
1066
1067
1068
  gtk_calendar_realize_day_names (widget);
  /* Week number window -------------------------------- */
  gtk_calendar_realize_week_numbers (widget);
1069
  /* Main Window --------------------------------------	 */
1070
1071
1072
  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);
1073
  
1074
  if (calendar->display_flags & GTK_CALENDAR_SHOW_WEEK_NUMBERS)
1075
    attributes.x = private_data->week_width;
1076
1077
  else
    attributes.x = 0;
1078
  attributes.x += (widget->style->xthickness + INNER_BORDER);
1079
  attributes.y = (private_data->header_h + private_data->day_name_h 
1080
		  + (widget->style->ythickness + INNER_BORDER));
1081
  attributes.width = (widget->allocation.width - attributes.x 
1082
		      - (widget->style->xthickness + INNER_BORDER));
1083
1084
1085
1086
1087
1088
1089
  attributes.height = private_data->main_h;
  private_data->main_win = gdk_window_new (widget->window,
					   &attributes, attributes_mask);
  gdk_window_set_background (private_data->main_win, 
			     BACKGROUND_COLOR ( GTK_WIDGET ( calendar)));
  gdk_window_show (private_data->main_win);
  gdk_window_set_user_data (private_data->main_win, widget);
1090
1091
1092
  gdk_window_set_background (widget->window, BACKGROUND_COLOR (widget));
  gdk_window_show (widget->window);
  gdk_window_set_user_data (widget->window, widget);
1093
  
1094
1095
  /* Set widgets gc */
  calendar->gc = gdk_gc_new (widget->window);
1096
1097
1098
1099
1100
1101
1102

  values.foreground = widget->style->white;
  values.function = GDK_XOR;
  calendar->xor_gc = gdk_gc_new_with_values (widget->window,
					     &values,
					     GDK_GC_FOREGROUND |
					     GDK_GC_FUNCTION);
1103
1104
1105
1106
1107
1108
}

static void
gtk_calendar_unrealize (GtkWidget *widget)
{
  GtkCalendar *calendar;
1109
  GtkCalendarPrivateData *private_data;
1110
  gint i;
1111
  
1112
1113
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CALENDAR (widget));
1114
  
1115
  calendar = GTK_CALENDAR (widget);
1116
  private_data = GTK_CALENDAR_PRIVATE_DATA (widget);
1117
  
1118
  if (private_data->header_win)
1119
    {
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
      for (i = 0; i < 4; i++)
	{
	  if (private_data->arrow_win[i])
	    {
	      gdk_window_set_user_data (private_data->arrow_win[i], NULL);
	      gdk_window_destroy (private_data->arrow_win[i]);
	      private_data->arrow_win[i] = NULL;
	    }
	}
      gdk_window_set_user_data (private_data->header_win, NULL);
      gdk_window_destroy (private_data->header_win);
      private_data->header_win = NULL;
1132
    }
1133
  
1134
  if (private_data->week_win)
1135
    {
1136
1137
1138
      gdk_window_set_user_data (private_data->week_win, NULL);
      gdk_window_destroy (private_data->week_win);
      private_data->week_win = NULL;      
1139
    }
1140
  
1141
  if (private_data->main_win)
1142
    {
1143
1144
1145
      gdk_window_set_user_data (private_data->main_win, NULL);
      gdk_window_destroy (private_data->main_win);
      private_data->main_win = NULL;      
1146
    }
1147
  
1148
1149
1150
1151
1152
  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static void
1153
gtk_calendar_size_request (GtkWidget	  *widget,
1154
1155
1156
			   GtkRequisition *requisition)
{
  GtkCalendar *calendar;
1157
  GtkCalendarPrivateData *private_data;