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