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