1 /*
2 * $Id: calendar.c,v 1.110 2022/04/03 22:38:16 tom Exp $
3 *
4 * calendar.c -- implements the calendar box
5 *
6 * Copyright 2001-2021,2022 Thomas E. Dickey
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License, version 2.1
10 * as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this program; if not, write to
19 * Free Software Foundation, Inc.
20 * 51 Franklin St., Fifth Floor
21 * Boston, MA 02110, USA.
22 */
23
24 #include <dlg_internals.h>
25 #include <dlg_keys.h>
26
27 #define ONE_DAY (60 * 60 * 24)
28
29 #define MON_WIDE 4 /* width of a month-name */
30 #define DAY_HIGH 6 /* maximum lines in day-grid */
31 #define DAY_WIDE (8 * MON_WIDE) /* width of the day-grid */
32 #define HDR_HIGH 1 /* height of cells with month/year */
33 #define BTN_HIGH 1 /* height of button-row excluding margin */
34
35 /* two more lines: titles for day-of-week and month/year boxes */
36 #define MIN_HIGH (DAY_HIGH + 2 + HDR_HIGH + BTN_HIGH + (MAX_DAYS * MARGIN))
37 #define MIN_WIDE (DAY_WIDE + (4 * MARGIN))
38
39 typedef enum {
40 sMONTH = -3
41 ,sYEAR = -2
42 ,sDAY = -1
43 } STATES;
44
45 struct _box;
46
47 typedef int (*BOX_DRAW) (struct _box *, struct tm *);
48
49 typedef struct _box {
50 WINDOW *parent;
51 WINDOW *window;
52 int x;
53 int y;
54 int width;
55 int height;
56 BOX_DRAW box_draw;
57 int week_start;
58 } BOX;
59
60 #define MAX_DAYS 7
61 #define MAX_MONTHS 12
62
63 static char *cached_days[MAX_DAYS];
64 static char *cached_months[MAX_MONTHS];
65
66 static const char *
nameOfDayOfWeek(int n)67 nameOfDayOfWeek(int n)
68 {
69 static bool shown[MAX_DAYS];
70
71 while (n < 0) {
72 n += MAX_DAYS;
73 }
74 n %= MAX_DAYS;
75 #ifdef ENABLE_NLS
76 if (cached_days[n] == 0) {
77 const nl_item items[MAX_DAYS] =
78 {
79 ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7
80 };
81 cached_days[n] = dlg_strclone(nl_langinfo(items[n]));
82 memset(shown, 0, sizeof(shown));
83 }
84 #endif
85 if (cached_days[n] == 0) {
86 static const char *posix_days[MAX_DAYS] =
87 {
88 "Sunday",
89 "Monday",
90 "Tuesday",
91 "Wednesday",
92 "Thursday",
93 "Friday",
94 "Saturday"
95 };
96 size_t limit = MON_WIDE - 1;
97 char *value = dlg_strclone(posix_days[n]);
98
99 /*
100 * POSIX does not actually say what the length of an abbreviated name
101 * is. Typically it is 2, which will fit into our layout. That also
102 * happens to work with CJK entries as seen in glibc, which are a
103 * double-width cell. For now (2016/01/26), handle too-long names only
104 * for POSIX values.
105 */
106 if (strlen(value) > limit)
107 value[limit] = '\0';
108 cached_days[n] = value;
109 }
110 if (!shown[n]) {
111 DLG_TRACE(("# DAY(%d) = '%s'\n", n, cached_days[n]));
112 shown[n] = TRUE;
113 }
114 return cached_days[n];
115 }
116
117 static const char *
nameOfMonth(int n)118 nameOfMonth(int n)
119 {
120 static bool shown[MAX_MONTHS];
121
122 while (n < 0) {
123 n += MAX_MONTHS;
124 }
125 n %= MAX_MONTHS;
126 #ifdef ENABLE_NLS
127 if (cached_months[n] == 0) {
128 const nl_item items[MAX_MONTHS] =
129 {
130 MON_1, MON_2, MON_3, MON_4, MON_5, MON_6,
131 MON_7, MON_8, MON_9, MON_10, MON_11, MON_12
132 };
133 cached_months[n] = dlg_strclone(nl_langinfo(items[n]));
134 memset(shown, 0, sizeof(shown));
135 }
136 #endif
137 if (cached_months[n] == 0) {
138 static const char *posix_mons[MAX_MONTHS] =
139 {
140 "January",
141 "February",
142 "March",
143 "April",
144 "May",
145 "June",
146 "July",
147 "August",
148 "September",
149 "October",
150 "November",
151 "December"
152 };
153 cached_months[n] = dlg_strclone(posix_mons[n]);
154 }
155 if (!shown[n]) {
156 DLG_TRACE(("# MON(%d) = '%s'\n", n, cached_months[n]));
157 shown[n] = TRUE;
158 }
159 return cached_months[n];
160 }
161
162 /*
163 * Algorithm for Gregorian calendar.
164 */
165 static int
isleap(int y)166 isleap(int y)
167 {
168 return ((y % 4 == 0) &&
169 ((y % 100 != 0) ||
170 (y % 400 == 0))) ? 1 : 0;
171 }
172
173 static void
adjust_year_month(int * year,int * month)174 adjust_year_month(int *year, int *month)
175 {
176 while (*month < 0) {
177 *month += MAX_MONTHS;
178 *year -= 1;
179 }
180 while (*month >= MAX_MONTHS) {
181 *month -= MAX_MONTHS;
182 *year += 1;
183 }
184 }
185
186 static int
days_per_month(int year,int month)187 days_per_month(int year, int month)
188 {
189 static const int nominal[] =
190 {
191 31, 28, 31, 30, 31, 30,
192 31, 31, 30, 31, 30, 31
193 };
194 int result;
195
196 adjust_year_month(&year, &month);
197 result = nominal[month];
198 if (month == 1)
199 result += isleap(year);
200 return result;
201 }
202
203 static int
days_in_month(struct tm * current,int offset)204 days_in_month(struct tm *current, int offset /* -1, 0, 1 */ )
205 {
206 int year = current->tm_year + 1900;
207 int month = current->tm_mon + offset;
208
209 adjust_year_month(&year, &month);
210 return days_per_month(year, month);
211 }
212
213 static int
days_per_year(int year)214 days_per_year(int year)
215 {
216 return (isleap(year) ? 366 : 365);
217 }
218
219 static int
days_in_year(struct tm * current,int offset)220 days_in_year(struct tm *current, int offset /* -1, 0, 1 */ )
221 {
222 return days_per_year(current->tm_year + 1900 + offset);
223 }
224
225 /*
226 * Adapted from C FAQ
227 * "17.28: How can I find the day of the week given the date?"
228 * implementation by Tomohiko Sakamoto.
229 *
230 * d = day (0 to whatever)
231 * m = month (1 through 12)
232 * y = year (1752 and later, for Gregorian calendar)
233 */
234 static int
day_of_week(int y,int m,int d)235 day_of_week(int y, int m, int d)
236 {
237 static int t[] =
238 {
239 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4
240 };
241 y -= (m < 3);
242 return (6 + (y + (y / 4) - (y / 100) + (y / 400) + t[m - 1] + d)) % MAX_DAYS;
243 }
244
245 static int
day_in_year(int year,int month,int day)246 day_in_year(int year, int month, int day)
247 {
248 int result = day;
249 while (--month >= 1)
250 result += days_per_month(year, month);
251 return result;
252 }
253
254 static int
iso_week(int year,int month,int day)255 iso_week(int year, int month, int day)
256 {
257 int week = 1;
258 int dow;
259 int new_year_dow;
260 int diy;
261 int new_years_eve_dow;
262 static const int thursday = 3;
263
264 /* add the number weeks *between* date and newyear */
265 diy = day_in_year(year, month, day);
266 week += (diy - 1) / MAX_DAYS;
267
268 /* 0 = Monday */
269 dow = day_of_week(year, month, day);
270 new_year_dow = day_of_week(year, 1, 1);
271
272 /*
273 * If New Year falls on Friday, Saturday or Sunday, then New Years's week
274 * is the last week of the preceding year. In that case subtract one week.
275 */
276 if (new_year_dow > thursday)
277 --week;
278
279 /* Add one week if there is a Sunday to Monday transition. */
280 if (dow - new_year_dow < 0)
281 ++week;
282
283 /* Check if we are in the last week of the preceding year. */
284 if (week < 1) {
285 week = iso_week(--year, 12, 31);
286 }
287
288 /*
289 * If we are in the same week as New Year's eve, check if New Year's eve is
290 * in the first week of the next year.
291 */
292 new_years_eve_dow = (new_year_dow + 364 + isleap(year)) % MAX_DAYS;
293 if (365 + isleap(year) - diy < MAX_DAYS
294 && new_years_eve_dow >= dow
295 && new_years_eve_dow < thursday) {
296 week = 1;
297 }
298 return week;
299 }
300
301 static int *
getisoweeks(int year,int month)302 getisoweeks(int year, int month)
303 {
304 static int result[10];
305 int windx = 0;
306 int day;
307 int dpm = days_per_month(year, month);
308
309 for (day = 1; day <= dpm; day += MAX_DAYS)
310 result[windx++] = iso_week(year, month, day);
311 /*
312 * Ensure that there is a week number associated with the last day of the
313 * month, e.g., in case the last day of the month falls before Thursday,
314 * so that we have to show the week-number for the beginning of the
315 * following month.
316 */
317 result[windx] = iso_week(year, month, dpm);
318 return result;
319 }
320
321 static int
day_cell_number(struct tm * current)322 day_cell_number(struct tm *current)
323 {
324 int cell;
325 cell = current->tm_mday - ((6 + current->tm_mday - current->tm_wday) % MAX_DAYS);
326 if ((current->tm_mday - 1) % MAX_DAYS != current->tm_wday)
327 cell += 6;
328 else
329 cell--;
330 return cell;
331 }
332
333 static int
next_or_previous(int key,int two_d)334 next_or_previous(int key, int two_d)
335 {
336 int result = 0;
337
338 switch (key) {
339 case DLGK_GRID_UP:
340 result = two_d ? -MAX_DAYS : -1;
341 break;
342 case DLGK_GRID_LEFT:
343 result = -1;
344 break;
345 case DLGK_GRID_DOWN:
346 result = two_d ? MAX_DAYS : 1;
347 break;
348 case DLGK_GRID_RIGHT:
349 result = 1;
350 break;
351 default:
352 beep();
353 break;
354 }
355 return result;
356 }
357
358 /*
359 * Draw the day-of-month selection box
360 */
361 static int
draw_day(BOX * data,struct tm * current)362 draw_day(BOX * data, struct tm *current)
363 {
364 int cell_wide = MON_WIDE;
365 int y, x, this_x;
366 int save_y = 0, save_x = 0;
367 int day = current->tm_mday;
368 int mday;
369 int week = 0;
370 int windx = 0;
371 int *weeks = 0;
372 int last = days_in_month(current, 0);
373 int prev = days_in_month(current, -1);
374
375 werase(data->window);
376 dlg_draw_box2(data->parent,
377 data->y - MARGIN, data->x - MARGIN,
378 data->height + (2 * MARGIN), data->width + (2 * MARGIN),
379 menubox_attr,
380 menubox_border_attr,
381 menubox_border2_attr);
382
383 dlg_attrset(data->window, menubox_attr); /* daynames headline */
384 for (x = 0; x < MAX_DAYS; x++) {
385 mvwprintw(data->window,
386 0, (x + 1) * cell_wide, "%*.*s ",
387 cell_wide - 1,
388 cell_wide - 1,
389 nameOfDayOfWeek(x + data->week_start));
390 }
391
392 mday = ((6 + current->tm_mday -
393 current->tm_wday +
394 data->week_start) % MAX_DAYS) - MAX_DAYS;
395 if (mday <= -MAX_DAYS)
396 mday += MAX_DAYS;
397
398 if (dialog_vars.iso_week) {
399 weeks = getisoweeks(current->tm_year + 1900, current->tm_mon + 1);
400 } else {
401 /* mday is now in the range -6 to 0. */
402 week = (current->tm_yday + 6 + mday - current->tm_mday) / MAX_DAYS;
403 }
404
405 for (y = 1; mday < last; y++) {
406 dlg_attrset(data->window, menubox_attr); /* weeknumbers headline */
407 mvwprintw(data->window,
408 y, 0,
409 "%*d ",
410 cell_wide - 1,
411 weeks ? weeks[windx++] : ++week);
412 for (x = 0; x < MAX_DAYS; x++) {
413 this_x = 1 + (x + 1) * cell_wide;
414 ++mday;
415 if (wmove(data->window, y, this_x) == ERR)
416 continue;
417 dlg_attrset(data->window, item_attr); /* not selected days */
418 if (mday == day) {
419 dlg_attrset(data->window, item_selected_attr); /* selected day */
420 save_y = y;
421 save_x = this_x;
422 }
423 if (mday > 0) {
424 if (mday <= last) {
425 wprintw(data->window, "%*d", cell_wide - 2, mday);
426 } else if (mday == day) {
427 wprintw(data->window, "%*d", cell_wide - 2, mday - last);
428 }
429 } else if (mday == day) {
430 wprintw(data->window, "%*d", cell_wide - 2, mday + prev);
431 }
432 }
433 wmove(data->window, save_y, save_x);
434 }
435 /* just draw arrows - scrollbar is unsuitable here */
436 dlg_draw_arrows(data->parent, TRUE, TRUE,
437 data->x + ARROWS_COL,
438 data->y - 1,
439 data->y + data->height);
440
441 return 0;
442 }
443
444 /*
445 * Draw the month-of-year selection box
446 */
447 static int
draw_month(BOX * data,struct tm * current)448 draw_month(BOX * data, struct tm *current)
449 {
450 int month;
451
452 month = current->tm_mon + 1;
453
454 dlg_attrset(data->parent, dialog_attr); /* Headline "Month" */
455 (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Month"));
456 dlg_draw_box2(data->parent,
457 data->y - 1, data->x - 1,
458 data->height + 2, data->width + 2,
459 menubox_attr,
460 menubox_border_attr,
461 menubox_border2_attr);
462 dlg_attrset(data->window, item_attr); /* color the month selection */
463 mvwprintw(data->window, 0, 0, "%s", nameOfMonth(month - 1));
464 wmove(data->window, 0, 0);
465 return 0;
466 }
467
468 /*
469 * Draw the year selection box
470 */
471 static int
draw_year(BOX * data,struct tm * current)472 draw_year(BOX * data, struct tm *current)
473 {
474 int year = current->tm_year + 1900;
475
476 dlg_attrset(data->parent, dialog_attr); /* Headline "Year" */
477 (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Year"));
478 dlg_draw_box2(data->parent,
479 data->y - 1, data->x - 1,
480 data->height + 2, data->width + 2,
481 menubox_attr,
482 menubox_border_attr,
483 menubox_border2_attr);
484 dlg_attrset(data->window, item_attr); /* color the year selection */
485 mvwprintw(data->window, 0, 0, "%4d", year);
486 wmove(data->window, 0, 0);
487 return 0;
488 }
489
490 static int
init_object(BOX * data,WINDOW * parent,int x,int y,int width,int height,BOX_DRAW box_draw,int key_offset,int code)491 init_object(BOX * data,
492 WINDOW *parent,
493 int x, int y,
494 int width, int height,
495 BOX_DRAW box_draw,
496 int key_offset,
497 int code)
498 {
499 data->parent = parent;
500 data->x = x;
501 data->y = y;
502 data->width = width;
503 data->height = height;
504 data->box_draw = box_draw;
505 data->week_start = key_offset;
506
507 data->window = dlg_der_window(data->parent,
508 data->height, data->width,
509 data->y, data->x);
510 if (data->window == 0)
511 return -1;
512
513 dlg_mouse_setbase(getbegx(parent), getbegy(parent));
514 if (code == 'D') {
515 dlg_mouse_mkbigregion(y + 1, x + MON_WIDE, height - 1, width - MON_WIDE,
516 KEY_MAX + key_offset, 1, MON_WIDE, 3);
517 } else {
518 dlg_mouse_mkregion(y, x, height, width, code);
519 }
520
521 return 0;
522 }
523
524 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY)
525 #elif defined(HAVE_DLG_GAUGE)
526 static int
read_locale_setting(const char * name,int which)527 read_locale_setting(const char *name, int which)
528 {
529 FILE *fp;
530 char command[80];
531 int result = -1;
532
533 sprintf(command, "locale %s", name);
534 if ((fp = dlg_popen(command, "r")) != 0) {
535 int count = 0;
536 char buf[80];
537
538 while (fgets(buf, (int) sizeof(buf) - 1, fp) != 0) {
539 if (++count > which) {
540 char *next = 0;
541 long check = strtol(buf, &next, 0);
542 if (next != 0 &&
543 next != buf &&
544 *next == '\n') {
545 result = (int) check;
546 }
547 break;
548 }
549 }
550 pclose(fp);
551 }
552 return result;
553 }
554 #endif
555
556 static int
WeekStart(void)557 WeekStart(void)
558 {
559 int result = 0;
560 char *option = dialog_vars.week_start;
561 if (option != 0) {
562 if (option[0]) {
563 char *next = 0;
564 long check = strtol(option, &next, 0);
565 if (next == 0 ||
566 next == option ||
567 *next != '\0') {
568 if (!strcmp(option, "locale")) {
569 #if defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY)
570 /*
571 * glibc-specific.
572 */
573 int first_day = nl_langinfo(_NL_TIME_FIRST_WEEKDAY)[0];
574 char *basis_ptr = nl_langinfo(_NL_TIME_WEEK_1STDAY);
575 int basis_day = (int) (intptr_t) basis_ptr;
576 #elif defined(HAVE_DLG_GAUGE)
577 /*
578 * probably Linux-specific, but harmless otherwise. When
579 * available, the locale program will return a single
580 * integer on one line.
581 */
582 int first_day = read_locale_setting("first_weekday", 0);
583 int basis_day = read_locale_setting("week-1stday", 0);
584 #endif
585 #if (defined(ENABLE_NLS) && defined(HAVE_NL_LANGINFO_1STDAY)) || defined(HAVE_DLG_GAUGE)
586 int week_1stday = -1;
587 if (basis_day == 19971130)
588 week_1stday = 0; /* Sun */
589 else if (basis_day == 19971201)
590 week_1stday = 1; /* Mon */
591 if (week_1stday >= 0) {
592 result = first_day - week_1stday - 1;
593 }
594 #else
595 result = 0; /* Sun */
596 #endif
597 } else {
598 int day;
599 size_t eql = strlen(option);
600 for (day = 0; day < MAX_DAYS; ++day) {
601 if (!strncmp(nameOfDayOfWeek(day), option, eql)) {
602 result = day;
603 break;
604 }
605 }
606 }
607 } else if (check < 0) {
608 result = -1;
609 } else {
610 result = (int) (check % MAX_DAYS);
611 }
612 }
613 }
614 return result;
615 }
616
617 static int
CleanupResult(int code,WINDOW * dialog,char * prompt,DIALOG_VARS * save_vars)618 CleanupResult(int code, WINDOW *dialog, char *prompt, DIALOG_VARS * save_vars)
619 {
620 int n;
621
622 if (dialog != 0)
623 dlg_del_window(dialog);
624 dlg_mouse_free_regions();
625 if (prompt != 0)
626 free(prompt);
627 dlg_restore_vars(save_vars);
628
629 for (n = 0; n < MAX_DAYS; ++n) {
630 free(cached_days[n]);
631 cached_days[n] = 0;
632 }
633 for (n = 0; n < MAX_MONTHS; ++n) {
634 free(cached_months[n]);
635 cached_months[n] = 0;
636 }
637
638 return code;
639 }
640
641 static void
trace_date(struct tm * current,struct tm * old)642 trace_date(struct tm *current, struct tm *old)
643 {
644 bool changed = (old == 0 ||
645 current->tm_mday != old->tm_mday ||
646 current->tm_mon != old->tm_mon ||
647 current->tm_year != old->tm_year);
648 if (changed) {
649 DLG_TRACE(("# current %04d/%02d/%02d\n",
650 current->tm_year + 1900,
651 current->tm_mon + 1,
652 current->tm_mday));
653 } else {
654 DLG_TRACE(("# current (unchanged)\n"));
655 }
656 }
657
658 #define DrawObject(data) (data)->box_draw(data, ¤t)
659
660 /*
661 * Display a dialog box for entering a date
662 */
663 int
dialog_calendar(const char * title,const char * subtitle,int height,int width,int day,int month,int year)664 dialog_calendar(const char *title,
665 const char *subtitle,
666 int height,
667 int width,
668 int day,
669 int month,
670 int year)
671 {
672 /* *INDENT-OFF* */
673 static DLG_KEYS_BINDING binding[] = {
674 HELPKEY_BINDINGS,
675 ENTERKEY_BINDINGS,
676 TOGGLEKEY_BINDINGS,
677 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
678 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
679 DLG_KEYS_DATA( DLGK_GRID_DOWN, 'j' ),
680 DLG_KEYS_DATA( DLGK_GRID_DOWN, DLGK_MOUSE(KEY_NPAGE) ),
681 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_DOWN ),
682 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_NPAGE ),
683 DLG_KEYS_DATA( DLGK_GRID_LEFT, '-' ),
684 DLG_KEYS_DATA( DLGK_GRID_LEFT, 'h' ),
685 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_BACKSPACE ),
686 DLG_KEYS_DATA( DLGK_GRID_LEFT, CHR_PREVIOUS ),
687 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFT ),
688 DLG_KEYS_DATA( DLGK_GRID_RIGHT, '+' ),
689 DLG_KEYS_DATA( DLGK_GRID_RIGHT, 'l' ),
690 DLG_KEYS_DATA( DLGK_GRID_RIGHT, CHR_NEXT ),
691 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_NEXT ),
692 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ),
693 DLG_KEYS_DATA( DLGK_GRID_UP, 'k' ),
694 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PPAGE ),
695 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_PREVIOUS ),
696 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_UP ),
697 DLG_KEYS_DATA( DLGK_GRID_UP, DLGK_MOUSE(KEY_PPAGE) ),
698 END_KEYS_BINDING
699 };
700 /* *INDENT-ON* */
701
702 #ifdef KEY_RESIZE
703 int old_height = height;
704 int old_width = width;
705 #endif
706 BOX dy_box, mn_box, yr_box;
707 int fkey;
708 int key;
709 int step;
710 int button;
711 int result = DLG_EXIT_UNKNOWN;
712 int week_start;
713 WINDOW *dialog;
714 time_t now_time;
715 struct tm current;
716 int state = dlg_default_button();
717 const char **buttons = dlg_ok_labels();
718 char *prompt;
719 int mincols = MIN_WIDE;
720 char buffer[MAX_LEN];
721 DIALOG_VARS save_vars;
722
723 DLG_TRACE(("# calendar args:\n"));
724 DLG_TRACE2S("title", title);
725 DLG_TRACE2S("message", subtitle);
726 DLG_TRACE2N("height", height);
727 DLG_TRACE2N("width", width);
728 DLG_TRACE2N("day", day);
729 DLG_TRACE2N("month", month);
730 DLG_TRACE2N("year", year);
731
732 dlg_save_vars(&save_vars);
733 dialog_vars.separate_output = TRUE;
734
735 dlg_does_output();
736
737 /*
738 * Unless overridden, the current time/date is our starting point.
739 */
740 now_time = time((time_t *) 0);
741 current = *localtime(&now_time);
742
743 #ifdef HAVE_MKTIME
744 current.tm_isdst = -1;
745 if (year >= 1900) {
746 current.tm_year = year - 1900;
747 }
748 if (month >= 1) {
749 current.tm_mon = month - 1;
750 }
751 if (day > 0 && day <= days_per_month(current.tm_year + 1900,
752 current.tm_mon)) {
753 current.tm_mday = day;
754 }
755 now_time = mktime(¤t);
756 #else
757 if (day < 0)
758 day = current.tm_mday;
759 if (month < 0)
760 month = current.tm_mon + 1;
761 if (year < 0)
762 year = current.tm_year + 1900;
763
764 /* compute a struct tm that matches the day/month/year parameters */
765 if (((year -= 1900) > 0) && (year < 200)) {
766 /* ugly, but I'd like to run this on older machines w/o mktime -TD */
767 for (;;) {
768 if (year > current.tm_year) {
769 now_time += ONE_DAY * days_in_year(¤t, 0);
770 } else if (year < current.tm_year) {
771 now_time -= ONE_DAY * days_in_year(¤t, -1);
772 } else if (month > current.tm_mon + 1) {
773 now_time += ONE_DAY * days_in_month(¤t, 0);
774 } else if (month < current.tm_mon + 1) {
775 now_time -= ONE_DAY * days_in_month(¤t, -1);
776 } else if (day > current.tm_mday) {
777 now_time += ONE_DAY;
778 } else if (day < current.tm_mday) {
779 now_time -= ONE_DAY;
780 } else {
781 break;
782 }
783 current = *localtime(&now_time);
784 }
785 }
786 #endif
787
788 dlg_button_layout(buttons, &mincols);
789
790 #ifdef KEY_RESIZE
791 retry:
792 #endif
793
794 prompt = dlg_strclone(subtitle);
795 dlg_auto_size(title, prompt, &height, &width, 0, mincols);
796
797 height += MIN_HIGH - 1;
798 dlg_print_size(height, width);
799 dlg_ctl_size(height, width);
800
801 dialog = dlg_new_window(height, width,
802 dlg_box_y_ordinate(height),
803 dlg_box_x_ordinate(width));
804 dlg_register_window(dialog, "calendar", binding);
805 dlg_register_buttons(dialog, "calendar", buttons);
806
807 /* mainbox */
808 dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
809 dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
810 dlg_draw_title(dialog, title);
811
812 dlg_attrset(dialog, dialog_attr); /* text mainbox */
813 dlg_print_autowrap(dialog, prompt, height, width);
814
815 /* compute positions of day, month and year boxes */
816 memset(&dy_box, 0, sizeof(dy_box));
817 memset(&mn_box, 0, sizeof(mn_box));
818 memset(&yr_box, 0, sizeof(yr_box));
819
820 if ((week_start = WeekStart()) < 0 ||
821 init_object(&dy_box,
822 dialog,
823 (width - DAY_WIDE) / 2,
824 1 + (height - (DAY_HIGH + BTN_HIGH + (5 * MARGIN))),
825 DAY_WIDE,
826 DAY_HIGH + 1,
827 draw_day,
828 week_start,
829 'D') < 0 ||
830 ((dy_box.week_start = WeekStart()) < 0) ||
831 DrawObject(&dy_box) < 0) {
832 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars);
833 }
834
835 if (init_object(&mn_box,
836 dialog,
837 dy_box.x,
838 dy_box.y - (HDR_HIGH + 2 * MARGIN),
839 (DAY_WIDE / 2) - MARGIN,
840 HDR_HIGH,
841 draw_month,
842 0,
843 'M') < 0
844 || DrawObject(&mn_box) < 0) {
845 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars);
846 }
847
848 if (init_object(&yr_box,
849 dialog,
850 dy_box.x + mn_box.width + 2,
851 mn_box.y,
852 mn_box.width,
853 mn_box.height,
854 draw_year,
855 0,
856 'Y') < 0
857 || DrawObject(&yr_box) < 0) {
858 return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars);
859 }
860
861 dlg_trace_win(dialog);
862 while (result == DLG_EXIT_UNKNOWN) {
863 int key2;
864 BOX *obj = (state == sDAY ? &dy_box
865 : (state == sMONTH ? &mn_box :
866 (state == sYEAR ? &yr_box : 0)));
867
868 button = (state < 0) ? 0 : state;
869 dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width);
870 if (obj != 0)
871 dlg_set_focus(dialog, obj->window);
872
873 key = dlg_mouse_wgetch(dialog, &fkey);
874 if (dlg_result_key(key, fkey, &result)) {
875 if (!dlg_button_key(result, &button, &key, &fkey))
876 break;
877 }
878 #define Mouse2Key(key) (key - M_EVENT)
879 if (fkey && (key >= DLGK_MOUSE(KEY_MIN) && key <= DLGK_MOUSE(KEY_MAX))) {
880 key = dlg_lookup_key(dialog, Mouse2Key(key), &fkey);
881 }
882
883 if ((key2 = dlg_char_to_button(key, buttons)) >= 0) {
884 result = key2;
885 } else if (fkey) {
886 /* handle function-keys */
887 switch (key) {
888 case DLGK_MOUSE('D'):
889 state = sDAY;
890 break;
891 case DLGK_MOUSE('M'):
892 state = sMONTH;
893 break;
894 case DLGK_MOUSE('Y'):
895 state = sYEAR;
896 break;
897 case DLGK_TOGGLE:
898 case DLGK_ENTER:
899 result = dlg_enter_buttoncode(button);
900 break;
901 case DLGK_LEAVE:
902 result = dlg_ok_buttoncode(button);
903 break;
904 case DLGK_FIELD_PREV:
905 state = dlg_prev_ok_buttonindex(state, sMONTH);
906 break;
907 case DLGK_FIELD_NEXT:
908 state = dlg_next_ok_buttonindex(state, sMONTH);
909 break;
910 #ifdef KEY_RESIZE
911 case KEY_RESIZE:
912 dlg_will_resize(dialog);
913 /* reset data */
914 height = old_height;
915 width = old_width;
916 free(prompt);
917 _dlg_resize_cleanup(dialog);
918 /* repaint */
919 goto retry;
920 #endif
921 default:
922 step = 0;
923 key2 = -1;
924 if (is_DLGK_MOUSE(key)) {
925 if ((key2 = dlg_ok_buttoncode(Mouse2Key(key))) >= 0) {
926 result = key2;
927 break;
928 } else if (key >= DLGK_MOUSE(KEY_MAX)) {
929 state = sDAY;
930 obj = &dy_box;
931 key2 = 1;
932 step = (key
933 - DLGK_MOUSE(KEY_MAX)
934 - day_cell_number(¤t));
935 DLG_TRACE(("# mouseclick decoded %d\n", step));
936 }
937 }
938 if (obj != 0) {
939 if (key2 < 0) {
940 step = next_or_previous(key, (obj == &dy_box));
941 }
942 if (step != 0) {
943 struct tm old = current;
944
945 /* see comment regarding mktime -TD */
946 if (obj == &dy_box) {
947 now_time += ONE_DAY * step;
948 } else if (obj == &mn_box) {
949 if (step > 0)
950 now_time += ONE_DAY *
951 days_in_month(¤t, 0);
952 else
953 now_time -= ONE_DAY *
954 days_in_month(¤t, -1);
955 } else if (obj == &yr_box) {
956 if (step > 0)
957 now_time += (ONE_DAY
958 * days_in_year(¤t, 0));
959 else
960 now_time -= (ONE_DAY
961 * days_in_year(¤t, -1));
962 }
963
964 current = *localtime(&now_time);
965
966 trace_date(¤t, &old);
967 if (obj != &dy_box
968 && (current.tm_mday != old.tm_mday
969 || current.tm_mon != old.tm_mon
970 || current.tm_year != old.tm_year))
971 DrawObject(&dy_box);
972 if (obj != &mn_box && current.tm_mon != old.tm_mon)
973 DrawObject(&mn_box);
974 if (obj != &yr_box && current.tm_year != old.tm_year)
975 DrawObject(&yr_box);
976 (void) DrawObject(obj);
977 }
978 } else if (state >= 0) {
979 if (next_or_previous(key, FALSE) < 0)
980 state = dlg_prev_ok_buttonindex(state, sMONTH);
981 else if (next_or_previous(key, FALSE) > 0)
982 state = dlg_next_ok_buttonindex(state, sMONTH);
983 }
984 break;
985 }
986 }
987 }
988
989 #define DefaultFormat(dst, src) \
990 sprintf(dst, "%02d/%02d/%0d", \
991 src.tm_mday, src.tm_mon + 1, src.tm_year + 1900)
992 #ifdef HAVE_STRFTIME
993 if (dialog_vars.date_format != 0) {
994 size_t used = strftime(buffer,
995 sizeof(buffer) - 1,
996 dialog_vars.date_format,
997 ¤t);
998 if (used == 0 || *buffer == '\0')
999 DefaultFormat(buffer, current);
1000 } else
1001 #endif
1002 DefaultFormat(buffer, current);
1003
1004 dlg_add_result(buffer);
1005 AddLastKey();
1006
1007 return CleanupResult(result, dialog, prompt, &save_vars);
1008 }
1009