Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / fe-text / statusbar.c
index 8e99c4fe5634a1e80515c911aea820e317e2a481..5eba9557f9ace85104067cf6b8687e3a930794ef 100644 (file)
 
 #include "module.h"
 #include "signals.h"
+#include "expandos.h"
+#include "special-vars.h"
 
 #include "themes.h"
 
 #include "statusbar.h"
+#include "statusbar-config.h"
 #include "gui-windows.h"
-
-static int backs[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; /* FIXME: should be in some more generic place.. */
+#include "gui-printtext.h"
 
 void statusbar_items_init(void);
 void statusbar_items_deinit(void);
 
-static GSList *statusbars;
-static int sbar_uppest, sbar_lowest, sbars_up, sbars_down;
+GSList *statusbar_groups;
+STATUSBAR_GROUP_REC *active_statusbar_group;
+
+/*
+   sbar_item_defs: char *name => char *value
+   sbar_item_funcs: char *name => STATUSBAR_FUNC func
+   sbar_signal_items: int signal_id => GSList *(SBAR_ITEM_REC *items)
+   sbar_item_signals: SBAR_ITEM_REC *item => GSList *(int *signal_ids)
+   named_sbar_items: const char *name => GSList *(SBAR_ITEM_REC *items)
+*/
+static GHashTable *sbar_item_defs, *sbar_item_funcs;
+static GHashTable *sbar_signal_items, *sbar_item_signals;
+static GHashTable *named_sbar_items;
+static int statusbar_need_recreate_items;
+
+void statusbar_item_register(const char *name, const char *value,
+                            STATUSBAR_FUNC func)
+{
+       gpointer hkey, hvalue;
+
+       statusbar_need_recreate_items = TRUE;
+       if (value != NULL) {
+               if (g_hash_table_lookup_extended(sbar_item_defs,
+                                                name, &hkey, &hvalue)) {
+                       g_hash_table_remove(sbar_item_defs, name);
+                       g_free(hkey);
+                        g_free(hvalue);
+               }
+               g_hash_table_insert(sbar_item_defs,
+                                   g_strdup(name), g_strdup(value));
+       }
+
+       if (func != NULL) {
+               if (g_hash_table_lookup(sbar_item_funcs, name) == NULL) {
+                       g_hash_table_insert(sbar_item_funcs,
+                                           g_strdup(name), (void *) func);
+               }
+       }
+}
 
-static void statusbar_item_destroy(SBAR_ITEM_REC *rec)
+void statusbar_item_unregister(const char *name)
 {
-       rec->bar->items = g_slist_remove(rec->bar->items, rec);
-       g_free(rec);
+       gpointer key, value;
+
+       statusbar_need_recreate_items = TRUE;
+       if (g_hash_table_lookup_extended(sbar_item_defs,
+                                        name, &key, &value)) {
+               g_hash_table_remove(sbar_item_defs, key);
+               g_free(key);
+                g_free(value);
+       }
+
+       if (g_hash_table_lookup_extended(sbar_item_funcs,
+                                        name, &key, &value)) {
+               g_hash_table_remove(sbar_item_funcs, key);
+               g_free(key);
+       }
+}
+
+STATUSBAR_GROUP_REC *statusbar_group_create(const char *name)
+{
+       STATUSBAR_GROUP_REC *rec;
+
+       rec = g_new0(STATUSBAR_GROUP_REC, 1);
+       rec->name = g_strdup(name);
+
+        statusbar_groups = g_slist_append(statusbar_groups, rec);
+       return rec;
+}
+
+void statusbar_group_destroy(STATUSBAR_GROUP_REC *rec)
+{
+       statusbar_groups = g_slist_remove(statusbar_groups, rec);
+
+       while (rec->bars != NULL)
+               statusbar_destroy(rec->bars->data);
+       while (rec->config_bars != NULL)
+                statusbar_config_destroy(rec, rec->config_bars->data);
+
+        g_free(rec->name);
+        g_free(rec);
+}
+
+STATUSBAR_GROUP_REC *statusbar_group_find(const char *name)
+{
+       GSList *tmp;
+
+       for (tmp = statusbar_groups; tmp != NULL; tmp = tmp->next) {
+               STATUSBAR_GROUP_REC *rec = tmp->data;
+
+               if (strcmp(rec->name, name) == 0)
+                        return rec;
+       }
+
+        return NULL;
 }
 
 static int sbar_item_cmp(SBAR_ITEM_REC *item1, SBAR_ITEM_REC *item2)
 {
-       return item1->priority == item2->priority ? 0 :
-               item1->priority < item2->priority ? -1 : 1;
+       return item1->config->priority == item2->config->priority ? 0 :
+               item1->config->priority < item2->config->priority ? -1 : 1;
 }
 
+static int sbar_cmp_position(STATUSBAR_REC *bar1, STATUSBAR_REC *bar2)
+{
+       return bar1->config->position < bar2->config->position ? -1 : 1;
+}
+
+/* Shink all items in statusbar to their minimum requested size.
+   The items list should be sorted by priority, highest first. */
 static int statusbar_shrink_to_min(GSList *items, int size, int max_width)
 {
        GSList *tmp;
@@ -71,6 +168,9 @@ static int statusbar_shrink_to_min(GSList *items, int size, int max_width)
         return size;
 }
 
+/* shink the items in statusbar, even if their size gets smaller than
+   their minimum requested size. The items list should be sorted by
+   priority, highest first. */
 static void statusbar_shrink_forced(GSList *items, int size, int max_width)
 {
        GSList *tmp;
@@ -80,7 +180,7 @@ static void statusbar_shrink_forced(GSList *items, int size, int max_width)
 
                if (size-rec->size > max_width) {
                        /* remove the whole item */
-                        size -= rec->size+1; /* +1 == the marginal */
+                        size -= rec->size;
                        rec->size = 0;
                } else {
                        /* shrink the item */
@@ -90,14 +190,14 @@ static void statusbar_shrink_forced(GSList *items, int size, int max_width)
        }
 }
 
-static void statusbar_get_sizes(STATUSBAR_REC *bar, int max_width)
+static void statusbar_resize_items(STATUSBAR_REC *bar, int max_width)
 {
        GSList *tmp, *prior_sorted;
         int width;
 
         /* first give items their max. size */
        prior_sorted = NULL;
-       width = -1; /* -1 because of the marginals */
+       width = 0;
        for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
                SBAR_ITEM_REC *rec = tmp->data;
 
@@ -105,8 +205,7 @@ static void statusbar_get_sizes(STATUSBAR_REC *bar, int max_width)
                rec->size = rec->max_size;
 
                if (rec->size > 0) {
-                        /* +1 == marginal between items */
-                       width += rec->max_size+1;
+                       width += rec->max_size;
 
                        prior_sorted = g_slist_insert_sorted(prior_sorted, rec,
                                                             (GCompareFunc)
@@ -131,239 +230,956 @@ static void statusbar_get_sizes(STATUSBAR_REC *bar, int max_width)
        g_slist_free(prior_sorted);
 }
 
-static void statusbar_redraw_line(STATUSBAR_REC *bar)
+#define SBAR_ITEM_REDRAW_NEEDED(_bar, _item, _xpos) \
+       (((_bar)->dirty_xpos != -1 && (_xpos) >= (_bar)->dirty_xpos) || \
+        (_item)->xpos != (_xpos) || (_item)->current_size != (_item)->size)
+
+static void statusbar_calc_item_positions(STATUSBAR_REC *bar)
 {
         WINDOW_REC *old_active_win;
-       GSList *tmp;
+       GSList *tmp, *right_items;
        int xpos, rxpos;
 
        old_active_win = active_win;
-        if (bar->window != NULL)
-               active_win = bar->window->active;
+        if (bar->parent_window != NULL)
+               active_win = bar->parent_window->active;
 
-       statusbar_get_sizes(bar, COLS-2);
+       statusbar_resize_items(bar, term_width);
 
-       xpos = 1;
+        /* left-aligned items */
+       xpos = 0;
        for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
                SBAR_ITEM_REC *rec = tmp->data;
 
-               if (!rec->right_justify && rec->size > 0) {
-                       rec->xpos = xpos;
-                        xpos += rec->size+1;
-                        rec->func(rec, FALSE);
+               if (!rec->config->right_alignment &&
+                   (rec->size > 0 || rec->current_size > 0)) {
+                       if (SBAR_ITEM_REDRAW_NEEDED(bar, rec, xpos)) {
+                                /* redraw the item */
+                               rec->dirty = TRUE;
+                               if (bar->dirty_xpos == -1 ||
+                                   xpos < bar->dirty_xpos) {
+                                        irssi_set_dirty();
+                                       bar->dirty = TRUE;
+                                       bar->dirty_xpos = xpos;
+                               }
+
+                               rec->xpos = xpos;
+                       }
+                       xpos += rec->size;
                }
        }
 
-       rxpos = COLS-1;
+       /* right-aligned items - first copy them to a new list backwards,
+          easier to draw them in right order */
+        right_items = NULL;
        for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
                SBAR_ITEM_REC *rec = tmp->data;
 
-               if (rec->right_justify && rec->size > 0) {
-                        rxpos -= rec->size+1;
-                       rec->xpos = rxpos+1;
-                       rec->func(rec, FALSE);
+               if (rec->config->right_alignment) {
+                        if (rec->size > 0)
+                               right_items = g_slist_prepend(right_items, rec);
+                       else if (rec->current_size > 0 &&
+                                (bar->dirty_xpos == -1 ||
+                                 rec->xpos < bar->dirty_xpos)) {
+                               /* item was hidden - set the dirty position
+                                  to begin from the item's old xpos */
+                               irssi_set_dirty();
+                               bar->dirty = TRUE;
+                                bar->dirty_xpos = rec->xpos;
+                       }
+               }
+       }
+
+       rxpos = term_width;
+       for (tmp = right_items; tmp != NULL; tmp = tmp->next) {
+               SBAR_ITEM_REC *rec = tmp->data;
+
+               rxpos -= rec->size;
+               if (SBAR_ITEM_REDRAW_NEEDED(bar, rec, rxpos)) {
+                       rec->dirty = TRUE;
+                       if (bar->dirty_xpos == -1 ||
+                           rxpos < bar->dirty_xpos) {
+                               irssi_set_dirty();
+                               bar->dirty = TRUE;
+                               bar->dirty_xpos = rxpos;
+                       }
+                       rec->xpos = rxpos;
                }
        }
+        g_slist_free(right_items);
 
        active_win = old_active_win;
 }
 
-static void statusbar_redraw_all(void)
+void statusbar_redraw(STATUSBAR_REC *bar, int force)
 {
-       screen_refresh_freeze();
-       g_slist_foreach(statusbars, (GFunc) statusbar_redraw, NULL);
-       screen_refresh_thaw();
+       if (statusbar_need_recreate_items)
+               return; /* don't bother yet */
+
+       if (bar != NULL) {
+               if (force) {
+                       irssi_set_dirty();
+                       bar->dirty = TRUE;
+                        bar->dirty_xpos = 0;
+               }
+               statusbar_calc_item_positions(bar);
+       } else if (active_statusbar_group != NULL) {
+               g_slist_foreach(active_statusbar_group->bars,
+                               (GFunc) statusbar_redraw,
+                               GINT_TO_POINTER(force));
+       }
 }
 
-STATUSBAR_REC *statusbar_find(int pos, int line)
+void statusbar_item_redraw(SBAR_ITEM_REC *item)
 {
-       GSList *tmp;
+        WINDOW_REC *old_active_win;
 
-       for (tmp = statusbars; tmp != NULL; tmp = tmp->next) {
-               STATUSBAR_REC *rec = tmp->data;
+       g_return_if_fail(item != NULL);
+
+       old_active_win = active_win;
+        if (item->bar->parent_window != NULL)
+               active_win = item->bar->parent_window->active;
 
-               if (rec->pos == pos && rec->line == line)
-                       return rec;
+       item->func(item, TRUE);
+
+       item->dirty = TRUE;
+       item->bar->dirty = TRUE;
+       irssi_set_dirty();
+
+       if (item->max_size != item->size) {
+               /* item wants a new size - we'll need to redraw
+                  the statusbar to see if this is allowed */
+               statusbar_redraw(item->bar, FALSE);
        }
 
-       return NULL;
+       active_win = old_active_win;
 }
 
-void statusbar_redraw(STATUSBAR_REC *bar)
+void statusbar_items_redraw(const char *name)
 {
-       if (bar == NULL) {
-               statusbar_redraw_all();
+       g_slist_foreach(g_hash_table_lookup(named_sbar_items, name),
+                       (GFunc) statusbar_item_redraw, NULL);
+}
+
+static void statusbars_recalc_ypos(STATUSBAR_REC *bar)
+{
+       GSList *tmp, *bar_group;
+        int ypos;
+
+       /* get list of statusbars with same type and placement,
+          sorted by position */
+        bar_group = NULL;
+       tmp = bar->config->type == STATUSBAR_TYPE_ROOT ? bar->group->bars :
+                bar->parent_window->statusbars;
+
+        for (; tmp != NULL; tmp = tmp->next) {
+               STATUSBAR_REC *rec = tmp->data;
+
+               if (rec->config->type == bar->config->type &&
+                   rec->config->placement == bar->config->placement) {
+                       bar_group = g_slist_insert_sorted(bar_group, rec,
+                                                         (GCompareFunc)
+                                                         sbar_cmp_position);
+               }
+       }
+
+       if (bar_group == NULL) {
+               /* we just destroyed the last statusbar in this
+                  type/placement group */
                return;
        }
 
-       set_bg(stdscr, backs[bar->color] << 4);
-       move(bar->ypos, 0); clrtoeol();
-       set_bg(stdscr, 0);
+        /* get the Y-position for the first statusbar */
+       if (bar->config->type == STATUSBAR_TYPE_ROOT) {
+               ypos = bar->config->placement == STATUSBAR_TOP ? 0 :
+                       term_height - g_slist_length(bar_group);
+       } else {
+               ypos = bar->config->placement == STATUSBAR_TOP ?
+                       bar->parent_window->first_line :
+                       bar->parent_window->last_line -
+                       (g_slist_length(bar_group)-1);
+       }
 
-       statusbar_redraw_line(bar);
+        /* set the Y-positions */
+       while (bar_group != NULL) {
+               bar = bar_group->data;
 
-        screen_refresh(NULL);
+               if (bar->real_ypos != ypos) {
+                       bar->real_ypos = ypos;
+                        statusbar_redraw(bar, TRUE);
+               }
+
+                ypos++;
+                bar_group = g_slist_remove(bar_group, bar_group->data);
+       }
 }
 
-void statusbar_item_redraw(SBAR_ITEM_REC *item)
+static void sig_terminal_resized(void)
 {
-       g_return_if_fail(item != NULL);
+       GSList *tmp;
 
-       item->func(item, TRUE);
-       if (item->max_size != item->size)
-               statusbar_redraw(item->bar);
-       else {
-               item->func(item, FALSE);
-                screen_refresh(NULL);
+       for (tmp = active_statusbar_group->bars; tmp != NULL; tmp = tmp->next) {
+               STATUSBAR_REC *bar = tmp->data;
+
+               if (bar->config->type == STATUSBAR_TYPE_ROOT &&
+                   bar->config->placement == STATUSBAR_BOTTOM) {
+                       statusbars_recalc_ypos(bar);
+                        break;
+               }
        }
 }
 
-static int get_last_bg(const char *str)
+static void mainwindow_recalc_ypos(MAIN_WINDOW_REC *window, int placement)
 {
-       int last = -1;
+       GSList *tmp;
 
-       while (*str != '\0') {
-               if (*str == '%' && str[1] != '\0') {
-                        str++;
-                       if (*str >= '0' && *str <= '7')
-                               last = *str-'0';
+       for (tmp = window->statusbars; tmp != NULL; tmp = tmp->next) {
+               STATUSBAR_REC *bar = tmp->data;
+
+               if (bar->config->placement == placement) {
+                       statusbars_recalc_ypos(bar);
+                        break;
                }
-                str++;
        }
+}
 
-        return last;
+static void sig_mainwindow_resized(MAIN_WINDOW_REC *window)
+{
+        mainwindow_recalc_ypos(window, STATUSBAR_TOP);
+        mainwindow_recalc_ypos(window, STATUSBAR_BOTTOM);
 }
 
-/* ypos is used only when pos == STATUSBAR_POS_MIDDLE */
-STATUSBAR_REC *statusbar_create(int pos, int ypos)
+STATUSBAR_REC *statusbar_create(STATUSBAR_GROUP_REC *group,
+                                STATUSBAR_CONFIG_REC *config,
+                                MAIN_WINDOW_REC *parent_window)
 {
-       STATUSBAR_REC *rec;
-        char *str;
+       STATUSBAR_REC *bar;
+       THEME_REC *theme;
+        GSList *tmp;
+       char *name, *value;
+
+        g_return_val_if_fail(group != NULL, NULL);
+        g_return_val_if_fail(config != NULL, NULL);
+       g_return_val_if_fail(config->type != STATUSBAR_TYPE_WINDOW ||
+                            parent_window != NULL, NULL);
 
-       rec = g_new0(STATUSBAR_REC, 1);
-       statusbars = g_slist_append(statusbars, rec);
+       bar = g_new0(STATUSBAR_REC, 1);
+       group->bars = g_slist_append(group->bars, bar);
 
-       rec->pos = pos;
-       rec->line = pos == STATUSBAR_POS_MIDDLE ? ypos :
-               mainwindows_reserve_lines(1, pos == STATUSBAR_POS_UP);
-       rec->ypos = pos == STATUSBAR_POS_MIDDLE ? ypos :
-               pos == STATUSBAR_POS_UP ? rec->line : LINES-1-rec->line;
+       bar->group = group;
+
+        bar->config = config;
+        bar->parent_window = parent_window;
+
+       irssi_set_dirty();
+       bar->dirty = TRUE;
+        bar->dirty_xpos = 0;
+
+        signal_remove("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
+       signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
+       signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
+
+       if (config->type == STATUSBAR_TYPE_ROOT) {
+               /* top/bottom of the screen */
+               mainwindows_reserve_lines(config->placement == STATUSBAR_TOP,
+                                         config->placement == STATUSBAR_BOTTOM);
+                theme = current_theme;
+       } else {
+               /* top/bottom of the window */
+               parent_window->statusbars =
+                       g_slist_append(parent_window->statusbars, bar);
+               mainwindow_set_statusbar_lines(parent_window,
+                                              config->placement == STATUSBAR_TOP,
+                                              config->placement == STATUSBAR_BOTTOM);
+               theme = parent_window != NULL && parent_window->active != NULL &&
+                       parent_window->active->theme != NULL ?
+                       parent_window->active->theme : current_theme;
+       }
+
+        signal_add("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
+       signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
+       signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
 
         /* get background color from sb_background abstract */
-       str = theme_format_expand(current_theme, "{sb_background}");
-       if (str == NULL) str = g_strdup("%n%8");
-       rec->color_string = g_strconcat("%n", str, NULL);
-        g_free(str);
+        name = g_strdup_printf("{sb_%s_bg}", config->name);
+       value = theme_format_expand(theme, name);
+       g_free(name);
+
+       if (*value == '\0') {
+                /* try with the statusbar group name */
+               g_free(value);
+
+               name = g_strdup_printf("{sb_%s_bg}", group->name);
+               value = theme_format_expand(theme, name);
+               g_free(name);
+
+               if (*value == '\0') {
+                       /* fallback to default statusbar background
+                          (also provides backwards compatibility..) */
+                        g_free(value);
+                       value = theme_format_expand(theme, "{sb_background}");
+               }
+       }
+
+       if (*value == '\0') {
+                g_free(value);
+               value = g_strdup("%8");
+       }
+       bar->color = g_strconcat("%n", value, NULL);
+       g_free(value);
+
+        statusbars_recalc_ypos(bar);
+        signal_emit("statusbar created", 1, bar);
 
-       rec->color = get_last_bg(rec->color_string);
-        if (rec->color < 0) rec->color = current_theme->default_real_color;
+        /* create the items to statusbar */
+       for (tmp = config->items; tmp != NULL; tmp = tmp->next) {
+               SBAR_ITEM_CONFIG_REC *rec = tmp->data;
 
-       if (pos == STATUSBAR_POS_UP) {
-                if (sbars_up == 0) sbar_uppest = rec->line;
-                sbars_up++;
-               rec->line -= sbar_uppest;
-       } else if (pos == STATUSBAR_POS_DOWN) {
-               if (sbars_down == 0) sbar_lowest = rec->line;
-               sbars_down++;
-               rec->line -= sbar_lowest;
+                statusbar_item_create(bar, rec);
        }
+       return bar;
+}
 
-       set_bg(stdscr, backs[rec->color] << 4);
-       move(rec->ypos, 0); clrtoeol();
-       set_bg(stdscr, 0);
+void statusbar_destroy(STATUSBAR_REC *bar)
+{
+       int top;
 
-       return rec;
+       g_return_if_fail(bar != NULL);
+
+       bar->group->bars = g_slist_remove(bar->group->bars, bar);
+       if (bar->parent_window != NULL) {
+               bar->parent_window->statusbars =
+                       g_slist_remove(bar->parent_window->statusbars, bar);
+       }
+
+        signal_emit("statusbar destroyed", 1, bar);
+
+       while (bar->items != NULL)
+               statusbar_item_destroy(bar->items->data);
+
+        g_free(bar->color);
+
+       if (bar->config->type != STATUSBAR_TYPE_WINDOW ||
+           bar->parent_window != NULL)
+               statusbars_recalc_ypos(bar);
+
+       top = bar->config->placement == STATUSBAR_TOP;
+       if (bar->config->type == STATUSBAR_TYPE_ROOT) {
+               /* top/bottom of the screen */
+               mainwindows_reserve_lines(top ? -1 : 0, !top ? -1 : 0);
+       } else if (bar->parent_window != NULL) {
+               /* top/bottom of the window */
+               mainwindow_set_statusbar_lines(bar->parent_window,
+                                              top ? -1 : 0, !top ? -1 : 0);
+       }
+
+       g_free(bar);
+}
+
+void statusbar_recreate_items(STATUSBAR_REC *bar)
+{
+       GSList *tmp;
+
+       /* destroy */
+       while (bar->items != NULL)
+               statusbar_item_destroy(bar->items->data);
+
+        /* create */
+       for (tmp = bar->config->items; tmp != NULL; tmp = tmp->next) {
+               SBAR_ITEM_CONFIG_REC *rec = tmp->data;
+
+                statusbar_item_create(bar, rec);
+       }
+
+        statusbar_redraw(bar, TRUE);
+}
+
+void statusbars_recreate_items(void)
+{
+       if (active_statusbar_group != NULL) {
+               g_slist_foreach(active_statusbar_group->bars,
+                               (GFunc) statusbar_recreate_items, NULL);
+       }
 }
 
-static void statusbars_pack(int pos, int line)
+STATUSBAR_REC *statusbar_find(STATUSBAR_GROUP_REC *group, const char *name,
+                             MAIN_WINDOW_REC *window)
 {
        GSList *tmp;
 
-       for (tmp = statusbars; tmp != NULL; tmp = tmp->next) {
+       for (tmp = group->bars; tmp != NULL; tmp = tmp->next) {
                STATUSBAR_REC *rec = tmp->data;
 
-               if (rec->pos == pos && rec->line > line) {
-                       rec->line--;
-                       rec->ypos += (pos == STATUSBAR_POS_UP ? -1 : 1);
+               if (rec->parent_window == window &&
+                   strcmp(rec->config->name, name) == 0)
+                        return rec;
+       }
+
+        return NULL;
+}
+
+static char *update_statusbar_bg(const char *str, const char *color)
+{
+       GString *out;
+        char *ret;
+
+       out = g_string_new(color);
+       while (*str != '\0') {
+               if (*str == '%' && str[1] == 'n') {
+                        g_string_append(out, color);
+                       str += 2;
+                        continue;
                }
+
+               g_string_append_c(out, *str);
+                str++;
        }
+
+        ret = out->str;
+        g_string_free(out, FALSE);
+        return ret;
 }
 
-void statusbar_destroy(STATUSBAR_REC *bar)
+const char *statusbar_item_get_value(SBAR_ITEM_REC *item)
 {
-       g_return_if_fail(bar != NULL);
+       const char *value;
 
-       if (bar->pos != STATUSBAR_POS_MIDDLE)
-               mainwindows_reserve_lines(-1, bar->pos == STATUSBAR_POS_UP);
+       value = item->config->value;
+       if (value == NULL) {
+               value = g_hash_table_lookup(sbar_item_defs,
+                                           item->config->name);
+       }
 
-       if (bar->pos == STATUSBAR_POS_UP) sbars_up--;
-       if (bar->pos == STATUSBAR_POS_DOWN) sbars_down--;
-        statusbars = g_slist_remove(statusbars, bar);
+        return value;
+}
 
-       while (bar->items != NULL)
-               statusbar_item_destroy(bar->items->data);
+static char *reverse_controls(const char *str)
+{
+       GString *out;
+        char *ret;
 
-       if (bar->pos != STATUSBAR_POS_MIDDLE)
-               statusbars_pack(bar->pos, bar->pos);
-        g_free(bar->color_string);
-       g_free(bar);
+       out = g_string_new(NULL);
 
-       if (!quitting) statusbar_redraw_all();
+       while (*str != '\0') {
+               if ((unsigned char) *str < 32 ||
+                   (term_type == TERM_TYPE_8BIT &&
+                    (unsigned char) (*str & 0x7f) < 32)) {
+                       /* control char */
+                       g_string_sprintfa(out, "%%8%c%%8",
+                                         'A'-1 + (*str & 0x7f));
+               } else {
+                       g_string_append_c(out, *str);
+               }
+
+               str++;
+       }
+
+       ret = out->str;
+        g_string_free(out, FALSE);
+       return ret;
+}
+
+void statusbar_item_default_handler(SBAR_ITEM_REC *item, int get_size_only,
+                                   const char *str, const char *data,
+                                   int escape_vars)
+{
+       SERVER_REC *server;
+       WI_ITEM_REC *wiitem; 
+       char *tmpstr, *tmpstr2;
+       int len;
+
+       if (str == NULL)
+               str = statusbar_item_get_value(item);
+       if (str == NULL || *str == '\0') {
+               item->min_size = item->max_size = 0;
+               return;
+       }
+
+       if (active_win == NULL) {
+               server = NULL;
+                wiitem = NULL;
+       } else {
+               server = active_win->active_server != NULL ?
+                       active_win->active_server : active_win->connect_server;
+               wiitem = active_win->active;
+       }
+
+       /* expand templates */
+       tmpstr = theme_format_expand_data(current_theme, &str,
+                                         'n', 'n',
+                                         NULL, NULL,
+                                         EXPAND_FLAG_ROOT |
+                                         EXPAND_FLAG_IGNORE_REPLACES |
+                                         EXPAND_FLAG_IGNORE_EMPTY);
+       /* expand $variables */
+       tmpstr2 = parse_special_string(tmpstr, server, wiitem, data, NULL,
+                                      (escape_vars ? PARSE_FLAG_ESCAPE_VARS : 0 ));
+        g_free(tmpstr);
+
+       /* remove color codes (not %formats) */
+       tmpstr = strip_codes(tmpstr2);
+        g_free(tmpstr2);
+
+       /* show all control chars reversed */
+       tmpstr2 = reverse_controls(tmpstr);
+       g_free(tmpstr);
+
+       tmpstr = tmpstr2;
+       if (get_size_only) {
+               item->min_size = item->max_size = format_get_length(tmpstr);
+       } else {
+               if (item->size < item->min_size) {
+                        /* they're forcing us smaller than minimum size.. */
+                       len = format_real_length(tmpstr, item->size);
+                        tmpstr[len] = '\0';
+               } else {
+                       /* make sure the str is big enough to fill the
+                          requested size, so it won't corrupt screen */
+                       len = format_get_length(tmpstr);
+                       if (len < item->size) {
+                               char *fill;
+
+                               len = item->size-len;
+                               fill = g_malloc(len + 1);
+                               memset(fill, ' ', len); fill[len] = '\0';
+
+                               tmpstr2 = g_strconcat(tmpstr, fill, NULL);
+                               g_free(fill);
+                               g_free(tmpstr);
+                               tmpstr = tmpstr2;
+                       }
+               }
+
+               tmpstr2 = update_statusbar_bg(tmpstr, item->bar->color);
+               gui_printtext(item->xpos, item->bar->real_ypos, tmpstr2);
+                g_free(tmpstr2);
+       }
+       g_free(tmpstr);
+}
+
+static void statusbar_item_default_func(SBAR_ITEM_REC *item, int get_size_only)
+{
+       statusbar_item_default_handler(item, get_size_only, NULL, "", TRUE);
+}
+
+static void statusbar_update_item(void)
+{
+       GSList *items;
+
+       items = g_hash_table_lookup(sbar_signal_items,
+                                   GINT_TO_POINTER(signal_get_emitted_id()));
+       while (items != NULL) {
+               SBAR_ITEM_REC *item = items->data;
+
+               statusbar_item_redraw(item);
+               items = items->next;
+       }
+}
+
+static void statusbar_update_server(SERVER_REC *server)
+{
+        SERVER_REC *item_server;
+       GSList *items;
+
+       items = g_hash_table_lookup(sbar_signal_items,
+                                   GINT_TO_POINTER(signal_get_emitted_id()));
+       while (items != NULL) {
+               SBAR_ITEM_REC *item = items->data;
+
+               item_server = item->bar->parent_window != NULL ?
+                       item->bar->parent_window->active->active_server :
+                       active_win->active_server;
+
+               if (item_server == server)
+                       statusbar_item_redraw(item);
+
+               items = items->next;
+       }
+}
+
+static void statusbar_update_window(WINDOW_REC *window)
+{
+        WINDOW_REC *item_window;
+       GSList *items;
+
+       items = g_hash_table_lookup(sbar_signal_items,
+                                   GINT_TO_POINTER(signal_get_emitted_id()));
+       while (items != NULL) {
+               SBAR_ITEM_REC *item = items->data;
+
+               item_window = item->bar->parent_window != NULL ?
+                       item->bar->parent_window->active : active_win;
+
+               if (item_window == window)
+                       statusbar_item_redraw(item);
+
+               items = items->next;
+       }
+}
+
+static void statusbar_update_window_item(WI_ITEM_REC *wiitem)
+{
+        WI_ITEM_REC *item_wi;
+       GSList *items;
+
+       items = g_hash_table_lookup(sbar_signal_items,
+                                   GINT_TO_POINTER(signal_get_emitted_id()));
+       while (items != NULL) {
+               SBAR_ITEM_REC *item = items->data;
+
+               item_wi = item->bar->parent_window != NULL ?
+                       item->bar->parent_window->active->active :
+                       active_win->active;
+
+               if (item_wi == wiitem)
+                       statusbar_item_redraw(item);
+
+               items = items->next;
+       }
+}
+
+static void statusbar_item_default_signals(SBAR_ITEM_REC *item)
+{
+       SIGNAL_FUNC func;
+        GSList *list;
+       const char *value;
+        void *signal_id;
+        int *signals, *pos;
+
+       value = statusbar_item_get_value(item);
+       if (value == NULL)
+               return;
+
+       signals = special_vars_get_signals(value);
+       if (signals == NULL)
+               return;
+
+       for (pos = signals; *pos != -1; pos += 2) {
+               /* update signal -> item mappings */
+                signal_id = GINT_TO_POINTER(*pos);
+               list = g_hash_table_lookup(sbar_signal_items, signal_id);
+               if (list == NULL) {
+                       switch (pos[1]) {
+                       case EXPANDO_ARG_NONE:
+                               func = (SIGNAL_FUNC) statusbar_update_item;
+                               break;
+                       case EXPANDO_ARG_SERVER:
+                               func = (SIGNAL_FUNC) statusbar_update_server;
+                               break;
+                       case EXPANDO_ARG_WINDOW:
+                               func = (SIGNAL_FUNC) statusbar_update_window;
+                               break;
+                       case EXPANDO_ARG_WINDOW_ITEM:
+                               func = (SIGNAL_FUNC) statusbar_update_window_item;
+                               break;
+                       default:
+                                func = NULL;
+                                break;
+                       }
+                       if (func != NULL) {
+                               signal_add_full_id(MODULE_NAME,
+                                                  SIGNAL_PRIORITY_DEFAULT,
+                                                  *pos, func, NULL);
+                       }
+               }
+
+               if (g_slist_find(list, item) == NULL)
+                       list = g_slist_append(list, item);
+               g_hash_table_insert(sbar_signal_items, signal_id, list);
+
+                /* update item -> signal mappings */
+               list = g_hash_table_lookup(sbar_item_signals, item);
+                if (g_slist_find(list, signal_id) == NULL)
+                       list = g_slist_append(list, signal_id);
+               g_hash_table_insert(sbar_item_signals, item, list);
+       }
+        g_free(signals);
 }
 
 SBAR_ITEM_REC *statusbar_item_create(STATUSBAR_REC *bar,
-                                    int priority, int right_justify,
-                                    STATUSBAR_FUNC func)
+                                    SBAR_ITEM_CONFIG_REC *config)
 {
        SBAR_ITEM_REC *rec;
+        GSList *items;
 
        g_return_val_if_fail(bar != NULL, NULL);
-       g_return_val_if_fail(func != NULL, NULL);
+       g_return_val_if_fail(config != NULL, NULL);
 
        rec = g_new0(SBAR_ITEM_REC, 1);
-       rec->bar = bar;
        bar->items = g_slist_append(bar->items, rec);
 
-        rec->priority = priority;
-       rec->right_justify = right_justify;
-       rec->func = func;
+       rec->bar = bar;
+       rec->config = config;
+
+       rec->func = (STATUSBAR_FUNC) g_hash_table_lookup(sbar_item_funcs,
+                                                        config->name);
+       if (rec->func == NULL)
+               rec->func = statusbar_item_default_func;
+       statusbar_item_default_signals(rec);
+
+       items = g_hash_table_lookup(named_sbar_items, config->name);
+       items = g_slist_append(items, rec);
+        g_hash_table_insert(named_sbar_items, config->name, items);
+
+       irssi_set_dirty();
+       rec->dirty = TRUE;
+       bar->dirty = TRUE;
 
+        signal_emit("statusbar item created", 1, rec);
        return rec;
 }
 
-void statusbar_item_remove(SBAR_ITEM_REC *item)
+static void statusbar_signal_remove(int signal_id)
 {
+       signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_item, NULL);
+       signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_server, NULL);
+       signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window, NULL);
+       signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window_item, NULL);
+}
+
+static void statusbar_item_remove_signal(SBAR_ITEM_REC *item, int signal_id)
+{
+       GSList *list;
+
+        /* update signal -> item hash */
+       list = g_hash_table_lookup(sbar_signal_items,
+                                  GINT_TO_POINTER(signal_id));
+       list = g_slist_remove(list, item);
+       if (list != NULL) {
+               g_hash_table_insert(sbar_signal_items,
+                                   GINT_TO_POINTER(signal_id), list);
+       } else {
+               g_hash_table_remove(sbar_signal_items,
+                                   GINT_TO_POINTER(signal_id));
+                statusbar_signal_remove(signal_id);
+       }
+}
+
+void statusbar_item_destroy(SBAR_ITEM_REC *item)
+{
+       GSList *list;
+
        g_return_if_fail(item != NULL);
 
-       statusbar_item_destroy(item);
-       if (!quitting) statusbar_redraw_all();
+       item->bar->items = g_slist_remove(item->bar->items, item);
+
+       list = g_hash_table_lookup(named_sbar_items, item->config->name);
+       list = g_slist_remove(list, item);
+       if (list == NULL)
+               g_hash_table_remove(named_sbar_items, item->config->name);
+        else
+               g_hash_table_insert(named_sbar_items, item->config->name, list);
+
+        signal_emit("statusbar item destroyed", 1, item);
+
+       list = g_hash_table_lookup(sbar_item_signals, item);
+        g_hash_table_remove(sbar_item_signals, item);
+
+       while (list != NULL) {
+                statusbar_item_remove_signal(item, GPOINTER_TO_INT(list->data));
+               list = g_slist_remove(list, list->data);
+       }
+
+       g_free(item);
 }
 
-static void sig_mainwindow_resized(MAIN_WINDOW_REC *window)
+static void statusbar_redraw_needed_items(STATUSBAR_REC *bar)
 {
-       STATUSBAR_REC *rec;
+        WINDOW_REC *old_active_win;
+       GSList *tmp;
+       char *str;
+
+       old_active_win = active_win;
+        if (bar->parent_window != NULL)
+               active_win = bar->parent_window->active;
+
+       if (bar->dirty_xpos >= 0) {
+               str = g_strconcat(bar->color, "%>", NULL);
+               gui_printtext(bar->dirty_xpos, bar->real_ypos, str);
+               g_free(str);
+       }
+
+       for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
+               SBAR_ITEM_REC *rec = tmp->data;
 
-       rec = window->statusbar;
-        rec->ypos = window->first_line+window->height;
+               if (rec->dirty ||
+                   (bar->dirty_xpos != -1 &&
+                    rec->xpos >= bar->dirty_xpos)) {
+                        rec->current_size = rec->size;
+                       rec->func(rec, FALSE);
+                       rec->dirty = FALSE;
+               }
+       }
+
+        active_win = old_active_win;
 }
 
-void statusbar_init(void)
+void statusbar_redraw_dirty(void)
 {
-       statusbars = NULL;
-       sbars_up = sbars_down = 0;
+       GSList *tmp;
 
-       statusbar_items_init();
+       if (statusbar_need_recreate_items) {
+               statusbar_need_recreate_items = FALSE;
+               statusbars_recreate_items();
+       }
+
+       for (tmp = active_statusbar_group->bars; tmp != NULL; tmp = tmp->next) {
+               STATUSBAR_REC *rec = tmp->data;
+
+               if (rec->dirty) {
+                        statusbar_redraw_needed_items(rec);
+                       rec->dirty = FALSE;
+                       rec->dirty_xpos = -1;
+               }
+       }
+}
+
+#define STATUSBAR_IS_VISIBLE(bar, window) \
+       ((bar)->visible == STATUSBAR_VISIBLE_ALWAYS || \
+       (active_mainwin == (window) && \
+        (bar)->visible == STATUSBAR_VISIBLE_ACTIVE) || \
+       (active_mainwin != (window) && \
+        (bar)->visible == STATUSBAR_VISIBLE_INACTIVE))
+
+static void statusbars_remove_unvisible(MAIN_WINDOW_REC *window)
+{
+       GSList *tmp, *next;
+
+       for (tmp = window->statusbars; tmp != NULL; tmp = next) {
+               STATUSBAR_REC *bar = tmp->data;
+
+               next = tmp->next;
+                if (!STATUSBAR_IS_VISIBLE(bar->config, window))
+                        statusbar_destroy(bar);
+       }
+}
+
+static void statusbars_add_visible(MAIN_WINDOW_REC *window)
+{
+       STATUSBAR_GROUP_REC *group;
+        STATUSBAR_REC *bar;
+       GSList *tmp;
+
+        group = active_statusbar_group;
+       for (tmp = group->config_bars; tmp != NULL; tmp = tmp->next) {
+               STATUSBAR_CONFIG_REC *config = tmp->data;
+
+               if (config->type == STATUSBAR_TYPE_WINDOW &&
+                   STATUSBAR_IS_VISIBLE(config, window) &&
+                   statusbar_find(group, config->name, window) == NULL) {
+                       bar = statusbar_create(group, config, window);
+                       statusbar_redraw(bar, TRUE);
+               }
+       }
+}
+
+static void sig_mainwindow_destroyed(MAIN_WINDOW_REC *window)
+{
+       while (window->statusbars != NULL) {
+               STATUSBAR_REC *bar = window->statusbars->data;
+
+               bar->parent_window->statusbars =
+                       g_slist_remove(bar->parent_window->statusbars, bar);
+               bar->parent_window = NULL;
+               statusbar_destroy(bar);
+       }
+}
+
+static void sig_window_changed(void)
+{
+       GSList *tmp;
+
+       for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+               MAIN_WINDOW_REC *rec = tmp->data;
+
+               statusbars_remove_unvisible(rec);
+                statusbars_add_visible(rec);
+       }
+}
+
+static void sig_gui_window_created(WINDOW_REC *window)
+{
+        statusbars_add_visible(WINDOW_MAIN(window));
+}
+
+static void statusbar_item_def_destroy(void *key, void *value)
+{
+       g_free(key);
+        g_free(value);
+}
+
+static void statusbar_signal_item_destroy(void *key, GSList *value)
+{
+       while (value != NULL) {
+               statusbar_signal_remove(GPOINTER_TO_INT(value->data));
+                value->data = g_slist_remove(value, value->data);
+       }
+}
+
+static void statusbar_item_signal_destroy(void *key, GSList *value)
+{
+        g_slist_free(value);
+}
+
+void statusbars_create_window_bars(void)
+{
+        g_slist_foreach(mainwindows, (GFunc) statusbars_add_visible, NULL);
+}
+
+void statusbar_init(void)
+{
+        statusbar_need_recreate_items = FALSE;
+       statusbar_groups = NULL;
+       active_statusbar_group = NULL;
+       sbar_item_defs = g_hash_table_new((GHashFunc) g_str_hash,
+                                         (GCompareFunc) g_str_equal);
+       sbar_item_funcs = g_hash_table_new((GHashFunc) g_str_hash,
+                                          (GCompareFunc) g_str_equal);
+       sbar_signal_items = g_hash_table_new((GHashFunc) g_direct_hash,
+                                            (GCompareFunc) g_direct_equal);
+       sbar_item_signals = g_hash_table_new((GHashFunc) g_direct_hash,
+                                            (GCompareFunc) g_direct_equal);
+       named_sbar_items = g_hash_table_new((GHashFunc) g_str_hash,
+                                           (GCompareFunc) g_str_equal);
+
+        signal_add("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
        signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
        signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
+       signal_add("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
+       signal_add("window changed", (SIGNAL_FUNC) sig_window_changed);
+       signal_add("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
+
+       statusbar_items_init();
+       statusbar_config_init(); /* signals need to be before this call */
 }
 
 void statusbar_deinit(void)
 {
-       statusbar_items_deinit();
+       while (statusbar_groups != NULL)
+               statusbar_group_destroy(statusbar_groups->data);
 
-       while (statusbars != NULL)
-               statusbar_destroy(statusbars->data);
+       g_hash_table_foreach(sbar_item_defs,
+                            (GHFunc) statusbar_item_def_destroy, NULL);
+       g_hash_table_destroy(sbar_item_defs);
 
+       g_hash_table_foreach(sbar_item_funcs, (GHFunc) g_free, NULL);
+       g_hash_table_destroy(sbar_item_funcs);
+
+       g_hash_table_foreach(sbar_signal_items,
+                            (GHFunc) statusbar_signal_item_destroy, NULL);
+       g_hash_table_destroy(sbar_signal_items);
+       g_hash_table_foreach(sbar_item_signals,
+                            (GHFunc) statusbar_item_signal_destroy, NULL);
+       g_hash_table_destroy(sbar_item_signals);
+       g_hash_table_destroy(named_sbar_items);
+
+        signal_remove("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
        signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
        signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
+       signal_remove("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
+       signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed);
+       signal_remove("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
+
+       statusbar_items_deinit();
+       statusbar_config_deinit();
 }