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