| 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 } |
|