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