Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/bsddialog/lib/slider.c
96290 views
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
672