xref: /freebsd/contrib/bsddialog/lib/datebox.c (revision 61e21613)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022-2023 Alfonso Sabato Siciliano
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <curses.h>
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include "bsddialog.h"
33 #include "bsddialog_theme.h"
34 #include "lib_util.h"
35 
36 /* Calendar */
37 #define MIN_YEAR_CAL   0
38 #define MAX_YEAR_CAL   999999999
39 #define MINHCAL        13
40 #define MINWCAL        36 /* 34 calendar, 1 + 1 margins */
41 /* Datebox */
42 #define MIN_YEAR_DATE  0
43 #define MAX_YEAR_DATE  9999
44 #define MINWDATE       23 /* 3 windows and their borders */
45 
46 #define ISLEAP(year) ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
47 
48 static int minyear;
49 static int maxyear;
50 
51 static const char *m[12] = {
52 	"January",
53 	"February",
54 	"March",
55 	"April",
56 	"May",
57 	"June",
58 	"July",
59 	"August",
60 	"September",
61 	"October",
62 	"November",
63 	"December"
64 };
65 
66 enum operation {
67 	UP_DAY,
68 	DOWN_DAY,
69 	LEFT_DAY,
70 	RIGHT_DAY,
71 	UP_MONTH,
72 	DOWN_MONTH,
73 	UP_YEAR,
74 	DOWN_YEAR
75 };
76 
77 /* private datebox item */
78 struct dateitem {
79 	enum operation up;
80 	enum operation down;
81 	WINDOW *win;
82 	int width;
83 	const char *fmt;
84 	int *value;
85 };
86 
87 static int month_days(int yy, int mm)
88 {
89 	int days;
90 
91 	if (mm == 2)
92 		days = ISLEAP(yy) ? 29 : 28;
93 	else if (mm == 4 || mm == 6 || mm == 9 || mm == 11)
94 		days = 30;
95 	else
96 		days = 31;
97 
98 	return (days);
99 }
100 
101 static int week_day(int yy, int mm, int dd)
102 {
103 	int wd;
104 
105 	dd += mm < 3 ? yy-- : yy - 2;
106 	wd = 23*mm/9 + dd + 4 + yy/4 - yy/100 + yy/400;
107 	wd %= 7;
108 
109 	return (wd);
110 }
111 
112 static void
113 init_date(unsigned int *year, unsigned int *month, unsigned int *day, int *yy,
114     int *mm, int *dd)
115 {
116 	*yy = MIN(*year, (unsigned int)maxyear);
117 	if (*yy < minyear)
118 		*yy = minyear;
119 	*mm = MIN(*month, 12);
120 	if (*mm == 0)
121 		*mm = 1;
122 	*dd = (*day == 0) ? 1 : *day;
123 	if(*dd > month_days(*yy, *mm))
124 		*dd = month_days(*yy, *mm);
125 }
126 
127 static void datectl(enum operation op, int *yy, int *mm, int *dd)
128 {
129 	int ndays;
130 
131 	ndays = month_days(*yy, *mm);
132 
133 	switch (op) {
134 	case UP_DAY:
135 		if (*dd > 7)
136 			*dd -= 7;
137 		else {
138 			if (*mm == 1) {
139 				*yy -= 1;
140 				*mm = 12;
141 			} else
142 				*mm -= 1;
143 			ndays = month_days(*yy, *mm);
144 			*dd = ndays - abs(7 - *dd);
145 		}
146 		break;
147 	case DOWN_DAY:
148 		if (*dd + 7 < ndays)
149 			*dd += 7;
150 		else {
151 			if (*mm == 12) {
152 				*yy += 1;
153 				*mm = 1;
154 			} else
155 				*mm += 1;
156 			*dd = *dd + 7 - ndays;
157 		}
158 		break;
159 	case LEFT_DAY:
160 		if (*dd > 1)
161 			*dd -= 1;
162 		else {
163 			if (*mm == 1) {
164 				*yy -= 1;
165 				*mm = 12;
166 			} else
167 				*mm -= 1;
168 			*dd = month_days(*yy, *mm);
169 		}
170 		break;
171 	case RIGHT_DAY:
172 		if (*dd < ndays)
173 			*dd += 1;
174 		else {
175 			if (*mm == 12) {
176 				*yy += 1;
177 				*mm = 1;
178 			} else
179 				*mm += 1;
180 			*dd = 1;
181 		}
182 		break;
183 	case UP_MONTH:
184 		if (*mm == 1) {
185 			*mm = 12;
186 			*yy -= 1;
187 		} else
188 			*mm -= 1;
189 		ndays = month_days(*yy, *mm);
190 		if (*dd > ndays)
191 			*dd = ndays;
192 		break;
193 	case DOWN_MONTH:
194 		if (*mm == 12) {
195 			*mm = 1;
196 			*yy += 1;
197 		} else
198 			*mm += 1;
199 		ndays = month_days(*yy, *mm);
200 		if (*dd > ndays)
201 			*dd = ndays;
202 		break;
203 	case UP_YEAR:
204 		*yy -= 1;
205 		ndays = month_days(*yy, *mm);
206 		if (*dd > ndays)
207 			*dd = ndays;
208 		break;
209 	case DOWN_YEAR:
210 		*yy += 1;
211 		ndays = month_days(*yy, *mm);
212 		if (*dd > ndays)
213 			*dd = ndays;
214 		break;
215 	}
216 
217 	if (*yy < minyear) {
218 		*yy = minyear;
219 		*mm = 1;
220 		*dd = 1;
221 	}
222 	if (*yy > maxyear) {
223 		*yy = maxyear;
224 		*mm = 12;
225 		*dd = 31;
226 	}
227 }
228 
229 static void
230 drawsquare(struct bsddialog_conf *conf, WINDOW *win, enum elevation elev,
231     const char *fmt, int value, bool focus)
232 {
233 	int h, l, w;
234 
235 	getmaxyx(win, h, w);
236 	draw_borders(conf, win, elev);
237 	if (focus) {
238 		l = 2 + w%2;
239 		wattron(win, t.dialog.arrowcolor);
240 		mvwhline(win, 0, w/2 - l/2,
241 		    conf->ascii_lines ? '^' : ACS_UARROW, l);
242 		mvwhline(win, h-1, w/2 - l/2,
243 		    conf->ascii_lines ? 'v' : ACS_DARROW, l);
244 		wattroff(win, t.dialog.arrowcolor);
245 	}
246 
247 	if (focus)
248 		wattron(win, t.menu.f_namecolor);
249 	if (strchr(fmt, 's') != NULL)
250 		mvwprintw(win, 1, 1, fmt, m[value - 1]);
251 	else
252 		mvwprintw(win, 1, 1, fmt, value);
253 	if (focus)
254 		wattroff(win, t.menu.f_namecolor);
255 
256 	wnoutrefresh(win);
257 }
258 
259 static void
260 print_calendar(struct bsddialog_conf *conf, WINDOW *win, int yy, int mm, int dd,
261     bool active)
262 {
263 	int ndays, i, y, x, wd, h, w;
264 
265 	getmaxyx(win, h, w);
266 	wclear(win);
267 	draw_borders(conf, win, RAISED);
268 	if (active) {
269 		wattron(win, t.dialog.arrowcolor);
270 		mvwhline(win, 0, 15, conf->ascii_lines ? '^' : ACS_UARROW, 4);
271 		mvwhline(win, h-1, 15, conf->ascii_lines ? 'v' : ACS_DARROW, 4);
272 		mvwvline(win, 3, 0, conf->ascii_lines ? '<' : ACS_LARROW, 3);
273 		mvwvline(win, 3, w-1, conf->ascii_lines ? '>' : ACS_RARROW, 3);
274 		wattroff(win, t.dialog.arrowcolor);
275 	}
276 
277 	mvwaddstr(win, 1, 5, "Sun Mon Tue Wed Thu Fri Sat");
278 	ndays = month_days(yy, mm);
279 	y = 2;
280 	wd = week_day(yy, mm, 1);
281 	for (i = 1; i <= ndays; i++) {
282 		x = 5 + (4 * wd); /* x has to be 6 with week number */
283 		wmove(win, y, x);
284 		mvwprintw(win, y, x, "%2d", i);
285 		if (i == dd) {
286 			wattron(win, t.menu.f_namecolor);
287 			mvwprintw(win, y, x, "%2d", i);
288 			wattroff(win, t.menu.f_namecolor);
289 		}
290 		wd++;
291 		if (wd > 6) {
292 			wd = 0;
293 			y++;
294 		}
295 	}
296 
297 	wnoutrefresh(win);
298 }
299 
300 static int
301 calendar_redraw(struct dialog *d, WINDOW *yy_win, WINDOW *mm_win,
302     WINDOW *dd_win)
303 {
304 	int ycal, xcal;
305 
306 	if (d->built) {
307 		hide_dialog(d);
308 		refresh(); /* Important for decreasing screen */
309 	}
310 	if (dialog_size_position(d, MINHCAL, MINWCAL, NULL) != 0)
311 		return (BSDDIALOG_ERROR);
312 	if (draw_dialog(d) != 0)
313 		return (BSDDIALOG_ERROR);
314 	if (d->built)
315 		refresh(); /* Important to fix grey lines expanding screen */
316 	TEXTPAD(d, MINHCAL + HBUTTONS);
317 
318 	ycal = d->y + d->h - 15;
319 	xcal = d->x + d->w/2 - 17;
320 	mvwaddstr(d->widget, d->h - 16, d->w/2 - 17, "Month");
321 	update_box(d->conf, mm_win, ycal, xcal, 3, 17, RAISED);
322 	mvwaddstr(d->widget, d->h - 16, d->w/2, "Year");
323 	update_box(d->conf, yy_win, ycal, xcal + 17, 3, 17, RAISED);
324 	update_box(d->conf, dd_win, ycal + 3, xcal, 9, 34, RAISED);
325 	wnoutrefresh(d->widget);
326 
327 	return (0);
328 }
329 
330 int
331 bsddialog_calendar(struct bsddialog_conf *conf, const char *text, int rows,
332     int cols, unsigned int *year, unsigned int *month, unsigned int *day)
333 {
334 	bool loop, focusbuttons;
335 	int retval, sel, yy, mm, dd;
336 	wint_t input;
337 	WINDOW *yy_win, *mm_win, *dd_win;
338 	struct dialog d;
339 
340 	CHECK_PTR(year);
341 	CHECK_PTR(month);
342 	CHECK_PTR(day);
343 	minyear = MIN_YEAR_CAL;
344 	maxyear = MAX_YEAR_CAL;
345 	init_date(year, month, day, &yy, &mm, &dd);
346 
347 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
348 		return (BSDDIALOG_ERROR);
349 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
350 	if ((yy_win = newwin(1, 1, 1, 1)) == NULL)
351 		RETURN_ERROR("Cannot build WINDOW for yy");
352 	wbkgd(yy_win, t.dialog.color);
353 	if ((mm_win = newwin(1, 1, 1, 1)) == NULL)
354 		RETURN_ERROR("Cannot build WINDOW for mm");
355 	wbkgd(mm_win, t.dialog.color);
356 	if ((dd_win = newwin(1, 1, 1, 1)) == NULL)
357 		RETURN_ERROR("Cannot build WINDOW for dd");
358 	wbkgd(dd_win, t.dialog.color);
359 	if (calendar_redraw(&d, yy_win, mm_win, dd_win) != 0)
360 		return (BSDDIALOG_ERROR);
361 
362 	sel = -1;
363 	loop = focusbuttons = true;
364 	while (loop) {
365 		drawsquare(conf, mm_win, RAISED, "%15s", mm, sel == 0);
366 		drawsquare(conf, yy_win, RAISED, "%15d", yy, sel == 1);
367 		print_calendar(conf, dd_win, yy, mm, dd, sel == 2);
368 		doupdate();
369 
370 		if (get_wch(&input) == ERR)
371 			continue;
372 		switch(input) {
373 		case KEY_ENTER:
374 		case 10: /* Enter */
375 			if (focusbuttons || conf->button.always_active) {
376 				retval = BUTTONVALUE(d.bs);
377 				loop = false;
378 			}
379 			break;
380 		case 27: /* Esc */
381 			if (conf->key.enable_esc) {
382 				retval = BSDDIALOG_ESC;
383 				loop = false;
384 			}
385 			break;
386 		case '\t': /* TAB */
387 			if (focusbuttons) {
388 				d.bs.curr++;
389 				if (d.bs.curr >= (int)d.bs.nbuttons) {
390 					focusbuttons = false;
391 					sel = 0;
392 					d.bs.curr = conf->button.always_active ?
393 					    0 : -1;
394 				}
395 			} else {
396 				sel++;
397 				if (sel > 2) {
398 					focusbuttons = true;
399 					sel = -1;
400 					d.bs.curr = 0;
401 				}
402 			}
403 			DRAW_BUTTONS(d);
404 			break;
405 		case KEY_RIGHT:
406 			if (focusbuttons) {
407 				d.bs.curr++;
408 				if (d.bs.curr >= (int)d.bs.nbuttons) {
409 					focusbuttons = false;
410 					sel = 0;
411 					d.bs.curr = conf->button.always_active ?
412 					    0 : -1;
413 				}
414 			} else if (sel == 2) {
415 				datectl(RIGHT_DAY, &yy, &mm, &dd);
416 			} else { /* Month or Year*/
417 				sel++;
418 			}
419 			DRAW_BUTTONS(d);
420 			break;
421 		case KEY_LEFT:
422 			if (focusbuttons) {
423 				d.bs.curr--;
424 				if (d.bs.curr < 0) {
425 					focusbuttons = false;
426 					sel = 2;
427 					d.bs.curr = conf->button.always_active ?
428 					    0 : -1;
429 				}
430 			} else if (sel == 2) {
431 				datectl(LEFT_DAY, &yy, &mm, &dd);
432 			} else if (sel == 1) {
433 				sel = 0;
434 			} else { /* sel = 0, Month */
435 				focusbuttons = true;
436 				sel = -1;
437 				d.bs.curr = 0;
438 			}
439 			DRAW_BUTTONS(d);
440 			break;
441 		case KEY_UP:
442 			if (focusbuttons) {
443 				sel = 2;
444 				focusbuttons = false;
445 				d.bs.curr = conf->button.always_active ? 0 : -1;
446 				DRAW_BUTTONS(d);
447 			} else if (sel == 0) {
448 				datectl(UP_MONTH, &yy, &mm, &dd);
449 			} else if (sel == 1) {
450 				datectl(UP_YEAR, &yy, &mm, &dd);
451 			} else { /* sel = 2 */
452 				datectl(UP_DAY, &yy, &mm, &dd);
453 			}
454 			break;
455 		case KEY_DOWN:
456 			if (focusbuttons) {
457 				break;
458 			} else if (sel == 0) {
459 				datectl(DOWN_MONTH, &yy, &mm, &dd);
460 			} else if (sel == 1) {
461 				datectl(DOWN_YEAR, &yy, &mm, &dd);
462 			} else { /* sel = 2 */
463 				datectl(DOWN_DAY, &yy, &mm, &dd);
464 			}
465 			break;
466 		case KEY_HOME:
467 			datectl(UP_MONTH, &yy, &mm, &dd);
468 			break;
469 		case KEY_END:
470 			datectl(DOWN_MONTH, &yy, &mm, &dd);
471 			break;
472 		case KEY_PPAGE:
473 			datectl(UP_YEAR, &yy, &mm, &dd);
474 			break;
475 		case KEY_NPAGE:
476 			datectl(DOWN_YEAR, &yy, &mm, &dd);
477 			break;
478 		case KEY_F(1):
479 			if (conf->key.f1_file == NULL &&
480 			    conf->key.f1_message == NULL)
481 				break;
482 			if (f1help_dialog(conf) != 0)
483 				return (BSDDIALOG_ERROR);
484 			if (calendar_redraw(&d, yy_win, mm_win, dd_win) != 0)
485 				return (BSDDIALOG_ERROR);
486 			break;
487 		case KEY_RESIZE:
488 			if (calendar_redraw(&d, yy_win, mm_win, dd_win) != 0)
489 				return (BSDDIALOG_ERROR);
490 			break;
491 		default:
492 			if (shortcut_buttons(input, &d.bs)) {
493 				DRAW_BUTTONS(d);
494 				doupdate();
495 				retval = BUTTONVALUE(d.bs);
496 				loop = false;
497 			}
498 		}
499 	}
500 
501 	*year  = yy;
502 	*month = mm;
503 	*day   = dd;
504 
505 	delwin(yy_win);
506 	delwin(mm_win);
507 	delwin(dd_win);
508 	end_dialog(&d);
509 
510 	return (retval);
511 }
512 
513 static int datebox_redraw(struct dialog *d, struct dateitem *di)
514 {
515 	int y, x;
516 
517 	if (d->built) {
518 		hide_dialog(d);
519 		refresh(); /* Important for decreasing screen */
520 	}
521 	if (dialog_size_position(d, 3 /*windows*/, MINWDATE, NULL) != 0)
522 		return (BSDDIALOG_ERROR);
523 	if (draw_dialog(d) != 0)
524 		return (BSDDIALOG_ERROR);
525 	if (d->built)
526 		refresh(); /* Important to fix grey lines expanding screen */
527 	TEXTPAD(d, 3 /*windows*/ + HBUTTONS);
528 
529 	y = d->y + d->h - 6;
530 	x = (d->x + d->w / 2) - 11;
531 	update_box(d->conf, di[0].win, y, x, 3, di[0].width, LOWERED);
532 	mvwaddch(d->widget, d->h - 5, x - d->x + di[0].width, '/');
533 	x += di[0].width + 1;
534 	update_box(d->conf, di[1].win, y, x , 3, di[1].width, LOWERED);
535 	mvwaddch(d->widget, d->h - 5, x - d->x + di[1].width, '/');
536 	x += di[1].width + 1;
537 	update_box(d->conf, di[2].win, y, x, 3, di[2].width, LOWERED);
538 	wnoutrefresh(d->widget);
539 
540 	return (0);
541 }
542 
543 static int
544 build_dateitem(const char *format, int *yy, int *mm, int *dd,
545     struct dateitem *dt)
546 {
547 	int i;
548 	wchar_t *wformat;
549 	struct dateitem init[3] = {
550 		{UP_YEAR,  DOWN_YEAR,  NULL, 6,  "%4d",  yy},
551 		{UP_MONTH, DOWN_MONTH, NULL, 11, "%9s",  mm},
552 		{LEFT_DAY, RIGHT_DAY,  NULL, 4,  "%02d", dd},
553 	};
554 
555 	for (i = 0; i < 3; i++) {
556 		if ((init[i].win = newwin(1, 1, 1, 1)) == NULL)
557 			RETURN_FMTERROR("Cannot build WINDOW dateitem[%d]", i);
558 		wbkgd(init[i].win, t.dialog.color);
559 	}
560 
561 	if ((wformat = alloc_mbstows(CHECK_STR(format))) == NULL)
562 		RETURN_ERROR("Cannot allocate conf.date.format in wchar_t*");
563 	if (format == NULL || wcscmp(wformat, L"d/m/y") == 0) {
564 		dt[0] = init[2];
565 		dt[1] = init[1];
566 		dt[2] = init[0];
567 	} else if (wcscmp(wformat, L"m/d/y") == 0) {
568 		dt[0] = init[1];
569 		dt[1] = init[2];
570 		dt[2] = init[0];
571 	} else if (wcscmp(wformat, L"y/m/d") == 0) {
572 		dt[0] = init[0];
573 		dt[1] = init[1];
574 		dt[2] = init[2];
575 	} else
576 		RETURN_FMTERROR("Invalid conf.date.format=\"%s\"", format);
577 	free(wformat);
578 
579 	return (0);
580 }
581 
582 int
583 bsddialog_datebox(struct bsddialog_conf *conf, const char *text, int rows,
584     int cols, unsigned int *year, unsigned int *month, unsigned int *day)
585 {
586 	bool loop, focusbuttons;
587 	int retval, i, sel, yy, mm, dd;
588 	wint_t input;
589 	struct dateitem  di[3];
590 	struct dialog d;
591 
592 	CHECK_PTR(year);
593 	CHECK_PTR(month);
594 	CHECK_PTR(day);
595 	minyear = MIN_YEAR_DATE;
596 	maxyear = MAX_YEAR_DATE;
597 	init_date(year, month, day, &yy, &mm, &dd);
598 
599 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
600 		return (BSDDIALOG_ERROR);
601 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
602 	if (build_dateitem(conf->date.format, &yy, &mm, &dd, di) != 0)
603 		return (BSDDIALOG_ERROR);
604 	if (datebox_redraw(&d, di) != 0)
605 		return (BSDDIALOG_ERROR);
606 
607 	sel = -1;
608 	loop = focusbuttons = true;
609 	while (loop) {
610 		for (i = 0; i < 3; i++)
611 			drawsquare(conf, di[i].win, LOWERED, di[i].fmt,
612 			    *di[i].value, sel == i);
613 		doupdate();
614 
615 		if (get_wch(&input) == ERR)
616 			continue;
617 		switch(input) {
618 		case KEY_ENTER:
619 		case 10: /* Enter */
620 			if (focusbuttons || conf->button.always_active) {
621 				retval = BUTTONVALUE(d.bs);
622 				loop = false;
623 			}
624 			break;
625 		case 27: /* Esc */
626 			if (conf->key.enable_esc) {
627 				retval = BSDDIALOG_ESC;
628 				loop = false;
629 			}
630 			break;
631 		case KEY_RIGHT:
632 		case '\t': /* TAB */
633 			if (focusbuttons) {
634 				d.bs.curr++;
635 				focusbuttons = d.bs.curr < (int)d.bs.nbuttons ?
636 				    true : false;
637 				if (focusbuttons == false) {
638 					sel = 0;
639 					d.bs.curr = conf->button.always_active ?
640 					    0 : -1;
641 				}
642 			} else {
643 				sel++;
644 				focusbuttons = sel > 2 ? true : false;
645 				if (focusbuttons) {
646 					d.bs.curr = 0;
647 				}
648 			}
649 			DRAW_BUTTONS(d);
650 			break;
651 		case KEY_LEFT:
652 			if (focusbuttons) {
653 				d.bs.curr--;
654 				focusbuttons = d.bs.curr < 0 ? false : true;
655 				if (focusbuttons == false) {
656 					sel = 2;
657 					d.bs.curr = conf->button.always_active ?
658 					    0 : -1;
659 				}
660 			} else {
661 				sel--;
662 				focusbuttons = sel < 0 ? true : false;
663 				if (focusbuttons)
664 					d.bs.curr = (int)d.bs.nbuttons - 1;
665 			}
666 			DRAW_BUTTONS(d);
667 			break;
668 		case KEY_UP:
669 			if (focusbuttons) {
670 				sel = 0;
671 				focusbuttons = false;
672 				d.bs.curr = conf->button.always_active ? 0 : -1;
673 				DRAW_BUTTONS(d);
674 			} else {
675 				datectl(di[sel].up, &yy, &mm, &dd);
676 			}
677 			break;
678 		case KEY_DOWN:
679 			if (focusbuttons)
680 				break;
681 			datectl(di[sel].down, &yy, &mm, &dd);
682 			break;
683 		case KEY_F(1):
684 			if (conf->key.f1_file == NULL &&
685 			    conf->key.f1_message == NULL)
686 				break;
687 			if (f1help_dialog(conf) != 0)
688 				return (BSDDIALOG_ERROR);
689 			if (datebox_redraw(&d, di) != 0)
690 				return (BSDDIALOG_ERROR);
691 			break;
692 		case KEY_RESIZE:
693 			if (datebox_redraw(&d, di) != 0)
694 				return (BSDDIALOG_ERROR);
695 			break;
696 		default:
697 			if (shortcut_buttons(input, &d.bs)) {
698 				DRAW_BUTTONS(d);
699 				doupdate();
700 				retval = BUTTONVALUE(d.bs);
701 				loop = false;
702 			}
703 		}
704 	}
705 
706 	*year  = yy;
707 	*month = mm;
708 	*day   = dd;
709 
710 	for (i = 0; i < 3 ; i++)
711 		delwin(di[i].win);
712 	end_dialog(&d);
713 
714 	return (retval);
715 }