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