4 Copyright (C) 1999-2000 Timo Sirainen
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.
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.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "module-formats.h"
27 #include "special-vars.h"
28 #include "lib-config/iconfig.h"
32 #include "printtext.h"
34 #include "default-theme.h"
37 THEME_REC *current_theme;
38 GHashTable *default_formats;
40 static int init_finished;
41 static char *init_errors;
42 static THEME_REC *internal_theme;
44 static int theme_read(THEME_REC *theme, const char *path);
46 THEME_REC *theme_create(const char *path, const char *name)
50 g_return_val_if_fail(path != NULL, NULL);
51 g_return_val_if_fail(name != NULL, NULL);
53 rec = g_new0(THEME_REC, 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);
67 static void theme_abstract_destroy(char *key, char *value)
73 static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec)
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]);
82 g_free(rec->expanded_formats);
88 static void theme_real_destroy(THEME_REC *rec)
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);
95 g_slist_foreach(rec->replace_values, (GFunc) g_free, NULL);
96 g_slist_free(rec->replace_values);
103 static void theme_unref(THEME_REC *rec)
105 if (--rec->refcount == 0)
106 theme_real_destroy(rec);
109 void theme_destroy(THEME_REC *rec)
111 themes = g_slist_remove(themes, rec);
112 signal_emit("theme destroyed", 1, rec);
117 static char *theme_replace_expand(THEME_REC *theme, int index,
118 char default_fg, char default_bg,
119 char *last_fg, char *last_bg,
123 char *ret, *abstract, data[2];
125 rec = g_slist_nth(theme->replace_values, index);
126 g_return_val_if_fail(rec != NULL, NULL);
128 data[0] = chr; data[1] = '\0';
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);
134 ret = parse_special_string(abstract, NULL, NULL, data, NULL,
135 PARSE_FLAG_ONLY_ARGS);
140 static const char *fgcolorformats = "nkrgybmpcwKRGYBMPCW";
141 static const char *bgcolorformats = "n01234567";
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)
148 /* append "variable" part in $variable, ie. not the contents of the variable */
149 static void theme_format_append_variable(GString *str, const char **format)
152 char *value, *args[1] = { NULL };
158 value = parse_special((char **) format, NULL, NULL,
159 args, &free_ret, NULL, PARSE_FLAG_ONLY_ARGS);
160 if (free_ret) g_free(value);
162 if (**format != '\0')
165 /* append the variable name */
166 value = g_strndup(orig, (int) (*format-orig));
167 g_string_append(str, value);
171 /* append next "item", either a character, $variable or %format */
172 static void theme_format_append_next(THEME_REC *theme, GString *str,
174 char default_fg, char default_bg,
175 char *last_fg, char *last_bg,
182 if ((chr == '$' || chr == '%') &&
183 (*format)[1] == '\0') {
184 /* last char, always append */
185 g_string_append_c(str, chr);
191 /* $variable .. we'll always need to skip this, since it
192 may contain characters that are in replace chars. */
193 theme_format_append_variable(str, format);
197 if (**format == '%') {
200 if (**format != '{' && **format != '}') {
202 if (**format == 'n') {
203 /* %n = change to default color */
204 g_string_append(str, "%n");
206 if (default_bg != 'n') {
207 g_string_append_c(str, '%');
208 g_string_append_c(str, default_bg);
210 if (default_fg != 'n') {
211 g_string_append_c(str, '%');
212 g_string_append_c(str, default_fg);
215 *last_fg = default_fg;
216 *last_bg = default_bg;
218 if (IS_FGCOLOR_FORMAT(chr))
220 if (IS_BGCOLOR_FORMAT(chr))
222 g_string_append_c(str, '%');
223 g_string_append_c(str, chr);
229 /* %{ or %} gives us { or } char - keep the % char
230 though to make sure {} isn't treated as abstract */
231 g_string_append_c(str, '%');
235 index = (flags & EXPAND_FLAG_IGNORE_REPLACES) ? -1 :
236 theme->replace_keys[(int) (unsigned char) chr];
238 g_string_append_c(str, chr);
242 value = theme_replace_expand(theme, index,
243 default_fg, default_bg,
244 last_fg, last_bg, chr, flags);
245 g_string_append(str, value);
252 /* returns TRUE if data is empty, or the data is a $variable which is empty */
253 static int data_is_empty(const char **data)
255 /* since we don't know the real argument list, assume there's always
256 an argument in them */
257 static char *arglist[] = {
258 "x", "x", "x", "x", "x", "x","x", "x", "x", "x",
267 while (*p == ' ') p++;
280 /* variable - check if it's empty */
283 server = active_win == NULL ? NULL :
284 active_win->active_server != NULL ?
285 active_win->active_server : active_win->connect_server;
287 ret = parse_special((char **) &p, server,
288 active_win == NULL ? NULL : active_win->active,
289 arglist, &free_ret, NULL, 0);
292 while (*p == ' ') p++;
293 empty = *p == '}' && (ret == NULL || *ret == '\0');
294 if (free_ret) g_free(ret);
305 /* return "data" from {abstract data} string */
306 char *theme_format_expand_get(THEME_REC *theme, const char **format)
310 int braces = 1; /* we start with one brace opened */
312 str = g_string_new(NULL);
313 while (**format != '\0' && braces != 0) {
316 else if (**format == '}')
318 else if ((braces > 1) && (**format == ' ')) {
319 g_string_append(str, "\\x20");
323 theme_format_append_next(theme, str, format,
334 g_string_append_c(str, **format);
339 g_string_free(str, FALSE);
343 /* expand a single {abstract ...data... } */
344 static char *theme_format_expand_abstract(THEME_REC *theme,
345 const char **formatp,
346 char default_fg, char default_bg,
350 const char *p, *format;
351 char *abstract, *data, *ret;
356 /* get abstract name first */
358 while (*p != '\0' && *p != ' ' &&
359 *p != '{' && *p != '}') p++;
360 if (*p == '\0' || p == format)
361 return NULL; /* error */
363 len = (int) (p-format);
364 abstract = g_strndup(format, len);
366 /* skip the following space, if there's any more spaces they're
367 treated as arguments */
370 if ((flags & EXPAND_FLAG_IGNORE_EMPTY) && data_is_empty(&p)) {
376 *formatp = format+len;
378 /* get the abstract data */
379 data = g_hash_table_lookup(theme->abstracts, abstract);
382 /* unknown abstract, just display the data */
385 abstract = g_strdup(data);
387 /* we'll need to get the data part. it may contain
388 more abstracts, they are _NOT_ expanded. */
389 data = theme_format_expand_get(theme, formatp);
392 if (len > 1 && i_isdigit(data[len-1]) && data[len-2] == '$') {
393 /* ends with $<digit> .. this breaks things if next
394 character is digit or '-' */
401 data = g_strdup_printf("%s{%c}", tmp, digit);
405 ret = parse_special_string(abstract, NULL, NULL, data, NULL,
406 PARSE_FLAG_ONLY_ARGS);
409 str = g_string_new(NULL);
415 chr = expand_escape(&p);
416 g_string_append_c(str, chr != -1 ? chr : *p);
418 g_string_append_c(str, *p);
423 g_string_free(str, FALSE);
425 /* abstract may itself contain abstracts or replaces */
427 ret = theme_format_expand_data(theme, &p, default_fg, default_bg,
428 &default_fg, &default_bg,
429 flags | EXPAND_FLAG_LASTCOLOR_ARG);
434 /* expand the data part in {abstract data} */
435 char *theme_format_expand_data(THEME_REC *theme, const char **format,
436 char default_fg, char default_bg,
437 char *save_last_fg, char *save_last_bg,
441 char *ret, *abstract;
442 char last_fg, last_bg;
445 last_fg = default_fg;
446 last_bg = default_bg;
447 recurse_flags = flags & EXPAND_FLAG_RECURSIVE_MASK;
449 str = g_string_new(NULL);
450 while (**format != '\0') {
451 if ((flags & EXPAND_FLAG_ROOT) == 0 && **format == '}') {
452 /* ignore } if we're expanding original string */
457 if (**format != '{') {
458 if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) &&
459 **format == '$' && (*format)[1] == '0') {
460 /* save the color before $0 ..
461 this is for the %n replacing */
462 if (save_last_fg != NULL) {
463 *save_last_fg = last_fg;
466 if (save_last_bg != NULL) {
467 *save_last_bg = last_bg;
472 theme_format_append_next(theme, str, format,
473 default_fg, default_bg,
480 if (**format == '\0' || **format == '}')
483 /* get a single {...} */
484 abstract = theme_format_expand_abstract(theme, format,
487 if (abstract != NULL) {
488 g_string_append(str, abstract);
493 if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) == 0) {
494 /* save the last color */
495 if (save_last_fg != NULL)
496 *save_last_fg = last_fg;
497 if (save_last_bg != NULL)
498 *save_last_bg = last_bg;
502 g_string_free(str, FALSE);
506 #define IS_OLD_FORMAT(code, last_fg, last_bg) \
507 (((code) == 'n' && (last_fg) == 'n' && (last_bg) == 'n') || \
508 ((code) != 'n' && ((code) == (last_fg) || (code) == (last_bg))))
510 static char *theme_format_compress_colors(THEME_REC *theme, const char *format)
513 char *ret, last_fg, last_bg;
515 str = g_string_new(NULL);
517 last_fg = last_bg = '\0';
518 while (*format != '\0') {
519 if (*format == '$') {
520 /* $variable, skrip it entirely */
521 theme_format_append_variable(str, &format);
522 last_fg = last_bg = '\0';
523 } else if (*format != '%') {
524 /* a normal character */
525 g_string_append_c(str, *format);
530 if (IS_OLD_FORMAT(*format, last_fg, last_bg)) {
531 /* active color set again */
532 } else if (IS_FGCOLOR_FORMAT(*format) &&
534 IS_FGCOLOR_FORMAT(format[2]) &&
535 (*format != 'n' || format[2] == 'n')) {
536 /* two fg colors in a row. bg colors are
537 so rare that we don't bother checking
540 /* some format, add it */
541 g_string_append_c(str, '%');
542 g_string_append_c(str, *format);
544 if (IS_FGCOLOR_FORMAT(*format))
546 if (IS_BGCOLOR_FORMAT(*format))
554 g_string_free(str, FALSE);
558 char *theme_format_expand(THEME_REC *theme, const char *format)
562 g_return_val_if_fail(theme != NULL, NULL);
563 g_return_val_if_fail(format != NULL, NULL);
565 data = theme_format_expand_data(theme, &format, 'n', 'n', NULL, NULL,
567 ret = theme_format_compress_colors(theme, data);
572 static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *module)
574 MODULE_THEME_REC *rec;
577 rec = g_hash_table_lookup(theme->modules, module);
578 if (rec != NULL) return rec;
580 formats = g_hash_table_lookup(default_formats, module);
581 g_return_val_if_fail(formats != NULL, NULL);
583 rec = g_new0(MODULE_THEME_REC, 1);
584 rec->name = g_strdup(module);
586 for (rec->count = 0; formats[rec->count].def != NULL; rec->count++) ;
587 rec->formats = g_new0(char *, rec->count);
588 rec->expanded_formats = g_new0(char *, rec->count);
590 g_hash_table_insert(theme->modules, rec->name, rec);
594 static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme)
601 /* reset replace keys */
602 for (index = 0; index < 256; index++)
603 theme->replace_keys[index] = -1;
606 node = config_node_traverse(config, "replaces", FALSE);
607 if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
609 for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
612 if (node->key != NULL && node->value != NULL) {
613 for (p = node->key; *p != '\0'; p++)
614 theme->replace_keys[(int) (unsigned char) *p] = index;
616 theme->replace_values =
617 g_slist_append(theme->replace_values,
618 g_strdup(node->value));
624 static void theme_read_abstracts(CONFIG_REC *config, THEME_REC *theme)
628 gpointer oldkey, oldvalue;
630 node = config_node_traverse(config, "abstracts", FALSE);
631 if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
633 for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
636 if (node->key == NULL || node->value == NULL)
639 if (g_hash_table_lookup_extended(theme->abstracts, node->key,
640 &oldkey, &oldvalue)) {
641 /* new values override old ones */
642 g_hash_table_remove(theme->abstracts, oldkey);
647 g_hash_table_insert(theme->abstracts, g_strdup(node->key),
648 g_strdup(node->value));
652 static void theme_set_format(THEME_REC *theme, MODULE_THEME_REC *rec,
654 const char *key, const char *value)
658 num = format_find_tag(module, key);
660 rec->formats[num] = g_strdup(value);
661 rec->expanded_formats[num] = theme_format_expand(theme, value);
665 static void theme_read_formats(THEME_REC *theme, const char *module,
666 CONFIG_REC *config, MODULE_THEME_REC *rec)
671 node = config_node_traverse(config, "formats", FALSE);
672 if (node == NULL) return;
673 node = config_node_section(node, module, -1);
674 if (node == NULL) return;
676 for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
679 if (node->key != NULL && node->value != NULL) {
680 theme_set_format(theme, rec, module,
681 node->key, node->value);
686 static void theme_init_module(THEME_REC *theme, const char *module,
689 MODULE_THEME_REC *rec;
693 formats = g_hash_table_lookup(default_formats, module);
694 g_return_if_fail(formats != NULL);
696 rec = theme_module_create(theme, module);
699 theme_read_formats(theme, module, config, rec);
701 /* expand the remaining formats */
702 for (n = 0; n < rec->count; n++) {
703 if (rec->expanded_formats[n] == NULL) {
704 rec->expanded_formats[n] =
705 theme_format_expand(theme, formats[n].def);
710 static void sig_print_errors(void)
712 init_finished = TRUE;
714 if (init_errors != NULL) {
715 signal_emit("gui dialog", 2, "error", init_errors);
720 static void theme_read_module(THEME_REC *theme, const char *module)
724 config = config_open(theme->path, -1);
726 config_parse(config);
728 theme_init_module(theme, module, config);
730 if (config != NULL) config_close(config);
733 static void themes_read_module(const char *module)
735 g_slist_foreach(themes, (GFunc) theme_read_module, (void *) module);
738 static void theme_remove_module(THEME_REC *theme, const char *module)
740 MODULE_THEME_REC *rec;
742 rec = g_hash_table_lookup(theme->modules, module);
743 if (rec == NULL) return;
745 g_hash_table_remove(theme->modules, module);
746 theme_module_destroy(module, rec);
749 static void themes_remove_module(const char *module)
751 g_slist_foreach(themes, (GFunc) theme_remove_module, (void *) module);
754 void theme_register_module(const char *module, FORMAT_REC *formats)
756 if (g_hash_table_lookup(default_formats, module) != NULL)
759 g_hash_table_insert(default_formats, g_strdup(module), formats);
760 themes_read_module(module);
763 void theme_unregister_module(const char *module)
767 if (default_formats == NULL)
768 return; /* already uninitialized */
770 if (!g_hash_table_lookup_extended(default_formats, module, &key, &value))
773 g_hash_table_remove(default_formats, key);
776 themes_remove_module(module);
779 void theme_set_default_abstract(const char *key, const char *value)
781 gpointer oldkey, oldvalue;
783 if (g_hash_table_lookup_extended(internal_theme->abstracts, key,
784 &oldkey, &oldvalue)) {
785 /* new values override old ones */
786 g_hash_table_remove(internal_theme->abstracts, oldkey);
791 g_hash_table_insert(internal_theme->abstracts,
792 g_strdup(key), g_strdup(value));
795 static THEME_REC *theme_find(const char *name)
799 for (tmp = themes; tmp != NULL; tmp = tmp->next) {
800 THEME_REC *rec = tmp->data;
802 if (g_strcasecmp(rec->name, name) == 0)
809 static void window_themes_update(void)
813 for (tmp = windows; tmp != NULL; tmp = tmp->next) {
814 WINDOW_REC *rec = tmp->data;
816 if (rec->theme_name != NULL)
817 rec->theme = theme_load(rec->theme_name);
821 THEME_REC *theme_load(const char *setname)
823 THEME_REC *theme, *oldtheme;
825 char *fname, *name, *p;
827 name = g_strdup(setname);
828 p = strrchr(name, '.');
829 if (p != NULL && strcmp(p, ".theme") == 0) {
830 /* remove the trailing .theme */
834 theme = theme_find(name);
837 fname = g_strdup_printf("%s/%s.theme", get_irssi_dir(), name);
838 if (stat(fname, &statbuf) != 0) {
839 /* check global config dir */
841 fname = g_strdup_printf(THEMESDIR"/%s.theme", name);
842 if (stat(fname, &statbuf) != 0) {
843 /* theme not found */
846 return theme; /* use the one in memory if possible */
850 if (theme != NULL && theme->last_modify == statbuf.st_mtime) {
851 /* theme not modified, use the one already in memory */
858 theme = theme_create(fname, name);
859 theme->last_modify = statbuf.st_mtime;
860 if (!theme_read(theme, theme->path)) {
861 /* error reading .theme file */
862 theme_destroy(theme);
866 if (oldtheme != NULL && theme != NULL) {
867 theme_destroy(oldtheme);
868 if (current_theme == oldtheme)
869 current_theme = theme;
870 window_themes_update();
878 static void copy_abstract_hash(char *key, char *value, GHashTable *dest)
880 g_hash_table_insert(dest, g_strdup(key), g_strdup(value));
883 static void theme_copy_abstracts(THEME_REC *dest, THEME_REC *src)
885 g_hash_table_foreach(src->abstracts, (GHFunc) copy_abstract_hash,
894 static void theme_read_modules(const char *module, void *value,
897 theme_init_module(rec->theme, module, rec->config);
900 static void read_error(const char *str)
905 printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str);
906 else if (init_errors == NULL)
907 init_errors = g_strdup(str);
910 init_errors = g_strconcat(init_errors, "\n", str, NULL);
915 static int theme_read(THEME_REC *theme, const char *path)
921 config = config_open(path, -1) ;
922 if (config == NULL) {
923 /* didn't exist or no access? */
924 str = g_strdup_printf("Error reading theme file %s: %s",
925 path, g_strerror(errno));
932 config_parse_data(config, default_theme, "internal");
934 config_parse(config);
936 if (config_last_error(config) != NULL) {
937 str = g_strdup_printf("Ignored errors in theme %s:\n%s",
938 theme->name, config_last_error(config));
943 theme->default_color =
944 config_get_int(config, NULL, "default_color", -1);
945 theme->info_eol = config_get_bool(config, NULL, "info_eol", FALSE);
947 /* FIXME: remove after 0.7.99 */
948 if (theme->default_color == 0 &&
949 config_get_int(config, NULL, "default_real_color", -1) != -1)
950 theme->default_color = -1;
951 theme_read_replaces(config, theme);
954 theme_copy_abstracts(theme, internal_theme);
955 theme_read_abstracts(config, theme);
959 g_hash_table_foreach(default_formats,
960 (GHFunc) theme_read_modules, &rec);
961 config_close(config);
971 static int theme_search_equal(THEME_SEARCH_REC *r1, THEME_SEARCH_REC *r2)
973 return g_strcasecmp(r1->short_name, r2->short_name);
976 static void theme_get_modules(char *module, FORMAT_REC *formats, GSList **list)
978 THEME_SEARCH_REC *rec;
980 rec = g_new(THEME_SEARCH_REC, 1);
982 rec->short_name = strrchr(module, '/');
983 if (rec->short_name != NULL)
984 rec->short_name++; else rec->short_name = module;
985 *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) theme_search_equal);
988 static GSList *get_sorted_modules(void)
993 g_hash_table_foreach(default_formats, (GHFunc) theme_get_modules, &list);
997 static THEME_SEARCH_REC *theme_search(GSList *list, const char *module)
999 THEME_SEARCH_REC *rec;
1001 while (list != NULL) {
1004 if (g_strcasecmp(rec->short_name, module) == 0)
1012 static void theme_show(THEME_SEARCH_REC *rec, const char *key, const char *value, int reset)
1014 MODULE_THEME_REC *theme;
1015 FORMAT_REC *formats;
1016 const char *text, *last_title;
1019 formats = g_hash_table_lookup(default_formats, rec->name);
1020 theme = g_hash_table_lookup(current_theme->modules, rec->name);
1022 last_title = NULL; first = TRUE;
1023 for (n = 1; formats[n].def != NULL; n++) {
1024 text = theme != NULL && theme->formats[n] != NULL ?
1025 theme->formats[n] : formats[n].def;
1027 if (formats[n].tag == NULL)
1029 else if ((value != NULL && key != NULL && g_strcasecmp(formats[n].tag, key) == 0) ||
1030 (value == NULL && (key == NULL || stristr(formats[n].tag, key) != NULL))) {
1032 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_TITLE, rec->short_name, formats[0].def);
1035 if (last_title != NULL)
1036 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_SUBTITLE, last_title);
1037 if (reset || value != NULL) {
1038 theme = theme_module_create(current_theme, rec->name);
1039 g_free_not_null(theme->formats[n]);
1040 g_free_not_null(theme->expanded_formats[n]);
1042 text = reset ? formats[n].def : value;
1043 theme->formats[n] = reset ? NULL : g_strdup(value);
1044 theme->expanded_formats[n] = theme_format_expand(current_theme, text);
1046 printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_ITEM, formats[n].tag, text);
1052 /* SYNTAX: FORMAT [-delete | -reset] [<module>] [<key> [<value>]] */
1053 static void cmd_format(const char *data)
1055 GHashTable *optlist;
1056 GSList *tmp, *modules;
1057 char *module, *key, *value;
1061 if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
1062 "format", &optlist, &module, &key, &value))
1065 modules = get_sorted_modules();
1066 if (*module == '\0')
1068 else if (theme_search(modules, module) == NULL) {
1069 /* first argument isn't module.. */
1070 cmd_params_free(free_arg);
1071 if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
1072 "format", &optlist, &key, &value))
1078 if (*key == '\0') key = NULL;
1079 if (g_hash_table_lookup(optlist, "reset"))
1081 else if (g_hash_table_lookup(optlist, "delete"))
1083 else if (*value == '\0')
1086 for (tmp = modules; tmp != NULL; tmp = tmp->next) {
1087 THEME_SEARCH_REC *rec = tmp->data;
1089 if (module == NULL || g_strcasecmp(rec->short_name, module) == 0)
1090 theme_show(rec, key, value, reset);
1092 g_slist_foreach(modules, (GFunc) g_free, NULL);
1093 g_slist_free(modules);
1095 cmd_params_free(free_arg);
1103 static void module_save(const char *module, MODULE_THEME_REC *rec,
1104 THEME_SAVE_REC *data)
1106 CONFIG_NODE *fnode, *node;
1107 FORMAT_REC *formats;
1110 formats = g_hash_table_lookup(default_formats, rec->name);
1111 if (formats == NULL) return;
1113 fnode = config_node_traverse(data->config, "formats", TRUE);
1115 node = config_node_section(fnode, rec->name, NODE_TYPE_BLOCK);
1116 for (n = 1; formats[n].def != NULL; n++) {
1117 if (rec->formats[n] != NULL) {
1118 config_node_set_str(data->config, node, formats[n].tag,
1120 } else if (data->save_all && formats[n].tag != NULL) {
1121 config_node_set_str(data->config, node, formats[n].tag,
1126 if (node->value == NULL) {
1127 /* not modified, don't keep the empty section */
1128 config_node_remove(data->config, fnode, node);
1129 if (fnode->value == NULL) {
1130 config_node_remove(data->config,
1131 data->config->mainnode, fnode);
1136 static void theme_save(THEME_REC *theme, int save_all)
1139 THEME_SAVE_REC data;
1143 config = config_open(theme->path, -1);
1145 config_parse(config);
1147 if (g_strcasecmp(theme->name, "default") == 0) {
1148 config = config_open(NULL, -1);
1149 config_parse_data(config, default_theme, "internal");
1150 config_change_file_name(config, theme->path, 0660);
1152 config = config_open(theme->path, 0660);
1155 config_parse(config);
1159 data.config = config;
1160 data.save_all = save_all;
1161 g_hash_table_foreach(theme->modules, (GHFunc) module_save, &data);
1163 /* always save the theme to ~/.irssi/ */
1164 path = g_strdup_printf("%s/%s", get_irssi_dir(),
1165 g_basename(theme->path));
1166 ok = config_write(config, path, 0660) == 0;
1168 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
1169 ok ? TXT_THEME_SAVED : TXT_THEME_SAVE_FAILED,
1170 path, config_last_error(config));
1173 config_close(config);
1176 /* save changed formats, -format saves all */
1177 static void cmd_save(const char *data)
1180 GHashTable *optlist;
1185 if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
1186 "save", &optlist, &fname))
1189 saveall = g_hash_table_lookup(optlist, "formats") != NULL;
1190 for (tmp = themes; tmp != NULL; tmp = tmp->next) {
1191 THEME_REC *theme = tmp->data;
1193 theme_save(theme, saveall);
1196 cmd_params_free(free_arg);
1199 static void complete_format_list(THEME_SEARCH_REC *rec, const char *key, GList **list)
1201 FORMAT_REC *formats;
1204 formats = g_hash_table_lookup(default_formats, rec->name);
1207 for (n = 1; formats[n].def != NULL; n++) {
1208 const char *item = formats[n].tag;
1210 if (item != NULL && g_strncasecmp(item, key, len) == 0)
1211 *list = g_list_append(*list, g_strdup(item));
1215 static GList *completion_get_formats(const char *module, const char *key)
1217 GSList *modules, *tmp;
1220 g_return_val_if_fail(key != NULL, NULL);
1224 modules = get_sorted_modules();
1225 if (*module == '\0' || theme_search(modules, module) != NULL) {
1226 for (tmp = modules; tmp != NULL; tmp = tmp->next) {
1227 THEME_SEARCH_REC *rec = tmp->data;
1229 if (*module == '\0' || g_strcasecmp(rec->short_name, module) == 0)
1230 complete_format_list(rec, key, &list);
1233 g_slist_foreach(modules, (GFunc) g_free, NULL);
1234 g_slist_free(modules);
1239 static void sig_complete_format(GList **list, WINDOW_REC *window,
1240 const char *word, const char *line, int *want_space)
1245 g_return_if_fail(list != NULL);
1246 g_return_if_fail(word != NULL);
1247 g_return_if_fail(line != NULL);
1256 ptr = strchr(ptr, ' ');
1257 } while (ptr != NULL);
1263 *list = completion_get_formats(line, word);
1264 if (*list != NULL) signal_stop();
1267 static void change_theme(const char *name, int verbose)
1271 rec = theme_load(name);
1273 current_theme = rec;
1274 signal_emit("theme changed", 1, rec);
1277 printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
1279 rec->name, rec->path);
1281 } else if (verbose) {
1282 printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
1283 TXT_THEME_NOT_FOUND, name);
1287 static void read_settings(void)
1292 theme = settings_get_str("theme");
1293 len = strlen(current_theme->name);
1294 if (strcmp(current_theme->name, theme) != 0 &&
1295 (strncmp(current_theme->name, theme, len) != 0 ||
1296 strcmp(theme+len, ".theme") != 0))
1297 change_theme(theme, TRUE);
1300 void themes_reload(void)
1305 /* increase every theme's refcount, and destroy them. this way if
1306 we want to use the theme before it's reloaded we don't crash. */
1308 while (themes != NULL) {
1309 THEME_REC *theme = themes->data;
1311 refs = g_slist_prepend(refs, theme);
1314 theme_destroy(theme);
1317 /* first there's default theme.. */
1318 current_theme = theme_load("default");
1319 if (current_theme == NULL) {
1320 fname = g_strdup_printf("%s/default.theme", get_irssi_dir());
1321 current_theme = theme_create(fname, "default");
1322 current_theme->default_color = -1;
1323 theme_read(current_theme, NULL);
1327 window_themes_update();
1328 change_theme(settings_get_str("theme"), FALSE);
1330 while (refs != NULL) {
1331 theme_unref(refs->data);
1332 refs = g_slist_remove(refs, refs->data);
1336 static THEME_REC *read_internal_theme(void)
1341 theme = theme_create("internal", "_internal");
1343 config = config_open(NULL, -1);
1344 config_parse_data(config, default_theme, "internal");
1345 theme_read_abstracts(config, theme);
1346 config_close(config);
1351 void themes_init(void)
1353 settings_add_str("lookandfeel", "theme", "default");
1355 default_formats = g_hash_table_new((GHashFunc) g_str_hash,
1356 (GCompareFunc) g_str_equal);
1357 internal_theme = read_internal_theme();
1359 init_finished = FALSE;
1365 command_bind("format", NULL, (SIGNAL_FUNC) cmd_format);
1366 command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
1367 signal_add("complete command format", (SIGNAL_FUNC) sig_complete_format);
1368 signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
1369 signal_add("setup changed", (SIGNAL_FUNC) read_settings);
1370 signal_add("setup reread", (SIGNAL_FUNC) themes_reload);
1372 command_set_options("format", "delete reset");
1373 command_set_options("save", "formats");
1376 void themes_deinit(void)
1378 while (themes != NULL)
1379 theme_destroy(themes->data);
1380 theme_destroy(internal_theme);
1382 g_hash_table_destroy(default_formats);
1383 default_formats = NULL;
1385 command_unbind("format", (SIGNAL_FUNC) cmd_format);
1386 command_unbind("save", (SIGNAL_FUNC) cmd_save);
1387 signal_remove("complete command format", (SIGNAL_FUNC) sig_complete_format);
1388 signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
1389 signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
1390 signal_remove("setup reread", (SIGNAL_FUNC) themes_reload);