Merged from silc_1_0_branch.
[silc.git] / apps / irssi / src / fe-common / core / themes.c
index de9b6dee1adf49b3db37231838346d4783b2b99f..437b118faeccf36a8a08eb1b6823c39164ecec22 100644 (file)
@@ -50,6 +50,7 @@ THEME_REC *theme_create(const char *path, const char *name)
        g_return_val_if_fail(name != NULL, NULL);
 
        rec = g_new0(THEME_REC, 1);
+       rec->refcount = 1;
        rec->path = g_strdup(path);
        rec->name = g_strdup(name);
        rec->abstracts = g_hash_table_new((GHashFunc) g_str_hash,
@@ -83,12 +84,8 @@ static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec)
        g_free(rec);
 }
 
-void theme_destroy(THEME_REC *rec)
+static void theme_real_destroy(THEME_REC *rec)
 {
-       themes = g_slist_remove(themes, rec);
-
-       signal_emit("theme destroyed", 1, rec);
-
        g_hash_table_foreach(rec->abstracts, (GHFunc) theme_abstract_destroy, NULL);
        g_hash_table_destroy(rec->abstracts);
        g_hash_table_foreach(rec->modules, (GHFunc) theme_module_destroy, NULL);
@@ -102,6 +99,20 @@ void theme_destroy(THEME_REC *rec)
        g_free(rec);
 }
 
+static void theme_unref(THEME_REC *rec)
+{
+       if (--rec->refcount == 0)
+               theme_real_destroy(rec);
+}
+
+void theme_destroy(THEME_REC *rec)
+{
+       themes = g_slist_remove(themes, rec);
+       signal_emit("theme destroyed", 1, rec);
+
+       theme_unref(rec);
+}
+
 static char *theme_replace_expand(THEME_REC *theme, int index,
                                  char default_fg, char default_bg,
                                  char *last_fg, char *last_bg,
@@ -119,7 +130,8 @@ static char *theme_replace_expand(THEME_REC *theme, int index,
        abstract = theme_format_expand_data(theme, (const char **) &abstract,
                                            default_fg, default_bg,
                                            last_fg, last_bg, flags);
-       ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0);
+       ret = parse_special_string(abstract, NULL, NULL, data, NULL,
+                                  PARSE_FLAG_ONLY_ARGS);
        g_free(abstract);
        return ret;
 }
@@ -143,9 +155,11 @@ static void theme_format_append_variable(GString *str, const char **format)
        (*format)++;
 
        value = parse_special((char **) format, NULL, NULL,
-                             args, &free_ret, NULL, 0);
+                             args, &free_ret, NULL, PARSE_FLAG_ONLY_ARGS);
        if (free_ret) g_free(value);
-       (*format)++;
+
+       if (**format != '\0')
+               (*format)++;
 
        /* append the variable name */
        value = g_strndup(orig, (int) (*format-orig));
@@ -211,12 +225,14 @@ static void theme_format_append_next(THEME_REC *theme, GString *str,
                        return;
                }
 
-               /* %{ or %} gives us { or } char */
+               /* %{ or %} gives us { or } char - keep the % char
+                  though to make sure {} isn't treated as abstract */
+               g_string_append_c(str, '%');
                chr = **format;
        }
 
        index = (flags & EXPAND_FLAG_IGNORE_REPLACES) ? -1 :
-               theme->replace_keys[(int) chr];
+               theme->replace_keys[(int) (unsigned char) chr];
        if (index == -1)
                g_string_append_c(str, chr);
        else {
@@ -232,12 +248,104 @@ static void theme_format_append_next(THEME_REC *theme, GString *str,
         (*format)++;
 }
 
+/* returns TRUE if data is empty, or the data is a $variable which is empty */
+static int data_is_empty(const char **data)
+{
+       /* since we don't know the real argument list, assume there's always
+          an argument in them */
+       static char *arglist[] = {
+               "x", "x", "x", "x", "x", "x","x", "x", "x", "x",
+               NULL
+       };
+       SERVER_REC *server;
+       const char *p;
+       char *ret;
+        int free_ret, empty;
+
+        p = *data;
+       while (*p == ' ') p++;
+
+       if (*p == '}') {
+                /* empty */
+                *data = p+1;
+                return TRUE;
+       }
+
+       if (*p != '$') {
+                /* not empty */
+               return FALSE;
+       }
+
+       /* variable - check if it's empty */
+        p++;
+
+       server = active_win == NULL ? NULL :
+               active_win->active_server != NULL ?
+               active_win->active_server : active_win->connect_server;
+
+       ret = parse_special((char **) &p, server,
+                           active_win == NULL ? NULL : active_win->active,
+                           arglist, &free_ret, NULL, 0);
+        p++;
+
+       while (*p == ' ') p++;
+       empty = *p == '}' && (ret == NULL || *ret == '\0');
+        if (free_ret) g_free(ret);
+
+       if (empty) {
+               /* empty */
+               *data = p+1;
+                return TRUE;
+       }
+
+        return FALSE;
+}
+
+/* return "data" from {abstract data} string */
+char *theme_format_expand_get(THEME_REC *theme, const char **format)
+{
+       GString *str;
+       char *ret, dummy;
+       int braces = 1; /* we start with one brace opened */
+
+       str = g_string_new(NULL);
+       while ((**format != '\0') && (braces)) {
+               if (**format == '{')
+                       braces++;
+               else if (**format == '}')
+                       braces--;
+               else if ((braces > 1) && (**format == ' ')) {
+                       g_string_append(str, "\\x20");
+                       (*format)++;
+                       continue;
+               } else {
+                       theme_format_append_next(theme, str, format,
+                                                'n', 'n',
+                                                &dummy, &dummy, 0);
+                       continue;
+               }
+               
+               if (!braces) {
+                       (*format)++;
+                       break;
+               }
+
+               g_string_append_c(str, **format);
+               (*format)++;
+       }
+
+       ret = str->str;
+        g_string_free(str, FALSE);
+        return ret;
+}
+
 /* expand a single {abstract ...data... } */
 static char *theme_format_expand_abstract(THEME_REC *theme,
                                          const char **formatp,
                                          char default_fg, char default_bg,
                                          int flags)
 {
+       GString *str;
        const char *p, *format;
        char *abstract, *data, *ret;
        int len;
@@ -258,17 +366,11 @@ static char *theme_format_expand_abstract(THEME_REC *theme,
           treated as arguments */
        if (*p == ' ') {
                len++;
-               if ((flags & EXPAND_FLAG_IGNORE_EMPTY)) {
-                        /* if the data is empty, ignore the abstract */
-                       p = format+len;
-                       while (*p == ' ') p++;
-                       if (*p == '}') {
-                                *formatp = p+1;
-                               g_free(abstract);
-                               return NULL;
-                       }
+               if ((flags & EXPAND_FLAG_IGNORE_EMPTY) && data_is_empty(&p)) {
+                       *formatp = p;
+                       g_free(abstract);
+                       return NULL;
                }
-
        }
        *formatp = format+len;
 
@@ -282,12 +384,11 @@ static char *theme_format_expand_abstract(THEME_REC *theme,
        abstract = g_strdup(data);
 
        /* we'll need to get the data part. it may contain
-          more abstracts, they are automatically expanded. */
-       data = theme_format_expand_data(theme, formatp, default_fg, default_bg,
-                                       NULL, NULL, flags);
+          more abstracts, they are _NOT_ expanded. */
+       data = theme_format_expand_get(theme, formatp);
        len = strlen(data);
 
-       if (len > 1 && isdigit(data[len-1]) && data[len-2] == '$') {
+       if (len > 1 && i_isdigit(data[len-1]) && data[len-2] == '$') {
                /* ends with $<digit> .. this breaks things if next
                   character is digit or '-' */
                 char digit, *tmp;
@@ -300,10 +401,25 @@ static char *theme_format_expand_abstract(THEME_REC *theme,
                g_free(tmp);
        }
 
-       ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0);
+       ret = parse_special_string(abstract, NULL, NULL, data, NULL,
+                                  PARSE_FLAG_ONLY_ARGS);
        g_free(abstract);
         g_free(data);
-       abstract = ret;
+       str = g_string_new(NULL);
+       p = ret;
+       while (*p != '\0') {
+               if (*p == '\\') {
+                       int chr;
+                       p++;
+                       chr = expand_escape(&p);
+                       g_string_append_c(str, chr != -1 ? chr : *p);
+               } else
+                       g_string_append_c(str, *p);
+               p++;
+       }
+       g_free(ret);
+       abstract = str->str;
+       g_string_free(str, FALSE);
 
        /* abstract may itself contain abstracts or replaces */
        p = abstract;
@@ -397,7 +513,7 @@ static char *theme_format_compress_colors(THEME_REC *theme, const char *format)
 
        str = g_string_new(NULL);
 
-       last_fg = last_bg = 'n';
+       last_fg = last_bg = '\0';
        while (*format != '\0') {
                if (*format == '$') {
                         /* $variable, skrip it entirely */
@@ -413,9 +529,9 @@ static char *theme_format_compress_colors(THEME_REC *theme, const char *format)
                        if (IS_OLD_FORMAT(*format, last_fg, last_bg)) {
                                /* active color set again */
                        } else if (IS_FGCOLOR_FORMAT(*format) &&
-                                  (*format != 'n' || format[2] == 'n') &&
                                   format[1] == '%' &&
-                                  IS_FGCOLOR_FORMAT(format[2])) {
+                                  IS_FGCOLOR_FORMAT(format[2]) &&
+                                  (*format != 'n' || format[2] == 'n')) {
                                /* two fg colors in a row. bg colors are
                                   so rare that we don't bother checking
                                   them */
@@ -494,7 +610,7 @@ static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme)
 
                if (node->key != NULL && node->value != NULL) {
                        for (p = node->key; *p != '\0'; p++)
-                                theme->replace_keys[(int) *p] = index;
+                                theme->replace_keys[(int) (unsigned char) *p] = index;
 
                        theme->replace_values =
                                g_slist_append(theme->replace_values,
@@ -701,11 +817,11 @@ THEME_REC *theme_load(const char *setname)
        theme = theme_find(name);
 
        /* check home dir */
-       fname = g_strdup_printf("%s/.irssi/%s.theme", g_get_home_dir(), name);
+       fname = g_strdup_printf("%s/%s.theme", get_irssi_dir(), name);
        if (stat(fname, &statbuf) != 0) {
                /* check global config dir */
                g_free(fname);
-               fname = g_strdup_printf(SYSCONFDIR"/irssi/%s.theme", name);
+               fname = g_strdup_printf(THEMESDIR"/%s.theme", name);
                if (stat(fname, &statbuf) != 0) {
                        /* theme not found */
                        g_free(fname);
@@ -795,9 +911,13 @@ static int theme_read(THEME_REC *theme, const char *path, const char *data)
        }
 
        theme->default_color =
-               config_get_int(config, NULL, "default_color", 0);
-       theme->default_real_color =
-               config_get_int(config, NULL, "default_real_color", 7);
+               config_get_int(config, NULL, "default_color", -1);
+       theme->info_eol = config_get_bool(config, NULL, "info_eol", FALSE);
+
+       /* FIXME: remove after 0.7.99 */
+       if (theme->default_color == 0 &&
+           config_get_int(config, NULL, "default_real_color", -1) != -1)
+                theme->default_color = -1;
        theme_read_replaces(config, theme);
 
        if (data == NULL) {
@@ -952,8 +1072,13 @@ static void cmd_format(const char *data)
         cmd_params_free(free_arg);
 }
 
+typedef struct {
+       CONFIG_REC *config;
+        int save_all;
+} THEME_SAVE_REC;
+
 static void module_save(const char *module, MODULE_THEME_REC *rec,
-                        CONFIG_REC *config)
+                        THEME_SAVE_REC *data)
 {
        CONFIG_NODE *fnode, *node;
        FORMAT_REC *formats;
@@ -962,27 +1087,33 @@ static void module_save(const char *module, MODULE_THEME_REC *rec,
         formats = g_hash_table_lookup(default_formats, rec->name);
        if (formats == NULL) return;
 
-       fnode = config_node_traverse(config, "formats", TRUE);
+       fnode = config_node_traverse(data->config, "formats", TRUE);
 
        node = config_node_section(fnode, rec->name, NODE_TYPE_BLOCK);
-       for (n = 0; formats[n].def != NULL; n++) {
+       for (n = 1; formats[n].def != NULL; n++) {
                 if (rec->formats[n] != NULL) {
-                        config_node_set_str(config, node, formats[n].tag,
+                        config_node_set_str(data->config, node, formats[n].tag,
                                             rec->formats[n]);
-                }
+               } else if (data->save_all && formats[n].tag != NULL) {
+                        config_node_set_str(data->config, node, formats[n].tag,
+                                            formats[n].def);
+               }
         }
 
         if (node->value == NULL) {
                 /* not modified, don't keep the empty section */
-                config_node_remove(config, fnode, node);
-                if (fnode->value == NULL)
-                        config_node_remove(config, config->mainnode, fnode);
+                config_node_remove(data->config, fnode, node);
+               if (fnode->value == NULL) {
+                       config_node_remove(data->config,
+                                          data->config->mainnode, fnode);
+               }
         }
 }
 
-static void theme_save(THEME_REC *theme)
+static void theme_save(THEME_REC *theme, int save_all)
 {
        CONFIG_REC *config;
+       THEME_SAVE_REC data;
        char *path;
        int ok;
 
@@ -1002,10 +1133,12 @@ static void theme_save(THEME_REC *theme)
                 }
         }
 
-       g_hash_table_foreach(theme->modules, (GHFunc) module_save, config);
+       data.config = config;
+        data.save_all = save_all;
+       g_hash_table_foreach(theme->modules, (GHFunc) module_save, &data);
 
         /* always save the theme to ~/.irssi/ */
-       path = g_strdup_printf("%s/.irssi/%s", g_get_home_dir(),
+       path = g_strdup_printf("%s/%s", get_irssi_dir(),
                               g_basename(theme->path));
        ok = config_write(config, path, 0660) == 0;
 
@@ -1017,16 +1150,27 @@ static void theme_save(THEME_REC *theme)
        config_close(config);
 }
 
-/* save changed formats */
-static void cmd_save(void)
+/* save changed formats, -format saves all */
+static void cmd_save(const char *data)
 {
        GSList *tmp;
+        GHashTable *optlist;
+        void *free_arg;
+       char *fname;
+       int saveall;
 
+       if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+                           "save", &optlist, &fname))
+               return;
+
+        saveall = g_hash_table_lookup(optlist, "formats") != NULL;
        for (tmp = themes; tmp != NULL; tmp = tmp->next) {
                THEME_REC *theme = tmp->data;
 
-               theme_save(theme);
+               theme_save(theme, saveall);
        }
+
+       cmd_params_free(free_arg);
 }
 
 static void complete_format_list(THEME_SEARCH_REC *rec, const char *key, GList **list)
@@ -1083,6 +1227,8 @@ static void sig_complete_format(GList **list, WINDOW_REC *window,
 
        words = 0;
        do {
+                ptr++;
+
                words++;
                 ptr = strchr(ptr, ' ');
        } while (ptr != NULL);
@@ -1101,10 +1247,12 @@ static void change_theme(const char *name, int verbose)
        rec = theme_load(name);
        if (rec != NULL) {
                current_theme = rec;
+                signal_emit("theme changed", 1, rec);
+
                if (verbose) {
-                       printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
-                                          TXT_THEME_CHANGED,
-                                          rec->name, rec->path);
+                       printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+                                   TXT_THEME_CHANGED,
+                                   rec->name, rec->path);
                }
        } else if (verbose) {
                printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
@@ -1115,33 +1263,50 @@ static void change_theme(const char *name, int verbose)
 static void read_settings(void)
 {
        const char *theme;
+        int len;
 
        theme = settings_get_str("theme");
-       if (strcmp(current_theme->name, theme) != 0)
+       len = strlen(current_theme->name);
+       if (strcmp(current_theme->name, theme) != 0 &&
+           (strncmp(current_theme->name, theme, len) != 0 ||
+            strcmp(theme+len, ".theme") != 0))
                change_theme(theme, TRUE);
 }
 
 static void themes_read(void)
 {
+       GSList *refs;
        char *fname;
 
-       while (themes != NULL)
-               theme_destroy(themes->data);
+       /* increase every theme's refcount, and destroy them. this way if
+          we want to use the theme before it's reloaded we don't crash. */
+       refs = NULL;
+       while (themes != NULL) {
+               THEME_REC *theme = themes->data;
+
+               refs = g_slist_prepend(refs, theme);
+
+               theme->refcount++;
+               theme_destroy(theme);
+       }
 
        /* first there's default theme.. */
        current_theme = theme_load("default");
        if (current_theme == NULL) {
-               fname = g_strdup_printf("%s/.irssi/default.theme",
-                                       g_get_home_dir());
+               fname = g_strdup_printf("%s/default.theme", get_irssi_dir());
                current_theme = theme_create(fname, "default");
-               current_theme->default_color = 0;
-               current_theme->default_real_color = 7;
+               current_theme->default_color = -1;
                 theme_read(current_theme, NULL, default_theme);
                g_free(fname);
        }
 
         window_themes_update();
-        change_theme(settings_get_str("theme"), FALSE);
+       change_theme(settings_get_str("theme"), FALSE);
+
+       while (refs != NULL) {
+               theme_unref(refs->data);
+               refs = g_slist_remove(refs, refs->data);
+       }
 }
 
 void themes_init(void)
@@ -1165,6 +1330,7 @@ void themes_init(void)
        signal_add("setup reread", (SIGNAL_FUNC) themes_read);
 
        command_set_options("format", "delete reset");
+       command_set_options("save", "formats");
 }
 
 void themes_deinit(void)