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