Added SILC Thread Queue API
[crypto.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 != NULL ?
703                         active_win->active_server : active_win->connect_server;
704                 wiitem = active_win->active;
705         }
706
707         /* expand templates */
708         tmpstr = theme_format_expand_data(current_theme, &str,
709                                           'n', 'n',
710                                           NULL, NULL,
711                                           EXPAND_FLAG_ROOT |
712                                           EXPAND_FLAG_IGNORE_REPLACES |
713                                           EXPAND_FLAG_IGNORE_EMPTY);
714         /* expand $variables */
715         tmpstr2 = parse_special_string(tmpstr, server, wiitem, data, NULL,
716                                        (escape_vars ? PARSE_FLAG_ESCAPE_VARS : 0 ));
717         g_free(tmpstr);
718
719         /* remove color codes (not %formats) */
720         tmpstr = strip_codes(tmpstr2);
721         g_free(tmpstr2);
722
723         /* show all control chars reversed */
724         tmpstr2 = reverse_controls(tmpstr);
725         g_free(tmpstr);
726
727         tmpstr = tmpstr2;
728         if (get_size_only) {
729                 item->min_size = item->max_size = format_get_length(tmpstr);
730         } else {
731                 if (item->size < item->min_size) {
732                         /* they're forcing us smaller than minimum size.. */
733                         len = format_real_length(tmpstr, item->size);
734                         tmpstr[len] = '\0';
735                 } else {
736                         /* make sure the str is big enough to fill the
737                            requested size, so it won't corrupt screen */
738                         len = format_get_length(tmpstr);
739                         if (len < item->size) {
740                                 char *fill;
741
742                                 len = item->size-len;
743                                 fill = g_malloc(len + 1);
744                                 memset(fill, ' ', len); fill[len] = '\0';
745
746                                 tmpstr2 = g_strconcat(tmpstr, fill, NULL);
747                                 g_free(fill);
748                                 g_free(tmpstr);
749                                 tmpstr = tmpstr2;
750                         }
751                 }
752
753                 tmpstr2 = update_statusbar_bg(tmpstr, item->bar->color);
754                 gui_printtext(item->xpos, item->bar->real_ypos, tmpstr2);
755                 g_free(tmpstr2);
756         }
757         g_free(tmpstr);
758 }
759
760 static void statusbar_item_default_func(SBAR_ITEM_REC *item, int get_size_only)
761 {
762         statusbar_item_default_handler(item, get_size_only, NULL, "", TRUE);
763 }
764
765 static void statusbar_update_item(void)
766 {
767         GSList *items;
768
769         items = g_hash_table_lookup(sbar_signal_items,
770                                     GINT_TO_POINTER(signal_get_emitted_id()));
771         while (items != NULL) {
772                 SBAR_ITEM_REC *item = items->data;
773
774                 statusbar_item_redraw(item);
775                 items = items->next;
776         }
777 }
778
779 static void statusbar_update_server(SERVER_REC *server)
780 {
781         SERVER_REC *item_server;
782         GSList *items;
783
784         items = g_hash_table_lookup(sbar_signal_items,
785                                     GINT_TO_POINTER(signal_get_emitted_id()));
786         while (items != NULL) {
787                 SBAR_ITEM_REC *item = items->data;
788
789                 item_server = item->bar->parent_window != NULL ?
790                         item->bar->parent_window->active->active_server :
791                         active_win->active_server;
792
793                 if (item_server == server)
794                         statusbar_item_redraw(item);
795
796                 items = items->next;
797         }
798 }
799
800 static void statusbar_update_window(WINDOW_REC *window)
801 {
802         WINDOW_REC *item_window;
803         GSList *items;
804
805         items = g_hash_table_lookup(sbar_signal_items,
806                                     GINT_TO_POINTER(signal_get_emitted_id()));
807         while (items != NULL) {
808                 SBAR_ITEM_REC *item = items->data;
809
810                 item_window = item->bar->parent_window != NULL ?
811                         item->bar->parent_window->active : active_win;
812
813                 if (item_window == window)
814                         statusbar_item_redraw(item);
815
816                 items = items->next;
817         }
818 }
819
820 static void statusbar_update_window_item(WI_ITEM_REC *wiitem)
821 {
822         WI_ITEM_REC *item_wi;
823         GSList *items;
824
825         items = g_hash_table_lookup(sbar_signal_items,
826                                     GINT_TO_POINTER(signal_get_emitted_id()));
827         while (items != NULL) {
828                 SBAR_ITEM_REC *item = items->data;
829
830                 item_wi = item->bar->parent_window != NULL ?
831                         item->bar->parent_window->active->active :
832                         active_win->active;
833
834                 if (item_wi == wiitem)
835                         statusbar_item_redraw(item);
836
837                 items = items->next;
838         }
839 }
840
841 static void statusbar_item_default_signals(SBAR_ITEM_REC *item)
842 {
843         SIGNAL_FUNC func;
844         GSList *list;
845         const char *value;
846         void *signal_id;
847         int *signals, *pos;
848
849         value = statusbar_item_get_value(item);
850         if (value == NULL)
851                 return;
852
853         signals = special_vars_get_signals(value);
854         if (signals == NULL)
855                 return;
856
857         for (pos = signals; *pos != -1; pos += 2) {
858                 /* update signal -> item mappings */
859                 signal_id = GINT_TO_POINTER(*pos);
860                 list = g_hash_table_lookup(sbar_signal_items, signal_id);
861                 if (list == NULL) {
862                         switch (pos[1]) {
863                         case EXPANDO_ARG_NONE:
864                                 func = (SIGNAL_FUNC) statusbar_update_item;
865                                 break;
866                         case EXPANDO_ARG_SERVER:
867                                 func = (SIGNAL_FUNC) statusbar_update_server;
868                                 break;
869                         case EXPANDO_ARG_WINDOW:
870                                 func = (SIGNAL_FUNC) statusbar_update_window;
871                                 break;
872                         case EXPANDO_ARG_WINDOW_ITEM:
873                                 func = (SIGNAL_FUNC) statusbar_update_window_item;
874                                 break;
875                         default:
876                                 func = NULL;
877                                 break;
878                         }
879                         if (func != NULL) {
880                                 signal_add_full_id(MODULE_NAME,
881                                                    SIGNAL_PRIORITY_DEFAULT,
882                                                    *pos, func, NULL);
883                         }
884                 }
885
886                 if (g_slist_find(list, item) == NULL)
887                         list = g_slist_append(list, item);
888                 g_hash_table_insert(sbar_signal_items, signal_id, list);
889
890                 /* update item -> signal mappings */
891                 list = g_hash_table_lookup(sbar_item_signals, item);
892                 if (g_slist_find(list, signal_id) == NULL)
893                         list = g_slist_append(list, signal_id);
894                 g_hash_table_insert(sbar_item_signals, item, list);
895         }
896         g_free(signals);
897 }
898
899 SBAR_ITEM_REC *statusbar_item_create(STATUSBAR_REC *bar,
900                                      SBAR_ITEM_CONFIG_REC *config)
901 {
902         SBAR_ITEM_REC *rec;
903         GSList *items;
904
905         g_return_val_if_fail(bar != NULL, NULL);
906         g_return_val_if_fail(config != NULL, NULL);
907
908         rec = g_new0(SBAR_ITEM_REC, 1);
909         bar->items = g_slist_append(bar->items, rec);
910
911         rec->bar = bar;
912         rec->config = config;
913
914         rec->func = (STATUSBAR_FUNC) g_hash_table_lookup(sbar_item_funcs,
915                                                          config->name);
916         if (rec->func == NULL)
917                 rec->func = statusbar_item_default_func;
918         statusbar_item_default_signals(rec);
919
920         items = g_hash_table_lookup(named_sbar_items, config->name);
921         items = g_slist_append(items, rec);
922         g_hash_table_insert(named_sbar_items, config->name, items);
923
924         irssi_set_dirty();
925         rec->dirty = TRUE;
926         bar->dirty = TRUE;
927
928         signal_emit("statusbar item created", 1, rec);
929         return rec;
930 }
931
932 static void statusbar_signal_remove(int signal_id)
933 {
934         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_item, NULL);
935         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_server, NULL);
936         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window, NULL);
937         signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window_item, NULL);
938 }
939
940 static void statusbar_item_remove_signal(SBAR_ITEM_REC *item, int signal_id)
941 {
942         GSList *list;
943
944         /* update signal -> item hash */
945         list = g_hash_table_lookup(sbar_signal_items,
946                                    GINT_TO_POINTER(signal_id));
947         list = g_slist_remove(list, item);
948         if (list != NULL) {
949                 g_hash_table_insert(sbar_signal_items,
950                                     GINT_TO_POINTER(signal_id), list);
951         } else {
952                 g_hash_table_remove(sbar_signal_items,
953                                     GINT_TO_POINTER(signal_id));
954                 statusbar_signal_remove(signal_id);
955         }
956 }
957
958 void statusbar_item_destroy(SBAR_ITEM_REC *item)
959 {
960         GSList *list;
961
962         g_return_if_fail(item != NULL);
963
964         item->bar->items = g_slist_remove(item->bar->items, item);
965
966         list = g_hash_table_lookup(named_sbar_items, item->config->name);
967         list = g_slist_remove(list, item);
968         if (list == NULL)
969                 g_hash_table_remove(named_sbar_items, item->config->name);
970         else
971                 g_hash_table_insert(named_sbar_items, item->config->name, list);
972
973         signal_emit("statusbar item destroyed", 1, item);
974
975         list = g_hash_table_lookup(sbar_item_signals, item);
976         g_hash_table_remove(sbar_item_signals, item);
977
978         while (list != NULL) {
979                 statusbar_item_remove_signal(item, GPOINTER_TO_INT(list->data));
980                 list = g_slist_remove(list, list->data);
981         }
982
983         g_free(item);
984 }
985
986 static void statusbar_redraw_needed_items(STATUSBAR_REC *bar)
987 {
988         WINDOW_REC *old_active_win;
989         GSList *tmp;
990         char *str;
991
992         old_active_win = active_win;
993         if (bar->parent_window != NULL)
994                 active_win = bar->parent_window->active;
995
996         if (bar->dirty_xpos >= 0) {
997                 str = g_strconcat(bar->color, "%>", NULL);
998                 gui_printtext(bar->dirty_xpos, bar->real_ypos, str);
999                 g_free(str);
1000         }
1001
1002         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
1003                 SBAR_ITEM_REC *rec = tmp->data;
1004
1005                 if (rec->dirty ||
1006                     (bar->dirty_xpos != -1 &&
1007                      rec->xpos >= bar->dirty_xpos)) {
1008                         rec->current_size = rec->size;
1009                         rec->func(rec, FALSE);
1010                         rec->dirty = FALSE;
1011                 }
1012         }
1013
1014         active_win = old_active_win;
1015 }
1016
1017 void statusbar_redraw_dirty(void)
1018 {
1019         GSList *tmp;
1020
1021         if (statusbar_need_recreate_items) {
1022                 statusbar_need_recreate_items = FALSE;
1023                 statusbars_recreate_items();
1024         }
1025
1026         for (tmp = active_statusbar_group->bars; tmp != NULL; tmp = tmp->next) {
1027                 STATUSBAR_REC *rec = tmp->data;
1028
1029                 if (rec->dirty) {
1030                         statusbar_redraw_needed_items(rec);
1031                         rec->dirty = FALSE;
1032                         rec->dirty_xpos = -1;
1033                 }
1034         }
1035 }
1036
1037 #define STATUSBAR_IS_VISIBLE(bar, window) \
1038         ((bar)->visible == STATUSBAR_VISIBLE_ALWAYS || \
1039         (active_mainwin == (window) && \
1040          (bar)->visible == STATUSBAR_VISIBLE_ACTIVE) || \
1041         (active_mainwin != (window) && \
1042          (bar)->visible == STATUSBAR_VISIBLE_INACTIVE))
1043
1044 static void statusbars_remove_unvisible(MAIN_WINDOW_REC *window)
1045 {
1046         GSList *tmp, *next;
1047
1048         for (tmp = window->statusbars; tmp != NULL; tmp = next) {
1049                 STATUSBAR_REC *bar = tmp->data;
1050
1051                 next = tmp->next;
1052                 if (!STATUSBAR_IS_VISIBLE(bar->config, window))
1053                         statusbar_destroy(bar);
1054         }
1055 }
1056
1057 static void statusbars_add_visible(MAIN_WINDOW_REC *window)
1058 {
1059         STATUSBAR_GROUP_REC *group;
1060         STATUSBAR_REC *bar;
1061         GSList *tmp;
1062
1063         group = active_statusbar_group;
1064         for (tmp = group->config_bars; tmp != NULL; tmp = tmp->next) {
1065                 STATUSBAR_CONFIG_REC *config = tmp->data;
1066
1067                 if (config->type == STATUSBAR_TYPE_WINDOW &&
1068                     STATUSBAR_IS_VISIBLE(config, window) &&
1069                     statusbar_find(group, config->name, window) == NULL) {
1070                         bar = statusbar_create(group, config, window);
1071                         statusbar_redraw(bar, TRUE);
1072                 }
1073         }
1074 }
1075
1076 static void sig_mainwindow_destroyed(MAIN_WINDOW_REC *window)
1077 {
1078         while (window->statusbars != NULL) {
1079                 STATUSBAR_REC *bar = window->statusbars->data;
1080
1081                 bar->parent_window->statusbars =
1082                         g_slist_remove(bar->parent_window->statusbars, bar);
1083                 bar->parent_window = NULL;
1084                 statusbar_destroy(bar);
1085         }
1086 }
1087
1088 static void sig_window_changed(void)
1089 {
1090         GSList *tmp;
1091
1092         for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
1093                 MAIN_WINDOW_REC *rec = tmp->data;
1094
1095                 statusbars_remove_unvisible(rec);
1096                 statusbars_add_visible(rec);
1097         }
1098 }
1099
1100 static void sig_gui_window_created(WINDOW_REC *window)
1101 {
1102         statusbars_add_visible(WINDOW_MAIN(window));
1103 }
1104
1105 static void statusbar_item_def_destroy(void *key, void *value)
1106 {
1107         g_free(key);
1108         g_free(value);
1109 }
1110
1111 static void statusbar_signal_item_destroy(void *key, GSList *value)
1112 {
1113         while (value != NULL) {
1114                 statusbar_signal_remove(GPOINTER_TO_INT(value->data));
1115                 value->data = g_slist_remove(value, value->data);
1116         }
1117 }
1118
1119 static void statusbar_item_signal_destroy(void *key, GSList *value)
1120 {
1121         g_slist_free(value);
1122 }
1123
1124 void statusbars_create_window_bars(void)
1125 {
1126         g_slist_foreach(mainwindows, (GFunc) statusbars_add_visible, NULL);
1127 }
1128
1129 void statusbar_init(void)
1130 {
1131         statusbar_need_recreate_items = FALSE;
1132         statusbar_groups = NULL;
1133         active_statusbar_group = NULL;
1134         sbar_item_defs = g_hash_table_new((GHashFunc) g_str_hash,
1135                                           (GCompareFunc) g_str_equal);
1136         sbar_item_funcs = g_hash_table_new((GHashFunc) g_str_hash,
1137                                            (GCompareFunc) g_str_equal);
1138         sbar_signal_items = g_hash_table_new((GHashFunc) g_direct_hash,
1139                                              (GCompareFunc) g_direct_equal);
1140         sbar_item_signals = g_hash_table_new((GHashFunc) g_direct_hash,
1141                                              (GCompareFunc) g_direct_equal);
1142         named_sbar_items = g_hash_table_new((GHashFunc) g_str_hash,
1143                                             (GCompareFunc) g_str_equal);
1144
1145         signal_add("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
1146         signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
1147         signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
1148         signal_add("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
1149         signal_add("window changed", (SIGNAL_FUNC) sig_window_changed);
1150         signal_add("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
1151
1152         statusbar_items_init();
1153         statusbar_config_init(); /* signals need to be before this call */
1154 }
1155
1156 void statusbar_deinit(void)
1157 {
1158         while (statusbar_groups != NULL)
1159                 statusbar_group_destroy(statusbar_groups->data);
1160
1161         g_hash_table_foreach(sbar_item_defs,
1162                              (GHFunc) statusbar_item_def_destroy, NULL);
1163         g_hash_table_destroy(sbar_item_defs);
1164
1165         g_hash_table_foreach(sbar_item_funcs, (GHFunc) g_free, NULL);
1166         g_hash_table_destroy(sbar_item_funcs);
1167
1168         g_hash_table_foreach(sbar_signal_items,
1169                              (GHFunc) statusbar_signal_item_destroy, NULL);
1170         g_hash_table_destroy(sbar_signal_items);
1171         g_hash_table_foreach(sbar_item_signals,
1172                              (GHFunc) statusbar_item_signal_destroy, NULL);
1173         g_hash_table_destroy(sbar_item_signals);
1174         g_hash_table_destroy(named_sbar_items);
1175
1176         signal_remove("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
1177         signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
1178         signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
1179         signal_remove("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
1180         signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed);
1181         signal_remove("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
1182
1183         statusbar_items_deinit();
1184         statusbar_config_deinit();
1185 }