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