1 #include "tickit.h"
2 #include "bindings.h"
3 
4 #include <stdarg.h>
5 #include <stdio.h>   /* sscanf */
6 #include <stdlib.h>
7 #include <string.h>
8 
9 #define streq(a,b) (!strcmp(a,b))
10 
11 #define COLOUR_DEFAULT -1
12 
13 struct TickitPen {
14   signed   int fgindex : 9, /* 0 - 255 or COLOUR_DEFAULT */
15                bgindex : 9; /* 0 - 255 or COLOUR_DEFAULT */
16   TickitPenRGB8 fg_rgb8,
17                 bg_rgb8;
18 
19   unsigned int bold    : 1,
20                italic  : 1,
21                reverse : 1,
22                strike  : 1,
23                blink   : 1;
24 
25   signed   int under   : 3; /* 0 to 3 or -1 */
26   signed   int altfont : 5; /* 1 - 10 or -1 */
27 
28   struct {
29     unsigned int fgindex : 1,
30                  bgindex : 1,
31                  fg_rgb8 : 1,
32                  bg_rgb8 : 1,
33                  bold    : 1,
34                  under   : 1,
35                  italic  : 1,
36                  reverse : 1,
37                  strike  : 1,
38                  altfont : 1,
39                  blink   : 1;
40   } valid;
41 
42   int refcount;
43   struct TickitBindings bindings;
44   int freezecount;
45   bool changed;
46 };
47 
DEFINE_BINDINGS_FUNCS(pen,TickitPen,TickitPenEventFn)48 DEFINE_BINDINGS_FUNCS(pen,TickitPen,TickitPenEventFn)
49 
50 TickitPen *tickit_pen_new(void)
51 {
52   TickitPen *pen = malloc(sizeof(TickitPen));
53   if(!pen)
54     return NULL;
55 
56   pen->refcount = 1;
57   pen->bindings = (struct TickitBindings){ NULL };
58   pen->freezecount = 0;
59   pen->changed = false;
60 
61   tickit_pen_clear(pen);
62 
63   return pen;
64 }
65 
tickit_pen_new_attrs(TickitPenAttr attr,...)66 TickitPen *tickit_pen_new_attrs(TickitPenAttr attr, ...)
67 {
68   TickitPen *pen = tickit_pen_new();
69   if(!pen)
70     return NULL;
71 
72   va_list args;
73   va_start(args, attr);
74   int first = 1;
75   int val;
76 
77   while(1) {
78     int a = first ? attr : va_arg(args, TickitPenAttr);
79     first = 0;
80     if(a < 1)
81       break;
82 
83     if(a == TICKIT_PEN_FG_DESC || a == TICKIT_PEN_BG_DESC) {
84       const char *str = va_arg(args, const char *);
85       tickit_pen_set_colour_attr_desc(pen, a - 0x100, str);
86       continue;
87     }
88 
89     // TODO: accept colour rgb8?
90     switch(tickit_pen_attrtype(a)) {
91     case TICKIT_PENTYPE_BOOL:
92       val = va_arg(args, int);
93       tickit_pen_set_bool_attr(pen, a, val);
94       break;
95     case TICKIT_PENTYPE_INT:
96       val = va_arg(args, int);
97       tickit_pen_set_int_attr(pen, a, val);
98       break;
99     case TICKIT_PENTYPE_COLOUR:
100       val = va_arg(args, int);
101       tickit_pen_set_colour_attr(pen, a, val);
102       break;
103     }
104   }
105 
106   va_end(args);
107 
108   return pen;
109 }
110 
tickit_pen_clone(const TickitPen * orig)111 TickitPen *tickit_pen_clone(const TickitPen *orig)
112 {
113   TickitPen *pen = tickit_pen_new();
114   tickit_pen_copy(pen, orig, true);
115   return pen;
116 }
117 
destroy(TickitPen * pen)118 static void destroy(TickitPen *pen)
119 {
120   tickit_bindings_unbind_and_destroy(&pen->bindings, pen);
121   free(pen);
122 }
123 
changed(TickitPen * pen)124 static void changed(TickitPen *pen)
125 {
126   if(!pen->freezecount)
127     run_events(pen, TICKIT_PEN_ON_CHANGE, NULL);
128   else
129     pen->changed = true;
130 }
131 
freeze(TickitPen * pen)132 static void freeze(TickitPen *pen)
133 {
134   pen->freezecount++;
135 }
136 
thaw(TickitPen * pen)137 static void thaw(TickitPen *pen)
138 {
139   pen->freezecount--;
140   if(!pen->freezecount && pen->changed) {
141     run_events(pen, TICKIT_PEN_ON_CHANGE, NULL);
142     pen->changed = false;
143   }
144 }
145 
tickit_pen_ref(TickitPen * pen)146 TickitPen *tickit_pen_ref(TickitPen *pen)
147 {
148   pen->refcount++;
149   return pen;
150 }
151 
tickit_pen_unref(TickitPen * pen)152 void tickit_pen_unref(TickitPen *pen)
153 {
154   if(pen->refcount < 1) {
155     fprintf(stderr, "tickit_pen_unref: invalid refcount %d\n", pen->refcount);
156     abort();
157   }
158   pen->refcount--;
159   if(!pen->refcount)
160     destroy(pen);
161 }
162 
tickit_pen_has_attr(const TickitPen * pen,TickitPenAttr attr)163 bool tickit_pen_has_attr(const TickitPen *pen, TickitPenAttr attr)
164 {
165   switch(attr) {
166     case TICKIT_PEN_FG:      return pen->valid.fgindex;
167     case TICKIT_PEN_BG:      return pen->valid.bgindex;
168     case TICKIT_PEN_BOLD:    return pen->valid.bold;
169     case TICKIT_PEN_UNDER:   return pen->valid.under;
170     case TICKIT_PEN_ITALIC:  return pen->valid.italic;
171     case TICKIT_PEN_REVERSE: return pen->valid.reverse;
172     case TICKIT_PEN_STRIKE:  return pen->valid.strike;
173     case TICKIT_PEN_ALTFONT: return pen->valid.altfont;
174     case TICKIT_PEN_BLINK:   return pen->valid.blink;
175 
176     case TICKIT_N_PEN_ATTRS:
177       return false;
178   }
179 
180   return false;
181 }
182 
tickit_pen_nondefault_attr(const TickitPen * pen,TickitPenAttr attr)183 bool tickit_pen_nondefault_attr(const TickitPen *pen, TickitPenAttr attr)
184 {
185   if(!tickit_pen_has_attr(pen, attr))
186     return false;
187 
188   switch(tickit_pen_attrtype(attr)) {
189   case TICKIT_PENTYPE_BOOL:
190     if(tickit_pen_get_bool_attr(pen, attr))
191       return true;
192     break;
193   case TICKIT_PENTYPE_INT:
194     if(tickit_pen_get_int_attr(pen, attr) > 0)
195       return true;
196     break;
197   case TICKIT_PENTYPE_COLOUR:
198     if(tickit_pen_get_colour_attr(pen, attr) != COLOUR_DEFAULT)
199       return true;
200     break;
201   }
202 
203   return false;
204 }
205 
tickit_pen_is_nonempty(const TickitPen * pen)206 bool tickit_pen_is_nonempty(const TickitPen *pen)
207 {
208   for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++) {
209     if(tickit_pen_has_attr(pen, attr))
210       return true;
211   }
212   return false;
213 }
214 
tickit_pen_is_nondefault(const TickitPen * pen)215 bool tickit_pen_is_nondefault(const TickitPen *pen)
216 {
217   for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++) {
218     if(tickit_pen_nondefault_attr(pen, attr))
219       return true;
220   }
221   return false;
222 }
223 
tickit_pen_get_bool_attr(const TickitPen * pen,TickitPenAttr attr)224 bool tickit_pen_get_bool_attr(const TickitPen *pen, TickitPenAttr attr)
225 {
226   if(!tickit_pen_has_attr(pen, attr))
227     return false;
228 
229   switch(attr) {
230     case TICKIT_PEN_BOLD:    return pen->bold;
231     case TICKIT_PEN_ITALIC:  return pen->italic;
232     case TICKIT_PEN_REVERSE: return pen->reverse;
233     case TICKIT_PEN_STRIKE:  return pen->strike;
234     case TICKIT_PEN_BLINK:   return pen->blink;
235 
236     /* back-compat */
237     case TICKIT_PEN_UNDER:
238       return pen->under > 0;
239     default:
240       return false;
241   }
242 }
243 
tickit_pen_set_bool_attr(TickitPen * pen,TickitPenAttr attr,bool val)244 void tickit_pen_set_bool_attr(TickitPen *pen, TickitPenAttr attr, bool val)
245 {
246   switch(attr) {
247     case TICKIT_PEN_BOLD:    pen->bold    = !!val; pen->valid.bold    = 1; break;
248     case TICKIT_PEN_ITALIC:  pen->italic  = !!val; pen->valid.italic  = 1; break;
249     case TICKIT_PEN_REVERSE: pen->reverse = !!val; pen->valid.reverse = 1; break;
250     case TICKIT_PEN_STRIKE:  pen->strike  = !!val; pen->valid.strike  = 1; break;
251     case TICKIT_PEN_BLINK:   pen->blink   = !!val; pen->valid.blink   = 1; break;
252 
253     /* back-compat */
254     case TICKIT_PEN_UNDER:
255       pen->under       = val ? TICKIT_PEN_UNDER_SINGLE : TICKIT_PEN_UNDER_NONE;
256       pen->valid.under = 1;
257       break;
258     default:
259       return;
260   }
261   changed(pen);
262 }
263 
tickit_pen_get_int_attr(const TickitPen * pen,TickitPenAttr attr)264 int tickit_pen_get_int_attr(const TickitPen *pen, TickitPenAttr attr)
265 {
266   if(!tickit_pen_has_attr(pen, attr))
267     return 0;
268 
269   switch(attr) {
270     case TICKIT_PEN_UNDER:   return pen->under;
271     case TICKIT_PEN_ALTFONT: return pen->altfont;
272     default:
273       return 0;
274   }
275 }
276 
tickit_pen_set_int_attr(TickitPen * pen,TickitPenAttr attr,int val)277 void tickit_pen_set_int_attr(TickitPen *pen, TickitPenAttr attr, int val)
278 {
279   switch(attr) {
280     case TICKIT_PEN_UNDER:   pen->under   = val; pen->valid.under   = 1; break;
281     case TICKIT_PEN_ALTFONT: pen->altfont = val; pen->valid.altfont = 1; break;
282     default:
283       return;
284   }
285   changed(pen);
286 }
287 
tickit_pen_get_colour_attr(const TickitPen * pen,TickitPenAttr attr)288 int tickit_pen_get_colour_attr(const TickitPen *pen, TickitPenAttr attr)
289 {
290   if(!tickit_pen_has_attr(pen, attr))
291     return COLOUR_DEFAULT;
292 
293   switch(attr) {
294     case TICKIT_PEN_FG: return pen->fgindex;
295     case TICKIT_PEN_BG: return pen->bgindex;
296     default:
297       return 0;
298   }
299 }
300 
tickit_pen_set_colour_attr(TickitPen * pen,TickitPenAttr attr,int val)301 void tickit_pen_set_colour_attr(TickitPen *pen, TickitPenAttr attr, int val)
302 {
303   switch(attr) {
304     case TICKIT_PEN_FG:
305       pen->fgindex = val; pen->valid.fgindex = 1;
306       pen->valid.fg_rgb8 = 0;
307       break;
308     case TICKIT_PEN_BG:
309       pen->bgindex = val; pen->valid.bgindex = 1;
310       pen->valid.bg_rgb8 = 0;
311       break;
312     default:
313       return;
314   }
315   run_events(pen, TICKIT_PEN_ON_CHANGE, NULL);
316 }
317 
tickit_pen_has_colour_attr_rgb8(const TickitPen * pen,TickitPenAttr attr)318 bool tickit_pen_has_colour_attr_rgb8(const TickitPen *pen, TickitPenAttr attr)
319 {
320   switch(attr) {
321     case TICKIT_PEN_FG: return pen->valid.fgindex && pen->valid.fg_rgb8;
322     case TICKIT_PEN_BG: return pen->valid.bgindex && pen->valid.bg_rgb8;
323     default:
324       return 0;
325   }
326 }
327 
tickit_pen_get_colour_attr_rgb8(const TickitPen * pen,TickitPenAttr attr)328 TickitPenRGB8 tickit_pen_get_colour_attr_rgb8(const TickitPen *pen, TickitPenAttr attr)
329 {
330   if(tickit_pen_has_colour_attr_rgb8(pen, attr))
331     switch(attr) {
332       case TICKIT_PEN_FG: return pen->fg_rgb8;
333       case TICKIT_PEN_BG: return pen->bg_rgb8;
334       default:
335         break;
336     }
337 
338   return (TickitPenRGB8){0, 0, 0};
339 }
340 
tickit_pen_set_colour_attr_rgb8(TickitPen * pen,TickitPenAttr attr,TickitPenRGB8 val)341 void tickit_pen_set_colour_attr_rgb8(TickitPen *pen, TickitPenAttr attr, TickitPenRGB8 val)
342 {
343   /* can only set an RGB8 version if the regular index version is already set */
344   if(!tickit_pen_has_attr(pen, attr))
345     return;
346 
347   switch(attr) {
348     case TICKIT_PEN_FG: pen->fg_rgb8 = val; pen->valid.fg_rgb8 = 1; break;
349     case TICKIT_PEN_BG: pen->bg_rgb8 = val; pen->valid.bg_rgb8 = 1; break;
350     default:
351       return;
352   }
353   changed(pen);
354 }
355 
356 static struct { const char *name; int colour; } colournames[] = {
357   { "black",   0 },
358   { "red",     1 },
359   { "green",   2 },
360   { "yellow",  3 },
361   { "blue",    4 },
362   { "magenta", 5 },
363   { "cyan",    6 },
364   { "white",   7 },
365   // 256-colour palette
366   { "grey",     8 },
367   { "brown",   94 },
368   { "orange", 208 },
369   { "pink",   212 },
370   { "purple", 128 },
371 };
372 
tickit_pen_set_colour_attr_desc(TickitPen * pen,TickitPenAttr attr,const char * desc)373 bool tickit_pen_set_colour_attr_desc(TickitPen *pen, TickitPenAttr attr, const char *desc)
374 {
375   int hi = 0;
376   int val;
377   if(strncmp(desc, "hi-", 3) == 0) {
378     desc += 3;
379     hi   = 8;
380   }
381 
382   const char *hashp = strchr(desc, '#');
383   size_t len;
384   if(hashp) {
385     len = hashp - desc;
386     // Trim spaces
387     while(len > 0 && desc[len-1] == ' ')
388       len--;
389   }
390   else
391     len = strlen(desc);
392 
393   if(sscanf(desc, "%d", &val) == 1) {
394     if(hi && val > 7)
395       return false;
396 
397     freeze(pen);
398     tickit_pen_set_colour_attr(pen, attr, val + hi);
399     goto parse_rgb8;
400   }
401 
402   for(int i = 0; i < sizeof(colournames)/sizeof(colournames[0]); i++) {
403     if(strncmp(desc, colournames[i].name, len) != 0)
404       continue;
405 
406     val = colournames[i].colour;
407     if(val < 8 && hi)
408       val += hi;
409 
410     freeze(pen);
411     tickit_pen_set_colour_attr(pen, attr, val);
412     goto parse_rgb8;
413   }
414 
415   return false;
416 
417 parse_rgb8:
418   if (hashp) {
419     TickitPenRGB8 rgb;
420     if(sscanf(hashp+1, "%2hhx%2hhx%2hhx", &rgb.r, &rgb.g, &rgb.b) == 3)
421       tickit_pen_set_colour_attr_rgb8(pen, attr, rgb);
422   }
423 
424   thaw(pen);
425   return true;
426 }
427 
tickit_pen_clear_attr(TickitPen * pen,TickitPenAttr attr)428 void tickit_pen_clear_attr(TickitPen *pen, TickitPenAttr attr)
429 {
430   switch(attr) {
431     case TICKIT_PEN_FG:      pen->valid.fgindex = 0; break;
432     case TICKIT_PEN_BG:      pen->valid.bgindex = 0; break;
433     case TICKIT_PEN_BOLD:    pen->valid.bold    = 0; break;
434     case TICKIT_PEN_UNDER:   pen->valid.under   = 0; break;
435     case TICKIT_PEN_ITALIC:  pen->valid.italic  = 0; break;
436     case TICKIT_PEN_REVERSE: pen->valid.reverse = 0; break;
437     case TICKIT_PEN_STRIKE:  pen->valid.strike  = 0; break;
438     case TICKIT_PEN_ALTFONT: pen->valid.altfont = 0; break;
439     case TICKIT_PEN_BLINK:   pen->valid.blink   = 0; break;
440 
441     case TICKIT_N_PEN_ATTRS:
442       return;
443   }
444   changed(pen);
445 }
446 
tickit_pen_clear(TickitPen * pen)447 void tickit_pen_clear(TickitPen *pen)
448 {
449   for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++)
450     tickit_pen_clear_attr(pen, attr);
451 }
452 
tickit_pen_equiv_attr(const TickitPen * a,const TickitPen * b,TickitPenAttr attr)453 bool tickit_pen_equiv_attr(const TickitPen *a, const TickitPen *b, TickitPenAttr attr)
454 {
455   switch(tickit_pen_attrtype(attr)) {
456   case TICKIT_PENTYPE_BOOL:
457     return tickit_pen_get_bool_attr(a, attr) == tickit_pen_get_bool_attr(b, attr);
458   case TICKIT_PENTYPE_INT:
459     return tickit_pen_get_int_attr(a, attr) == tickit_pen_get_int_attr(b, attr);
460   case TICKIT_PENTYPE_COLOUR:
461     if(tickit_pen_get_colour_attr(a, attr) != tickit_pen_get_colour_attr(b, attr))
462       return false;
463     /* indexes are equal; now compare RGB8s */
464     if(!tickit_pen_has_colour_attr_rgb8(a, attr) && !tickit_pen_has_colour_attr_rgb8(b, attr))
465       return true;
466     if(!tickit_pen_has_colour_attr_rgb8(a, attr) || !tickit_pen_has_colour_attr_rgb8(b, attr))
467       return false;
468     TickitPenRGB8 acol = tickit_pen_get_colour_attr_rgb8(a, attr),
469                   bcol = tickit_pen_get_colour_attr_rgb8(b, attr);
470     return (acol.r == bcol.r) && (acol.g == bcol.g) && (acol.b == bcol.b);
471   }
472 
473   return false;
474 }
475 
tickit_pen_equiv(const TickitPen * a,const TickitPen * b)476 bool tickit_pen_equiv(const TickitPen *a, const TickitPen *b)
477 {
478   if(a == b)
479     return true;
480 
481   for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++)
482     if(!tickit_pen_equiv_attr(a, b, attr))
483       return false;
484 
485   return true;
486 }
487 
tickit_pen_copy_attr(TickitPen * dst,const TickitPen * src,TickitPenAttr attr)488 void tickit_pen_copy_attr(TickitPen *dst, const TickitPen *src, TickitPenAttr attr)
489 {
490   switch(tickit_pen_attrtype(attr)) {
491   case TICKIT_PENTYPE_BOOL:
492     tickit_pen_set_bool_attr(dst, attr, tickit_pen_get_bool_attr(src, attr));
493     return;
494   case TICKIT_PENTYPE_INT:
495     tickit_pen_set_int_attr(dst, attr, tickit_pen_get_int_attr(src, attr));
496     return;
497   case TICKIT_PENTYPE_COLOUR:
498     freeze(dst);
499     tickit_pen_set_colour_attr(dst, attr, tickit_pen_get_colour_attr(src, attr));
500     if(tickit_pen_has_colour_attr_rgb8(src, attr))
501       tickit_pen_set_colour_attr_rgb8(dst, attr, tickit_pen_get_colour_attr_rgb8(src, attr));
502     thaw(dst);
503     return;
504   }
505 
506   return;
507 }
508 
tickit_pen_copy(TickitPen * dst,const TickitPen * src,bool overwrite)509 void tickit_pen_copy(TickitPen *dst, const TickitPen *src, bool overwrite)
510 {
511   freeze(dst);
512 
513   for(TickitPenAttr attr = 1; attr < TICKIT_N_PEN_ATTRS; attr++) {
514     if(!tickit_pen_has_attr(src, attr))
515       continue;
516     if(tickit_pen_has_attr(dst, attr) &&
517        (!overwrite || tickit_pen_equiv_attr(src, dst, attr)))
518       continue;
519 
520     tickit_pen_copy_attr(dst, src, attr);
521   }
522 
523   thaw(dst);
524 }
525 
tickit_pen_attrtype(TickitPenAttr attr)526 TickitPenAttrType tickit_pen_attrtype(TickitPenAttr attr)
527 {
528   switch(attr) {
529     case TICKIT_PEN_FG:
530     case TICKIT_PEN_BG:
531       return TICKIT_PENTYPE_COLOUR;
532 
533     case TICKIT_PEN_ALTFONT:
534     case TICKIT_PEN_UNDER:
535       return TICKIT_PENTYPE_INT;
536 
537     case TICKIT_PEN_BOLD:
538     case TICKIT_PEN_ITALIC:
539     case TICKIT_PEN_REVERSE:
540     case TICKIT_PEN_STRIKE:
541     case TICKIT_PEN_BLINK:
542       return TICKIT_PENTYPE_BOOL;
543 
544     case TICKIT_N_PEN_ATTRS:
545       return -1;
546   }
547 
548   return -1;
549 }
550 
tickit_pen_attrname(TickitPenAttr attr)551 const char *tickit_pen_attrname(TickitPenAttr attr)
552 {
553   switch(attr) {
554     case TICKIT_PEN_FG:      return "fg";
555     case TICKIT_PEN_BG:      return "bg";
556     case TICKIT_PEN_BOLD:    return "b";
557     case TICKIT_PEN_UNDER:   return "u";
558     case TICKIT_PEN_ITALIC:  return "i";
559     case TICKIT_PEN_REVERSE: return "rv";
560     case TICKIT_PEN_STRIKE:  return "strike";
561     case TICKIT_PEN_ALTFONT: return "af";
562     case TICKIT_PEN_BLINK:   return "blink";
563 
564     case TICKIT_N_PEN_ATTRS: ;
565   }
566   return NULL;
567 }
568 
tickit_pen_lookup_attr(const char * name)569 TickitPenAttr tickit_pen_lookup_attr(const char *name)
570 {
571   switch(name[0]) {
572     case 'a':
573       return streq(name+1,"f") ? TICKIT_PEN_ALTFONT
574                                : -1;
575     case 'b':
576       return name[1] == 0      ? TICKIT_PEN_BOLD
577            : streq(name+1,"g") ? TICKIT_PEN_BG
578            : streq(name+1,"link") ? TICKIT_PEN_BLINK
579                                : -1;
580     case 'f':
581       return streq(name+1,"g") ? TICKIT_PEN_FG
582                                : -1;
583     case 'i':
584       return name[1] == 0      ? TICKIT_PEN_ITALIC
585                                : -1;
586     case 'r':
587       return streq(name+1,"v") ? TICKIT_PEN_REVERSE
588                                : -1;
589     case 's':
590       return streq(name+1,"trike") ? TICKIT_PEN_STRIKE
591                                : -1;
592     case 'u':
593       return name[1] == 0      ? TICKIT_PEN_UNDER
594                                : -1;
595   }
596   return -1;
597 }
598