/* * SPL - The SPL Programming Language * Copyright (C) 2004, 2005 Clifford Wolf * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * mod_wsf_dialog.spl: WSF Dialog Component */ /** * A module which implements a "Qt Designer"-like component for creating WSF * dialogs in a web browser. */ load "wsf"; load "format_xml"; load "encode_xml"; /** * A WSF component for creating simple WSF components in a web browser. * * A Dialog is simply a collection of form elements with small code snippets, * which are automatically executed when the content of the element has been * modified (or the button been pushed). * * Such a dialog can be dumped as XML and later loaded again. * * If the "save" button should be functional, you eighter need to derive your * own object from this one or overwrite the method name after instanciating * this object. * * Derived from [[wsf:WsfComponent]]. */ object WsfDialog WsfComponent { /** * The configuration of the dialog, in the format described in * [[xml:format_xml_parse()]]. * * All changes are done directly in this data structrue. So there is * no need of calling any kind of convertation function in order to * keep this structure up to date. * * Simply call [[xml:format_xml_dump()]] on this variable to create an * XML file for this dialog (usually this is done in an overloaded * [[.save()]] method. */ var xmltree; /** * The current values of the elements. If there is an input box * "foobar", "values.foobar" can be used to read and modify the value * of that input box. */ var values; /** * When this variable is set to '1', the dialog is currently in edit * mode. Usually applications only allow privelidged users to switch * to edit mode. * * The dirty variable ([[wsf:WsfComponent.dirty]]) must be set when * this variable is changed. Usually the method [[.set_edit_mode()]] is * used to set this variable. */ var edit_mode = 0; /** * The name of the element which is open in the editor right now. If * this is set to "", the screen with the generic dialog options is * displayed. * * Usually this is only set internally by [[.main()]] when the user * changes the current element in the web frontend. */ var edit_current = ""; /** * The frontend allows displaying the current XML tree so it can e.g. * be copy&pasted to a file. This variable is set to '1' when displaying * of the XML tree is currently active. */ var edit_show_xml = 0; /** * Set this to '1' if you want this component to add an 'edit' button * to the component. With this button, the user can switch to the editor * view at any time. This should be used with care since someone with * access to the editor may access any SPL code! Only privelidged * users show have access to this feature. */ var show_edit_button = 0; /** * Call this function if you want to switch this component to edit mode * (parameter = '1') or back to executing the dialog (parameter = '0'). */ method set_edit_mode(newmode) { if ( newmode != edit_mode ) { edit_mode = newmode; edit_current = ""; edit_show_xml = 0; dirty = 1; } } method __WsfDialog_get_html_element(element) { var onchange = << EOT: :document.${id}_f.el.value = '${element.["A:name"]}'; :submit(); return true; EOT; if ( element.["A:type"] ~== "text" ) { return << EOT: : EOT; } else if ( element.["A:type"] ~== "textarea" ) { return << EOT: : EOT; } else if ( element.["A:type"] ~== "button" ) { return << EOT: : EOT; } else if ( element.["A:type"] ~== "label" ) { return values.[element.["A:name"]]; } return ""; } method __WsfDialog_get_html_editor() { var d = xmltree.["E0:wsfdialog"]; var html = ""; html ~= '\n'; html ~= '\n'; if ( edit_current ~== "" ) { html ~= << EOT: :

: :
New Element:
:  :

:

: : : :
Width:cells
Height:cells
Step:pixel/cell

:

Init code:
:

:

EOT; } else { var e = d.[edit_current]; var set_type = ''; html ~= << EOT: :

: : : : : : : :
Name:text
Pos. X:cells
Pos. Y:cells
Width:cells
Height:cells
Type:$set_typetype
Value:text

:

Action code:
:

:

EOT; } return html; } /** * Overloaded [[wsf:WsfComponent.get_html()]]. */ method get_html() { var html = ""; var d = xmltree.["E0:wsfdialog"]; html ~= << EOT: :
: : EOT; if ( edit_mode ) html ~= '
\n'; else html ~= '\n'; if (edit_mode ) html ~= < :var ${id}_grab = ""; :function ${id}_clickhdl(x, y) { : if ( ${id}_grab == "wh" ) { : window.document.${id}_f.set_width.value = x- : window.document.${id}_f.set_posx.value+1; : window.document.${id}_f.set_height.value = y- : window.document.${id}_f.set_posy.value+1; : window.document.${id}_f.submit(); : } : if ( ${id}_grab == "xy" ) { : window.document.${id}_f.set_posx.value = x; : window.document.${id}_f.set_posy.value = y; : window.document.${id}_f.submit(); : } : ${id}_grab = ""; :} : EOT; html ~= << EOT: : EOT; var m; foreach f (d) { if ( f =~ /^E\d+:field/ ) { var entry; entry.["type"] = "element"; entry.["link"] = d.[f]; entry.["ln_x"] = d.[f].["A:width"]; entry.["ln_y"] = d.[f].["A:height"]; entry.["done"] = 0; for (var y = d.[f].["A:posy"]; y < d.[f].["A:posy"]+d.[f].["A:height"]; y++) for (var x = d.[f].["A:posx"]; x < d.[f].["A:posx"]+d.[f].["A:width"]; x++) if (not declared m.[x].[y]) m.[x].[y] = entry; } } for (var y=1; y <= d.["A:height"]; y++) for (var x=1; x <= d.["A:width"]; x++) if (not declared m.[x].[y]) { var entry; var end_x = x+1, end_y = y+1; m.[x].[y] = entry; if ( !edit_mode ) { for (; end_x <= d.["A:width"] && not declared m.[end_x].[y]; end_x++) m.[end_x].[y] = entry; var new_y_is_ok = 1; for (; end_y <= d.["A:height"] && new_y_is_ok; ) { for (var x2 = x; x2 < end_x; x2++) if (declared m.[x2].[end_y]) new_y_is_ok = 0; if (new_y_is_ok) { for (var x2 = x; x2 < end_x; x2++) m.[x2].[end_y] = entry; end_y++; } } } entry.["type"] = "filler"; entry.["ln_x"] = end_x - x; entry.["ln_y"] = end_y - y; entry.["done"] = 0; } for (var y=1; y <= d.["A:height"]; y++) { for (var x=1; x <= d.["A:width"]; x++) if (not m.[x].[y].done) { html ~= << EOT: :
:
EOT; if ( edit_mode ) { html ~= '
\n'; html ~= __WsfDialog_get_html_editor(); if ( edit_show_xml ) html ~= '
\n' ~ '
${xml::(format_xml_dump(xmltree))}
\n'; html ~= '
\n'; } else if ( show_edit_button ) html ~= '\n'; html ~= << EOT: : :
EOT; return html; } /** * This method is called when the user clicks on the "save" button in * the dialog editor. It does nothing in the default implementation. So * you need to overload it in a derived object to add this * functionality. */ method save() { /* To be overloaded */ return; } /** * Overloaded [[wsf:WsfComponent.main()]]. */ method main() { while (1) { task_co_return(); var d = xmltree.["E0:wsfdialog"]; if ( edit_mode ) { edit_current = cgi.param.new_current; if ( declared cgi.param.show_xml ) edit_show_xml = not edit_show_xml; if ( cgi.param.current ~== "" ) { d.["A:width"] = cgi.param.set_width; d.["A:height"] = cgi.param.set_height; d.["A:step"] = cgi.param.set_step; if ( cgi.param.set_code =~ /\S/ ) d.["E0:code"].["C0"] = cgi.param.set_code; else delete d.["E0:code"]; if ( cgi.param.new_element =~ /\S/ ) { var id = 0; while ( declared d.["E${id}:field"] ) id++; edit_current = "E${id}:field"; d.["E${id}:field"].["A:name"] = cgi.param.new_element; d.["E${id}:field"].["A:width"] = 1; d.["E${id}:field"].["A:height"] = 1; d.["E${id}:field"].["A:posx"] = 0; d.["E${id}:field"].["A:posy"] = 0; d.["E${id}:field"].["A:type"] = "text"; } if ( declared cgi.param.save ) save(); if ( declared cgi.param.run_dialog ) { set_edit_mode(0); show_edit_button = 1; reset_values(); } } else if ( declared cgi.param.delete ) { delete d.[cgi.param.current]; edit_current = ""; } else { var e = d.[cgi.param.current]; e.["A:name"] = cgi.param.set_name; e.["A:width"] = cgi.param.set_width; e.["A:height"] = cgi.param.set_height; e.["A:posx"] = cgi.param.set_posx; e.["A:posy"] = cgi.param.set_posy; e.["A:type"] = cgi.param.set_type; if ( cgi.param.set_code =~ /\S/ ) e.["E0:code"].["C0"] = cgi.param.set_code; else delete e.["E0:code"]; if ( cgi.param.set_value =~ /\S/ ) e.["E0:value"].["C0"] = cgi.param.set_value; else delete e.["E0:value"]; if ( declared cgi.param.update ) edit_current = ""; } } else if ( show_edit_button && declared cgi.param.switch_edit_mode ) { show_edit_button = 0; set_edit_mode(1); } else { foreach f (d) { var fn = d.[f].["A:name"]; if ( declared cgi.param.["${id}_${fn}"] ) values.[fn] = cgi.param.["${id}_${fn}"]; } foreach f (d) { var fn = d.[f].["A:name"]; if ( f =~ /^E\d+:field/ && fn ~== cgi.param.el && declared d.[f].["E0:code"].["C0"]) task_eval(d.[f].["E0:code"].["C0"], this); } } dirty = 1; } } /** * Reset the dialog elements to their default values. This can also be * used in the code snippets assigned to the dialog elements. To reset * the dialog. */ method reset_values() { var d = xmltree.["E0:wsfdialog"]; foreach f (d) { if ( f =~ /^E\d+:field/ ) { values.[d.[f].["A:name"]] = declared d.[f].["E0:value"] ? d.[f].["E0:value"].["C0"] : undef; } } dirty = 1; } /** * Overwrite xml data. The argument is the XML text. This might also be * called by one of the code snippets attached to dialog elements to * switch to another dialog. (E.g. when clicking on a "Next" button.) */ method import_xml(xmldata) { if (not defined xmldata) xmldata = ''; xmltree = format_xml_parse(xmldata); reset_values(); } /** * The constructor. * * The parameter is the XML text containing the dialog configuration. * If it is undef (or not specified at all), an empty dialog will be * created. */ method init(xmldata) { *WsfComponent.init(); import_xml(xmldata); return this; } }