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