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