Merged Irssi 0.8.2 from irssi.org cvs.
[silc.git] / apps / irssi / src / fe-text / statusbar.c
1 /*
2  statusbar.c : irssi
3
4     Copyright (C) 1999-2001 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 "signals.h"
23 #include "expandos.h"
24 #include "special-vars.h"
25
26 #include "themes.h"
27
28 #include "statusbar.h"
29 #include "statusbar-config.h"
30 #include "gui-windows.h"
31 #include "gui-printtext.h"
32
33 void statusbar_items_init(void);
34 void statusbar_items_deinit(void);
35
36 GSList *statusbar_groups;
37 STATUSBAR_GROUP_REC *active_statusbar_group;
38
39 /*
40    sbar_item_defs: char *name => char *value
41    sbar_item_funcs: char *name => STATUSBAR_FUNC func
42    sbar_signal_items: int signal_id => GSList *(SBAR_ITEM_REC *items)
43    sbar_item_signals: SBAR_ITEM_REC *item => GSList *(int *signal_ids)
44    named_sbar_items: const char *name => GSList *(SBAR_ITEM_REC *items)
45 */
46 static GHashTable *sbar_item_defs, *sbar_item_funcs;
47 static GHashTable *sbar_signal_items, *sbar_item_signals;
48 static GHashTable *named_sbar_items;
49 static int statusbar_need_recreate_items;
50
51 void statusbar_item_register(const char *name, const char *value,
52                              STATUSBAR_FUNC func)
53 {
54         gpointer hkey, hvalue;
55
56         statusbar_need_recreate_items = TRUE;
57         if (value != NULL) {
58                 if (g_hash_table_lookup_extended(sbar_item_defs,
59                                                  name, &hkey, &hvalue)) {
60                         g_hash_table_remove(sbar_item_defs, name);
61                         g_free(hkey);
62                         g_free(hvalue);
63                 }
64                 g_hash_table_insert(sbar_item_defs,
65                                     g_strdup(name), g_strdup(value));
66         }
67
68         if (func != NULL) {
69                 if (g_hash_table_lookup(sbar_item_funcs, name) == NULL) {
70                         g_hash_table_insert(sbar_item_funcs,
71                                             g_strdup(name), (void *) func);
72                 }
73         }
74 }
75
76 void statusbar_item_unregister(const char *name)
77 {
78         gpointer key, value;
79
80         statusbar_need_recreate_items = TRUE;
81         if (g_hash_table_lookup_extended(sbar_item_defs,
82                                          name, &key, &value)) {
83                 g_hash_table_remove(sbar_item_defs, key);
84                 g_free(key);
85                 g_free(value);
86         }
87
88         if (g_hash_table_lookup_extended(sbar_item_funcs,
89                                          name, &key, &value)) {
90                 g_hash_table_remove(sbar_item_funcs, key);
91                 g_free(key);
92         }
93 }
94
95 STATUSBAR_GROUP_REC *statusbar_group_create(const char *name)
96 {
97         STATUSBAR_GROUP_REC *rec;
98
99         rec = g_new0(STATUSBAR_GROUP_REC, 1);
100         rec->name = g_strdup(name);
101
102         statusbar_groups = g_slist_append(statusbar_groups, rec);
103         return rec;
104 }
105
106 void statusbar_group_destroy(STATUSBAR_GROUP_REC *rec)
107 {
108         statusbar_groups = g_slist_remove(statusbar_groups, rec);
109
110         while (rec->bars != NULL)
111                 statusbar_destroy(rec->bars->data);
112         while (rec->config_bars != NULL)
113                 statusbar_config_destroy(rec, rec->config_bars->data);
114
115         g_free(rec->name);
116         g_free(rec);
117 }
118
119 STATUSBAR_GROUP_REC *statusbar_group_find(const char *name)
120 {
121         GSList *tmp;
122
123         for (tmp = statusbar_groups; tmp != NULL; tmp = tmp->next) {
124                 STATUSBAR_GROUP_REC *rec = tmp->data;
125
126                 if (strcmp(rec->name, name) == 0)
127                         return rec;
128         }
129
130         return NULL;
131 }
132
133 static int sbar_item_cmp(SBAR_ITEM_REC *item1, SBAR_ITEM_REC *item2)
134 {
135         return item1->config->priority == item2->config->priority ? 0 :
136                 item1->config->priority < item2->config->priority ? -1 : 1;
137 }
138
139 static int sbar_cmp_position(STATUSBAR_REC *bar1, STATUSBAR_REC *bar2)
140 {
141         return bar1->config->position < bar2->config->position ? -1 : 1;
142 }
143
144 /* Shink all items in statusbar to their minimum requested size.
145    The items list should be sorted by priority, highest first. */
146 static int statusbar_shrink_to_min(GSList *items, int size, int max_width)
147 {
148         GSList *tmp;
149
150         for (tmp = items; tmp != NULL; tmp = tmp->next) {
151                 SBAR_ITEM_REC *rec = tmp->data;
152
153                 size -= (rec->max_size-rec->min_size);
154                 rec->size = rec->min_size;
155
156                 if (size <= max_width) {
157                         rec->size += max_width-size;
158                         break;
159                 }
160
161                 if (rec->size == 0) {
162                         /* min_size was 0, item removed.
163                            remove the marginal too */
164                         size--;
165                 }
166         }
167
168         return size;
169 }
170
171 /* shink the items in statusbar, even if their size gets smaller than
172    their minimum requested size. The items list should be sorted by
173    priority, highest first. */
174 static void statusbar_shrink_forced(GSList *items, int size, int max_width)
175 {
176         GSList *tmp;
177
178         for (tmp = items; tmp != NULL; tmp = tmp->next) {
179                 SBAR_ITEM_REC *rec = tmp->data;
180
181                 if (size-rec->size > max_width) {
182                         /* remove the whole item */
183                         size -= rec->size;
184                         rec->size = 0;
185                 } else {
186                         /* shrink the item */
187                         rec->size -= size-max_width;
188                         break;
189                 }
190         }
191 }
192
193 static void statusbar_resize_items(STATUSBAR_REC *bar, int max_width)
194 {
195         GSList *tmp, *prior_sorted;
196         int width;
197
198         /* first give items their max. size */
199         prior_sorted = NULL;
200         width = 0;
201         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
202                 SBAR_ITEM_REC *rec = tmp->data;
203
204                 rec->func(rec, TRUE);
205                 rec->size = rec->max_size;
206
207                 if (rec->size > 0) {
208                         width += rec->max_size;
209
210                         prior_sorted = g_slist_insert_sorted(prior_sorted, rec,
211                                                              (GCompareFunc)
212                                                              sbar_item_cmp);
213                 }
214         }
215
216         if (width > max_width) {
217                 /* too big, start shrinking from items with lowest priority
218                    and shrink until everything fits or until we've shrinked
219                    all items. */
220                 width = statusbar_shrink_to_min(prior_sorted, width,
221                                                 max_width);
222                 if (width > max_width) {
223                         /* still need to shrink, remove the items with lowest
224                            priority until everything fits to screen */
225                         statusbar_shrink_forced(prior_sorted, width,
226                                                 max_width);
227                 }
228         }
229
230         g_slist_free(prior_sorted);
231 }
232
233 #define SBAR_ITEM_REDRAW_NEEDED(_bar, _item, _xpos) \
234         (((_bar)->dirty_xpos != -1 && (_xpos) >= (_bar)->dirty_xpos) || \
235          (_item)->xpos != (_xpos) || (_item)->current_size != (_item)->size)
236
237 static void statusbar_calc_item_positions(STATUSBAR_REC *bar)
238 {
239         WINDOW_REC *old_active_win;
240         GSList *tmp, *right_items;
241         int xpos, rxpos;
242
243         old_active_win = active_win;
244         if (bar->parent_window != NULL)
245                 active_win = bar->parent_window->active;
246
247         statusbar_resize_items(bar, term_width);
248
249         /* left-aligned items */
250         xpos = 0;
251         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
252                 SBAR_ITEM_REC *rec = tmp->data;
253
254                 if (!rec->config->right_alignment &&
255                     (rec->size > 0 || rec->current_size > 0)) {
256                         if (SBAR_ITEM_REDRAW_NEEDED(bar, rec, xpos)) {
257                                 /* redraw the item */
258                                 rec->dirty = TRUE;
259                                 if (bar->dirty_xpos == -1 ||
260                                     xpos < bar->dirty_xpos) {
261                                         irssi_set_dirty();
262                                         bar->dirty = TRUE;
263                                         bar->dirty_xpos = xpos;
264                                 }
265
266                                 rec->xpos = xpos;
267                         }
268                         xpos += rec->size;
269                 }
270         }
271
272         /* right-aligned items - first copy them to a new list backwards,
273            easier to draw them in right order */
274         right_items = NULL;
275         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
276                 SBAR_ITEM_REC *rec = tmp->data;
277
278                 if (rec->config->right_alignment) {
279                         if (rec->size > 0)
280                                 right_items = g_slist_prepend(right_items, rec);
281                         else if (rec->current_size > 0 &&
282                                  (bar->dirty_xpos == -1 ||
283                                   rec->xpos < bar->dirty_xpos)) {
284                                 /* item was hidden - set the dirty position
285                                    to begin from the item's old xpos */
286                                 irssi_set_dirty();
287                                 bar->dirty = TRUE;
288                                 bar->dirty_xpos = rec->xpos;
289                         }
290                 }
291         }
292
293         rxpos = term_width;
294         for (tmp = right_items; tmp != NULL; tmp = tmp->next) {
295                 SBAR_ITEM_REC *rec = tmp->data;
296
297                 rxpos -= rec->size;
298                 if (SBAR_ITEM_REDRAW_NEEDED(bar, rec, rxpos)) {
299                         rec->dirty = TRUE;
300                         if (bar->dirty_xpos == -1 ||
301                             rxpos < bar->dirty_xpos) {
302                                 irssi_set_dirty();
303                                 bar->dirty = TRUE;
304                                 bar->dirty_xpos = rxpos;
305                         }
306                         rec->xpos = rxpos;
307                 }
308         }
309         g_slist_free(right_items);
310
311         active_win = old_active_win;
312 }
313
314 void statusbar_redraw(STATUSBAR_REC *bar, int force)
315 {
316         if (statusbar_need_recreate_items)
317                 return; /* don't bother yet */
318
319         if (bar != NULL) {
320                 if (force) {
321                         irssi_set_dirty();
322                         bar->dirty = TRUE;
323                         bar->dirty_xpos = 0;
324                 }
325                 statusbar_calc_item_positions(bar);
326         } else if (active_statusbar_group != NULL) {
327                 g_slist_foreach(active_statusbar_group->bars,
328                                 (GFunc) statusbar_redraw,
329                                 GINT_TO_POINTER(force));
330         }
331 }
332
333 void statusbar_item_redraw(SBAR_ITEM_REC *item)
334 {
335         WINDOW_REC *old_active_win;
336
337         g_return_if_fail(item != NULL);
338
339         old_active_win = active_win;
340         if (item->bar->parent_window != NULL)
341                 active_win = item->bar->parent_window->active;
342
343         item->func(item, TRUE);
344
345         item->dirty = TRUE;
346         item->bar->dirty = TRUE;
347         irssi_set_dirty();
348
349         if (item->max_size != item->size) {
350                 /* item wants a new size - we'll need to redraw
351                    the statusbar to see if this is allowed */
352                 statusbar_redraw(item->bar, FALSE);
353         }
354
355         active_win = old_active_win;
356 }
357
358 void statusbar_items_redraw(const char *name)
359 {
360         g_slist_foreach(g_hash_table_lookup(named_sbar_items, name),
361                         (GFunc) statusbar_item_redraw, NULL);
362 }
363
364 static void statusbars_recalc_ypos(STATUSBAR_REC *bar)
365 {
366         GSList *tmp, *bar_group;
367         int ypos;
368
369         /* get list of statusbars with same type and placement,
370            sorted by position */
371         bar_group = NULL;
372         tmp = bar->config->type == STATUSBAR_TYPE_ROOT ? bar->group->bars :
373                 bar->parent_window->statusbars;
374
375         for (; tmp != NULL; tmp = tmp->next) {
376                 STATUSBAR_REC *rec = tmp->data;
377
378                 if (rec->config->type == bar->config->type &&
379                     rec->config->placement == bar->config->placement) {
380                         bar_group = g_slist_insert_sorted(bar_group, rec,
381                                                           (GCompareFunc)
382                                                           sbar_cmp_position);
383                 }
384         }
385
386         if (bar_group == NULL) {
387                 /* we just destroyed the last statusbar in this
388                    type/placement group */
389                 return;
390         }
391
392         /* get the Y-position for the first statusbar */
393         if (bar->config->type == STATUSBAR_TYPE_ROOT) {
394                 ypos = bar->config->placement == STATUSBAR_TOP ? 0 :
395                         term_height - g_slist_length(bar_group);
396         } else {
397                 ypos = bar->config->placement == STATUSBAR_TOP ?
398                         bar->parent_window->first_line :
399                         bar->parent_window->last_line -
400                         (g_slist_length(bar_group)-1);
401         }
402
403         /* set the Y-positions */
404         while (bar_group != NULL) {
405                 bar = bar_group->data;
406
407                 if (bar->real_ypos != ypos) {
408                         bar->real_ypos = ypos;
409                         statusbar_redraw(bar, TRUE);
410                 }
411
412                 ypos++;
413                 bar_group = g_slist_remove(bar_group, bar_group->data);
414         }
415 }
416
417 static void sig_terminal_resized(void)
418 {
419         GSList *tmp;
420
421         for (tmp = active_statusbar_group->bars; tmp != NULL; tmp = tmp->next) {
422                 STATUSBAR_REC *bar = tmp->data;
423
424                 if (bar->config->type == STATUSBAR_TYPE_ROOT &&
425                     bar->config->placement == STATUSBAR_BOTTOM) {
426                         statusbars_recalc_ypos(bar);
427                         break;
428                 }
429         }
430 }
431
432 static void mainwindow_recalc_ypos(MAIN_WINDOW_REC *window, int placement)
433 {
434         GSList *tmp;
435
436         for (tmp = window->statusbars; tmp != NULL; tmp = tmp->next) {
437                 STATUSBAR_REC *bar = tmp->data;
438
439                 if (bar->config->placement == placement) {
440                         statusbars_recalc_ypos(bar);
441                         break;
442                 }
443         }
444 }
445
446 static void sig_mainwindow_resized(MAIN_WINDOW_REC *window)
447 {
448         mainwindow_recalc_ypos(window, STATUSBAR_TOP);
449         mainwindow_recalc_ypos(window, STATUSBAR_BOTTOM);
450 }
451
452 STATUSBAR_REC *statusbar_create(STATUSBAR_GROUP_REC *group,
453                                 STATUSBAR_CONFIG_REC *config,
454                                 MAIN_WINDOW_REC *parent_window)
455 {
456         STATUSBAR_REC *bar;
457         THEME_REC *theme;
458         GSList *tmp;
459         char *name, *value;
460
461         g_return_val_if_fail(group != NULL, NULL);
462         g_return_val_if_fail(config != NULL, NULL);
463         g_return_val_if_fail(config->type != STATUSBAR_TYPE_WINDOW ||
464                              parent_window != NULL, NULL);
465
466         bar = g_new0(STATUSBAR_REC, 1);
467         group->bars = g_slist_append(group->bars, bar);
468
469         bar->group = group;
470
471         bar->config = config;
472         bar->parent_window = parent_window;
473
474         irssi_set_dirty();
475         bar->dirty = TRUE;
476         bar->dirty_xpos = 0;
477
478         signal_remove("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
479         signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
480         signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
481
482         if (config->type == STATUSBAR_TYPE_ROOT) {
483                 /* top/bottom of the screen */
484                 mainwindows_reserve_lines(config->placement == STATUSBAR_TOP,
485                                           config->placement == STATUSBAR_BOTTOM);
486                 theme = current_theme;
487         } else {
488                 /* top/bottom of the window */
489                 parent_window->statusbars =
490                         g_slist_append(parent_window->statusbars, bar);
491                 mainwindow_set_statusbar_lines(parent_window,
492                                                config->placement == STATUSBAR_TOP,
493                                                config->placement == STATUSBAR_BOTTOM);
494                 theme = parent_window != NULL && parent_window->active != NULL &&
495                         parent_window->active->theme != NULL ?
496                         parent_window->active->theme : current_theme;
497         }
498
499         signal_add("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
500         signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
501         signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
502
503         /* get background color from sb_background abstract */
504         name = g_strdup_printf("{sb_%s_bg}", config->name);
505         value = theme_format_expand(theme, name);
506         g_free(name);
507
508         if (*value == '\0') {
509                 /* try with the statusbar group name */
510                 g_free(value);
511
512                 name = g_strdup_printf("{sb_%s_bg}", group->name);
513                 value = theme_format_expand(theme, name);
514                 g_free(name);
515
516                 if (*value == '\0') {
517                         /* fallback to default statusbar background
518                            (also provides backwards compatibility..) */
519                         g_free(value);
520                         value = theme_format_expand(theme, "{sb_background}");
521                 }
522         }
523
524         if (*value == '\0') {
525                 g_free(value);
526                 value = g_strdup("%8");
527         }
528         bar->color = g_strconcat("%n", value, NULL);
529         g_free(value);
530
531         statusbars_recalc_ypos(bar);
532         signal_emit("statusbar created", 1, bar);
533
534         /* create the items to statusbar */
535         for (tmp = config->items; tmp != NULL; tmp = tmp->next) {
536                 SBAR_ITEM_CONFIG_REC *rec = tmp->data;
537
538                 statusbar_item_create(bar, rec);
539         }
540         return bar;
541 }
542
543 void statusbar_destroy(STATUSBAR_REC *bar)
544 {
545         int top;
546
547         g_return_if_fail(bar != NULL);
548
549         bar->group->bars = g_slist_remove(bar->group->bars, bar);
550         if (bar->parent_window != NULL) {
551                 bar->parent_window->statusbars =
552                         g_slist_remove(bar->parent_window->statusbars, bar);
553         }
554
555         signal_emit("statusbar destroyed", 1, bar);
556
557         while (bar->items != NULL)
558                 statusbar_item_destroy(bar->items->data);
559
560         g_free(bar->color);
561
562         if (bar->config->type != STATUSBAR_TYPE_WINDOW ||
563             bar->parent_window != NULL)
564                 statusbars_recalc_ypos(bar);
565
566         top = bar->config->placement == STATUSBAR_TOP;
567         if (bar->config->type == STATUSBAR_TYPE_ROOT) {
568                 /* top/bottom of the screen */
569                 mainwindows_reserve_lines(top ? -1 : 0, !top ? -1 : 0);
570         } else if (bar->parent_window != NULL) {
571                 /* top/bottom of the window */
572                 mainwindow_set_statusbar_lines(bar->parent_window,
573                                                top ? -1 : 0, !top ? -1 : 0);
574         }
575
576         g_free(bar);
577 }
578
579 void statusbar_recreate_items(STATUSBAR_REC *bar)
580 {
581         GSList *tmp;
582
583         /* destroy */
584         while (bar->items != NULL)
585                 statusbar_item_destroy(bar->items->data);
586
587         /* create */
588         for (tmp = bar->config->items; tmp != NULL; tmp = tmp->next) {
589                 SBAR_ITEM_CONFIG_REC *rec = tmp->data;
590
591                 statusbar_item_create(bar, rec);
592         }
593
594         statusbar_redraw(bar, TRUE);
595 }
596
597 void statusbars_recreate_items(void)
598 {
599         if (active_statusbar_group != NULL) {
600                 g_slist_foreach(active_statusbar_group->bars,
601                                 (GFunc) statusbar_recreate_items, NULL);
602         }
603 }
604
605 STATUSBAR_REC *statusbar_find(STATUSBAR_GROUP_REC *group, const char *name,
606                               MAIN_WINDOW_REC *window)
607 {
608         GSList *tmp;
609
610         for (tmp = group->bars; tmp != NULL; tmp = tmp->next) {
611                 STATUSBAR_REC *rec = tmp->data;
612
613                 if (rec->parent_window == window &&
614                     strcmp(rec->config->name, name) == 0)
615                         return rec;
616         }
617
618         return NULL;
619 }
620
621 static char *update_statusbar_bg(const char *str, const char *color)
622 {
623         GString *out;
624         char *ret;
625
626         out = g_string_new(color);
627         while (*str != '\0') {
628                 if (*str == '%' && str[1] == 'n') {
629                         g_string_append(out, color);
630                         str += 2;
631                         continue;
632                 }
633
634                 g_string_append_c(out, *str);
635                 str++;
636         }
637
638         ret = out->str;
639         g_string_free(out, FALSE);
640         return ret;
641 }
642
643 const char *statusbar_item_get_value(SBAR_ITEM_REC *item)
644 {
645         const char *value;
646
647         value = item->config->value;
648         if (value == NULL) {
649                 value = g_hash_table_lookup(sbar_item_defs,
650                                             item->config->name);
651         }
652
653         return value;
654 }
655
656 static char *reverse_controls(const char *str)
657 {
658         GString *out;
659         char *ret;
660
661         out = g_string_new(NULL);
662
663         while (*str != '\0') {
664                 if ((unsigned char) *str < 32 ||
665                     (term_type == TERM_TYPE_8BIT &&
666                      (unsigned char) (*str & 0x7f) < 32)) {
667                         /* control char */
668                         g_string_sprintfa(out, "%%8%c%%8",
669                                           'A'-1 + (*str & 0x7f));
670                 } else {
671                         g_string_append_c(out, *str);
672                 }
673
674                 str++;
675         }
676
677         ret = out->str;
678         g_string_free(out, FALSE);
679         return ret;
680 }
681
682 void statusbar_item_default_handler(SBAR_ITEM_REC *item, int get_size_only,
683                                     const char *str, const char *data,
684                                     int escape_vars)
685 {
686         SERVER_REC *server;
687         WI_ITEM_REC *wiitem;
688         char *tmpstr, *tmpstr2;
689         int len;
690
691         if (str == NULL)
692                 str = statusbar_item_get_value(item);
693         if (str == NULL || *str == '\0') {
694                 item->min_size = item->max_size = 0;
695                 return;
696         }
697
698         if (active_win == NULL) {
699                 server = NULL;
700                 wiitem = NULL;
701         } else {
702                 server = active_win->active_server;
703                 wiitem = active_win->active;
704         }
705
706         /* expand templates */
707         tmpstr = theme_format_expand_data(current_theme, &str,
708                                           'n', 'n',
709                                           NULL, NULL,
710                                           EXPAND_FLAG_ROOT |
711                                           EXPAND_FLAG_IGNORE_REPLACES |
712                                           EXPAND_FLAG_IGNORE_EMPTY);
713         /* expand $variables */
714         tmpstr2 = parse_special_string(tmpstr, server, wiitem, data, NULL,
715                                        (escape_vars ? PARSE_FLAG_ESCAPE_VARS : 0 ));
716         g_free(tmpstr);
717
718         /* remove color codes (not %formats) */
719         tmpstr = strip_codes(tmpstr2);
720         g_free(tmpstr2);
721
722         /* show all control chars reversed */
723         tmpstr2 = reverse_controls(tmpstr);
724         g_free(tmpstr);
725
726         tmpstr = tmpstr2;
727         if (get_size_only) {
728                 item->min_size = item->max_size = format_get_length(tmpstr);
729         } else {
730                 if (item->size < item->min_size) {
731                         /* they're forcing us smaller than minimum size.. */
732                         len = format_real_length(tmpstr, item->size);
733                         tmpstr[len] = '\0';
734                 }
735
736                 tmpstr2 = update_statusbar_bg(tmpstr, item->bar->color);
737                 gui_printtext(item->xpos, item->bar->real_ypos, tmpstr2);
738                 g_free(tmpstr2);
739         }
740         g_free(tmpstr);
741 }
742
743 static void statusbar_item_default_func(SBAR_ITEM_REC *item, int get_size_only)
744 {
745         statusbar_item_default_handler(item, get_size_only, NULL, "", TRUE);
746 }
747
748 static void statusbar_update_item(void)
749 {
750         GSList *items;
751
752         items = g_hash_table_lookup(sbar_signal_items,
753                                     GINT_TO_POINTER(signal_get_emitted_id()));
754         while (items != NULL) {
755                 SBAR_ITEM_REC *item = items->data;
756
757                 statusbar_item_redraw(item);
758                 items = items->next;
759         }
760 }
761
762 static void statusbar_update_server(SERVER_REC *server)
763 {
764         SERVER_REC *item_server;
765         GSList *items;
766
767         items = g_hash_table_lookup(sbar_signal_items,
768                                     GINT_TO_POINTER(signal_get_emitted_id()));
769         while (items != NULL) {
770                 SBAR_ITEM_REC *item = items->data;
771
772                 item_server = item->bar->parent_window != NULL ?
773                         item->bar->parent_window->active->active_server :
774                         active_win->active_server;
775
776                 if (item_server == server)
777                         statusbar_item_redraw(item);
778
779                 items = items->next;
780         }
781 }
782
783 static void statusbar_update_window(WINDOW_REC *window)
784 {
785         WINDOW_REC *item_window;
786         GSList *items;
787
788         items = g_hash_table_lookup(sbar_signal_items,
789                                     GINT_TO_POINTER(signal_get_emitted_id()));
790         while (items != NULL) {
791                 SBAR_ITEM_REC *item = items->data;
792
793                 item_window = item->bar->parent_window != NULL ?
794                         item->bar->parent_window->active : active_win;
795
796                 if (item_window == window)
797                         statusbar_item_redraw(item);
798
799                 items = items->next;
800         }
801 }
802
803 static void statusbar_update_window_item(WI_ITEM_REC *wiitem)
804 {
805         WI_ITEM_REC *item_wi;
806         GSList *items;
807
808         items = g_hash_table_lookup(sbar_signal_items,
809                                     GINT_TO_POINTER(signal_get_emitted_id()));
810         while (items != NULL) {
811                 SBAR_ITEM_REC *item = items->data;
812
813                 item_wi = item->bar->parent_window != NULL ?
814                         item->bar->parent_window->active->active :
815                         active_win->active;
816
817                 if (item_wi == wiitem)
818                         statusbar_item_redraw(item);
819
820                 items = items->next;
821         }
822 }
823
824 static void statusbar_item_default_signals(SBAR_ITEM_REC *item)
825 {
826         SIGNAL_FUNC func;
827         GSList *list;
828         const char *value;
829         void *signal_id;
830         int *signals, *pos;
831
832         value = statusbar_item_get_value(item);
833         if (value == NULL)
834                 return;
835
836         signals = special_vars_get_signals(value);
837         if (signals == NULL)
838                 return;
839
840         for (pos = signals; *pos != -1; pos += 2) {
841                 /* update signal -> item mappings */
842                 signal_id = GINT_TO_POINTER(*pos);
843                 list = g_hash_table_lookup(sbar_signal_items, signal_id);
844                 if (list == NULL) {
845                         switch (pos[1]) {
846                         case EXPANDO_ARG_NONE:
847                                 func = (SIGNAL_FUNC) statusbar_update_item;
848                                 break;
849                         case EXPANDO_ARG_SERVER:
850                                 func = (SIGNAL_FUNC) statusbar_update_server;
851                                 break;
852                         case EXPANDO_ARG_WINDOW:
853                                 func = (SIGNAL_FUNC) statusbar_update_window;
854                                 break;
855                         case EXPANDO_ARG_WINDOW_ITEM:
856                                 func = (SIGNAL_FUNC) statusbar_update_window_item;
857                                 break;
858                         default:
859                                 func = NULL;
860                                 break;
861                         }
862                         if (func != NULL)
863                                 signal_add_to_id(MODULE_NAME, 1, *pos, func);
864                 }
865
866                 if (g_slist_find(list, item) == NULL)
867                         list = g_slist_append(list, item);
868                 g_hash_table_insert(sbar_signal_items, signal_id, list);
869
870                 /* update item -> signal mappings */
871                 list = g_hash_table_lookup(sbar_item_signals, item);
872                 if (g_slist_find(list, signal_id) == NULL)
873                         list = g_slist_append(list, signal_id);
874                 g_hash_table_insert(sbar_item_signals, item, list);
875         }
876         g_free(signals);
877 }
878
879 SBAR_ITEM_REC *statusbar_item_create(STATUSBAR_REC *bar,
880                                      SBAR_ITEM_CONFIG_REC *config)
881 {
882         SBAR_ITEM_REC *rec;
883         GSList *items;
884
885         g_return_val_if_fail(bar != NULL, NULL);
886         g_return_val_if_fail(config != NULL, NULL);
887
888         rec = g_new0(SBAR_ITEM_REC, 1);
889         bar->items = g_slist_append(bar->items, rec);
890
891         rec->bar = bar;
892         rec->config = config;
893
894         rec->func = (STATUSBAR_FUNC) g_hash_table_lookup(sbar_item_funcs,
895                                                          config->name);
896         if (rec->func == NULL)
897                 rec->func = statusbar_item_default_func;
898         statusbar_item_default_signals(rec);
899
900         items = g_hash_table_lookup(named_sbar_items, config->name);
901         items = g_slist_append(items, rec);
902         g_hash_table_insert(named_sbar_items, config->name, items);
903
904         irssi_set_dirty();
905         rec->dirty = TRUE;
906         bar->dirty = TRUE;
907
908         signal_emit("statusbar item created", 1, rec);
909         return rec;
910 }
911
912 static void statusbar_signal_remove(int signal_id)
913 {
914         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_item);
915         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_server);
916         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window);
917         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window_item);
918 }
919
920 static void statusbar_item_remove_signal(SBAR_ITEM_REC *item, int signal_id)
921 {
922         GSList *list;
923
924         /* update signal -> item hash */
925         list = g_hash_table_lookup(sbar_signal_items,
926                                    GINT_TO_POINTER(signal_id));
927         list = g_slist_remove(list, item);
928         if (list != NULL) {
929                 g_hash_table_insert(sbar_signal_items,
930                                     GINT_TO_POINTER(signal_id), list);
931         } else {
932                 g_hash_table_remove(sbar_signal_items,
933                                     GINT_TO_POINTER(signal_id));
934                 statusbar_signal_remove(signal_id);
935         }
936 }
937
938 void statusbar_item_destroy(SBAR_ITEM_REC *item)
939 {
940         GSList *list;
941
942         g_return_if_fail(item != NULL);
943
944         item->bar->items = g_slist_remove(item->bar->items, item);
945
946         list = g_hash_table_lookup(named_sbar_items, item->config->name);
947         list = g_slist_remove(list, item);
948         if (list == NULL)
949                 g_hash_table_remove(named_sbar_items, item->config->name);
950         else
951                 g_hash_table_insert(named_sbar_items, item->config->name, list);
952
953         signal_emit("statusbar item destroyed", 1, item);
954
955         list = g_hash_table_lookup(sbar_item_signals, item);
956         g_hash_table_remove(sbar_item_signals, item);
957
958         while (list != NULL) {
959                 statusbar_item_remove_signal(item, GPOINTER_TO_INT(list->data));
960                 list = g_slist_remove(list, list->data);
961         }
962
963         g_free(item);
964 }
965
966 static void statusbar_redraw_needed_items(STATUSBAR_REC *bar)
967 {
968         WINDOW_REC *old_active_win;
969         GSList *tmp;
970         char *str;
971
972         old_active_win = active_win;
973         if (bar->parent_window != NULL)
974                 active_win = bar->parent_window->active;
975
976         if (bar->dirty_xpos >= 0) {
977                 str = g_strconcat(bar->color, "%>", NULL);
978                 gui_printtext(bar->dirty_xpos, bar->real_ypos, str);
979                 g_free(str);
980         }
981
982         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
983                 SBAR_ITEM_REC *rec = tmp->data;
984
985                 if (rec->dirty ||
986                     (bar->dirty_xpos != -1 &&
987                      rec->xpos >= bar->dirty_xpos)) {
988                         rec->current_size = rec->size;
989                         rec->func(rec, FALSE);
990                         rec->dirty = FALSE;
991                 }
992         }
993
994         active_win = old_active_win;
995 }
996
997 void statusbar_redraw_dirty(void)
998 {
999         GSList *tmp;
1000
1001         if (statusbar_need_recreate_items) {
1002                 statusbar_need_recreate_items = FALSE;
1003                 statusbars_recreate_items();
1004         }
1005
1006         for (tmp = active_statusbar_group->bars; tmp != NULL; tmp = tmp->next) {
1007                 STATUSBAR_REC *rec = tmp->data;
1008
1009                 if (rec->dirty) {
1010                         statusbar_redraw_needed_items(rec);
1011                         rec->dirty = FALSE;
1012                         rec->dirty_xpos = -1;
1013                 }
1014         }
1015 }
1016
1017 #define STATUSBAR_IS_VISIBLE(bar, window) \
1018         ((bar)->visible == STATUSBAR_VISIBLE_ALWAYS || \
1019         (active_mainwin == (window) && \
1020          (bar)->visible == STATUSBAR_VISIBLE_ACTIVE) || \
1021         (active_mainwin != (window) && \
1022          (bar)->visible == STATUSBAR_VISIBLE_INACTIVE))
1023
1024 static void statusbars_remove_unvisible(MAIN_WINDOW_REC *window)
1025 {
1026         GSList *tmp, *next;
1027
1028         for (tmp = window->statusbars; tmp != NULL; tmp = next) {
1029                 STATUSBAR_REC *bar = tmp->data;
1030
1031                 next = tmp->next;
1032                 if (!STATUSBAR_IS_VISIBLE(bar->config, window))
1033                         statusbar_destroy(bar);
1034         }
1035 }
1036
1037 static void statusbars_add_visible(MAIN_WINDOW_REC *window)
1038 {
1039         STATUSBAR_GROUP_REC *group;
1040         STATUSBAR_REC *bar;
1041         GSList *tmp;
1042
1043         group = active_statusbar_group;
1044         for (tmp = group->config_bars; tmp != NULL; tmp = tmp->next) {
1045                 STATUSBAR_CONFIG_REC *config = tmp->data;
1046
1047                 if (config->type == STATUSBAR_TYPE_WINDOW &&
1048                     STATUSBAR_IS_VISIBLE(config, window) &&
1049                     statusbar_find(group, config->name, window) == NULL) {
1050                         bar = statusbar_create(group, config, window);
1051                         statusbar_redraw(bar, TRUE);
1052                 }
1053         }
1054 }
1055
1056 static void sig_mainwindow_destroyed(MAIN_WINDOW_REC *window)
1057 {
1058         while (window->statusbars != NULL) {
1059                 STATUSBAR_REC *bar = window->statusbars->data;
1060
1061                 bar->parent_window->statusbars =
1062                         g_slist_remove(bar->parent_window->statusbars, bar);
1063                 bar->parent_window = NULL;
1064                 statusbar_destroy(bar);
1065         }
1066 }
1067
1068 static void sig_window_changed(void)
1069 {
1070         GSList *tmp;
1071
1072         for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
1073                 MAIN_WINDOW_REC *rec = tmp->data;
1074
1075                 statusbars_remove_unvisible(rec);
1076                 statusbars_add_visible(rec);
1077         }
1078 }
1079
1080 static void sig_gui_window_created(WINDOW_REC *window)
1081 {
1082         statusbars_add_visible(WINDOW_MAIN(window));
1083 }
1084
1085 static void statusbar_item_def_destroy(void *key, void *value)
1086 {
1087         g_free(key);
1088         g_free(value);
1089 }
1090
1091 static void statusbar_signal_item_destroy(void *key, GSList *value)
1092 {
1093         while (value != NULL) {
1094                 statusbar_signal_remove(GPOINTER_TO_INT(value->data));
1095                 value->data = g_slist_remove(value, value->data);
1096         }
1097 }
1098
1099 static void statusbar_item_signal_destroy(void *key, GSList *value)
1100 {
1101         g_slist_free(value);
1102 }
1103
1104 void statusbars_create_window_bars(void)
1105 {
1106         g_slist_foreach(mainwindows, (GFunc) statusbars_add_visible, NULL);
1107 }
1108
1109 void statusbar_init(void)
1110 {
1111         statusbar_need_recreate_items = FALSE;
1112         statusbar_groups = NULL;
1113         active_statusbar_group = NULL;
1114         sbar_item_defs = g_hash_table_new((GHashFunc) g_str_hash,
1115                                           (GCompareFunc) g_str_equal);
1116         sbar_item_funcs = g_hash_table_new((GHashFunc) g_str_hash,
1117                                            (GCompareFunc) g_str_equal);
1118         sbar_signal_items = g_hash_table_new((GHashFunc) g_direct_hash,
1119                                              (GCompareFunc) g_direct_equal);
1120         sbar_item_signals = g_hash_table_new((GHashFunc) g_direct_hash,
1121                                              (GCompareFunc) g_direct_equal);
1122         named_sbar_items = g_hash_table_new((GHashFunc) g_str_hash,
1123                                             (GCompareFunc) g_str_equal);
1124
1125         signal_add("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
1126         signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
1127         signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
1128         signal_add("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
1129         signal_add("window changed", (SIGNAL_FUNC) sig_window_changed);
1130         signal_add("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
1131
1132         statusbar_items_init();
1133         statusbar_config_init(); /* signals need to be before this call */
1134 }
1135
1136 void statusbar_deinit(void)
1137 {
1138         while (statusbar_groups != NULL)
1139                 statusbar_group_destroy(statusbar_groups->data);
1140
1141         g_hash_table_foreach(sbar_item_defs,
1142                              (GHFunc) statusbar_item_def_destroy, NULL);
1143         g_hash_table_destroy(sbar_item_defs);
1144
1145         g_hash_table_foreach(sbar_item_funcs, (GHFunc) g_free, NULL);
1146         g_hash_table_destroy(sbar_item_funcs);
1147
1148         g_hash_table_foreach(sbar_signal_items,
1149                              (GHFunc) statusbar_signal_item_destroy, NULL);
1150         g_hash_table_destroy(sbar_signal_items);
1151         g_hash_table_foreach(sbar_item_signals,
1152                              (GHFunc) statusbar_item_signal_destroy, NULL);
1153         g_hash_table_destroy(sbar_item_signals);
1154         g_hash_table_destroy(named_sbar_items);
1155
1156         signal_remove("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
1157         signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
1158         signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
1159         signal_remove("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
1160         signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed);
1161         signal_remove("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
1162
1163         statusbar_items_deinit();
1164         statusbar_config_deinit();
1165 }