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