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