Notcurses 3.0.16
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
tree.c
Go to the documentation of this file.
1#include "internal.h"
2
3// these are never allocated themselves, but always as arrays of object
10
11typedef struct nctree {
12 int (*cbfxn)(ncplane*, void*, int);
13 nctree_int_item items; // topmost set of items, holds widget plane
14 nctree_int_item* curitem; // item addressed by the path
15 unsigned maxdepth; // maximum hierarchy level
16 unsigned* currentpath; // array of |maxdepth|+1 elements, ended by UINT_MAX
17 int activerow; // active row -1 <= activerow < dimy
18 int indentcols; // cols to indent per level
19 uint64_t bchannels; // border glyph channels
21
22static void
23nctree_debug_path(const unsigned* path, const void* pr){
24 fprintf(stderr, "PATH ");
25 for(const unsigned* p = path ; *p != UINT_MAX ; ++p){
26 fprintf(stderr, "%03u ", *p);
27 }
28 fprintf(stderr, "%p\n", pr);
29}
30
31static void
32nctree_debug_internal(const nctree_int_item* nii, unsigned* path, unsigned depth){
33 if(depth){
34 nctree_debug_path(path, nii->curry);
35 }
36 for(unsigned z = 0 ; z < nii->subcount ; ++z){
37 path[depth] = z;
38 path[depth + 1] = UINT_MAX;
39 nctree_debug_internal(&nii->subs[z], path, depth + 1);
40 }
41 path[depth] = UINT_MAX;
42}
43
44__attribute__ ((unused)) static void
45nctree_debug(const nctree* n){
46 unsigned* path = malloc(sizeof(*path) * (n->maxdepth + 2));
47 path[0] = UINT_MAX;
48 nctree_debug_internal(&n->items, path, 0);
49 free(path);
50}
51
52// recursively free an array of nctree_int_item; nctree_int_item structs are
53// never individually free()d, just their innards
54static void
55free_tree_items(nctree_int_item* iarray){
56 for(unsigned c = 0 ; c < iarray->subcount ; ++c){
57 free_tree_items(&iarray->subs[c]);
58 }
59 ncplane_destroy(iarray->ncp);
60 free(iarray->subs);
61}
62
63// allocates a |count|-sized array of nctree_int_items, and fills |fill| in,
64// using |items|. updates |*maxdepth| when appropriate.
65static int
66dup_tree_items(nctree_int_item* fill, const struct nctree_item* items,
67 unsigned count, unsigned depth, unsigned* maxdepth){
68 fill->subcount = count;
69 // FIXME perhaps better to alloc a page at a time and take all items from
70 // there, for better TLB performance?
71 fill->subs = malloc(sizeof(*fill->subs) * count);
72 if(fill->subs == NULL){
73 return -1;
74 }
75 for(unsigned c = 0 ; c < fill->subcount ; ++c){
76 nctree_int_item* nii = &fill->subs[c];
77 nii->curry = items[c].curry;
78 if(nii->curry == NULL){
79 while(c--){
80 free_tree_items(&fill->subs[c]);
81 }
82 free(fill->subs);
83 return -1;
84 }
85 nii->ncp = NULL;
86 if(dup_tree_items(nii, items[c].subs, items[c].subcount, depth + 1, maxdepth)){
87 while(c--){
88 free_tree_items(&fill->subs[c]);
89 }
90 free(fill->subs);
91 return -1;
92 }
93 }
94 if(depth > *maxdepth){
95 *maxdepth = depth;
96 }
97 return 0;
98}
99
100static void
101goto_last_item(nctree* n){
102 void* prev = NULL;
103 void* r;
104 while((r = nctree_next(n))){
105 if(r == prev){
106 return;
107 }
108 prev = r;
109 }
110}
111
112static void
113goto_first_item(nctree* n){
114 if(n->maxdepth == 0){
115 n->currentpath[0] = UINT_MAX;
116 n->curitem = NULL;
117 n->activerow = -1;
118 }else{
119 n->currentpath[0] = 0;
120 n->currentpath[1] = UINT_MAX;
121 n->curitem = &n->items.subs[0];
122 n->activerow = 0;
123 }
124}
125
126// the initial path ought point to the first item.
127static int
128prep_initial_path(nctree* n, unsigned maxdepth){
129 n->currentpath = malloc(sizeof(*n->currentpath) * (maxdepth + 2));
130 if(n->currentpath == NULL){
131 return -1;
132 }
133 goto_first_item(n);
134 return 0;
135}
136
137static nctree*
138nctree_inner_create(ncplane* n, const nctree_options* opts){
139 nctree* ret = malloc(sizeof(*ret));
140 if(ret){
141 ret->cbfxn = opts->nctreecb;
142 ret->indentcols = opts->indentcols;
143 ret->maxdepth = 0;
144 if(dup_tree_items(&ret->items, opts->items, opts->count, 0, &ret->maxdepth)){
145 free(ret);
146 return NULL;
147 }
148//fprintf(stderr, "MAXDEPTH: %u\n", ret->maxdepth);
149 if(prep_initial_path(ret, ret->maxdepth)){
150 free_tree_items(&ret->items);
151 free(ret);
152 return NULL;
153 }
154 ret->items.ncp = n;
155 ret->items.curry = NULL;
156 nctree_redraw(ret);
157 }
158 return ret;
159}
160
161// add the single item (*not* the hierarchy) of |add| at |spec|. the |spec|
162// must be valid along the path.
163// precondition: spec[0] != UINT_MAX
164static int
165nctree_add_internal(nctree* n, nctree_int_item* nii, const unsigned* spec,
166 const struct nctree_item* add){
167 const unsigned* p = spec;
168 while(p[1] != UINT_MAX){ // we know p[0] isn't UINT_MAX
169 if(*p >= nii->subcount){
170 logerror("invalid path element (%u >= %u)", *p, nii->subcount);
171 return -1;
172 }
173 nii = &nii->subs[*p];
174 ++p;
175 }
176 // we're at the node into which |add| ought be inserted
177 // this last one can be equal to subcount; we're placing it at the end
178 if(*p > nii->subcount){
179 logerror("invalid path element (%u >= %u)", *p, nii->subcount);
180 return -1;
181 }
182 struct nctree_int_item* tmparr = realloc(nii->subs, sizeof(*nii->subs) * (nii->subcount + 1));
183 if(tmparr == NULL){
184 return -1;
185 }
186 nii->subs = tmparr;
187 if(*p != nii->subcount){
188 memmove(&nii->subs[*p + 1], &nii->subs[*p],
189 sizeof(*nii->subs) * (nii->subcount - *p));
190 }
191 ++nii->subcount;
192 if((unsigned)(p - spec) >= n->maxdepth){
193 unsigned max = p - spec + 1;
194 unsigned* tmp = realloc(n->currentpath, sizeof(*n->currentpath) * (max + 2));
195 if(tmp == NULL){
196 return -1;
197 }
198 n->currentpath = tmp;
199 n->currentpath[max] = UINT_MAX;
200 n->maxdepth = max;
201 }
202 nii->subs[*p].subs = NULL;
203 nii->subs[*p].subcount = 0;
204 nii->subs[*p].curry = add->curry;
205 nii->subs[*p].ncp = NULL;
206 return 0;
207}
208
209int nctree_add(nctree* n, const unsigned* spec, const struct nctree_item* add){
210 // it's illegal to pass an empty path for addition; one must pass { 0, UINT_MAX }
211 if(spec[0] == UINT_MAX){
212 logerror("invalid empty path");
213 return -1;
214 }
215 if(add->subs){
216 logerror("invalid subs %p", add->subs);
217 return -1;
218 }
219 if(add->subcount){
220 logerror("invalid subcount %u", add->subcount);
221 return -1;
222 }
223 if(nctree_add_internal(n, &n->items, spec, add)){
224 return -1;
225 }
226 if(n->activerow == -1){
227 n->activerow = 0;
228 n->curitem = &n->items.subs[0];
229 n->currentpath = malloc(sizeof(*n->currentpath) * 3);
230 n->currentpath[0] = 0;
231 n->currentpath[1] = UINT_MAX;
232 n->maxdepth = 1;
233 }
234 return 0;
235}
236
237int nctree_del(nctree* n, const unsigned* spec){
238 nctree_int_item* parent = NULL;
239 nctree_int_item* nii = &n->items;
240 const unsigned* p = spec;
241 while(*p != UINT_MAX){
242 if(*p >= nii->subcount){
243 logerror("invalid path element (%u >= %u)", *p, nii->subcount);
244 return -1;
245 }
246 parent = nii;
247 nii = &nii->subs[*p];
248 ++p;
249 }
250 free_tree_items(nii);
251 if(parent){
252 // parent can only be defined if we had at least one path element
253 unsigned lastelem = spec[-1];
254 if(lastelem != --parent->subcount){
255 memmove(&parent->subs[lastelem], &parent->subs[lastelem + 1],
256 sizeof(*parent->subs) * (parent->subcount - lastelem));
257 }
258 }
259 if(n->items.subcount == 0){
260 n->activerow = -1;
261 n->curitem = NULL;
262 }
263 return 0;
264}
265
267 if(opts->flags){
268 logwarn("passed invalid flags 0x%016" PRIx64, opts->flags);
269 }
271 logerror("can't use the standard plane");
272 goto error;
273 }
274 if(opts->nctreecb == NULL){
275 logerror("can't use NULL callback");
276 goto error;
277 }
278 if(opts->indentcols < 0){
279 logerror("can't indent negative columns");
280 goto error;
281 }
282 nctree* ret = nctree_inner_create(n, opts);
283 if(ret == NULL){
284 logerror("couldn't prepare nctree");
285 goto error;
286 }
287 return ret;
288
289error:
291 return NULL;
292}
293
295 if(n){
296 free_tree_items(&n->items);
297 free(n->currentpath);
298 free(n);
299 }
300}
301
302// Returns the ncplane on which this nctree lives.
304 return n->items.ncp;
305}
306
307// the prev is either:
308// the item to the left, if the last path component is 0, or
309// a drop from the rightmost non-zero path component, extended out to the right, or
310// the current item
311// so we can always just go to the last path component, act there, and possibly
312// extend it out to the maximal topright.
313static nctree_int_item*
314nctree_prev_internal(nctree* n, unsigned* newpath){
315 nctree_int_item* nii = &n->items;
316 nctree_int_item* wedge = NULL; // tracks the rightmost non-zero path
317 int idx = 0;
318 while(newpath[idx] != UINT_MAX){
319 nii = &nii->subs[newpath[idx]];
320 if(idx == 0){
321 wedge = &n->items;
322 }else{// if(idx > 1){
323 wedge = &wedge->subs[newpath[idx - 1]];
324 }
325 ++idx;
326 }
327 --idx;
328 if(newpath[idx]){
329 --newpath[idx];
330 nii = &wedge->subs[newpath[idx]];
331 ++idx;
332//fprintf(stderr, "nii->subcount: %u idx: %d\n", nii->subcount, idx);
333 while(nii->subcount){
334 newpath[idx] = nii->subcount - 1;
335 nii = &nii->subs[newpath[idx]];
336 ++idx;
337//fprintf(stderr, "nii->subcount: %u idx: %d\n", nii->subcount, idx);
338 }
339 newpath[idx] = UINT_MAX;
340 return nii;
341 }
342 if(wedge == &n->items){
343 return nii; // no change
344 }
345 newpath[idx] = UINT_MAX;
346 return wedge;
347}
348
350 int rows = 0;
351 if(n->curitem->ncp){
352 rows = ncplane_dim_y(n->curitem->ncp);
353 }
354 nctree_int_item* tmp = nctree_prev_internal(n, n->currentpath);
355 if(tmp != n->curitem){
356 n->curitem = tmp;
357 n->activerow -= rows;
358 if(n->activerow < 0){
359 n->activerow = 0;
360 }
361 }
362 return n->curitem->curry;
363}
364
365// the next is either:
366// - an extension to the right, if subs are available, or
367// - a bump to the rightmost path component with subcount available, or
368// - the current item
369static nctree_int_item*
370nctree_next_internal(nctree* n, unsigned* newpath){
371 nctree_int_item* nii = &n->items;
372 nctree_int_item* wedge = NULL; // tracks the rightmost with room in subs
373 int idx = 0;
374 int wedidx = 0;
375 while(newpath[idx] != UINT_MAX){
376 if(newpath[idx] < nii->subcount - 1){
377 wedge = nii;
378 wedidx = idx;
379 }
380 nii = &nii->subs[newpath[idx]];
381 ++idx;
382 }
383 if(nii->subcount){
384 newpath[idx] = 0;
385 newpath[idx + 1] = UINT_MAX;
386 return &nii->subs[newpath[idx]];
387 }
388 if(wedge){
389 ++newpath[wedidx];
390 newpath[wedidx + 1] = UINT_MAX;
391 return &wedge->subs[newpath[wedidx]];
392 }
393 return nii;
394}
395
397 int rows = 0;
398 if(n->curitem->ncp){
399 rows = ncplane_dim_y(n->curitem->ncp);
400 }
401 nctree_int_item* tmp = nctree_next_internal(n, n->currentpath);
402 if(tmp != n->curitem){
403 n->curitem = tmp;
404 n->activerow += rows;
405 if(n->activerow >= (int)ncplane_dim_y(n->items.ncp)){
406 n->activerow = ncplane_dim_y(n->items.ncp) - 1;
407 }
408 }
409 return n->curitem->curry;
410}
411
412static int
413tree_path_length(const unsigned* path){
414 int len = 0;
415 while(path[len] != UINT_MAX){
416 ++len;
417 }
418 return len;
419}
420
421// draw the item. if *|frontiert| == *|frontierb|, we're the current item, and
422// can use all the available space. if *|frontiert| < 0, draw down from
423// *|frontierb|. otherwise, draw up from *|frontiert|.
424static int
425draw_tree_item(nctree* n, nctree_int_item* nii, const unsigned* path,
426 int* frontiert, int* frontierb, int distance){
427 if(!nii->ncp){
428 const int startx = (tree_path_length(path) - 1) * n->indentcols;
429 int ymin, ymax;
430 if(*frontiert == *frontierb){
431 ymin = 0;
432 ymax = ncplane_dim_y(n->items.ncp) - 1;
433 }else if(*frontiert < 0){
434 ymin = *frontierb;
435 ymax = ncplane_dim_y(n->items.ncp) - 1;
436 }else{
437 ymin = 0;
438 ymax = *frontiert;
439 }
440//fprintf(stderr, "x: %d y: %d\n", startx, ymin);
441 struct ncplane_options nopts = {
442 .x = startx,
443 .y = ymin,
444 .cols = ncplane_dim_x(n->items.ncp) - startx,
445 .rows = ymax - ymin + 1,
446 .userptr = NULL,
447 .name = NULL,
448 .resizecb = NULL,
449 .flags = 0,
450 };
451 nii->ncp = ncplane_create(n->items.ncp, &nopts);
452 if(nii->ncp == NULL){
453 return -1;
454 }
455 }else{
456 // FIXME possibly enlarge nii->ncp?
457 }
458 if(ncplane_y(nii->ncp) <= *frontiert || *frontierb >= (int)ncplane_dim_y(n->items.ncp)){
459 ncplane_move_yx(nii->ncp, *frontiert, ncplane_x(nii->ncp));
460 }else{
461 ncplane_move_yx(nii->ncp, *frontierb, ncplane_x(nii->ncp));
462 }
463 int ret = n->cbfxn(nii->ncp, nii->curry, distance);
464 if(ret < 0){
465 return -1;
466 }
467 // FIXME shrink plane if it was enlarged
468//fprintf(stderr, "ft: %d fb: %d %p ncplane_y: %d\n", *frontiert, *frontierb, nii->ncp, ncplane_y(nii->ncp));
469 if(ncplane_y(nii->ncp) <= *frontiert){
470 *frontiert = ncplane_y(nii->ncp) - 1;
471 }
472 if(ncplane_y(nii->ncp) + (int)ncplane_dim_y(nii->ncp) > *frontierb){
473 *frontierb = ncplane_y(nii->ncp) + ncplane_dim_y(nii->ncp);
474 }
475 return 0;
476}
477
478// iterate backwards from tmppath, destroying any ncplanes we find. they've
479// been pushed off-screen. tmppath is changed as we iterate. nii will not be
480// destroyed, only items above nii.
481static int
482destroy_above(nctree* n, nctree_int_item* nii, unsigned* path, int distance){
483 nctree_int_item* tmpnii;
484 while((tmpnii = nctree_prev_internal(n, path)) != nii){
485 nii = tmpnii;
486 --distance;
487 if(nii->ncp){
488 ncplane_destroy(nii->ncp);
489 nii->ncp = NULL;
490 n->cbfxn(nii->ncp, nii->curry, distance);
491 }
492 }
493 return 0;
494}
495
496// iterate forwards from tmppath, destroying any ncplanes we find. they've
497// been pushed off-screen. tmppath is changed as we iterate. nii will not be
498// destroyed, only items below nii.
499static int
500destroy_below(nctree* n, nctree_int_item* nii, unsigned* path, int distance){
501 nctree_int_item* tmpnii;
502 while((tmpnii = nctree_next_internal(n, path)) != nii){
503 nii = tmpnii;
504 ++distance;
505 if(nii->ncp){
506 ncplane_destroy(nii->ncp);
507 nii->ncp = NULL;
508 n->cbfxn(nii->ncp, nii->curry, distance);
509 }
510 }
511 return 0;
512}
513
514// tmppath ought be initialized with currentpath, but having size sufficient
515// to hold n->maxdepth + 1 unsigneds.
516static int
517nctree_inner_redraw(nctree* n, unsigned* tmppath){
518 if(n->activerow < 0){
519 return 0;
520 }
521 ncplane* ncp = n->items.ncp;
522 if(ncplane_cursor_move_yx(ncp, n->activerow, 0)){
523 return -1;
524 }
525 int frontiert = n->activerow;
526 int frontierb = n->activerow;
527 nctree_int_item* nii = n->curitem;
528 int distance = 0;
529 // draw the focused item
530 if(draw_tree_item(n, nii, tmppath, &frontiert, &frontierb, distance)){
531 return -1;
532 }
533 nctree_int_item* tmpnii;
534 // draw items above the current one
535 while(frontiert >= 0){
536 if((tmpnii = nctree_prev_internal(n, tmppath)) == nii){
537 break;
538 }
539 nii = tmpnii;
540 --distance;
541 if(draw_tree_item(n, nii, tmppath, &frontiert, &frontierb, distance)){
542 return -1;
543 }
544 }
545 destroy_above(n, nii, tmppath, distance);
546 // move items up if there is a gap at the top FIXME
547 if(frontiert >= 0){
548 }
549 distance = 0;
550 n->activerow = ncplane_y(n->curitem->ncp);
551 nii = n->curitem;
552 // draw items below the current one FIXME
553 memcpy(tmppath, n->currentpath, sizeof(*tmppath) * (n->maxdepth + 1));
554 while(frontierb < (int)ncplane_dim_y(n->items.ncp)){
555 if((tmpnii = nctree_next_internal(n, tmppath)) == nii){
556 break;
557 }
558 nii = tmpnii;
559 ++distance;
560 if(draw_tree_item(n, nii, tmppath, &frontiert, &frontierb, distance)){
561 return -1;
562 }
563 }
564 destroy_below(n, nii, tmppath, distance);
565 return 0;
566}
567
569 unsigned* tmppath = malloc(sizeof(*tmppath) * (n->maxdepth + 2));
570 if(tmppath == NULL){
571 return -1;
572 }
573 memcpy(tmppath, n->currentpath, sizeof(*tmppath) * (n->maxdepth + 1));
574 int ret = nctree_inner_redraw(n, tmppath);
575 free(tmppath);
576 return ret;
577}
578
580 if(ni->evtype == NCTYPE_RELEASE){
581 return false;
582 }
583 if(ni->id == NCKEY_UP){
584 nctree_prev(n);
585 return true;
586 }else if(ni->id == NCKEY_DOWN){
587 nctree_next(n);
588 return true;
589 }else if(ni->id == NCKEY_PGUP){
590 nctree_prev(n); // more FIXME
591 return true;
592 }else if(ni->id == NCKEY_PGDOWN){
593 nctree_next(n); // more FIXME
594 return true;
595 }else if(ni->id == NCKEY_HOME){
596 goto_first_item(n);
597 return true;
598 }else if(ni->id == NCKEY_END){
599 goto_last_item(n);
600 return true;
601 }
602 // FIXME implement left, right, +, - (expand/collapse)
603 return false;
604}
605
607 return n->curitem->curry;
608}
609
610void* nctree_goto(nctree* n, const unsigned* spec, int* failspec){
611 n->activerow = 0;
612 (void)spec;
613 (void)failspec;
614 // FIXME
615 return NULL;
616}
const nccell * c
Definition egcpool.h:207
uint32_t idx
Definition egcpool.h:209
int r
Definition fbuf.h:226
#define logerror(fmt,...)
Definition logging.h:32
#define logwarn(fmt,...)
Definition logging.h:37
#define NCKEY_END
Definition nckeys.h:47
#define NCKEY_UP
Definition nckeys.h:37
#define NCKEY_DOWN
Definition nckeys.h:39
#define NCKEY_PGUP
Definition nckeys.h:45
#define NCKEY_HOME
Definition nckeys.h:46
#define NCKEY_PGDOWN
Definition nckeys.h:44
int ncplane_cursor_move_yx(ncplane *n, int y, int x)
Definition notcurses.c:723
int ncplane_x(const ncplane *n)
Definition notcurses.c:2447
int ncplane_y(const ncplane *n)
Definition notcurses.c:2440
ncplane * notcurses_stdplane(notcurses *nc)
Definition notcurses.c:702
int ncplane_destroy(ncplane *ncp)
Definition notcurses.c:1021
int ncplane_move_yx(ncplane *n, int y, int x)
Definition notcurses.c:2417
notcurses * ncplane_notcurses(const ncplane *n)
Definition notcurses.c:2631
ncplane * ncplane_create(ncplane *n, const ncplane_options *nopts)
Definition notcurses.c:710
const struct ncplane_options * opts
Definition notcurses.h:3487
@ NCTYPE_RELEASE
Definition notcurses.h:1198
vopts n
Definition notcurses.h:3506
API int API int const nccell unsigned len
Definition notcurses.h:2592
ncintype_e evtype
Definition notcurses.h:1218
uint32_t id
Definition notcurses.h:1210
ncplane * ncp
Definition tree.c:6
unsigned subcount
Definition tree.c:7
struct nctree_int_item * subs
Definition tree.c:8
void * curry
Definition tree.c:5
unsigned subcount
Definition notcurses.h:4048
void * curry
Definition notcurses.h:4046
struct nctree_item * subs
Definition notcurses.h:4047
Definition tree.c:11
int(* cbfxn)(ncplane *, void *, int)
Definition tree.c:12
nctree_int_item items
Definition tree.c:13
int activerow
Definition tree.c:17
int indentcols
Definition tree.c:18
nctree_int_item * curitem
Definition tree.c:14
unsigned maxdepth
Definition tree.c:15
unsigned * currentpath
Definition tree.c:16
uint64_t bchannels
Definition tree.c:19
return NULL
Definition termdesc.h:229
bool nctree_offer_input(nctree *n, const ncinput *ni)
Definition tree.c:579
int nctree_redraw(nctree *n)
Definition tree.c:568
__attribute__((unused))
Definition tree.c:44
nctree * nctree_create(ncplane *n, const nctree_options *opts)
Definition tree.c:266
void * nctree_goto(nctree *n, const unsigned *spec, int *failspec)
Definition tree.c:610
ncplane * nctree_plane(nctree *n)
Definition tree.c:303
void nctree_destroy(nctree *n)
Definition tree.c:294
int nctree_add(nctree *n, const unsigned *spec, const struct nctree_item *add)
Definition tree.c:209
int nctree_del(nctree *n, const unsigned *spec)
Definition tree.c:237
void * nctree_focused(nctree *n)
Definition tree.c:606
void * nctree_prev(nctree *n)
Definition tree.c:349
void * nctree_next(nctree *n)
Definition tree.c:396