xref: /freebsd/contrib/bsddialog/lib/datebox.c (revision a90b9d0159070121c221b966469c3e36d912bf82)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022-2024 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, UARROW(conf), l);
241 		mvwhline(win, h-1, w/2 - l/2, DARROW(conf), l);
242 		wattroff(win, t.dialog.arrowcolor);
243 	}
244 
245 	if (focus)
246 		wattron(win, t.menu.f_namecolor);
247 	if (strchr(fmt, 's') != NULL)
248 		mvwprintw(win, 1, 1, fmt, m[value - 1]);
249 	else
250 		mvwprintw(win, 1, 1, fmt, value);
251 	if (focus)
252 		wattroff(win, t.menu.f_namecolor);
253 
254 	wnoutrefresh(win);
255 }
256 
257 static void
258 print_calendar(struct bsddialog_conf *conf, WINDOW *win, int yy, int mm, int dd,
259     bool active)
260 {
261 	int ndays, i, y, x, wd, h, w;
262 
263 	getmaxyx(win, h, w);
264 	wclear(win);
265 	draw_borders(conf, win, RAISED);
266 	if (active) {
267 		wattron(win, t.dialog.arrowcolor);
268 		mvwhline(win, 0, 15, UARROW(conf), 4);
269 		mvwhline(win, h-1, 15, DARROW(conf), 4);
270 		mvwvline(win, 3, 0, LARROW(conf), 3);
271 		mvwvline(win, 3, w-1, RARROW(conf), 3);
272 		wattroff(win, t.dialog.arrowcolor);
273 	}
274 
275 	mvwaddstr(win, 1, 5, "Sun Mon Tue Wed Thu Fri Sat");
276 	ndays = month_days(yy, mm);
277 	y = 2;
278 	wd = week_day(yy, mm, 1);
279 	for (i = 1; i <= ndays; i++) {
280 		x = 5 + (4 * wd); /* x has to be 6 with week number */
281 		wmove(win, y, x);
282 		mvwprintw(win, y, x, "%2d", i);
283 		if (i == dd) {
284 			wattron(win, t.menu.f_namecolor);
285 			mvwprintw(win, y, x, "%2d", i);
286 			wattroff(win, t.menu.f_namecolor);
287 		}
288 		wd++;
289 		if (wd > 6) {
290 			wd = 0;
291 			y++;
292 		}
293 	}
294 
295 	wnoutrefresh(win);
296 }
297 
298 static int
299 calendar_redraw(struct dialog *d, WINDOW *yy_win, WINDOW *mm_win,
300     WINDOW *dd_win)
301 {
302 	int ycal, xcal;
303 
304 	if (d->built) {
305 		hide_dialog(d);
306 		refresh(); /* Important for decreasing screen */
307 	}
308 	if (dialog_size_position(d, MINHCAL, MINWCAL, NULL) != 0)
309 		return (BSDDIALOG_ERROR);
310 	if (draw_dialog(d) != 0)
311 		return (BSDDIALOG_ERROR);
312 	if (d->built)
313 		refresh(); /* Important to fix grey lines expanding screen */
314 	TEXTPAD(d, MINHCAL + HBUTTONS);
315 
316 	ycal = d->y + d->h - 15;
317 	xcal = d->x + d->w/2 - 17;
318 	mvwaddstr(d->widget, d->h - 16, d->w/2 - 17, "Month");
319 	update_box(d->conf, mm_win, ycal, xcal, 3, 17, RAISED);
320 	mvwaddstr(d->widget, d->h - 16, d->w/2, "Year");
321 	update_box(d->conf, yy_win, ycal, xcal + 17, 3, 17, RAISED);
322 	update_box(d->conf, dd_win, ycal + 3, xcal, 9, 34, RAISED);
323 	wnoutrefresh(d->widget);
324 
325 	return (0);
326 }
327 
328 int
329 bsddialog_calendar(struct bsddialog_conf *conf, const char *text, int rows,
330     int cols, unsigned int *year, unsigned int *month, unsigned int *day)
331 {
332 	bool loop, focusbuttons;
333 	int retval, sel, yy, mm, dd;
334 	wint_t input;
335 	WINDOW *yy_win, *mm_win, *dd_win;
336 	struct dialog d;
337 
338 	CHECK_PTR(year);
339 	CHECK_PTR(month);
340 	CHECK_PTR(day);
341 	minyear = MIN_YEAR_CAL;
342 	maxyear = MAX_YEAR_CAL;
343 	init_date(year, month, day, &yy, &mm, &dd);
344 
345 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
346 		return (BSDDIALOG_ERROR);
347 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
348 	if ((yy_win = newwin(1, 1, 1, 1)) == NULL)
349 		RETURN_ERROR("Cannot build WINDOW for yy");
350 	wbkgd(yy_win, t.dialog.color);
351 	if ((mm_win = newwin(1, 1, 1, 1)) == NULL)
352 		RETURN_ERROR("Cannot build WINDOW for mm");
353 	wbkgd(mm_win, t.dialog.color);
354 	if ((dd_win = newwin(1, 1, 1, 1)) == NULL)
355 		RETURN_ERROR("Cannot build WINDOW for dd");
356 	wbkgd(dd_win, t.dialog.color);
357 	if (calendar_redraw(&d, yy_win, mm_win, dd_win) != 0)
358 		return (BSDDIALOG_ERROR);
359 
360 	sel = -1;
361 	loop = focusbuttons = true;
362 	while (loop) {
363 		drawsquare(conf, mm_win, RAISED, "%15s", mm, sel == 0);
364 		drawsquare(conf, yy_win, RAISED, "%15d", yy, sel == 1);
365 		print_calendar(conf, dd_win, yy, mm, dd, sel == 2);
366 		doupdate();
367 
368 		if (get_wch(&input) == ERR)
369 			continue;
370 		switch(input) {
371 		case KEY_ENTER:
372 		case 10: /* Enter */
373 			if (focusbuttons || conf->button.always_active) {
374 				retval = BUTTONVALUE(d.bs);
375 				loop = false;
376 			}
377 			break;
378 		case 27: /* Esc */
379 			if (conf->key.enable_esc) {
380 				retval = BSDDIALOG_ESC;
381 				loop = false;
382 			}
383 			break;
384 		case '\t': /* TAB */
385 			if (focusbuttons) {
386 				d.bs.curr++;
387 				if (d.bs.curr >= (int)d.bs.nbuttons) {
388 					focusbuttons = false;
389 					sel = 0;
390 					d.bs.curr = conf->button.always_active ?
391 					    0 : -1;
392 				}
393 			} else {
394 				sel++;
395 				if (sel > 2) {
396 					focusbuttons = true;
397 					sel = -1;
398 					d.bs.curr = 0;
399 				}
400 			}
401 			DRAW_BUTTONS(d);
402 			break;
403 		case KEY_CTRL('n'):
404 		case KEY_RIGHT:
405 			if (focusbuttons) {
406 				d.bs.curr++;
407 				if (d.bs.curr >= (int)d.bs.nbuttons) {
408 					focusbuttons = false;
409 					sel = 0;
410 					d.bs.curr = conf->button.always_active ?
411 					    0 : -1;
412 				}
413 			} else if (sel == 2) {
414 				datectl(RIGHT_DAY, &yy, &mm, &dd);
415 			} else { /* Month or Year*/
416 				sel++;
417 			}
418 			DRAW_BUTTONS(d);
419 			break;
420 		case KEY_CTRL('p'):
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 '-':
467 			if (focusbuttons) {
468 				break;
469 			} else if (sel == 0) {
470 				datectl(UP_MONTH, &yy, &mm, &dd);
471 			} else if (sel == 1) {
472 				datectl(UP_YEAR, &yy, &mm, &dd);
473 			} else { /* sel = 2 */
474 				datectl(LEFT_DAY, &yy, &mm, &dd);
475 			}
476 			break;
477 		case '+':
478 			if (focusbuttons) {
479 				break;
480 			} else if (sel == 0) {
481 				datectl(DOWN_MONTH, &yy, &mm, &dd);
482 			} else if (sel == 1) {
483 				datectl(DOWN_YEAR, &yy, &mm, &dd);
484 			} else { /* sel = 2 */
485 				datectl(RIGHT_DAY, &yy, &mm, &dd);
486 			}
487 			break;
488 		case KEY_HOME:
489 			datectl(UP_MONTH, &yy, &mm, &dd);
490 			break;
491 		case KEY_END:
492 			datectl(DOWN_MONTH, &yy, &mm, &dd);
493 			break;
494 		case KEY_PPAGE:
495 			datectl(UP_YEAR, &yy, &mm, &dd);
496 			break;
497 		case KEY_NPAGE:
498 			datectl(DOWN_YEAR, &yy, &mm, &dd);
499 			break;
500 		case KEY_F(1):
501 			if (conf->key.f1_file == NULL &&
502 			    conf->key.f1_message == NULL)
503 				break;
504 			if (f1help_dialog(conf) != 0)
505 				return (BSDDIALOG_ERROR);
506 			if (calendar_redraw(&d, yy_win, mm_win, dd_win) != 0)
507 				return (BSDDIALOG_ERROR);
508 			break;
509 		case KEY_CTRL('l'):
510 		case KEY_RESIZE:
511 			if (calendar_redraw(&d, yy_win, mm_win, dd_win) != 0)
512 				return (BSDDIALOG_ERROR);
513 			break;
514 		default:
515 			if (shortcut_buttons(input, &d.bs)) {
516 				DRAW_BUTTONS(d);
517 				doupdate();
518 				retval = BUTTONVALUE(d.bs);
519 				loop = false;
520 			}
521 		}
522 	}
523 
524 	*year  = yy;
525 	*month = mm;
526 	*day   = dd;
527 
528 	delwin(yy_win);
529 	delwin(mm_win);
530 	delwin(dd_win);
531 	end_dialog(&d);
532 
533 	return (retval);
534 }
535 
536 static int datebox_redraw(struct dialog *d, struct dateitem *di)
537 {
538 	int y, x;
539 
540 	if (d->built) {
541 		hide_dialog(d);
542 		refresh(); /* Important for decreasing screen */
543 	}
544 	if (dialog_size_position(d, 3 /*windows*/, MINWDATE, NULL) != 0)
545 		return (BSDDIALOG_ERROR);
546 	if (draw_dialog(d) != 0)
547 		return (BSDDIALOG_ERROR);
548 	if (d->built)
549 		refresh(); /* Important to fix grey lines expanding screen */
550 	TEXTPAD(d, 3 /*windows*/ + HBUTTONS);
551 
552 	y = d->y + d->h - 6;
553 	x = (d->x + d->w / 2) - 11;
554 	update_box(d->conf, di[0].win, y, x, 3, di[0].width, LOWERED);
555 	mvwaddch(d->widget, d->h - 5, x - d->x + di[0].width, '/');
556 	x += di[0].width + 1;
557 	update_box(d->conf, di[1].win, y, x , 3, di[1].width, LOWERED);
558 	mvwaddch(d->widget, d->h - 5, x - d->x + di[1].width, '/');
559 	x += di[1].width + 1;
560 	update_box(d->conf, di[2].win, y, x, 3, di[2].width, LOWERED);
561 	wnoutrefresh(d->widget);
562 
563 	return (0);
564 }
565 
566 static int
567 build_dateitem(const char *format, int *yy, int *mm, int *dd,
568     struct dateitem *dt)
569 {
570 	int i;
571 	wchar_t *wformat;
572 	struct dateitem init[3] = {
573 		{UP_YEAR,  DOWN_YEAR,  NULL, 6,  "%4d",  yy},
574 		{UP_MONTH, DOWN_MONTH, NULL, 11, "%9s",  mm},
575 		{LEFT_DAY, RIGHT_DAY,  NULL, 4,  "%02d", dd},
576 	};
577 
578 	for (i = 0; i < 3; i++) {
579 		if ((init[i].win = newwin(1, 1, 1, 1)) == NULL)
580 			RETURN_FMTERROR("Cannot build WINDOW dateitem[%d]", i);
581 		wbkgd(init[i].win, t.dialog.color);
582 	}
583 
584 	if ((wformat = alloc_mbstows(CHECK_STR(format))) == NULL)
585 		RETURN_ERROR("Cannot allocate conf.date.format in wchar_t*");
586 	if (format == NULL || wcscmp(wformat, L"d/m/y") == 0) {
587 		dt[0] = init[2];
588 		dt[1] = init[1];
589 		dt[2] = init[0];
590 	} else if (wcscmp(wformat, L"m/d/y") == 0) {
591 		dt[0] = init[1];
592 		dt[1] = init[2];
593 		dt[2] = init[0];
594 	} else if (wcscmp(wformat, L"y/m/d") == 0) {
595 		dt[0] = init[0];
596 		dt[1] = init[1];
597 		dt[2] = init[2];
598 	} else
599 		RETURN_FMTERROR("Invalid conf.date.format=\"%s\"", format);
600 	free(wformat);
601 
602 	return (0);
603 }
604 
605 int
606 bsddialog_datebox(struct bsddialog_conf *conf, const char *text, int rows,
607     int cols, unsigned int *year, unsigned int *month, unsigned int *day)
608 {
609 	bool loop, focusbuttons;
610 	int retval, i, sel, yy, mm, dd;
611 	wint_t input;
612 	struct dateitem  di[3];
613 	struct dialog d;
614 
615 	CHECK_PTR(year);
616 	CHECK_PTR(month);
617 	CHECK_PTR(day);
618 	minyear = MIN_YEAR_DATE;
619 	maxyear = MAX_YEAR_DATE;
620 	init_date(year, month, day, &yy, &mm, &dd);
621 
622 	if (prepare_dialog(conf, text, rows, cols, &d) != 0)
623 		return (BSDDIALOG_ERROR);
624 	set_buttons(&d, true, OK_LABEL, CANCEL_LABEL);
625 	if (build_dateitem(conf->date.format, &yy, &mm, &dd, di) != 0)
626 		return (BSDDIALOG_ERROR);
627 	if (datebox_redraw(&d, di) != 0)
628 		return (BSDDIALOG_ERROR);
629 
630 	sel = -1;
631 	loop = focusbuttons = true;
632 	while (loop) {
633 		for (i = 0; i < 3; i++)
634 			drawsquare(conf, di[i].win, LOWERED, di[i].fmt,
635 			    *di[i].value, sel == i);
636 		doupdate();
637 
638 		if (get_wch(&input) == ERR)
639 			continue;
640 		switch(input) {
641 		case KEY_ENTER:
642 		case 10: /* Enter */
643 			if (focusbuttons || conf->button.always_active) {
644 				retval = BUTTONVALUE(d.bs);
645 				loop = false;
646 			}
647 			break;
648 		case 27: /* Esc */
649 			if (conf->key.enable_esc) {
650 				retval = BSDDIALOG_ESC;
651 				loop = false;
652 			}
653 			break;
654 		case '\t': /* TAB */
655 		case KEY_CTRL('n'):
656 		case KEY_RIGHT:
657 			if (focusbuttons) {
658 				d.bs.curr++;
659 				focusbuttons = d.bs.curr < (int)d.bs.nbuttons ?
660 				    true : false;
661 				if (focusbuttons == false) {
662 					sel = 0;
663 					d.bs.curr = conf->button.always_active ?
664 					    0 : -1;
665 				}
666 			} else {
667 				sel++;
668 				focusbuttons = sel > 2 ? true : false;
669 				if (focusbuttons) {
670 					d.bs.curr = 0;
671 				}
672 			}
673 			DRAW_BUTTONS(d);
674 			break;
675 		case KEY_CTRL('p'):
676 		case KEY_LEFT:
677 			if (focusbuttons) {
678 				d.bs.curr--;
679 				focusbuttons = d.bs.curr < 0 ? false : true;
680 				if (focusbuttons == false) {
681 					sel = 2;
682 					d.bs.curr = conf->button.always_active ?
683 					    0 : -1;
684 				}
685 			} else {
686 				sel--;
687 				focusbuttons = sel < 0 ? true : false;
688 				if (focusbuttons)
689 					d.bs.curr = (int)d.bs.nbuttons - 1;
690 			}
691 			DRAW_BUTTONS(d);
692 			break;
693 		case '-':
694 			if (focusbuttons == false)
695 				datectl(di[sel].up, &yy, &mm, &dd);
696 			break;
697 		case KEY_UP:
698 			if (focusbuttons) {
699 				sel = 0;
700 				focusbuttons = false;
701 				d.bs.curr = conf->button.always_active ? 0 : -1;
702 				DRAW_BUTTONS(d);
703 			} else {
704 				datectl(di[sel].up, &yy, &mm, &dd);
705 			}
706 			break;
707 		case '+':
708 		case KEY_DOWN:
709 			if (focusbuttons)
710 				break;
711 			datectl(di[sel].down, &yy, &mm, &dd);
712 			break;
713 		case KEY_F(1):
714 			if (conf->key.f1_file == NULL &&
715 			    conf->key.f1_message == NULL)
716 				break;
717 			if (f1help_dialog(conf) != 0)
718 				return (BSDDIALOG_ERROR);
719 			if (datebox_redraw(&d, di) != 0)
720 				return (BSDDIALOG_ERROR);
721 			break;
722 		case KEY_CTRL('l'):
723 		case KEY_RESIZE:
724 			if (datebox_redraw(&d, di) != 0)
725 				return (BSDDIALOG_ERROR);
726 			break;
727 		default:
728 			if (shortcut_buttons(input, &d.bs)) {
729 				DRAW_BUTTONS(d);
730 				doupdate();
731 				retval = BUTTONVALUE(d.bs);
732 				loop = false;
733 			}
734 		}
735 	}
736 
737 	*year  = yy;
738 	*month = mm;
739 	*day   = dd;
740 
741 	for (i = 0; i < 3 ; i++)
742 		delwin(di[i].win);
743 	end_dialog(&d);
744 
745 	return (retval);
746 }