Notcurses 3.0.13
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
selector.c
Go to the documentation of this file.
1#include "internal.h"
2
3// internal ncselector item
5 char* option;
6 char* desc;
7 size_t opcolumns; // filled in by library
8 size_t desccolumns; // filled in by library
9};
10
12 char* option;
13 char* desc;
15};
16
17typedef struct ncselector {
18 ncplane* ncp; // backing ncplane
19 unsigned selected; // index of selection
20 unsigned startdisp; // index of first option displayed
21 unsigned maxdisplay; // max number of items to display, 0 -> no limit
22 unsigned longop; // columns occupied by longest option
23 unsigned longdesc; // columns occupied by longest description
24 struct ncselector_int* items; // list of items and descriptions, heap-copied
25 unsigned itemcount; // number of pairs in 'items'
26 char* title; // can be NULL, in which case there's no riser
27 unsigned titlecols; // columns occupied by title
28 char* secondary; // can be NULL
29 unsigned secondarycols; // columns occupied by secondary
30 char* footer; // can be NULL
31 unsigned footercols; // columns occupied by footer
32 uint64_t opchannels; // option channels
33 uint64_t descchannels; // description channels
34 uint64_t titlechannels; // title channels
35 uint64_t footchannels; // secondary and footer channels
36 uint64_t boxchannels; // border channels
37 int uarrowy, darrowy, arrowx;// location of scrollarrows, even if not present
39
40typedef struct ncmultiselector {
41 ncplane* ncp; // backing ncplane
42 unsigned current; // index of highlighted item
43 unsigned startdisp; // index of first option displayed
44 unsigned maxdisplay; // max number of items to display, 0 -> no limit
45 unsigned longitem; // columns occupied by longest item
46 struct ncmselector_int* items; // items, descriptions, and statuses, heap-copied
47 unsigned itemcount; // number of pairs in 'items'
48 char* title; // can be NULL, in which case there's no riser
49 unsigned titlecols; // columns occupied by title
50 char* secondary; // can be NULL
51 unsigned secondarycols; // columns occupied by secondary
52 char* footer; // can be NULL
53 unsigned footercols; // columns occupied by footer
54 uint64_t opchannels; // option channels
55 uint64_t descchannels; // description channels
56 uint64_t titlechannels; // title channels
57 uint64_t footchannels; // secondary and footer channels
58 uint64_t boxchannels; // border channels
59 int uarrowy, darrowy, arrowx; // location of scrollarrows, even if not present
61
62// ideal body width given the ncselector's items and secondary/footer
63static int
64ncselector_body_width(const ncselector* n){
65 unsigned cols = 0;
66 // the body is the maximum of
67 // * longop + longdesc + 5
68 // * secondary + 2
69 // * footer + 2
70 if(n->footercols + 2 > cols){
71 cols = n->footercols + 2;
72 }
73 if(n->secondarycols + 2 > cols){
74 cols = n->secondarycols + 2;
75 }
76 if(n->longop + n->longdesc + 5 > cols){
77 cols = n->longop + n->longdesc + 5;
78 }
79 return cols;
80}
81
82// redraw the selector widget in its entirety
83static int
84ncselector_draw(ncselector* n){
85 ncplane_erase(n->ncp);
87 nccell_set_fg_alpha(&transchar, NCALPHA_TRANSPARENT);
88 nccell_set_bg_alpha(&transchar, NCALPHA_TRANSPARENT);
89 // if we have a title, we'll draw a riser. the riser is two rows tall, and
90 // exactly four columns longer than the title, and aligned to the right. we
91 // draw a rounded box. the body will blow part or all of the bottom away.
92 int yoff = 0;
93 if(n->title){
94 size_t riserwidth = n->titlecols + 4;
95 int offx = ncplane_halign(n->ncp, NCALIGN_RIGHT, riserwidth);
96 ncplane_cursor_move_yx(n->ncp, 0, 0);
97 if(offx){
98 ncplane_hline(n->ncp, &transchar, offx);
99 }
100 ncplane_cursor_move_yx(n->ncp, 0, offx);
101 ncplane_rounded_box_sized(n->ncp, 0, n->boxchannels, 3, riserwidth, 0);
102 n->ncp->channels = n->titlechannels;
103 ncplane_printf_yx(n->ncp, 1, offx + 1, " %s ", n->title);
104 yoff += 2;
105 ncplane_cursor_move_yx(n->ncp, 1, 0);
106 if(offx){
107 ncplane_hline(n->ncp, &transchar, offx);
108 }
109 }
110 unsigned bodywidth = ncselector_body_width(n);
111 unsigned dimy, dimx;
112 ncplane_dim_yx(n->ncp, &dimy, &dimx);
113 int xoff = ncplane_halign(n->ncp, NCALIGN_RIGHT, bodywidth);
114 if(xoff){
115 for(unsigned y = yoff + 1 ; y < dimy ; ++y){
116 ncplane_cursor_move_yx(n->ncp, y, 0);
117 ncplane_hline(n->ncp, &transchar, xoff);
118 }
119 }
120 ncplane_cursor_move_yx(n->ncp, yoff, xoff);
121 ncplane_rounded_box_sized(n->ncp, 0, n->boxchannels, dimy - yoff, bodywidth, 0);
122 if(n->title){
123 n->ncp->channels = n->boxchannels;
124 if(notcurses_canutf8(ncplane_notcurses(n->ncp))){
125 ncplane_putegc_yx(n->ncp, 2, dimx - 1, "┤", NULL);
126 }else{
127 ncplane_putchar_yx(n->ncp, 2, dimx - 1, '|');
128 }
129 if(bodywidth < dimx){
130 if(notcurses_canutf8(ncplane_notcurses(n->ncp))){
131 ncplane_putegc_yx(n->ncp, 2, dimx - bodywidth, "┬", NULL);
132 }else{
133 ncplane_putchar_yx(n->ncp, 2, dimx - bodywidth, '-');
134 }
135 }
136 if((n->titlecols + 4 != dimx) && n->titlecols > n->secondarycols){
137 if(notcurses_canutf8(ncplane_notcurses(n->ncp))){
138 ncplane_putegc_yx(n->ncp, 2, dimx - (n->titlecols + 4), "┴", NULL);
139 }else{
140 ncplane_putchar_yx(n->ncp, 2, dimx - (n->titlecols + 4), '-');
141 }
142 }
143 }
144 // There is always at least one space available on the right for the
145 // secondary title and footer, but we'd prefer to use a few more if we can.
146 if(n->secondary){
147 int xloc = bodywidth - (n->secondarycols + 1) + xoff;
148 if(n->secondarycols < bodywidth - 2){
149 --xloc;
150 }
151 n->ncp->channels = n->footchannels;
152 ncplane_putstr_yx(n->ncp, yoff, xloc, n->secondary);
153 }
154 if(n->footer){
155 int xloc = bodywidth - (n->footercols + 1) + xoff;
156 if(n->footercols < bodywidth - 2){
157 --xloc;
158 }
159 n->ncp->channels = n->footchannels;
160 ncplane_putstr_yx(n->ncp, dimy - 1, xloc, n->footer);
161 }
162 // Top line of body (background and possibly up arrow)
163 ++yoff;
164 ncplane_cursor_move_yx(n->ncp, yoff, xoff + 1);
165 for(unsigned i = xoff + 1 ; i < dimx - 1 ; ++i){
166 nccell transc = NCCELL_TRIVIAL_INITIALIZER; // fall back to base cell
167 ncplane_putc(n->ncp, &transc);
168 }
169 const int bodyoffset = dimx - bodywidth + 2;
170 if(n->maxdisplay && n->maxdisplay < n->itemcount){
171 n->ncp->channels = n->descchannels;
172 n->arrowx = bodyoffset + n->longop;
173 if(notcurses_canutf8(ncplane_notcurses(n->ncp))){
174 ncplane_putegc_yx(n->ncp, yoff, n->arrowx, "↑", NULL);
175 }else{
176 ncplane_putchar_yx(n->ncp, yoff, n->arrowx, '<');
177 }
178 }else{
179 n->arrowx = -1;
180 }
181 n->uarrowy = yoff;
182 unsigned printidx = n->startdisp;
183 unsigned printed = 0;
184 for(yoff += 1 ; yoff < (int)dimy - 2 ; ++yoff){
185 if(n->maxdisplay && printed == n->maxdisplay){
186 break;
187 }
188 ncplane_cursor_move_yx(n->ncp, yoff, xoff + 1);
189 for(int i = xoff + 1 ; i < (int)dimx - 1 ; ++i){
190 nccell transc = NCCELL_TRIVIAL_INITIALIZER; // fall back to base cell
191 ncplane_putc(n->ncp, &transc);
192 }
193 n->ncp->channels = n->opchannels;
194 if(printidx == n->selected){
195 n->ncp->channels = (uint64_t)ncchannels_bchannel(n->opchannels) << 32u | ncchannels_fchannel(n->opchannels);
196 }
197 ncplane_printf_yx(n->ncp, yoff, bodyoffset + (n->longop - n->items[printidx].opcolumns), "%s", n->items[printidx].option);
198 n->ncp->channels = n->descchannels;
199 if(printidx == n->selected){
200 n->ncp->channels = (uint64_t)ncchannels_bchannel(n->descchannels) << 32u | ncchannels_fchannel(n->descchannels);
201 }
202 ncplane_printf_yx(n->ncp, yoff, bodyoffset + n->longop, " %s", n->items[printidx].desc);
203 if(++printidx == n->itemcount){
204 printidx = 0;
205 }
206 ++printed;
207 }
208 // Bottom line of body (background and possibly down arrow)
209 ncplane_cursor_move_yx(n->ncp, yoff, xoff + 1);
210 for(int i = xoff + 1 ; i < (int)dimx - 1 ; ++i){
211 nccell transc = NCCELL_TRIVIAL_INITIALIZER; // fall back to base cell
212 ncplane_putc(n->ncp, &transc);
213 }
214 if(n->maxdisplay && n->maxdisplay < n->itemcount){
215 n->ncp->channels = n->descchannels;
216 if(notcurses_canutf8(ncplane_notcurses(n->ncp))){
217 ncplane_putegc_yx(n->ncp, yoff, n->arrowx, "↓", NULL);
218 }else{
219 ncplane_putchar_yx(n->ncp, yoff, n->arrowx, '>');
220 }
221 }
222 n->darrowy = yoff;
223 return 0;
224}
225
226// calculate the necessary dimensions based off properties of the selector
227static void
228ncselector_dim_yx(const ncselector* n, unsigned* ncdimy, unsigned* ncdimx){
229 unsigned rows = 0, cols = 0; // desired dimensions
230 const ncplane* parent = ncplane_parent(n->ncp);
231 unsigned dimy, dimx; // dimensions of containing plane
232 ncplane_dim_yx(parent, &dimy, &dimx);
233 if(n->title){ // header adds two rows for riser
234 rows += 2;
235 }
236 // we have a top line, a bottom line, two lines of margin, and must be able
237 // to display at least one row beyond that, so require five more
238 rows += 5;
239 rows += (!n->maxdisplay || n->maxdisplay > n->itemcount ? n->itemcount : n->maxdisplay) - 1; // rows necessary to display all options
240 if(rows > dimy){ // claw excess back
241 rows = dimy;
242 }
243 *ncdimy = rows;
244 cols = ncselector_body_width(n);
245 // the riser, if it exists, is header + 4. the cols are the max of these two.
246 if(n->titlecols + 4 > cols){
247 cols = n->titlecols + 4;
248 }
249 *ncdimx = cols;
250}
251
252static void
253ncselector_destroy_internal(ncselector* n){
254 if(n){
255 while(n->itemcount--){
256 free(n->items[n->itemcount].option);
257 free(n->items[n->itemcount].desc);
258 }
259 if(ncplane_set_widget(n->ncp, NULL, NULL) == 0){
260 ncplane_destroy(n->ncp);
261 }
262 free(n->items);
263 free(n->title);
264 free(n->secondary);
265 free(n->footer);
266 free(n);
267 }
268}
269
270void ncselector_destroy(ncselector* n, char** item){
271 if(n){
272 if(item){
273 *item = n->items[n->selected].option;
274 n->items[n->selected].option = NULL;
275 }
276 ncselector_destroy_internal(n);
277 }
278}
279
282 logerror("won't use the standard plane"); // would fail later on resize
283 return NULL;
284 }
285 ncselector_options zeroed = {0};
286 if(!opts){
287 opts = &zeroed;
288 }
289 unsigned itemcount = 0;
290 if(opts->flags > 0){
291 logwarn("provided unsupported flags %016" PRIx64, opts->flags);
292 }
293 if(opts->items){
294 for(const struct ncselector_item* i = opts->items ; i->option ; ++i){
295 ++itemcount;
296 }
297 }
298 ncselector* ns = malloc(sizeof(*ns));
299 if(ns == NULL){
300 return NULL;
301 }
302 memset(ns, 0, sizeof(*ns));
303 if(opts->defidx && opts->defidx >= itemcount){
304 logerror("default index %u too large (%u items)", opts->defidx, itemcount);
305 goto freeitems;
306 }
307 ns->title = opts->title ? strdup(opts->title) : NULL;
308 ns->titlecols = opts->title ? ncstrwidth(opts->title, NULL, NULL) : 0;
309 ns->secondary = opts->secondary ? strdup(opts->secondary) : NULL;
310 ns->secondarycols = opts->secondary ? ncstrwidth(opts->secondary, NULL, NULL) : 0;
311 ns->footer = opts->footer ? strdup(opts->footer) : NULL;
312 ns->footercols = opts->footer ? ncstrwidth(opts->footer, NULL, NULL) : 0;
313 ns->selected = opts->defidx;
314 ns->longop = 0;
315 if( (ns->maxdisplay = opts->maxdisplay) ){
316 if(opts->defidx >= ns->maxdisplay){
317 ns->startdisp = opts->defidx - ns->maxdisplay + 1;
318 }else{
319 ns->startdisp = 0;
320 }
321 }else{
322 ns->startdisp = 0;
323 }
324 ns->longdesc = 0;
325 ns->opchannels = opts->opchannels;
326 ns->boxchannels = opts->boxchannels;
327 ns->descchannels = opts->descchannels;
328 ns->titlechannels = opts->titlechannels;
329 ns->footchannels = opts->footchannels;
330 ns->boxchannels = opts->boxchannels;
331 ns->darrowy = ns->uarrowy = ns->arrowx = -1;
332 if(itemcount){
333 if(!(ns->items = malloc(sizeof(*ns->items) * itemcount))){
334 goto freeitems;
335 }
336 }else{
337 ns->items = NULL;
338 }
339 for(ns->itemcount = 0 ; ns->itemcount < itemcount ; ++ns->itemcount){
340 const struct ncselector_item* src = &opts->items[ns->itemcount];
341 int unsafe = ncstrwidth(src->option, NULL, NULL);
342 if(unsafe < 0){
343 goto freeitems;
344 }
345 unsigned cols = unsafe;
346 ns->items[ns->itemcount].opcolumns = cols;
347 if(cols > ns->longop){
348 ns->longop = cols;
349 }
350 const char *desc = src->desc ? src->desc : "";
351 unsafe = ncstrwidth(desc, NULL, NULL);
352 if(unsafe < 0){
353 goto freeitems;
354 }
355 cols = unsafe;
356 ns->items[ns->itemcount].desccolumns = cols;
357 if(cols > ns->longdesc){
358 ns->longdesc = cols;
359 }
360 ns->items[ns->itemcount].option = strdup(src->option);
361 ns->items[ns->itemcount].desc = strdup(desc);
362 if(!(ns->items[ns->itemcount].desc && ns->items[ns->itemcount].option)){
363 free(ns->items[ns->itemcount].option);
364 free(ns->items[ns->itemcount].desc);
365 goto freeitems;
366 }
367 }
368 unsigned dimy, dimx;
369 ns->ncp = n;
370 ncselector_dim_yx(ns, &dimy, &dimx);
371 if(ncplane_resize_simple(n, dimy, dimx)){
372 goto freeitems;
373 }
374 if(ncplane_set_widget(ns->ncp, ns, (void(*)(void*))ncselector_destroy_internal)){
375 goto freeitems;
376 }
377 ncselector_draw(ns); // deal with error here?
378 return ns;
379
380freeitems:
381 while(ns->itemcount--){
382 free(ns->items[ns->itemcount].option);
383 free(ns->items[ns->itemcount].desc);
384 }
385 free(ns->items);
386 free(ns->title); free(ns->secondary); free(ns->footer);
387 free(ns);
389 return NULL;
390}
391
393 unsigned origdimy, origdimx;
394 ncselector_dim_yx(n, &origdimy, &origdimx);
395 size_t newsize = sizeof(*n->items) * (n->itemcount + 1);
396 struct ncselector_int* items = realloc(n->items, newsize);
397 if(!items){
398 return -1;
399 }
400 n->items = items;
401 n->items[n->itemcount].option = strdup(item->option);
402 const char *desc = item->desc ? item->desc : "";
403 n->items[n->itemcount].desc = strdup(desc);
404 int usafecols = ncstrwidth(item->option, NULL, NULL);
405 if(usafecols < 0){
406 return -1;
407 }
408 unsigned cols = usafecols;
409 n->items[n->itemcount].opcolumns = cols;
410 if(cols > n->longop){
411 n->longop = cols;
412 }
413 cols = ncstrwidth(desc, NULL, NULL);
414 n->items[n->itemcount].desccolumns = cols;
415 if(cols > n->longdesc){
416 n->longdesc = cols;
417 }
418 ++n->itemcount;
419 unsigned dimy, dimx;
420 ncselector_dim_yx(n, &dimy, &dimx);
421 if(origdimx < dimx || origdimy < dimy){ // resize if too small
422 ncplane_resize_simple(n->ncp, dimy, dimx);
423 }
424 return ncselector_draw(n);
425}
426
427int ncselector_delitem(ncselector* n, const char* item){
428 unsigned origdimy, origdimx;
429 ncselector_dim_yx(n, &origdimy, &origdimx);
430 bool found = false;
431 int maxop = 0, maxdesc = 0;
432 for(unsigned idx = 0 ; idx < n->itemcount ; ++idx){
433 if(strcmp(n->items[idx].option, item) == 0){ // found it
434 free(n->items[idx].option);
435 free(n->items[idx].desc);
436 if(idx < n->itemcount - 1){
437 memmove(n->items + idx, n->items + idx + 1, sizeof(*n->items) * (n->itemcount - idx - 1));
438 }else{
439 if(idx){
440 --n->selected;
441 }
442 }
443 --n->itemcount;
444 found = true;
445 --idx;
446 }else{
447 int cols = ncstrwidth(n->items[idx].option, NULL, NULL);
448 if(cols > maxop){
449 maxop = cols;
450 }
451 cols = ncstrwidth(n->items[idx].desc, NULL, NULL);
452 if(cols > maxdesc){
453 maxdesc = cols;
454 }
455 }
456 }
457 if(found){
458 n->longop = maxop;
459 n->longdesc = maxdesc;
460 unsigned dimy, dimx;
461 ncselector_dim_yx(n, &dimy, &dimx);
462 if(origdimx > dimx || origdimy > dimy){ // resize if too big
463 ncplane_resize_simple(n->ncp, dimy, dimx);
464 }
465 return ncselector_draw(n);
466 }
467 return -1; // wasn't found
468}
469
471 return n->ncp;
472}
473
474const char* ncselector_selected(const ncselector* n){
475 if(n->itemcount == 0){
476 return NULL;
477 }
478 return n->items[n->selected].option;
479}
480
482 const char* ret = NULL;
483 if(n->itemcount == 0){
484 return ret;
485 }
486 if(n->selected == n->startdisp){
487 if(n->startdisp-- == 0){
488 n->startdisp = n->itemcount - 1;
489 }
490 }
491 if(n->selected == 0){
492 n->selected = n->itemcount;
493 }
494 --n->selected;
495 ret = n->items[n->selected].option;
496 ncselector_draw(n);
497 return ret;
498}
499
501 const char* ret = NULL;
502 if(n->itemcount == 0){
503 return NULL;
504 }
505 unsigned lastdisp = n->startdisp;
506 lastdisp += n->maxdisplay && n->maxdisplay < n->itemcount ? n->maxdisplay : n->itemcount;
507 --lastdisp;
508 lastdisp %= n->itemcount;
509 if(lastdisp == n->selected){
510 if(++n->startdisp == n->itemcount){
511 n->startdisp = 0;
512 }
513 }
514 ++n->selected;
515 if(n->selected == n->itemcount){
516 n->selected = 0;
517 }
518 ret = n->items[n->selected].option;
519 ncselector_draw(n);
520 return ret;
521}
522
524 const int items_shown = ncplane_dim_y(n->ncp) - 4 - (n->title ? 2 : 0);
525 if(nc->id == NCKEY_BUTTON1 && nc->evtype == NCTYPE_RELEASE){
526 int y = nc->y, x = nc->x;
527 if(!ncplane_translate_abs(n->ncp, &y, &x)){
528 return false;
529 }
530 if(y == n->uarrowy && x == n->arrowx){
532 return true;
533 }else if(y == n->darrowy && x == n->arrowx){
535 return true;
536 }else if(n->uarrowy < y && y < n->darrowy){
537 // FIXME we probably only want to consider it a click if both the release
538 // and the depress happened to be on us. for now, just check release.
539 // FIXME verify that we're within the body walls!
540 // FIXME verify we're on the left of the split?
541 // FIXME verify that we're on a visible glyph?
542 int cury = (n->selected + n->itemcount - n->startdisp) % n->itemcount;
543 int click = y - n->uarrowy - 1;
544 while(click > cury){
546 ++cury;
547 }
548 while(click < cury){
550 --cury;
551 }
552 return true;
553 }
554 }else if(nc->evtype != NCTYPE_RELEASE){
555 if(nc->id == NCKEY_UP){
557 return true;
558 }else if(nc->id == NCKEY_DOWN){
560 return true;
561 }else if(nc->id == NCKEY_SCROLL_UP){
563 return true;
564 }else if(nc->id == NCKEY_SCROLL_DOWN){
566 return true;
567 }else if(nc->id == NCKEY_PGDOWN){
568 if(items_shown > 0){
569 for(int i = 0 ; i < items_shown ; ++i){
571 }
572 }
573 return true;
574 }else if(nc->id == NCKEY_PGUP){
575 if(items_shown > 0){
576 for(int i = 0 ; i < items_shown ; ++i){
578 }
579 }
580 return true;
581 }
582 }
583 return false;
584}
585
589
590// ideal body width given the ncselector's items and secondary/footer
591static unsigned
592ncmultiselector_body_width(const ncmultiselector* n){
593 unsigned cols = 0;
594 // the body is the maximum of
595 // * longop + longdesc + 5
596 // * secondary + 2
597 // * footer + 2
598 if(n->footercols + 2 > cols){
599 cols = n->footercols + 2;
600 }
601 if(n->secondarycols + 2 > cols){
602 cols = n->secondarycols + 2;
603 }
604 if(n->longitem + 7 > cols){
605 cols = n->longitem + 7;
606 }
607 return cols;
608}
609
610// redraw the multiselector widget in its entirety
611static int
612ncmultiselector_draw(ncmultiselector* n){
613 ncplane_erase(n->ncp);
615 nccell_set_fg_alpha(&transchar, NCALPHA_TRANSPARENT);
616 nccell_set_bg_alpha(&transchar, NCALPHA_TRANSPARENT);
617 // if we have a title, we'll draw a riser. the riser is two rows tall, and
618 // exactly four columns longer than the title, and aligned to the right. we
619 // draw a rounded box. the body will blow part or all of the bottom away.
620 unsigned yoff = 0;
621 if(n->title){
622 size_t riserwidth = n->titlecols + 4;
623 int offx = ncplane_halign(n->ncp, NCALIGN_RIGHT, riserwidth);
624 ncplane_cursor_move_yx(n->ncp, 0, 0);
625 if(offx){
626 ncplane_hline(n->ncp, &transchar, offx);
627 }
628 ncplane_rounded_box_sized(n->ncp, 0, n->boxchannels, 3, riserwidth, 0);
629 n->ncp->channels = n->titlechannels;
630 ncplane_printf_yx(n->ncp, 1, offx + 1, " %s ", n->title);
631 yoff += 2;
632 ncplane_cursor_move_yx(n->ncp, 1, 0);
633 if(offx){
634 ncplane_hline(n->ncp, &transchar, offx);
635 }
636 }
637 unsigned bodywidth = ncmultiselector_body_width(n);
638 unsigned dimy, dimx;
639 ncplane_dim_yx(n->ncp, &dimy, &dimx);
640 int xoff = ncplane_halign(n->ncp, NCALIGN_RIGHT, bodywidth);
641 if(xoff){
642 for(unsigned y = yoff + 1 ; y < dimy ; ++y){
643 ncplane_cursor_move_yx(n->ncp, y, 0);
644 ncplane_hline(n->ncp, &transchar, xoff);
645 }
646 }
647 ncplane_cursor_move_yx(n->ncp, yoff, xoff);
648 ncplane_rounded_box_sized(n->ncp, 0, n->boxchannels, dimy - yoff, bodywidth, 0);
649 if(n->title){
650 n->ncp->channels = n->boxchannels;
651 ncplane_putegc_yx(n->ncp, 2, dimx - 1, "┤", NULL);
652 if(bodywidth < dimx){
653 ncplane_putegc_yx(n->ncp, 2, dimx - bodywidth, "┬", NULL);
654 }
655 if((n->titlecols + 4 != dimx) && n->titlecols > n->secondarycols){
656 ncplane_putegc_yx(n->ncp, 2, dimx - (n->titlecols + 4), "┴", NULL);
657 }
658 }
659 // There is always at least one space available on the right for the
660 // secondary title and footer, but we'd prefer to use a few more if we can.
661 if(n->secondary){
662 int xloc = bodywidth - (n->secondarycols + 1) + xoff;
663 if(n->secondarycols < bodywidth - 2){
664 --xloc;
665 }
666 n->ncp->channels = n->footchannels;
667 ncplane_putstr_yx(n->ncp, yoff, xloc, n->secondary);
668 }
669 if(n->footer){
670 int xloc = bodywidth - (n->footercols + 1) + xoff;
671 if(n->footercols < bodywidth - 2){
672 --xloc;
673 }
674 n->ncp->channels = n->footchannels;
675 ncplane_putstr_yx(n->ncp, dimy - 1, xloc, n->footer);
676 }
677 // Top line of body (background and possibly up arrow)
678 ++yoff;
679 ncplane_cursor_move_yx(n->ncp, yoff, xoff + 1);
680 for(unsigned i = xoff + 1 ; i < dimx - 1 ; ++i){
681 nccell transc = NCCELL_TRIVIAL_INITIALIZER; // fall back to base cell
682 ncplane_putc(n->ncp, &transc);
683 }
684 const int bodyoffset = dimx - bodywidth + 2;
685 if(n->maxdisplay && n->maxdisplay < n->itemcount){
686 n->ncp->channels = n->descchannels;
687 n->arrowx = bodyoffset + 1;
688 ncplane_putegc_yx(n->ncp, yoff, n->arrowx, "↑", NULL);
689 }else{
690 n->arrowx = -1;
691 }
692 n->uarrowy = yoff;
693 unsigned printidx = n->startdisp;
694 unsigned printed = 0;
695 // visible option lines
696 for(yoff += 1 ; yoff < dimy - 2 ; ++yoff){
697 if(n->maxdisplay && printed == n->maxdisplay){
698 break;
699 }
700 ncplane_cursor_move_yx(n->ncp, yoff, xoff + 1);
701 for(unsigned i = xoff + 1 ; i < dimx - 1 ; ++i){
702 nccell transc = NCCELL_TRIVIAL_INITIALIZER; // fall back to base cell
703 ncplane_putc(n->ncp, &transc);
704 }
705 n->ncp->channels = n->descchannels;
706 if(printidx == n->current){
707 n->ncp->channels = (uint64_t)ncchannels_bchannel(n->descchannels) << 32u | ncchannels_fchannel(n->descchannels);
708 }
709 if(notcurses_canutf8(ncplane_notcurses(n->ncp))){
710 ncplane_putegc_yx(n->ncp, yoff, bodyoffset, n->items[printidx].selected ? "☒" : "☐", NULL);
711 }else{
712 ncplane_putchar_yx(n->ncp, yoff, bodyoffset, n->items[printidx].selected ? 'X' : '-');
713 }
714 n->ncp->channels = n->opchannels;
715 if(printidx == n->current){
716 n->ncp->channels = (uint64_t)ncchannels_bchannel(n->opchannels) << 32u | ncchannels_fchannel(n->opchannels);
717 }
718 ncplane_printf(n->ncp, " %s ", n->items[printidx].option);
719 n->ncp->channels = n->descchannels;
720 if(printidx == n->current){
721 n->ncp->channels = (uint64_t)ncchannels_bchannel(n->descchannels) << 32u | ncchannels_fchannel(n->descchannels);
722 }
723 ncplane_printf(n->ncp, "%s", n->items[printidx].desc);
724 if(++printidx == n->itemcount){
725 printidx = 0;
726 }
727 ++printed;
728 }
729 // Bottom line of body (background and possibly down arrow)
730 ncplane_cursor_move_yx(n->ncp, yoff, xoff + 1);
731 for(unsigned i = xoff + 1 ; i < dimx - 1 ; ++i){
732 nccell transc = NCCELL_TRIVIAL_INITIALIZER; // fall back to base cell
733 ncplane_putc(n->ncp, &transc);
734 }
735 if(n->maxdisplay && n->maxdisplay < n->itemcount){
736 n->ncp->channels = n->descchannels;
737 ncplane_putegc_yx(n->ncp, yoff, n->arrowx, "↓", NULL);
738 }
739 n->darrowy = yoff;
740 return 0;
741}
742
744 const char* ret = NULL;
745 if(n->itemcount == 0){
746 return ret;
747 }
748 if(n->current == n->startdisp){
749 if(n->startdisp-- == 0){
750 n->startdisp = n->itemcount - 1;
751 }
752 }
753 if(n->current == 0){
754 n->current = n->itemcount;
755 }
756 --n->current;
757 ret = n->items[n->current].option;
758 ncmultiselector_draw(n);
759 return ret;
760}
761
763 const char* ret = NULL;
764 if(n->itemcount == 0){
765 return NULL;
766 }
767 unsigned lastdisp = n->startdisp;
768 lastdisp += n->maxdisplay && n->maxdisplay < n->itemcount ? n->maxdisplay : n->itemcount;
769 --lastdisp;
770 lastdisp %= n->itemcount;
771 if(lastdisp == n->current){
772 if(++n->startdisp == n->itemcount){
773 n->startdisp = 0;
774 }
775 }
776 ++n->current;
777 if(n->current == n->itemcount){
778 n->current = 0;
779 }
780 ret = n->items[n->current].option;
781 ncmultiselector_draw(n);
782 return ret;
783}
784
786 const int items_shown = ncplane_dim_y(n->ncp) - 4 - (n->title ? 2 : 0);
787 if(nc->id == NCKEY_BUTTON1 && nc->evtype == NCTYPE_RELEASE){
788 int y = nc->y, x = nc->x;
789 if(!ncplane_translate_abs(n->ncp, &y, &x)){
790 return false;
791 }
792 if(y == n->uarrowy && x == n->arrowx){
794 return true;
795 }else if(y == n->darrowy && x == n->arrowx){
797 return true;
798 }else if(n->uarrowy < y && y < n->darrowy){
799 // FIXME we probably only want to consider it a click if both the release
800 // and the depress happened to be on us. for now, just check release.
801 // FIXME verify that we're within the body walls!
802 // FIXME verify we're on the left of the split?
803 // FIXME verify that we're on a visible glyph?
804 int cury = (n->current + n->itemcount - n->startdisp) % n->itemcount;
805 int click = y - n->uarrowy - 1;
806 while(click > cury){
808 ++cury;
809 }
810 while(click < cury){
812 --cury;
813 }
814 return true;
815 }
816 }else if(nc->evtype != NCTYPE_RELEASE){
817 if(nc->id == ' '){
818 n->items[n->current].selected = !n->items[n->current].selected;
819 ncmultiselector_draw(n);
820 return true;
821 }else if(nc->id == NCKEY_UP){
823 return true;
824 }else if(nc->id == NCKEY_DOWN){
826 return true;
827 }else if(nc->id == NCKEY_PGDOWN){
828 if(items_shown > 0){
829 for(int i = 0 ; i < items_shown ; ++i){
831 }
832 }
833 return true;
834 }else if(nc->id == NCKEY_PGUP){
835 if(items_shown > 0){
836 for(int i = 0 ; i < items_shown ; ++i){
838 }
839 }
840 return true;
841 }else if(nc->id == NCKEY_SCROLL_UP){
843 return true;
844 }else if(nc->id == NCKEY_SCROLL_DOWN){
846 return true;
847 }
848 }
849 return false;
850}
851
852// calculate the necessary dimensions based off properties of the selector and
853// the containing plane
854static int
855ncmultiselector_dim_yx(const ncmultiselector* n, unsigned* ncdimy, unsigned* ncdimx){
856 unsigned rows = 0, cols = 0; // desired dimensions
857 unsigned dimy, dimx; // dimensions of containing screen
858 ncplane_dim_yx(ncplane_parent(n->ncp), &dimy, &dimx);
859 if(n->title){ // header adds two rows for riser
860 rows += 2;
861 }
862 // we have a top line, a bottom line, two lines of margin, and must be able
863 // to display at least one row beyond that, so require five more
864 rows += 5;
865 if(rows > dimy){ // insufficient height to display selector
866 return -1;
867 }
868 rows += (!n->maxdisplay || n->maxdisplay > n->itemcount ? n->itemcount : n->maxdisplay) - 1; // rows necessary to display all options
869 if(rows > dimy){ // claw excess back
870 rows = dimy;
871 }
872 *ncdimy = rows;
873 cols = ncmultiselector_body_width(n);
874 // the riser, if it exists, is header + 4. the cols are the max of these two.
875 if(n->titlecols + 4 > cols){
876 cols = n->titlecols + 4;
877 }
878 if(cols > dimx){ // insufficient width to display selector
879 return -1;
880 }
881 *ncdimx = cols;
882 return 0;
883}
884
887 logerror("won't use the standard plane"); // would fail later on resize
888 return NULL;
889 }
890 ncmultiselector_options zeroed = {0};
891 if(!opts){
892 opts = &zeroed;
893 }
894 if(opts->flags > 0){
895 logwarn("provided unsupported flags %016" PRIx64, opts->flags);
896 }
897 unsigned itemcount = 0;
898 if(opts->items){
899 for(const struct ncmselector_item* i = opts->items ; i->option ; ++i){
900 ++itemcount;
901 }
902 }
903 ncmultiselector* ns = malloc(sizeof(*ns));
904 if(ns == NULL){
905 return NULL;
906 }
907 memset(ns, 0, sizeof(*ns));
908 ns->title = opts->title ? strdup(opts->title) : NULL;
909 ns->titlecols = opts->title ? ncstrwidth(opts->title, NULL, NULL) : 0;
910 ns->secondary = opts->secondary ? strdup(opts->secondary) : NULL;
911 ns->secondarycols = opts->secondary ? ncstrwidth(opts->secondary, NULL, NULL) : 0;
912 ns->footer = opts->footer ? strdup(opts->footer) : NULL;
913 ns->footercols = opts->footer ? ncstrwidth(opts->footer, NULL, NULL) : 0;
914 ns->current = 0;
915 ns->startdisp = 0;
916 ns->longitem = 0;
917 ns->maxdisplay = opts->maxdisplay;
918 ns->opchannels = opts->opchannels;
919 ns->boxchannels = opts->boxchannels;
920 ns->descchannels = opts->descchannels;
921 ns->titlechannels = opts->titlechannels;
922 ns->footchannels = opts->footchannels;
923 ns->boxchannels = opts->boxchannels;
924 ns->darrowy = ns->uarrowy = ns->arrowx = -1;
925 if(itemcount){
926 if(!(ns->items = malloc(sizeof(*ns->items) * itemcount))){
927 goto freeitems;
928 }
929 }else{
930 ns->items = NULL;
931 }
932 for(ns->itemcount = 0 ; ns->itemcount < itemcount ; ++ns->itemcount){
933 const struct ncmselector_item* src = &opts->items[ns->itemcount];
934 int unsafe = ncstrwidth(src->option, NULL, NULL);
935 if(unsafe < 0){
936 goto freeitems;
937 }
938 unsigned cols = unsafe;
939 if(cols > ns->longitem){
940 ns->longitem = cols;
941 }
942 unsafe = ncstrwidth(src->desc, NULL, NULL);
943 if(unsafe < 0){
944 goto freeitems;
945 }
946 unsigned cols2 = unsafe;
947 if(cols + cols2 > ns->longitem){
948 ns->longitem = cols + cols2;
949 }
950 ns->items[ns->itemcount].option = strdup(src->option);
951 ns->items[ns->itemcount].desc = strdup(src->desc);
952 ns->items[ns->itemcount].selected = src->selected;
953 if(!(ns->items[ns->itemcount].desc && ns->items[ns->itemcount].option)){
954 free(ns->items[ns->itemcount].option);
955 free(ns->items[ns->itemcount].desc);
956 goto freeitems;
957 }
958 }
959 unsigned dimy, dimx;
960 ns->ncp = n;
961 if(ncmultiselector_dim_yx(ns, &dimy, &dimx)){
962 goto freeitems;
963 }
964 if(ncplane_resize_simple(ns->ncp, dimy, dimx)){
965 goto freeitems;
966 }
967 if(ncplane_set_widget(ns->ncp, ns, (void(*)(void*))ncmultiselector_destroy)){
968 goto freeitems;
969 }
970 ncmultiselector_draw(ns); // deal with error here?
971 return ns;
972
973freeitems:
974 while(ns->itemcount--){
975 free(ns->items[ns->itemcount].option);
976 free(ns->items[ns->itemcount].desc);
977 }
978 free(ns->items);
979 free(ns->title); free(ns->secondary); free(ns->footer);
980 free(ns);
982 return NULL;
983}
984
986 if(n){
987 while(n->itemcount--){
988 free(n->items[n->itemcount].option);
989 free(n->items[n->itemcount].desc);
990 }
991 if(ncplane_set_widget(n->ncp, NULL, NULL) == 0){
992 ncplane_destroy(n->ncp);
993 }
994 free(n->items);
995 free(n->title);
996 free(n->secondary);
997 free(n->footer);
998 free(n);
999 }
1000}
1001
1003 if(n->itemcount != count || n->itemcount < 1){
1004 return -1;
1005 }
1006 while(--count){
1007 selected[count] = n->items[count].selected;
1008 }
1009 return 0;
1010}
uint32_t idx
Definition egcpool.h:298
free(duplicated)
#define logerror(fmt,...)
Definition logging.h:32
#define logwarn(fmt,...)
Definition logging.h:37
#define NCKEY_SCROLL_UP
Definition nckeys.h:192
#define NCKEY_SCROLL_DOWN
Definition nckeys.h:193
#define NCKEY_UP
Definition nckeys.h:37
#define NCKEY_DOWN
Definition nckeys.h:39
#define NCKEY_PGUP
Definition nckeys.h:45
#define NCKEY_BUTTON1
Definition nckeys.h:166
#define NCKEY_PGDOWN
Definition nckeys.h:44
int ncplane_cursor_move_yx(ncplane *n, int y, int x)
Definition notcurses.c:720
ncplane * notcurses_stdplane(notcurses *nc)
Definition notcurses.c:699
int ncplane_destroy(ncplane *ncp)
Definition notcurses.c:1018
int ncplane_putegc_yx(ncplane *n, int y, int x, const char *gclust, size_t *sbytes)
Definition notcurses.c:1995
int ncstrwidth(const char *egcs, int *validbytes, int *validwidth)
Definition notcurses.c:3309
bool ncplane_translate_abs(const ncplane *n, int *restrict y, int *restrict x)
Definition notcurses.c:2590
notcurses * ncplane_notcurses(const ncplane *n)
Definition notcurses.c:2626
void ncplane_erase(ncplane *n)
Definition notcurses.c:2458
ncplane * ncplane_parent(ncplane *n)
Definition notcurses.c:2651
void ncplane_dim_yx(const ncplane *n, unsigned *rows, unsigned *cols)
Definition notcurses.c:301
int y
Definition notcurses.h:1905
const struct ncplane_options * opts
Definition notcurses.h:3483
@ NCALIGN_RIGHT
Definition notcurses.h:84
@ NCTYPE_RELEASE
Definition notcurses.h:1198
#define NCALPHA_TRANSPARENT
Definition notcurses.h:106
vopts n
Definition notcurses.h:3502
int int x
Definition notcurses.h:1905
#define NCCELL_TRIVIAL_INITIALIZER
Definition notcurses.h:737
void ncselector_destroy(ncselector *n, char **item)
Definition selector.c:270
void ncmultiselector_destroy(ncmultiselector *n)
Definition selector.c:985
int ncmultiselector_selected(ncmultiselector *n, bool *selected, unsigned count)
Definition selector.c:1002
const char * ncselector_nextitem(ncselector *n)
Definition selector.c:500
ncplane * ncmultiselector_plane(ncmultiselector *n)
Definition selector.c:586
ncplane * ncselector_plane(ncselector *n)
Definition selector.c:470
const char * ncmultiselector_nextitem(ncmultiselector *n)
Definition selector.c:762
bool ncselector_offer_input(ncselector *n, const ncinput *nc)
Definition selector.c:523
ncselector * ncselector_create(ncplane *n, const ncselector_options *opts)
Definition selector.c:280
int ncselector_delitem(ncselector *n, const char *item)
Definition selector.c:427
int ncselector_additem(ncselector *n, const struct ncselector_item *item)
Definition selector.c:392
const char * ncselector_selected(const ncselector *n)
Definition selector.c:474
const char * ncmultiselector_previtem(ncmultiselector *n)
Definition selector.c:743
const char * ncselector_previtem(ncselector *n)
Definition selector.c:481
ncmultiselector * ncmultiselector_create(ncplane *n, const ncmultiselector_options *opts)
Definition selector.c:885
bool ncmultiselector_offer_input(ncmultiselector *n, const ncinput *nc)
Definition selector.c:785
ncintype_e evtype
Definition notcurses.h:1218
uint32_t id
Definition notcurses.h:1210
char * option
Definition selector.c:12
const char * option
Definition notcurses.h:3968
const char * desc
Definition notcurses.h:3969
char * secondary
Definition selector.c:50
unsigned maxdisplay
Definition selector.c:44
ncplane * ncp
Definition selector.c:41
uint64_t footchannels
Definition selector.c:57
unsigned longitem
Definition selector.c:45
unsigned startdisp
Definition selector.c:43
uint64_t descchannels
Definition selector.c:55
uint64_t titlechannels
Definition selector.c:56
unsigned itemcount
Definition selector.c:47
unsigned titlecols
Definition selector.c:49
uint64_t boxchannels
Definition selector.c:58
unsigned current
Definition selector.c:42
unsigned secondarycols
Definition selector.c:51
struct ncmselector_int * items
Definition selector.c:46
unsigned footercols
Definition selector.c:53
char * footer
Definition selector.c:52
uint64_t opchannels
Definition selector.c:54
char * desc
Definition selector.c:6
char * option
Definition selector.c:5
size_t desccolumns
Definition selector.c:8
size_t opcolumns
Definition selector.c:7
const char * option
Definition notcurses.h:3908
const char * desc
Definition notcurses.h:3909
char * secondary
Definition selector.c:28
unsigned maxdisplay
Definition selector.c:21
ncplane * ncp
Definition selector.c:18
uint64_t footchannels
Definition selector.c:35
unsigned longdesc
Definition selector.c:23
int darrowy
Definition selector.c:37
unsigned startdisp
Definition selector.c:20
uint64_t descchannels
Definition selector.c:33
uint64_t titlechannels
Definition selector.c:34
unsigned itemcount
Definition selector.c:25
unsigned titlecols
Definition selector.c:27
uint64_t boxchannels
Definition selector.c:36
int arrowx
Definition selector.c:37
unsigned secondarycols
Definition selector.c:29
unsigned longop
Definition selector.c:22
int uarrowy
Definition selector.c:37
unsigned footercols
Definition selector.c:31
char * footer
Definition selector.c:30
struct ncselector_int * items
Definition selector.c:24
unsigned selected
Definition selector.c:19
char * title
Definition selector.c:26
uint64_t opchannels
Definition selector.c:32
return NULL
Definition termdesc.h:229