svghmi/widgets_common.ysl2
changeset 3302 c89fc366bebd
parent 3233 315f17e74ef5
child 3408 13c5cac55ac7
equal deleted inserted replaced
2744:577118ebd179 3302:c89fc366bebd
       
     1 // widgets_common.ysl2
       
     2 
       
     3 in xsl decl labels(*ptr, name="defs_by_labels") alias call-template {
       
     4     with "hmi_element", "$hmi_element";
       
     5     with "labels"{text *ptr};
       
     6     content;
       
     7 };
       
     8 
       
     9 decl optional_labels(*ptr) alias - {
       
    10     /* TODO add some per label xslt variable to check if exist */
       
    11     labels(*ptr){
       
    12         with "mandatory","'no'";
       
    13         content;
       
    14     }
       
    15 };
       
    16 
       
    17 decl activable_labels(*ptr) alias - {
       
    18     optional_labels(*ptr) {
       
    19         with "subelements","'active inactive'";
       
    20         content;
       
    21     }
       
    22 };
       
    23 
       
    24 in xsl decl widget_desc(%name, match="widget[@type='%name']", mode="widget_desc") alias template {
       
    25     type > «@type»
       
    26     content;
       
    27 };
       
    28 
       
    29 in xsl decl widget_class(%name, *clsname="%nameWidget", match="widget[@type='%name']", mode="widget_class") alias template {
       
    30     | class `text **clsname` extends Widget{
       
    31     content;
       
    32     | }
       
    33 };
       
    34 
       
    35 in xsl decl widget_defs(%name, match="widget[@type='%name']", mode="widget_defs") alias template {
       
    36     param "hmi_element";
       
    37     content;
       
    38 };
       
    39 
       
    40 in xsl decl widget_page(%name, match="widget[@type='%name']", mode="widget_page") alias template {
       
    41     param "page_desc";
       
    42     content;
       
    43 };
       
    44 
       
    45 decl gen_index_xhtml alias - {
       
    46     content;
       
    47 };
       
    48 
       
    49 template "svg:*", mode="hmi_widgets" {
       
    50     const "widget", "func:widget(@id)";
       
    51     const "eltid","@id";
       
    52     const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,`
       
    53     const "indexes" foreach "$widget/path" {
       
    54         choose {
       
    55             when "not(@index)" {
       
    56                 choose {
       
    57                     when "not(@type)" {
       
    58                         warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree
       
    59                         > undefined
       
    60                     }
       
    61                     when "@type = 'PAGE_LOCAL'" 
       
    62                         > "«@value»"
       
    63                     when "@type = 'HMI_LOCAL'" 
       
    64                         > hmi_local_index("«@value»")
       
    65                     otherwise 
       
    66                         error > Internal error while processing widget's non indexed HMI tree path : unknown type
       
    67                 }
       
    68             }
       
    69             otherwise {
       
    70                 > «@index»
       
    71             }
       
    72         }
       
    73         if "position()!=last()" > ,
       
    74     }
       
    75 
       
    76     const "minmaxes" foreach "$widget/path" {
       
    77         choose {
       
    78             when "@min and @max"
       
    79                 > [«@min»,«@max»]
       
    80             otherwise
       
    81                 > undefined
       
    82         }
       
    83         if "position()!=last()" > ,
       
    84     }
       
    85 
       
    86     |   "«@id»": new «$widget/@type»Widget ("«@id»",[«$args»],[«$indexes»],[«$minmaxes»],{
       
    87     apply "$widget", mode="widget_defs" with "hmi_element",".";
       
    88     |   })`if "position()!=last()" > ,`
       
    89 }
       
    90 
       
    91 emit "preamble:local-variable-indexes" {
       
    92     ||
       
    93 
       
    94     let hmi_locals = {};
       
    95     var last_remote_index = hmitree_types.length - 1;
       
    96     var next_available_index = hmitree_types.length;
       
    97     let cookies = new Map(document.cookie.split("; ").map(s=>s.split("=")));
       
    98 
       
    99     const local_defaults = {
       
   100     ||
       
   101     foreach "$parsed_widgets/widget[starts-with(@type,'VarInit')]"{
       
   102         if "count(path) != 1" error > VarInit «@id» must have only one variable given.
       
   103         if "path/@type != 'PAGE_LOCAL' and path/@type != 'HMI_LOCAL'" error > VarInit «@id» only applies to HMI variable.
       
   104         >     "«path/@value»":
       
   105         choose {
       
   106             when "@type = 'VarInitPersistent'" > cookies.has("«path/@value»")?cookies.get("«path/@value»"):«arg[1]/@value»
       
   107             otherwise > «arg[1]/@value»
       
   108         }
       
   109         > \n
       
   110         if "position()!=last()" > ,
       
   111     }
       
   112     ||
       
   113     };
       
   114 
       
   115     const persistent_locals = new Set([
       
   116     ||
       
   117     foreach "$parsed_widgets/widget[@type='VarInitPersistent']"{
       
   118     |    "«path/@value»"`if "position()!=last()" > ,`
       
   119     }
       
   120     ||
       
   121     ]);
       
   122     var persistent_indexes = new Map();
       
   123     var cache = hmitree_types.map(_ignored => undefined);
       
   124     var updates = new Map();
       
   125 
       
   126     function page_local_index(varname, pagename){
       
   127         let pagevars = hmi_locals[pagename];
       
   128         let new_index;
       
   129         if(pagevars == undefined){
       
   130             new_index = next_available_index++;
       
   131             hmi_locals[pagename] = {[varname]:new_index}
       
   132         } else {
       
   133             let result = pagevars[varname];
       
   134             if(result != undefined) {
       
   135                 return result;
       
   136             }
       
   137 
       
   138             new_index = next_available_index++;
       
   139             pagevars[varname] = new_index;
       
   140         }
       
   141         let defaultval = local_defaults[varname];
       
   142         if(defaultval != undefined) {
       
   143             cache[new_index] = defaultval; 
       
   144             updates.set(new_index, defaultval);
       
   145             if(persistent_locals.has(varname))
       
   146                 persistent_indexes.set(new_index, varname);
       
   147         }
       
   148         return new_index;
       
   149     }
       
   150 
       
   151     function hmi_local_index(varname){
       
   152         return page_local_index(varname, "HMI_LOCAL");
       
   153     }
       
   154     ||
       
   155 }
       
   156 
       
   157 emit "preamble:widget-base-class" {
       
   158     ||
       
   159     var pending_widget_animates = [];
       
   160 
       
   161     class Widget {
       
   162         offset = 0;
       
   163         frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */
       
   164         unsubscribable = false;
       
   165         pending_animate = false;
       
   166 
       
   167         constructor(elt_id,args,indexes,minmaxes,members){
       
   168             this.element_id = elt_id;
       
   169             this.element = id(elt_id);
       
   170             this.args = args;
       
   171             this.indexes = indexes;
       
   172             this.minmaxes = minmaxes;
       
   173             Object.keys(members).forEach(prop => this[prop]=members[prop]);
       
   174             this.lastapply = indexes.map(() => undefined);
       
   175             this.inhibit = indexes.map(() => undefined);
       
   176             this.pending = indexes.map(() => undefined);
       
   177             this.bound_unhinibit = this.unhinibit.bind(this);
       
   178         }
       
   179 
       
   180         unsub(){
       
   181             /* remove subsribers */
       
   182             if(!this.unsubscribable)
       
   183                 for(let i = 0; i < this.indexes.length; i++) {
       
   184                     /* flush updates pending because of inhibition */
       
   185                     let inhibition = this.inhibit[i];
       
   186                     if(inhibition != undefined){
       
   187                         clearTimeout(inhibition);
       
   188                         this.lastapply[i] = undefined;
       
   189                         this.unhinibit(i);
       
   190                     }
       
   191                     let index = this.indexes[i];
       
   192                     if(this.relativeness[i])
       
   193                         index += this.offset;
       
   194                     subscribers(index).delete(this);
       
   195                 }
       
   196             this.offset = 0;
       
   197             this.relativeness = undefined;
       
   198         }
       
   199 
       
   200         sub(new_offset=0, relativeness, container_id){
       
   201             this.offset = new_offset;
       
   202             this.relativeness = relativeness;
       
   203             this.container_id = container_id ;
       
   204             /* add this's subsribers */
       
   205             if(!this.unsubscribable)
       
   206                 for(let i = 0; i < this.indexes.length; i++) {
       
   207                     let index = this.get_variable_index(i);
       
   208                     if(index == undefined) continue;
       
   209                     subscribers(index).add(this);
       
   210                 }
       
   211             need_cache_apply.push(this); 
       
   212         }
       
   213 
       
   214         apply_cache() {
       
   215             if(!this.unsubscribable) for(let index in this.indexes){
       
   216                 /* dispatch current cache in newly opened page widgets */
       
   217                 let realindex = this.get_variable_index(index);
       
   218                 if(realindex == undefined) continue;
       
   219                 let cached_val = cache[realindex];
       
   220                 if(cached_val != undefined)
       
   221                     this._dispatch(cached_val, cached_val, index);
       
   222             }
       
   223         }
       
   224 
       
   225         get_variable_index(varnum) {
       
   226             let index = this.indexes[varnum];
       
   227             if(typeof(index) == "string"){
       
   228                 index = page_local_index(index, this.container_id);
       
   229             } else {
       
   230                 if(this.relativeness[varnum]){
       
   231                     index += this.offset;
       
   232                 }
       
   233             }
       
   234             return index;
       
   235         }
       
   236 
       
   237         overshot(new_val, max) {
       
   238         }
       
   239 
       
   240         undershot(new_val, min) {
       
   241         }
       
   242 
       
   243         clip_min_max(index, new_val) {
       
   244             let minmax = this.minmaxes[index];
       
   245             if(minmax !== undefined && typeof new_val == "number") {
       
   246                 let [min,max] = minmax;
       
   247                 if(new_val < min){
       
   248                     this.undershot(new_val, min);
       
   249                     return min;
       
   250                 }
       
   251                 if(new_val > max){
       
   252                     this.overshot(new_val, max);
       
   253                     return max;
       
   254                 }
       
   255             }
       
   256             return new_val;
       
   257         }
       
   258 
       
   259         change_hmi_value(index, opstr) {
       
   260             let realindex = this.get_variable_index(index);
       
   261             if(realindex == undefined) return undefined;
       
   262             let old_val = cache[realindex];
       
   263             let new_val = eval_operation_string(old_val, opstr);
       
   264             new_val = this.clip_min_max(index, new_val);
       
   265             return apply_hmi_value(realindex, new_val);
       
   266         }
       
   267 
       
   268         _apply_hmi_value(index, new_val) {
       
   269             let realindex = this.get_variable_index(index);
       
   270             if(realindex == undefined) return undefined;
       
   271             new_val = this.clip_min_max(index, new_val);
       
   272             return apply_hmi_value(realindex, new_val);
       
   273         }
       
   274 
       
   275         unhinibit(index){
       
   276             this.inhibit[index] = undefined;
       
   277             let new_val = this.pending[index];
       
   278             this.pending[index] = undefined;
       
   279             return this.apply_hmi_value(index, new_val);
       
   280         }
       
   281 
       
   282         apply_hmi_value(index, new_val) {
       
   283             if(this.inhibit[index] == undefined){
       
   284                 let now = Date.now();
       
   285                 let min_interval = 1000/this.frequency;
       
   286                 let lastapply = this.lastapply[index];
       
   287                 if(lastapply == undefined || now > lastapply + min_interval){
       
   288                     this.lastapply[index] = now;
       
   289                     return this._apply_hmi_value(index, new_val);
       
   290                 }
       
   291                 else {
       
   292                     let elapsed = now - lastapply;
       
   293                     this.pending[index] = new_val;
       
   294                     this.inhibit[index] = setTimeout(this.bound_unhinibit, min_interval - elapsed, index);
       
   295                 }
       
   296             }
       
   297             else {
       
   298                 this.pending[index] = new_val;
       
   299                 return new_val;
       
   300             }
       
   301         }
       
   302 
       
   303         new_hmi_value(index, value, oldval) {
       
   304             // TODO avoid searching, store index at sub()
       
   305             for(let i = 0; i < this.indexes.length; i++) {
       
   306                 let refindex = this.get_variable_index(i);
       
   307                 if(refindex == undefined) continue;
       
   308 
       
   309                 if(index == refindex) {
       
   310                     this._dispatch(value, oldval, i);
       
   311                     break;
       
   312                 }
       
   313             }
       
   314         }
       
   315         
       
   316         _dispatch(value, oldval, varnum) {
       
   317             let dispatch = this.dispatch;
       
   318             if(dispatch != undefined){
       
   319                 try {
       
   320                     dispatch.call(this, value, oldval, varnum);
       
   321                 } catch(err) {
       
   322                     console.log(err);
       
   323                 }
       
   324             }
       
   325         }
       
   326 
       
   327         _animate(){
       
   328             this.animate();
       
   329             this.pending_animate = false;
       
   330         }
       
   331 
       
   332         request_animate(){
       
   333             if(!this.pending_animate){
       
   334                 pending_widget_animates.push(this);
       
   335                 this.pending_animate = true;
       
   336                 requestHMIAnimation();
       
   337             }
       
   338 
       
   339         }
       
   340 
       
   341         activate_activable(eltsub) {
       
   342             eltsub.inactive.style.display = "none";
       
   343             eltsub.active.style.display = "";
       
   344         }
       
   345 
       
   346         inactivate_activable(eltsub) {
       
   347             eltsub.active.style.display = "none";
       
   348             eltsub.inactive.style.display = "";
       
   349         }
       
   350     }
       
   351     ||
       
   352 }
       
   353 
       
   354 const "excluded_types", "str:split('Page VarInit VarInitPersistent')";
       
   355 
       
   356 // Key to filter unique types
       
   357 key "TypesKey", "widget", "@type";
       
   358 
       
   359 emit "declarations:hmi-classes" {
       
   360     const "used_widget_types", """$parsed_widgets/widget[
       
   361                                     generate-id() = generate-id(key('TypesKey', @type)) and 
       
   362                                     not(@type = $excluded_types)]""";
       
   363     apply "$used_widget_types", mode="widget_class";
       
   364 }
       
   365 
       
   366 template "widget", mode="widget_class"
       
   367 ||
       
   368 class «@type»Widget extends Widget{
       
   369     /* empty class, as «@type» widget didn't provide any */
       
   370 }
       
   371 ||
       
   372 
       
   373 const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types) and not(@id = $discardable_elements/@id)]/@id";
       
   374 const "hmi_widgets","$hmi_elements[@id = $included_ids]";
       
   375 const "result_widgets","$result_svg_ns//*[@id = $hmi_widgets/@id]";
       
   376 
       
   377 emit "declarations:hmi-elements" {
       
   378     | var hmi_widgets = {
       
   379     apply "$hmi_widgets", mode="hmi_widgets";
       
   380     | }
       
   381 }
       
   382 
       
   383 function "defs_by_labels" {
       
   384     param "labels","''";
       
   385     param "mandatory","'yes'";
       
   386     param "subelements","/..";
       
   387     param "hmi_element";
       
   388     const "widget_type","@type";
       
   389     foreach "str:split($labels)" {
       
   390         const "name",".";
       
   391         const "elt","$result_widgets[@id = $hmi_element/@id]//*[@inkscape:label=$name][1]";
       
   392         choose {
       
   393             when "not($elt/@id)" {
       
   394                 if "$mandatory='yes'" {
       
   395                     error > «$widget_type» widget must have a «$name» element
       
   396                 }
       
   397                 // otherwise produce nothing
       
   398             }
       
   399             otherwise {
       
   400                 |     «$name»_elt: id("«$elt/@id»"),
       
   401                 if "$subelements" {
       
   402                 |     «$name»_sub: {
       
   403                     foreach "str:split($subelements)" {
       
   404                         const "subname",".";
       
   405                         const "subelt","$elt/*[@inkscape:label=$subname][1]";
       
   406                         choose {
       
   407                             when "not($subelt/@id)" {
       
   408                                 if "$mandatory='yes'" {
       
   409                                     error > «$widget_type» widget must have a «$name»/«$subname» element
       
   410                                 }
       
   411                 |         /* missing «$name»/«$subname» element */
       
   412                             }
       
   413                             otherwise {
       
   414                 |         "«$subname»": id("«$subelt/@id»")`if "position()!=last()" > ,`
       
   415                             }
       
   416                         }
       
   417                     }
       
   418                 |     },
       
   419                 }
       
   420             }
       
   421         }
       
   422     }
       
   423 }
       
   424 
       
   425 def "func:escape_quotes" {
       
   426     param "txt";
       
   427     // have to use a python string to enter escaped quote
       
   428     // const "frstln", "string-length($frst)";
       
   429     choose {
       
   430         when !"contains($txt,'\"')"! {
       
   431             result !"concat(substring-before($txt,'\"'),'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!;
       
   432         }
       
   433         otherwise {
       
   434             result "$txt";
       
   435         }
       
   436     }
       
   437 }
       
   438