/* xul.c: XUL-parsing support for libRUIN * Copyright (C) 2007 Julian Graham * * libRUIN is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * libRUIN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with libRUIN; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include "css.h" #include "dialect.h" #include "layout.h" #include "scheme.h" #include "util.h" #include "window.h" #include "xul.h" void _ruin_generate_tree_parse_attrs(SCM doc, SCM node, SCM parent, ruin_element_t *t) { char *attr_str = NULL; char *attr_name = NULL; char *attr_ns = NULL; char *parent_name = NULL; SCM local_name_scm = SCM_EOL; SCM plocal_name_scm = SCM_EOL; SCM ns_name_scm = SCM_EOL; SCM attr_str_scm = SCM_EOL; if ((node == SCM_BOOL_F) || (t == NULL)) return; /* TODO: This is a hack -- we're just clobbering the parent's namespace at the moment... */ plocal_name_scm = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), parent, scm_makfrom0str("sdom:local-name")); if (plocal_name_scm == SCM_BOOL_F) { plocal_name_scm = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), parent, scm_makfrom0str("sdom:tag-name")); parent_name = scm_to_locale_string(plocal_name_scm); } else parent_name = scm_to_locale_string(plocal_name_scm); /* First we need to check the namespaces... */ local_name_scm = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:local-name")); if (local_name_scm == SCM_BOOL_F) { local_name_scm = scm_call_2 (scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:name")); attr_name = scm_to_locale_string(local_name_scm); } else { attr_name = scm_to_locale_string(local_name_scm); ns_name_scm = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:namespace-uri")); attr_ns = scm_to_locale_string(ns_name_scm); } attr_str_scm = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:value")); attr_str = scm_to_locale_string(attr_str_scm); if (attr_ns != NULL) { if (strcmp((char *) attr_ns, "http://www.w3.org/TR/CSS21") == 0) ruin_layout_add_style (&t->additional_attribute_style, attr_name, attr_str); /* This is where we register the various XUL event handlers... */ else if (strcmp((char *) attr_ns, "sdom") == 0) { /* The value of this attribute should be executable Scheme code. */ SCM event = SCM_EOL; if (strcmp((char *) attr_name, "DOMAttrModified") == 0) { } else if (strcmp((char *) attr_name, "DOMMenuItemActive") == 0) { } else if (strcmp((char *) attr_name, "DOMMenuItemInactive") == 0) { } else if (strcmp((char *) attr_name, "DOMMouseScroll") == 0) { } else if (strcmp((char *) attr_name, "DOMNodeInserted") == 0) { } else if (strcmp((char *) attr_name, "DOMNodeRemoved") == 0) { } else if (strcmp((char *) attr_name, "RadioStateChange") == 0) { } else if (strcmp((char *) attr_name, "onblur") == 0) { event = scm_makfrom0str("sdom:event-dom-focus-out"); } else if (strcmp((char *) attr_name, "onbroadcast") == 0) { if (strcmp(parent_name, "observes") == 0) event = scm_makfrom0str("sdom:event-dom-character-data-modified"); } else if (strcmp((char *) attr_name, "onchange") == 0) { } else if (strcmp((char *) attr_name, "onclick") == 0) { event = scm_makfrom0str("sdom:event-click"); } else if (strcmp((char *) attr_name, "onclose") == 0) { } else if (strcmp((char *) attr_name, "oncommand") == 0) { } else if (strcmp((char *) attr_name, "oncommandupdate") == 0) { } else if (strcmp((char *) attr_name, "oncontextmenu") == 0) { } else if (strcmp((char *) attr_name, "ondblclick") == 0) { } else if (strcmp((char *) attr_name, "ondragdrop") == 0) { } else if (strcmp((char *) attr_name, "ondragenter") == 0) { } else if (strcmp((char *) attr_name, "ondragexit") == 0) { } else if (strcmp((char *) attr_name, "ondraggesture") == 0) { } else if (strcmp((char *) attr_name, "ondragover") == 0) { } else if (strcmp((char *) attr_name, "onfocus") == 0) { event = scm_makfrom0str("sdom:event-dom-focus-out"); } else if (strcmp((char *) attr_name, "oninput") == 0) { event = scm_makfrom0str("sdom:event-text-input"); } else if (strcmp((char *) attr_name, "onkeydown") == 0) { event = scm_makfrom0str("sdom:event-keydown"); } else if (strcmp((char *) attr_name, "onkeypress") == 0) { } else if (strcmp((char *) attr_name, "onkeyup") == 0) { event = scm_makfrom0str("sdom:event-keyup"); } else if (strcmp((char *) attr_name, "onload") == 0) { if (strcmp(parent_name, "window") == 0) event = scm_makfrom0str("sdom:event-load"); } else if (strcmp((char *) attr_name, "onmousedown") == 0) { event = scm_makfrom0str("sdom:event-mousedown"); } else if (strcmp((char *) attr_name, "onmousemove") == 0) { event = scm_makfrom0str("sdom:event-mousemove"); } else if (strcmp((char *) attr_name, "onmouseout") == 0) { event = scm_makfrom0str("sdom:event-mouseout"); } else if (strcmp((char *) attr_name, "onmouseover") == 0) { event = scm_makfrom0str("sdom:event-mouseover"); } else if (strcmp((char *) attr_name, "onmouseup") == 0) { event = scm_makfrom0str("sdom:event-mouseup"); } else if (strcmp((char *) attr_name, "onoverflow") == 0) { event = scm_makfrom0str("sdom:event-error"); } else if (strcmp((char *) attr_name, "onoverflowchanged") == 0) { } else if (strcmp((char *) attr_name, "onpopuphidden") == 0) { } else if (strcmp((char *) attr_name, "onpopuphiding") == 0) { } else if (strcmp((char *) attr_name, "onpopupshowing") == 0) { } else if (strcmp((char *) attr_name, "onpopupshown") == 0) { } else if (strcmp((char *) attr_name, "onselect") == 0) { } else if (strcmp((char *) attr_name, "onunderflow") == 0) { event = scm_makfrom0str("sdom:event-resize"); } else if (strcmp((char *) attr_name, "onunload") == 0) { event = scm_makfrom0str("sdom:event-unload"); } if (event != SCM_EOL) { SCM handler = scm_c_eval_string(attr_str); if (scm_procedure_p(handler) == SCM_BOOL_T) scm_apply(scm_c_eval_string("sdom:add-event-listener!"), node, scm_list_4(event, scm_makfrom0str("default"), handler, SCM_BOOL_F)); else { } } } } else if (strcmp((char *) attr_name, "align") == 0) { if (strcmp(attr_str, "start") == 0) { ruin_layout_add_style(&t->additional_attribute_style, "align", "left"); ruin_layout_add_style (&t->additional_attribute_style, "vertical-align", "top"); } else if (strcmp(attr_str, "center") == 0) { ruin_layout_add_style(&t->additional_attribute_style, "align", "center"); ruin_layout_add_style (&t->additional_attribute_style, "vertical-align", "center"); } else if (strcmp(attr_str, "end") == 0) { ruin_layout_add_style(&t->additional_attribute_style, "align", "right"); ruin_layout_add_style(&t->additional_attribute_style, "vertical-align", "bottom"); } } else if (strcmp((char *) attr_name, "allowevents") == 0) { } else if (strcmp((char *) attr_name, "allownegativeassertions") == 0) { } else if (strcmp((char *) attr_name, "class") == 0) { } else if (strcmp((char *) attr_name, "coalesceduplicatearcs") == 0) { } else if (strcmp((char *) attr_name, "collapsed") == 0) { } else if (strcmp((char *) attr_name, "container") == 0) { } else if (strcmp((char *) attr_name, "containment") == 0) { } else if (strcmp((char *) attr_name, "context") == 0) { } else if (strcmp((char *) attr_name, "contextmenu") == 0) { } else if (strcmp((char *) attr_name, "datasources") == 0) { } else if (strcmp((char *) attr_name, "debug") == 0) { } else if (strcmp((char *) attr_name, "dir") == 0) { } else if (strcmp((char *) attr_name, "empty") == 0) { } else if (strcmp((char *) attr_name, "equalsize") == 0) { } else if (strcmp((char *) attr_name, "flags") == 0) { } else if (strcmp((char *) attr_name, "flex") == 0) { } else if (strcmp((char *) attr_name, "height") == 0) { ruin_layout_add_style(&t->additional_attribute_style, "height", attr_str); } else if (strcmp((char *) attr_name, "hidden") == 0) { } else if (strcmp((char *) attr_name, "id") == 0) { if (strlen(attr_str) > 0) { free(t->id); t->id = strdup(attr_str); ruin_util_hash_insert(t->ids, attr_str, t); } } else if (strcmp((char *) attr_name, "insertafter") == 0) { } else if (strcmp((char *) attr_name, "inserbefore") == 0) { } else if (strcmp((char *) attr_name, "left") == 0) { } else if (strcmp((char *) attr_name, "maxheight") == 0) { int max_height = 0; int attr_str_len = strlen((char *) attr_str); if (sscanf((char *) attr_str, "%d", &max_height) == 1) { t->max_height.computed = max_height; t->max_height.units = RUIN_LAYOUT_UNITS_CHARS; if ((attr_str_len > 1) && (strcmp(attr_str + attr_str_len - 1, "%") == 0)) t->max_height.units = RUIN_LAYOUT_UNITS_PERCENT; } } else if (strcmp((char *) attr_name, "maxwidth") == 0) { int max_width = 0; int attr_str_len = strlen((char *) attr_str); if (sscanf((char *) attr_str, "%d", &max_width) == 1) { t->max_width.computed = max_width; t->max_width.units = RUIN_LAYOUT_UNITS_CHARS; if ((attr_str_len > 1) && (strcmp(attr_str + attr_str_len - 1, "%") == 0)) t->max_width.units = RUIN_LAYOUT_UNITS_PERCENT; } } else if (strcmp((char *) attr_name, "menu") == 0) { } else if (strcmp((char *) attr_name, "minheight") == 0) { } else if (strcmp((char *) attr_name, "minwidth") == 0) { } else if (strcmp((char *) attr_name, "mousethrough") == 0) { } else if (strcmp((char *) attr_name, "observes") == 0) { } else if (strcmp((char *) attr_name, "ordinal") == 0) { } else if (strcmp((char *) attr_name, "orient") == 0) { if (strcmp(attr_str, "horizontal") == 0) { ruin_layout_add_style (&t->additional_attribute_style, "display", "table"); } else if (strcmp(attr_str, "vertical") == 0) { ruin_layout_add_style (&t->additional_attribute_style, "display", "block"); } } else if (strcmp((char *) attr_name, "pack") == 0) { } else if (strcmp((char *) attr_name, "persist") == 0) { } else if (strcmp((char *) attr_name, "popup") == 0) { } else if (strcmp((char *) attr_name, "position") == 0) { } else if (strcmp((char *) attr_name, "ref") == 0) { } else if (strcmp((char *) attr_name, "removeelement") == 0) { } else if (strcmp((char *) attr_name, "sortDirection") == 0) { } else if (strcmp((char *) attr_name, "sortResource") == 0) { } else if (strcmp((char *) attr_name, "sortResource2") == 0) { } else if (strcmp((char *) attr_name, "statustext") == 0) { } else if (strcmp((char *) attr_name, "style") == 0) { } else if (strcmp((char *) attr_name, "template") == 0) { } else if (strcmp((char *) attr_name, "tooltip") == 0) { } else if (strcmp((char *) attr_name, "tooltiptext") == 0) { } else if (strcmp((char *) attr_name, "top") == 0) { } else if (strcmp((char *) attr_name, "uri") == 0) { } else if (strcmp((char *) attr_name, "wait-cursor") == 0) { } else if (strcmp((char *) attr_name, "width") == 0) { ruin_layout_add_style(&t->additional_attribute_style, "width", attr_str); } /* These attributes are specific to certain elements. */ else if (strcmp((char *) parent_name, "label") == 0) { if (strcmp((char *) attr_name, "value") == 0) { t->content = strdup(attr_str); } } else if (strcmp((char *) attr_name, "accesskey") == 0) { } else if (strcmp((char *) attr_name, "autoCheck") == 0) { } else if (strcmp((char *) attr_name, "checked") == 0) { } else if (strcmp((char *) attr_name, "command") == 0) { } else if (strcmp((char *) attr_name, "crop") == 0) { } else if (strcmp((char *) attr_name, "dir") == 0) { } else if (strcmp((char *) attr_name, "disabled") == 0) { } else if (strcmp((char *) attr_name, "dlgtype") == 0) { } else if (strcmp((char *) attr_name, "group") == 0) { } else if (strcmp((char *) attr_name, "image") == 0) { } else if (strcmp((char *) attr_name, "label") == 0) { if ((strcmp((char *) parent_name, "button") == 0) || (strcmp((char *) parent_name, "caption") == 0) || (strcmp((char *) parent_name, "menuitem") == 0) || (strcmp((char *) parent_name, "radio") == 0)) t->first_child = ruin_dialect_generate_text_node(node, t, NULL); } else if (strcmp((char *) attr_name, "open") == 0) { } else if (strcmp((char *) attr_name, "orient") == 0) { } else if (strcmp((char *) attr_name, "selected") == 0) { if (strcmp((char *) parent_name, "menuitem") == 0) { t->selected = TRUE; } } else if (strcmp((char *) attr_name, "size") == 0) { if (strcmp((char *) parent_name, "textbox") == 0) { int width = 0; int attr_str_len = strlen((char *) attr_str); if (sscanf((char *) attr_str, "%d", &width) == 1) { t->width.computed = width; t->width.units = RUIN_LAYOUT_UNITS_CHARS; if ((attr_str_len > 1) && (strcmp(attr_str + attr_str_len - 1, "%") == 0)) t->width.units = RUIN_LAYOUT_UNITS_PERCENT; } } } else if (strcmp((char *) attr_name, "tabindex") == 0) { /* Ooh, this one is interesting. We need to check the current tab index -- if it's changed, we need to adjust this guy's position in the window's tab list. Searching for, removing, and adding this pointer could certainly be optimized. */ int new_value; if ((t->focusable) && (sscanf((char *) attr_str, "%d", &new_value) == 1) && (new_value != t->tab_index)) { ruin_dialect_update_tab_position(t, new_value); t->tab_index = new_value; } } else if (strcmp((char *) attr_name, "type") == 0) { } scm_remember_upto_here_1(local_name_scm); scm_remember_upto_here_1(plocal_name_scm); scm_remember_upto_here_1(ns_name_scm); scm_remember_upto_here_1(attr_str_scm); } void ruin_xul_generate_tree_parse_attrs(ruin_element_t *t) { SCM list_ptr = SCM_EOL; scm_gc_unprotect_object(t->attributes); t->attributes = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), t->element, scm_makfrom0str("sdom:attributes")); scm_gc_protect_object(t->attributes); list_ptr = t->attributes; while (list_ptr != SCM_EOL) { _ruin_generate_tree_parse_attrs(t->doc, SCM_CAR(list_ptr), t->element, t); list_ptr = SCM_CDR(list_ptr); } return; } ruin_element_t *ruin_xul_generate_tree(ruin_window_t *w, SCM node, ruin_element_t *parent, ruin_element_t *sibling) { int not_sibling = FALSE; ruin_element_t *children_result = NULL; ruin_element_t *my_result = NULL; SCM node_next = SCM_EOL; SCM node_type = SCM_EOL; if (node == SCM_BOOL_F) return NULL; node_next = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:next-sibling")); node_type = scm_call_1(scm_c_eval_string("sdom:node-type"), node); if (scm_eqv_p(scm_c_eval_string("sdom:node-type-document"), node_type) == SCM_BOOL_T) return ruin_xul_generate_tree (w, scm_call_2(scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:document-element")), parent, NULL); /* If this is a processing-instruction node, we need to try to retrieve a potential CSS stylesheet and apply it to our document. */ if (scm_eqv_p(scm_c_eval_string("sdom:node-type-processing-instruction"), node_type) == SCM_BOOL_T) { } /* Otherwise, we should check for any of the user-specified parts of the CSS cascade; this means looking for stuff attached to this node that's in the "css" namespace. If there is no namespace, we should treat this as a XUL attribute and add it accordingly. */ else if (scm_eqv_p(scm_c_eval_string("sdom:node-type-text"), node_type) == SCM_BOOL_T) my_result = ruin_dialect_generate_text_node(node, parent, sibling); else if (scm_eqv_p(scm_c_eval_string("sdom:node-type-element"), node_type) == SCM_BOOL_T) { char *node_name = ruin_dialect_get_node_name(node); my_result = ruin_element_new(); my_result->dialect = parent->dialect; my_result->cascade = parent->cascade; my_result->doc = parent->doc; my_result->parent = parent; my_result->parent_window = my_result->parent->parent_window; ruin_layout_add_style (&my_result->inherent_attribute_style, "display", "block"); if (my_result->parent->parent == NULL) { my_result->parent_window->top = my_result; } my_result->element = node; scm_hashq_set_x (my_result->parent_window->scm_hash, node, scm_makfrom0str(ruin_util_ptr_to_string((void *) my_result))); my_result->ids = (parent != NULL) ? parent->ids : NULL; my_result->prev_sibling = sibling; if (strcmp((char *) node_name, "button") == 0) my_result->focusable = TRUE; else if (strcmp((char *) node_name, "checkbox") == 0) { my_result->focusable = TRUE; my_result->extra_content = RUIN_LAYOUT_EXTRA_CONTENT_CHECKBOX; } else if (strcmp((char *) node_name, "colorpicker") == 0) { my_result->focusable = TRUE; my_result->extra_content = RUIN_LAYOUT_EXTRA_CONTENT_COLORPICKER; } else if (strcmp((char *) node_name, "radio") == 0) { my_result->focusable = TRUE; my_result->extra_content = RUIN_LAYOUT_EXTRA_CONTENT_RADIO; } else if (strcmp((char *) node_name, "textbox") == 0) { my_result->focusable = TRUE; my_result->editable = RUIN_LAYOUT_EDITABLE_TYPE_SINGLE_LINE; } /* Insert in a preliminary position in the tab ordering and add the default handler for focus-switches... */ if (my_result->focusable) { ruin_dialect_update_tab_position(my_result, 0); } my_result->attributes = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:attributes")); scm_gc_protect_object(my_result->attributes); ruin_xul_generate_tree_parse_attrs(my_result); /* Looking at one of them should take care of all non-attribute children. */ { SCM children = scm_call_2(scm_c_eval_string("sdom:get-dom-property"), node, scm_makfrom0str("sdom:child-nodes")); if (children != SCM_BOOL_F) { children_result = ruin_xul_generate_tree (w, SCM_CAR(children), my_result, NULL); my_result->first_child = children_result; } } /* For some elements, we may need to do some post-processing... */ if (strcmp((char *) node_name, "grid") == 0) { } else if (strcmp((char *) node_name, "menulist") == 0) { /* Is there a menupopup? (DANGER: ASSUMING THE CHILD OF MENULIST WILL ALWAYS BE A MENUPOPUP! Does this menupopup have any menuitems? (DANGER: ASSUMING THE CHILD OF MENUPOPUP WILL ALWAY BE A MENUITEM! If yes, then scan the menupopup for a 'selected' menuitem -- set the content of the menulist to the first selected menuitem or the first menuitem if none are selected. */ if ((my_result->first_child != NULL) && (my_result->first_child->first_child != NULL)) { int found_selected = FALSE; ruin_element_t *ptr = my_result->first_child->first_child; do { if (ptr->selected) { found_selected = TRUE; break; } ptr = ptr->next_sibling; } while (ptr != NULL); if (found_selected) { if (ptr->content != NULL) my_result->content = strdup(ptr->content); } else my_result->content = strdup(my_result->first_child->first_child->content); } } else if (strcmp((char *) node_name, "textbox") == 0) { if (my_result->editable == RUIN_LAYOUT_EDITABLE_TYPE_SINGLE_LINE) if (my_result->min_height.computed == RUIN_LAYOUT_VALUE_AUTO) my_result->min_height.computed = 3; } } ruin_dialect_add_table_parents(my_result); ruin_dialect_add_table_children(my_result); ruin_dialect_add_table_columns(my_result); if ((my_result != NULL) && (!not_sibling)) { ruin_element_t *return_ptr = my_result; ruin_element_t *parent_ptr = my_result->parent; ruin_util_hash_insert (w->internal_ids, ruin_util_long_to_string(my_result->internal_id), ruin_util_ptr_to_string((void *) my_result)); if ((parent_ptr != my_result->parent) && (strcmp(ruin_css_lookup(my_result->parent, "display", NULL), "table-cell") == 0)) { /* If we've inserted a cell, we don't want to pass the cell as the parent pointer because then all the siblings will go into the same cell. Also, we need to set the sibling pointer to be the newly- inserted cell element. */ parent_ptr = my_result->parent->parent; my_result->parent->next_sibling = ruin_xul_generate_tree(w, node_next, parent_ptr, my_result->parent); } else { my_result->next_sibling = ruin_xul_generate_tree(w, node_next, parent_ptr, my_result); } return return_ptr; } else return ruin_xul_generate_tree(w, node_next, parent, sibling); }