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