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