1 /*
2 themes.c : irssi
3
4 Copyright (C) 1999-2000 Timo Sirainen
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "module.h"
22 #include "module-formats.h"
23 #include "signals.h"
24 #include "commands.h"
25 #include "levels.h"
26 #include "misc.h"
27 #include "special-vars.h"
28 #include "lib-config/iconfig.h"
29 #include "settings.h"
30
31 #include "themes.h"
32 #include "printtext.h"
33
34 #include "default-theme.h"
35
36 GSList *themes;
37 THEME_REC *current_theme;
38 GHashTable *default_formats;
39
40 static int init_finished;
41 static char *init_errors;
42 static THEME_REC *internal_theme;
43
44 static int theme_read(THEME_REC *theme, const char *path);
45
theme_create(const char * path,const char * name)46 THEME_REC *theme_create(const char *path, const char *name)
47 {
48 THEME_REC *rec;
49
50 g_return_val_if_fail(path != NULL, NULL);
51 g_return_val_if_fail(name != NULL, NULL);
52
53 rec = g_new0(THEME_REC, 1);
54 rec->refcount = 1;
55 rec->path = g_strdup(path);
56 rec->name = g_strdup(name);
57 rec->abstracts = g_hash_table_new((GHashFunc) g_str_hash,
58 (GCompareFunc) g_str_equal);
59 rec->modules = g_hash_table_new((GHashFunc) g_istr_hash,
60 (GCompareFunc) g_istr_equal);
61 themes = g_slist_append(themes, rec);
62 signal_emit("theme created", 1, rec);
63
64 return rec;
65 }
66
theme_abstract_destroy(char * key,char * value)67 static void theme_abstract_destroy(char *key, char *value)
68 {
69 g_free(key);
70 g_free(value);
71 }
72
theme_module_destroy(const char * key,MODULE_THEME_REC * rec)73 static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec)
74 {
75 int n;
76
77 for (n = 0; n < rec->count; n++) {
78 g_free_not_null(rec->formats[n]);
79 g_free_not_null(rec->expanded_formats[n]);
80 }
81 g_free(rec->formats);
82 g_free(rec->expanded_formats);
83
84 g_free(rec->name);
85 g_free(rec);
86 }
87
theme_real_destroy(THEME_REC * rec)88 static void theme_real_destroy(THEME_REC *rec)
89 {
90 g_hash_table_foreach(rec->abstracts, (GHFunc) theme_abstract_destroy, NULL);
91 g_hash_table_destroy(rec->abstracts);
92 g_hash_table_foreach(rec->modules, (GHFunc) theme_module_destroy, NULL);
93 g_hash_table_destroy(rec->modules);
94
95 g_slist_foreach(rec->replace_values, (GFunc) g_free, NULL);
96 g_slist_free(rec->replace_values);
97
98 g_free(rec->path);
99 g_free(rec->name);
100 g_free(rec);
101 }
102
theme_unref(THEME_REC * rec)103 static void theme_unref(THEME_REC *rec)
104 {
105 if (--rec->refcount == 0)
106 theme_real_destroy(rec);
107 }
108
theme_destroy(THEME_REC * rec)109 void theme_destroy(THEME_REC *rec)
110 {
111 themes = g_slist_remove(themes, rec);
112 signal_emit("theme destroyed", 1, rec);
113
114 theme_unref(rec);
115 }
116
theme_replace_expand(THEME_REC * theme,int index,theme_rm_col default_fg,theme_rm_col default_bg,theme_rm_col * last_fg,theme_rm_col * last_bg,char chr,int flags)117 static char *theme_replace_expand(THEME_REC *theme, int index,
118 theme_rm_col default_fg, theme_rm_col default_bg,
119 theme_rm_col *last_fg, theme_rm_col *last_bg,
120 char chr, int flags)
121 {
122 GSList *rec;
123 char *ret, *abstract, data[2];
124
125 rec = g_slist_nth(theme->replace_values, index);
126 g_return_val_if_fail(rec != NULL, NULL);
127
128 data[0] = chr; data[1] = '\0';
129
130 abstract = rec->data;
131 abstract = theme_format_expand_data(theme, (const char **) &abstract,
132 default_fg, default_bg,
133 last_fg, last_bg, (flags | EXPAND_FLAG_IGNORE_REPLACES));
134 ret = parse_special_string(abstract, NULL, NULL, data, NULL,
135 PARSE_FLAG_ONLY_ARGS);
136 g_free(abstract);
137 return ret;
138 }
139
140 static const char *fgcolorformats = "nkrgybmpcwKRGYBMPCW";
141 static const char *bgcolorformats = "n01234567";
142
143 #define IS_FGCOLOR_FORMAT(c) \
144 ((c) != '\0' && strchr(fgcolorformats, c) != NULL)
145 #define IS_BGCOLOR_FORMAT(c) \
146 ((c) != '\0' && strchr(bgcolorformats, c) != NULL)
147
148 /* append "variable" part in $variable, ie. not the contents of the variable */
theme_format_append_variable(GString * str,const char ** format)149 static void theme_format_append_variable(GString *str, const char **format)
150 {
151 const char *orig;
152 char *value, *args[1] = { NULL };
153 int free_ret;
154
155 orig = *format;
156 (*format)++;
157
158 value = parse_special((char **) format, NULL, NULL,
159 args, &free_ret, NULL, PARSE_FLAG_ONLY_ARGS);
160 if (free_ret) g_free(value);
161
162 if (**format != '\0')
163 (*format)++;
164
165 /* append the variable name */
166 value = g_strndup(orig, (int) (*format-orig));
167 g_string_append(str, value);
168 g_free(value);
169 }
170
chr_is_valid_rgb(const char format[])171 static inline int chr_is_valid_rgb(const char format[])
172 {
173 int tmp;
174 for (tmp = 1; tmp < 7; ++tmp) {
175 if (!isxdigit(format[tmp]))
176 return tmp;
177 }
178 return 0;
179 }
180
chr_is_valid_ext(const char format[])181 static inline int chr_is_valid_ext(const char format[])
182 {
183 if (format[1] < '0' || format[1] > '7')
184 return 1;
185
186 if (format[1] == '7') {
187 if (!isalpha(format[2]) || format[2] == 'y' || format[2] == 'Y'
188 || format[2] =='z' || format[2] == 'Z')
189 return 2;
190 } else if (format[1] == '0') {
191 if (!isxdigit(format[2]))
192 return 2;
193 } else if (!isalnum(format[2]))
194 return 2;
195
196 return 0;
197 }
198
199 /* append next "item", either a character, $variable or %format */
theme_format_append_next(THEME_REC * theme,GString * str,const char ** format,theme_rm_col default_fg,theme_rm_col default_bg,theme_rm_col * last_fg,theme_rm_col * last_bg,int flags)200 static void theme_format_append_next(THEME_REC *theme, GString *str,
201 const char **format,
202 theme_rm_col default_fg, theme_rm_col default_bg,
203 theme_rm_col *last_fg, theme_rm_col *last_bg,
204 int flags)
205 {
206 int index;
207 unsigned char chr;
208 char *t;
209
210 chr = **format;
211 if ((chr == '$' || chr == '%') &&
212 (*format)[1] == '\0') {
213 /* last char, always append */
214 g_string_append_c(str, chr);
215 (*format)++;
216 return;
217 }
218
219 if (chr == '$') {
220 /* $variable .. we'll always need to skip this, since it
221 may contain characters that are in replace chars. */
222 theme_format_append_variable(str, format);
223 return;
224 }
225
226 if (**format == '%') {
227 /* format */
228 (*format)++;
229 if (**format != '{' && **format != '}') {
230 chr = **format;
231 if (**format == 'n') {
232 /* %n = change to default color */
233 g_string_append(str, "%n");
234
235 if (default_bg.m[0] != 'n') {
236 g_string_append_c(str, '%');
237 g_string_append(str, default_bg.m);
238 }
239 if (default_fg.m[0] != 'n') {
240 g_string_append_c(str, '%');
241 g_string_append(str, default_fg.m);
242 }
243
244 *last_fg = default_fg;
245 *last_bg = default_bg;
246 } else if (chr == 'z' || chr == 'Z') {
247 if (chr_is_valid_rgb(*format) == 0) {
248 t = chr == 'z' ? (*last_bg).m : (*last_fg).m;
249 strncpy(t, *format, 7);
250 t[7] = '\0';
251 g_string_append_c(str, '%');
252 g_string_append(str, t);
253 (*format)+=6;
254 } else {
255 g_string_append(str, "%%");
256 g_string_append_c(str, **format);
257 }
258 } else if (chr == 'x' || chr == 'X') {
259 if (chr_is_valid_ext(*format) == 0) {
260 t = chr == 'x' ? (*last_bg).m : (*last_fg).m;
261 strncpy(t, *format, 3);
262 t[3] = '\0';
263 g_string_append_c(str, '%');
264 g_string_append(str, t);
265 (*format)+=2;
266 } else {
267 g_string_append(str, "%%");
268 g_string_append_c(str, **format);
269 }
270 } else {
271 if (IS_FGCOLOR_FORMAT(chr)) {
272 (*last_fg).m[0] = chr;
273 (*last_fg).m[1] = '\0';
274 }
275 if (IS_BGCOLOR_FORMAT(chr)) {
276 (*last_bg).m[0] = chr;
277 (*last_bg).m[1] = '\0';
278 }
279 g_string_append_c(str, '%');
280 g_string_append_c(str, chr);
281 }
282 (*format)++;
283 return;
284 }
285
286 /* %{ or %} gives us { or } char - keep the % char
287 though to make sure {} isn't treated as abstract */
288 g_string_append_c(str, '%');
289 chr = **format;
290 }
291
292 index = (flags & EXPAND_FLAG_IGNORE_REPLACES) ? -1 :
293 theme->replace_keys[(int) (unsigned char) chr];
294 if (index == -1)
295 g_string_append_c(str, chr);
296 else {
297 char *value;
298
299 value = theme_replace_expand(theme, index,
300 default_fg, default_bg,
301 last_fg, last_bg, chr, flags);
302 g_string_append(str, value);
303 g_free(value);
304 }
305
306 (*format)++;
307 }
308
309 /* returns TRUE if data is empty, or the data is a $variable which is empty */
data_is_empty(const char ** data)310 static int data_is_empty(const char **data)
311 {
312 /* since we don't know the real argument list, assume there's always
313 an argument in them */
314 static char *arglist[] = {
315 "x", "x", "x", "x", "x", "x","x", "x", "x", "x",
316 NULL
317 };
318 SERVER_REC *server;
319 const char *p;
320 char *ret;
321 int free_ret, empty;
322
323 p = *data;
324 while (*p == ' ') p++;
325
326 if (*p == '}') {
327 /* empty */
328 *data = p+1;
329 return TRUE;
330 }
331
332 if (*p != '$') {
333 /* not empty */
334 return FALSE;
335 }
336
337 /* variable - check if it's empty */
338 p++;
339
340 server = active_win == NULL ? NULL :
341 active_win->active_server != NULL ?
342 active_win->active_server : active_win->connect_server;
343
344 ret = parse_special((char **) &p, server,
345 active_win == NULL ? NULL : active_win->active,
346 arglist, &free_ret, NULL, 0);
347 p++;
348
349 while (*p == ' ') p++;
350 empty = *p == '}' && (ret == NULL || *ret == '\0');
351 if (free_ret) g_free(ret);
352
353 if (empty) {
354 /* empty */
355 *data = p+1;
356 return TRUE;
357 }
358
359 return FALSE;
360 }
361
362 /* return "data" from {abstract data} string */
theme_format_expand_get(THEME_REC * theme,const char ** format)363 char *theme_format_expand_get(THEME_REC *theme, const char **format)
364 {
365 GString *str;
366 char *ret;
367 theme_rm_col dummy, reset;
368 int braces = 1; /* we start with one brace opened */
369 dummy.m[0] = '\0';
370 strcpy(reset.m, "n");
371
372 str = g_string_new(NULL);
373 while (**format != '\0' && braces != 0) {
374 if (**format == '{')
375 braces++;
376 else if (**format == '}')
377 braces--;
378 else if ((braces > 1) && (**format == ' ')) {
379 g_string_append(str, "\\x20");
380 (*format)++;
381 continue;
382 } else {
383 theme_format_append_next(theme, str, format,
384 reset, reset,
385 &dummy, &dummy,
386 EXPAND_FLAG_IGNORE_REPLACES);
387 continue;
388 }
389
390 if (braces == 0) {
391 (*format)++;
392 break;
393 }
394
395 g_string_append_c(str, **format);
396 (*format)++;
397 }
398
399 ret = str->str;
400 g_string_free(str, FALSE);
401 return ret;
402 }
403
404 static char *theme_format_expand_data_rec(THEME_REC *theme, const char **format,
405 theme_rm_col default_fg, theme_rm_col default_bg,
406 theme_rm_col *save_last_fg, theme_rm_col *save_last_bg,
407 int flags, GTree *block_list);
408
409 /* expand a single {abstract ...data... } */
theme_format_expand_abstract(THEME_REC * theme,const char ** formatp,theme_rm_col * last_fg,theme_rm_col * last_bg,int flags,GTree * block_list)410 static char *theme_format_expand_abstract(THEME_REC *theme, const char **formatp,
411 theme_rm_col *last_fg, theme_rm_col *last_bg, int flags,
412 GTree *block_list)
413 {
414 GString *str;
415 const char *p, *format;
416 char *abstract, *data, *ret, *blocking;
417 theme_rm_col default_fg, default_bg;
418 int len;
419
420 format = *formatp;
421 default_fg = *last_fg;
422 default_bg = *last_bg;
423
424 /* get abstract name first */
425 p = format;
426 while (*p != '\0' && *p != ' ' &&
427 *p != '{' && *p != '}') p++;
428 if (*p == '\0' || p == format)
429 return NULL; /* error */
430
431 len = (int) (p-format);
432 abstract = g_strndup(format, len);
433
434 /* skip the following space, if there's any more spaces they're
435 treated as arguments */
436 if (*p == ' ') {
437 len++;
438 if ((flags & EXPAND_FLAG_IGNORE_EMPTY) && data_is_empty(&p)) {
439 *formatp = p;
440 g_free(abstract);
441 return NULL;
442 }
443 }
444 *formatp = format+len;
445
446 if (block_list == NULL) {
447 block_list = g_tree_new_full((GCompareDataFunc) g_strcmp0, NULL, g_free, NULL);
448 } else {
449 g_tree_ref(block_list);
450 }
451
452 /* get the abstract data */
453 data = g_hash_table_lookup(theme->abstracts, abstract);
454 if (data == NULL || g_tree_lookup(block_list, abstract) != NULL) {
455 /* unknown abstract, just display the data */
456 data = "$0-";
457 g_free(abstract);
458 blocking = NULL;
459 } else {
460 blocking = abstract;
461 g_tree_insert(block_list, blocking, blocking);
462 }
463 abstract = g_strdup(data);
464
465 /* we'll need to get the data part. it may contain
466 more abstracts, they are _NOT_ expanded. */
467 data = theme_format_expand_get(theme, formatp);
468 len = strlen(data);
469
470 if (len > 1 && i_isdigit(data[len-1]) && data[len-2] == '$') {
471 /* ends with $<digit> .. this breaks things if next
472 character is digit or '-' */
473 char digit, *tmp;
474
475 tmp = data;
476 digit = tmp[len-1];
477 tmp[len-1] = '\0';
478
479 data = g_strdup_printf("%s{%c}", tmp, digit);
480 g_free(tmp);
481 }
482
483 ret = parse_special_string(abstract, NULL, NULL, data, NULL,
484 PARSE_FLAG_ONLY_ARGS);
485 g_free(abstract);
486 g_free(data);
487 str = g_string_new(NULL);
488 p = ret;
489 while (*p != '\0') {
490 if (*p == '\\' && p[1] != '\0') {
491 int chr;
492 p++;
493 chr = expand_escape(&p);
494 g_string_append_c(str, chr != -1 ? chr : *p);
495 } else
496 g_string_append_c(str, *p);
497 p++;
498 }
499 g_free(ret);
500 abstract = str->str;
501 g_string_free(str, FALSE);
502
503 /* abstract may itself contain abstracts or replaces */
504 p = abstract;
505 ret = theme_format_expand_data_rec(theme, &p, default_fg, default_bg, last_fg, last_bg,
506 flags | EXPAND_FLAG_LASTCOLOR_ARG, block_list);
507 g_free(abstract);
508 if (blocking != NULL) {
509 g_tree_remove(block_list, blocking);
510 }
511 g_tree_unref(block_list);
512 return ret;
513 }
514
theme_format_expand_data_rec(THEME_REC * theme,const char ** format,theme_rm_col default_fg,theme_rm_col default_bg,theme_rm_col * save_last_fg,theme_rm_col * save_last_bg,int flags,GTree * block_list)515 static char *theme_format_expand_data_rec(THEME_REC *theme, const char **format,
516 theme_rm_col default_fg, theme_rm_col default_bg,
517 theme_rm_col *save_last_fg, theme_rm_col *save_last_bg,
518 int flags, GTree *block_list)
519 {
520 GString *str;
521 char *ret, *abstract;
522 theme_rm_col last_fg, last_bg;
523 int recurse_flags;
524
525 last_fg = default_fg;
526 last_bg = default_bg;
527 recurse_flags = flags & EXPAND_FLAG_RECURSIVE_MASK;
528
529 str = g_string_new(NULL);
530 while (**format != '\0') {
531 if ((flags & EXPAND_FLAG_ROOT) == 0 && **format == '}') {
532 /* ignore } if we're expanding original string */
533 (*format)++;
534 break;
535 }
536
537 if (**format != '{') {
538 if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) &&
539 **format == '$' && (*format)[1] == '0') {
540 /* save the color before $0 ..
541 this is for the %n replacing */
542 if (save_last_fg != NULL) {
543 *save_last_fg = last_fg;
544 save_last_fg = NULL;
545 }
546 if (save_last_bg != NULL) {
547 *save_last_bg = last_bg;
548 save_last_bg = NULL;
549 }
550 }
551
552 theme_format_append_next(theme, str, format,
553 default_fg, default_bg,
554 &last_fg, &last_bg,
555 recurse_flags);
556 continue;
557 }
558
559 (*format)++;
560 if (**format == '\0' || **format == '}')
561 break; /* error */
562
563 /* get a single {...} */
564 abstract = theme_format_expand_abstract(theme, format, &last_fg, &last_bg,
565 recurse_flags, block_list);
566 if (abstract != NULL) {
567 g_string_append(str, abstract);
568 g_free(abstract);
569 }
570 }
571
572 /* save the last color */
573 if (save_last_fg != NULL)
574 *save_last_fg = last_fg;
575 if (save_last_bg != NULL)
576 *save_last_bg = last_bg;
577
578 ret = str->str;
579 g_string_free(str, FALSE);
580 return ret;
581 }
582
583 /* expand the data part in {abstract data} */
theme_format_expand_data(THEME_REC * theme,const char ** format,theme_rm_col default_fg,theme_rm_col default_bg,theme_rm_col * save_last_fg,theme_rm_col * save_last_bg,int flags)584 char *theme_format_expand_data(THEME_REC *theme, const char **format, theme_rm_col default_fg,
585 theme_rm_col default_bg, theme_rm_col *save_last_fg,
586 theme_rm_col *save_last_bg, int flags)
587 {
588 return theme_format_expand_data_rec(theme, format, default_fg, default_bg, save_last_bg,
589 save_last_bg, flags, NULL);
590 }
591
592 #define IS_OLD_FORMAT(code, last_fg, last_bg) \
593 (((code) == 'n' && (last_fg) == 'n' && (last_bg) == 'n') || \
594 ((code) != 'n' && ((code) == (last_fg) || (code) == (last_bg))))
595
theme_format_compress_colors(THEME_REC * theme,const char * format)596 static char *theme_format_compress_colors(THEME_REC *theme, const char *format)
597 {
598 GString *str;
599 char *ret;
600 char last_fg, last_bg;
601
602 str = g_string_new(NULL);
603
604 last_fg = last_bg = '\0';
605 while (*format != '\0') {
606 if (*format == '$') {
607 /* $variable, skrip it entirely */
608 theme_format_append_variable(str, &format);
609 last_fg = last_bg = '\0';
610 } else if (*format != '%') {
611 /* a normal character */
612 g_string_append_c(str, *format);
613 format++;
614 } else if (format[1] != '\0') {
615 /* %format */
616 format++;
617 if (IS_OLD_FORMAT(*format, last_fg, last_bg)) {
618 /* active color set again */
619 } else if (IS_FGCOLOR_FORMAT(*format) &&
620 format[1] == '%' &&
621 IS_FGCOLOR_FORMAT(format[2]) &&
622 (*format != 'n' || format[2] == 'n')) {
623 /* two fg colors in a row. bg colors are
624 so rare that we don't bother checking
625 them */
626 } else {
627 /* some format, add it */
628 g_string_append_c(str, '%');
629 g_string_append_c(str, *format);
630
631 if (IS_FGCOLOR_FORMAT(*format))
632 last_fg = *format;
633 else if (*format == 'Z' || *format == 'X')
634 last_fg = '\0';
635 if (IS_BGCOLOR_FORMAT(*format))
636 last_bg = *format;
637 else if (*format == 'z' || *format == 'x')
638 last_bg = '\0';
639 }
640 format++;
641 } else {
642 /* % at end of string */
643 format++;
644 g_string_append_c(str, '%');
645 g_string_append_c(str, '%');
646 }
647 }
648
649 ret = str->str;
650 g_string_free(str, FALSE);
651 return ret;
652 }
653
theme_format_expand(THEME_REC * theme,const char * format)654 char *theme_format_expand(THEME_REC *theme, const char *format)
655 {
656 char *data, *ret;
657 theme_rm_col reset;
658 strcpy(reset.m, "n");
659
660 g_return_val_if_fail(theme != NULL, NULL);
661 g_return_val_if_fail(format != NULL, NULL);
662
663 data = theme_format_expand_data(theme, &format, reset, reset, NULL, NULL,
664 EXPAND_FLAG_ROOT);
665 ret = theme_format_compress_colors(theme, data);
666 g_free(data);
667 return ret;
668 }
669
theme_module_create(THEME_REC * theme,const char * module)670 static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *module)
671 {
672 MODULE_THEME_REC *rec;
673 FORMAT_REC *formats;
674
675 rec = g_hash_table_lookup(theme->modules, module);
676 if (rec != NULL) return rec;
677
678 formats = g_hash_table_lookup(default_formats, module);
679 g_return_val_if_fail(formats != NULL, NULL);
680
681 rec = g_new0(MODULE_THEME_REC, 1);
682 rec->name = g_strdup(module);
683
684 for (rec->count = 0; formats[rec->count].def != NULL; rec->count++) ;
685 rec->formats = g_new0(char *, rec->count);
686 rec->expanded_formats = g_new0(char *, rec->count);
687
688 g_hash_table_insert(theme->modules, rec->name, rec);
689 return rec;
690 }
691
theme_read_replaces(CONFIG_REC * config,THEME_REC * theme)692 static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme)
693 {
694 GSList *tmp;
695 CONFIG_NODE *node;
696 const char *p;
697 int index;
698
699 /* reset replace keys */
700 for (index = 0; index < 256; index++)
701 theme->replace_keys[index] = -1;
702 index = 0;
703
704 node = config_node_traverse(config, "replaces", FALSE);
705 if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
706
707 for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
708 node = tmp->data;
709
710 if (node->key != NULL && node->value != NULL) {
711 for (p = node->key; *p != '\0'; p++)
712 theme->replace_keys[(int) (unsigned char) *p] = index;
713
714 theme->replace_values =
715 g_slist_append(theme->replace_values,
716 g_strdup(node->value));
717 index++;
718 }
719 }
720 }
721
theme_read_abstracts(CONFIG_REC * config,THEME_REC * theme)722 static void theme_read_abstracts(CONFIG_REC *config, THEME_REC *theme)
723 {
724 GSList *tmp;
725 CONFIG_NODE *node;
726 gpointer oldkey, oldvalue;
727
728 node = config_node_traverse(config, "abstracts", FALSE);
729 if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
730
731 for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
732 node = tmp->data;
733
734 if (node->key == NULL || node->value == NULL)
735 continue;
736
737 if (g_hash_table_lookup_extended(theme->abstracts, node->key,
738 &oldkey, &oldvalue)) {
739 /* new values override old ones */
740 g_hash_table_remove(theme->abstracts, oldkey);
741 g_free(oldkey);
742 g_free(oldvalue);
743 }
744
745 g_hash_table_insert(theme->abstracts, g_strdup(node->key),
746 g_strdup(node->value));
747 }
748 }
749
theme_set_format(THEME_REC * theme,MODULE_THEME_REC * rec,const char * module,const char * key,const char * value)750 static void theme_set_format(THEME_REC *theme, MODULE_THEME_REC *rec,
751 const char *module,
752 const char *key, const char *value)
753 {
754 int num;
755
756 num = format_find_tag(module, key);
757 if (num != -1) {
758 rec->formats[num] = g_strdup(value);
759 rec->expanded_formats[num] = theme_format_expand(theme, value);
760 }
761 }
762
theme_read_formats(THEME_REC * theme,const char * module,CONFIG_REC * config,MODULE_THEME_REC * rec)763 static void theme_read_formats(THEME_REC *theme, const char *module,
764 CONFIG_REC *config, MODULE_THEME_REC *rec)
765 {
766 CONFIG_NODE *node;
767 GSList *tmp;
768
769 node = config_node_traverse(config, "formats", FALSE);
770 if (node == NULL) return;
771 node = config_node_section(config, node, module, -1);
772 if (node == NULL) return;
773
774 for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
775 node = tmp->data;
776
777 if (node->key != NULL && node->value != NULL) {
778 theme_set_format(theme, rec, module,
779 node->key, node->value);
780 }
781 }
782 }
783
theme_init_module(THEME_REC * theme,const char * module,CONFIG_REC * config)784 static void theme_init_module(THEME_REC *theme, const char *module,
785 CONFIG_REC *config)
786 {
787 MODULE_THEME_REC *rec;
788 FORMAT_REC *formats;
789 int n;
790
791 formats = g_hash_table_lookup(default_formats, module);
792 g_return_if_fail(formats != NULL);
793
794 rec = theme_module_create(theme, module);
795
796 if (config != NULL)
797 theme_read_formats(theme, module, config, rec);
798
799 /* expand the remaining formats */
800 for (n = 0; n < rec->count; n++) {
801 if (rec->expanded_formats[n] == NULL) {
802 rec->expanded_formats[n] =
803 theme_format_expand(theme, formats[n].def);
804 }
805 }
806 }
807
sig_print_errors(void)808 static void sig_print_errors(void)
809 {
810 init_finished = TRUE;
811
812 if (init_errors != NULL) {
813 signal_emit("gui dialog", 2, "error", init_errors);
814 g_free(init_errors);
815 }
816 }
817
theme_read_module(THEME_REC * theme,const char * module)818 static void theme_read_module(THEME_REC *theme, const char *module)
819 {
820 CONFIG_REC *config;
821
822 config = config_open(theme->path, -1);
823 if (config != NULL)
824 config_parse(config);
825
826 theme_init_module(theme, module, config);
827
828 if (config != NULL) config_close(config);
829 }
830
themes_read_module(const char * module)831 static void themes_read_module(const char *module)
832 {
833 g_slist_foreach(themes, (GFunc) theme_read_module, (void *) module);
834 }
835
theme_remove_module(THEME_REC * theme,const char * module)836 static void theme_remove_module(THEME_REC *theme, const char *module)
837 {
838 MODULE_THEME_REC *rec;
839
840 rec = g_hash_table_lookup(theme->modules, module);
841 if (rec == NULL) return;
842
843 g_hash_table_remove(theme->modules, module);
844 theme_module_destroy(module, rec);
845 }
846
themes_remove_module(const char * module)847 static void themes_remove_module(const char *module)
848 {
849 g_slist_foreach(themes, (GFunc) theme_remove_module, (void *) module);
850 }
851
theme_register_module(const char * module,FORMAT_REC * formats)852 void theme_register_module(const char *module, FORMAT_REC *formats)
853 {
854 if (g_hash_table_lookup(default_formats, module) != NULL)
855 return;
856
857 g_hash_table_insert(default_formats, g_strdup(module), formats);
858 themes_read_module(module);
859 }
860
theme_unregister_module(const char * module)861 void theme_unregister_module(const char *module)
862 {
863 gpointer key, value;
864
865 if (default_formats == NULL)
866 return; /* already uninitialized */
867
868 if (!g_hash_table_lookup_extended(default_formats, module, &key, &value))
869 return;
870
871 g_hash_table_remove(default_formats, key);
872 g_free(key);
873
874 themes_remove_module(module);
875 }
876
theme_set_default_abstract(const char * key,const char * value)877 void theme_set_default_abstract(const char *key, const char *value)
878 {
879 gpointer oldkey, oldvalue;
880
881 if (g_hash_table_lookup_extended(internal_theme->abstracts, key,
882 &oldkey, &oldvalue)) {
883 /* new values override old ones */
884 g_hash_table_remove(internal_theme->abstracts, oldkey);
885 g_free(oldkey);
886 g_free(oldvalue);
887 }
888
889 g_hash_table_insert(internal_theme->abstracts,
890 g_strdup(key), g_strdup(value));
891 }
892
theme_find(const char * name)893 static THEME_REC *theme_find(const char *name)
894 {
895 GSList *tmp;
896
897 for (tmp = themes; tmp != NULL; tmp = tmp->next) {
898 THEME_REC *rec = tmp->data;
899
900 if (g_ascii_strcasecmp(rec->name, name) == 0)
901 return rec;
902 }
903
904 return NULL;
905 }
906
window_themes_update(void)907 static void window_themes_update(void)
908 {
909 GSList *tmp;
910
911 for (tmp = windows; tmp != NULL; tmp = tmp->next) {
912 WINDOW_REC *rec = tmp->data;
913
914 if (rec->theme_name != NULL)
915 rec->theme = theme_load(rec->theme_name);
916 }
917 }
918
theme_load(const char * setname)919 THEME_REC *theme_load(const char *setname)
920 {
921 THEME_REC *theme, *oldtheme;
922 struct stat statbuf;
923 char *fname, *name, *p;
924
925 name = g_strdup(setname);
926 p = strrchr(name, '.');
927 if (p != NULL && g_strcmp0(p, ".theme") == 0) {
928 /* remove the trailing .theme */
929 *p = '\0';
930 }
931
932 theme = theme_find(name);
933
934 /* check home dir */
935 fname = g_strdup_printf("%s/%s.theme", get_irssi_dir(), name);
936 if (stat(fname, &statbuf) != 0) {
937 /* check global config dir */
938 g_free(fname);
939 fname = g_strdup_printf(THEMESDIR"/%s.theme", name);
940 if (stat(fname, &statbuf) != 0) {
941 /* theme not found */
942 g_free(fname);
943 g_free(name);
944 return theme; /* use the one in memory if possible */
945 }
946 }
947
948 if (theme != NULL && theme->last_modify == statbuf.st_mtime) {
949 /* theme not modified, use the one already in memory */
950 g_free(fname);
951 g_free(name);
952 return theme;
953 }
954
955 oldtheme = theme;
956 theme = theme_create(fname, name);
957 theme->last_modify = statbuf.st_mtime;
958 if (!theme_read(theme, theme->path)) {
959 /* error reading .theme file */
960 theme_destroy(theme);
961 theme = NULL;
962 }
963
964 if (oldtheme != NULL && theme != NULL) {
965 theme_destroy(oldtheme);
966 if (current_theme == oldtheme)
967 current_theme = theme;
968 window_themes_update();
969 }
970
971 g_free(fname);
972 g_free(name);
973 return theme;
974 }
975
copy_abstract_hash(char * key,char * value,GHashTable * dest)976 static void copy_abstract_hash(char *key, char *value, GHashTable *dest)
977 {
978 g_hash_table_insert(dest, g_strdup(key), g_strdup(value));
979 }
980
theme_copy_abstracts(THEME_REC * dest,THEME_REC * src)981 static void theme_copy_abstracts(THEME_REC *dest, THEME_REC *src)
982 {
983 g_hash_table_foreach(src->abstracts, (GHFunc) copy_abstract_hash,
984 dest->abstracts);
985 }
986
987 typedef struct {
988 THEME_REC *theme;
989 CONFIG_REC *config;
990 } THEME_READ_REC;
991
theme_read_modules(const char * module,void * value,THEME_READ_REC * rec)992 static void theme_read_modules(const char *module, void *value,
993 THEME_READ_REC *rec)
994 {
995 theme_init_module(rec->theme, module, rec->config);
996 }
997
read_error(const char * str)998 static void read_error(const char *str)
999 {
1000 char *old;
1001
1002 if (init_finished)
1003 printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str);
1004 else if (init_errors == NULL)
1005 init_errors = g_strdup(str);
1006 else {
1007 old = init_errors;
1008 init_errors = g_strconcat(init_errors, "\n", str, NULL);
1009 g_free(old);
1010 }
1011 }
1012
theme_read(THEME_REC * theme,const char * path)1013 static int theme_read(THEME_REC *theme, const char *path)
1014 {
1015 CONFIG_REC *config;
1016 THEME_READ_REC rec;
1017 char *str;
1018
1019 config = config_open(path, -1) ;
1020 if (config == NULL) {
1021 /* didn't exist or no access? */
1022 str = g_strdup_printf("Error reading theme file %s: %s",
1023 path, g_strerror(errno));
1024 read_error(str);
1025 g_free(str);
1026 return FALSE;
1027 }
1028
1029 if (path == NULL)
1030 config_parse_data(config, default_theme, "internal");
1031 else
1032 config_parse(config);
1033
1034 if (config_last_error(config) != NULL) {
1035 str = g_strdup_printf("Ignored errors in theme %s:\n%s",
1036 theme->name, config_last_error(config));
1037 read_error(str);
1038 g_free(str);
1039 }
1040
1041 theme->default_color =
1042 config_get_int(config, NULL, "default_color", -1);
1043 theme->info_eol = config_get_bool(config, NULL, "info_eol", FALSE);
1044
1045 theme_read_replaces(config, theme);
1046
1047 if (path != NULL)
1048 theme_copy_abstracts(theme, internal_theme);
1049 theme_read_abstracts(config, theme);
1050
1051 rec.theme = theme;
1052 rec.config = config;
1053 g_hash_table_foreach(default_formats,
1054 (GHFunc) theme_read_modules, &rec);
1055 config_close(config);
1056
1057 return TRUE;
1058 }
1059
1060 typedef struct {
1061 char *name;
1062 char *short_name;
1063 } THEME_SEARCH_REC;
1064
theme_search_equal(THEME_SEARCH_REC * r1,THEME_SEARCH_REC * r2)1065 static int theme_search_equal(THEME_SEARCH_REC *r1, THEME_SEARCH_REC *r2)
1066 {
1067 return g_ascii_strcasecmp(r1->short_name, r2->short_name);
1068 }
1069
theme_get_modules(char * module,FORMAT_REC * formats,GSList ** list)1070 static void theme_get_modules(char *module, FORMAT_REC *formats, GSList **list)
1071 {
1072 THEME_SEARCH_REC *rec;
1073
1074 rec = g_new(THEME_SEARCH_REC, 1);
1075 rec->name = module;
1076 rec->short_name = strrchr(module, '/');
1077 if (rec->short_name != NULL)
1078 rec->short_name++; else rec->short_name = module;
1079 *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) theme_search_equal);
1080 }
1081
get_sorted_modules(void)1082 static GSList *get_sorted_modules(void)
1083 {
1084 GSList *list;
1085
1086 list = NULL;
1087 g_hash_table_foreach(default_formats, (GHFunc) theme_get_modules, &list);
1088 return list;
1089 }
1090
theme_search(GSList * list,const char * module)1091 static THEME_SEARCH_REC *theme_search(GSList *list, const char *module)
1092 {
1093 THEME_SEARCH_REC *rec;
1094
1095 while (list != NULL) {
1096 rec = list->data;
1097
1098 if (g_ascii_strcasecmp(rec->short_name, module) == 0)
1099 return rec;
1100 list = list->next;
1101 }
1102
1103 return NULL;
1104 }
1105
theme_show(THEME_SEARCH_REC * rec,const char * key,const char * value,int reset)1106 static void theme_show(THEME_SEARCH_REC *rec, const char *key, const char *value, int reset)
1107 {
1108 MODULE_THEME_REC *theme;
1109 FORMAT_REC *formats;
1110 const char *text, *last_title;
1111 int n, first;
1112
1113 formats = g_hash_table_lookup(default_formats, rec->name);
1114 theme = g_hash_table_lookup(current_theme->modules, rec->name);
1115
1116 last_title = NULL; first = TRUE;
1117 for (n = 1; formats[n].def != NULL; n++) {
1118 text = theme != NULL && theme->formats[n] != NULL ?
1119 theme->formats[n] : formats[n].def;
1120
1121 if (formats[n].tag == NULL)
1122 last_title = text;
1123 else if ((value != NULL && key != NULL && g_ascii_strcasecmp(formats[n].tag, key) == 0) ||
1124 (value == NULL && (key == NULL || stristr(formats[n].tag, key) != NULL))) {
1125 if (first) {
1126 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_TITLE, rec->short_name, formats[0].def);
1127 first = FALSE;
1128 }
1129 if (last_title != NULL)
1130 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_SUBTITLE, last_title);
1131 if (reset || value != NULL) {
1132 theme = theme_module_create(current_theme, rec->name);
1133 g_free_not_null(theme->formats[n]);
1134 g_free_not_null(theme->expanded_formats[n]);
1135
1136 text = reset ? formats[n].def : value;
1137 theme->formats[n] = reset ? NULL : g_strdup(value);
1138 theme->expanded_formats[n] = theme_format_expand(current_theme, text);
1139 }
1140 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_ITEM, formats[n].tag, text);
1141 last_title = NULL;
1142 }
1143 }
1144 }
1145
1146 /* SYNTAX: FORMAT [-delete | -reset] [<module>] [<key> [<value>]] */
cmd_format(const char * data)1147 static void cmd_format(const char *data)
1148 {
1149 GHashTable *optlist;
1150 GSList *tmp, *modules;
1151 char *module, *key, *value;
1152 void *free_arg;
1153 int reset;
1154
1155 if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
1156 "format", &optlist, &module, &key, &value))
1157 return;
1158
1159 modules = get_sorted_modules();
1160 if (*module == '\0')
1161 module = NULL;
1162 else if (theme_search(modules, module) == NULL) {
1163 /* first argument isn't module.. */
1164 cmd_params_free(free_arg);
1165 if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
1166 "format", &optlist, &key, &value))
1167 return;
1168 module = NULL;
1169 }
1170
1171 reset = FALSE;
1172 if (*key == '\0') key = NULL;
1173 if (g_hash_table_lookup(optlist, "reset"))
1174 reset = TRUE;
1175 else if (g_hash_table_lookup(optlist, "delete"))
1176 value = "";
1177 else if (*value == '\0')
1178 value = NULL;
1179
1180 for (tmp = modules; tmp != NULL; tmp = tmp->next) {
1181 THEME_SEARCH_REC *rec = tmp->data;
1182
1183 if (module == NULL || g_ascii_strcasecmp(rec->short_name, module) == 0)
1184 theme_show(rec, key, value, reset);
1185 }
1186 g_slist_foreach(modules, (GFunc) g_free, NULL);
1187 g_slist_free(modules);
1188
1189 cmd_params_free(free_arg);
1190 }
1191
1192 typedef struct {
1193 CONFIG_REC *config;
1194 int save_all;
1195 } THEME_SAVE_REC;
1196
module_save(const char * module,MODULE_THEME_REC * rec,THEME_SAVE_REC * data)1197 static void module_save(const char *module, MODULE_THEME_REC *rec,
1198 THEME_SAVE_REC *data)
1199 {
1200 CONFIG_NODE *fnode, *node;
1201 FORMAT_REC *formats;
1202 int n;
1203
1204 formats = g_hash_table_lookup(default_formats, rec->name);
1205 if (formats == NULL) return;
1206
1207 fnode = config_node_traverse(data->config, "formats", TRUE);
1208
1209 node = config_node_section(data->config, fnode, rec->name, NODE_TYPE_BLOCK);
1210 for (n = 1; formats[n].def != NULL; n++) {
1211 if (rec->formats[n] != NULL) {
1212 config_node_set_str(data->config, node, formats[n].tag,
1213 rec->formats[n]);
1214 } else if (data->save_all && formats[n].tag != NULL) {
1215 config_node_set_str(data->config, node, formats[n].tag,
1216 formats[n].def);
1217 }
1218 }
1219
1220 if (node->value == NULL) {
1221 /* not modified, don't keep the empty section */
1222 config_node_remove(data->config, fnode, node);
1223 if (fnode->value == NULL) {
1224 config_node_remove(data->config,
1225 data->config->mainnode, fnode);
1226 }
1227 }
1228 }
1229
theme_save(THEME_REC * theme,int save_all)1230 static void theme_save(THEME_REC *theme, int save_all)
1231 {
1232 CONFIG_REC *config;
1233 THEME_SAVE_REC data;
1234 char *path;
1235 char *basename;
1236 int ok;
1237
1238 config = config_open(theme->path, -1);
1239 if (config != NULL)
1240 config_parse(config);
1241 else {
1242 if (g_ascii_strcasecmp(theme->name, "default") == 0) {
1243 config = config_open(NULL, -1);
1244 config_parse_data(config, default_theme, "internal");
1245 config_change_file_name(config, theme->path, 0660);
1246 } else {
1247 config = config_open(theme->path, 0660);
1248 if (config == NULL)
1249 return;
1250 config_parse(config);
1251 }
1252 }
1253
1254 data.config = config;
1255 data.save_all = save_all;
1256 g_hash_table_foreach(theme->modules, (GHFunc) module_save, &data);
1257
1258 basename = g_path_get_basename(theme->path);
1259 /* always save the theme to ~/.irssi/ */
1260 path = g_strdup_printf("%s/%s", get_irssi_dir(), basename);
1261 ok = config_write(config, path, 0660) == 0;
1262 g_free(basename);
1263
1264 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
1265 ok ? TXT_THEME_SAVED : TXT_THEME_SAVE_FAILED,
1266 path, config_last_error(config));
1267
1268 g_free(path);
1269 config_close(config);
1270 }
1271
1272 /* save changed formats, -format saves all */
cmd_save(const char * data)1273 static void cmd_save(const char *data)
1274 {
1275 GSList *tmp;
1276 GHashTable *optlist;
1277 void *free_arg;
1278 char *fname;
1279 int saveall;
1280
1281 if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
1282 "save", &optlist, &fname))
1283 return;
1284
1285 saveall = g_hash_table_lookup(optlist, "formats") != NULL;
1286 for (tmp = themes; tmp != NULL; tmp = tmp->next) {
1287 THEME_REC *theme = tmp->data;
1288
1289 theme_save(theme, saveall);
1290 }
1291
1292 cmd_params_free(free_arg);
1293 }
1294
complete_format_list(THEME_SEARCH_REC * rec,const char * key,GList ** list)1295 static void complete_format_list(THEME_SEARCH_REC *rec, const char *key, GList **list)
1296 {
1297 FORMAT_REC *formats;
1298 int n, len;
1299
1300 formats = g_hash_table_lookup(default_formats, rec->name);
1301
1302 len = strlen(key);
1303 for (n = 1; formats[n].def != NULL; n++) {
1304 const char *item = formats[n].tag;
1305
1306 if (item != NULL && g_ascii_strncasecmp(item, key, len) == 0)
1307 *list = g_list_append(*list, g_strdup(item));
1308 }
1309 }
1310
completion_get_formats(const char * module,const char * key)1311 static GList *completion_get_formats(const char *module, const char *key)
1312 {
1313 GSList *modules, *tmp;
1314 GList *list;
1315
1316 g_return_val_if_fail(key != NULL, NULL);
1317
1318 list = NULL;
1319
1320 modules = get_sorted_modules();
1321 if (*module == '\0' || theme_search(modules, module) != NULL) {
1322 for (tmp = modules; tmp != NULL; tmp = tmp->next) {
1323 THEME_SEARCH_REC *rec = tmp->data;
1324
1325 if (*module == '\0' || g_ascii_strcasecmp(rec->short_name, module) == 0)
1326 complete_format_list(rec, key, &list);
1327 }
1328 }
1329 g_slist_foreach(modules, (GFunc) g_free, NULL);
1330 g_slist_free(modules);
1331
1332 return list;
1333 }
1334
sig_complete_format(GList ** list,WINDOW_REC * window,const char * word,const char * line,int * want_space)1335 static void sig_complete_format(GList **list, WINDOW_REC *window,
1336 const char *word, const char *line, int *want_space)
1337 {
1338 const char *ptr;
1339 int words;
1340
1341 g_return_if_fail(list != NULL);
1342 g_return_if_fail(word != NULL);
1343 g_return_if_fail(line != NULL);
1344
1345 ptr = line;
1346
1347 words = 0;
1348 if (*ptr != '\0') {
1349 do {
1350 ptr++;
1351 words++;
1352 ptr = strchr(ptr, ' ');
1353 } while (ptr != NULL);
1354 }
1355
1356 if (words > 2)
1357 return;
1358
1359 *list = completion_get_formats(line, word);
1360 if (*list != NULL) signal_stop();
1361 }
1362
change_theme(const char * name,int verbose)1363 static void change_theme(const char *name, int verbose)
1364 {
1365 THEME_REC *rec;
1366
1367 rec = theme_load(name);
1368 if (rec != NULL) {
1369 current_theme = rec;
1370 signal_emit("theme changed", 1, rec);
1371
1372 if (verbose) {
1373 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
1374 TXT_THEME_CHANGED,
1375 rec->name, rec->path);
1376 }
1377 } else if (verbose) {
1378 printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
1379 TXT_THEME_NOT_FOUND, name);
1380 }
1381 }
1382
read_settings(void)1383 static void read_settings(void)
1384 {
1385 const char *theme;
1386 int len;
1387
1388 theme = settings_get_str("theme");
1389 len = strlen(current_theme->name);
1390 if (g_strcmp0(current_theme->name, theme) != 0 &&
1391 (strncmp(current_theme->name, theme, len) != 0 ||
1392 g_strcmp0(theme+len, ".theme") != 0))
1393 change_theme(theme, TRUE);
1394 }
1395
themes_reload(void)1396 void themes_reload(void)
1397 {
1398 GSList *refs;
1399 char *fname;
1400
1401 /* increase every theme's refcount, and destroy them. this way if
1402 we want to use the theme before it's reloaded we don't crash. */
1403 refs = NULL;
1404 while (themes != NULL) {
1405 THEME_REC *theme = themes->data;
1406
1407 refs = g_slist_prepend(refs, theme);
1408
1409 theme->refcount++;
1410 theme_destroy(theme);
1411 }
1412
1413 /* first there's default theme.. */
1414 current_theme = theme_load("default");
1415 if (current_theme == NULL) {
1416 fname = g_strdup_printf("%s/default.theme", get_irssi_dir());
1417 current_theme = theme_create(fname, "default");
1418 current_theme->default_color = -1;
1419 theme_read(current_theme, NULL);
1420 g_free(fname);
1421 }
1422
1423 window_themes_update();
1424 change_theme(settings_get_str("theme"), FALSE);
1425
1426 while (refs != NULL) {
1427 void *tmp = refs->data;
1428 refs = g_slist_remove(refs, refs->data);
1429 theme_unref(tmp);
1430 }
1431 }
1432
read_internal_theme(void)1433 static THEME_REC *read_internal_theme(void)
1434 {
1435 CONFIG_REC *config;
1436 THEME_REC *theme;
1437
1438 theme = theme_create("internal", "_internal");
1439 theme->refcount++;
1440 theme_destroy(theme);
1441
1442 config = config_open(NULL, -1);
1443 config_parse_data(config, default_theme, "internal");
1444 theme_read_abstracts(config, theme);
1445 config_close(config);
1446
1447 return theme;
1448 }
1449
themes_init(void)1450 void themes_init(void)
1451 {
1452 settings_add_str("lookandfeel", "theme", "default");
1453
1454 default_formats = g_hash_table_new((GHashFunc) g_str_hash,
1455 (GCompareFunc) g_str_equal);
1456 internal_theme = read_internal_theme();
1457
1458 init_finished = FALSE;
1459 init_errors = NULL;
1460
1461 themes_reload();
1462
1463 command_bind("format", NULL, (SIGNAL_FUNC) cmd_format);
1464 command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
1465 signal_add("complete command format", (SIGNAL_FUNC) sig_complete_format);
1466 signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
1467 signal_add("setup changed", (SIGNAL_FUNC) read_settings);
1468 signal_add("setup reread", (SIGNAL_FUNC) themes_reload);
1469
1470 command_set_options("format", "delete reset");
1471 command_set_options("save", "formats");
1472 }
1473
themes_deinit(void)1474 void themes_deinit(void)
1475 {
1476 while (themes != NULL)
1477 theme_destroy(themes->data);
1478 theme_destroy(internal_theme);
1479
1480 g_hash_table_destroy(default_formats);
1481 default_formats = NULL;
1482
1483 command_unbind("format", (SIGNAL_FUNC) cmd_format);
1484 command_unbind("save", (SIGNAL_FUNC) cmd_save);
1485 signal_remove("complete command format", (SIGNAL_FUNC) sig_complete_format);
1486 signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
1487 signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
1488 signal_remove("setup reread", (SIGNAL_FUNC) themes_reload);
1489 }
1490