imported irssi.
[silc.git] / apps / irssi / src / fe-text / statusbar.c
1 /*
2  statusbar.c : irssi
3
4     Copyright (C) 1999-2001 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23
24 #include "themes.h"
25
26 #include "statusbar.h"
27 #include "gui-windows.h"
28
29 static int backs[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; /* FIXME: should be in some more generic place.. */
30
31 void statusbar_items_init(void);
32 void statusbar_items_deinit(void);
33
34 static GSList *statusbars;
35 static int sbar_uppest, sbar_lowest, sbars_up, sbars_down;
36
37 static void statusbar_item_destroy(SBAR_ITEM_REC *rec)
38 {
39         rec->bar->items = g_slist_remove(rec->bar->items, rec);
40         g_free(rec);
41 }
42
43 static int sbar_item_cmp(SBAR_ITEM_REC *item1, SBAR_ITEM_REC *item2)
44 {
45         return item1->priority == item2->priority ? 0 :
46                 item1->priority < item2->priority ? -1 : 1;
47 }
48
49 static int statusbar_shrink_to_min(GSList *items, int size, int max_width)
50 {
51         GSList *tmp;
52
53         for (tmp = items; tmp != NULL; tmp = tmp->next) {
54                 SBAR_ITEM_REC *rec = tmp->data;
55
56                 size -= (rec->max_size-rec->min_size);
57                 rec->size = rec->min_size;
58
59                 if (size <= max_width) {
60                         rec->size += max_width-size;
61                         break;
62                 }
63
64                 if (rec->size == 0) {
65                         /* min_size was 0, item removed.
66                            remove the marginal too */
67                         size--;
68                 }
69         }
70
71         return size;
72 }
73
74 static void statusbar_shrink_forced(GSList *items, int size, int max_width)
75 {
76         GSList *tmp;
77
78         for (tmp = items; tmp != NULL; tmp = tmp->next) {
79                 SBAR_ITEM_REC *rec = tmp->data;
80
81                 if (size-rec->size > max_width) {
82                         /* remove the whole item */
83                         size -= rec->size+1; /* +1 == the marginal */
84                         rec->size = 0;
85                 } else {
86                         /* shrink the item */
87                         rec->size -= size-max_width;
88                         break;
89                 }
90         }
91 }
92
93 static void statusbar_get_sizes(STATUSBAR_REC *bar, int max_width)
94 {
95         GSList *tmp, *prior_sorted;
96         int width;
97
98         /* first give items their max. size */
99         prior_sorted = NULL;
100         width = -1; /* -1 because of the marginals */
101         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
102                 SBAR_ITEM_REC *rec = tmp->data;
103
104                 rec->func(rec, TRUE);
105                 rec->size = rec->max_size;
106
107                 if (rec->size > 0) {
108                         /* +1 == marginal between items */
109                         width += rec->max_size+1;
110
111                         prior_sorted = g_slist_insert_sorted(prior_sorted, rec,
112                                                              (GCompareFunc)
113                                                              sbar_item_cmp);
114                 }
115         }
116
117         if (width > max_width) {
118                 /* too big, start shrinking from items with lowest priority
119                    and shrink until everything fits or until we've shrinked
120                    all items. */
121                 width = statusbar_shrink_to_min(prior_sorted, width,
122                                                 max_width);
123                 if (width > max_width) {
124                         /* still need to shrink, remove the items with lowest
125                            priority until everything fits to screen */
126                         statusbar_shrink_forced(prior_sorted, width,
127                                                 max_width);
128                 }
129         }
130
131         g_slist_free(prior_sorted);
132 }
133
134 static void statusbar_redraw_line(STATUSBAR_REC *bar)
135 {
136         WINDOW_REC *old_active_win;
137         GSList *tmp;
138         int xpos, rxpos;
139
140         old_active_win = active_win;
141         if (bar->window != NULL)
142                 active_win = bar->window->active;
143
144         statusbar_get_sizes(bar, COLS-2);
145
146         xpos = 1;
147         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
148                 SBAR_ITEM_REC *rec = tmp->data;
149
150                 if (!rec->right_justify && rec->size > 0) {
151                         rec->xpos = xpos;
152                         xpos += rec->size+1;
153                         rec->func(rec, FALSE);
154                 }
155         }
156
157         rxpos = COLS-1;
158         for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
159                 SBAR_ITEM_REC *rec = tmp->data;
160
161                 if (rec->right_justify && rec->size > 0) {
162                         rxpos -= rec->size+1;
163                         rec->xpos = rxpos+1;
164                         rec->func(rec, FALSE);
165                 }
166         }
167
168         active_win = old_active_win;
169 }
170
171 static void statusbar_redraw_all(void)
172 {
173         screen_refresh_freeze();
174         g_slist_foreach(statusbars, (GFunc) statusbar_redraw, NULL);
175         screen_refresh_thaw();
176 }
177
178 STATUSBAR_REC *statusbar_find(int pos, int line)
179 {
180         GSList *tmp;
181
182         for (tmp = statusbars; tmp != NULL; tmp = tmp->next) {
183                 STATUSBAR_REC *rec = tmp->data;
184
185                 if (rec->pos == pos && rec->line == line)
186                         return rec;
187         }
188
189         return NULL;
190 }
191
192 void statusbar_redraw(STATUSBAR_REC *bar)
193 {
194         if (bar == NULL) {
195                 statusbar_redraw_all();
196                 return;
197         }
198
199         set_bg(stdscr, backs[bar->color] << 4);
200         move(bar->ypos, 0); clrtoeol();
201         set_bg(stdscr, 0);
202
203         statusbar_redraw_line(bar);
204
205         screen_refresh(NULL);
206 }
207
208 void statusbar_item_redraw(SBAR_ITEM_REC *item)
209 {
210         g_return_if_fail(item != NULL);
211
212         item->func(item, TRUE);
213         if (item->max_size != item->size)
214                 statusbar_redraw(item->bar);
215         else {
216                 item->func(item, FALSE);
217                 screen_refresh(NULL);
218         }
219 }
220
221 static int get_last_bg(const char *str)
222 {
223         int last = -1;
224
225         while (*str != '\0') {
226                 if (*str == '%' && str[1] != '\0') {
227                         str++;
228                         if (*str >= '0' && *str <= '7')
229                                 last = *str-'0';
230                 }
231                 str++;
232         }
233
234         return last;
235 }
236
237 /* ypos is used only when pos == STATUSBAR_POS_MIDDLE */
238 STATUSBAR_REC *statusbar_create(int pos, int ypos)
239 {
240         STATUSBAR_REC *rec;
241         char *str;
242
243         rec = g_new0(STATUSBAR_REC, 1);
244         statusbars = g_slist_append(statusbars, rec);
245
246         rec->pos = pos;
247         rec->line = pos == STATUSBAR_POS_MIDDLE ? ypos :
248                 mainwindows_reserve_lines(1, pos == STATUSBAR_POS_UP);
249         rec->ypos = pos == STATUSBAR_POS_MIDDLE ? ypos :
250                 pos == STATUSBAR_POS_UP ? rec->line : LINES-1-rec->line;
251
252         /* get background color from sb_background abstract */
253         str = theme_format_expand(current_theme, "{sb_background}");
254         if (str == NULL) str = g_strdup("%n%8");
255         rec->color_string = g_strconcat("%n", str, NULL);
256         g_free(str);
257
258         rec->color = get_last_bg(rec->color_string);
259         if (rec->color < 0) rec->color = current_theme->default_real_color;
260
261         if (pos == STATUSBAR_POS_UP) {
262                 if (sbars_up == 0) sbar_uppest = rec->line;
263                 sbars_up++;
264                 rec->line -= sbar_uppest;
265         } else if (pos == STATUSBAR_POS_DOWN) {
266                 if (sbars_down == 0) sbar_lowest = rec->line;
267                 sbars_down++;
268                 rec->line -= sbar_lowest;
269         }
270
271         set_bg(stdscr, backs[rec->color] << 4);
272         move(rec->ypos, 0); clrtoeol();
273         set_bg(stdscr, 0);
274
275         return rec;
276 }
277
278 static void statusbars_pack(int pos, int line)
279 {
280         GSList *tmp;
281
282         for (tmp = statusbars; tmp != NULL; tmp = tmp->next) {
283                 STATUSBAR_REC *rec = tmp->data;
284
285                 if (rec->pos == pos && rec->line > line) {
286                         rec->line--;
287                         rec->ypos += (pos == STATUSBAR_POS_UP ? -1 : 1);
288                 }
289         }
290 }
291
292 void statusbar_destroy(STATUSBAR_REC *bar)
293 {
294         g_return_if_fail(bar != NULL);
295
296         if (bar->pos != STATUSBAR_POS_MIDDLE)
297                 mainwindows_reserve_lines(-1, bar->pos == STATUSBAR_POS_UP);
298
299         if (bar->pos == STATUSBAR_POS_UP) sbars_up--;
300         if (bar->pos == STATUSBAR_POS_DOWN) sbars_down--;
301         statusbars = g_slist_remove(statusbars, bar);
302
303         while (bar->items != NULL)
304                 statusbar_item_destroy(bar->items->data);
305
306         if (bar->pos != STATUSBAR_POS_MIDDLE)
307                 statusbars_pack(bar->pos, bar->pos);
308         g_free(bar->color_string);
309         g_free(bar);
310
311         if (!quitting) statusbar_redraw_all();
312 }
313
314 SBAR_ITEM_REC *statusbar_item_create(STATUSBAR_REC *bar,
315                                      int priority, int right_justify,
316                                      STATUSBAR_FUNC func)
317 {
318         SBAR_ITEM_REC *rec;
319
320         g_return_val_if_fail(bar != NULL, NULL);
321         g_return_val_if_fail(func != NULL, NULL);
322
323         rec = g_new0(SBAR_ITEM_REC, 1);
324         rec->bar = bar;
325         bar->items = g_slist_append(bar->items, rec);
326
327         rec->priority = priority;
328         rec->right_justify = right_justify;
329         rec->func = func;
330
331         return rec;
332 }
333
334 void statusbar_item_remove(SBAR_ITEM_REC *item)
335 {
336         g_return_if_fail(item != NULL);
337
338         statusbar_item_destroy(item);
339         if (!quitting) statusbar_redraw_all();
340 }
341
342 static void sig_mainwindow_resized(MAIN_WINDOW_REC *window)
343 {
344         STATUSBAR_REC *rec;
345
346         rec = window->statusbar;
347         rec->ypos = window->first_line+window->height;
348 }
349
350 void statusbar_init(void)
351 {
352         statusbars = NULL;
353         sbars_up = sbars_down = 0;
354
355         statusbar_items_init();
356         signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
357         signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
358 }
359
360 void statusbar_deinit(void)
361 {
362         statusbar_items_deinit();
363
364         while (statusbars != NULL)
365                 statusbar_destroy(statusbars->data);
366
367         signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
368         signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
369 }