finch/libgnt/gntutils.c

changeset 39360
e7bed293aad5
parent 39302
64aabebb476b
child 39361
a1068caa3600
equal deleted inserted replaced
39302:64aabebb476b 39360:e7bed293aad5
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 "gntinternal.h"
24 #undef GNT_LOG_DOMAIN
25 #define GNT_LOG_DOMAIN "Utils"
26
27 #include "gntbutton.h"
28 #include "gntcheckbox.h"
29 #include "gntcombobox.h"
30 #include "gntentry.h"
31 #include "gntlabel.h"
32 #include "gntline.h"
33 #include "gnttextview.h"
34 #include "gnttree.h"
35 #include "gntutils.h"
36 #include "gntwindow.h"
37
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #ifndef NO_LIBXML
43 #include <libxml/parser.h>
44 #include <libxml/tree.h>
45 #endif
46
47 void gnt_util_get_text_bound(const char *text, int *width, int *height)
48 {
49 const char *s = text, *last;
50 int count = 1, max = 0;
51 int len;
52
53 /* XXX: ew ... everyone look away */
54 last = s;
55 if (s)
56 {
57 while (*s)
58 {
59 if (*s == '\n' || *s == '\r')
60 {
61 count++;
62 len = gnt_util_onscreen_width(last, s);
63 if (max < len)
64 max = len;
65 last = s + 1;
66 }
67 s = g_utf8_next_char(s);
68 }
69
70 len = gnt_util_onscreen_width(last, s);
71 if (max < len)
72 max = len;
73 }
74
75 if (height)
76 *height = count;
77 if (width)
78 *width = max + (count > 1);
79 }
80
81 int gnt_util_onscreen_width(const char *start, const char *end)
82 {
83 int width = 0;
84
85 if (end == NULL)
86 end = start + strlen(start);
87
88 while (start < end) {
89 width += g_unichar_iswide(g_utf8_get_char(start)) ? 2 : 1;
90 start = g_utf8_next_char(start);
91 }
92 return width;
93 }
94
95 const char *gnt_util_onscreen_width_to_pointer(const char *string, int len, int *w)
96 {
97 int size;
98 int width = 0;
99 const char *str = string;
100
101 if (len <= 0) {
102 len = gnt_util_onscreen_width(string, NULL);
103 }
104
105 while (width < len && *str) {
106 size = g_unichar_iswide(g_utf8_get_char(str)) ? 2 : 1;
107 if (width + size > len)
108 break;
109 str = g_utf8_next_char(str);
110 width += size;
111 }
112 if (w)
113 *w = width;
114 return str;
115 }
116
117 char *gnt_util_onscreen_fit_string(const char *string, int maxw)
118 {
119 const char *start, *end;
120 GString *str;
121
122 if (maxw <= 0)
123 maxw = getmaxx(stdscr) - 4;
124
125 start = string;
126 str = g_string_new(NULL);
127
128 while (*start) {
129 if ((end = strchr(start, '\n')) != NULL ||
130 (end = strchr(start, '\r')) != NULL) {
131 if (gnt_util_onscreen_width(start, end) > maxw)
132 end = NULL;
133 }
134 if (end == NULL)
135 end = gnt_util_onscreen_width_to_pointer(start, maxw, NULL);
136 str = g_string_append_len(str, start, end - start);
137 if (*end) {
138 str = g_string_append_c(str, '\n');
139 if (*end == '\n' || *end == '\r')
140 end++;
141 }
142 start = end;
143 }
144 return g_string_free(str, FALSE);
145 }
146
147 struct duplicate_fns
148 {
149 GntDuplicateFunc key_dup;
150 GntDuplicateFunc value_dup;
151 GHashTable *table;
152 };
153
154 static void
155 duplicate_values(gpointer key, gpointer value, gpointer data)
156 {
157 struct duplicate_fns *fns = data;
158 g_hash_table_insert(fns->table, fns->key_dup ? fns->key_dup(key) : key,
159 fns->value_dup ? fns->value_dup(value) : value);
160 }
161
162 GHashTable *gnt_hash_table_duplicate(GHashTable *src, GHashFunc hash,
163 GEqualFunc equal, GDestroyNotify key_d, GDestroyNotify value_d,
164 GntDuplicateFunc key_dup, GntDuplicateFunc value_dup)
165 {
166 GHashTable *dest = g_hash_table_new_full(hash, equal, key_d, value_d);
167 struct duplicate_fns fns = {key_dup, value_dup, dest};
168 g_hash_table_foreach(src, duplicate_values, &fns);
169 return dest;
170 }
171
172 gboolean gnt_boolean_handled_accumulator(GSignalInvocationHint *ihint,
173 GValue *return_accu,
174 const GValue *handler_return,
175 gpointer dummy)
176 {
177 gboolean continue_emission;
178 gboolean signal_handled;
179
180 signal_handled = g_value_get_boolean (handler_return);
181 g_value_set_boolean (return_accu, signal_handled);
182 continue_emission = !signal_handled;
183
184 return continue_emission;
185 }
186
187 typedef struct {
188 GHashTable *hash;
189 GntTree *tree;
190 } BindingView;
191
192 static void
193 add_binding(gpointer key, gpointer value, gpointer data)
194 {
195 BindingView *bv = data;
196 GntBindableActionParam *act = value;
197 const char *name = g_hash_table_lookup(bv->hash, act->action);
198 if (name && *name) {
199 const char *k = gnt_key_lookup(key);
200 if (!k)
201 k = key;
202 gnt_tree_add_row_after(bv->tree, (gpointer)k,
203 gnt_tree_create_row(bv->tree, k, name), NULL, NULL);
204 }
205 }
206
207 static void
208 add_action(gpointer key, gpointer value, gpointer data)
209 {
210 BindingView *bv = data;
211 g_hash_table_insert(bv->hash, value, key);
212 }
213
214 GntWidget *gnt_widget_bindings_view(GntWidget *widget)
215 {
216 GntBindable *bind = GNT_BINDABLE(widget);
217 GntWidget *tree = gnt_tree_new_with_columns(2);
218 GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bind));
219 GHashTable *hash = g_hash_table_new(g_direct_hash, g_direct_equal);
220 BindingView bv = {hash, GNT_TREE(tree)};
221
222 gnt_tree_set_compare_func(bv.tree, (GCompareFunc)g_utf8_collate);
223 g_hash_table_foreach(klass->actions, add_action, &bv);
224 g_hash_table_foreach(klass->bindings, add_binding, &bv);
225 if (GNT_TREE(tree)->list == NULL) {
226 gnt_widget_destroy(tree);
227 tree = NULL;
228 } else
229 gnt_tree_adjust_columns(bv.tree);
230 g_hash_table_destroy(hash);
231
232 return tree;
233 }
234
235 #ifndef NO_LIBXML
236 static GntWidget *
237 gnt_widget_from_xmlnode(xmlNode *node, GntWidget **data[], int max)
238 {
239 GntWidget *widget = NULL;
240 char *name;
241 char *id, *prop, *content;
242 int val;
243
244 if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE)
245 return NULL;
246
247 name = (char*)node->name;
248 content = (char*)xmlNodeGetContent(node);
249 if (strcmp(name + 1, "window") == 0 || strcmp(name + 1, "box") == 0) {
250 xmlNode *ch;
251 char *title;
252 gboolean vert = (*name == 'v');
253
254 if (name[1] == 'w')
255 widget = gnt_window_box_new(FALSE, vert);
256 else
257 widget = gnt_box_new(FALSE, vert);
258
259 title = (char*)xmlGetProp(node, (xmlChar*)"title");
260 if (title) {
261 gnt_box_set_title(GNT_BOX(widget), title);
262 xmlFree(title);
263 }
264
265 prop = (char*)xmlGetProp(node, (xmlChar*)"fill");
266 if (prop) {
267 if (sscanf(prop, "%d", &val) == 1)
268 gnt_box_set_fill(GNT_BOX(widget), !!val);
269 xmlFree(prop);
270 }
271
272 prop = (char*)xmlGetProp(node, (xmlChar*)"align");
273 if (prop) {
274 if (sscanf(prop, "%d", &val) == 1)
275 gnt_box_set_alignment(GNT_BOX(widget), val);
276 xmlFree(prop);
277 }
278
279 prop = (char*)xmlGetProp(node, (xmlChar*)"pad");
280 if (prop) {
281 if (sscanf(prop, "%d", &val) == 1)
282 gnt_box_set_pad(GNT_BOX(widget), val);
283 xmlFree(prop);
284 }
285
286 for (ch = node->children; ch; ch=ch->next)
287 gnt_box_add_widget(GNT_BOX(widget), gnt_widget_from_xmlnode(ch, data, max));
288 } else if (strcmp(name, "button") == 0) {
289 widget = gnt_button_new(content);
290 } else if (strcmp(name, "label") == 0) {
291 widget = gnt_label_new(content);
292 } else if (strcmp(name, "entry") == 0) {
293 widget = gnt_entry_new(content);
294 } else if (strcmp(name, "combobox") == 0) {
295 widget = gnt_combo_box_new();
296 } else if (strcmp(name, "checkbox") == 0) {
297 widget = gnt_check_box_new(content);
298 } else if (strcmp(name, "tree") == 0) {
299 widget = gnt_tree_new();
300 } else if (strcmp(name, "textview") == 0) {
301 widget = gnt_text_view_new();
302 } else if (strcmp(name + 1, "line") == 0) {
303 widget = gnt_line_new(*name == 'v');
304 }
305
306 xmlFree(content);
307
308 if (widget == NULL) {
309 gnt_warning("Invalid widget name %s", name);
310 return NULL;
311 }
312
313 id = (char*)xmlGetProp(node, (xmlChar*)"id");
314 if (id) {
315 int i;
316 if (sscanf(id, "%d", &i) == 1 && i >= 0 && i < max) {
317 *data[i] = widget;
318 xmlFree(id);
319 }
320 }
321
322 prop = (char*)xmlGetProp(node, (xmlChar*)"border");
323 if (prop) {
324 int val;
325 if (sscanf(prop, "%d", &val) == 1) {
326 if (val)
327 GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_NO_BORDER);
328 else
329 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER);
330 }
331 xmlFree(prop);
332 }
333
334 prop = (char*)xmlGetProp(node, (xmlChar*)"shadow");
335 if (prop) {
336 int val;
337 if (sscanf(prop, "%d", &val) == 1) {
338 if (val)
339 GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_NO_BORDER);
340 else
341 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER);
342 }
343 xmlFree(prop);
344 }
345
346 return widget;
347 }
348 #endif
349
350 void gnt_util_parse_widgets(const char *string, int num, ...)
351 {
352 #ifndef NO_LIBXML
353 xmlParserCtxtPtr ctxt;
354 xmlDocPtr doc;
355 xmlNodePtr node;
356 va_list list;
357 GntWidget ***data;
358 int id;
359
360 ctxt = xmlNewParserCtxt();
361 doc = xmlCtxtReadDoc(ctxt, (xmlChar*)string, NULL, NULL, XML_PARSE_NOBLANKS);
362
363 data = g_new0(GntWidget **, num);
364
365 va_start(list, num);
366 for (id = 0; id < num; id++)
367 data[id] = va_arg(list, gpointer);
368
369 node = xmlDocGetRootElement(doc);
370 gnt_widget_from_xmlnode(node, data, num);
371
372 xmlFreeDoc(doc);
373 xmlFreeParserCtxt(ctxt);
374 va_end(list);
375 g_free(data);
376 #endif
377 }
378
379 #ifndef NO_LIBXML
380 static void
381 util_parse_html_to_tv(xmlNode *node, GntTextView *tv, GntTextFormatFlags flag)
382 {
383 const char *name;
384 char *content;
385 xmlNode *ch;
386 char *url = NULL;
387 gboolean insert_nl_s = FALSE, insert_nl_e = FALSE;
388
389 if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE)
390 return;
391
392 name = (char*)node->name;
393 if (g_ascii_strcasecmp(name, "b") == 0 ||
394 g_ascii_strcasecmp(name, "strong") == 0 ||
395 g_ascii_strcasecmp(name, "i") == 0 ||
396 g_ascii_strcasecmp(name, "blockquote") == 0) {
397 flag |= GNT_TEXT_FLAG_BOLD;
398 } else if (g_ascii_strcasecmp(name, "u") == 0) {
399 flag |= GNT_TEXT_FLAG_UNDERLINE;
400 } else if (g_ascii_strcasecmp(name, "br") == 0) {
401 insert_nl_e = TRUE;
402 } else if (g_ascii_strcasecmp(name, "a") == 0) {
403 flag |= GNT_TEXT_FLAG_UNDERLINE;
404 url = (char *)xmlGetProp(node, (xmlChar*)"href");
405 } else if (g_ascii_strcasecmp(name, "h1") == 0 ||
406 g_ascii_strcasecmp(name, "h2") == 0 ||
407 g_ascii_strcasecmp(name, "h3") == 0 ||
408 g_ascii_strcasecmp(name, "h4") == 0 ||
409 g_ascii_strcasecmp(name, "h5") == 0 ||
410 g_ascii_strcasecmp(name, "h6") == 0) {
411 insert_nl_s = TRUE;
412 insert_nl_e = TRUE;
413 } else if (g_ascii_strcasecmp(name, "title") == 0) {
414 insert_nl_s = TRUE;
415 insert_nl_e = TRUE;
416 flag |= GNT_TEXT_FLAG_BOLD | GNT_TEXT_FLAG_UNDERLINE;
417 } else {
418 /* XXX: Process other possible tags */
419 }
420
421 if (insert_nl_s)
422 gnt_text_view_append_text_with_flags(tv, "\n", flag);
423
424 for (ch = node->children; ch; ch = ch->next) {
425 if (ch->type == XML_ELEMENT_NODE) {
426 util_parse_html_to_tv(ch, tv, flag);
427 } else if (ch->type == XML_TEXT_NODE) {
428 content = (char*)xmlNodeGetContent(ch);
429 gnt_text_view_append_text_with_flags(tv, content, flag);
430 xmlFree(content);
431 }
432 }
433
434 if (url) {
435 char *href = g_strdup_printf(" (%s)", url);
436 gnt_text_view_append_text_with_flags(tv, href, flag);
437 g_free(href);
438 xmlFree(url);
439 }
440
441 if (insert_nl_e)
442 gnt_text_view_append_text_with_flags(tv, "\n", flag);
443 }
444 #endif
445
446 gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv)
447 {
448 #ifdef NO_LIBXML
449 return FALSE;
450 #else
451 xmlParserCtxtPtr ctxt;
452 xmlDocPtr doc;
453 xmlNodePtr node;
454 GntTextFormatFlags flag = GNT_TEXT_FLAG_NORMAL;
455 gboolean ret = FALSE;
456
457 ctxt = xmlNewParserCtxt();
458 doc = xmlCtxtReadDoc(ctxt, (xmlChar*)string, NULL, NULL, XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
459 if (doc) {
460 node = xmlDocGetRootElement(doc);
461 util_parse_html_to_tv(node, tv, flag);
462 xmlFreeDoc(doc);
463 ret = TRUE;
464 }
465 xmlFreeParserCtxt(ctxt);
466 return ret;
467 #endif
468 }
469
470 /* Setup trigger widget */
471 typedef struct {
472 char *text;
473 GntWidget *button;
474 } TriggerButton;
475
476 static void
477 free_trigger_button(TriggerButton *b)
478 {
479 g_free(b->text);
480 g_free(b);
481 }
482
483 static gboolean
484 key_pressed(GntWidget *widget, const char *text, TriggerButton *trig)
485 {
486 if (text && trig->text &&
487 strcmp(text, trig->text) == 0) {
488 gnt_widget_activate(trig->button);
489 return TRUE;
490 }
491 return FALSE;
492 }
493
494 void gnt_util_set_trigger_widget(GntWidget *wid, const char *text, GntWidget *button)
495 {
496 TriggerButton *tb = g_new0(TriggerButton, 1);
497 tb->text = g_strdup(text);
498 tb->button = button;
499 g_signal_connect(G_OBJECT(wid), "key_pressed", G_CALLBACK(key_pressed), tb);
500 g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(free_trigger_button), tb);
501 }

mercurial