1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021-2022 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 <sys/param.h> 29 30 #include <ctype.h> 31 #include <curses.h> 32 #include <string.h> 33 34 #include "bsddialog.h" 35 #include "bsddialog_theme.h" 36 #include "lib_util.h" 37 38 #define MINWDATE 23 /* 3 windows and their borders */ 39 #define MINWTIME 14 /* 3 windows and their borders */ 40 41 static void 42 drawquare(struct bsddialog_conf *conf, WINDOW *win, const char *fmt, 43 const void *value, bool focus) 44 { 45 int h, l, w; 46 47 getmaxyx(win, h, w); 48 draw_borders(conf, win, h, w, LOWERED); 49 if (focus) { 50 l = 2 + w%2; 51 wattron(win, t.dialog.arrowcolor); 52 mvwhline(win, 0, w/2 - l/2, 53 conf->ascii_lines ? '^' : ACS_UARROW, l); 54 mvwhline(win, h-1, w/2 - l/2, 55 conf->ascii_lines ? 'v' : ACS_DARROW, l); 56 wattroff(win, t.dialog.arrowcolor); 57 } 58 59 if (focus) 60 wattron(win, t.menu.f_namecolor); 61 if (strchr(fmt, 's') != NULL) 62 mvwprintw(win, 1, 1, fmt, (const char*)value); 63 else 64 mvwprintw(win, 1, 1, fmt, *((const int*)value)); 65 if (focus) 66 wattroff(win, t.menu.f_namecolor); 67 68 wrefresh(win); 69 } 70 71 static int 72 datetime_autosize(struct bsddialog_conf *conf, int rows, int cols, int *h, 73 int *w, int minw, const char *text, struct buttons bs) 74 { 75 int htext, wtext; 76 77 if (cols == BSDDIALOG_AUTOSIZE || rows == BSDDIALOG_AUTOSIZE) { 78 if (text_size(conf, rows, cols, text, &bs, 3, minw, &htext, 79 &wtext) != 0) 80 return (BSDDIALOG_ERROR); 81 } 82 83 if (cols == BSDDIALOG_AUTOSIZE) 84 *w = widget_min_width(conf, wtext, minw, &bs); 85 86 if (rows == BSDDIALOG_AUTOSIZE) 87 *h = widget_min_height(conf, htext, 3 /* windows */, true); 88 89 return (0); 90 } 91 92 static int 93 datetime_checksize(int rows, int cols, int minw, struct buttons bs) 94 { 95 int mincols; 96 97 mincols = VBORDERS; 98 mincols += buttons_min_width(bs); 99 mincols = MAX(minw, mincols); 100 101 if (cols < mincols) 102 RETURN_ERROR("Few cols for this timebox/datebox"); 103 104 if (rows < 7) /* 2 button + 2 borders + 3 windows */ 105 RETURN_ERROR("Few rows for this timebox/datebox, at least 7"); 106 107 return (0); 108 } 109 110 int 111 bsddialog_timebox(struct bsddialog_conf *conf, const char* text, int rows, 112 int cols, unsigned int *hh, unsigned int *mm, unsigned int *ss) 113 { 114 bool loop, focusbuttons; 115 int i, retval, y, x, h, w, sel; 116 wint_t input; 117 WINDOW *widget, *textpad, *shadow; 118 struct buttons bs; 119 struct myclockstruct { 120 unsigned int max; 121 unsigned int value; 122 WINDOW *win; 123 }; 124 125 if (hh == NULL || mm == NULL || ss == NULL) 126 RETURN_ERROR("hh / mm / ss cannot be NULL"); 127 128 struct myclockstruct c[3] = { 129 {23, *hh, NULL}, 130 {59, *mm, NULL}, 131 {59, *ss, NULL} 132 }; 133 134 for (i = 0 ; i < 3; i++) { 135 if (c[i].value > c[i].max) 136 c[i].value = c[i].max; 137 } 138 139 get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL); 140 141 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 142 return (BSDDIALOG_ERROR); 143 if (datetime_autosize(conf, rows, cols, &h, &w, MINWTIME, text, 144 bs) != 0) 145 return (BSDDIALOG_ERROR); 146 if (datetime_checksize(h, w, MINWTIME, bs) != 0) 147 return (BSDDIALOG_ERROR); 148 if (set_widget_position(conf, &y, &x, h, w) != 0) 149 return (BSDDIALOG_ERROR); 150 151 if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs, 152 true) != 0) 153 return (BSDDIALOG_ERROR); 154 155 pnoutrefresh(textpad, 0, 0, y+1, x+2, y+h-7, x+w-2); 156 doupdate(); 157 158 c[0].win = new_boxed_window(conf, y+h-6, x + w/2 - 7, 3, 4, LOWERED); 159 mvwaddch(widget, h - 5, w/2 - 3, ':'); 160 c[1].win = new_boxed_window(conf, y+h-6, x + w/2 - 2, 3, 4, LOWERED); 161 mvwaddch(widget, h - 5, w/2 + 2, ':'); 162 c[2].win = new_boxed_window(conf, y+h-6, x + w/2 + 3, 3, 4, LOWERED); 163 164 wrefresh(widget); 165 166 sel = -1; 167 loop = focusbuttons = true; 168 while (loop) { 169 for (i = 0; i < 3; i++) 170 drawquare(conf, c[i].win, "%02d", &c[i].value, 171 sel == i); 172 173 if (get_wch(&input) == ERR) 174 continue; 175 switch(input) { 176 case KEY_ENTER: 177 case 10: /* Enter */ 178 if (focusbuttons || conf->button.always_active) { 179 retval = bs.value[bs.curr]; 180 loop = false; 181 } 182 break; 183 case 27: /* Esc */ 184 if (conf->key.enable_esc) { 185 retval = BSDDIALOG_ESC; 186 loop = false; 187 } 188 break; 189 case KEY_RIGHT: 190 case '\t': /* TAB */ 191 if (focusbuttons) { 192 bs.curr++; 193 focusbuttons = bs.curr < (int)bs.nbuttons ? 194 true : false; 195 if (focusbuttons == false) { 196 sel = 0; 197 bs.curr = conf->button.always_active ? 0 : -1; 198 } 199 } else { 200 sel++; 201 focusbuttons = sel > 2 ? true : false; 202 if (focusbuttons) { 203 bs.curr = 0; 204 } 205 } 206 draw_buttons(widget, bs, true); 207 wrefresh(widget); 208 break; 209 case KEY_LEFT: 210 if (focusbuttons) { 211 bs.curr--; 212 focusbuttons = bs.curr < 0 ? false : true; 213 if (focusbuttons == false) { 214 sel = 2; 215 bs.curr = conf->button.always_active ? 0 : -1; 216 } 217 } else { 218 sel--; 219 focusbuttons = sel < 0 ? true : false; 220 if (focusbuttons) 221 bs.curr = (int)bs.nbuttons - 1; 222 } 223 draw_buttons(widget, bs, true); 224 wrefresh(widget); 225 break; 226 case KEY_UP: 227 if (focusbuttons) { 228 sel = 0; 229 focusbuttons = false; 230 bs.curr = conf->button.always_active ? 0 : -1; 231 draw_buttons(widget, bs, true); 232 wrefresh(widget); 233 } else { c[sel].value = c[sel].value > 0 ? 234 c[sel].value - 1 : c[sel].max; 235 } 236 break; 237 case KEY_DOWN: 238 if (focusbuttons) 239 break; 240 c[sel].value = c[sel].value < c[sel].max ? 241 c[sel].value + 1 : 0; 242 break; 243 case KEY_F(1): 244 if (conf->key.f1_file == NULL && 245 conf->key.f1_message == NULL) 246 break; 247 if (f1help(conf) != 0) 248 return (BSDDIALOG_ERROR); 249 /* No break, screen size can change */ 250 case KEY_RESIZE: 251 /* Important for decreasing screen */ 252 hide_widget(y, x, h, w, conf->shadow); 253 refresh(); 254 255 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 256 return (BSDDIALOG_ERROR); 257 if (datetime_autosize(conf, rows, cols, &h, &w, 258 MINWTIME, text, bs) != 0) 259 return (BSDDIALOG_ERROR); 260 if (datetime_checksize(h, w, MINWTIME, bs) != 0) 261 return (BSDDIALOG_ERROR); 262 if (set_widget_position(conf, &y, &x, h, w) != 0) 263 return (BSDDIALOG_ERROR); 264 265 if (update_dialog(conf, shadow, widget, y, x, h, w, 266 textpad, text, &bs, true) != 0) 267 return (BSDDIALOG_ERROR); 268 269 doupdate(); 270 271 mvwaddch(widget, h - 5, w/2 - 3, ':'); 272 mvwaddch(widget, h - 5, w/2 + 2, ':'); 273 wrefresh(widget); 274 275 prefresh(textpad, 0, 0, y+1, x+2, y+h-7, x+w-2); 276 277 wclear(c[0].win); 278 mvwin(c[0].win, y + h - 6, x + w/2 - 7); 279 wclear(c[1].win); 280 mvwin(c[1].win, y + h - 6, x + w/2 - 2); 281 wclear(c[2].win); 282 mvwin(c[2].win, y + h - 6, x + w/2 + 3); 283 284 /* Important to avoid grey lines expanding screen */ 285 refresh(); 286 break; 287 default: 288 if (shortcut_buttons(input, &bs)) { 289 retval = bs.value[bs.curr]; 290 loop = false; 291 } 292 } 293 } 294 295 if (retval == BSDDIALOG_OK) { 296 *hh = c[0].value; 297 *mm = c[1].value; 298 *ss = c[2].value; 299 } 300 301 for (i = 0; i < 3; i++) 302 delwin(c[i].win); 303 end_dialog(conf, shadow, widget, textpad); 304 305 return (retval); 306 } 307 308 int 309 bsddialog_datebox(struct bsddialog_conf *conf, const char *text, int rows, 310 int cols, unsigned int *yy, unsigned int *mm, unsigned int *dd) 311 { 312 bool loop, focusbuttons; 313 int i, retval, y, x, h, w, sel; 314 wint_t input; 315 WINDOW *widget, *textpad, *shadow; 316 struct buttons bs; 317 struct calendar { 318 int max; 319 int value; 320 WINDOW *win; 321 unsigned int x; 322 }; 323 struct month { 324 const char *name; 325 unsigned int days; 326 }; 327 328 if (yy == NULL || mm == NULL || dd == NULL) 329 RETURN_ERROR("yy / mm / dd cannot be NULL"); 330 331 struct calendar c[3] = { 332 {9999, *yy, NULL, 4 }, 333 {12, *mm, NULL, 9 }, 334 {31, *dd, NULL, 2 } 335 }; 336 337 struct month m[12] = { 338 { "January", 31 }, { "February", 28 }, { "March", 31 }, 339 { "April", 30 }, { "May", 31 }, { "June", 30 }, 340 { "July", 31 }, { "August", 31 }, { "September", 30 }, 341 { "October", 31 }, { "November", 30 }, { "December", 31 } 342 }; 343 344 #define ISLEAF(year) ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) 345 346 for (i = 0 ; i < 3; i++) { 347 if (c[i].value > c[i].max) 348 c[i].value = c[i].max; 349 if (c[i].value < 1) 350 c[i].value = 1; 351 } 352 c[2].max = m[c[1].value -1].days; 353 if (c[1].value == 2 && ISLEAF(c[0].value)) 354 c[2].max = 29; 355 if (c[2].value > c[2].max) 356 c[2].value = c[2].max; 357 358 get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL); 359 360 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 361 return (BSDDIALOG_ERROR); 362 if (datetime_autosize(conf, rows, cols, &h, &w, MINWDATE, text, 363 bs) != 0) 364 return (BSDDIALOG_ERROR); 365 if (datetime_checksize(h, w, MINWDATE, bs) != 0) 366 return (BSDDIALOG_ERROR); 367 if (set_widget_position(conf, &y, &x, h, w) != 0) 368 return (BSDDIALOG_ERROR); 369 370 if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs, 371 true) != 0) 372 return (BSDDIALOG_ERROR); 373 374 pnoutrefresh(textpad, 0, 0, y+1, x+2, y+h-7, x+w-2); 375 doupdate(); 376 377 c[0].win = new_boxed_window(conf, y+h-6, x + w/2 - 11, 3, 6, LOWERED); 378 mvwaddch(widget, h - 5, w/2 - 5, '/'); 379 c[1].win = new_boxed_window(conf, y+h-6, x + w/2 - 4, 3, 11, LOWERED); 380 mvwaddch(widget, h - 5, w/2 + 7, '/'); 381 c[2].win = new_boxed_window(conf, y+h-6, x + w/2 + 8, 3, 4, LOWERED); 382 383 wrefresh(widget); 384 385 sel = -1; 386 loop = focusbuttons = true; 387 while (loop) { 388 drawquare(conf, c[0].win, "%4d", &c[0].value, sel == 0); 389 drawquare(conf, c[1].win, "%9s", m[c[1].value-1].name, 390 sel == 1); 391 drawquare(conf, c[2].win, "%02d", &c[2].value, sel == 2); 392 393 if (get_wch(&input) == ERR) 394 continue; 395 switch(input) { 396 case KEY_ENTER: 397 case 10: /* Enter */ 398 if (focusbuttons || conf->button.always_active) { 399 retval = bs.value[bs.curr]; 400 loop = false; 401 } 402 break; 403 case 27: /* Esc */ 404 if (conf->key.enable_esc) { 405 retval = BSDDIALOG_ESC; 406 loop = false; 407 } 408 break; 409 case KEY_RIGHT: 410 case '\t': /* TAB */ 411 if (focusbuttons) { 412 bs.curr++; 413 focusbuttons = bs.curr < (int)bs.nbuttons ? 414 true : false; 415 if (focusbuttons == false) { 416 sel = 0; 417 bs.curr = conf->button.always_active ? 0 : -1; 418 } 419 } else { 420 sel++; 421 focusbuttons = sel > 2 ? true : false; 422 if (focusbuttons) { 423 bs.curr = 0; 424 } 425 } 426 draw_buttons(widget, bs, true); 427 wrefresh(widget); 428 break; 429 case KEY_LEFT: 430 if (focusbuttons) { 431 bs.curr--; 432 focusbuttons = bs.curr < 0 ? false : true; 433 if (focusbuttons == false) { 434 sel = 2; 435 bs.curr = conf->button.always_active ? 0 : -1; 436 } 437 } else { 438 sel--; 439 focusbuttons = sel < 0 ? true : false; 440 if (focusbuttons) 441 bs.curr = (int)bs.nbuttons - 1; 442 } 443 draw_buttons(widget, bs, true); 444 wrefresh(widget); 445 break; 446 case KEY_UP: 447 if (focusbuttons) { 448 sel = 0; 449 focusbuttons = false; 450 bs.curr = conf->button.always_active ? 0 : -1; 451 draw_buttons(widget, bs, true); 452 wrefresh(widget); 453 } else { 454 c[sel].value = c[sel].value > 1 ? 455 c[sel].value - 1 : c[sel].max ; 456 /* if mount change */ 457 c[2].max = m[c[1].value -1].days; 458 /* if year change */ 459 if (c[1].value == 2 && ISLEAF(c[0].value)) 460 c[2].max = 29; 461 /* set new day */ 462 if (c[2].value > c[2].max) 463 c[2].value = c[2].max; 464 } 465 break; 466 case KEY_DOWN: 467 if (focusbuttons) 468 break; 469 c[sel].value = c[sel].value < c[sel].max ? 470 c[sel].value + 1 : 1; 471 /* if mount change */ 472 c[2].max = m[c[1].value -1].days; 473 /* if year change */ 474 if (c[1].value == 2 && ISLEAF(c[0].value)) 475 c[2].max = 29; 476 /* set new day */ 477 if (c[2].value > c[2].max) 478 c[2].value = c[2].max; 479 break; 480 case KEY_F(1): 481 if (conf->key.f1_file == NULL && 482 conf->key.f1_message == NULL) 483 break; 484 if (f1help(conf) != 0) 485 return (BSDDIALOG_ERROR); 486 /* No break, screen size can change */ 487 case KEY_RESIZE: 488 /* Important for decreasing screen */ 489 hide_widget(y, x, h, w, conf->shadow); 490 refresh(); 491 492 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 493 return (BSDDIALOG_ERROR); 494 if (datetime_autosize(conf, rows, cols, &h, &w, 495 MINWDATE, text, bs) != 0) 496 return (BSDDIALOG_ERROR); 497 if (datetime_checksize(h, w, MINWDATE, bs) != 0) 498 return (BSDDIALOG_ERROR); 499 if (set_widget_position(conf, &y, &x, h, w) != 0) 500 return (BSDDIALOG_ERROR); 501 502 if (update_dialog(conf, shadow, widget, y, x, h, w, 503 textpad, text, &bs, true) != 0) 504 return (BSDDIALOG_ERROR); 505 doupdate(); 506 507 mvwaddch(widget, h - 5, w/2 - 5, '/'); 508 mvwaddch(widget, h - 5, w/2 + 7, '/'); 509 wrefresh(widget); 510 511 prefresh(textpad, 0, 0, y+1, x+2, y+h-7, x+w-2); 512 513 wclear(c[0].win); 514 mvwin(c[0].win, y + h - 6, x + w/2 - 11); 515 wclear(c[1].win); 516 mvwin(c[1].win, y + h - 6, x + w/2 - 4); 517 wclear(c[2].win); 518 mvwin(c[2].win, y + h - 6, x + w/2 + 8); 519 520 /* Important to avoid grey lines expanding screen */ 521 refresh(); 522 break; 523 default: 524 if (shortcut_buttons(input, &bs)) { 525 retval = bs.value[bs.curr]; 526 loop = false; 527 } 528 } 529 } 530 531 if (retval == BSDDIALOG_OK) { 532 *yy = c[0].value; 533 *mm = c[1].value; 534 *dd = c[2].value; 535 } 536 537 for (i = 0; i < 3; i++) 538 delwin(c[i].win); 539 end_dialog(conf, shadow, widget, textpad); 540 541 return (retval); 542 } 543