freebsd-dev/contrib/bsddialog/lib/calendarbox.c
Alfonso S. Siciliano 84823cc708
contrib/bsddialog: Import version 0.4
Improvements and changes to integrate bsddialog(1) with scripts in BASE.
Overview:

 * New options. --and-widget, --keep-tite, --calendar.
 * Change output format. Menus and --print-maxsize.
 * Redefine sizing. Fixed rows, cols and menurows became at the most.
 * Add DIAGNOSTICS. Error messages for bad arguments and options.
 * Add keys. Space for --menu, fast keys for --msgbox and --yesno.
 * Text. Change default text modification, add --cr-wrap.

See /usr/src/contrib/bsddialog/CHANGELOG '2022-09-24 Version 0.4'
for more detailed information.

Merge commit '9f24fda5a8e7ab8243e71473c7e2dc98b4877e64'
2022-09-25 15:09:16 +02:00

520 lines
12 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2022 Alfonso Sabato Siciliano
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include "bsddialog.h"
#include "bsddialog_theme.h"
#include "lib_util.h"
#define MINHCAL 13
#define MINWCAL 36 /* 34 calendar, 1 + 1 margins */
#define MINYEAR 1900
#define MAXYEAR 999999999
static int month_days(int yy, int mm)
{
int days;
if (mm == 2)
days = ISLEAP(yy) ? 29 : 28;
else if (mm == 4 || mm == 6 || mm == 9 || mm == 11)
days = 30;
else
days = 31;
return (days);
}
enum operation {
UP_DAY,
DOWN_DAY,
LEFT_DAY,
RIGHT_DAY,
UP_MONTH,
DOWN_MONTH,
UP_YEAR,
DOWN_YEAR
};
static void datectl(enum operation op, int *yy, int *mm, int *dd)
{
int ndays;
ndays = month_days(*yy, *mm);
switch (op) {
case UP_DAY:
if (*dd > 7)
*dd -= 7;
else {
if (*mm == 1) {
*yy -= 1;
*mm = 12;
} else
*mm -= 1;
ndays = month_days(*yy, *mm);
*dd = ndays - abs(7 - *dd);
}
break;
case DOWN_DAY:
if (*dd + 7 < ndays)
*dd += 7;
else {
if (*mm == 12) {
*yy += 1;
*mm = 1;
} else
*mm += 1;
*dd = *dd + 7 - ndays;
}
break;
case LEFT_DAY:
if (*dd > 1)
*dd -= 1;
else {
if (*mm == 1) {
*yy -= 1;
*mm = 12;
} else
*mm -= 1;
*dd = month_days(*yy, *mm);
}
break;
case RIGHT_DAY:
if (*dd < ndays)
*dd += 1;
else {
if (*mm == 12) {
*yy += 1;
*mm = 1;
} else
*mm += 1;
*dd = 1;
}
break;
case UP_MONTH:
if (*mm == 1) {
*mm = 12;
*yy -= 1;
} else
*mm -= 1;
ndays = month_days(*yy, *mm);
if (*dd > ndays)
*dd = ndays;
break;
case DOWN_MONTH:
if (*mm == 12) {
*mm = 1;
*yy += 1;
} else
*mm += 1;
ndays = month_days(*yy, *mm);
if (*dd > ndays)
*dd = ndays;
break;
case UP_YEAR:
*yy -= 1;
ndays = month_days(*yy, *mm);
if (*dd > ndays)
*dd = ndays;
break;
case DOWN_YEAR:
*yy += 1;
ndays = month_days(*yy, *mm);
if (*dd > ndays)
*dd = ndays;
break;
}
if (*yy < MINYEAR) {
*yy = MINYEAR;
*mm = 1;
*dd = 1;
}
if (*yy > MAXYEAR) {
*yy = MAXYEAR;
*mm = 12;
*dd = 31;
}
}
static int week_day(int yy, int mm, int dd)
{
int wd;
dd += mm < 3 ? yy-- : yy - 2;
wd = 23*mm/9 + dd + 4 + yy/4 - yy/100 + yy/400;
wd %= 7;
return (wd);
}
static void
print_calendar(struct bsddialog_conf *conf, WINDOW *win, int yy, int mm, int dd,
bool active)
{
int ndays, i, y, x, wd, h, w;
getmaxyx(win, h, w);
wclear(win);
draw_borders(conf, win, h, w, RAISED);
if (active) {
wattron(win, t.dialog.arrowcolor);
mvwhline(win, 0, 15, conf->ascii_lines ? '^' : ACS_UARROW, 4);
mvwhline(win, h-1, 15, conf->ascii_lines ? 'v' : ACS_DARROW, 4);
mvwvline(win, 3, 0, conf->ascii_lines ? '<' : ACS_LARROW, 3);
mvwvline(win, 3, w-1, conf->ascii_lines ? '>' : ACS_RARROW, 3);
wattroff(win, t.dialog.arrowcolor);
}
mvwaddstr(win, 1, 5, "Sun Mon Tue Wed Thu Fri Sat");
ndays = month_days(yy, mm);
y = 2;
wd = week_day(yy, mm, 1);
for (i = 1; i <= ndays; i++) {
x = 5 + (4 * wd); /* x has to be 6 with week number */
wmove(win, y, x);
mvwprintw(win, y, x, "%2d", i);
if (i == dd) {
wattron(win, t.menu.f_namecolor);
mvwprintw(win, y, x, "%2d", i);
wattroff(win, t.menu.f_namecolor);
}
wd++;
if (wd > 6) {
wd = 0;
y++;
}
}
wrefresh(win);
}
static void
drawsquare(struct bsddialog_conf *conf, WINDOW *win, const char *fmt,
const void *value, bool focus)
{
int h, w;
getmaxyx(win, h, w);
draw_borders(conf, win, h, w, RAISED);
if (focus) {
wattron(win, t.dialog.arrowcolor);
mvwhline(win, 0, 7, conf->ascii_lines ? '^' : ACS_UARROW, 3);
mvwhline(win, 2, 7, conf->ascii_lines ? 'v' : ACS_DARROW, 3);
wattroff(win, t.dialog.arrowcolor);
}
if (focus)
wattron(win, t.menu.f_namecolor);
if (strchr(fmt, 's') != NULL)
mvwprintw(win, 1, 1, fmt, (const char*)value);
else
mvwprintw(win, 1, 1, fmt, *((const int*)value));
if (focus)
wattroff(win, t.menu.f_namecolor);
wrefresh(win);
}
static int
calendar_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h,
int *w, const char *text, struct buttons bs)
{
int htext, wtext;
if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) {
if (text_size(conf, rows, cols, text, &bs, MINHCAL, MINWCAL,
&htext, &wtext) != 0)
return (BSDDIALOG_ERROR);
}
if (cols == BSDDIALOG_AUTOSIZE)
*w = widget_min_width(conf, wtext, MINWCAL, &bs);
if (rows == BSDDIALOG_AUTOSIZE)
*h = widget_min_height(conf, htext, MINHCAL, true);
return (0);
}
static int calendar_checksize(int rows, int cols, struct buttons bs)
{
int mincols;
mincols = MAX(MINWCAL, buttons_min_width(bs));
mincols += VBORDERS;
if (cols < mincols)
RETURN_ERROR("Few cols for this calendar (at least 38)");
if (rows < MINHCAL + 2 + 2) /* 2 buttons + 2 borders */
RETURN_ERROR("Few rows for calendar (at least 17)");
return (0);
}
int
bsddialog_calendar(struct bsddialog_conf *conf, const char *text, int rows,
int cols, unsigned int *yy, unsigned int *mm, unsigned int *dd)
{
bool loop, focusbuttons;
int retval, y, x, h, w, sel, ycal, xcal, year, month, day;
wint_t input;
WINDOW *widget, *textpad, *shadow, *yearwin, *monthwin, *daywin;
struct buttons bs;
const char *m[12] = {
"January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"
};
if (yy == NULL || mm == NULL || dd == NULL)
RETURN_ERROR("yy / mm / dd cannot be NULL");
year = *yy > MAXYEAR ? MAXYEAR : *yy;
if (year < MINYEAR)
year = MINYEAR;
month = *mm > 12 ? 12 : *mm;
if (month == 0)
month = 1;
day = *dd == 0 ? 1 : *dd;
if(day > month_days(year, month))
day = month_days(year, month);
get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL);
if (set_widget_size(conf, rows, cols, &h, &w) != 0)
return (BSDDIALOG_ERROR);
if (calendar_autosize(conf, rows, cols, &h, &w, text, bs) != 0)
return (BSDDIALOG_ERROR);
if (calendar_checksize(h, w, bs) != 0)
return (BSDDIALOG_ERROR);
if (set_widget_position(conf, &y, &x, h, w) != 0)
return (BSDDIALOG_ERROR);
if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs,
true) != 0)
return (BSDDIALOG_ERROR);
pnoutrefresh(textpad, 0, 0, y+1, x+2, y+h-17, x+w-2);
doupdate();
ycal = y + h - 15;
xcal = x + w/2 - 17;
mvwaddstr(widget, h - 16, w/2 - 17, "Month");
monthwin = new_boxed_window(conf, ycal, xcal, 3, 17, RAISED);
mvwaddstr(widget, h - 16, w/2, "Year");
yearwin = new_boxed_window(conf, ycal, xcal + 17, 3, 17, RAISED);
daywin = new_boxed_window(conf, ycal + 3, xcal, 9, 34, RAISED);
wrefresh(widget);
sel = -1;
loop = focusbuttons = true;
while (loop) {
drawsquare(conf, monthwin, "%15s", m[month - 1], sel == 0);
drawsquare(conf, yearwin, "%15d", &year, sel == 1);
print_calendar(conf, daywin, year, month, day, sel == 2);
if (get_wch(&input) == ERR)
continue;
switch(input) {
case KEY_ENTER:
case 10: /* Enter */
if (focusbuttons || conf->button.always_active) {
retval = bs.value[bs.curr];
loop = false;
}
break;
case 27: /* Esc */
if (conf->key.enable_esc) {
retval = BSDDIALOG_ESC;
loop = false;
}
break;
case '\t': /* TAB */
if (focusbuttons) {
bs.curr++;
if (bs.curr >= (int)bs.nbuttons) {
focusbuttons = false;
sel = 0;
bs.curr = conf->button.always_active ?
0 : -1;
}
} else {
sel++;
if (sel > 2) {
focusbuttons = true;
sel = -1;
bs.curr = 0;
}
}
draw_buttons(widget, bs, true);
wrefresh(widget);
break;
case KEY_RIGHT:
if (focusbuttons) {
bs.curr++;
if (bs.curr >= (int)bs.nbuttons) {
focusbuttons = false;
sel = 0;
bs.curr = conf->button.always_active ?
0 : -1;
}
} else if (sel == 2) {
datectl(RIGHT_DAY, &year, &month, &day);
} else { /* Month or Year*/
sel++;
}
draw_buttons(widget, bs, true);
wrefresh(widget);
break;
case KEY_LEFT:
if (focusbuttons) {
bs.curr--;
if (bs.curr < 0) {
focusbuttons = false;
sel = 2;
bs.curr = conf->button.always_active ?
0 : -1;
}
} else if (sel == 2) {
datectl(LEFT_DAY, &year, &month, &day);
} else if (sel == 1) {
sel = 0;
} else { /* sel = 0, Month */
focusbuttons = true;
sel = -1;
bs.curr = 0;
}
draw_buttons(widget, bs, true);
wrefresh(widget);
break;
case KEY_UP:
if (focusbuttons) {
sel = 2;
focusbuttons = false;
bs.curr = conf->button.always_active ? 0 : -1;
draw_buttons(widget, bs, true);
wrefresh(widget);
} else if (sel == 0) {
datectl(UP_MONTH, &year, &month, &day);
} else if (sel == 1) {
datectl(UP_YEAR, &year, &month, &day);
} else { /* sel = 2 */
datectl(UP_DAY, &year, &month, &day);
}
break;
case KEY_DOWN:
if (focusbuttons) {
break;
} else if (sel == 0) {
datectl(DOWN_MONTH, &year, &month, &day);
} else if (sel == 1) {
datectl(DOWN_YEAR, &year, &month, &day);
} else { /* sel = 2 */
datectl(DOWN_DAY, &year, &month, &day);
}
break;
case KEY_HOME:
datectl(UP_MONTH, &year, &month, &day);
break;
case KEY_END:
datectl(DOWN_MONTH, &year, &month, &day);
break;
case KEY_PPAGE:
datectl(UP_YEAR, &year, &month, &day);
break;
case KEY_NPAGE:
datectl(DOWN_YEAR, &year, &month, &day);
break;
case KEY_F(1):
if (conf->key.f1_file == NULL &&
conf->key.f1_message == NULL)
break;
if (f1help(conf) != 0)
return (BSDDIALOG_ERROR);
/* No break, screen size can change */
case KEY_RESIZE:
/* Important for decreasing screen */
hide_widget(y, x, h, w, conf->shadow);
refresh();
if (set_widget_size(conf, rows, cols, &h, &w) != 0)
return (BSDDIALOG_ERROR);
if (calendar_autosize(conf, rows, cols, &h, &w, text,
bs) != 0)
return (BSDDIALOG_ERROR);
if (calendar_checksize(h, w, bs) != 0)
return (BSDDIALOG_ERROR);
if (set_widget_position(conf, &y, &x, h, w) != 0)
return (BSDDIALOG_ERROR);
if (update_dialog(conf, shadow, widget, y, x, h, w,
textpad, text, &bs, true) != 0)
return (BSDDIALOG_ERROR);
pnoutrefresh(textpad, 0, 0, y+1, x+2, y+h-17, x+w-2);
doupdate();
ycal = y + h - 15;
xcal = x + w/2 - 17;
mvwaddstr(widget, h - 16, w/2 - 17, "Month");
mvwin(monthwin, ycal, xcal);
mvwaddstr(widget, h - 16, w/2, "Year");
mvwin(yearwin, ycal, xcal + 17);
mvwin(daywin, ycal + 3, xcal);
wrefresh(widget);
/* Important to avoid grey lines expanding screen */
refresh();
break;
default:
if (shortcut_buttons(input, &bs)) {
retval = bs.value[bs.curr];
loop = false;
}
}
}
if (retval == BSDDIALOG_OK) {
*yy = year;
*mm = month;
*dd = day;
}
delwin(yearwin);
delwin(monthwin);
delwin(daywin);
end_dialog(conf, shadow, widget, textpad);
return (retval);
}