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
crashes(long x,long y,long a,long b)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
fits(long x,long y,long a,long b)76 static int fits(long x, long y, long a, long b)
77 {
78 return ((x <= a) && (b <= y));
79 }
80
handlesliderctl(struct sliderctl * sliderctl)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
drawsquare(struct bsddialog_conf * conf,WINDOW * win,enum elevation elev,bool focus,const char * fmt,unsigned long value)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
print_slider(struct bsddialog_conf * conf,WINDOW * win,unsigned long spaces[][2],int nspaces,unsigned long length,unsigned long * start,unsigned long * end,bool active)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
slider_draw(struct dialog * d,bool redraw,WINDOW * start_win,WINDOW * end_win,WINDOW * size_win,WINDOW * step_win,WINDOW * slider_win,const char * unit)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
bsddialog_slider(struct bsddialog_conf * conf,const char * text,int rows,int cols,const char * unit,unsigned long length,unsigned long * start,unsigned long * end,bool resize,unsigned int nblocks,unsigned long blocks[][2])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