Merge Irssi 0.8.16-rc1
[silc.git] / apps / irssi / src / fe-common / core / formats.c
1 /*
2  formats.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 "special-vars.h"
25 #include "settings.h"
26
27 #include "levels.h"
28 #include "servers.h"
29
30 #include "fe-windows.h"
31 #include "window-items.h"
32 #include "formats.h"
33 #include "themes.h"
34 #include "recode.h"
35 #include "utf8.h"
36
37 static const char *format_backs = "04261537";
38 static const char *format_fores = "kbgcrmyw";
39 static const char *format_boldfores = "KBGCRMYW";
40
41 static int signal_gui_print_text;
42 static int hide_text_style, hide_server_tags, hide_colors;
43
44 static int timestamp_level;
45 static int timestamp_timeout;
46
47 int format_find_tag(const char *module, const char *tag)
48 {
49         FORMAT_REC *formats;
50         int n;
51
52         formats = g_hash_table_lookup(default_formats, module);
53         if (formats == NULL)
54                 return -1;
55
56         for (n = 0; formats[n].def != NULL; n++) {
57                 if (formats[n].tag != NULL &&
58                     g_strcasecmp(formats[n].tag, tag) == 0)
59                         return n;
60         }
61
62         return -1;
63 }
64
65 static void format_expand_code(const char **format, GString *out, int *flags)
66 {
67         int set;
68
69         if (flags == NULL) {
70                 /* flags are being ignored - skip the code */
71                 while (**format != ']')
72                         (*format)++;
73                 return;
74         }
75
76         set = TRUE;
77         (*format)++;
78         while (**format != ']' && **format != '\0') {
79                 if (**format == '+')
80                         set = TRUE;
81                 else if (**format == '-')
82                         set = FALSE;
83                 else switch (**format) {
84                 case 's':
85                 case 'S':
86                         *flags |= !set ? PRINT_FLAG_UNSET_LINE_START :
87                                 **format == 's' ? PRINT_FLAG_SET_LINE_START :
88                                 PRINT_FLAG_SET_LINE_START_IRSSI;
89                         break;
90                 case 't':
91                         *flags |= set ? PRINT_FLAG_SET_TIMESTAMP :
92                                 PRINT_FLAG_UNSET_TIMESTAMP;
93                         break;
94                 case 'T':
95                         *flags |= set ? PRINT_FLAG_SET_SERVERTAG :
96                                 PRINT_FLAG_UNSET_SERVERTAG;
97                         break;
98                 }
99
100                 (*format)++;
101         }
102 }
103
104 int format_expand_styles(GString *out, const char **format, int *flags)
105 {
106         char *p, fmt;
107
108         fmt = **format;
109         switch (fmt) {
110         case '{':
111         case '}':
112         case '%':
113                 /* escaped char */
114                 g_string_append_c(out, fmt);
115                 break;
116         case 'U':
117                 /* Underline on/off */
118                 g_string_append_c(out, 4);
119                 g_string_append_c(out, FORMAT_STYLE_UNDERLINE);
120                 break;
121         case '9':
122         case '_':
123                 /* bold on/off */
124                 g_string_append_c(out, 4);
125                 g_string_append_c(out, FORMAT_STYLE_BOLD);
126                 break;
127         case '8':
128                 /* reverse */
129                 g_string_append_c(out, 4);
130                 g_string_append_c(out, FORMAT_STYLE_REVERSE);
131                 break;
132         case ':':
133                 /* Newline */
134                 g_string_append_c(out, '\n');
135                 break;
136         case '|':
137                 /* Indent here */
138                 g_string_append_c(out, 4);
139                 g_string_append_c(out, FORMAT_STYLE_INDENT);
140                 break;
141         case 'F':
142                 /* blink */
143                 g_string_append_c(out, 4);
144                 g_string_append_c(out, FORMAT_STYLE_BLINK);
145                 break;
146         case 'n':
147         case 'N':
148                 /* default color */
149                 g_string_append_c(out, 4);
150                 g_string_append_c(out, FORMAT_STYLE_DEFAULTS);
151                 break;
152         case '>':
153                 /* clear to end of line */
154                 g_string_append_c(out, 4);
155                 g_string_append_c(out, FORMAT_STYLE_CLRTOEOL);
156                 break;
157         case '#':
158                 g_string_append_c(out, 4);
159                 g_string_append_c(out, FORMAT_STYLE_MONOSPACE);
160                 break;
161         case '[':
162                 /* code */
163                 format_expand_code(format, out, flags);
164                 break;
165         default:
166                 /* check if it's a background color */
167                 p = strchr(format_backs, fmt);
168                 if (p != NULL) {
169                         g_string_append_c(out, 4);
170                         g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
171                         g_string_append_c(out, (char) ((int) (p-format_backs)+'0'));
172                         break;
173                 }
174
175                 /* check if it's a foreground color */
176                 if (fmt == 'p') fmt = 'm';
177                 p = strchr(format_fores, fmt);
178                 if (p != NULL) {
179                         g_string_append_c(out, 4);
180                         g_string_append_c(out, (char) ((int) (p-format_fores)+'0'));
181                         g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
182                         break;
183                 }
184
185                 /* check if it's a bold foreground color */
186                 if (fmt == 'P') fmt = 'M';
187                 p = strchr(format_boldfores, fmt);
188                 if (p != NULL) {
189                         g_string_append_c(out, 4);
190                         g_string_append_c(out, (char) (8+(int) (p-format_boldfores)+'0'));
191                         g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
192                         break;
193                 }
194
195                 return FALSE;
196         }
197
198         return TRUE;
199 }
200
201 void format_read_arglist(va_list va, FORMAT_REC *format,
202                          char **arglist, int arglist_size,
203                          char *buffer, int buffer_size)
204 {
205         int num, len, bufpos;
206
207         g_return_if_fail(format->params < arglist_size);
208
209         bufpos = 0;
210         arglist[format->params] = NULL;
211         for (num = 0; num < format->params; num++) {
212                 switch (format->paramtypes[num]) {
213                 case FORMAT_STRING:
214                         arglist[num] = (char *) va_arg(va, char *);
215                         if (arglist[num] == NULL)
216                                 arglist[num] = "";
217                         break;
218                 case FORMAT_INT: {
219                         int d = (int) va_arg(va, int);
220
221                         if (bufpos >= buffer_size) {
222                                 arglist[num] = "";
223                                 break;
224                         }
225
226                         arglist[num] = buffer+bufpos;
227                         len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
228                                          "%d", d);
229                         bufpos += len+1;
230                         break;
231                 }
232                 case FORMAT_LONG: {
233                         long l = (long) va_arg(va, long);
234
235                         if (bufpos >= buffer_size) {
236                                 arglist[num] = "";
237                                 break;
238                         }
239
240                         arglist[num] = buffer+bufpos;
241                         len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
242                                          "%ld", l);
243                         bufpos += len+1;
244                         break;
245                 }
246                 case FORMAT_FLOAT: {
247                         double f = (double) va_arg(va, double);
248
249                         if (bufpos >= buffer_size) {
250                                 arglist[num] = "";
251                                 break;
252                         }
253
254                         arglist[num] = buffer+bufpos;
255                         len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
256                                          "%0.2f", f);
257                         bufpos += len+1;
258                         break;
259                 }
260                 }
261         }
262 }
263 void format_create_dest(TEXT_DEST_REC *dest,
264                         void *server, const char *target,
265                         int level, WINDOW_REC *window)
266 {
267         format_create_dest_tag(dest, server, NULL, target, level, window);
268 }
269
270 void format_create_dest_tag(TEXT_DEST_REC *dest, void *server,
271                             const char *server_tag, const char *target,
272                             int level, WINDOW_REC *window)
273 {
274         memset(dest, 0, sizeof(TEXT_DEST_REC));
275
276         dest->server = server;
277         dest->server_tag = server != NULL ? SERVER(server)->tag : server_tag;
278         dest->target = target;
279         dest->level = level;
280         dest->window = window != NULL ? window :
281                 window_find_closest(server, target, level);
282 }
283
284 static int advance (char const **str, gboolean utf8)
285 {
286         if (utf8) {
287                 gunichar c;
288
289                 c = g_utf8_get_char(*str);
290                 *str = g_utf8_next_char(*str);
291
292                 return unichar_isprint(c) ? mk_wcwidth(c) : 1;
293         } else {
294                 *str += 1;
295
296                 return 1;
297         }
298 }
299
300 /* Return length of text part in string (ie. without % codes) */
301 int format_get_length(const char *str)
302 {
303         GString *tmp;
304         int len;
305         gboolean utf8;
306
307         g_return_val_if_fail(str != NULL, 0);
308
309         utf8 = is_utf8() && g_utf8_validate(str, -1, NULL);
310
311         tmp = g_string_new(NULL);
312         len = 0;
313         while (*str != '\0') {
314                 if (*str == '%' && str[1] != '\0') {
315                         str++;
316                         if (*str != '%' &&
317                             format_expand_styles(tmp, &str, NULL)) {
318                                 str++;
319                                 continue;
320                         }
321
322                         /* %% or unknown %code, written as-is */
323                         if (*str != '%')
324                                 len++;
325                 }
326
327                 len += advance(&str, utf8);
328         }
329
330         g_string_free(tmp, TRUE);
331         return len;
332 }
333
334 /* Return how many characters in `str' must be skipped before `len'
335    characters of text is skipped. Like strip_real_length(), except this
336    handles %codes. */
337 int format_real_length(const char *str, int len)
338 {
339         GString *tmp;
340         const char *start;
341         const char *oldstr;
342         gboolean utf8;
343
344         g_return_val_if_fail(str != NULL, 0);
345         g_return_val_if_fail(len >= 0, 0);
346
347         utf8 = is_utf8() && g_utf8_validate(str, -1, NULL);
348
349         start = str;
350         tmp = g_string_new(NULL);
351         while (*str != '\0' && len > 0) {
352                 if (*str == '%' && str[1] != '\0') {
353                         str++;
354                         if (*str != '%' &&
355                             format_expand_styles(tmp, &str, NULL)) {
356                                 str++;
357                                 continue;
358                         }
359
360                         /* %% or unknown %code, written as-is */
361                         if (*str != '%') {
362                                 if (--len == 0)
363                                         break;
364                         }
365                 }
366
367                 oldstr = str;
368                 len -= advance(&str, utf8);
369                 if (len < 0)
370                         str = oldstr;
371         }
372
373         g_string_free(tmp, TRUE);
374         return (int) (str-start);
375 }
376
377 char *format_string_expand(const char *text, int *flags)
378 {
379         GString *out;
380         char code, *ret;
381
382         g_return_val_if_fail(text != NULL, NULL);
383
384         out = g_string_new(NULL);
385
386         if (flags != NULL) *flags = 0;
387         code = 0;
388         while (*text != '\0') {
389                 if (code == '%') {
390                         /* color code */
391                         if (!format_expand_styles(out, &text, flags)) {
392                                 g_string_append_c(out, '%');
393                                 g_string_append_c(out, '%');
394                                 g_string_append_c(out, *text);
395                         }
396                         code = 0;
397                 } else {
398                         if (*text == '%')
399                                 code = *text;
400                         else
401                                 g_string_append_c(out, *text);
402                 }
403
404                 text++;
405         }
406
407         ret = out->str;
408         g_string_free(out, FALSE);
409         return ret;
410 }
411
412 static char *format_get_text_args(TEXT_DEST_REC *dest,
413                                   const char *text, char **arglist)
414 {
415         GString *out;
416         char code, *ret;
417         int need_free;
418
419         out = g_string_new(NULL);
420
421         code = 0;
422         while (*text != '\0') {
423                 if (code == '%') {
424                         /* color code */
425                         if (!format_expand_styles(out, &text, &dest->flags)) {
426                                 g_string_append_c(out, '%');
427                                 g_string_append_c(out, '%');
428                                 g_string_append_c(out, *text);
429                         }
430                         code = 0;
431                 } else if (code == '$') {
432                         /* argument */
433                         char *ret;
434
435                         ret = parse_special((char **) &text, dest->server,
436                                             dest->target == NULL ? NULL :
437                                             window_item_find(dest->server, dest->target),
438                                             arglist, &need_free, NULL, 0);
439
440                         if (ret != NULL) {
441                                 /* string shouldn't end with \003 or it could
442                                    mess up the next one or two characters */
443                                 int diff;
444                                 int len = strlen(ret);
445                                 while (len > 0 && ret[len-1] == 3) len--;
446                                 diff = strlen(ret)-len;
447
448                                 g_string_append(out, ret);
449                                 if (diff > 0)
450                                         g_string_truncate(out, out->len-diff);
451                                 if (need_free) g_free(ret);
452                         }
453                         code = 0;
454                 } else {
455                         if (*text == '%' || *text == '$')
456                                 code = *text;
457                         else
458                                 g_string_append_c(out, *text);
459                 }
460
461                 text++;
462         }
463
464         ret = out->str;
465         g_string_free(out, FALSE);
466         return ret;
467 }
468
469 char *format_get_text_theme(THEME_REC *theme, const char *module,
470                             TEXT_DEST_REC *dest, int formatnum, ...)
471 {
472         va_list va;
473         char *str;
474
475         if (theme == NULL)
476                 theme = window_get_theme(dest->window);
477
478         va_start(va, formatnum);
479         str = format_get_text_theme_args(theme, module, dest, formatnum, va);
480         va_end(va);
481
482         return str;
483 }
484
485 char *format_get_text_theme_args(THEME_REC *theme, const char *module,
486                                  TEXT_DEST_REC *dest, int formatnum,
487                                  va_list va)
488 {
489         char *arglist[MAX_FORMAT_PARAMS];
490         char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
491         FORMAT_REC *formats;
492
493         formats = g_hash_table_lookup(default_formats, module);
494         format_read_arglist(va, &formats[formatnum],
495                             arglist, sizeof(arglist)/sizeof(char *),
496                             buffer, sizeof(buffer));
497
498         return format_get_text_theme_charargs(theme, module, dest,
499                                               formatnum, arglist);
500 }
501
502 char *format_get_text_theme_charargs(THEME_REC *theme, const char *module,
503                                      TEXT_DEST_REC *dest, int formatnum,
504                                      char **args)
505 {
506         MODULE_THEME_REC *module_theme;
507         char *text;
508
509         module_theme = g_hash_table_lookup(theme->modules, module);
510         if (module_theme == NULL)
511                 return NULL;
512
513         text = module_theme->expanded_formats[formatnum];
514         return format_get_text_args(dest, text, args);
515 }
516
517 char *format_get_text(const char *module, WINDOW_REC *window,
518                       void *server, const char *target,
519                       int formatnum, ...)
520 {
521         TEXT_DEST_REC dest;
522         THEME_REC *theme;
523         va_list va;
524         char *str;
525
526         format_create_dest(&dest, server, target, 0, window);
527         theme = window_get_theme(dest.window);
528
529         va_start(va, formatnum);
530         str = format_get_text_theme_args(theme, module, &dest, formatnum, va);
531         va_end(va);
532
533         return str;
534 }
535
536 /* add `linestart' to start of each line in `text'. `text' may contain
537    multiple lines separated with \n. */
538 char *format_add_linestart(const char *text, const char *linestart)
539 {
540         GString *str;
541         char *ret;
542
543         if (linestart == NULL)
544                 return g_strdup(text);
545
546         if (strchr(text, '\n') == NULL)
547                 return g_strconcat(linestart, text, NULL);
548
549         str = g_string_new(linestart);
550         while (*text != '\0') {
551                 g_string_append_c(str, *text);
552                 if (*text == '\n')
553                         g_string_append(str, linestart);
554                 text++;
555         }
556
557         ret = str->str;
558         g_string_free(str, FALSE);
559         return ret;
560 }
561
562 char *format_add_lineend(const char *text, const char *linestart)
563 {
564         GString *str;
565         char *ret;
566
567         if (linestart == NULL)
568                 return g_strdup(text);
569
570         if (strchr(text, '\n') == NULL)
571                 return g_strconcat(text, linestart, NULL);
572
573         str = g_string_new(NULL);
574         while (*text != '\0') {
575                 if (*text == '\n')
576                         g_string_append(str, linestart);
577                 g_string_append_c(str, *text);
578                 text++;
579         }
580         g_string_append(str, linestart);
581
582         ret = str->str;
583         g_string_free(str, FALSE);
584         return ret;
585 }
586
587 #define LINE_START_IRSSI_LEVEL \
588         (MSGLEVEL_CLIENTERROR | MSGLEVEL_CLIENTNOTICE)
589
590 #define NOT_LINE_START_LEVEL \
591         (MSGLEVEL_NEVER | MSGLEVEL_LASTLOG | MSGLEVEL_CLIENTCRAP | \
592         MSGLEVEL_MSGS | MSGLEVEL_PUBLIC | MSGLEVEL_DCC | MSGLEVEL_DCCMSGS | \
593         MSGLEVEL_ACTIONS | MSGLEVEL_NOTICES | MSGLEVEL_SNOTES | MSGLEVEL_CTCPS)
594
595 /* return the "-!- " text at the start of the line */
596 char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
597 {
598         int format;
599
600         /* check for flags if we want to override defaults */
601         if (dest->flags & PRINT_FLAG_UNSET_LINE_START)
602                 return NULL;
603
604         if (dest->flags & PRINT_FLAG_SET_LINE_START)
605                 format = TXT_LINE_START;
606         else if (dest->flags & PRINT_FLAG_SET_LINE_START_IRSSI)
607                 format = TXT_LINE_START_IRSSI;
608         else {
609                 /* use defaults */
610                 if (dest->level & LINE_START_IRSSI_LEVEL)
611                         format = TXT_LINE_START_IRSSI;
612                 else if ((dest->level & NOT_LINE_START_LEVEL) == 0)
613                         format = TXT_LINE_START;
614                 else
615                         return NULL;
616         }
617
618         return format_get_text_theme(theme, MODULE_NAME, dest, format);
619 }
620
621 static char *get_timestamp(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
622 {
623         char *format, str[256];
624         struct tm *tm;
625         int diff;
626
627         if ((timestamp_level & dest->level) == 0)
628                 return NULL;
629
630         /* check for flags if we want to override defaults */
631         if (dest->flags & PRINT_FLAG_UNSET_TIMESTAMP)
632                 return NULL;
633
634         if ((dest->flags & PRINT_FLAG_SET_TIMESTAMP) == 0 &&
635             (dest->level & (MSGLEVEL_NEVER|MSGLEVEL_LASTLOG)) != 0)
636                 return NULL;
637
638
639         if (timestamp_timeout > 0) {
640                 diff = t - dest->window->last_timestamp;
641                 dest->window->last_timestamp = t;
642                 if (diff < timestamp_timeout)
643                         return NULL;
644         }
645
646         tm = localtime(&t);
647         format = format_get_text_theme(theme, MODULE_NAME, dest,
648                                        TXT_TIMESTAMP);
649         if (strftime(str, sizeof(str), format, tm) <= 0)
650                 str[0] = '\0';
651         g_free(format);
652         return g_strdup(str);
653 }
654
655 static char *get_server_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
656 {
657         int count = 0;
658
659         if (dest->server_tag == NULL || hide_server_tags)
660                 return NULL;
661
662         /* check for flags if we want to override defaults */
663         if (dest->flags & PRINT_FLAG_UNSET_SERVERTAG)
664                 return NULL;
665
666         if ((dest->flags & PRINT_FLAG_SET_SERVERTAG) == 0) {
667                 if (dest->window->active != NULL &&
668                     dest->window->active->server == dest->server)
669                         return NULL;
670
671                 if (servers != NULL) {
672                         count++;
673                         if (servers->next != NULL)
674                                 count++;
675                 }
676                 if (count < 2 && lookup_servers != NULL) {
677                         count++;
678                         if (lookup_servers->next != NULL)
679                                 count++;
680                 }
681
682                 if (count < 2)
683                         return NULL;
684         }
685
686         return format_get_text_theme(theme, MODULE_NAME, dest,
687                                      TXT_SERVERTAG, dest->server_tag);
688 }
689
690 char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
691 {
692         char *timestamp, *servertag;
693         char *linestart;
694
695         timestamp = get_timestamp(theme, dest, t);
696         servertag = get_server_tag(theme, dest);
697
698         if (timestamp == NULL && servertag == NULL)
699                 return NULL;
700
701         linestart = g_strconcat(timestamp != NULL ? timestamp : "",
702                                 servertag, NULL);
703
704         g_free_not_null(timestamp);
705         g_free_not_null(servertag);
706         return linestart;
707 }
708
709 void format_newline(WINDOW_REC *window)
710 {
711         g_return_if_fail(window != NULL);
712
713         signal_emit_id(signal_gui_print_text, 6, window,
714                        GINT_TO_POINTER(-1), GINT_TO_POINTER(-1),
715                        GINT_TO_POINTER(GUI_PRINT_FLAG_NEWLINE),
716                        "", NULL);
717 }
718
719 /* parse ANSI color string */
720 static const char *get_ansi_color(THEME_REC *theme, const char *str,
721                                   int *fg_ret, int *bg_ret, int *flags_ret)
722 {
723         static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
724         const char *start;
725         int fg, bg, flags, num;
726
727         if (*str != '[')
728                 return str;
729         start = str++;
730
731         fg = fg_ret == NULL || *fg_ret < 0 ? theme->default_color : *fg_ret;
732         bg = bg_ret == NULL || *bg_ret < 0 ? -1 : *bg_ret;
733         flags = flags_ret == NULL ? 0 : *flags_ret;
734
735         num = 0;
736         for (;; str++) {
737                 if (*str == '\0') return start;
738
739                 if (i_isdigit(*str)) {
740                         num = num*10 + (*str-'0');
741                         continue;
742                 }
743
744                 if (*str != ';' && *str != 'm')
745                         return start;
746
747                 switch (num) {
748                 case 0:
749                         /* reset colors back to default */
750                         fg = theme->default_color;
751                         bg = -1;
752                         flags &= ~GUI_PRINT_FLAG_INDENT;
753                         break;
754                 case 1:
755                         /* hilight */
756                         flags |= GUI_PRINT_FLAG_BOLD;
757                         break;
758                 case 5:
759                         /* blink */
760                         flags |= GUI_PRINT_FLAG_BLINK;
761                         break;
762                 case 7:
763                         /* reverse */
764                         flags |= GUI_PRINT_FLAG_REVERSE;
765                         break;
766                 default:
767                         if (num >= 30 && num <= 37) {
768                                 if (fg == -1) fg = 0;
769                                 fg = (fg & 0xf8) | ansitab[num-30];
770                         }
771                         if (num >= 40 && num <= 47) {
772                                 if (bg == -1) bg = 0;
773                                 bg = (bg & 0xf8) | ansitab[num-40];
774                         }
775                         break;
776                 }
777                 num = 0;
778
779                 if (*str == 'm') {
780                         if (fg_ret != NULL) *fg_ret = fg;
781                         if (bg_ret != NULL) *bg_ret = bg;
782                         if (flags_ret != NULL) *flags_ret = flags;
783
784                         str++;
785                         break;
786                 }
787         }
788
789         return str;
790 }
791
792 /* parse MIRC color string */
793 static void get_mirc_color(const char **str, int *fg_ret, int *bg_ret)
794 {
795         int fg, bg;
796
797         fg = fg_ret == NULL ? -1 : *fg_ret;
798         bg = bg_ret == NULL ? -1 : *bg_ret;
799
800         if (!i_isdigit(**str) && **str != ',') {
801                 fg = -1;
802                 bg = -1;
803         } else {
804                 /* foreground color */
805                 if (**str != ',') {
806                         fg = **str-'0';
807                         (*str)++;
808                         if (i_isdigit(**str)) {
809                                 fg = fg*10 + (**str-'0');
810                                 (*str)++;
811                         }
812                 }
813                 if (**str == ',') {
814                         /* background color */
815                         if (!i_isdigit((*str)[1]))
816                                 bg = -1;
817                         else {
818                                 (*str)++;
819                                 bg = **str-'0';
820                                 (*str)++;
821                                 if (i_isdigit(**str)) {
822                                         bg = bg*10 + (**str-'0');
823                                         (*str)++;
824                                 }
825                         }
826                 }
827         }
828
829         if (fg_ret) *fg_ret = fg;
830         if (bg_ret) *bg_ret = bg;
831 }
832
833 #define IS_COLOR_CODE(c) \
834         ((c) == 2 || (c) == 3 || (c) == 4 || (c) == 6 || (c) == 7 || \
835         (c) == 15 || (c) == 22 || (c) == 27 || (c) == 31)
836
837 /* Return how many characters in `str' must be skipped before `len'
838    characters of text is skipped. */
839 int strip_real_length(const char *str, int len,
840                       int *last_color_pos, int *last_color_len)
841 {
842         const char *start = str;
843
844         if (last_color_pos != NULL)
845                 *last_color_pos = -1;
846         if (last_color_len != NULL)
847                 *last_color_len = -1;
848
849         while (*str != '\0') {
850                 if (*str == 3) {
851                         const char *mircstart = str;
852
853                         if (last_color_pos != NULL)
854                                 *last_color_pos = (int) (str-start);
855                         str++;
856                         get_mirc_color(&str, NULL, NULL);
857                         if (last_color_len != NULL)
858                                 *last_color_len = (int) (str-mircstart);
859
860                 } else if (*str == 4 && str[1] != '\0') {
861                         if (str[1] < FORMAT_STYLE_SPECIAL && str[2] != '\0') {
862                                 if (last_color_pos != NULL)
863                                         *last_color_pos = (int) (str-start);
864                                 if (last_color_len != NULL)
865                                         *last_color_len = 3;
866                                 str++;
867                         } else if (str[1] == FORMAT_STYLE_DEFAULTS) {
868                                 if (last_color_pos != NULL)
869                                         *last_color_pos = (int) (str-start);
870                                 if (last_color_len != NULL)
871                                         *last_color_len = 2;
872                         }
873                         str += 2;
874                 } else {
875                         if (!IS_COLOR_CODE(*str)) {
876                                 if (len-- == 0)
877                                         break;
878                         }
879                         str++;
880                 }
881         }
882
883         return (int) (str-start);
884 }
885
886 char *strip_codes(const char *input)
887 {
888         const char *p;
889         char *str, *out;
890
891         out = str = g_strdup(input);
892         for (p = input; *p != '\0'; p++) {
893                 if (*p == 3) {
894                         p++;
895
896                         /* mirc color */
897                         get_mirc_color(&p, NULL, NULL);
898                         p--;
899                         continue;
900                 }
901
902                 if (*p == 4 && p[1] != '\0') {
903                         if (p[1] >= FORMAT_STYLE_SPECIAL) {
904                                 p++;
905                                 continue;
906                         }
907
908                         /* irssi color */
909                         if (p[2] != '\0') {
910                                 p += 2;
911                                 continue;
912                         }
913                 }
914
915                 if (*p == 27 && p[1] != '\0') {
916                         p++;
917                         p = get_ansi_color(current_theme, p, NULL, NULL, NULL);
918                         p--;
919                 } else if (!IS_COLOR_CODE(*p))
920                         *out++ = *p;
921         }
922
923         *out = '\0';
924         return str;
925 }
926
927 /* send a fully parsed text string for GUI to print */
928 void format_send_to_gui(TEXT_DEST_REC *dest, const char *text)
929 {
930         THEME_REC *theme;
931         char *dup, *str, *ptr, type;
932         int fgcolor, bgcolor;
933         int flags;
934
935         theme = window_get_theme(dest->window);
936
937         dup = str = g_strdup(text);
938
939         flags = 0; fgcolor = theme->default_color; bgcolor = -1;
940         while (*str != '\0') {
941                 type = '\0';
942                 for (ptr = str; *ptr != '\0'; ptr++) {
943                         if (IS_COLOR_CODE(*ptr) || *ptr == '\n') {
944                                 type = *ptr;
945                                 *ptr++ = '\0';
946                                 break;
947                         }
948                 }
949
950                 if (type == 7) {
951                         /* bell */
952                         if (settings_get_bool("bell_beeps"))
953                                 signal_emit("beep", 0);
954                 } else if (type == 4 && *ptr == FORMAT_STYLE_CLRTOEOL) {
955                         /* clear to end of line */
956                         flags |= GUI_PRINT_FLAG_CLRTOEOL;
957                 }
958
959                 if (*str != '\0' || (flags & GUI_PRINT_FLAG_CLRTOEOL)) {
960                         /* send the text to gui handler */
961                         signal_emit_id(signal_gui_print_text, 6, dest->window,
962                                        GINT_TO_POINTER(fgcolor),
963                                        GINT_TO_POINTER(bgcolor),
964                                        GINT_TO_POINTER(flags), str,
965                                        dest);
966                         flags &= ~(GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_CLRTOEOL);
967                 }
968
969                 if (type == '\n') {
970                         format_newline(dest->window);
971                         fgcolor = theme->default_color;
972                         bgcolor = -1;
973                         flags &= GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_MONOSPACE;
974                 }
975
976                 if (*ptr == '\0')
977                         break;
978
979                 switch (type)
980                 {
981                 case 2:
982                         /* bold */
983                         if (!hide_text_style)
984                                 flags ^= GUI_PRINT_FLAG_BOLD;
985                         break;
986                 case 3:
987                         /* MIRC color */
988                         get_mirc_color((const char **) &ptr,
989                                         hide_colors ? NULL : &fgcolor,
990                                         hide_colors ? NULL : &bgcolor);
991                         if (!hide_colors)
992                                 flags |= GUI_PRINT_FLAG_MIRC_COLOR;
993                         break;
994                 case 4:
995                         /* user specific colors */
996                         flags &= ~GUI_PRINT_FLAG_MIRC_COLOR;
997                         switch (*ptr) {
998                         case FORMAT_STYLE_BLINK:
999                                 flags ^= GUI_PRINT_FLAG_BLINK;
1000                                 break;
1001                         case FORMAT_STYLE_UNDERLINE:
1002                                 flags ^= GUI_PRINT_FLAG_UNDERLINE;
1003                                 break;
1004                         case FORMAT_STYLE_BOLD:
1005                                 flags ^= GUI_PRINT_FLAG_BOLD;
1006                                 break;
1007                         case FORMAT_STYLE_REVERSE:
1008                                 flags ^= GUI_PRINT_FLAG_REVERSE;
1009                                 break;
1010                         case FORMAT_STYLE_MONOSPACE:
1011                                 flags ^= GUI_PRINT_FLAG_MONOSPACE;
1012                                 break;
1013                         case FORMAT_STYLE_INDENT:
1014                                 flags |= GUI_PRINT_FLAG_INDENT;
1015                                 break;
1016                         case FORMAT_STYLE_DEFAULTS:
1017                                 fgcolor = theme->default_color;
1018                                 bgcolor = -1;
1019                                 flags &= GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_MONOSPACE;
1020                                 break;
1021                         case FORMAT_STYLE_CLRTOEOL:
1022                                 break;
1023                         default:
1024                                 if (*ptr != FORMAT_COLOR_NOCHANGE) {
1025                                         fgcolor = (unsigned char) *ptr-'0';
1026                                 }
1027                                 if (ptr[1] == '\0')
1028                                         break;
1029
1030                                 ptr++;
1031                                 if (*ptr != FORMAT_COLOR_NOCHANGE) {
1032                                         bgcolor = *ptr-'0';
1033                                 }
1034                         }
1035                         ptr++;
1036                         break;
1037                 case 6:
1038                         /* blink */
1039                         if (!hide_text_style)
1040                                 flags ^= GUI_PRINT_FLAG_BLINK;
1041                         break;
1042                 case 15:
1043                         /* remove all styling */
1044                         fgcolor = theme->default_color;
1045                         bgcolor = -1;
1046                         flags &= GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_MONOSPACE;
1047                         break;
1048                 case 22:
1049                         /* reverse */
1050                         if (!hide_text_style)
1051                                 flags ^= GUI_PRINT_FLAG_REVERSE;
1052                         break;
1053                 case 31:
1054                         /* underline */
1055                         if (!hide_text_style)
1056                                 flags ^= GUI_PRINT_FLAG_UNDERLINE;
1057                         break;
1058                 case 27:
1059                         /* ansi color code */
1060                         ptr = (char *)
1061                                 get_ansi_color(theme, ptr,
1062                                                hide_colors ? NULL : &fgcolor,
1063                                                hide_colors ? NULL : &bgcolor,
1064                                                hide_colors ? NULL : &flags);
1065                         break;
1066                 }
1067
1068                 str = ptr;
1069         }
1070
1071         g_free(dup);
1072 }
1073
1074 static void read_settings(void)
1075 {
1076         timestamp_level = settings_get_bool("timestamps") ? MSGLEVEL_ALL : 0;
1077         if (timestamp_level > 0)
1078                 timestamp_level = settings_get_level("timestamp_level");
1079         timestamp_timeout = settings_get_time("timestamp_timeout")/1000;
1080
1081         hide_server_tags = settings_get_bool("hide_server_tags");
1082         hide_text_style = settings_get_bool("hide_text_style");
1083         hide_colors = hide_text_style || settings_get_bool("hide_colors");
1084 }
1085
1086 void formats_init(void)
1087 {
1088         signal_gui_print_text = signal_get_uniq_id("gui print text");
1089
1090         read_settings();
1091         signal_add("setup changed", (SIGNAL_FUNC) read_settings);
1092 }
1093
1094 void formats_deinit(void)
1095 {
1096         signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
1097 }