1 #include "internal.h"
2
3 typedef struct nctabbed_opsint {
4 uint64_t selchan; // channel for the selected tab header
5 uint64_t hdrchan; // channel for unselected tab headers
6 uint64_t sepchan; // channel for the tab separator
7 char* separator; // separator string (copied by nctabbed_create())
8 uint64_t flags; // bitmask of NCTABBED_OPTION_*
9 } nctabbed_opsint;
10
11 typedef struct nctabbed {
12 ncplane* ncp; // widget ncplane
13 ncplane* p; // tab content ncplane
14 ncplane* hp; // tab headers ncplane
15 // a doubly-linked circular list of tabs
16 nctab* leftmost; // the tab most to the left
17 nctab* selected; // the currently selected tab
18 int tabcount; // tab separator (can be NULL)
19 int sepcols; // separator with in columns
20 nctabbed_opsint opts; // copied in nctabbed_create()
21 } nctabbed;
22
nctabbed_redraw(nctabbed * nt)23 void nctabbed_redraw(nctabbed* nt){
24 nctab* t;
25 unsigned drawn_cols = 0;
26 unsigned rows, cols;
27 if(nt->tabcount == 0){
28 // no tabs = nothing to draw
29 ncplane_erase(nt->hp);
30 return;
31 }
32 // update sizes for planes
33 ncplane_dim_yx(nt->ncp, &rows, &cols);
34 if(nt->opts.flags & NCTABBED_OPTION_BOTTOM){
35 ncplane_resize_simple(nt->hp, -1, cols);
36 ncplane_resize_simple(nt->p, rows - 1, cols);
37 ncplane_move_yx(nt->hp, rows - 2, 0);
38 }else{
39 ncplane_resize_simple(nt->hp, -1, cols);
40 ncplane_resize_simple(nt->p, rows - 1, cols);
41 }
42 // the callback draws the tab contents
43 if(nt->selected->cb){
44 nt->selected->cb(nt->selected, nt->p, nt->selected->curry);
45 }
46 // now we draw the headers
47 t = nt->leftmost;
48 ncplane_erase(nt->hp);
49 ncplane_set_channels(nt->hp, nt->opts.hdrchan);
50 do{
51 if(t == nt->selected){
52 ncplane_set_channels(nt->hp, nt->opts.selchan);
53 drawn_cols += ncplane_putstr(nt->hp, t->name);
54 ncplane_set_channels(nt->hp, nt->opts.hdrchan);
55 }else{
56 drawn_cols += ncplane_putstr(nt->hp, t->name);
57 }
58 // avoid drawing the separator after the last tab, or when we
59 // ran out of space, or when it's not set
60 if((t->next != nt->leftmost || drawn_cols >= cols) && nt->opts.separator){
61 ncplane_set_channels(nt->hp, nt->opts.sepchan);
62 drawn_cols += ncplane_putstr(nt->hp, nt->opts.separator);
63 ncplane_set_channels(nt->hp, nt->opts.hdrchan);
64 }
65 t = t->next;
66 }while(t != nt->leftmost && drawn_cols < cols);
67 }
68
nctabbed_ensure_selected_header_visible(nctabbed * nt)69 void nctabbed_ensure_selected_header_visible(nctabbed* nt){
70 nctab* t = nt->leftmost;
71 int cols = ncplane_dim_x(nt->hp);
72 int takencols = 0;
73 if(!t){
74 return;
75 }
76 //fprintf(stderr, "ensuring selected header visible\n");
77 do{
78 if(t == nt->selected){
79 break;
80 }
81 takencols += t->namecols + nt->sepcols;
82 if(takencols >= cols){
83 //fprintf(stderr, "not enough space, rotating\n");
84 takencols -= nt->leftmost->namecols + nt->sepcols;
85 nctabbed_rotate(nt, -1);
86 }
87 t = t->next;
88 //fprintf(stderr, "iteration over: takencols = %d, cols = %d\n", takencols, cols);
89 }while(t != nt->leftmost);
90 //fprintf(stderr, "ensuring done\n");
91 }
92
93 static bool
nctabbed_validate_opts(const nctabbed_options * opts)94 nctabbed_validate_opts(const nctabbed_options* opts){
95 if(opts->flags > NCTABBED_OPTION_BOTTOM){
96 logwarn("Provided unsupported flags 0x%016" PRIx64 "\n", opts->flags);
97 }
98 if(opts->sepchan && !opts->separator){
99 logwarn("Provided non-zero separator channel when separator is NULL")
100 }
101 return true;
102 }
103
nctabbed_selected(nctabbed * nt)104 nctab* nctabbed_selected(nctabbed* nt){
105 return nt->selected;
106 }
107
nctabbed_leftmost(nctabbed * nt)108 nctab* nctabbed_leftmost(nctabbed* nt){
109 return nt->leftmost;
110 }
111
nctabbed_tabcount(nctabbed * nt)112 int nctabbed_tabcount(nctabbed* nt){
113 return nt->tabcount;
114 }
115
nctabbed_plane(nctabbed * nt)116 ncplane* nctabbed_plane(nctabbed* nt){
117 return nt->ncp;
118 }
119
nctabbed_content_plane(nctabbed * nt)120 ncplane* nctabbed_content_plane(nctabbed* nt){
121 return nt->p;
122 }
123
nctab_cb(nctab * t)124 tabcb nctab_cb(nctab* t){
125 return t->cb;
126 }
127
nctab_name(nctab * t)128 const char* nctab_name(nctab* t){
129 return t->name;
130 }
131
nctab_name_width(nctab * t)132 int nctab_name_width(nctab* t){
133 return t->namecols;
134 }
135
nctab_userptr(nctab * t)136 void* nctab_userptr(nctab* t){
137 return t->curry;
138 }
139
nctab_next(nctab * t)140 nctab* nctab_next(nctab* t){
141 return t->next;
142 }
143
nctab_prev(nctab * t)144 nctab* nctab_prev(nctab* t){
145 return t->prev;
146 }
147
nctabbed_create(ncplane * n,const nctabbed_options * topts)148 nctabbed* nctabbed_create(ncplane* n, const nctabbed_options* topts){
149 nctabbed_options zeroed = {};
150 ncplane_options nopts = {};
151 unsigned nrows, ncols;
152 nctabbed* nt = NULL;
153 if(!topts){
154 topts = &zeroed;
155 }
156 if(!nctabbed_validate_opts(topts)){
157 goto err;
158 }
159 if((nt = malloc(sizeof(*nt))) == NULL){
160 logerror("Couldn't allocate nctabbed");
161 goto err;
162 }
163 nt->ncp = n;
164 nt->leftmost = nt->selected = NULL;
165 nt->tabcount = 0;
166 nt->sepcols = 0;
167 memcpy(&nt->opts, topts, sizeof(*topts));
168 nt->opts.separator = NULL;
169 nt->opts.selchan = topts->selchan;
170 nt->opts.hdrchan = topts->hdrchan;
171 nt->opts.sepchan = topts->sepchan;
172 nt->opts.flags = topts->flags;
173 if(topts->separator){
174 if((nt->opts.separator = strdup(topts->separator)) == NULL){
175 logerror("Couldn't allocate nctabbed separator");
176 goto err;
177 }
178 if((nt->sepcols = ncstrwidth(nt->opts.separator, NULL, NULL)) < 0){
179 logerror("Separator string contains illegal characters");
180 free(nt->opts.separator);
181 goto err;
182 }
183 }
184 ncplane_dim_yx(n, &nrows, &ncols);
185 if(topts->flags & NCTABBED_OPTION_BOTTOM){
186 nopts.y = nopts.x = 0;
187 nopts.cols = ncols;
188 nopts.rows = nrows - 1;
189 if((nt->p = ncplane_create(n, &nopts)) == NULL){
190 logerror("Couldn't create the tab content plane");
191 goto err;
192 }
193 nopts.y = nrows - 2;
194 nopts.rows = 1;
195 if((nt->hp = ncplane_create(n, &nopts)) == NULL){
196 logerror("Couldn't create the tab headers plane");
197 ncplane_destroy(nt->p);
198 goto err;
199 }
200 }else{
201 nopts.y = nopts.x = 0;
202 nopts.cols = ncols;
203 nopts.rows = 1;
204 if((nt->hp = ncplane_create(n, &nopts)) == NULL){
205 logerror("Couldn't create the tab headers plane");
206 goto err;
207 }
208 nopts.y = 1;
209 nopts.rows = nrows - 1;
210 if((nt->p = ncplane_create(n, &nopts)) == NULL){
211 logerror("Couldn't create the tab content plane");
212 ncplane_destroy(nt->hp);
213 goto err;
214 }
215 }
216 if(ncplane_set_widget(nt->ncp, nt, (void(*)(void*))nctabbed_destroy)){
217 ncplane_destroy(nt->hp);
218 ncplane_destroy(nt->p);
219 goto err;
220 }
221 nctabbed_redraw(nt);
222 return nt;
223
224 err:
225 ncplane_destroy_family(n);
226 if(nt){
227 free(nt->opts.separator);
228 free(nt);
229 }
230 return NULL;
231 }
232
nctabbed_add(nctabbed * nt,nctab * after,nctab * before,tabcb cb,const char * name,void * opaque)233 nctab* nctabbed_add(nctabbed* nt, nctab* after, nctab* before, tabcb cb,
234 const char* name, void* opaque){
235 nctab* t;
236 if(after && before){
237 if(after->next != before || before->prev != after){
238 logerror("bad before (%p) / after (%p) spec\n", before, after);
239 return NULL;
240 }
241 }else if(!after && !before){
242 // add it to the right of the selected tab
243 after = nt->selected;
244 }
245 if((t = malloc(sizeof(*t))) == NULL){
246 logerror("Couldn't allocate nctab")
247 return NULL;
248 }
249 if((t->name = strdup(name)) == NULL){
250 logerror("Couldn't allocate the tab name");
251 free(t);
252 return NULL;
253 }
254 if((t->namecols = ncstrwidth(name, NULL, NULL)) < 0){
255 logerror("Tab name contains illegal characters")
256 free(t->name);
257 free(t);
258 return NULL;
259 }
260 if(after){
261 t->next = after->next;
262 t->prev = after;
263 after->next = t;
264 t->next->prev = t;
265 }else if(before){
266 t->next = before;
267 t->prev = before->prev;
268 before->prev = t;
269 t->prev->next = t;
270 }else{
271 // the first tab
272 t->prev = t->next = t;
273 nt->leftmost = nt->selected = t;
274 }
275 t->nt = nt;
276 t->cb = cb;
277 t->curry = opaque;
278 ++nt->tabcount;
279 return t;
280 }
281
nctabbed_del(nctabbed * nt,nctab * t)282 int nctabbed_del(nctabbed* nt, nctab* t){
283 if(!t){
284 logerror("Provided NULL nctab");
285 return -1;
286 }
287 if(nt->tabcount == 1){
288 nt->leftmost = nt->selected = NULL;
289 }else{
290 if(nt->selected == t){
291 nt->selected = t->next;
292 }
293 if(nt->leftmost == t){
294 nt->leftmost = t->next;
295 }
296 t->next->prev = t->prev;
297 t->prev->next = t->next;
298 }
299 free(t->name);
300 free(t);
301 --nt->tabcount;
302 return 0;
303 }
304
nctab_move(nctabbed * nt,nctab * t,nctab * after,nctab * before)305 int nctab_move(nctabbed* nt __attribute__ ((unused)), nctab* t, nctab* after, nctab* before){
306 if(after && before){
307 if(after->prev != before || before->next != after){
308 logerror("bad before (%p) / after (%p) spec\n", before, after);
309 return -1;
310 }
311 }else if(!after && !before){
312 logerror("bad before (%p) / after (%p) spec\n", before, after);
313 return -1;
314 }
315 // bad things would happen
316 if(t == after || t == before){
317 logerror("Cannot move a tab before or after itself.");
318 return -1;
319 }
320 t->prev->next = t->next;
321 t->next->prev = t->prev;
322 if(after){
323 t->next = after->next;
324 t->prev = after;
325 after->next = t;
326 t->next->prev = t;
327 }else{
328 t->next = before;
329 t->prev = before->prev;
330 before->prev = t;
331 t->prev->next = t;
332 }
333 return 0;
334 }
335
nctab_move_right(nctabbed * nt,nctab * t)336 void nctab_move_right(nctabbed* nt, nctab* t){
337 if(t == nt->leftmost->prev){
338 nctab_move(nt, t, NULL, nt->leftmost);
339 nt->leftmost = t;
340 return;
341 }else if(t == nt->leftmost){
342 nt->leftmost = t->next;
343 }
344 nctab_move(nt, t, t->next, NULL);
345 }
346
nctab_move_left(nctabbed * nt,nctab * t)347 void nctab_move_left(nctabbed* nt, nctab* t){
348 if(t == nt->leftmost){
349 nt->leftmost = t->next;
350 nctab_move(nt, t, nt->leftmost->prev, NULL);
351 return;
352 }else if(t == nt->leftmost->next){
353 nt->leftmost = t;
354 }
355 nctab_move(nt, t, NULL, t->prev);
356 }
357
nctabbed_rotate(nctabbed * nt,int amt)358 void nctabbed_rotate(nctabbed* nt, int amt){
359 if(amt > 0){
360 for(int i = 0 ; i < amt ; ++i){
361 nt->leftmost = nt->leftmost->prev;
362 }
363 }else{
364 for(int i = 0 ; i < -amt ; ++i){
365 nt->leftmost = nt->leftmost->next;
366 }
367 }
368 }
369
nctabbed_next(nctabbed * nt)370 nctab* nctabbed_next(nctabbed* nt){
371 if(nt->tabcount == 0){
372 return NULL;
373 }
374 nt->selected = nt->selected->next;
375 return nt->selected;
376 }
377
nctabbed_prev(nctabbed * nt)378 nctab* nctabbed_prev(nctabbed* nt){
379 if(nt->tabcount == 0){
380 return NULL;
381 }
382 nt->selected = nt->selected->prev;
383 return nt->selected;
384 }
385
nctabbed_select(nctabbed * nt,nctab * t)386 nctab* nctabbed_select(nctabbed* nt, nctab* t){
387 nctab* prevsel = nt->selected;
388 nt->selected = t;
389 return prevsel;
390 }
391
nctabbed_channels(nctabbed * nt,uint64_t * RESTRICT hdrchan,uint64_t * RESTRICT selchan,uint64_t * RESTRICT sepchan)392 void nctabbed_channels(nctabbed* nt, uint64_t* RESTRICT hdrchan,
393 uint64_t* RESTRICT selchan, uint64_t* RESTRICT sepchan){
394 if(hdrchan){
395 memcpy(hdrchan, &nt->opts.hdrchan, sizeof(*hdrchan));
396 }
397 if(selchan){
398 memcpy(selchan, &nt->opts.selchan, sizeof(*selchan));
399 }
400 if(sepchan){
401 memcpy(sepchan, &nt->opts.sepchan, sizeof(*sepchan));
402 }
403 }
404
nctabbed_separator(nctabbed * nt)405 const char* nctabbed_separator(nctabbed* nt){
406 return nt->opts.separator;
407 }
408
nctabbed_separator_width(nctabbed * nt)409 int nctabbed_separator_width(nctabbed* nt){
410 return nt->sepcols;
411 }
412
nctabbed_destroy(nctabbed * nt)413 void nctabbed_destroy(nctabbed* nt){
414 if(!nt){
415 return;
416 }
417 if(ncplane_set_widget(nt->ncp, NULL, NULL) == 0){
418 nctab* t = nt->leftmost;
419 nctab* tmp;
420 if(t){
421 t->prev->next = NULL;
422 if(t->next){
423 t->next->prev = NULL;
424 }
425 }
426 while(t){
427 tmp = t->next;
428 free(t->name);
429 free(t);
430 t = tmp;
431 }
432 ncplane_destroy_family(nt->ncp);
433 free(nt->opts.separator);
434 free(nt);
435 }
436 }
437
nctabbed_set_hdrchan(nctabbed * nt,uint64_t chan)438 void nctabbed_set_hdrchan(nctabbed* nt, uint64_t chan){
439 nt->opts.hdrchan = chan;
440 }
441
nctabbed_set_selchan(nctabbed * nt,uint64_t chan)442 void nctabbed_set_selchan(nctabbed* nt, uint64_t chan){
443 nt->opts.selchan = chan;
444 }
445
nctabbed_set_sepchan(nctabbed * nt,uint64_t chan)446 void nctabbed_set_sepchan(nctabbed* nt, uint64_t chan){
447 nt->opts.sepchan = chan;
448 }
449
nctab_set_cb(nctab * t,tabcb newcb)450 tabcb nctab_set_cb(nctab* t, tabcb newcb){
451 tabcb prevcb = t->cb;
452 t->cb = newcb;
453 return prevcb;
454 }
455
nctab_set_name(nctab * t,const char * newname)456 int nctab_set_name(nctab* t, const char* newname){
457 int newnamecols;
458 char* prevname = t->name;
459 if((newnamecols = ncstrwidth(newname, NULL, NULL)) < 0){
460 logerror("New tab name contains illegal characters");
461 return -1;
462 }
463 if((t->name = strdup(newname)) == NULL){
464 logerror("Couldn't allocate new tab name");
465 t->name = prevname;
466 return -1;
467 }
468 free(prevname);
469 t->namecols = newnamecols;
470 return 0;
471 }
472
nctab_set_userptr(nctab * t,void * newopaque)473 void* nctab_set_userptr(nctab* t, void* newopaque){
474 void* prevcurry = t->curry;
475 t->curry = newopaque;
476 return prevcurry;
477 }
478
nctabbed_set_separator(nctabbed * nt,const char * separator)479 int nctabbed_set_separator(nctabbed* nt, const char* separator){
480 int newsepcols;
481 char* prevsep = nt->opts.separator;
482 if((newsepcols = ncstrwidth(separator, NULL, NULL)) < 0){
483 logerror("New tab separator contains illegal characters");
484 return -1;
485 }
486 if((nt->opts.separator = strdup(separator)) == NULL){
487 logerror("Couldn't allocate new tab separator");
488 nt->opts.separator = prevsep;
489 return -1;
490 }
491 free(prevsep);
492 nt->sepcols = newsepcols;
493 return 0;
494 }
495