edouard@2884: // widgets_common.ysl2 edouard@2884: Edouard@2808: in xsl decl labels(*ptr, name="defs_by_labels") alias call-template { Edouard@2808: with "hmi_element", "$hmi_element"; Edouard@2810: with "labels"{text *ptr}; edouard@2937: content; Edouard@2808: }; Edouard@2808: edouard@2937: decl optional_labels(*ptr) alias - { edouard@2937: /* TODO add some per label xslt variable to check if exist */ edouard@2937: labels(*ptr){ edouard@2937: with "mandatory","'no'"; edouard@2937: content; edouard@2937: } Edouard@2836: }; Edouard@2836: edouard@2937: decl activable_labels(*ptr) alias - { edouard@2937: optional_labels(*ptr) { edouard@2937: with "subelements","'active inactive'"; edouard@2937: content; edouard@2937: } edouard@2920: }; edouard@2920: edouard@3233: in xsl decl widget_desc(%name, match="widget[@type='%name']", mode="widget_desc") alias template { edouard@3233: type > «@type» edouard@3233: content; edouard@3233: }; edouard@3233: edouard@3232: in xsl decl widget_class(%name, *clsname="%nameWidget", match="widget[@type='%name']", mode="widget_class") alias template { edouard@3232: | class `text **clsname` extends Widget{ edouard@3232: content; edouard@3232: | } edouard@3232: }; edouard@3232: edouard@3232: in xsl decl widget_defs(%name, match="widget[@type='%name']", mode="widget_defs") alias template { edouard@3232: param "hmi_element"; edouard@3232: content; edouard@3232: }; edouard@3232: edouard@3232: in xsl decl widget_page(%name, match="widget[@type='%name']", mode="widget_page") alias template { edouard@3232: param "page_desc"; edouard@3232: content; edouard@3232: }; edouard@3232: edouard@3232: decl gen_index_xhtml alias - { edouard@3232: content; edouard@3232: }; edouard@3232: edouard@2955: template "svg:*", mode="hmi_widgets" { edouard@2886: const "widget", "func:widget(@id)"; Edouard@2881: const "eltid","@id"; Edouard@3024: const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,` edouard@2950: const "indexes" foreach "$widget/path" { Edouard@2881: choose { Edouard@2881: when "not(@index)" { edouard@3017: choose { Edouard@3058: when "not(@type)" { usveticic@3056: warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree edouard@3101: > undefined Edouard@3058: } edouard@3017: when "@type = 'PAGE_LOCAL'" edouard@3101: > "«@value»" edouard@3017: when "@type = 'HMI_LOCAL'" edouard@3101: > hmi_local_index("«@value»") edouard@3101: otherwise edouard@3101: error > Internal error while processing widget's non indexed HMI tree path : unknown type edouard@3017: } Edouard@2881: } Edouard@2881: otherwise { edouard@3101: > «@index» edouard@3101: } edouard@3101: } edouard@3101: if "position()!=last()" > , Edouard@2792: } edouard@2950: edouard@3099: const "minmaxes" foreach "$widget/path" { edouard@3099: choose { edouard@3099: when "@min and @max" edouard@3099: > [«@min»,«@max»] edouard@3099: otherwise edouard@3099: > undefined edouard@3099: } edouard@3099: if "position()!=last()" > , edouard@3099: } edouard@3099: Edouard@3408: const "freq" choose { edouard@3412: when "$widget/@freq" edouard@3412: > «$widget/@freq» Edouard@3408: otherwise Edouard@3408: > undefined Edouard@3408: } Edouard@3408: Edouard@3408: | "«@id»": new «$widget/@type»Widget ("«@id»",«$freq»,[«$args»],[«$indexes»],[«$minmaxes»],{ Edouard@2881: apply "$widget", mode="widget_defs" with "hmi_element","."; edouard@2949: | })`if "position()!=last()" > ,` edouard@2949: } edouard@2948: edouard@3017: emit "preamble:local-variable-indexes" { edouard@3017: || edouard@3142: edouard@3017: let hmi_locals = {}; edouard@3017: var last_remote_index = hmitree_types.length - 1; edouard@3017: var next_available_index = hmitree_types.length; edouard@3128: let cookies = new Map(document.cookie.split("; ").map(s=>s.split("="))); edouard@3017: Edouard@3025: const local_defaults = { Edouard@3025: || edouard@3128: foreach "$parsed_widgets/widget[starts-with(@type,'VarInit')]"{ Edouard@3025: if "count(path) != 1" error > VarInit «@id» must have only one variable given. Edouard@3025: if "path/@type != 'PAGE_LOCAL' and path/@type != 'HMI_LOCAL'" error > VarInit «@id» only applies to HMI variable. edouard@3128: > "«path/@value»": edouard@3128: choose { edouard@3128: when "@type = 'VarInitPersistent'" > cookies.has("«path/@value»")?cookies.get("«path/@value»"):«arg[1]/@value» edouard@3128: otherwise > «arg[1]/@value» edouard@3128: } edouard@3128: > \n edouard@3128: if "position()!=last()" > , Edouard@3025: } Edouard@3025: || Edouard@3025: }; edouard@3128: edouard@3128: const persistent_locals = new Set([ edouard@3128: || edouard@3128: foreach "$parsed_widgets/widget[@type='VarInitPersistent']"{ edouard@3128: | "«path/@value»"`if "position()!=last()" > ,` edouard@3128: } edouard@3128: || edouard@3128: ]); edouard@3128: var persistent_indexes = new Map(); Edouard@3022: var cache = hmitree_types.map(_ignored => undefined); edouard@3152: var updates = new Map(); Edouard@3022: edouard@3017: function page_local_index(varname, pagename){ edouard@3017: let pagevars = hmi_locals[pagename]; Edouard@3022: let new_index; edouard@3017: if(pagevars == undefined){ Edouard@3022: new_index = next_available_index++; Edouard@3022: hmi_locals[pagename] = {[varname]:new_index} edouard@3017: } else { edouard@3017: let result = pagevars[varname]; Edouard@3022: if(result != undefined) { Edouard@3022: return result; Edouard@3022: } Edouard@3022: Edouard@3022: new_index = next_available_index++; Edouard@3022: pagevars[varname] = new_index; Edouard@3022: } Edouard@3025: let defaultval = local_defaults[varname]; edouard@3128: if(defaultval != undefined) { Edouard@3025: cache[new_index] = defaultval; edouard@3152: updates.set(new_index, defaultval); edouard@3128: if(persistent_locals.has(varname)) edouard@3128: persistent_indexes.set(new_index, varname); edouard@3128: } Edouard@3022: return new_index; edouard@3017: } edouard@3017: edouard@3017: function hmi_local_index(varname){ edouard@3017: return page_local_index(varname, "HMI_LOCAL"); edouard@3017: } edouard@3017: || edouard@3017: } edouard@3017: edouard@2949: emit "preamble:widget-base-class" { edouard@3017: || Edouard@3019: var pending_widget_animates = []; Edouard@3019: edouard@2949: class Widget { edouard@2963: offset = 0; edouard@2963: frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */ Edouard@2980: unsubscribable = false; Edouard@3019: pending_animate = false; Edouard@3019: Edouard@3408: constructor(elt_id, freq, args, indexes, minmaxes, members){ edouard@2958: this.element_id = elt_id; edouard@2950: this.element = id(elt_id); edouard@2950: this.args = args; edouard@2950: this.indexes = indexes; edouard@3099: this.minmaxes = minmaxes; edouard@2949: Object.keys(members).forEach(prop => this[prop]=members[prop]); edouard@3139: this.lastapply = indexes.map(() => undefined); edouard@3139: this.inhibit = indexes.map(() => undefined); edouard@3139: this.pending = indexes.map(() => undefined); edouard@3139: this.bound_unhinibit = this.unhinibit.bind(this); Edouard@3417: this.forced_frequency = freq; Edouard@3453: this.clip = true; edouard@2949: } edouard@2958: edouard@2951: unsub(){ edouard@2951: /* remove subsribers */ edouard@3005: if(!this.unsubscribable) edouard@3005: for(let i = 0; i < this.indexes.length; i++) { edouard@3139: /* flush updates pending because of inhibition */ edouard@3142: let inhibition = this.inhibit[i]; edouard@3139: if(inhibition != undefined){ edouard@3139: clearTimeout(inhibition); edouard@3142: this.lastapply[i] = undefined; edouard@3142: this.unhinibit(i); edouard@3139: } edouard@3005: let index = this.indexes[i]; edouard@3005: if(this.relativeness[i]) edouard@3005: index += this.offset; Edouard@3022: subscribers(index).delete(this); edouard@3005: } edouard@2951: this.offset = 0; edouard@3005: this.relativeness = undefined; edouard@3005: } edouard@3005: edouard@3017: sub(new_offset=0, relativeness, container_id){ edouard@2951: this.offset = new_offset; edouard@3005: this.relativeness = relativeness; edouard@3017: this.container_id = container_id ; edouard@2951: /* add this's subsribers */ edouard@3005: if(!this.unsubscribable) edouard@3005: for(let i = 0; i < this.indexes.length; i++) { edouard@3017: let index = this.get_variable_index(i); Edouard@3058: if(index == undefined) continue; Edouard@3022: subscribers(index).add(this); edouard@3005: } edouard@2951: need_cache_apply.push(this); edouard@2951: } edouard@2951: edouard@2951: apply_cache() { Edouard@3025: if(!this.unsubscribable) for(let index in this.indexes){ edouard@2951: /* dispatch current cache in newly opened page widgets */ Edouard@3024: let realindex = this.get_variable_index(index); Edouard@3058: if(realindex == undefined) continue; edouard@2951: let cached_val = cache[realindex]; edouard@2951: if(cached_val != undefined) Edouard@3026: this._dispatch(cached_val, cached_val, index); edouard@2951: } edouard@2951: } edouard@2951: edouard@3017: get_variable_index(varnum) { edouard@3017: let index = this.indexes[varnum]; edouard@3017: if(typeof(index) == "string"){ edouard@3017: index = page_local_index(index, this.container_id); edouard@3017: } else { edouard@3017: if(this.relativeness[varnum]){ edouard@3017: index += this.offset; edouard@3017: } edouard@3017: } edouard@3017: return index; edouard@3001: } edouard@3098: edouard@3099: overshot(new_val, max) { edouard@3099: } edouard@3099: edouard@3099: undershot(new_val, min) { edouard@3099: } edouard@3099: edouard@3099: clip_min_max(index, new_val) { edouard@3099: let minmax = this.minmaxes[index]; edouard@3099: if(minmax !== undefined && typeof new_val == "number") { edouard@3099: let [min,max] = minmax; edouard@3099: if(new_val < min){ edouard@3099: this.undershot(new_val, min); edouard@3099: return min; edouard@3099: } edouard@3099: if(new_val > max){ edouard@3099: this.overshot(new_val, max); edouard@3099: return max; edouard@3099: } edouard@3099: } edouard@3099: return new_val; edouard@3099: } edouard@3099: Edouard@3058: change_hmi_value(index, opstr) { Edouard@3058: let realindex = this.get_variable_index(index); Edouard@3058: if(realindex == undefined) return undefined; edouard@3098: let old_val = cache[realindex]; edouard@3098: let new_val = eval_operation_string(old_val, opstr); Edouard@3453: if(this.clip) Edouard@3453: new_val = this.clip_min_max(index, new_val); edouard@3098: return apply_hmi_value(realindex, new_val); edouard@3004: } edouard@3004: edouard@3139: _apply_hmi_value(index, new_val) { Edouard@3058: let realindex = this.get_variable_index(index); Edouard@3058: if(realindex == undefined) return undefined; Edouard@3453: if(this.clip) Edouard@3453: new_val = this.clip_min_max(index, new_val); Edouard@3058: return apply_hmi_value(realindex, new_val); edouard@3004: } edouard@3006: edouard@3139: unhinibit(index){ edouard@3139: this.inhibit[index] = undefined; edouard@3139: let new_val = this.pending[index]; edouard@3139: this.pending[index] = undefined; edouard@3139: return this.apply_hmi_value(index, new_val); edouard@3139: } edouard@3139: edouard@3139: apply_hmi_value(index, new_val) { edouard@3139: if(this.inhibit[index] == undefined){ edouard@3139: let now = Date.now(); edouard@3139: let min_interval = 1000/this.frequency; edouard@3139: let lastapply = this.lastapply[index]; edouard@3139: if(lastapply == undefined || now > lastapply + min_interval){ edouard@3139: this.lastapply[index] = now; edouard@3139: return this._apply_hmi_value(index, new_val); edouard@3139: } edouard@3139: else { edouard@3139: let elapsed = now - lastapply; edouard@3139: this.pending[index] = new_val; edouard@3139: this.inhibit[index] = setTimeout(this.bound_unhinibit, min_interval - elapsed, index); edouard@3139: } edouard@3139: } edouard@3139: else { edouard@3139: this.pending[index] = new_val; edouard@3139: return new_val; edouard@3139: } edouard@3139: } edouard@3139: edouard@3006: new_hmi_value(index, value, oldval) { Edouard@3025: // TODO avoid searching, store index at sub() Edouard@3025: for(let i = 0; i < this.indexes.length; i++) { Edouard@3025: let refindex = this.get_variable_index(i); Edouard@3058: if(refindex == undefined) continue; Edouard@3025: Edouard@3025: if(index == refindex) { Edouard@3026: this._dispatch(value, oldval, i); Edouard@3025: break; Edouard@3025: } edouard@3006: } edouard@3006: } Edouard@3019: Edouard@3026: _dispatch(value, oldval, varnum) { Edouard@3026: let dispatch = this.dispatch; Edouard@3026: if(dispatch != undefined){ Edouard@3026: try { Edouard@3026: dispatch.call(this, value, oldval, varnum); Edouard@3026: } catch(err) { Edouard@3026: console.log(err); Edouard@3026: } Edouard@3026: } Edouard@3026: } Edouard@3026: Edouard@3019: _animate(){ Edouard@3019: this.animate(); Edouard@3019: this.pending_animate = false; Edouard@3019: } Edouard@3019: Edouard@3019: request_animate(){ Edouard@3019: if(!this.pending_animate){ Edouard@3019: pending_widget_animates.push(this); Edouard@3019: this.pending_animate = true; Edouard@3019: requestHMIAnimation(); Edouard@3019: } Edouard@3019: Edouard@3019: } edouard@3125: edouard@3125: activate_activable(eltsub) { edouard@3125: eltsub.inactive.style.display = "none"; edouard@3125: eltsub.active.style.display = ""; edouard@3125: } edouard@3125: edouard@3125: inactivate_activable(eltsub) { edouard@3125: eltsub.active.style.display = "none"; edouard@3125: eltsub.inactive.style.display = ""; edouard@3125: } edouard@2949: } edouard@2949: || edouard@2949: } edouard@2949: edouard@3128: const "excluded_types", "str:split('Page VarInit VarInitPersistent')"; edouard@3120: edouard@3120: // Key to filter unique types edouard@3120: key "TypesKey", "widget", "@type"; edouard@3120: edouard@3007: emit "declarations:hmi-classes" { edouard@3120: const "used_widget_types", """$parsed_widgets/widget[ edouard@3120: generate-id() = generate-id(key('TypesKey', @type)) and edouard@3120: not(@type = $excluded_types)]"""; edouard@2948: apply "$used_widget_types", mode="widget_class"; Edouard@2881: } Edouard@2810: edouard@2949: template "widget", mode="widget_class" edouard@2949: || edouard@2949: class «@type»Widget extends Widget{ edouard@2949: /* empty class, as «@type» widget didn't provide any */ edouard@2949: } edouard@2949: || edouard@2949: Edouard@3119: const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types) and not(@id = $discardable_elements/@id)]/@id"; Edouard@3121: const "hmi_widgets","$hmi_elements[@id = $included_ids]"; edouard@3162: const "result_widgets","$result_svg_ns//*[@id = $hmi_widgets/@id]"; edouard@2955: edouard@3007: emit "declarations:hmi-elements" { edouard@2941: | var hmi_widgets = { Edouard@3121: apply "$hmi_widgets", mode="hmi_widgets"; edouard@2941: | } edouard@2941: } edouard@2941: Edouard@2881: function "defs_by_labels" { Edouard@2881: param "labels","''"; Edouard@2881: param "mandatory","'yes'"; edouard@2920: param "subelements","/.."; Edouard@2881: param "hmi_element"; Edouard@2881: const "widget_type","@type"; Edouard@2881: foreach "str:split($labels)" { Edouard@3452: const "absolute", "starts-with(., '/')"; Edouard@3452: const "name","substring(.,number($absolute)+1)"; Edouard@3452: const "widget","$result_widgets[@id = $hmi_element/@id]"; Edouard@3452: const "elt","($widget//*[not($absolute) and @inkscape:label=$name] | $widget/*[$absolute and @inkscape:label=$name])[1]"; Edouard@2881: choose { edouard@2920: when "not($elt/@id)" { Edouard@2881: if "$mandatory='yes'" { edouard@2920: error > «$widget_type» widget must have a «$name» element Edouard@2834: } Edouard@2881: // otherwise produce nothing Edouard@2797: } Edouard@2881: otherwise { edouard@2920: | «$name»_elt: id("«$elt/@id»"), edouard@2920: if "$subelements" { edouard@2920: | «$name»_sub: { edouard@2920: foreach "str:split($subelements)" { edouard@2920: const "subname","."; edouard@2920: const "subelt","$elt/*[@inkscape:label=$subname][1]"; edouard@2920: choose { edouard@2920: when "not($subelt/@id)" { edouard@2920: if "$mandatory='yes'" { edouard@2920: error > «$widget_type» widget must have a «$name»/«$subname» element edouard@2920: } edouard@2920: | /* missing «$name»/«$subname» element */ edouard@2920: } edouard@2920: otherwise { edouard@2920: | "«$subname»": id("«$subelt/@id»")`if "position()!=last()" > ,` edouard@2920: } edouard@2920: } edouard@2920: } edouard@2920: | }, edouard@2920: } Edouard@2836: } Edouard@2808: } Edouard@2808: } Edouard@2881: } Edouard@2808: edouard@2883: def "func:escape_quotes" { edouard@2883: param "txt"; edouard@2883: // have to use a python string to enter escaped quote Edouard@3024: // const "frstln", "string-length($frst)"; edouard@2883: choose { Edouard@3024: when !"contains($txt,'\"')"! { Edouard@3024: result !"concat(substring-before($txt,'\"'),'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!; edouard@2883: } edouard@2883: otherwise { edouard@2883: result "$txt"; edouard@2883: } edouard@2883: } edouard@2883: } edouard@2883: