]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/dialog/calendar.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / dialog / calendar.c
1 /*
2  * $Id: calendar.c,v 1.62 2011/06/29 09:47:06 tom Exp $
3  *
4  *  calendar.c -- implements the calendar box
5  *
6  *  Copyright 2001-2010,2011    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 <dialog.h>
25 #include <dlg_keys.h>
26
27 #include <time.h>
28
29 #define ONE_DAY  (60 * 60 * 24)
30
31 #define MON_WIDE 4              /* width of a month-name */
32 #define DAY_HIGH 6              /* maximum lines in day-grid */
33 #define DAY_WIDE (8 * MON_WIDE) /* width of the day-grid */
34 #define HDR_HIGH 1              /* height of cells with month/year */
35 #define BTN_HIGH 1              /* height of button-row excluding margin */
36
37 /* two more lines: titles for day-of-week and month/year boxes */
38 #define MIN_HIGH (DAY_HIGH + 2 + HDR_HIGH + BTN_HIGH + (7 * MARGIN))
39 #define MIN_WIDE (DAY_WIDE + (4 * MARGIN))
40
41 typedef enum {
42     sMONTH = -3
43     ,sYEAR = -2
44     ,sDAY = -1
45 } STATES;
46
47 struct _box;
48
49 typedef int (*BOX_DRAW) (struct _box *, struct tm *);
50
51 typedef struct _box {
52     WINDOW *parent;
53     WINDOW *window;
54     int x;
55     int y;
56     int width;
57     int height;
58     BOX_DRAW box_draw;
59 } BOX;
60
61 static const char *
62 nameOfDayOfWeek(int n)
63 {
64     static const char *table[7]
65 #ifndef ENABLE_NLS
66     =
67     {
68         "Sunday",
69         "Monday",
70         "Tuesday",
71         "Wednesday",
72         "Thursday",
73         "Friday",
74         "Saturday"
75     }
76 #endif
77      ;
78     const char *result = 0;
79
80     if (n >= 0 && n < 7) {
81 #ifdef ENABLE_NLS
82         if (table[n] == 0) {
83             nl_item items[7] =
84             {
85                 ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7
86             };
87             table[n] = nl_langinfo(items[n]);
88         }
89 #endif
90         result = table[n];
91     }
92     if (result == 0) {
93         result = "?";
94     }
95     return result;
96 }
97
98 static const char *
99 nameOfMonth(int n)
100 {
101     static const char *table[12]
102 #ifndef ENABLE_NLS
103     =
104     {
105         "January",
106         "February",
107         "March",
108         "April",
109         "May",
110         "June",
111         "July",
112         "August",
113         "September",
114         "October",
115         "November",
116         "December"
117     }
118 #endif
119      ;
120     const char *result = 0;
121
122     if (n >= 0 && n < 12) {
123 #ifdef ENABLE_NLS
124         if (table[n] == 0) {
125             nl_item items[12] =
126             {
127                 MON_1, MON_2, MON_3, MON_4, MON_5, MON_6,
128                 MON_7, MON_8, MON_9, MON_10, MON_11, MON_12
129             };
130             table[n] = nl_langinfo(items[n]);
131         }
132 #endif
133         result = table[n];
134     }
135     if (result == 0) {
136         result = "?";
137     }
138     return result;
139 }
140
141 static int
142 days_in_month(struct tm *current, int offset /* -1, 0, 1 */ )
143 {
144     static const int nominal[] =
145     {
146         31, 28, 31, 30, 31, 30,
147         31, 31, 30, 31, 30, 31
148     };
149     int year = current->tm_year;
150     int month = current->tm_mon + offset;
151     int result;
152
153     while (month < 0) {
154         month += 12;
155         year -= 1;
156     }
157     while (month >= 12) {
158         month -= 12;
159         year += 1;
160     }
161     result = nominal[month];
162     if (month == 1)
163         result += ((year % 4) == 0);
164     return result;
165 }
166
167 static int
168 days_in_year(struct tm *current, int offset /* -1, 0, 1 */ )
169 {
170     int year = current->tm_year + 1900 + offset;
171
172     return ((year % 4) == 0) ? 366 : 365;
173 }
174
175 static int
176 day_cell_number(struct tm *current)
177 {
178     int cell;
179     cell = current->tm_mday - ((6 + current->tm_mday - current->tm_wday) % 7);
180     if ((current->tm_mday - 1) % 7 != current->tm_wday)
181         cell += 6;
182     else
183         cell--;
184     return cell;
185 }
186
187 static int
188 next_or_previous(int key, int two_d)
189 {
190     int result = 0;
191
192     switch (key) {
193     case DLGK_GRID_UP:
194         result = two_d ? -7 : -1;
195         break;
196     case DLGK_GRID_LEFT:
197         result = -1;
198         break;
199     case DLGK_GRID_DOWN:
200         result = two_d ? 7 : 1;
201         break;
202     case DLGK_GRID_RIGHT:
203         result = 1;
204         break;
205     default:
206         beep();
207         break;
208     }
209     return result;
210 }
211
212 /*
213  * Draw the day-of-month selection box
214  */
215 static int
216 draw_day(BOX * data, struct tm *current)
217 {
218     int cell_wide = MON_WIDE;
219     int y, x, this_x = 0;
220     int save_y = 0, save_x = 0;
221     int day = current->tm_mday;
222     int mday;
223     int week;
224     int last = days_in_month(current, 0);
225     int prev = days_in_month(current, -1);
226
227     werase(data->window);
228     dlg_draw_box(data->parent,
229                  data->y - MARGIN, data->x - MARGIN,
230                  data->height + (2 * MARGIN), data->width + (2 * MARGIN),
231                  menubox_border_attr, menubox_attr);    /* border of daybox */
232
233     wattrset(data->window, menubox_attr);       /* daynames headline */
234     for (x = 0; x < 7; x++) {
235         mvwprintw(data->window,
236                   0, (x + 1) * cell_wide, "%*.*s ",
237                   cell_wide - 1,
238                   cell_wide - 1,
239                   nameOfDayOfWeek(x));
240     }
241
242     mday = ((6 + current->tm_mday - current->tm_wday) % 7) - 7;
243     if (mday <= -7)
244         mday += 7;
245     /* mday is now in the range -6 to 0. */
246     week = (current->tm_yday + 6 + mday - current->tm_mday) / 7;
247
248     for (y = 1; mday < last; y++) {
249         wattrset(data->window, menubox_attr);   /* weeknumbers headline */
250         mvwprintw(data->window,
251                   y, 0,
252                   "%*d ",
253                   cell_wide - 1,
254                   ++week);
255         for (x = 0; x < 7; x++) {
256             this_x = 1 + (x + 1) * cell_wide;
257             ++mday;
258             if (wmove(data->window, y, this_x) == ERR)
259                 continue;
260             wattrset(data->window, item_attr);  /* not selected days */
261             if (mday == day) {
262                 wattrset(data->window, item_selected_attr);     /* selected day */
263                 save_y = y;
264                 save_x = this_x;
265             }
266             if (mday > 0) {
267                 if (mday <= last) {
268                     wprintw(data->window, "%*d", cell_wide - 2, mday);
269                 } else if (mday == day) {
270                     wprintw(data->window, "%*d", cell_wide - 2, mday - last);
271                 }
272             } else if (mday == day) {
273                 wprintw(data->window, "%*d", cell_wide - 2, mday + prev);
274             }
275         }
276         wmove(data->window, save_y, save_x);
277     }
278     /* just draw arrows - scrollbar is unsuitable here */
279     dlg_draw_arrows(data->parent, TRUE, TRUE,
280                     data->x + ARROWS_COL,
281                     data->y - 1,
282                     data->y + data->height);
283
284     return 0;
285 }
286
287 /*
288  * Draw the month-of-year selection box
289  */
290 static int
291 draw_month(BOX * data, struct tm *current)
292 {
293     int month;
294
295     month = current->tm_mon + 1;
296
297     wattrset(data->parent, dialog_attr);        /* Headline "Month" */
298     (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Month"));
299     dlg_draw_box(data->parent,
300                  data->y - 1, data->x - 1,
301                  data->height + 2, data->width + 2,
302                  menubox_border_attr, menubox_attr);    /* borders of monthbox */
303     wattrset(data->window, item_attr);  /* color the month selection */
304     mvwprintw(data->window, 0, 0, "%s", nameOfMonth(month - 1));
305     wmove(data->window, 0, 0);
306     return 0;
307 }
308
309 /*
310  * Draw the year selection box
311  */
312 static int
313 draw_year(BOX * data, struct tm *current)
314 {
315     int year = current->tm_year + 1900;
316
317     wattrset(data->parent, dialog_attr);        /* Headline "Year" */
318     (void) mvwprintw(data->parent, data->y - 2, data->x - 1, _("Year"));
319     dlg_draw_box(data->parent,
320                  data->y - 1, data->x - 1,
321                  data->height + 2, data->width + 2,
322                  menubox_border_attr, menubox_attr);    /* borders of yearbox */
323     wattrset(data->window, item_attr);  /* color the year selection */
324     mvwprintw(data->window, 0, 0, "%4d", year);
325     wmove(data->window, 0, 0);
326     return 0;
327 }
328
329 static int
330 init_object(BOX * data,
331             WINDOW *parent,
332             int x, int y,
333             int width, int height,
334             BOX_DRAW box_draw,
335             int code)
336 {
337     data->parent = parent;
338     data->x = x;
339     data->y = y;
340     data->width = width;
341     data->height = height;
342     data->box_draw = box_draw;
343
344     data->window = derwin(data->parent,
345                           data->height, data->width,
346                           data->y, data->x);
347     if (data->window == 0)
348         return -1;
349     (void) keypad(data->window, TRUE);
350
351     dlg_mouse_setbase(getbegx(parent), getbegy(parent));
352     if (code == 'D') {
353         dlg_mouse_mkbigregion(y + 1, x + MON_WIDE, height - 1, width - MON_WIDE,
354                               KEY_MAX, 1, MON_WIDE, 3);
355     } else {
356         dlg_mouse_mkregion(y, x, height, width, code);
357     }
358
359     return 0;
360 }
361
362 static int
363 CleanupResult(int code, WINDOW *dialog, char *prompt, DIALOG_VARS * save_vars)
364 {
365     if (dialog != 0)
366         dlg_del_window(dialog);
367     dlg_mouse_free_regions();
368     if (prompt != 0)
369         free(prompt);
370     dlg_restore_vars(save_vars);
371
372     return code;
373 }
374
375 #define DrawObject(data) (data)->box_draw(data, &current)
376
377 /*
378  * Display a dialog box for entering a date
379  */
380 int
381 dialog_calendar(const char *title,
382                 const char *subtitle,
383                 int height,
384                 int width,
385                 int day,
386                 int month,
387                 int year)
388 {
389     /* *INDENT-OFF* */
390     static DLG_KEYS_BINDING binding[] = {
391         HELPKEY_BINDINGS,
392         ENTERKEY_BINDINGS,
393         DLG_KEYS_DATA( DLGK_ENTER,      ' ' ),
394         DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
395         DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
396         DLG_KEYS_DATA( DLGK_GRID_DOWN,  'j' ),
397         DLG_KEYS_DATA( DLGK_GRID_DOWN,  DLGK_MOUSE(KEY_NPAGE) ),
398         DLG_KEYS_DATA( DLGK_GRID_DOWN,  KEY_DOWN ),
399         DLG_KEYS_DATA( DLGK_GRID_DOWN,  KEY_NPAGE ),
400         DLG_KEYS_DATA( DLGK_GRID_LEFT,  '-' ),
401         DLG_KEYS_DATA( DLGK_GRID_LEFT,  'h' ),
402         DLG_KEYS_DATA( DLGK_GRID_LEFT,  CHR_BACKSPACE ),
403         DLG_KEYS_DATA( DLGK_GRID_LEFT,  CHR_PREVIOUS ),
404         DLG_KEYS_DATA( DLGK_GRID_LEFT,  KEY_LEFT ),
405         DLG_KEYS_DATA( DLGK_GRID_RIGHT, '+' ),
406         DLG_KEYS_DATA( DLGK_GRID_RIGHT, 'l' ),
407         DLG_KEYS_DATA( DLGK_GRID_RIGHT, CHR_NEXT ),
408         DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_NEXT ),
409         DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ),
410         DLG_KEYS_DATA( DLGK_GRID_UP,    'k' ),
411         DLG_KEYS_DATA( DLGK_GRID_UP,    KEY_PPAGE ),
412         DLG_KEYS_DATA( DLGK_GRID_UP,    KEY_PREVIOUS ),
413         DLG_KEYS_DATA( DLGK_GRID_UP,    KEY_UP ),
414         DLG_KEYS_DATA( DLGK_GRID_UP,    DLGK_MOUSE(KEY_PPAGE) ),
415         END_KEYS_BINDING
416     };
417     /* *INDENT-ON* */
418
419 #ifdef KEY_RESIZE
420     int old_height = height;
421     int old_width = width;
422 #endif
423     BOX dy_box, mn_box, yr_box;
424     int fkey;
425     int key = 0;
426     int key2;
427     int step;
428     int button;
429     int result = DLG_EXIT_UNKNOWN;
430     WINDOW *dialog;
431     time_t now_time = time((time_t *) 0);
432     struct tm current;
433     int state = dlg_defaultno_button();
434     const char **buttons = dlg_ok_labels();
435     char *prompt = dlg_strclone(subtitle);
436     int mincols = MIN_WIDE;
437     char buffer[MAX_LEN];
438     DIALOG_VARS save_vars;
439
440     dlg_save_vars(&save_vars);
441     dialog_vars.separate_output = TRUE;
442
443     dlg_does_output();
444
445     now_time = time((time_t *) 0);
446     current = *localtime(&now_time);
447     if (day < 0)
448         day = current.tm_mday;
449     if (month < 0)
450         month = current.tm_mon + 1;
451     if (year < 0)
452         year = current.tm_year + 1900;
453
454     /* compute a struct tm that matches the day/month/year parameters */
455     if (((year -= 1900) > 0) && (year < 200)) {
456         /* ugly, but I'd like to run this on older machines w/o mktime -TD */
457         for (;;) {
458             if (year > current.tm_year) {
459                 now_time += ONE_DAY * days_in_year(&current, 0);
460             } else if (year < current.tm_year) {
461                 now_time -= ONE_DAY * days_in_year(&current, -1);
462             } else if (month > current.tm_mon + 1) {
463                 now_time += ONE_DAY * days_in_month(&current, 0);
464             } else if (month < current.tm_mon + 1) {
465                 now_time -= ONE_DAY * days_in_month(&current, -1);
466             } else if (day > current.tm_mday) {
467                 now_time += ONE_DAY;
468             } else if (day < current.tm_mday) {
469                 now_time -= ONE_DAY;
470             } else {
471                 break;
472             }
473             current = *localtime(&now_time);
474         }
475     }
476     dlg_button_layout(buttons, &mincols);
477
478 #ifdef KEY_RESIZE
479   retry:
480 #endif
481
482     dlg_auto_size(title, prompt, &height, &width, 0, mincols);
483     height += MIN_HIGH - 1;
484     dlg_print_size(height, width);
485     dlg_ctl_size(height, width);
486
487     dialog = dlg_new_window(height, width,
488                             dlg_box_y_ordinate(height),
489                             dlg_box_x_ordinate(width));
490     dlg_register_window(dialog, "calendar", binding);
491     dlg_register_buttons(dialog, "calendar", buttons);
492
493     /* mainbox */
494     dlg_draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
495     dlg_draw_bottom_box(dialog);
496     dlg_draw_title(dialog, title);
497
498     wattrset(dialog, dialog_attr);      /* text mainbox */
499     dlg_print_autowrap(dialog, prompt, height, width);
500
501     /* compute positions of day, month and year boxes */
502     memset(&dy_box, 0, sizeof(dy_box));
503     memset(&mn_box, 0, sizeof(mn_box));
504     memset(&yr_box, 0, sizeof(yr_box));
505
506     if (init_object(&dy_box,
507                     dialog,
508                     (width - DAY_WIDE) / 2,
509                     1 + (height - (DAY_HIGH + BTN_HIGH + (5 * MARGIN))),
510                     DAY_WIDE,
511                     DAY_HIGH + 1,
512                     draw_day,
513                     'D') < 0
514         || DrawObject(&dy_box) < 0) {
515         return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars);
516     }
517
518     if (init_object(&mn_box,
519                     dialog,
520                     dy_box.x,
521                     dy_box.y - (HDR_HIGH + 2 * MARGIN),
522                     (DAY_WIDE / 2) - MARGIN,
523                     HDR_HIGH,
524                     draw_month,
525                     'M') < 0
526         || DrawObject(&mn_box) < 0) {
527         return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars);
528     }
529
530     if (init_object(&yr_box,
531                     dialog,
532                     dy_box.x + mn_box.width + 2,
533                     mn_box.y,
534                     mn_box.width,
535                     mn_box.height,
536                     draw_year,
537                     'Y') < 0
538         || DrawObject(&yr_box) < 0) {
539         return CleanupResult(DLG_EXIT_ERROR, dialog, prompt, &save_vars);
540     }
541
542     while (result == DLG_EXIT_UNKNOWN) {
543         BOX *obj = (state == sDAY ? &dy_box
544                     : (state == sMONTH ? &mn_box :
545                        (state == sYEAR ? &yr_box : 0)));
546
547         button = (state < 0) ? 0 : state;
548         dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width);
549         if (obj != 0)
550             dlg_set_focus(dialog, obj->window);
551
552         key = dlg_mouse_wgetch(dialog, &fkey);
553         if (dlg_result_key(key, fkey, &result))
554             break;
555
556         if (fkey && (key >= DLGK_MOUSE(KEY_MIN) && key <= DLGK_MOUSE(KEY_MAX))) {
557             key = dlg_lookup_key(dialog, key - M_EVENT, &fkey);
558         }
559
560         if ((key2 = dlg_char_to_button(key, buttons)) >= 0) {
561             result = key2;
562         } else if (fkey) {
563             /* handle function-keys */
564             switch (key) {
565             case DLGK_MOUSE('D'):
566                 state = sDAY;
567                 break;
568             case DLGK_MOUSE('M'):
569                 state = sMONTH;
570                 break;
571             case DLGK_MOUSE('Y'):
572                 state = sYEAR;
573                 break;
574             case DLGK_ENTER:
575                 result = dlg_enter_buttoncode(button);
576                 break;
577             case DLGK_FIELD_PREV:
578                 state = dlg_prev_ok_buttonindex(state, sMONTH);
579                 break;
580             case DLGK_FIELD_NEXT:
581                 state = dlg_next_ok_buttonindex(state, sMONTH);
582                 break;
583 #ifdef KEY_RESIZE
584             case KEY_RESIZE:
585                 /* reset data */
586                 height = old_height;
587                 width = old_width;
588                 /* repaint */
589                 dlg_clear();
590                 dlg_del_window(dialog);
591                 refresh();
592                 dlg_mouse_free_regions();
593                 goto retry;
594 #endif
595             default:
596                 step = 0;
597                 key2 = -1;
598                 if (is_DLGK_MOUSE(key)) {
599                     if ((key2 = dlg_ok_buttoncode(key - M_EVENT)) >= 0) {
600                         result = key2;
601                         break;
602                     } else if (key >= DLGK_MOUSE(KEY_MAX)) {
603                         state = sDAY;
604                         obj = &dy_box;
605                         key2 = 1;
606                         step = (key
607                                 - DLGK_MOUSE(KEY_MAX)
608                                 - day_cell_number(&current));
609                     }
610                 }
611                 if (obj != 0) {
612                     if (key2 < 0)
613                         step = next_or_previous(key, (obj == &dy_box));
614                     if (step != 0) {
615                         struct tm old = current;
616
617                         /* see comment regarding mktime -TD */
618                         if (obj == &dy_box) {
619                             now_time += ONE_DAY * step;
620                         } else if (obj == &mn_box) {
621                             if (step > 0)
622                                 now_time += ONE_DAY *
623                                     days_in_month(&current, 0);
624                             else
625                                 now_time -= ONE_DAY *
626                                     days_in_month(&current, -1);
627                         } else if (obj == &yr_box) {
628                             if (step > 0)
629                                 now_time += (ONE_DAY
630                                              * days_in_year(&current, 0));
631                             else
632                                 now_time -= (ONE_DAY
633                                              * days_in_year(&current, -1));
634                         }
635
636                         current = *localtime(&now_time);
637
638                         if (obj != &dy_box
639                             && (current.tm_mday != old.tm_mday
640                                 || current.tm_mon != old.tm_mon
641                                 || current.tm_year != old.tm_year))
642                             DrawObject(&dy_box);
643                         if (obj != &mn_box && current.tm_mon != old.tm_mon)
644                             DrawObject(&mn_box);
645                         if (obj != &yr_box && current.tm_year != old.tm_year)
646                             DrawObject(&yr_box);
647                         (void) DrawObject(obj);
648                     }
649                 } else if (state >= 0) {
650                     if (next_or_previous(key, FALSE) < 0)
651                         state = dlg_prev_ok_buttonindex(state, sMONTH);
652                     else if (next_or_previous(key, FALSE) > 0)
653                         state = dlg_next_ok_buttonindex(state, sMONTH);
654                 }
655                 break;
656             }
657         }
658     }
659
660 #define DefaultFormat(dst, src) \
661         sprintf(dst, "%02d/%02d/%0d", \
662                 src.tm_mday, src.tm_mon + 1, src.tm_year + 1900)
663 #ifdef HAVE_STRFTIME
664     if (dialog_vars.date_format != 0) {
665         size_t used = strftime(buffer,
666                                sizeof(buffer) - 1,
667                                dialog_vars.date_format,
668                                &current);
669         if (used == 0 || *buffer == '\0')
670             DefaultFormat(buffer, current);
671     } else
672 #endif
673         DefaultFormat(buffer, current);
674
675     dlg_add_result(buffer);
676     dlg_add_separator();
677
678     return CleanupResult(result, dialog, prompt, &save_vars);
679 }