2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2022-2023 Alfonso Sabato Siciliano
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
32 #include "bsddialog.h"
33 #include "bsddialog_theme.h"
37 #define MIN_YEAR_CAL 0
38 #define MAX_YEAR_CAL 999999999
40 #define MINWCAL 36 /* 34 calendar, 1 + 1 margins */
42 #define MIN_YEAR_DATE 0
43 #define MAX_YEAR_DATE 9999
44 #define MINWDATE 23 /* 3 windows and their borders */
46 #define ISLEAP(year) ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
51 static const char *m[12] = {
77 /* private datebox item */
87 static int month_days(int yy, int mm)
92 days = ISLEAP(yy) ? 29 : 28;
93 else if (mm == 4 || mm == 6 || mm == 9 || mm == 11)
101 static int week_day(int yy, int mm, int dd)
105 dd += mm < 3 ? yy-- : yy - 2;
106 wd = 23*mm/9 + dd + 4 + yy/4 - yy/100 + yy/400;
113 init_date(unsigned int *year, unsigned int *month, unsigned int *day, int *yy,
116 *yy = MIN(*year, (unsigned int)maxyear);
119 *mm = MIN(*month, 12);
122 *dd = (*day == 0) ? 1 : *day;
123 if(*dd > month_days(*yy, *mm))
124 *dd = month_days(*yy, *mm);
127 static void datectl(enum operation op, int *yy, int *mm, int *dd)
131 ndays = month_days(*yy, *mm);
143 ndays = month_days(*yy, *mm);
144 *dd = ndays - abs(7 - *dd);
156 *dd = *dd + 7 - ndays;
168 *dd = month_days(*yy, *mm);
189 ndays = month_days(*yy, *mm);
199 ndays = month_days(*yy, *mm);
205 ndays = month_days(*yy, *mm);
211 ndays = month_days(*yy, *mm);
230 drawsquare(struct bsddialog_conf *conf, WINDOW *win, enum elevation elev,
231 const char *fmt, int value, bool focus)
236 draw_borders(conf, win, elev);
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);
248 wattron(win, t.menu.f_namecolor);
249 if (strchr(fmt, 's') != NULL)
250 mvwprintw(win, 1, 1, fmt, m[value - 1]);
252 mvwprintw(win, 1, 1, fmt, value);
254 wattroff(win, t.menu.f_namecolor);
260 print_calendar(struct bsddialog_conf *conf, WINDOW *win, int yy, int mm, int dd,
263 int ndays, i, y, x, wd, h, w;
267 draw_borders(conf, win, RAISED);
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);
277 mvwaddstr(win, 1, 5, "Sun Mon Tue Wed Thu Fri Sat");
278 ndays = month_days(yy, mm);
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 */
284 mvwprintw(win, y, x, "%2d", i);
286 wattron(win, t.menu.f_namecolor);
287 mvwprintw(win, y, x, "%2d", i);
288 wattroff(win, t.menu.f_namecolor);
301 calendar_redraw(struct dialog *d, WINDOW *yy_win, WINDOW *mm_win,
308 refresh(); /* Important for decreasing screen */
310 if (dialog_size_position(d, MINHCAL, MINWCAL, NULL) != 0)
311 return (BSDDIALOG_ERROR);
312 if (draw_dialog(d) != 0)
313 return (BSDDIALOG_ERROR);
315 refresh(); /* Important to fix grey lines expanding screen */
316 TEXTPAD(d, MINHCAL + HBUTTONS);
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);
331 bsddialog_calendar(struct bsddialog_conf *conf, const char *text, int rows,
332 int cols, unsigned int *year, unsigned int *month, unsigned int *day)
334 bool loop, focusbuttons;
335 int retval, sel, yy, mm, dd;
337 WINDOW *yy_win, *mm_win, *dd_win;
343 minyear = MIN_YEAR_CAL;
344 maxyear = MAX_YEAR_CAL;
345 init_date(year, month, day, &yy, &mm, &dd);
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);
363 loop = focusbuttons = true;
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);
370 if (get_wch(&input) == ERR)
375 if (focusbuttons || conf->button.always_active) {
376 retval = BUTTONVALUE(d.bs);
381 if (conf->key.enable_esc) {
382 retval = BSDDIALOG_ESC;
389 if (d.bs.curr >= (int)d.bs.nbuttons) {
390 focusbuttons = false;
392 d.bs.curr = conf->button.always_active ?
408 if (d.bs.curr >= (int)d.bs.nbuttons) {
409 focusbuttons = false;
411 d.bs.curr = conf->button.always_active ?
414 } else if (sel == 2) {
415 datectl(RIGHT_DAY, &yy, &mm, &dd);
416 } else { /* Month or Year*/
425 focusbuttons = false;
427 d.bs.curr = conf->button.always_active ?
430 } else if (sel == 2) {
431 datectl(LEFT_DAY, &yy, &mm, &dd);
432 } else if (sel == 1) {
434 } else { /* sel = 0, Month */
444 focusbuttons = false;
445 d.bs.curr = conf->button.always_active ? 0 : -1;
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);
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);
467 datectl(UP_MONTH, &yy, &mm, &dd);
470 datectl(DOWN_MONTH, &yy, &mm, &dd);
473 datectl(UP_YEAR, &yy, &mm, &dd);
476 datectl(DOWN_YEAR, &yy, &mm, &dd);
479 if (conf->key.f1_file == NULL &&
480 conf->key.f1_message == NULL)
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);
488 if (calendar_redraw(&d, yy_win, mm_win, dd_win) != 0)
489 return (BSDDIALOG_ERROR);
492 if (shortcut_buttons(input, &d.bs)) {
495 retval = BUTTONVALUE(d.bs);
513 static int datebox_redraw(struct dialog *d, struct dateitem *di)
519 refresh(); /* Important for decreasing screen */
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);
526 refresh(); /* Important to fix grey lines expanding screen */
527 TEXTPAD(d, 3 /*windows*/ + HBUTTONS);
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);
544 build_dateitem(const char *format, int *yy, int *mm, int *dd,
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},
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);
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) {
567 } else if (wcscmp(wformat, L"m/d/y") == 0) {
571 } else if (wcscmp(wformat, L"y/m/d") == 0) {
576 RETURN_FMTERROR("Invalid conf.date.format=\"%s\"", format);
583 bsddialog_datebox(struct bsddialog_conf *conf, const char *text, int rows,
584 int cols, unsigned int *year, unsigned int *month, unsigned int *day)
586 bool loop, focusbuttons;
587 int retval, i, sel, yy, mm, dd;
589 struct dateitem di[3];
595 minyear = MIN_YEAR_DATE;
596 maxyear = MAX_YEAR_DATE;
597 init_date(year, month, day, &yy, &mm, &dd);
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);
608 loop = focusbuttons = true;
610 for (i = 0; i < 3; i++)
611 drawsquare(conf, di[i].win, LOWERED, di[i].fmt,
612 *di[i].value, sel == i);
615 if (get_wch(&input) == ERR)
620 if (focusbuttons || conf->button.always_active) {
621 retval = BUTTONVALUE(d.bs);
626 if (conf->key.enable_esc) {
627 retval = BSDDIALOG_ESC;
635 focusbuttons = d.bs.curr < (int)d.bs.nbuttons ?
637 if (focusbuttons == false) {
639 d.bs.curr = conf->button.always_active ?
644 focusbuttons = sel > 2 ? true : false;
654 focusbuttons = d.bs.curr < 0 ? false : true;
655 if (focusbuttons == false) {
657 d.bs.curr = conf->button.always_active ?
662 focusbuttons = sel < 0 ? true : false;
664 d.bs.curr = (int)d.bs.nbuttons - 1;
671 focusbuttons = false;
672 d.bs.curr = conf->button.always_active ? 0 : -1;
675 datectl(di[sel].up, &yy, &mm, &dd);
681 datectl(di[sel].down, &yy, &mm, &dd);
684 if (conf->key.f1_file == NULL &&
685 conf->key.f1_message == NULL)
687 if (f1help_dialog(conf) != 0)
688 return (BSDDIALOG_ERROR);
689 if (datebox_redraw(&d, di) != 0)
690 return (BSDDIALOG_ERROR);
693 if (datebox_redraw(&d, di) != 0)
694 return (BSDDIALOG_ERROR);
697 if (shortcut_buttons(input, &d.bs)) {
700 retval = BUTTONVALUE(d.bs);
710 for (i = 0; i < 3 ; i++)