# HG changeset patch # User Edouard Tisserant # Date 1598607078 -7200 # Node ID c113904f0e62558358568e46b38d82b808b43a10 # Parent d1bb0c09e412d938e76230d9c8baeb94bc62a6d1# Parent f6d428330e0471cbb7c4f75a4c8edf7785f3cd4f Merged diff -r f6d428330e04 -r c113904f0e62 svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/gen_index_xhtml.xslt Fri Aug 28 11:31:18 2020 +0200 @@ -1,9 +1,9 @@ - - - - - + + + + + HMI_PLC_STATUS @@ -12,11 +12,11 @@ HMI_CURRENT_PAGE - + - + @@ -63,8 +63,8 @@ - - + + @@ -96,7 +96,7 @@ - + @@ -104,7 +104,7 @@ - + @@ -114,16 +114,16 @@ - + - - - + + + @@ -134,7 +134,7 @@ - + @@ -160,15 +160,15 @@ - + - - + + @@ -202,7 +202,7 @@ - + @@ -215,12 +215,12 @@ - - + + - + @@ -266,7 +266,7 @@ - + @@ -301,8 +301,8 @@ - - + + @@ -324,10 +324,10 @@ - + - + @@ -337,13 +337,13 @@ - - - + + + - - + + @@ -383,13 +383,13 @@ - - + + - - - + + + @@ -401,9 +401,9 @@ - - - + + + @@ -417,13 +417,13 @@ - - + + - - - + + + @@ -437,8 +437,8 @@ - - + + @@ -472,18 +472,18 @@ - - - + + + - - - - - - - - + + + + + + + + " ": { @@ -540,10 +540,10 @@ jumps: [ - + - + @@ -576,7 +576,7 @@ } - + } @@ -637,7 +637,10 @@ - + + + + @@ -670,23 +673,26 @@ All units must be set to "px" in Inkscape's document properties - - - - + + + + + + + + + - - + - + - - + - + @@ -711,7 +717,7 @@ id - + transform @@ -720,16 +726,30 @@ style - + - - - + + + + + + + _ + + + + + + + + + + @@ -756,7 +776,7 @@ - + @@ -766,7 +786,7 @@ - + @@ -779,15 +799,31 @@ _ + + + - - - + + + + + + + + + + + + + + + + @@ -802,7 +838,7 @@ - + @@ -811,7 +847,7 @@ - + @@ -861,8 +897,8 @@ - - + + " @@ -929,7 +965,7 @@ ],{ - + }) @@ -942,7 +978,7 @@ - + @@ -1205,7 +1241,7 @@ apply_hmi_value(index, new_val) { - return apply_hmi_value(this.get_variable_index(0), new_val); + return apply_hmi_value(this.get_variable_index(index), new_val); } @@ -1298,7 +1334,7 @@ - + @@ -1315,8 +1351,8 @@ } - - + + @@ -1336,14 +1372,14 @@ - - - + + + - + - - + + @@ -1368,8 +1404,8 @@ _sub: { - - + + @@ -1460,61 +1496,77 @@ - on_mouse_down(evt) { - - if (this.active_style && this.inactive_style) { - - this.active_elt.setAttribute("style", this.active_style); - - this.inactive_elt.setAttribute("style", "display:none"); - - } - - this.apply_hmi_value(0, 1); - - } - - - - on_mouse_up(evt) { - - if (this.active_style && this.inactive_style) { - - this.active_elt.setAttribute("style", "display:none"); - - this.inactive_elt.setAttribute("style", this.inactive_style); - - } - - this.apply_hmi_value(0, 0); - - } - - - - init() { - - this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined; - - this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined; - - + // TODO decouple update of DOM from event (i.e use animate()) + + + + + + // TODO State of the button should distinguish UI feedbak from current PLC value + + + + on_mouse_down(evt) { if (this.active_style && this.inactive_style) { + this.active_elt.setAttribute("style", this.active_style); + + this.inactive_elt.setAttribute("style", "display:none"); + + } + + this.apply_hmi_value(0, 1); + + // TODO inhibit all mouse/touch events except mouse up (in other word grab cursor) + + } + + + + on_mouse_up(evt) { + + if (this.active_style && this.inactive_style) { + this.active_elt.setAttribute("style", "display:none"); this.inactive_elt.setAttribute("style", this.inactive_style); } - - - this.element.setAttribute("onmousedown", "hmi_widgets["+this.element_id+"].on_mouse_down(evt)"); - - this.element.setAttribute("onmouseup", "hmi_widgets["+this.element_id+"].on_mouse_up(evt)"); - - } + this.apply_hmi_value(0, 0); + + // TODO release inhibited events + + } + + + + init() { + + // TODO : move to widget_defs so that we can have generated string literals directly + + this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined; + + this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined; + + + + if (this.active_style && this.inactive_style) { + + this.active_elt.setAttribute("style", "display:none"); + + this.inactive_elt.setAttribute("style", this.inactive_style); + + } + + + + this.element.setAttribute("onmousedown", "hmi_widgets[\""+this.element_id+"\"].on_mouse_down(evt)"); + + this.element.setAttribute("onmouseup", "hmi_widgets[\""+this.element_id+"\"].on_mouse_up(evt)"); + + } } @@ -1522,11 +1574,11 @@ - + active inactive - + @@ -1624,17 +1676,17 @@ - + path - + value min max - + @@ -2028,17 +2080,17 @@ - + handle range - + value min max - + @@ -2052,8 +2104,6 @@ this.fields[index] = value; - console.log(value, index); - this.element.textContent = this.args.length == 1 ? vsprintf(this.args[0],this.fields) : this.fields.join(' '); } @@ -2570,7 +2620,7 @@ - + text box button @@ -2891,8 +2941,6 @@ } - console.log(this.menu_offset); - this.set_partial_text(); }, @@ -3096,13 +3144,13 @@ must have one argument given : a class name. - - - - - - - + + + + + + + index_pool: [ @@ -3118,11 +3166,11 @@ init: function() { - - - + + + - + id(" ").setAttribute("onclick", "hmi_widgets[' @@ -3136,13 +3184,13 @@ this.items = [ - - + + - - - - + + + + [ /* item=" " path=" @@ -3333,22 +3381,22 @@ - + key_pos - + - + value - + - + frequency: 5, @@ -3366,7 +3414,7 @@ }, - + init: function() { @@ -3403,7 +3451,7 @@ ", " - ", this, this.last_val,size); + ", this, this.last_val, size); }, @@ -3417,15 +3465,17 @@ class JsonTableWidget extends Widget{ + cache = []; + do_http_request() { const query = { - offset: '42', - - filter: '*powerloss*', - - args: this.args + args: this.args, + + vars: this.cache, + + visible: this.visible }; @@ -3439,7 +3489,7 @@ headers: {'Content-Type': 'application/json'} - } + }; @@ -3447,13 +3497,15 @@ .then(res => res.json()) - .then(this.spread_json_data); - - - - } - - dispatch(value) { + .then(this.spread_json_data.bind(this)); + + + + } + + dispatch(value, oldval, index) { + + this.cache[index] = value; this.do_http_request(); @@ -3481,13 +3533,83 @@ . - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JsonTable : missplaced '=' or inconsistent names in Json data expressions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jdata + + + + - - - - + + + id(" @@ -3497,29 +3619,13 @@ "#"+hmi_widgets[" "].items[ - - ]); - - - - console.log("from_textsylelist"," - - ", " - - ", - - , - - hmi_widgets[" - - "].items[ - + ]); - Clones (svg:use) in JsonTable Widget must point to a valid HMI:List or HMI:TextStyleList widget or item. Reference " + Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or item. Reference " " is not valid and will not be updated. @@ -3527,59 +3633,182 @@ - - id(" + + + + + + + + + + Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignement following value expression in label. + + + { + + let elt = id(" + + "); + + elt.textContent = String( + + ); + + elt.style = hmi_widgets[" + + "].styles[ + + ]; + + } + + + + id(" + + ").textContent = String( + + ); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + obj_ + + _ + + try { + + + let + + + = + + ; + + if( + + + == undefined) { + + console.log(" + + + = + + "); + + throw null; + + } + + + + + + + + + + + + + + id(" - ").textContent = String( - - ); - - - - - - - - - + ").setAttribute("style", " + + "); + + + + - - - - let obj_ - - = - - - ; - - - - obj_ - - - + } catch(err) { + + id(" + + ").setAttribute("style", "display:none"); + + } + - + data - + forward backward cursor - + - - spread_json_data: function(jdata) { - - - + + visible: + + , + + spread_json_data: function(janswer) { + + let [range,position,jdata] = janswer; + + this.apply_hmi_value(1, range); + + this.apply_hmi_value(2, position); + + console.log(range,position,jdata); + + + + } @@ -3587,38 +3816,38 @@ - + active inactive - + - + disabled - + - + - + - + - + active: false, @@ -3821,9 +4050,9 @@ var keypads = { - + - + " ":[" @@ -4020,11 +4249,25 @@ on_Enter_click() { - end_modal.call(this); - - let callback_obj = this.result_callback_obj; - - callback_obj.edit_callback(this.editstr); + let coercedval = (typeof this.initial) == "number" ? Number(this.editstr) : this.editstr; + + if(typeof coercedval == 'number' && isNaN(coercedval)){ + + // revert to initial so it explicitely shows input was ignored + + this.editstr = String(this.initial); + + this.update(); + + } else { + + let callback_obj = this.result_callback_obj; + + end_modal.call(this); + + callback_obj.edit_callback(coercedval); + + } } @@ -4118,7 +4361,7 @@ show_modal.call(this,size); - this.editstr = initial; + this.editstr = String(initial); this.result_callback_obj = callback_obj; @@ -4128,6 +4371,10 @@ this.caps = false; + this.initial = initial; + + + this.update(); } @@ -4168,25 +4415,25 @@ - + Esc Enter BackSpace Keys Info Value - + Sign Space NumDot position - + - + CapsLock Shift - - + + init: function() { @@ -4232,7 +4479,7 @@ - + coordinates: [ , @@ -4240,13 +4487,13 @@ ], - + items: { - + : " ", @@ -4255,6 +4502,22 @@ }, + + + styles: { + + + + + + : " + + ", + + + }, + + class MeterWidget extends Widget{ @@ -4312,17 +4575,17 @@ - + needle range - + value min max - + @@ -4413,9 +4676,9 @@ choices: [ - + - + { elt:id(" @@ -4761,17 +5024,17 @@ - + handle range - + value min max setpoint - + @@ -4806,9 +5069,9 @@ choices: [ - + - + { elt:id(" @@ -4896,7 +5159,7 @@ - + active inactive @@ -5432,8 +5695,6 @@ if(index > last_remote_index){ - console.log("updated local variable ",index,value); - updates[index] = value; requestHMIAnimation(); @@ -5784,8 +6045,6 @@ let [keypadid, xcoord, ycoord] = keypads[valuetype]; - console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid); - edit_callback = callback; let widget = hmi_widgets[keypadid]; @@ -5874,8 +6133,6 @@ eltsub.active.setAttribute("style", eltsub.active_style); - console.log("active", eltsub); - }; function widget_inactive_activable(eltsub) { @@ -5890,8 +6147,6 @@ eltsub.inactive.setAttribute("style", eltsub.inactive_style); - console.log("inactive", eltsub); - }; diff -r f6d428330e04 -r c113904f0e62 svghmi/inline_svg.ysl2 --- a/svghmi/inline_svg.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/inline_svg.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -10,7 +10,10 @@ // Identity template : // - copy every attributes // - copy every sub-elements -template "@* | node()", mode="inline_svg" { + +svgtmpl "@*", mode="inline_svg" xsl:copy; + +template "node()", mode="inline_svg" { // use real xsl:copy instead copy-of alias from yslt.yml2 if "not(@id = $discardable_elements/@id)" xsl:copy apply "@* | node()", mode="inline_svg"; @@ -51,19 +54,22 @@ const "hmi_lists", "$hmi_elements[@id = $hmi_lists_descs/@id]"; const "targets_not_to_unlink", "$hmi_lists/descendant-or-self::svg:*"; const "to_unlink", "$hmi_elements[not(@id = $hmi_pages/@id)]/descendant-or-self::svg:use"; -svgtmpl "svg:use", mode="inline_svg" -{ - param "seed"; + +def "func:is_unlinkable" { + param "targetid"; + param "eltid"; + result "$eltid = $to_unlink/@id and not($targetid = $targets_not_to_unlink/@id)"; +} + +svgtmpl "svg:use", mode="inline_svg"{ const "targetid","substring-after(@xlink:href,'#')"; choose { - when "@id = $to_unlink/@id and not($targetid = $targets_not_to_unlink/@id)" { + when "func:is_unlinkable($targetid, @id)" { call "unlink_clone" { with "targetid", "$targetid"; - with "seed","$seed"; - } - } - otherwise - xsl:copy apply "@* | node()", mode="inline_svg"; + } + } + otherwise xsl:copy apply "@*", mode="inline_svg"; } } @@ -87,11 +93,16 @@ svgfunc "unlink_clone"{ param "targetid"; - param "seed"; + param "seed","''"; const "target", "//svg:*[@id = $targetid]"; - const "seeded_id","concat($seed, @id)"; + const "seeded_id" choose { + when "string-length($seed) > 0" > «$seed»_«@id» + otherwise value "@id"; + } g{ attrib "id" value "$seeded_id"; + attrib "original" value "@id"; + choose { when "$target[self::svg:g]" { foreach "@*[not(local-name() = $excluded_use_attrs/name | $merge_use_attrs)]" @@ -112,7 +123,7 @@ } apply "$target/*", mode="unlink_clone"{ - with "seed","concat($seed, @id)"; + with "seed","$seeded_id"; } } otherwise { @@ -121,7 +132,7 @@ attrib "{name()}" > «.» apply "$target", mode="unlink_clone"{ - with "seed","concat($seed, @id)"; + with "seed","$seeded_id"; } } } @@ -133,13 +144,25 @@ svgtmpl "@id", mode="unlink_clone" { param "seed"; attrib "id" > «$seed»_«.» + attrib "original" > «.» } svgtmpl "@*", mode="unlink_clone" xsl:copy; svgtmpl "svg:use", mode="unlink_clone" { param "seed"; - apply "." mode="inline_svg" with "seed","concat($seed, '_')"; + const "targetid","substring-after(@xlink:href,'#')"; + choose { + when "func:is_unlinkable($targetid, @id)" { + call "unlink_clone" { + with "targetid", "$targetid"; + with "seed","$seed"; + } + } + otherwise xsl:copy apply "@*", mode="unlink_clone" { + with "seed","$seed"; + } + } } // copying widgets would have unwanted effect diff -r f6d428330e04 -r c113904f0e62 svghmi/svghmi.js --- a/svghmi/svghmi.js Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/svghmi.js Fri Aug 28 11:31:18 2020 +0200 @@ -240,7 +240,6 @@ function send_hmi_value(index, value) { if(index > last_remote_index){ - console.log("updated local variable ",index,value); updates[index] = value; requestHMIAnimation(); return; @@ -416,7 +415,6 @@ function edit_value(path, valuetype, callback, initial, size) { let [keypadid, xcoord, ycoord] = keypads[valuetype]; - console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid); edit_callback = callback; let widget = hmi_widgets[keypadid]; widget.start_edit(path, valuetype, callback, initial, size); @@ -461,7 +459,6 @@ eltsub.inactive.setAttribute("style", "display:none"); if(eltsub.active_style !== undefined) eltsub.active.setAttribute("style", eltsub.active_style); - console.log("active", eltsub); }; function widget_inactive_activable(eltsub) { if(eltsub.active_style === undefined) @@ -469,5 +466,4 @@ eltsub.active.setAttribute("style", "display:none"); if(eltsub.inactive_style !== undefined) eltsub.inactive.setAttribute("style", eltsub.inactive_style); - console.log("inactive", eltsub); -}; +}; diff -r f6d428330e04 -r c113904f0e62 svghmi/svghmi.py --- a/svghmi/svghmi.py Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/svghmi.py Fri Aug 28 11:31:18 2020 +0200 @@ -485,10 +485,13 @@ inkpath = get_inkscape_path() svgpath = self._getSVGpath() - _status, result, _err_result = ProcessLogger(self.GetCTRoot().logger, - inkpath + " -S " + svgpath, + status, result, _err_result = ProcessLogger(self.GetCTRoot().logger, + '"' + inkpath + '" -S "' + svgpath + '"', no_stdout=True, no_stderr=True).spin() + if status != 0: + self.FatalError("SVGHMI : inkscape couldn't extract geometry from given SVG") + res = [] for line in result.split(): strippedline = line.strip() diff -r f6d428330e04 -r c113904f0e62 svghmi/widget_button.ysl2 --- a/svghmi/widget_button.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widget_button.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -8,34 +8,42 @@ active_style = undefined; inactive_style = undefined; - on_mouse_down(evt) { - if (this.active_style && this.inactive_style) { - this.active_elt.setAttribute("style", this.active_style); - this.inactive_elt.setAttribute("style", "display:none"); - } - this.apply_hmi_value(0, 1); - } + // TODO decouple update of DOM from event (i.e use animate()) - on_mouse_up(evt) { - if (this.active_style && this.inactive_style) { - this.active_elt.setAttribute("style", "display:none"); - this.inactive_elt.setAttribute("style", this.inactive_style); - } - this.apply_hmi_value(0, 0); - } - init() { - this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined; - this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined; + // TODO State of the button should distinguish UI feedbak from current PLC value + on_mouse_down(evt) { + if (this.active_style && this.inactive_style) { + this.active_elt.setAttribute("style", this.active_style); + this.inactive_elt.setAttribute("style", "display:none"); + } + this.apply_hmi_value(0, 1); + // TODO inhibit all mouse/touch events except mouse up (in other word grab cursor) + } + + on_mouse_up(evt) { if (this.active_style && this.inactive_style) { this.active_elt.setAttribute("style", "display:none"); this.inactive_elt.setAttribute("style", this.inactive_style); } + this.apply_hmi_value(0, 0); + // TODO release inhibited events + } - this.element.setAttribute("onmousedown", "hmi_widgets["+this.element_id+"].on_mouse_down(evt)"); - this.element.setAttribute("onmouseup", "hmi_widgets["+this.element_id+"].on_mouse_up(evt)"); - } + init() { + // TODO : move to widget_defs so that we can have generated string literals directly + this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined; + this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined; + + if (this.active_style && this.inactive_style) { + this.active_elt.setAttribute("style", "display:none"); + this.inactive_elt.setAttribute("style", this.inactive_style); + } + + this.element.setAttribute("onmousedown", "hmi_widgets[\""+this.element_id+"\"].on_mouse_down(evt)"); + this.element.setAttribute("onmouseup", "hmi_widgets[\""+this.element_id+"\"].on_mouse_up(evt)"); + } } || } diff -r f6d428330e04 -r c113904f0e62 svghmi/widget_display.ysl2 --- a/svghmi/widget_display.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widget_display.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -7,7 +7,6 @@ frequency = 5; dispatch(value, oldval, index) { this.fields[index] = value; - console.log(value, index); this.element.textContent = this.args.length == 1 ? vsprintf(this.args[0],this.fields) : this.fields.join(' '); } } diff -r f6d428330e04 -r c113904f0e62 svghmi/widget_dropdown.ysl2 --- a/svghmi/widget_dropdown.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widget_dropdown.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -158,7 +158,6 @@ 0, this.menu_offset - spanslength); } - console.log(this.menu_offset); this.set_partial_text(); }, // Setup partial view text content diff -r f6d428330e04 -r c113904f0e62 svghmi/widget_input.ysl2 --- a/svghmi/widget_input.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widget_input.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -35,7 +35,7 @@ | }, | on_edit_click: function(opstr) { | var size = (typeof this.key_pos_elt !== 'undefined') ? this.key_pos_elt.getBBox() : undefined - | edit_value("«path/@value»", "«path/@type»", this, this.last_val,size); + | edit_value("«path/@value»", "«path/@type»", this, this.last_val, size); | }, | edit_callback: function(new_val) { diff -r f6d428330e04 -r c113904f0e62 svghmi/widget_jsontable.ysl2 --- a/svghmi/widget_jsontable.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widget_jsontable.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -3,25 +3,27 @@ template "widget[@type='JsonTable']", mode="widget_class" || class JsonTableWidget extends Widget{ + cache = []; do_http_request() { const query = { - offset: '42', - filter: '*powerloss*', - args: this.args + args: this.args, + vars: this.cache, + visible: this.visible }; const options = { method: 'POST', body: JSON.stringify(query), headers: {'Content-Type': 'application/json'} - } + }; fetch(this.args[0], options) .then(res => res.json()) - .then(this.spread_json_data); - - } - dispatch(value) { + .then(this.spread_json_data.bind(this)); + + } + dispatch(value, oldval, index) { + this.cache[index] = value; this.do_http_request(); } on_click(evt) { @@ -41,44 +43,163 @@ const "hmi_textstylelists_descs", "$parsed_widgets/widget[@type = 'TextStyleList']"; const "hmi_textstylelists", "$hmi_elements[@id = $hmi_textstylelists_descs/@id]"; +const "textstylelist_related" foreach "$hmi_textstylelists" list { + attrib "listid" value "@id"; + foreach "func:refered_elements(.)" elt { + attrib "eltid" value "@id"; + } +} +const "textstylelist_related_ns", "exsl:node-set($textstylelist_related)"; + +def "func:json_expressions" { + param "expressions"; + param "label"; + + // compute javascript expressions to access JSON data + // desscribed in given svg element's "label" + // knowing that parent element already has given "expressions". + + choose { + when "$label" { + const "suffixes", "str:split($label)"; + const "res" foreach "$suffixes" expression { + const "suffix","."; + const "pos","position()"; + // take last available expression (i.e can have more suffixes than expressions) + const "expr","$expressions[position() <= $pos][last()]/expression"; + choose { + when "contains($suffix,'=')" { + const "name", "substring-before($suffix,'=')"; + if "$expr/@name[. != $name]" + error > JsonTable : missplaced '=' or inconsistent names in Json data expressions. + attrib "name" value "$name"; + attrib "content" > «$expr/@content»«substring-after($suffix,'=')» + } + otherwise { + copy "$expr/@name"; + attrib "content" > «$expr/@content»«$suffix» + } + } + } + result "exsl:node-set($res)"; + } + // Empty labels are ignored, expressions are then passed as-is. + otherwise result "$expressions"; + } + +} + +const "initexpr" expression attrib "content" > jdata +const "initexpr_ns", "exsl:node-set($initexpr)"; + template "svg:use", mode="json_table_elt_render" { - param "value_expr"; + param "expressions"; // cloned element must be part of a HMI:List const "targetid", "substring-after(@xlink:href,'#')"; const "from_list", "$hmi_lists[(@id | */@id) = $targetid]"; - const "from_textstylelist", "$hmi_textstylelists[(@id | */@id) = $targetid]"; choose { when "count($from_list) > 0" { | id("«@id»").setAttribute("xlink:href", // obtain new target id from HMI:List widget - | "#"+hmi_widgets["«$from_list/@id»"].items[«$value_expr»]); - } + | "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]); + } + otherwise + warning > Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or item. Reference "«@xlink:href»" is not valid and will not be updated. + } +} + +template "svg:text", mode="json_table_elt_render" { + param "expressions"; + const "value_expr", "$expressions/expression[1]/@content"; + const "original", "@original"; + const "from_textstylelist", "$textstylelist_related_ns/list[elt/@eltid = $original]"; + choose { + when "count($from_textstylelist) > 0" { - | console.log("from_textsylelist","«@id»", "«$value_expr»", «$value_expr», - // obtain new style from HMI:TextStyleList widget - | hmi_widgets["«$from_textstylelist/@id»"].items[«$value_expr»]); - } - otherwise - warning > Clones (svg:use) in JsonTable Widget must point to a valid HMI:List or HMI:TextStyleList widget or item. Reference "«@xlink:href»" is not valid and will not be updated. - } -} - -template "svg:text", mode="json_table_elt_render" { - param "value_expr"; - | id("«@id»").textContent = String(«$value_expr»); + const "content_expr", "$expressions/expression[2]/@content"; + if "string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'" + error > Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignement following value expression in label. + | { + | let elt = id("«@id»"); + | elt.textContent = String(«$content_expr»); + | elt.style = hmi_widgets["«$from_textstylelist/@listid»"].styles[«$value_expr»]; + | } + } + otherwise { + | id("«@id»").textContent = String(«$value_expr»); + } + } +} + + +// only labels comming from Json widget are counted in +def "func:filter_non_widget_label" { + param "elt"; + param "widget_elts"; + const "eltid" choose { + when "$elt/@original" value "$elt/@original"; + otherwise value "$elt/@id"; + } + result "$widget_elts[@id=$eltid]/@inkscape:label"; +} + +template "svg:*", mode="json_table_render_except_comments"{ + param "expressions"; + param "widget_elts"; + + const "label", "func:filter_non_widget_label(., $widget_elts)"; + // filter out "# commented" elements + if "not(starts-with($label,'#'))" + apply ".", mode="json_table_render"{ + with "expressions", "$expressions"; + with "widget_elts", "$widget_elts"; + with "label", "$label"; + } } template "svg:*", mode="json_table_render" { - param "objname"; - apply ".", mode="json_table_elt_render" with "value_expr" > «$objname»«substring-before(@inkscape:label, ' ')» + param "expressions"; + param "widget_elts"; + param "label"; + apply ".", mode="json_table_elt_render" + with "expressions", "func:json_expressions($expressions, $label)"; } template "svg:g", mode="json_table_render" { - param "objname"; - | let obj_«@id» = «$objname»«substring-before(@inkscape:label, ' ')»; - apply "*[@inkscape:label]", mode="json_table_render" - with "objname" > obj_«@id» + param "expressions"; + param "widget_elts"; + param "label"; + const "gid", "@id"; + + // use intermediate variables for optimization + const "varprefix" > obj_«$gid»_ + | try { + + foreach "$expressions/expression"{ + | let «$varprefix»«position()» = «@content»; + | if(«$varprefix»«position()» == undefined) { + | console.log("«$varprefix»«position()» = «@content»"); + | throw null; + | } + } + + // because we put values in a variables, we can replace corresponding expression with variable name + const "new_expressions" foreach "$expressions/expression" xsl:copy { + copy "@name"; + attrib "content" > «$varprefix»«position()» + } + + // revert hiding in case it did happen before + | id("«@id»").setAttribute("style", "«@style»"); + + apply "*", mode="json_table_render_except_comments" { + with "expressions", "func:json_expressions(exsl:node-set($new_expressions), $label)"; + with "widget_elts", "$widget_elts"; + } + | } catch(err) { + | id("«$gid»").setAttribute("style", "display:none"); + | } } template "widget[@type='JsonTable']", mode="widget_defs" { @@ -86,7 +207,15 @@ labels("data"); optional_labels("forward backward cursor"); const "data_elt", "$result_svg_ns//*[@id = $hmi_element/@id]/*[@inkscape:label = 'data']"; - | spread_json_data: function(jdata) { - apply "$data_elt/*", mode="json_table_render" with "objname","'jdata'"; + | visible: «count($data_elt/*[@inkscape:label])», + | spread_json_data: function(janswer) { + | let [range,position,jdata] = janswer; + | this.apply_hmi_value(1, range); + | this.apply_hmi_value(2, position); + | console.log(range,position,jdata); + apply "$data_elt", mode="json_table_render_except_comments" { + with "expressions","$initexpr_ns"; + with "widget_elts","$hmi_element/*[@inkscape:label = 'data']/descendant::svg:*"; + } | } } diff -r f6d428330e04 -r c113904f0e62 svghmi/widget_keypad.ysl2 --- a/svghmi/widget_keypad.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widget_keypad.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -104,9 +104,16 @@ } on_Enter_click() { - end_modal.call(this); - let callback_obj = this.result_callback_obj; - callback_obj.edit_callback(this.editstr); + let coercedval = (typeof this.initial) == "number" ? Number(this.editstr) : this.editstr; + if(typeof coercedval == 'number' && isNaN(coercedval)){ + // revert to initial so it explicitely shows input was ignored + this.editstr = String(this.initial); + this.update(); + } else { + let callback_obj = this.result_callback_obj; + end_modal.call(this); + callback_obj.edit_callback(coercedval); + } } on_BackSpace_click() { @@ -153,11 +160,13 @@ result_callback_obj = undefined; start_edit(info, valuetype, callback_obj, initial,size) { show_modal.call(this,size); - this.editstr = initial; + this.editstr = String(initial); this.result_callback_obj = callback_obj; this.Info_elt.textContent = info; this.shift = false; this.caps = false; + this.initial = initial; + this.update(); } diff -r f6d428330e04 -r c113904f0e62 svghmi/widget_list.ysl2 --- a/svghmi/widget_list.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widget_list.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -1,10 +1,20 @@ // widget_list.ysl2 -template "widget[@type='List' or @type='TextStyleList']", mode="widget_defs" { +template "widget[@type='List']", mode="widget_defs" { param "hmi_element"; | items: { foreach "$hmi_element/*[@inkscape:label]" { - | «func:escape_quotes(@inkscape:label)»: "«@id»", + | «@inkscape:label»: "«@id»", } | }, } + +template "widget[@type='TextStyleList']", mode="widget_defs" { + param "hmi_element"; + | styles: { + foreach "$hmi_element/*[@inkscape:label]" { + const "style", "func:refered_elements(.)[self::svg:text]/@style"; + | «@inkscape:label»: "«$style»", + } + | }, +} diff -r f6d428330e04 -r c113904f0e62 svghmi/widgets_common.ysl2 --- a/svghmi/widgets_common.ysl2 Tue Aug 18 11:42:28 2020 +0200 +++ b/svghmi/widgets_common.ysl2 Fri Aug 28 11:31:18 2020 +0200 @@ -183,7 +183,7 @@ } apply_hmi_value(index, new_val) { - return apply_hmi_value(this.get_variable_index(0), new_val); + return apply_hmi_value(this.get_variable_index(index), new_val); } new_hmi_value(index, value, oldval) { diff -r f6d428330e04 -r c113904f0e62 tests/svghmi/py_ext_0@py_ext/pyfile.xml --- a/tests/svghmi/py_ext_0@py_ext/pyfile.xml Tue Aug 18 11:42:28 2020 +0200 +++ b/tests/svghmi/py_ext_0@py_ext/pyfile.xml Fri Aug 28 11:31:18 2020 +0200 @@ -1,33 +1,58 @@ - - + + + + delta else old_position + new_visible = new_range if delta <= 0 else visible + + visible_alarms = [] + for ts, text, status in Alarms[new_position:new_position + new_visible]: + visible_alarms.append({ + "time": time.ctime(ts), + "text": text, # TODO translate text + "status": status + }) -def MyOnChangeFunc(changed_var_name): - print changed_var_name + ": " + getattr(PLCGlobals, changed_var_name) + return new_range, new_position, visible_alarms + ]]> diff -r f6d428330e04 -r c113904f0e62 tests/svghmi/svghmi_0@svghmi/svghmi.svg --- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg Tue Aug 18 11:42:28 2020 +0200 +++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg Fri Aug 28 11:31:18 2020 +0200 @@ -16,7 +16,7 @@ version="1.1" id="hmi0" sodipodi:docname="svghmi.svg" - inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> + inkscape:version="0.92.3 (2405546, 2018-03-11)"> @@ -34,6 +34,21 @@ + + + @@ -182,17 +197,17 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:document-units="px" - inkscape:current-layer="g907" + inkscape:current-layer="g1384" showgrid="false" units="px" - inkscape:zoom="0.50000001" - inkscape:cx="632.83299" - inkscape:cy="-317.59408" - inkscape:window-width="2443" - inkscape:window-height="1567" - inkscape:window-x="816" - inkscape:window-y="103" - inkscape:window-maximized="0" + inkscape:zoom="0.77167689" + inkscape:cx="-954.74063" + inkscape:cy="270.19826" + inkscape:window-width="1800" + inkscape:window-height="836" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" showguides="true" inkscape:guide-bbox="true" /> + inkscape:label="HMI:Keypad:HMI_STRING:HMI_LOCAL:PAGE_LOCAL"> - 8888 - - - - dhu - - - - plop - - - - mhoo - - - - yodl - - - - mhe - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - information - - 8888 - - - - + style="fill:none;fill-rule:evenodd;stroke:#ff3000;stroke-width:2.9633333;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:29.63333301;stroke-opacity:1;marker-end:url(#marker1656)" /> 8888 + transform="matrix(0.14295135,0,0,0.14295135,489.21833,37.615184)"> 8888 + style="stroke-width:4px">8888 - + @@ -4269,21 +3786,21 @@ id="path1465" d="m 797.19546,145.18619 -80.62929,0.60214 -0.60215,-80.629288 80.6293,-0.60214 z" inkscape:transform-center-y="-14.956361" - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> dhu @@ -4302,12 +3819,12 @@ sodipodi:cx="596.74072" sodipodi:sides="3" id="path1473" - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" transform="matrix(0,-2.0000001,1.9999999,0,1034.195,1298.6541)" /> plop - - plop + + @@ -4326,7 +3843,7 @@ inkscape:transform-center-x="14.956364" transform="rotate(-90,746.45698,-44.543641)" sodipodi:type="star" - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path1481" sodipodi:sides="3" sodipodi:cx="596.74072" @@ -4344,16 +3861,16 @@ id="text1485" y="111.05016" x="537.25018" - style="font-style:normal;font-weight:normal;font-size:20px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + style="font-style:normal;font-weight:normal;font-size:20px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" xml:space="preserve">mhoo @@ -4373,22 +3890,22 @@ sodipodi:cx="596.74072" sodipodi:sides="3" id="path1489" - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" /> yodl @@ -4396,7 +3913,7 @@ inkscape:transform-center-x="-14.956349" transform="matrix(0,-2.0000001,-1.9999999,0,1122.1514,1298.6541)" sodipodi:type="star" - style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path1497" sodipodi:sides="3" sodipodi:cx="596.74072" @@ -4412,7 +3929,7 @@ inkscape:transform-center-y="-3.3040441e-05" /> mhe + style="stroke-width:2px">mhe HMI_LOCAL variables - - - - - + x="509.67926" + y="43.42762">HMI_LOCAL variables 8888 + style="stroke-width:4"> 8888 - + dhu + style="stroke-width:2px">dhu + style="stroke-width:4"> + style="stroke-width:4"> mhoo + style="stroke-width:2px">mhoo + style="stroke-width:4"> yodl + style="stroke-width:2px">yodl + style="stroke-width:4"> @@ -4656,9 +4145,9 @@ id="text1601" y="111.05016" x="842.71497" - style="font-style:normal;font-weight:normal;font-size:20px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + style="font-style:normal;font-weight:normal;font-size:20px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" xml:space="preserve"> PAGE_LOCAL variables @@ -4700,7 +4189,7 @@ inkscape:connector-curvature="0" id="path691" d="M 130.96206,4.0725977 79.111776,-41.363223" - style="fill:none;fill-rule:evenodd;stroke:#c130f7;stroke-width:2.96323133;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0, 32.59554547;stroke-dashoffset:29.63231468;stroke-opacity:1;marker-end:url(#marker1536)" /> + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff3000;stroke-width:2.96333337;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0, 32.59666667;stroke-dashoffset:29.63333321;stroke-opacity:1;marker-end:url(#marker1971)" /> + + + Alarm Page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8888 + + + + + + + + + + + transform="translate(-1566.6506,56.936266)"> + transform="translate(-990.65059,102.93627)"> + + 8888 + + + + + + 8888 + + + + + range + position + notify + + + 8888 + + + + + + + + trigger + + + + + 8888 + + + ack + + + + disabled + + + + active + + + + alarm + + + Alarm Text + Status + TODO + + + + + + Alarms + +