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