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 drawsquare(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 drawsquare(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 for (i = 0 ; i < 3; i++) { 345 if (c[i].value > c[i].max) 346 c[i].value = c[i].max; 347 if (c[i].value < 1) 348 c[i].value = 1; 349 } 350 c[2].max = m[c[1].value -1].days; 351 if (c[1].value == 2 && ISLEAP(c[0].value)) 352 c[2].max = 29; 353 if (c[2].value > c[2].max) 354 c[2].value = c[2].max; 355 356 get_buttons(conf, &bs, BUTTON_OK_LABEL, BUTTON_CANCEL_LABEL); 357 358 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 359 return (BSDDIALOG_ERROR); 360 if (datetime_autosize(conf, rows, cols, &h, &w, MINWDATE, text, 361 bs) != 0) 362 return (BSDDIALOG_ERROR); 363 if (datetime_checksize(h, w, MINWDATE, bs) != 0) 364 return (BSDDIALOG_ERROR); 365 if (set_widget_position(conf, &y, &x, h, w) != 0) 366 return (BSDDIALOG_ERROR); 367 368 if (new_dialog(conf, &shadow, &widget, y, x, h, w, &textpad, text, &bs, 369 true) != 0) 370 return (BSDDIALOG_ERROR); 371 372 pnoutrefresh(textpad, 0, 0, y+1, x+2, y+h-7, x+w-2); 373 doupdate(); 374 375 c[0].win = new_boxed_window(conf, y+h-6, x + w/2 - 11, 3, 6, LOWERED); 376 mvwaddch(widget, h - 5, w/2 - 5, '/'); 377 c[1].win = new_boxed_window(conf, y+h-6, x + w/2 - 4, 3, 11, LOWERED); 378 mvwaddch(widget, h - 5, w/2 + 7, '/'); 379 c[2].win = new_boxed_window(conf, y+h-6, x + w/2 + 8, 3, 4, LOWERED); 380 381 wrefresh(widget); 382 383 sel = -1; 384 loop = focusbuttons = true; 385 while (loop) { 386 drawsquare(conf, c[0].win, "%4d", &c[0].value, sel == 0); 387 drawsquare(conf, c[1].win, "%9s", m[c[1].value-1].name, 388 sel == 1); 389 drawsquare(conf, c[2].win, "%02d", &c[2].value, sel == 2); 390 391 if (get_wch(&input) == ERR) 392 continue; 393 switch(input) { 394 case KEY_ENTER: 395 case 10: /* Enter */ 396 if (focusbuttons || conf->button.always_active) { 397 retval = bs.value[bs.curr]; 398 loop = false; 399 } 400 break; 401 case 27: /* Esc */ 402 if (conf->key.enable_esc) { 403 retval = BSDDIALOG_ESC; 404 loop = false; 405 } 406 break; 407 case KEY_RIGHT: 408 case '\t': /* TAB */ 409 if (focusbuttons) { 410 bs.curr++; 411 focusbuttons = bs.curr < (int)bs.nbuttons ? 412 true : false; 413 if (focusbuttons == false) { 414 sel = 0; 415 bs.curr = conf->button.always_active ? 0 : -1; 416 } 417 } else { 418 sel++; 419 focusbuttons = sel > 2 ? true : false; 420 if (focusbuttons) { 421 bs.curr = 0; 422 } 423 } 424 draw_buttons(widget, bs, true); 425 wrefresh(widget); 426 break; 427 case KEY_LEFT: 428 if (focusbuttons) { 429 bs.curr--; 430 focusbuttons = bs.curr < 0 ? false : true; 431 if (focusbuttons == false) { 432 sel = 2; 433 bs.curr = conf->button.always_active ? 0 : -1; 434 } 435 } else { 436 sel--; 437 focusbuttons = sel < 0 ? true : false; 438 if (focusbuttons) 439 bs.curr = (int)bs.nbuttons - 1; 440 } 441 draw_buttons(widget, bs, true); 442 wrefresh(widget); 443 break; 444 case KEY_UP: 445 if (focusbuttons) { 446 sel = 0; 447 focusbuttons = false; 448 bs.curr = conf->button.always_active ? 0 : -1; 449 draw_buttons(widget, bs, true); 450 wrefresh(widget); 451 } else { 452 c[sel].value = c[sel].value > 1 ? 453 c[sel].value - 1 : c[sel].max ; 454 /* if mount change */ 455 c[2].max = m[c[1].value -1].days; 456 /* if year change */ 457 if (c[1].value == 2 && ISLEAP(c[0].value)) 458 c[2].max = 29; 459 /* set new day */ 460 if (c[2].value > c[2].max) 461 c[2].value = c[2].max; 462 } 463 break; 464 case KEY_DOWN: 465 if (focusbuttons) 466 break; 467 c[sel].value = c[sel].value < c[sel].max ? 468 c[sel].value + 1 : 1; 469 /* if mount change */ 470 c[2].max = m[c[1].value -1].days; 471 /* if year change */ 472 if (c[1].value == 2 && ISLEAP(c[0].value)) 473 c[2].max = 29; 474 /* set new day */ 475 if (c[2].value > c[2].max) 476 c[2].value = c[2].max; 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(conf) != 0) 483 return (BSDDIALOG_ERROR); 484 /* No break, screen size can change */ 485 case KEY_RESIZE: 486 /* Important for decreasing screen */ 487 hide_widget(y, x, h, w, conf->shadow); 488 refresh(); 489 490 if (set_widget_size(conf, rows, cols, &h, &w) != 0) 491 return (BSDDIALOG_ERROR); 492 if (datetime_autosize(conf, rows, cols, &h, &w, 493 MINWDATE, text, bs) != 0) 494 return (BSDDIALOG_ERROR); 495 if (datetime_checksize(h, w, MINWDATE, bs) != 0) 496 return (BSDDIALOG_ERROR); 497 if (set_widget_position(conf, &y, &x, h, w) != 0) 498 return (BSDDIALOG_ERROR); 499 500 if (update_dialog(conf, shadow, widget, y, x, h, w, 501 textpad, text, &bs, true) != 0) 502 return (BSDDIALOG_ERROR); 503 doupdate(); 504 505 mvwaddch(widget, h - 5, w/2 - 5, '/'); 506 mvwaddch(widget, h - 5, w/2 + 7, '/'); 507 wrefresh(widget); 508 509 prefresh(textpad, 0, 0, y+1, x+2, y+h-7, x+w-2); 510 511 wclear(c[0].win); 512 mvwin(c[0].win, y + h - 6, x + w/2 - 11); 513 wclear(c[1].win); 514 mvwin(c[1].win, y + h - 6, x + w/2 - 4); 515 wclear(c[2].win); 516 mvwin(c[2].win, y + h - 6, x + w/2 + 8); 517 518 /* Important to avoid grey lines expanding screen */ 519 refresh(); 520 break; 521 default: 522 if (shortcut_buttons(input, &bs)) { 523 retval = bs.value[bs.curr]; 524 loop = false; 525 } 526 } 527 } 528 529 if (retval == BSDDIALOG_OK) { 530 *yy = c[0].value; 531 *mm = c[1].value; 532 *dd = c[2].value; 533 } 534 535 for (i = 0; i < 3; i++) 536 delwin(c[i].win); 537 end_dialog(conf, shadow, widget, textpad); 538 539 return (retval); 540 }