1 /*
2  * GNT - The GLib Ncurses Toolkit
3  *
4  * GNT is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
7  *
8  * This library is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  */
22 
23 #include "gntconfig.h"
24 
25 #include "gntinternal.h"
26 #undef GNT_LOG_DOMAIN
27 #define GNT_LOG_DOMAIN "Utils"
28 
29 #include "gntbutton.h"
30 #include "gntcheckbox.h"
31 #include "gntcombobox.h"
32 #include "gntentry.h"
33 #include "gntlabel.h"
34 #include "gntline.h"
35 #include "gnttextview.h"
36 #include "gnttree.h"
37 #include "gntutils.h"
38 #include "gntwindow.h"
39 
40 #include <stdarg.h>
41 #include <stdlib.h>
42 #include <string.h>
43 
44 #ifndef NO_LIBXML
45 #include <libxml/parser.h>
46 #include <libxml/tree.h>
47 #endif
48 
gnt_util_get_text_bound(const char * text,int * width,int * height)49 void gnt_util_get_text_bound(const char *text, int *width, int *height)
50 {
51 	const char *s = text, *last;
52 	int count = 1, max = 0;
53 	int len;
54 
55 	/* XXX: ew ... everyone look away */
56 	last = s;
57 	if (s)
58 	{
59 		while (*s)
60 		{
61 			if (*s == '\n' || *s == '\r')
62 			{
63 				count++;
64 				len = gnt_util_onscreen_width(last, s);
65 				if (max < len)
66 					max = len;
67 				last = s + 1;
68 			}
69 			s = g_utf8_next_char(s);
70 		}
71 
72 		len = gnt_util_onscreen_width(last, s);
73 		if (max < len)
74 			max = len;
75 	}
76 
77 	if (height)
78 		*height = count;
79 	if (width)
80 		*width = max + (count > 1);
81 }
82 
gnt_util_onscreen_width(const char * start,const char * end)83 int gnt_util_onscreen_width(const char *start, const char *end)
84 {
85 	int width = 0;
86 
87 	if (end == NULL)
88 		end = start + strlen(start);
89 
90 	while (start < end) {
91 		width += g_unichar_iswide(g_utf8_get_char(start)) ? 2 : 1;
92 		start = g_utf8_next_char(start);
93 	}
94 	return width;
95 }
96 
gnt_util_onscreen_width_to_pointer(const char * string,int len,int * w)97 const char *gnt_util_onscreen_width_to_pointer(const char *string, int len, int *w)
98 {
99 	int size;
100 	int width = 0;
101 	const char *str = string;
102 
103 	if (len <= 0) {
104 		len = gnt_util_onscreen_width(string, NULL);
105 	}
106 
107 	while (width < len && *str) {
108 		size = g_unichar_iswide(g_utf8_get_char(str)) ? 2 : 1;
109 		if (width + size > len)
110 			break;
111 		str = g_utf8_next_char(str);
112 		width += size;
113 	}
114 	if (w)
115 		*w = width;
116 	return str;
117 }
118 
gnt_util_onscreen_fit_string(const char * string,int maxw)119 char *gnt_util_onscreen_fit_string(const char *string, int maxw)
120 {
121 	const char *start, *end;
122 	GString *str;
123 
124 	if (maxw <= 0)
125 		maxw = getmaxx(stdscr) - 4;
126 
127 	start = string;
128 	str = g_string_new(NULL);
129 
130 	while (*start) {
131 		if ((end = strchr(start, '\n')) != NULL ||
132 			(end = strchr(start, '\r')) != NULL) {
133 			if (gnt_util_onscreen_width(start, end) > maxw)
134 				end = NULL;
135 		}
136 		if (end == NULL)
137 			end = gnt_util_onscreen_width_to_pointer(start, maxw, NULL);
138 		str = g_string_append_len(str, start, end - start);
139 		if (*end) {
140 			str = g_string_append_c(str, '\n');
141 			if (*end == '\n' || *end == '\r')
142 				end++;
143 		}
144 		start = end;
145 	}
146 	return g_string_free(str, FALSE);
147 }
148 
149 struct duplicate_fns
150 {
151 	GDupFunc key_dup;
152 	GDupFunc value_dup;
153 	GHashTable *table;
154 };
155 
156 static void
duplicate_values(gpointer key,gpointer value,gpointer data)157 duplicate_values(gpointer key, gpointer value, gpointer data)
158 {
159 	struct duplicate_fns *fns = data;
160 	g_hash_table_insert(fns->table, fns->key_dup ? fns->key_dup(key) : key,
161 			fns->value_dup ? fns->value_dup(value) : value);
162 }
163 
g_hash_table_duplicate(GHashTable * src,GHashFunc hash,GEqualFunc equal,GDestroyNotify key_d,GDestroyNotify value_d,GDupFunc key_dup,GDupFunc value_dup)164 GHashTable *g_hash_table_duplicate(GHashTable *src, GHashFunc hash,
165 		GEqualFunc equal, GDestroyNotify key_d, GDestroyNotify value_d,
166 		GDupFunc key_dup, GDupFunc value_dup)
167 {
168 	GHashTable *dest = g_hash_table_new_full(hash, equal, key_d, value_d);
169 	struct duplicate_fns fns = {key_dup, value_dup, dest};
170 	g_hash_table_foreach(src, duplicate_values, &fns);
171 	return dest;
172 }
173 
gnt_boolean_handled_accumulator(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer dummy)174 gboolean gnt_boolean_handled_accumulator(GSignalInvocationHint *ihint,
175 				  GValue                *return_accu,
176 				  const GValue          *handler_return,
177 				  gpointer               dummy)
178 {
179 	gboolean continue_emission;
180 	gboolean signal_handled;
181 
182 	signal_handled = g_value_get_boolean (handler_return);
183 	g_value_set_boolean (return_accu, signal_handled);
184 	continue_emission = !signal_handled;
185 
186 	return continue_emission;
187 }
188 
gnt_widget_bindings_view(GntWidget * widget)189 GntWidget *gnt_widget_bindings_view(GntWidget *widget)
190 {
191 	return GNT_WIDGET(gnt_bindable_bindings_view(GNT_BINDABLE(widget)));
192 }
193 
194 #ifndef NO_LIBXML
195 static GntWidget *
gnt_widget_from_xmlnode(xmlNode * node,GntWidget ** data[],int max)196 gnt_widget_from_xmlnode(xmlNode *node, GntWidget **data[], int max)
197 {
198 	GntWidget *widget = NULL;
199 	char *name;
200 	char *id, *prop, *content;
201 	int val;
202 
203 	if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE)
204 		return NULL;
205 
206 	name = (char*)node->name;
207 	content = (char*)xmlNodeGetContent(node);
208 	if (strcmp(name + 1, "window") == 0 || strcmp(name + 1, "box") == 0) {
209 		xmlNode *ch;
210 		char *title;
211 		gboolean vert = (*name == 'v');
212 
213 		if (name[1] == 'w')
214 			widget = gnt_window_box_new(FALSE, vert);
215 		else
216 			widget = gnt_box_new(FALSE, vert);
217 
218 		title = (char*)xmlGetProp(node, (xmlChar*)"title");
219 		if (title) {
220 			gnt_box_set_title(GNT_BOX(widget), title);
221 			xmlFree(title);
222 		}
223 
224 		prop = (char*)xmlGetProp(node, (xmlChar*)"fill");
225 		if (prop) {
226 			if (sscanf(prop, "%d", &val) == 1)
227 				gnt_box_set_fill(GNT_BOX(widget), !!val);
228 			xmlFree(prop);
229 		}
230 
231 		prop = (char*)xmlGetProp(node, (xmlChar*)"align");
232 		if (prop) {
233 			if (sscanf(prop, "%d", &val) == 1)
234 				gnt_box_set_alignment(GNT_BOX(widget), val);
235 			xmlFree(prop);
236 		}
237 
238 		prop = (char*)xmlGetProp(node, (xmlChar*)"pad");
239 		if (prop) {
240 			if (sscanf(prop, "%d", &val) == 1)
241 				gnt_box_set_pad(GNT_BOX(widget), val);
242 			xmlFree(prop);
243 		}
244 
245 		for (ch = node->children; ch; ch=ch->next)
246 			gnt_box_add_widget(GNT_BOX(widget), gnt_widget_from_xmlnode(ch, data, max));
247 	} else if (strcmp(name, "button") == 0) {
248 		widget = gnt_button_new(content);
249 	} else if (strcmp(name, "label") == 0) {
250 		widget = gnt_label_new(content);
251 	} else if (strcmp(name, "entry") == 0) {
252 		widget = gnt_entry_new(content);
253 	} else if (strcmp(name, "combobox") == 0) {
254 		widget = gnt_combo_box_new();
255 	} else if (strcmp(name, "checkbox") == 0) {
256 		widget = gnt_check_box_new(content);
257 	} else if (strcmp(name, "tree") == 0) {
258 		widget = gnt_tree_new();
259 	} else if (strcmp(name, "textview") == 0) {
260 		widget = gnt_text_view_new();
261 	} else if (strcmp(name + 1, "line") == 0) {
262 		widget = gnt_line_new(*name == 'v');
263 	}
264 
265 	xmlFree(content);
266 
267 	if (widget == NULL) {
268 		gnt_warning("Invalid widget name %s", name);
269 		return NULL;
270 	}
271 
272 	id = (char*)xmlGetProp(node, (xmlChar*)"id");
273 	if (id) {
274 		int i;
275 		if (sscanf(id, "%d", &i) == 1 && i >= 0 && i < max) {
276 			*data[i] = widget;
277 			xmlFree(id);
278 		}
279 	}
280 
281 	prop = (char*)xmlGetProp(node, (xmlChar*)"border");
282 	if (prop) {
283 		int val;
284 		if (sscanf(prop, "%d", &val) == 1) {
285 			gnt_widget_set_has_border(widget, !!val);
286 		}
287 		xmlFree(prop);
288 	}
289 
290 	prop = (char*)xmlGetProp(node, (xmlChar*)"shadow");
291 	if (prop) {
292 		int val;
293 		if (sscanf(prop, "%d", &val) == 1) {
294 			gnt_widget_set_has_border(widget, !!val);
295 		}
296 		xmlFree(prop);
297 	}
298 
299 	return widget;
300 }
301 #endif
302 
gnt_util_parse_widgets(const char * string,int num,...)303 void gnt_util_parse_widgets(const char *string, int num, ...)
304 {
305 #ifndef NO_LIBXML
306 	xmlParserCtxtPtr ctxt;
307 	xmlDocPtr doc;
308 	xmlNodePtr node;
309 	va_list list;
310 	GntWidget ***data;
311 	int id;
312 
313 	ctxt = xmlNewParserCtxt();
314 	doc = xmlCtxtReadDoc(ctxt, (xmlChar*)string, NULL, NULL, XML_PARSE_NOBLANKS);
315 
316 	data = g_new0(GntWidget **, num);
317 
318 	va_start(list, num);
319 	for (id = 0; id < num; id++)
320 		data[id] = va_arg(list, gpointer);
321 
322 	node = xmlDocGetRootElement(doc);
323 	gnt_widget_from_xmlnode(node, data, num);
324 
325 	xmlFreeDoc(doc);
326 	xmlFreeParserCtxt(ctxt);
327 	va_end(list);
328 	g_free(data);
329 #endif
330 }
331 
332 #ifndef NO_LIBXML
333 static void
util_parse_html_to_tv(xmlNode * node,GntTextView * tv,GntTextFormatFlags flag)334 util_parse_html_to_tv(xmlNode *node, GntTextView *tv, GntTextFormatFlags flag)
335 {
336 	const char *name;
337 	char *content;
338 	xmlNode *ch;
339 	char *url = NULL;
340 	gboolean insert_nl_s = FALSE, insert_nl_e = FALSE;
341 
342 	if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE)
343 		return;
344 
345 	name = (char*)node->name;
346 	if (g_ascii_strcasecmp(name, "b") == 0 ||
347 		g_ascii_strcasecmp(name, "strong") == 0 ||
348 		g_ascii_strcasecmp(name, "i") == 0 ||
349 		g_ascii_strcasecmp(name, "blockquote") == 0) {
350 		flag |= GNT_TEXT_FLAG_BOLD;
351 	} else if (g_ascii_strcasecmp(name, "u") == 0) {
352 		flag |= GNT_TEXT_FLAG_UNDERLINE;
353 	} else if (g_ascii_strcasecmp(name, "br") == 0) {
354 		insert_nl_e = TRUE;
355 	} else if (g_ascii_strcasecmp(name, "a") == 0) {
356 		flag |= GNT_TEXT_FLAG_UNDERLINE;
357 		url = (char *)xmlGetProp(node, (xmlChar*)"href");
358 	} else if (g_ascii_strcasecmp(name, "h1") == 0 ||
359 			g_ascii_strcasecmp(name, "h2") == 0 ||
360 			g_ascii_strcasecmp(name, "h3") == 0 ||
361 			g_ascii_strcasecmp(name, "h4") == 0 ||
362 			g_ascii_strcasecmp(name, "h5") == 0 ||
363 			g_ascii_strcasecmp(name, "h6") == 0) {
364 		insert_nl_s = TRUE;
365 		insert_nl_e = TRUE;
366 	} else if (g_ascii_strcasecmp(name, "title") == 0) {
367 		insert_nl_s = TRUE;
368 		insert_nl_e = TRUE;
369 		flag |= GNT_TEXT_FLAG_BOLD | GNT_TEXT_FLAG_UNDERLINE;
370 	} else {
371 		/* XXX: Process other possible tags */
372 	}
373 
374 	if (insert_nl_s)
375 		gnt_text_view_append_text_with_flags(tv, "\n", flag);
376 
377 	for (ch = node->children; ch; ch = ch->next) {
378 		if (ch->type == XML_ELEMENT_NODE) {
379 			util_parse_html_to_tv(ch, tv, flag);
380 		} else if (ch->type == XML_TEXT_NODE) {
381 			content = (char*)xmlNodeGetContent(ch);
382 			gnt_text_view_append_text_with_flags(tv, content, flag);
383 			xmlFree(content);
384 		}
385 	}
386 
387 	if (url) {
388 		char *href = g_strdup_printf(" (%s)", url);
389 		gnt_text_view_append_text_with_flags(tv, href, flag);
390 		g_free(href);
391 		xmlFree(url);
392 	}
393 
394 	if (insert_nl_e)
395 		gnt_text_view_append_text_with_flags(tv, "\n", flag);
396 }
397 #endif
398 
gnt_util_parse_xhtml_to_textview(const char * string,GntTextView * tv)399 gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv)
400 {
401 #ifdef NO_LIBXML
402 	return FALSE;
403 #else
404 	xmlParserCtxtPtr ctxt;
405 	xmlDocPtr doc;
406 	xmlNodePtr node;
407 	GntTextFormatFlags flag = GNT_TEXT_FLAG_NORMAL;
408 	gboolean ret = FALSE;
409 
410 	ctxt = xmlNewParserCtxt();
411 	doc = xmlCtxtReadDoc(ctxt, (xmlChar*)string, NULL, NULL, XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
412 	if (doc) {
413 		node = xmlDocGetRootElement(doc);
414 		util_parse_html_to_tv(node, tv, flag);
415 		xmlFreeDoc(doc);
416 		ret = TRUE;
417 	}
418 	xmlFreeParserCtxt(ctxt);
419 	return ret;
420 #endif
421 }
422 
423 /* Setup trigger widget */
424 typedef struct {
425 	char *text;
426 	GntWidget *button;
427 } TriggerButton;
428 
429 static void
free_trigger_button(TriggerButton * b)430 free_trigger_button(TriggerButton *b)
431 {
432 	g_free(b->text);
433 	g_free(b);
434 }
435 
436 static gboolean
key_pressed(GntWidget * widget,const char * text,TriggerButton * trig)437 key_pressed(GntWidget *widget, const char *text, TriggerButton *trig)
438 {
439 	if (text && trig->text &&
440 			strcmp(text, trig->text) == 0) {
441 		gnt_widget_activate(trig->button);
442 		return TRUE;
443 	}
444 	return FALSE;
445 }
446 
gnt_util_set_trigger_widget(GntWidget * wid,const char * text,GntWidget * button)447 void gnt_util_set_trigger_widget(GntWidget *wid, const char *text, GntWidget *button)
448 {
449 	TriggerButton *tb = g_new0(TriggerButton, 1);
450 	tb->text = g_strdup(text);
451 	tb->button = button;
452 	g_signal_connect(G_OBJECT(wid), "key_pressed", G_CALLBACK(key_pressed), tb);
453 	g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(free_trigger_button), tb);
454 }
455