1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2025 Braulio Rivas 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 <curses.h> 29 #include <stdlib.h> 30 #include <string.h> 31 32 #include "bsddialog.h" 33 #include "bsddialog_theme.h" 34 #include "lib_util.h" 35 36 #define MINHSLIDER 13 37 #define MINWSLIDER 36 38 39 #define NULLWIN -1 40 #define START_WIN 0 41 #define END_WIN 1 42 #define STEP_WIN 2 43 #define SLIDER_WIN 3 44 #define NWIN 4 45 46 enum operation { 47 MOVERIGHT, 48 MOVEFARRIGHT, 49 MOVEFASTRIGHT, 50 MOVELEFT, 51 MOVEFARLEFT, 52 MOVEFASTLEFT, 53 INCREASELEFT, 54 DECREASELEFT, 55 INCREASERIGHT, 56 DECREASERIGHT, 57 INCREASESTEP, 58 DECREASESTEP, 59 }; 60 61 struct sliderctl { 62 enum operation op; 63 unsigned long (*spaces)[2]; 64 int nspaces; /* api unsigned, but segfault handlesliderctl():MOVELEFT */ 65 unsigned long length; 66 unsigned long *start; 67 unsigned long *end; 68 unsigned long step; 69 }; 70 71 static int crashes(long x, long y, long a, long b) 72 { 73 return ((x <= a && a <= y) || (x <= b && b <= y)); 74 } 75 76 static int fits(long x, long y, long a, long b) 77 { 78 return ((x <= a) && (b <= y)); 79 } 80 81 static void handlesliderctl(struct sliderctl *sliderctl) 82 { 83 int i, step, tmpstep; 84 unsigned long x, y, size, old_start, old_end; 85 signed long new_start, new_end; 86 87 step = sliderctl->step; 88 old_start = *(sliderctl->start); 89 new_start = old_start; 90 old_end = *(sliderctl->end); 91 new_end = old_end; 92 size = old_end - old_start + 1; 93 94 switch (sliderctl->op) { 95 case MOVERIGHT: 96 new_start = old_start + step; 97 new_end = old_end + step; 98 99 for (i = 0; i < sliderctl->nspaces; i++) { 100 x = (sliderctl->spaces)[i][0]; 101 y = (sliderctl->spaces)[i][1]; 102 103 if (crashes(x, y, new_start, new_end)) { 104 new_start = y + 1; 105 new_end = new_start + size - 1; 106 break; 107 } 108 } 109 break; 110 case MOVELEFT: 111 new_start = old_start - step; 112 new_end = old_end - step; 113 114 for (i = sliderctl->nspaces - 1; i >= 0; i--) { 115 x = (sliderctl->spaces)[i][0]; 116 y = (sliderctl->spaces)[i][1]; 117 118 if (crashes(x, y, new_start, new_end)) { 119 new_end = x - 1; 120 new_start = new_end - size + 1; 121 break; 122 } 123 } 124 break; 125 case INCREASELEFT: 126 new_start = old_start + step; 127 break; 128 case DECREASELEFT: 129 new_start = old_start - step; 130 for (i = 0; i < sliderctl->nspaces; i++) { 131 x = (sliderctl->spaces)[i][0]; 132 y = (sliderctl->spaces)[i][1]; 133 134 if (crashes(x, y, new_start, new_end)) { 135 new_start = old_start; 136 break; 137 } 138 } 139 break; 140 case INCREASERIGHT: 141 new_end = old_end + step; 142 for (i = 0; i < sliderctl->nspaces; i++) { 143 x = (sliderctl->spaces)[i][0]; 144 y = (sliderctl->spaces)[i][1]; 145 146 if (crashes(x, y, new_start, new_end)) { 147 new_end = old_end; 148 break; 149 } 150 } 151 break; 152 case DECREASERIGHT: 153 new_end = old_end - step; 154 break; 155 case MOVEFARLEFT: 156 new_start = 0; 157 new_end = size - 1; 158 for (i = 0; i < sliderctl->nspaces; i++) { 159 x = (sliderctl->spaces)[i][0]; 160 y = (sliderctl->spaces)[i][1]; 161 162 if (crashes(x, y, new_start, new_end)) { 163 new_start = y + 1; 164 new_end = new_start + size - 1; 165 break; 166 } 167 } 168 break; 169 case MOVEFARRIGHT: 170 new_end = (sliderctl->length) - 1; 171 new_start = new_end - size + 1; 172 for (i = sliderctl->nspaces - 1; i >= 0; i--) { 173 x = (sliderctl->spaces)[i][0]; 174 y = (sliderctl->spaces)[i][1]; 175 176 if (crashes(x, y, new_start, new_end)) { 177 new_end = x - 1; 178 new_start = new_end - size + 1; 179 break; 180 } 181 } 182 break; 183 case MOVEFASTLEFT: 184 if (size < 10) { 185 tmpstep = 1; 186 } else { 187 tmpstep = ((sliderctl->length) * 10) / 100; 188 } 189 new_start = old_start - tmpstep; 190 new_end = old_end - tmpstep; 191 192 for (i = sliderctl->nspaces - 1; i >= 0; i--) { 193 x = (sliderctl->spaces)[i][0]; 194 y = (sliderctl->spaces)[i][1]; 195 196 if (crashes(x, y, new_start, new_end)) { 197 new_end = x - 1; 198 new_start = new_end - size + 1; 199 break; 200 } 201 } 202 break; 203 case MOVEFASTRIGHT: 204 if (size < 10) { 205 tmpstep = 1; 206 } else { 207 tmpstep = ((sliderctl->length) * 10) / 100; 208 } 209 new_start = old_start + tmpstep; 210 new_end = old_end + tmpstep; 211 212 for (i = 0; i < sliderctl->nspaces; i++) { 213 x = (sliderctl->spaces)[i][0]; 214 y = (sliderctl->spaces)[i][1]; 215 216 if (crashes(x, y, new_start, new_end)) { 217 new_start = y + 1; 218 new_end = new_start + size - 1; 219 break; 220 } 221 } 222 break; 223 case INCREASESTEP: 224 ++step; 225 break; 226 case DECREASESTEP: 227 if (step > 1) { 228 --step; 229 } 230 break; 231 } 232 233 if (fits(0, (sliderctl->length) - 1, new_start, new_end) != 1) { 234 new_start = old_start; 235 new_end = old_end; 236 } 237 238 if (new_start > new_end) { 239 new_start = old_start; 240 new_end = old_end; 241 } 242 243 sliderctl->step = step; 244 245 *(sliderctl->start) = new_start; 246 *(sliderctl->end) = new_end; 247 } 248 249 static void 250 drawsquare(struct bsddialog_conf *conf, WINDOW *win, enum elevation elev, 251 bool focus, const char *fmt, unsigned long value) 252 { 253 int h, l, w; 254 255 getmaxyx(win, h, w); 256 draw_borders(conf, win, elev); 257 if (focus) { 258 l = 2 + w % 2; 259 wattron(win, t.dialog.arrowcolor); 260 mvwhline(win, 0, w / 2 - l / 2, UARROW(conf), l); 261 mvwhline(win, h - 1, w / 2 - l / 2, DARROW(conf), l); 262 wattroff(win, t.dialog.arrowcolor); 263 } 264 265 if (focus) 266 wattron(win, t.menu.f_namecolor); 267 268 mvwprintw(win, 1, 1, fmt, value); 269 270 if (focus) 271 wattroff(win, t.menu.f_namecolor); 272 273 wnoutrefresh(win); 274 } 275 276 static void 277 print_slider(struct bsddialog_conf *conf, WINDOW *win, 278 unsigned long spaces[][2], int nspaces, unsigned long length, 279 unsigned long *start, unsigned long *end, bool active) 280 { 281 int i, y, x, l, height, width; 282 unsigned long s, e; 283 chtype ch; 284 285 getmaxyx(win, height, width); 286 wclear(win); 287 draw_borders(conf, win, RAISED); 288 289 if (active) { 290 wattron(win, t.dialog.arrowcolor); 291 mvwvline(win, 1, 0, LARROW(conf), 1); 292 mvwvline(win, 1, width - 1, RARROW(conf), 1); 293 wattroff(win, t.dialog.arrowcolor); 294 } 295 296 y = height / 2; 297 width -= 1; 298 299 ch = ' ' | bsddialog_color(BSDDIALOG_RED, BSDDIALOG_RED, 0); 300 for (i = 0; i < nspaces; i++) { 301 s = spaces[i][0]; 302 e = spaces[i][1]; 303 304 x = (s * width) / length; 305 l = ((e - s) * width) / length; 306 307 if ((e - s) == 0) { 308 l = 0; 309 } else if (l == 0) { 310 l = 1; 311 } 312 313 mvwhline(win, y, x + 1, ch, l); 314 } 315 316 ch = ' ' | t.bar.f_color; 317 s = ((*start) * width) / length; 318 l = (((*end) - (*start)) * width) / length; 319 if ((*end - *start) == 0) { 320 l = 0; 321 } else if (l == 0) { 322 l = 1; 323 } 324 mvwhline(win, y, s + 1, ch, l); 325 326 wnoutrefresh(win); 327 } 328 329 static int 330 slider_draw(struct dialog *d, bool redraw, WINDOW *start_win, WINDOW *end_win, 331 WINDOW *size_win, WINDOW *step_win, WINDOW *slider_win, const char *unit) 332 { 333 char *buf; 334 int yslider, xslider; 335 336 if (redraw) { 337 hide_dialog(d); 338 refresh(); /* Important for decreasing screen */ 339 } 340 if (dialog_size_position(d, MINHSLIDER, MINWSLIDER, NULL) != 0) 341 return (BSDDIALOG_ERROR); 342 if (draw_dialog(d) != 0) /* doupdate in main loop */ 343 return (BSDDIALOG_ERROR); 344 if (redraw) 345 refresh(); /* Important to fix grey lines expanding screen */ 346 TEXTPAD(d, MINHSLIDER + HBUTTONS); 347 348 yslider = d->y + d->h - 15; 349 xslider = d->x + d->w / 2 - 17; 350 asprintf(&buf, "Start (%s)", unit); 351 mvwaddstr(d->widget, d->h - 16, d->w / 2 - 17, buf); 352 free(buf); 353 update_box(d->conf, start_win, yslider, xslider, 3, 17, RAISED); 354 asprintf(&buf, "End (%s)", unit); 355 mvwaddstr(d->widget, d->h - 16, d->w / 2, buf); 356 free(buf); 357 update_box(d->conf, end_win, yslider, xslider + 17, 3, 17, RAISED); 358 asprintf(&buf, "Size (%s)", unit); 359 mvwaddstr(d->widget, d->h - 12, d->w / 2 - 17, buf); 360 free(buf); 361 update_box(d->conf, size_win, yslider + 4, xslider, 3, 17, RAISED); 362 asprintf(&buf, "Step (%s)", unit); 363 mvwaddstr(d->widget, d->h - 12, d->w / 2, buf); 364 free(buf); 365 update_box(d->conf, step_win, yslider + 4, xslider + 17, 3, 17, RAISED); 366 367 update_box(d->conf, slider_win, yslider + 7, xslider, 3, 34, RAISED); 368 wnoutrefresh(d->widget); 369 370 return (0); 371 } 372 373 /* API */ 374 int 375 bsddialog_slider(struct bsddialog_conf *conf, const char *text, int rows, 376 int cols, const char *unit, unsigned long length, unsigned long *start, 377 unsigned long *end, bool resize, unsigned int nblocks, 378 unsigned long blocks[][2]) 379 { 380 struct sliderctl ctl; 381 bool loop, focusbuttons; 382 int retval, sel; 383 wint_t input; 384 unsigned long size; 385 WINDOW *start_win, *end_win, *size_win, *step_win, *slider_win; 386 struct dialog dialog; 387 388 CHECK_PTR(start); 389 CHECK_PTR(end); 390 391 ctl.spaces = blocks; 392 ctl.nspaces = nblocks; 393 ctl.length = length; 394 ctl.start = start; 395 ctl.end = end; 396 ctl.step = 1; 397 398 if (prepare_dialog(conf, text, rows, cols, &dialog) != 0) 399 return (BSDDIALOG_ERROR); 400 set_buttons(&dialog, true, OK_LABEL, CANCEL_LABEL); 401 402 if ((start_win = newwin(1, 1, 1, 1)) == NULL) 403 RETURN_ERROR("Cannot build WINDOW for start"); 404 wbkgd(start_win, t.dialog.color); 405 406 if ((end_win = newwin(1, 1, 1, 1)) == NULL) 407 RETURN_ERROR("Cannot build WINDOW for end"); 408 wbkgd(end_win, t.dialog.color); 409 410 if ((step_win = newwin(1, 1, 1, 1)) == NULL) 411 RETURN_ERROR("Cannot build WINDOW for step"); 412 wbkgd(step_win, t.dialog.color); 413 414 if ((size_win = newwin(1, 1, 1, 1)) == NULL) 415 RETURN_ERROR("Cannot build WINDOW for size"); 416 wbkgd(size_win, t.dialog.color); 417 418 if ((slider_win = newwin(1, 1, 1, 1)) == NULL) 419 RETURN_ERROR("Cannot build WINDOW for slider"); 420 wbkgd(slider_win, t.dialog.color); 421 422 if (slider_draw(&dialog, false, start_win, end_win, size_win, step_win, 423 slider_win, unit) != 0) 424 return (BSDDIALOG_ERROR); 425 426 sel = NULLWIN; 427 loop = focusbuttons = true; 428 while (loop) { 429 size = *(ctl.end) - *(ctl.start) + 1; 430 drawsquare(conf, start_win, RAISED, sel == START_WIN, "%15lu", *start); 431 drawsquare(conf, end_win, RAISED, sel == END_WIN, "%15lu", *end); 432 drawsquare(conf, size_win, RAISED, 0, "%15lu", size); 433 drawsquare(conf, step_win, RAISED, sel == STEP_WIN, "%15d", ctl.step); 434 print_slider(conf, slider_win, blocks, nblocks, length, start, 435 end, sel == SLIDER_WIN); 436 doupdate(); 437 438 if (get_wch(&input) == ERR) 439 continue; 440 switch (input) { 441 case KEY_ENTER: 442 case 10: /* Enter */ 443 if (focusbuttons || conf->button.always_active) { 444 retval = BUTTONVALUE(dialog.bs); 445 loop = false; 446 } 447 break; 448 case 27: /* Esc */ 449 if (conf->key.enable_esc) { 450 retval = BSDDIALOG_ESC; 451 loop = false; 452 } 453 break; 454 case '\t': /* TAB */ 455 if (focusbuttons) { 456 dialog.bs.curr++; 457 if (dialog.bs.curr >= (int)dialog.bs.nbuttons) { 458 focusbuttons = false; 459 sel = START_WIN; 460 dialog.bs.curr = 461 conf->button.always_active ? 0 : -1; 462 } 463 } else { 464 sel++; 465 if ((sel + 1) > NWIN) { 466 focusbuttons = true; 467 sel = NULLWIN; 468 dialog.bs.curr = 0; 469 } 470 } 471 DRAW_BUTTONS(dialog); 472 break; 473 case KEY_CTRL('n'): 474 case KEY_RIGHT: 475 if (focusbuttons) { 476 dialog.bs.curr++; 477 if (dialog.bs.curr >= (int)dialog.bs.nbuttons) { 478 focusbuttons = false; 479 sel = START_WIN; 480 dialog.bs.curr = 481 conf->button.always_active ? 0 : -1; 482 } 483 } else if (sel == SLIDER_WIN) { 484 ctl.op = MOVERIGHT; 485 handlesliderctl(&ctl); 486 } else { 487 sel++; 488 } 489 DRAW_BUTTONS(dialog); 490 break; 491 case KEY_CTRL('p'): 492 case KEY_LEFT: 493 if (focusbuttons) { 494 dialog.bs.curr--; 495 if (dialog.bs.curr < 0) { 496 focusbuttons = false; 497 sel = SLIDER_WIN; 498 dialog.bs.curr = 499 conf->button.always_active ? 0 : -1; 500 } 501 } else if (sel == SLIDER_WIN) { 502 ctl.op = MOVELEFT; 503 handlesliderctl(&ctl); 504 } else if (sel == END_WIN) { 505 sel = START_WIN; 506 } else { 507 focusbuttons = true; 508 sel = NULLWIN; 509 dialog.bs.curr = 0; 510 } 511 DRAW_BUTTONS(dialog); 512 break; 513 case KEY_UP: 514 if (focusbuttons) { 515 sel = SLIDER_WIN; 516 focusbuttons = false; 517 dialog.bs.curr = 518 conf->button.always_active ? 0 : -1; 519 DRAW_BUTTONS(dialog); 520 } else if (sel == START_WIN) { 521 if (resize) { 522 ctl.op = INCREASELEFT; 523 } else { 524 ctl.op = MOVERIGHT; 525 } 526 handlesliderctl(&ctl); 527 } else if (sel == END_WIN) { 528 if (resize) { 529 ctl.op = INCREASERIGHT; 530 } else { 531 ctl.op = MOVERIGHT; 532 } 533 handlesliderctl(&ctl); 534 } else if (sel == STEP_WIN) { 535 ctl.op = INCREASESTEP; 536 handlesliderctl(&ctl); 537 } 538 break; 539 case KEY_DOWN: 540 if (focusbuttons) { 541 break; 542 } else if (sel == START_WIN) { 543 if (resize) { 544 ctl.op = DECREASELEFT; 545 } else { 546 ctl.op = MOVELEFT; 547 } 548 handlesliderctl(&ctl); 549 } else if (sel == END_WIN) { 550 if (resize) { 551 ctl.op = DECREASERIGHT; 552 } else { 553 ctl.op = MOVELEFT; 554 } 555 handlesliderctl(&ctl); 556 } else if (sel == STEP_WIN) { 557 ctl.op = DECREASESTEP; 558 handlesliderctl(&ctl); 559 } 560 break; 561 case '-': 562 if (focusbuttons) { 563 break; 564 } else if (sel == START_WIN) { 565 if (resize) { 566 ctl.op = DECREASELEFT; 567 } else { 568 ctl.op = MOVELEFT; 569 } 570 handlesliderctl(&ctl); 571 } else if (sel == END_WIN) { 572 if (resize) { 573 ctl.op = DECREASERIGHT; 574 } else { 575 ctl.op = MOVELEFT; 576 } 577 handlesliderctl(&ctl); 578 } else if (sel == STEP_WIN) { 579 ctl.op = DECREASESTEP; 580 handlesliderctl(&ctl); 581 } 582 break; 583 case '+': 584 if (focusbuttons) { 585 break; 586 } else if (sel == START_WIN) { 587 if (resize) { 588 ctl.op = INCREASELEFT; 589 } else { 590 ctl.op = MOVERIGHT; 591 } 592 handlesliderctl(&ctl); 593 } else if (sel == END_WIN) { 594 if (resize) { 595 ctl.op = INCREASERIGHT; 596 } else { 597 ctl.op = MOVERIGHT; 598 } 599 handlesliderctl(&ctl); 600 } else if (sel == STEP_WIN) { 601 ctl.op = INCREASESTEP; 602 handlesliderctl(&ctl); 603 } 604 break; 605 case KEY_HOME: 606 if (focusbuttons) { 607 break; 608 } else if (sel == SLIDER_WIN) { 609 ctl.op = MOVEFARLEFT; 610 handlesliderctl(&ctl); 611 } 612 break; 613 case KEY_END: 614 if (focusbuttons) { 615 break; 616 } else if (sel == SLIDER_WIN) { 617 ctl.op = MOVEFARRIGHT; 618 handlesliderctl(&ctl); 619 } 620 break; 621 case KEY_PPAGE: 622 if (focusbuttons) { 623 break; 624 } else if (sel == SLIDER_WIN) { 625 ctl.op = MOVEFASTLEFT; 626 handlesliderctl(&ctl); 627 } 628 break; 629 case KEY_NPAGE: 630 if (focusbuttons) { 631 break; 632 } else if (sel == SLIDER_WIN) { 633 ctl.op = MOVEFASTRIGHT; 634 handlesliderctl(&ctl); 635 } 636 break; 637 case KEY_F(1): 638 if (conf->key.f1_file == NULL && 639 conf->key.f1_message == NULL) 640 break; 641 if (f1help_dialog(conf) != 0) 642 return (BSDDIALOG_ERROR); 643 if (slider_draw(&dialog, true, start_win, end_win, size_win, 644 step_win, slider_win, unit) != 0) 645 return (BSDDIALOG_ERROR); 646 break; 647 case KEY_CTRL('l'): 648 case KEY_RESIZE: 649 if (slider_draw(&dialog, true, start_win, end_win, size_win, 650 step_win, slider_win, unit) != 0) 651 return (BSDDIALOG_ERROR); 652 break; 653 default: 654 if (shortcut_buttons(input, &dialog.bs)) { 655 DRAW_BUTTONS(dialog); 656 doupdate(); 657 retval = BUTTONVALUE(dialog.bs); 658 loop = false; 659 } 660 } 661 } 662 663 delwin(start_win); 664 delwin(end_win); 665 delwin(step_win); 666 delwin(slider_win); 667 end_dialog(&dialog); 668 669 return (retval); 670 } 671