svghmi/gen_index_xhtml.xslt
changeset 3629 f117526d41ba
parent 3621 da020d88db0c
child 3630 921f620577e8
child 3651 e3a29c5b74c4
equal deleted inserted replaced
3628:c1796e57affd 3629:f117526d41ba
   553     <xsl:param name="elt"/>
   553     <xsl:param name="elt"/>
   554     <xsl:variable name="g" select="$geometry[@Id = $elt/@id]"/>
   554     <xsl:variable name="g" select="$geometry[@Id = $elt/@id]"/>
   555     <xsl:variable name="candidates" select="$geometry[@Id != $elt/@id]"/>
   555     <xsl:variable name="candidates" select="$geometry[@Id != $elt/@id]"/>
   556     <func:result select="$candidates[(@Id = $groups/@id and (func:intersect($g, .) = 9)) or &#10;                          (not(@Id = $groups/@id) and (func:intersect($g, .) &gt; 0 ))]"/>
   556     <func:result select="$candidates[(@Id = $groups/@id and (func:intersect($g, .) = 9)) or &#10;                          (not(@Id = $groups/@id) and (func:intersect($g, .) &gt; 0 ))]"/>
   557   </func:function>
   557   </func:function>
       
   558   <func:function name="func:offset">
       
   559     <xsl:param name="elt1"/>
       
   560     <xsl:param name="elt2"/>
       
   561     <xsl:variable name="g1" select="$geometry[@Id = $elt1/@id]"/>
       
   562     <xsl:variable name="g2" select="$geometry[@Id = $elt2/@id]"/>
       
   563     <xsl:variable name="result">
       
   564       <vector>
       
   565         <xsl:attribute name="x">
       
   566           <xsl:value-of select="$g2/@x - $g1/@x"/>
       
   567         </xsl:attribute>
       
   568         <xsl:attribute name="y">
       
   569           <xsl:value-of select="$g2/@y - $g1/@y"/>
       
   570         </xsl:attribute>
       
   571       </vector>
       
   572     </xsl:variable>
       
   573     <func:result select="exsl:node-set($result)"/>
       
   574   </func:function>
   558   <xsl:variable name="hmi_lists_descs" select="$parsed_widgets/widget[@type = 'List']"/>
   575   <xsl:variable name="hmi_lists_descs" select="$parsed_widgets/widget[@type = 'List']"/>
   559   <xsl:variable name="hmi_lists" select="$hmi_elements[@id = $hmi_lists_descs/@id]"/>
   576   <xsl:variable name="hmi_lists" select="$hmi_elements[@id = $hmi_lists_descs/@id]"/>
   560   <xsl:variable name="hmi_textlists_descs" select="$parsed_widgets/widget[@type = 'TextList']"/>
   577   <xsl:variable name="hmi_textlists_descs" select="$parsed_widgets/widget[@type = 'TextList']"/>
   561   <xsl:variable name="hmi_textlists" select="$hmi_elements[@id = $hmi_textlists_descs/@id]"/>
   578   <xsl:variable name="hmi_textlists" select="$hmi_elements[@id = $hmi_textlists_descs/@id]"/>
   562   <xsl:variable name="hmi_textstylelists_descs" select="$parsed_widgets/widget[@type = 'TextStyleList']"/>
   579   <xsl:variable name="hmi_textstylelists_descs" select="$parsed_widgets/widget[@type = 'TextStyleList']"/>
   655   <xsl:variable name="overlapping_geometry" select="exsl:node-set($_overlapping_geometry)"/>
   672   <xsl:variable name="overlapping_geometry" select="exsl:node-set($_overlapping_geometry)"/>
   656   <func:function name="func:all_related_elements">
   673   <func:function name="func:all_related_elements">
   657     <xsl:param name="page"/>
   674     <xsl:param name="page"/>
   658     <xsl:variable name="page_overlapping_geometry" select="$overlapping_geometry/elt[@id = $page/@id]/*"/>
   675     <xsl:variable name="page_overlapping_geometry" select="$overlapping_geometry/elt[@id = $page/@id]/*"/>
   659     <xsl:variable name="page_overlapping_elements" select="//svg:*[@id = $page_overlapping_geometry/@Id]"/>
   676     <xsl:variable name="page_overlapping_elements" select="//svg:*[@id = $page_overlapping_geometry/@Id]"/>
   660     <xsl:variable name="page_sub_elements" select="func:refered_elements($page | $page_overlapping_elements)"/>
   677     <xsl:variable name="page_widgets_elements" select="&#10;        $hmi_elements[not(@id=$page/@id)&#10;                      and descendant-or-self::svg:*/@id = $page_overlapping_elements/@id]&#10;        /descendant-or-self::svg:*"/>
       
   678     <xsl:variable name="page_sub_elements" select="func:refered_elements($page | $page_overlapping_elements | $page_widgets_elements)"/>
   661     <func:result select="$page_sub_elements"/>
   679     <func:result select="$page_sub_elements"/>
   662   </func:function>
   680   </func:function>
   663   <func:function name="func:required_elements">
   681   <func:function name="func:required_elements">
   664     <xsl:param name="pages"/>
   682     <xsl:param name="pages"/>
   665     <xsl:choose>
   683     <xsl:choose>
   888       <xsl:text> </xsl:text>
   906       <xsl:text> </xsl:text>
   889       <xsl:value-of select="@id"/>
   907       <xsl:value-of select="@id"/>
   890       <xsl:text>
   908       <xsl:text>
   891 </xsl:text>
   909 </xsl:text>
   892     </xsl:for-each>
   910     </xsl:for-each>
       
   911     <xsl:text>DISCARDABLES:
       
   912 </xsl:text>
       
   913     <xsl:for-each select="$discardable_elements">
       
   914       <xsl:text> </xsl:text>
       
   915       <xsl:value-of select="@id"/>
       
   916       <xsl:text>
       
   917 </xsl:text>
       
   918     </xsl:for-each>
   893     <xsl:text>In Foreach:
   919     <xsl:text>In Foreach:
   894 </xsl:text>
   920 </xsl:text>
   895     <xsl:for-each select="$in_forEach_widget_ids">
   921     <xsl:for-each select="$in_forEach_widget_ids">
   896       <xsl:text> </xsl:text>
   922       <xsl:text> </xsl:text>
   897       <xsl:value-of select="."/>
   923       <xsl:value-of select="."/>
   942   </xsl:template>
   968   </xsl:template>
   943   <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:text/@inkscape:label[starts-with(., '_')]">
   969   <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:text/@inkscape:label[starts-with(., '_')]">
   944     <xsl:attribute name="{name()}">
   970     <xsl:attribute name="{name()}">
   945       <xsl:value-of select="substring(., 2)"/>
   971       <xsl:value-of select="substring(., 2)"/>
   946     </xsl:attribute>
   972     </xsl:attribute>
       
   973   </xsl:template>
       
   974   <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:rect[@inkscape:label='reference' or @inkscape:label='frame']"/>
       
   975   <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:g[svg:rect/@inkscape:label='frame']">
       
   976     <xsl:variable name="reference_rect" select="(../svg:rect | ../svg:g/svg:rect)[@inkscape:label='reference']"/>
       
   977     <xsl:variable name="frame_rect" select="svg:rect[@inkscape:label='frame']"/>
       
   978     <xsl:variable name="offset" select="func:offset($frame_rect, $reference_rect)"/>
       
   979     <xsl:copy>
       
   980       <xsl:attribute name="svghmi_x_offset">
       
   981         <xsl:value-of select="$offset/vector/@x"/>
       
   982       </xsl:attribute>
       
   983       <xsl:attribute name="svghmi_y_offset">
       
   984         <xsl:value-of select="$offset/vector/@y"/>
       
   985       </xsl:attribute>
       
   986       <xsl:apply-templates mode="inline_svg" select="@* | node()"/>
       
   987     </xsl:copy>
   947   </xsl:template>
   988   </xsl:template>
   948   <xsl:variable name="targets_not_to_unlink" select="$hmi_lists/descendant-or-self::svg:*"/>
   989   <xsl:variable name="targets_not_to_unlink" select="$hmi_lists/descendant-or-self::svg:*"/>
   949   <xsl:variable name="to_unlink" select="$hmi_widgets/descendant-or-self::svg:use"/>
   990   <xsl:variable name="to_unlink" select="$hmi_widgets/descendant-or-self::svg:use"/>
   950   <func:function name="func:is_unlinkable">
   991   <func:function name="func:is_unlinkable">
   951     <xsl:param name="targetid"/>
   992     <xsl:param name="targetid"/>
  1514 </xsl:text>
  1555 </xsl:text>
  1515     <xsl:text>var persistent_indexes = new Map();
  1556     <xsl:text>var persistent_indexes = new Map();
  1516 </xsl:text>
  1557 </xsl:text>
  1517     <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
  1558     <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
  1518 </xsl:text>
  1559 </xsl:text>
  1519     <xsl:text>var updates = new Map();
       
  1520 </xsl:text>
       
  1521     <xsl:text>
  1560     <xsl:text>
  1522 </xsl:text>
  1561 </xsl:text>
  1523     <xsl:text>function page_local_index(varname, pagename){
  1562     <xsl:text>function page_local_index(varname, pagename){
  1524 </xsl:text>
  1563 </xsl:text>
  1525     <xsl:text>    let pagevars = hmi_locals[pagename];
  1564     <xsl:text>    let pagevars = hmi_locals[pagename];
  1528 </xsl:text>
  1567 </xsl:text>
  1529     <xsl:text>    if(pagevars == undefined){
  1568     <xsl:text>    if(pagevars == undefined){
  1530 </xsl:text>
  1569 </xsl:text>
  1531     <xsl:text>        new_index = next_available_index++;
  1570     <xsl:text>        new_index = next_available_index++;
  1532 </xsl:text>
  1571 </xsl:text>
  1533     <xsl:text>        hmi_locals[pagename] = {[varname]:new_index}
  1572     <xsl:text>        hmi_locals[pagename] = {[varname]:new_index};
  1534 </xsl:text>
  1573 </xsl:text>
  1535     <xsl:text>    } else {
  1574     <xsl:text>    } else {
  1536 </xsl:text>
  1575 </xsl:text>
  1537     <xsl:text>        let result = pagevars[varname];
  1576     <xsl:text>        let result = pagevars[varname];
  1538 </xsl:text>
  1577 </xsl:text>
  1553     <xsl:text>    let defaultval = local_defaults[varname];
  1592     <xsl:text>    let defaultval = local_defaults[varname];
  1554 </xsl:text>
  1593 </xsl:text>
  1555     <xsl:text>    if(defaultval != undefined) {
  1594     <xsl:text>    if(defaultval != undefined) {
  1556 </xsl:text>
  1595 </xsl:text>
  1557     <xsl:text>        cache[new_index] = defaultval; 
  1596     <xsl:text>        cache[new_index] = defaultval; 
  1558 </xsl:text>
       
  1559     <xsl:text>        updates.set(new_index, defaultval);
       
  1560 </xsl:text>
  1597 </xsl:text>
  1561     <xsl:text>        if(persistent_locals.has(varname))
  1598     <xsl:text>        if(persistent_locals.has(varname))
  1562 </xsl:text>
  1599 </xsl:text>
  1563     <xsl:text>            persistent_indexes.set(new_index, varname);
  1600     <xsl:text>            persistent_indexes.set(new_index, varname);
  1564 </xsl:text>
  1601 </xsl:text>
  2652     <xsl:text>        this.widget_center = [(widget_pos.x+widget_pos.width/2), (widget_pos.y+widget_pos.height/2)];
  2689     <xsl:text>        this.widget_center = [(widget_pos.x+widget_pos.width/2), (widget_pos.y+widget_pos.height/2)];
  2653 </xsl:text>
  2690 </xsl:text>
  2654     <xsl:text>    }
  2691     <xsl:text>    }
  2655 </xsl:text>
  2692 </xsl:text>
  2656     <xsl:text>}
  2693     <xsl:text>}
       
  2694 </xsl:text>
       
  2695   </xsl:template>
       
  2696   <xsl:template match="widget[@type='Assign']" mode="widget_desc">
       
  2697     <type>
       
  2698       <xsl:value-of select="@type"/>
       
  2699     </type>
       
  2700     <longdesc>
       
  2701       <xsl:text>
       
  2702 </xsl:text>
       
  2703       <xsl:text>Arguments are either:
       
  2704 </xsl:text>
       
  2705       <xsl:text>
       
  2706 </xsl:text>
       
  2707       <xsl:text>- name=value: setting variable with literal value.
       
  2708 </xsl:text>
       
  2709       <xsl:text>- name=other_name: copy variable content into another
       
  2710 </xsl:text>
       
  2711       <xsl:text>
       
  2712 </xsl:text>
       
  2713       <xsl:text>"active"+"inactive" labeled elements can be provided to show feedback when pressed
       
  2714 </xsl:text>
       
  2715       <xsl:text>
       
  2716 </xsl:text>
       
  2717       <xsl:text>Exemples:
       
  2718 </xsl:text>
       
  2719       <xsl:text>
       
  2720 </xsl:text>
       
  2721       <xsl:text>HMI:Assign:notify=1@notify=/PLCVAR
       
  2722 </xsl:text>
       
  2723       <xsl:text>HMI:Assign:ack=2:notify=1@ack=.local_var@notify=/PLCVAR
       
  2724 </xsl:text>
       
  2725       <xsl:text>
       
  2726 </xsl:text>
       
  2727     </longdesc>
       
  2728     <shortdesc>
       
  2729       <xsl:text>Assign variables on click</xsl:text>
       
  2730     </shortdesc>
       
  2731   </xsl:template>
       
  2732   <xsl:template match="widget[@type='Assign']" mode="widget_class">
       
  2733     <xsl:text>class </xsl:text>
       
  2734     <xsl:text>AssignWidget</xsl:text>
       
  2735     <xsl:text> extends Widget{
       
  2736 </xsl:text>
       
  2737     <xsl:text>        frequency = 2;
       
  2738 </xsl:text>
       
  2739     <xsl:text>
       
  2740 </xsl:text>
       
  2741     <xsl:text>        onmouseup(evt) {
       
  2742 </xsl:text>
       
  2743     <xsl:text>            svg_root.removeEventListener("pointerup", this.bound_onmouseup, true);
       
  2744 </xsl:text>
       
  2745     <xsl:text>            if(this.enable_state) {
       
  2746 </xsl:text>
       
  2747     <xsl:text>                this.activity_state = false
       
  2748 </xsl:text>
       
  2749     <xsl:text>                this.request_animate();
       
  2750 </xsl:text>
       
  2751     <xsl:text>                this.assign();
       
  2752 </xsl:text>
       
  2753     <xsl:text>            }
       
  2754 </xsl:text>
       
  2755     <xsl:text>        }
       
  2756 </xsl:text>
       
  2757     <xsl:text>
       
  2758 </xsl:text>
       
  2759     <xsl:text>        onmousedown(){
       
  2760 </xsl:text>
       
  2761     <xsl:text>            if(this.enable_state) {
       
  2762 </xsl:text>
       
  2763     <xsl:text>                svg_root.addEventListener("pointerup", this.bound_onmouseup, true);
       
  2764 </xsl:text>
       
  2765     <xsl:text>                this.activity_state = true;
       
  2766 </xsl:text>
       
  2767     <xsl:text>                this.request_animate();
       
  2768 </xsl:text>
       
  2769     <xsl:text>            }
       
  2770 </xsl:text>
       
  2771     <xsl:text>        }
       
  2772 </xsl:text>
       
  2773     <xsl:text>
       
  2774 </xsl:text>
       
  2775     <xsl:text>}
       
  2776 </xsl:text>
       
  2777   </xsl:template>
       
  2778   <xsl:template match="widget[@type='Assign']" mode="widget_defs">
       
  2779     <xsl:param name="hmi_element"/>
       
  2780     <xsl:variable name="disability">
       
  2781       <xsl:call-template name="defs_by_labels">
       
  2782         <xsl:with-param name="hmi_element" select="$hmi_element"/>
       
  2783         <xsl:with-param name="labels">
       
  2784           <xsl:text>/disabled</xsl:text>
       
  2785         </xsl:with-param>
       
  2786         <xsl:with-param name="mandatory" select="'no'"/>
       
  2787       </xsl:call-template>
       
  2788     </xsl:variable>
       
  2789     <xsl:value-of select="$disability"/>
       
  2790     <xsl:variable name="has_disability" select="string-length($disability)&gt;0"/>
       
  2791     <xsl:text>    activable_sub:{
       
  2792 </xsl:text>
       
  2793     <xsl:variable name="activity">
       
  2794       <xsl:call-template name="defs_by_labels">
       
  2795         <xsl:with-param name="hmi_element" select="$hmi_element"/>
       
  2796         <xsl:with-param name="labels">
       
  2797           <xsl:text>/active /inactive</xsl:text>
       
  2798         </xsl:with-param>
       
  2799         <xsl:with-param name="mandatory">
       
  2800           <xsl:text>no</xsl:text>
       
  2801         </xsl:with-param>
       
  2802       </xsl:call-template>
       
  2803     </xsl:variable>
       
  2804     <xsl:value-of select="$activity"/>
       
  2805     <xsl:variable name="has_activity" select="string-length($activity)&gt;0"/>
       
  2806     <xsl:text>    },
       
  2807 </xsl:text>
       
  2808     <xsl:text>    has_activity: </xsl:text>
       
  2809     <xsl:value-of select="$has_activity"/>
       
  2810     <xsl:text>,
       
  2811 </xsl:text>
       
  2812     <xsl:text>    init: function() {
       
  2813 </xsl:text>
       
  2814     <xsl:text>        this.bound_onmouseup = this.onmouseup.bind(this);
       
  2815 </xsl:text>
       
  2816     <xsl:text>        this.element.addEventListener("pointerdown", this.onmousedown.bind(this));
       
  2817 </xsl:text>
       
  2818     <xsl:text>    },
       
  2819 </xsl:text>
       
  2820     <xsl:text>    assignments: {},
       
  2821 </xsl:text>
       
  2822     <xsl:text>    dispatch: function(value, oldval, varnum) {
       
  2823 </xsl:text>
       
  2824     <xsl:variable name="widget" select="."/>
       
  2825     <xsl:for-each select="path">
       
  2826       <xsl:variable name="varid" select="generate-id()"/>
       
  2827       <xsl:variable name="varnum" select="position()-1"/>
       
  2828       <xsl:if test="@assign">
       
  2829         <xsl:for-each select="$widget/path[@assign]">
       
  2830           <xsl:if test="$varid = generate-id()">
       
  2831             <xsl:text>        if(varnum == </xsl:text>
       
  2832             <xsl:value-of select="$varnum"/>
       
  2833             <xsl:text>) this.assignments["</xsl:text>
       
  2834             <xsl:value-of select="@assign"/>
       
  2835             <xsl:text>"] = value;
       
  2836 </xsl:text>
       
  2837           </xsl:if>
       
  2838         </xsl:for-each>
       
  2839       </xsl:if>
       
  2840     </xsl:for-each>
       
  2841     <xsl:text>    },
       
  2842 </xsl:text>
       
  2843     <xsl:text>    assign: function() {
       
  2844 </xsl:text>
       
  2845     <xsl:variable name="paths" select="path"/>
       
  2846     <xsl:for-each select="arg[contains(@value,'=')]">
       
  2847       <xsl:variable name="name" select="substring-before(@value,'=')"/>
       
  2848       <xsl:variable name="value" select="substring-after(@value,'=')"/>
       
  2849       <xsl:variable name="index">
       
  2850         <xsl:for-each select="$paths">
       
  2851           <xsl:if test="@assign = $name">
       
  2852             <xsl:value-of select="position()-1"/>
       
  2853           </xsl:if>
       
  2854         </xsl:for-each>
       
  2855       </xsl:variable>
       
  2856       <xsl:variable name="isVarName" select="regexp:test($value,'^[a-zA-Z_][a-zA-Z0-9_]+$')"/>
       
  2857       <xsl:choose>
       
  2858         <xsl:when test="$isVarName">
       
  2859           <xsl:text>        const </xsl:text>
       
  2860           <xsl:value-of select="$value"/>
       
  2861           <xsl:text> = this.assignments["</xsl:text>
       
  2862           <xsl:value-of select="$value"/>
       
  2863           <xsl:text>"];
       
  2864 </xsl:text>
       
  2865           <xsl:text>        if(</xsl:text>
       
  2866           <xsl:value-of select="$value"/>
       
  2867           <xsl:text> != undefined)
       
  2868 </xsl:text>
       
  2869           <xsl:text>            this.apply_hmi_value(</xsl:text>
       
  2870           <xsl:value-of select="$index"/>
       
  2871           <xsl:text>, </xsl:text>
       
  2872           <xsl:value-of select="$value"/>
       
  2873           <xsl:text>);
       
  2874 </xsl:text>
       
  2875         </xsl:when>
       
  2876         <xsl:otherwise>
       
  2877           <xsl:text>        this.apply_hmi_value(</xsl:text>
       
  2878           <xsl:value-of select="$index"/>
       
  2879           <xsl:text>, </xsl:text>
       
  2880           <xsl:value-of select="$value"/>
       
  2881           <xsl:text>);
       
  2882 </xsl:text>
       
  2883         </xsl:otherwise>
       
  2884       </xsl:choose>
       
  2885     </xsl:for-each>
       
  2886     <xsl:text>    },
  2657 </xsl:text>
  2887 </xsl:text>
  2658   </xsl:template>
  2888   </xsl:template>
  2659   <xsl:template match="widget[@type='Back']" mode="widget_desc">
  2889   <xsl:template match="widget[@type='Back']" mode="widget_desc">
  2660     <type>
  2890     <type>
  2661       <xsl:value-of select="@type"/>
  2891       <xsl:value-of select="@type"/>
  5921 </xsl:text>
  6151 </xsl:text>
  5922     <xsl:text>        activable = false;
  6152     <xsl:text>        activable = false;
  5923 </xsl:text>
  6153 </xsl:text>
  5924     <xsl:text>        frequency = 2;
  6154     <xsl:text>        frequency = 2;
  5925 </xsl:text>
  6155 </xsl:text>
  5926     <xsl:text>
  6156     <xsl:text>        target_page_is_current_page = false;
  5927 </xsl:text>
  6157 </xsl:text>
  5928     <xsl:text>        make_on_click() {
  6158     <xsl:text>        button_beeing_pressed = false;
  5929 </xsl:text>
  6159 </xsl:text>
  5930     <xsl:text>            let that = this;
  6160     <xsl:text>
  5931 </xsl:text>
  6161 </xsl:text>
  5932     <xsl:text>            const name = this.args[0];
  6162     <xsl:text>        onmouseup(evt) {
  5933 </xsl:text>
  6163 </xsl:text>
  5934     <xsl:text>            return function(evt){
  6164     <xsl:text>            svg_root.removeEventListener("pointerup", this.bound_onmouseup, true);
  5935 </xsl:text>
  6165 </xsl:text>
  5936     <xsl:text>                /* TODO: in order to allow jumps to page selected through
  6166     <xsl:text>            if(this.enable_state) {
  5937 </xsl:text>
  6167 </xsl:text>
  5938     <xsl:text>                   for exemple a dropdown, support path pointing to local
  6168     <xsl:text>                const index =
  5939 </xsl:text>
  6169 </xsl:text>
  5940     <xsl:text>                   variable whom value would be an HMI_TREE index and then
  6170     <xsl:text>                    (this.is_relative &amp;&amp; this.indexes.length &gt; 0) ?
  5941 </xsl:text>
  6171 </xsl:text>
  5942     <xsl:text>                   jump to a relative page not hard-coded in advance
  6172     <xsl:text>                    this.indexes[0] + this.offset : undefined;
  5943 </xsl:text>
  6173 </xsl:text>
  5944     <xsl:text>                */
  6174     <xsl:text>                this.button_beeing_pressed = false;
  5945 </xsl:text>
  6175 </xsl:text>
  5946     <xsl:text>                if(that.enable_state) {
  6176     <xsl:text>                this.activity_state = this.target_page_is_current_page || this.button_beeing_pressed;
  5947 </xsl:text>
  6177 </xsl:text>
  5948     <xsl:text>                    const index =
  6178     <xsl:text>                fading_page_switch(this.args[0], index);
  5949 </xsl:text>
  6179 </xsl:text>
  5950     <xsl:text>                        (that.is_relative &amp;&amp; that.indexes.length &gt; 0) ?
  6180     <xsl:text>                this.notify();
  5951 </xsl:text>
       
  5952     <xsl:text>                        that.indexes[0] + that.offset : undefined;
       
  5953 </xsl:text>
       
  5954     <xsl:text>                    fading_page_switch(name, index);
       
  5955 </xsl:text>
       
  5956     <xsl:text>                    that.notify();
       
  5957 </xsl:text>
       
  5958     <xsl:text>                }
       
  5959 </xsl:text>
  6181 </xsl:text>
  5960     <xsl:text>            }
  6182     <xsl:text>            }
  5961 </xsl:text>
  6183 </xsl:text>
  5962     <xsl:text>        }
  6184     <xsl:text>        }
  5963 </xsl:text>
  6185 </xsl:text>
  5964     <xsl:text>
  6186     <xsl:text>
  5965 </xsl:text>
  6187 </xsl:text>
       
  6188     <xsl:text>        onmousedown(){
       
  6189 </xsl:text>
       
  6190     <xsl:text>            if(this.enable_state) {
       
  6191 </xsl:text>
       
  6192     <xsl:text>                svg_root.addEventListener("pointerup", this.bound_onmouseup, true);
       
  6193 </xsl:text>
       
  6194     <xsl:text>                this.button_beeing_pressed = true;
       
  6195 </xsl:text>
       
  6196     <xsl:text>                this.activity_state = true;
       
  6197 </xsl:text>
       
  6198     <xsl:text>                this.request_animate();
       
  6199 </xsl:text>
       
  6200     <xsl:text>            }
       
  6201 </xsl:text>
       
  6202     <xsl:text>        }
       
  6203 </xsl:text>
       
  6204     <xsl:text>
       
  6205 </xsl:text>
  5966     <xsl:text>        notify_page_change(page_name, index) {
  6206     <xsl:text>        notify_page_change(page_name, index) {
  5967 </xsl:text>
  6207 </xsl:text>
  5968     <xsl:text>            // called from animate()
  6208     <xsl:text>            // called from animate()
  5969 </xsl:text>
  6209 </xsl:text>
  5970     <xsl:text>            if(this.activable) {
  6210     <xsl:text>            if(this.activable) {
  5971 </xsl:text>
  6211 </xsl:text>
  5972     <xsl:text>                const ref_index = this.indexes.length &gt; 0 ? this.indexes[0] + this.offset : undefined;
  6212     <xsl:text>                const ref_index = this.indexes.length &gt; 0 ? this.indexes[0] + this.offset : undefined;
  5973 </xsl:text>
  6213 </xsl:text>
  5974     <xsl:text>                const ref_name = this.args[0];
  6214     <xsl:text>                const ref_name = this.args[0];
  5975 </xsl:text>
  6215 </xsl:text>
  5976     <xsl:text>                this.activity_state = ((ref_name == undefined || ref_name == page_name) &amp;&amp; index == ref_index);
  6216     <xsl:text>                this.target_page_is_current_page = ((ref_name == undefined || ref_name == page_name) &amp;&amp; index == ref_index);
       
  6217 </xsl:text>
       
  6218     <xsl:text>                this.activity_state = this.target_page_is_current_page || this.button_beeing_pressed;
  5977 </xsl:text>
  6219 </xsl:text>
  5978     <xsl:text>                // Since called from animate, update activity directly
  6220     <xsl:text>                // Since called from animate, update activity directly
  5979 </xsl:text>
  6221 </xsl:text>
  5980     <xsl:text>                if(this.enable_displayed_state &amp;&amp; this.has_activity) {
  6222     <xsl:text>                if(this.enable_displayed_state &amp;&amp; this.has_activity) {
  5981 </xsl:text>
  6223 </xsl:text>
  6029     <xsl:text>,
  6271     <xsl:text>,
  6030 </xsl:text>
  6272 </xsl:text>
  6031     <xsl:variable name="jump_disability" select="$has_activity and $has_disability"/>
  6273     <xsl:variable name="jump_disability" select="$has_activity and $has_disability"/>
  6032     <xsl:text>    init: function() {
  6274     <xsl:text>    init: function() {
  6033 </xsl:text>
  6275 </xsl:text>
  6034     <xsl:text>        this.element.onclick = this.make_on_click();
  6276     <xsl:text>        this.bound_onmouseup = this.onmouseup.bind(this);
       
  6277 </xsl:text>
       
  6278     <xsl:text>        this.element.addEventListener("pointerdown", this.onmousedown.bind(this));
  6035 </xsl:text>
  6279 </xsl:text>
  6036     <xsl:if test="$has_activity">
  6280     <xsl:if test="$has_activity">
  6037       <xsl:text>        this.activable = true;
  6281       <xsl:text>        this.activable = true;
  6038 </xsl:text>
  6282 </xsl:text>
  6039     </xsl:if>
  6283     </xsl:if>
 11089 </xsl:text>
 11333 </xsl:text>
 11090           <xsl:text>};
 11334           <xsl:text>};
 11091 </xsl:text>
 11335 </xsl:text>
 11092           <xsl:text>
 11336           <xsl:text>
 11093 </xsl:text>
 11337 </xsl:text>
 11094           <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
 11338           <xsl:text>// Called on requestAnimationFrame, modifies DOM
 11095 </xsl:text>
 11339 </xsl:text>
 11096           <xsl:text>function apply_updates() {
 11340           <xsl:text>var requestAnimationFrameID = null;
 11097 </xsl:text>
 11341 </xsl:text>
 11098           <xsl:text>    updates.forEach((value, index) =&gt; {
 11342           <xsl:text>function animate() {
       
 11343 </xsl:text>
       
 11344           <xsl:text>    let rearm = true;
       
 11345 </xsl:text>
       
 11346           <xsl:text>    do{
       
 11347 </xsl:text>
       
 11348           <xsl:text>        if(page_fading == "pending" || page_fading == "forced"){
       
 11349 </xsl:text>
       
 11350           <xsl:text>            if(page_fading == "pending")
       
 11351 </xsl:text>
       
 11352           <xsl:text>                svg_root.classList.add("fade-out-page");
       
 11353 </xsl:text>
       
 11354           <xsl:text>            page_fading = "in_progress";
       
 11355 </xsl:text>
       
 11356           <xsl:text>            if(page_fading_args.length)
       
 11357 </xsl:text>
       
 11358           <xsl:text>                setTimeout(function(){
       
 11359 </xsl:text>
       
 11360           <xsl:text>                    switch_page(...page_fading_args);
       
 11361 </xsl:text>
       
 11362           <xsl:text>                },1);
       
 11363 </xsl:text>
       
 11364           <xsl:text>            break;
       
 11365 </xsl:text>
       
 11366           <xsl:text>        }
       
 11367 </xsl:text>
       
 11368           <xsl:text>
       
 11369 </xsl:text>
       
 11370           <xsl:text>        // Do the page swith if pending
       
 11371 </xsl:text>
       
 11372           <xsl:text>        if(page_switch_in_progress){
       
 11373 </xsl:text>
       
 11374           <xsl:text>            if(current_subscribed_page != current_visible_page){
       
 11375 </xsl:text>
       
 11376           <xsl:text>                switch_visible_page(current_subscribed_page);
       
 11377 </xsl:text>
       
 11378           <xsl:text>            }
       
 11379 </xsl:text>
       
 11380           <xsl:text>
       
 11381 </xsl:text>
       
 11382           <xsl:text>            page_switch_in_progress = false;
       
 11383 </xsl:text>
       
 11384           <xsl:text>
       
 11385 </xsl:text>
       
 11386           <xsl:text>            if(page_fading == "in_progress"){
       
 11387 </xsl:text>
       
 11388           <xsl:text>                svg_root.classList.remove("fade-out-page");
       
 11389 </xsl:text>
       
 11390           <xsl:text>                page_fading = "off";
       
 11391 </xsl:text>
       
 11392           <xsl:text>            }
       
 11393 </xsl:text>
       
 11394           <xsl:text>        }
       
 11395 </xsl:text>
       
 11396           <xsl:text>
       
 11397 </xsl:text>
       
 11398           <xsl:text>        if(jumps_need_update) update_jumps();
       
 11399 </xsl:text>
       
 11400           <xsl:text>
       
 11401 </xsl:text>
       
 11402           <xsl:text>
       
 11403 </xsl:text>
       
 11404           <xsl:text>        pending_widget_animates.forEach(widget =&gt; widget._animate());
       
 11405 </xsl:text>
       
 11406           <xsl:text>        pending_widget_animates = [];
       
 11407 </xsl:text>
       
 11408           <xsl:text>        rearm = false;
       
 11409 </xsl:text>
       
 11410           <xsl:text>    } while(0);
       
 11411 </xsl:text>
       
 11412           <xsl:text>
       
 11413 </xsl:text>
       
 11414           <xsl:text>    requestAnimationFrameID = null;
       
 11415 </xsl:text>
       
 11416           <xsl:text>
       
 11417 </xsl:text>
       
 11418           <xsl:text>    if(rearm) requestHMIAnimation();
       
 11419 </xsl:text>
       
 11420           <xsl:text>}
       
 11421 </xsl:text>
       
 11422           <xsl:text>
       
 11423 </xsl:text>
       
 11424           <xsl:text>function requestHMIAnimation() {
       
 11425 </xsl:text>
       
 11426           <xsl:text>    if(requestAnimationFrameID == null){
       
 11427 </xsl:text>
       
 11428           <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
 11429 </xsl:text>
       
 11430           <xsl:text>    }
       
 11431 </xsl:text>
       
 11432           <xsl:text>}
       
 11433 </xsl:text>
       
 11434           <xsl:text>
       
 11435 </xsl:text>
       
 11436           <xsl:text>// Message reception handler
       
 11437 </xsl:text>
       
 11438           <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
 11439 </xsl:text>
       
 11440           <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
 11441 </xsl:text>
       
 11442           <xsl:text>ws.onmessage = function (evt) {
       
 11443 </xsl:text>
       
 11444           <xsl:text>
       
 11445 </xsl:text>
       
 11446           <xsl:text>    let data = evt.data;
       
 11447 </xsl:text>
       
 11448           <xsl:text>    let dv = new DataView(data);
       
 11449 </xsl:text>
       
 11450           <xsl:text>    let i = 0;
       
 11451 </xsl:text>
       
 11452           <xsl:text>    try {
       
 11453 </xsl:text>
       
 11454           <xsl:text>        for(let hash_int of hmi_hash) {
       
 11455 </xsl:text>
       
 11456           <xsl:text>            if(hash_int != dv.getUint8(i)){
       
 11457 </xsl:text>
       
 11458           <xsl:text>                throw new Error("Hash doesn't match");
       
 11459 </xsl:text>
       
 11460           <xsl:text>            };
       
 11461 </xsl:text>
       
 11462           <xsl:text>            i++;
       
 11463 </xsl:text>
       
 11464           <xsl:text>        };
       
 11465 </xsl:text>
       
 11466           <xsl:text>
       
 11467 </xsl:text>
       
 11468           <xsl:text>        while(i &lt; data.byteLength){
       
 11469 </xsl:text>
       
 11470           <xsl:text>            let index = dv.getUint32(i, true);
       
 11471 </xsl:text>
       
 11472           <xsl:text>            i += 4;
       
 11473 </xsl:text>
       
 11474           <xsl:text>            let iectype = hmitree_types[index];
       
 11475 </xsl:text>
       
 11476           <xsl:text>            if(iectype != undefined){
       
 11477 </xsl:text>
       
 11478           <xsl:text>                let dvgetter = dvgetters[iectype];
       
 11479 </xsl:text>
       
 11480           <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
 11481 </xsl:text>
       
 11482           <xsl:text>                dispatch_value(index, value);
       
 11483 </xsl:text>
       
 11484           <xsl:text>                i += bytesize;
       
 11485 </xsl:text>
       
 11486           <xsl:text>            } else {
       
 11487 </xsl:text>
       
 11488           <xsl:text>                throw new Error("Unknown index "+index);
       
 11489 </xsl:text>
       
 11490           <xsl:text>            }
       
 11491 </xsl:text>
       
 11492           <xsl:text>        };
       
 11493 </xsl:text>
       
 11494           <xsl:text>
       
 11495 </xsl:text>
       
 11496           <xsl:text>        // register for rendering on next frame, since there are updates
       
 11497 </xsl:text>
       
 11498           <xsl:text>    } catch(err) {
       
 11499 </xsl:text>
       
 11500           <xsl:text>        // 1003 is for "Unsupported Data"
       
 11501 </xsl:text>
       
 11502           <xsl:text>        // ws.close(1003, err.message);
       
 11503 </xsl:text>
       
 11504           <xsl:text>
       
 11505 </xsl:text>
       
 11506           <xsl:text>        // TODO : remove debug alert ?
       
 11507 </xsl:text>
       
 11508           <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
 11509 </xsl:text>
       
 11510           <xsl:text>
       
 11511 </xsl:text>
       
 11512           <xsl:text>        // force reload ignoring cache
       
 11513 </xsl:text>
       
 11514           <xsl:text>        location.reload(true);
       
 11515 </xsl:text>
       
 11516           <xsl:text>    }
       
 11517 </xsl:text>
       
 11518           <xsl:text>};
       
 11519 </xsl:text>
       
 11520           <xsl:text>
       
 11521 </xsl:text>
       
 11522           <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash);
       
 11523 </xsl:text>
       
 11524           <xsl:text>
       
 11525 </xsl:text>
       
 11526           <xsl:text>function send_blob(data) {
       
 11527 </xsl:text>
       
 11528           <xsl:text>    if(data.length &gt; 0) {
       
 11529 </xsl:text>
       
 11530           <xsl:text>        ws.send(new Blob([hmi_hash_u8].concat(data)));
       
 11531 </xsl:text>
       
 11532           <xsl:text>    };
       
 11533 </xsl:text>
       
 11534           <xsl:text>};
       
 11535 </xsl:text>
       
 11536           <xsl:text>
       
 11537 </xsl:text>
       
 11538           <xsl:text>const typedarray_types = {
       
 11539 </xsl:text>
       
 11540           <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
 11541 </xsl:text>
       
 11542           <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
 11543 </xsl:text>
       
 11544           <xsl:text>    NODE: (truth) =&gt; new Int16Array([truth]),
       
 11545 </xsl:text>
       
 11546           <xsl:text>    REAL: (number) =&gt; new Float32Array([number]),
       
 11547 </xsl:text>
       
 11548           <xsl:text>    STRING: (str) =&gt; {
       
 11549 </xsl:text>
       
 11550           <xsl:text>        // beremiz default string max size is 128
       
 11551 </xsl:text>
       
 11552           <xsl:text>        str = str.slice(0,128);
       
 11553 </xsl:text>
       
 11554           <xsl:text>        binary = new Uint8Array(str.length + 1);
       
 11555 </xsl:text>
       
 11556           <xsl:text>        binary[0] = str.length;
       
 11557 </xsl:text>
       
 11558           <xsl:text>        for(let i = 0; i &lt; str.length; i++){
       
 11559 </xsl:text>
       
 11560           <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
 11561 </xsl:text>
       
 11562           <xsl:text>        }
       
 11563 </xsl:text>
       
 11564           <xsl:text>        return binary;
       
 11565 </xsl:text>
       
 11566           <xsl:text>    }
       
 11567 </xsl:text>
       
 11568           <xsl:text>    /* TODO */
       
 11569 </xsl:text>
       
 11570           <xsl:text>};
       
 11571 </xsl:text>
       
 11572           <xsl:text>
       
 11573 </xsl:text>
       
 11574           <xsl:text>function send_reset() {
       
 11575 </xsl:text>
       
 11576           <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
 11577 </xsl:text>
       
 11578           <xsl:text>};
       
 11579 </xsl:text>
       
 11580           <xsl:text>
       
 11581 </xsl:text>
       
 11582           <xsl:text>var subscriptions = [];
       
 11583 </xsl:text>
       
 11584           <xsl:text>
       
 11585 </xsl:text>
       
 11586           <xsl:text>function subscribers(index) {
       
 11587 </xsl:text>
       
 11588           <xsl:text>    let entry = subscriptions[index];
       
 11589 </xsl:text>
       
 11590           <xsl:text>    let res;
       
 11591 </xsl:text>
       
 11592           <xsl:text>    if(entry == undefined){
       
 11593 </xsl:text>
       
 11594           <xsl:text>        res = new Set();
       
 11595 </xsl:text>
       
 11596           <xsl:text>        subscriptions[index] = [res,0];
       
 11597 </xsl:text>
       
 11598           <xsl:text>    }else{
       
 11599 </xsl:text>
       
 11600           <xsl:text>        [res, _ign] = entry;
       
 11601 </xsl:text>
       
 11602           <xsl:text>    }
       
 11603 </xsl:text>
       
 11604           <xsl:text>    return res
       
 11605 </xsl:text>
       
 11606           <xsl:text>}
       
 11607 </xsl:text>
       
 11608           <xsl:text>
       
 11609 </xsl:text>
       
 11610           <xsl:text>function get_subscription_period(index) {
       
 11611 </xsl:text>
       
 11612           <xsl:text>    let entry = subscriptions[index];
       
 11613 </xsl:text>
       
 11614           <xsl:text>    if(entry == undefined)
       
 11615 </xsl:text>
       
 11616           <xsl:text>        return 0;
       
 11617 </xsl:text>
       
 11618           <xsl:text>    let [_ign, period] = entry;
       
 11619 </xsl:text>
       
 11620           <xsl:text>    return period;
       
 11621 </xsl:text>
       
 11622           <xsl:text>}
       
 11623 </xsl:text>
       
 11624           <xsl:text>
       
 11625 </xsl:text>
       
 11626           <xsl:text>function set_subscription_period(index, period) {
       
 11627 </xsl:text>
       
 11628           <xsl:text>    let entry = subscriptions[index];
       
 11629 </xsl:text>
       
 11630           <xsl:text>    if(entry == undefined){
       
 11631 </xsl:text>
       
 11632           <xsl:text>        subscriptions[index] = [new Set(), period];
       
 11633 </xsl:text>
       
 11634           <xsl:text>    } else {
       
 11635 </xsl:text>
       
 11636           <xsl:text>        entry[1] = period;
       
 11637 </xsl:text>
       
 11638           <xsl:text>    }
       
 11639 </xsl:text>
       
 11640           <xsl:text>}
       
 11641 </xsl:text>
       
 11642           <xsl:text>
       
 11643 </xsl:text>
       
 11644           <xsl:text>if(has_watchdog){
       
 11645 </xsl:text>
       
 11646           <xsl:text>    // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
 11647 </xsl:text>
       
 11648           <xsl:text>    // Since dispatch directly calls change_hmi_value,
       
 11649 </xsl:text>
       
 11650           <xsl:text>    // PLC will periodically send variable at given frequency
       
 11651 </xsl:text>
       
 11652           <xsl:text>    subscribers(heartbeat_index).add({
       
 11653 </xsl:text>
       
 11654           <xsl:text>        /* type: "Watchdog", */
       
 11655 </xsl:text>
       
 11656           <xsl:text>        frequency: 1,
       
 11657 </xsl:text>
       
 11658           <xsl:text>        indexes: [heartbeat_index],
       
 11659 </xsl:text>
       
 11660           <xsl:text>        new_hmi_value: function(index, value, oldval) {
       
 11661 </xsl:text>
       
 11662           <xsl:text>            apply_hmi_value(heartbeat_index, value+1);
       
 11663 </xsl:text>
       
 11664           <xsl:text>        }
       
 11665 </xsl:text>
       
 11666           <xsl:text>    });
       
 11667 </xsl:text>
       
 11668           <xsl:text>}
       
 11669 </xsl:text>
       
 11670           <xsl:text>
       
 11671 </xsl:text>
       
 11672           <xsl:text>
       
 11673 </xsl:text>
       
 11674           <xsl:text>var page_fading = "off";
       
 11675 </xsl:text>
       
 11676           <xsl:text>var page_fading_args = "off";
       
 11677 </xsl:text>
       
 11678           <xsl:text>function fading_page_switch(...args){
       
 11679 </xsl:text>
       
 11680           <xsl:text>    if(page_fading == "in_progress")
       
 11681 </xsl:text>
       
 11682           <xsl:text>        page_fading = "forced";
       
 11683 </xsl:text>
       
 11684           <xsl:text>    else
       
 11685 </xsl:text>
       
 11686           <xsl:text>        page_fading = "pending";
       
 11687 </xsl:text>
       
 11688           <xsl:text>    page_fading_args = args;
       
 11689 </xsl:text>
       
 11690           <xsl:text>
       
 11691 </xsl:text>
       
 11692           <xsl:text>    requestHMIAnimation();
       
 11693 </xsl:text>
       
 11694           <xsl:text>
       
 11695 </xsl:text>
       
 11696           <xsl:text>}
       
 11697 </xsl:text>
       
 11698           <xsl:text>document.body.style.backgroundColor = "black";
       
 11699 </xsl:text>
       
 11700           <xsl:text>
       
 11701 </xsl:text>
       
 11702           <xsl:text>// subscribe to per instance current page hmi variable
       
 11703 </xsl:text>
       
 11704           <xsl:text>// PLC must prefix page name with "!" for page switch to happen
       
 11705 </xsl:text>
       
 11706           <xsl:text>subscribers(current_page_var_index).add({
       
 11707 </xsl:text>
       
 11708           <xsl:text>    frequency: 1,
       
 11709 </xsl:text>
       
 11710           <xsl:text>    indexes: [current_page_var_index],
       
 11711 </xsl:text>
       
 11712           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11713 </xsl:text>
       
 11714           <xsl:text>        if(value.startsWith("!"))
       
 11715 </xsl:text>
       
 11716           <xsl:text>            fading_page_switch(value.slice(1));
       
 11717 </xsl:text>
       
 11718           <xsl:text>    }
       
 11719 </xsl:text>
       
 11720           <xsl:text>});
       
 11721 </xsl:text>
       
 11722           <xsl:text>
       
 11723 </xsl:text>
       
 11724           <xsl:text>function svg_text_to_multiline(elt) {
       
 11725 </xsl:text>
       
 11726           <xsl:text>    return(Array.prototype.map.call(elt.children, x=&gt;x.textContent).join("\n")); 
       
 11727 </xsl:text>
       
 11728           <xsl:text>}
       
 11729 </xsl:text>
       
 11730           <xsl:text>
       
 11731 </xsl:text>
       
 11732           <xsl:text>function multiline_to_svg_text(elt, str, blank) {
       
 11733 </xsl:text>
       
 11734           <xsl:text>    str.split('\n').map((line,i) =&gt; {elt.children[i].textContent = blank?"":line;});
       
 11735 </xsl:text>
       
 11736           <xsl:text>}
       
 11737 </xsl:text>
       
 11738           <xsl:text>
       
 11739 </xsl:text>
       
 11740           <xsl:text>function switch_langnum(langnum) {
       
 11741 </xsl:text>
       
 11742           <xsl:text>    langnum = Math.max(0, Math.min(langs.length - 1, langnum));
       
 11743 </xsl:text>
       
 11744           <xsl:text>
       
 11745 </xsl:text>
       
 11746           <xsl:text>    for (let translation of translations) {
       
 11747 </xsl:text>
       
 11748           <xsl:text>        let [objs, msgs] = translation;
       
 11749 </xsl:text>
       
 11750           <xsl:text>        let msg = msgs[langnum];
       
 11751 </xsl:text>
       
 11752           <xsl:text>        for (let obj of objs) {
       
 11753 </xsl:text>
       
 11754           <xsl:text>            multiline_to_svg_text(obj, msg);
       
 11755 </xsl:text>
       
 11756           <xsl:text>            obj.setAttribute("lang",langnum);
       
 11757 </xsl:text>
       
 11758           <xsl:text>        }
       
 11759 </xsl:text>
       
 11760           <xsl:text>    }
       
 11761 </xsl:text>
       
 11762           <xsl:text>    return langnum;
       
 11763 </xsl:text>
       
 11764           <xsl:text>}
       
 11765 </xsl:text>
       
 11766           <xsl:text>
       
 11767 </xsl:text>
       
 11768           <xsl:text>// backup original texts
       
 11769 </xsl:text>
       
 11770           <xsl:text>for (let translation of translations) {
       
 11771 </xsl:text>
       
 11772           <xsl:text>    let [objs, msgs] = translation;
       
 11773 </xsl:text>
       
 11774           <xsl:text>    msgs.unshift(svg_text_to_multiline(objs[0])); 
       
 11775 </xsl:text>
       
 11776           <xsl:text>}
       
 11777 </xsl:text>
       
 11778           <xsl:text>
       
 11779 </xsl:text>
       
 11780           <xsl:text>var lang_local_index = hmi_local_index("lang");
       
 11781 </xsl:text>
       
 11782           <xsl:text>var langcode_local_index = hmi_local_index("lang_code");
       
 11783 </xsl:text>
       
 11784           <xsl:text>var langname_local_index = hmi_local_index("lang_name");
       
 11785 </xsl:text>
       
 11786           <xsl:text>subscribers(lang_local_index).add({
       
 11787 </xsl:text>
       
 11788           <xsl:text>    indexes: [lang_local_index],
       
 11789 </xsl:text>
       
 11790           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11791 </xsl:text>
       
 11792           <xsl:text>        let current_lang =  switch_langnum(value);
       
 11793 </xsl:text>
       
 11794           <xsl:text>        let [langname,langcode] = langs[current_lang];
       
 11795 </xsl:text>
       
 11796           <xsl:text>        apply_hmi_value(langcode_local_index, langcode);
       
 11797 </xsl:text>
       
 11798           <xsl:text>        apply_hmi_value(langname_local_index, langname);
       
 11799 </xsl:text>
       
 11800           <xsl:text>        switch_page();
       
 11801 </xsl:text>
       
 11802           <xsl:text>    }
       
 11803 </xsl:text>
       
 11804           <xsl:text>});
       
 11805 </xsl:text>
       
 11806           <xsl:text>
       
 11807 </xsl:text>
       
 11808           <xsl:text>// returns en_US, fr_FR or en_UK depending on selected language
       
 11809 </xsl:text>
       
 11810           <xsl:text>function get_current_lang_code(){
       
 11811 </xsl:text>
       
 11812           <xsl:text>    return cache[langcode_local_index];
       
 11813 </xsl:text>
       
 11814           <xsl:text>}
       
 11815 </xsl:text>
       
 11816           <xsl:text>
       
 11817 </xsl:text>
       
 11818           <xsl:text>function setup_lang(){
       
 11819 </xsl:text>
       
 11820           <xsl:text>    let current_lang = cache[lang_local_index];
       
 11821 </xsl:text>
       
 11822           <xsl:text>    let new_lang = switch_langnum(current_lang);
       
 11823 </xsl:text>
       
 11824           <xsl:text>    if(current_lang != new_lang){
       
 11825 </xsl:text>
       
 11826           <xsl:text>        apply_hmi_value(lang_local_index, new_lang);
       
 11827 </xsl:text>
       
 11828           <xsl:text>    }
       
 11829 </xsl:text>
       
 11830           <xsl:text>}
       
 11831 </xsl:text>
       
 11832           <xsl:text>
       
 11833 </xsl:text>
       
 11834           <xsl:text>setup_lang();
       
 11835 </xsl:text>
       
 11836           <xsl:text>
       
 11837 </xsl:text>
       
 11838           <xsl:text>function update_subscriptions() {
       
 11839 </xsl:text>
       
 11840           <xsl:text>    let delta = [];
       
 11841 </xsl:text>
       
 11842           <xsl:text>    for(let index in subscriptions){
       
 11843 </xsl:text>
       
 11844           <xsl:text>        let widgets = subscribers(index);
       
 11845 </xsl:text>
       
 11846           <xsl:text>
       
 11847 </xsl:text>
       
 11848           <xsl:text>        // periods are in ms
       
 11849 </xsl:text>
       
 11850           <xsl:text>        let previous_period = get_subscription_period(index);
       
 11851 </xsl:text>
       
 11852           <xsl:text>
       
 11853 </xsl:text>
       
 11854           <xsl:text>        // subscribing with a zero period is unsubscribing
       
 11855 </xsl:text>
       
 11856           <xsl:text>        let new_period = 0;
       
 11857 </xsl:text>
       
 11858           <xsl:text>        if(widgets.size &gt; 0) {
       
 11859 </xsl:text>
       
 11860           <xsl:text>            let maxfreq = 0;
       
 11861 </xsl:text>
       
 11862           <xsl:text>            for(let widget of widgets){
       
 11863 </xsl:text>
       
 11864           <xsl:text>                let wf = widget.frequency;
       
 11865 </xsl:text>
       
 11866           <xsl:text>                if(wf != undefined &amp;&amp; maxfreq &lt; wf)
       
 11867 </xsl:text>
       
 11868           <xsl:text>                    maxfreq = wf;
       
 11869 </xsl:text>
       
 11870           <xsl:text>            }
       
 11871 </xsl:text>
       
 11872           <xsl:text>
       
 11873 </xsl:text>
       
 11874           <xsl:text>            if(maxfreq != 0)
       
 11875 </xsl:text>
       
 11876           <xsl:text>                new_period = 1000/maxfreq;
       
 11877 </xsl:text>
       
 11878           <xsl:text>        }
       
 11879 </xsl:text>
       
 11880           <xsl:text>
       
 11881 </xsl:text>
       
 11882           <xsl:text>        if(previous_period != new_period) {
       
 11883 </xsl:text>
       
 11884           <xsl:text>            set_subscription_period(index, new_period);
       
 11885 </xsl:text>
       
 11886           <xsl:text>            if(index &lt;= last_remote_index){
       
 11887 </xsl:text>
       
 11888           <xsl:text>                delta.push(
       
 11889 </xsl:text>
       
 11890           <xsl:text>                    new Uint8Array([2]), /* subscribe = 2 */
       
 11891 </xsl:text>
       
 11892           <xsl:text>                    new Uint32Array([index]),
       
 11893 </xsl:text>
       
 11894           <xsl:text>                    new Uint16Array([new_period]));
       
 11895 </xsl:text>
       
 11896           <xsl:text>            }
       
 11897 </xsl:text>
       
 11898           <xsl:text>        }
       
 11899 </xsl:text>
       
 11900           <xsl:text>    }
       
 11901 </xsl:text>
       
 11902           <xsl:text>    send_blob(delta);
       
 11903 </xsl:text>
       
 11904           <xsl:text>};
       
 11905 </xsl:text>
       
 11906           <xsl:text>
       
 11907 </xsl:text>
       
 11908           <xsl:text>function send_hmi_value(index, value) {
       
 11909 </xsl:text>
       
 11910           <xsl:text>    if(index &gt; last_remote_index){
 11099 </xsl:text>
 11911 </xsl:text>
 11100           <xsl:text>        dispatch_value(index, value);
 11912           <xsl:text>        dispatch_value(index, value);
 11101 </xsl:text>
 11913 </xsl:text>
       
 11914           <xsl:text>
       
 11915 </xsl:text>
       
 11916           <xsl:text>        if(persistent_indexes.has(index)){
       
 11917 </xsl:text>
       
 11918           <xsl:text>            let varname = persistent_indexes.get(index);
       
 11919 </xsl:text>
       
 11920           <xsl:text>            document.cookie = varname+"="+value+"; max-age=3153600000";
       
 11921 </xsl:text>
       
 11922           <xsl:text>        }
       
 11923 </xsl:text>
       
 11924           <xsl:text>
       
 11925 </xsl:text>
       
 11926           <xsl:text>        return;
       
 11927 </xsl:text>
       
 11928           <xsl:text>    }
       
 11929 </xsl:text>
       
 11930           <xsl:text>
       
 11931 </xsl:text>
       
 11932           <xsl:text>    let iectype = hmitree_types[index];
       
 11933 </xsl:text>
       
 11934           <xsl:text>    let tobinary = typedarray_types[iectype];
       
 11935 </xsl:text>
       
 11936           <xsl:text>    send_blob([
       
 11937 </xsl:text>
       
 11938           <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
 11939 </xsl:text>
       
 11940           <xsl:text>        new Uint32Array([index]),
       
 11941 </xsl:text>
       
 11942           <xsl:text>        tobinary(value)]);
       
 11943 </xsl:text>
       
 11944           <xsl:text>
       
 11945 </xsl:text>
       
 11946           <xsl:text>    // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
       
 11947 </xsl:text>
       
 11948           <xsl:text>    // cache[index] = value;
       
 11949 </xsl:text>
       
 11950           <xsl:text>};
       
 11951 </xsl:text>
       
 11952           <xsl:text>
       
 11953 </xsl:text>
       
 11954           <xsl:text>function apply_hmi_value(index, new_val) {
       
 11955 </xsl:text>
       
 11956           <xsl:text>    // Similarly to previous comment, taking decision to update based 
       
 11957 </xsl:text>
       
 11958           <xsl:text>    // on cache content is bad and can lead to inconsistency
       
 11959 </xsl:text>
       
 11960           <xsl:text>    /*let old_val = cache[index];*/
       
 11961 </xsl:text>
       
 11962           <xsl:text>    if(new_val != undefined /*&amp;&amp; old_val != new_val*/)
       
 11963 </xsl:text>
       
 11964           <xsl:text>        send_hmi_value(index, new_val);
       
 11965 </xsl:text>
       
 11966           <xsl:text>    return new_val;
       
 11967 </xsl:text>
       
 11968           <xsl:text>}
       
 11969 </xsl:text>
       
 11970           <xsl:text>
       
 11971 </xsl:text>
       
 11972           <xsl:text>const quotes = {"'":null, '"':null};
       
 11973 </xsl:text>
       
 11974           <xsl:text>
       
 11975 </xsl:text>
       
 11976           <xsl:text>function eval_operation_string(old_val, opstr) {
       
 11977 </xsl:text>
       
 11978           <xsl:text>    let op = opstr[0];
       
 11979 </xsl:text>
       
 11980           <xsl:text>    let given_val;
       
 11981 </xsl:text>
       
 11982           <xsl:text>    if(opstr.length &lt; 2) 
       
 11983 </xsl:text>
       
 11984           <xsl:text>        return undefined;
       
 11985 </xsl:text>
       
 11986           <xsl:text>    if(opstr[1] in quotes){
       
 11987 </xsl:text>
       
 11988           <xsl:text>        if(opstr.length &lt; 3) 
       
 11989 </xsl:text>
       
 11990           <xsl:text>            return undefined;
       
 11991 </xsl:text>
       
 11992           <xsl:text>        if(opstr[opstr.length-1] == opstr[1]){
       
 11993 </xsl:text>
       
 11994           <xsl:text>            given_val = opstr.slice(2,opstr.length-1);
       
 11995 </xsl:text>
       
 11996           <xsl:text>        }
       
 11997 </xsl:text>
       
 11998           <xsl:text>    } else {
       
 11999 </xsl:text>
       
 12000           <xsl:text>        given_val = Number(opstr.slice(1));
       
 12001 </xsl:text>
       
 12002           <xsl:text>    }
       
 12003 </xsl:text>
       
 12004           <xsl:text>    let new_val;
       
 12005 </xsl:text>
       
 12006           <xsl:text>    switch(op){
       
 12007 </xsl:text>
       
 12008           <xsl:text>      case "=":
       
 12009 </xsl:text>
       
 12010           <xsl:text>        new_val = given_val;
       
 12011 </xsl:text>
       
 12012           <xsl:text>        break;
       
 12013 </xsl:text>
       
 12014           <xsl:text>      case "+":
       
 12015 </xsl:text>
       
 12016           <xsl:text>        new_val = old_val + given_val;
       
 12017 </xsl:text>
       
 12018           <xsl:text>        break;
       
 12019 </xsl:text>
       
 12020           <xsl:text>      case "-":
       
 12021 </xsl:text>
       
 12022           <xsl:text>        new_val = old_val - given_val;
       
 12023 </xsl:text>
       
 12024           <xsl:text>        break;
       
 12025 </xsl:text>
       
 12026           <xsl:text>      case "*":
       
 12027 </xsl:text>
       
 12028           <xsl:text>        new_val = old_val * given_val;
       
 12029 </xsl:text>
       
 12030           <xsl:text>        break;
       
 12031 </xsl:text>
       
 12032           <xsl:text>      case "/":
       
 12033 </xsl:text>
       
 12034           <xsl:text>        new_val = old_val / given_val;
       
 12035 </xsl:text>
       
 12036           <xsl:text>        break;
       
 12037 </xsl:text>
       
 12038           <xsl:text>    }
       
 12039 </xsl:text>
       
 12040           <xsl:text>    return new_val;
       
 12041 </xsl:text>
       
 12042           <xsl:text>}
       
 12043 </xsl:text>
       
 12044           <xsl:text>
       
 12045 </xsl:text>
       
 12046           <xsl:text>var current_visible_page;
       
 12047 </xsl:text>
       
 12048           <xsl:text>var current_subscribed_page;
       
 12049 </xsl:text>
       
 12050           <xsl:text>var current_page_index;
       
 12051 </xsl:text>
       
 12052           <xsl:text>var page_node_local_index = hmi_local_index("page_node");
       
 12053 </xsl:text>
       
 12054           <xsl:text>var page_switch_in_progress = false;
       
 12055 </xsl:text>
       
 12056           <xsl:text>
       
 12057 </xsl:text>
       
 12058           <xsl:text>function toggleFullscreen() {
       
 12059 </xsl:text>
       
 12060           <xsl:text>  let elem = document.documentElement;
       
 12061 </xsl:text>
       
 12062           <xsl:text>
       
 12063 </xsl:text>
       
 12064           <xsl:text>  if (!document.fullscreenElement) {
       
 12065 </xsl:text>
       
 12066           <xsl:text>    elem.requestFullscreen().catch(err =&gt; {
       
 12067 </xsl:text>
       
 12068           <xsl:text>      console.log("Error attempting to enable full-screen mode: "+err.message+" ("+err.name+")");
       
 12069 </xsl:text>
 11102           <xsl:text>    });
 12070           <xsl:text>    });
 11103 </xsl:text>
 12071 </xsl:text>
 11104           <xsl:text>    updates.clear();
 12072           <xsl:text>  } else {
       
 12073 </xsl:text>
       
 12074           <xsl:text>    document.exitFullscreen();
       
 12075 </xsl:text>
       
 12076           <xsl:text>  }
 11105 </xsl:text>
 12077 </xsl:text>
 11106           <xsl:text>}
 12078           <xsl:text>}
 11107 </xsl:text>
 12079 </xsl:text>
 11108           <xsl:text>
 12080           <xsl:text>
 11109 </xsl:text>
 12081 </xsl:text>
 11110           <xsl:text>// Called on requestAnimationFrame, modifies DOM
 12082           <xsl:text>function prepare_svg() {
 11111 </xsl:text>
 12083 </xsl:text>
 11112           <xsl:text>var requestAnimationFrameID = null;
 12084           <xsl:text>    // prevents context menu from appearing on right click and long touch
 11113 </xsl:text>
 12085 </xsl:text>
 11114           <xsl:text>function animate() {
 12086           <xsl:text>    document.body.addEventListener('contextmenu', e =&gt; {
 11115 </xsl:text>
 12087 </xsl:text>
 11116           <xsl:text>    let rearm = true;
 12088           <xsl:text>        toggleFullscreen();
 11117 </xsl:text>
 12089 </xsl:text>
 11118           <xsl:text>    do{
 12090           <xsl:text>        e.preventDefault();
 11119 </xsl:text>
 12091 </xsl:text>
 11120           <xsl:text>        if(page_fading == "pending" || page_fading == "forced"){
 12092           <xsl:text>    });
 11121 </xsl:text>
 12093 </xsl:text>
 11122           <xsl:text>            if(page_fading == "pending")
 12094           <xsl:text>
 11123 </xsl:text>
 12095 </xsl:text>
 11124           <xsl:text>                svg_root.classList.add("fade-out-page");
 12096           <xsl:text>    for(let eltid in detachable_elements){
 11125 </xsl:text>
 12097 </xsl:text>
 11126           <xsl:text>            page_fading = "in_progress";
 12098           <xsl:text>        let [element,parent] = detachable_elements[eltid];
 11127 </xsl:text>
 12099 </xsl:text>
 11128           <xsl:text>            if(page_fading_args.length)
 12100           <xsl:text>        parent.removeChild(element);
 11129 </xsl:text>
 12101 </xsl:text>
 11130           <xsl:text>                setTimeout(function(){
 12102           <xsl:text>    }
 11131 </xsl:text>
 12103 </xsl:text>
 11132           <xsl:text>                    switch_page(...page_fading_args);
 12104           <xsl:text>};
 11133 </xsl:text>
 12105 </xsl:text>
 11134           <xsl:text>                },1);
 12106           <xsl:text>
 11135 </xsl:text>
 12107 </xsl:text>
 11136           <xsl:text>            break;
 12108           <xsl:text>function switch_page(page_name, page_index) {
       
 12109 </xsl:text>
       
 12110           <xsl:text>    if(page_switch_in_progress){
       
 12111 </xsl:text>
       
 12112           <xsl:text>        /* page switch already going */
       
 12113 </xsl:text>
       
 12114           <xsl:text>        /* TODO LOG ERROR */
       
 12115 </xsl:text>
       
 12116           <xsl:text>        return false;
       
 12117 </xsl:text>
       
 12118           <xsl:text>    }
       
 12119 </xsl:text>
       
 12120           <xsl:text>    page_switch_in_progress = true;
       
 12121 </xsl:text>
       
 12122           <xsl:text>
       
 12123 </xsl:text>
       
 12124           <xsl:text>    if(page_name == undefined)
       
 12125 </xsl:text>
       
 12126           <xsl:text>        page_name = current_subscribed_page;
       
 12127 </xsl:text>
       
 12128           <xsl:text>    else if(page_index == undefined){
       
 12129 </xsl:text>
       
 12130           <xsl:text>        [page_name, page_index] = page_name.split('@')
       
 12131 </xsl:text>
       
 12132           <xsl:text>    }
       
 12133 </xsl:text>
       
 12134           <xsl:text>
       
 12135 </xsl:text>
       
 12136           <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
 12137 </xsl:text>
       
 12138           <xsl:text>    let new_desc = page_desc[page_name];
       
 12139 </xsl:text>
       
 12140           <xsl:text>
       
 12141 </xsl:text>
       
 12142           <xsl:text>    if(new_desc == undefined){
       
 12143 </xsl:text>
       
 12144           <xsl:text>        /* TODO LOG ERROR */
       
 12145 </xsl:text>
       
 12146           <xsl:text>        return false;
       
 12147 </xsl:text>
       
 12148           <xsl:text>    }
       
 12149 </xsl:text>
       
 12150           <xsl:text>
       
 12151 </xsl:text>
       
 12152           <xsl:text>    if(page_index == undefined)
       
 12153 </xsl:text>
       
 12154           <xsl:text>        page_index = new_desc.page_index;
       
 12155 </xsl:text>
       
 12156           <xsl:text>    else if(typeof(page_index) == "string") {
       
 12157 </xsl:text>
       
 12158           <xsl:text>        let hmitree_node = hmitree_nodes[page_index];
       
 12159 </xsl:text>
       
 12160           <xsl:text>        if(hmitree_node !== undefined){
       
 12161 </xsl:text>
       
 12162           <xsl:text>            let [int_index, hmiclass] = hmitree_node;
       
 12163 </xsl:text>
       
 12164           <xsl:text>            if(hmiclass == new_desc.page_class)
       
 12165 </xsl:text>
       
 12166           <xsl:text>                page_index = int_index;
       
 12167 </xsl:text>
       
 12168           <xsl:text>            else
       
 12169 </xsl:text>
       
 12170           <xsl:text>                page_index = new_desc.page_index;
       
 12171 </xsl:text>
       
 12172           <xsl:text>        } else {
       
 12173 </xsl:text>
       
 12174           <xsl:text>            page_index = new_desc.page_index;
 11137 </xsl:text>
 12175 </xsl:text>
 11138           <xsl:text>        }
 12176           <xsl:text>        }
 11139 </xsl:text>
 12177 </xsl:text>
 11140           <xsl:text>
 12178           <xsl:text>    }
 11141 </xsl:text>
 12179 </xsl:text>
 11142           <xsl:text>        // Do the page swith if pending
 12180           <xsl:text>
 11143 </xsl:text>
 12181 </xsl:text>
 11144           <xsl:text>        if(page_switch_in_progress){
 12182           <xsl:text>    if(old_desc){
 11145 </xsl:text>
 12183 </xsl:text>
 11146           <xsl:text>            if(current_subscribed_page != current_visible_page){
 12184           <xsl:text>        old_desc.widgets.map(([widget,relativeness])=&gt;widget.unsub());
 11147 </xsl:text>
 12185 </xsl:text>
 11148           <xsl:text>                switch_visible_page(current_subscribed_page);
 12186           <xsl:text>    }
       
 12187 </xsl:text>
       
 12188           <xsl:text>    const new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
 12189 </xsl:text>
       
 12190           <xsl:text>
       
 12191 </xsl:text>
       
 12192           <xsl:text>    const container_id = page_name + (page_index != undefined ? page_index : "");
       
 12193 </xsl:text>
       
 12194           <xsl:text>
       
 12195 </xsl:text>
       
 12196           <xsl:text>    new_desc.widgets.map(([widget,relativeness])=&gt;widget.sub(new_offset,relativeness,container_id));
       
 12197 </xsl:text>
       
 12198           <xsl:text>
       
 12199 </xsl:text>
       
 12200           <xsl:text>    update_subscriptions();
       
 12201 </xsl:text>
       
 12202           <xsl:text>
       
 12203 </xsl:text>
       
 12204           <xsl:text>    current_subscribed_page = page_name;
       
 12205 </xsl:text>
       
 12206           <xsl:text>    current_page_index = page_index;
       
 12207 </xsl:text>
       
 12208           <xsl:text>    let page_node;
       
 12209 </xsl:text>
       
 12210           <xsl:text>    if(page_index != undefined){
       
 12211 </xsl:text>
       
 12212           <xsl:text>        page_node = hmitree_paths[page_index];
       
 12213 </xsl:text>
       
 12214           <xsl:text>    }else{
       
 12215 </xsl:text>
       
 12216           <xsl:text>        page_node = "";
       
 12217 </xsl:text>
       
 12218           <xsl:text>    }
       
 12219 </xsl:text>
       
 12220           <xsl:text>    apply_hmi_value(page_node_local_index, page_node);
       
 12221 </xsl:text>
       
 12222           <xsl:text>
       
 12223 </xsl:text>
       
 12224           <xsl:text>    jumps_need_update = true;
       
 12225 </xsl:text>
       
 12226           <xsl:text>
       
 12227 </xsl:text>
       
 12228           <xsl:text>    requestHMIAnimation();
       
 12229 </xsl:text>
       
 12230           <xsl:text>    jump_history.push([page_name, page_index]);
       
 12231 </xsl:text>
       
 12232           <xsl:text>    if(jump_history.length &gt; 42)
       
 12233 </xsl:text>
       
 12234           <xsl:text>        jump_history.shift();
       
 12235 </xsl:text>
       
 12236           <xsl:text>
       
 12237 </xsl:text>
       
 12238           <xsl:text>    apply_hmi_value(current_page_var_index, page_index == undefined
       
 12239 </xsl:text>
       
 12240           <xsl:text>        ? page_name
       
 12241 </xsl:text>
       
 12242           <xsl:text>        : page_name + "@" + hmitree_paths[page_index]);
       
 12243 </xsl:text>
       
 12244           <xsl:text>
       
 12245 </xsl:text>
       
 12246           <xsl:text>    return true;
       
 12247 </xsl:text>
       
 12248           <xsl:text>};
       
 12249 </xsl:text>
       
 12250           <xsl:text>
       
 12251 </xsl:text>
       
 12252           <xsl:text>function switch_visible_page(page_name) {
       
 12253 </xsl:text>
       
 12254           <xsl:text>
       
 12255 </xsl:text>
       
 12256           <xsl:text>    let old_desc = page_desc[current_visible_page];
       
 12257 </xsl:text>
       
 12258           <xsl:text>    let new_desc = page_desc[page_name];
       
 12259 </xsl:text>
       
 12260           <xsl:text>
       
 12261 </xsl:text>
       
 12262           <xsl:text>    if(old_desc){
       
 12263 </xsl:text>
       
 12264           <xsl:text>        for(let eltid in old_desc.required_detachables){
       
 12265 </xsl:text>
       
 12266           <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
 12267 </xsl:text>
       
 12268           <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
 12269 </xsl:text>
       
 12270           <xsl:text>                parent.removeChild(element);
 11149 </xsl:text>
 12271 </xsl:text>
 11150           <xsl:text>            }
 12272           <xsl:text>            }
 11151 </xsl:text>
 12273 </xsl:text>
 11152           <xsl:text>
 12274           <xsl:text>        }
 11153 </xsl:text>
 12275 </xsl:text>
 11154           <xsl:text>            page_switch_in_progress = false;
 12276           <xsl:text>        for(let eltid in new_desc.required_detachables){
 11155 </xsl:text>
 12277 </xsl:text>
 11156           <xsl:text>
 12278           <xsl:text>            if(!(eltid in old_desc.required_detachables)){
 11157 </xsl:text>
 12279 </xsl:text>
 11158           <xsl:text>            if(page_fading == "in_progress"){
 12280           <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
 11159 </xsl:text>
 12281 </xsl:text>
 11160           <xsl:text>                svg_root.classList.remove("fade-out-page");
 12282           <xsl:text>                parent.appendChild(element);
 11161 </xsl:text>
       
 11162           <xsl:text>                page_fading = "off";
       
 11163 </xsl:text>
 12283 </xsl:text>
 11164           <xsl:text>            }
 12284           <xsl:text>            }
 11165 </xsl:text>
 12285 </xsl:text>
 11166           <xsl:text>        }
 12286           <xsl:text>        }
 11167 </xsl:text>
 12287 </xsl:text>
 11168           <xsl:text>
 12288           <xsl:text>    }else{
 11169 </xsl:text>
 12289 </xsl:text>
 11170           <xsl:text>        if(jumps_need_update) update_jumps();
 12290           <xsl:text>        for(let eltid in new_desc.required_detachables){
 11171 </xsl:text>
 12291 </xsl:text>
 11172           <xsl:text>
 12292           <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
 11173 </xsl:text>
 12293 </xsl:text>
 11174           <xsl:text>
 12294           <xsl:text>            parent.appendChild(element);
 11175 </xsl:text>
 12295 </xsl:text>
 11176           <xsl:text>        pending_widget_animates.forEach(widget =&gt; widget._animate());
 12296           <xsl:text>        }
 11177 </xsl:text>
 12297 </xsl:text>
 11178           <xsl:text>        pending_widget_animates = [];
 12298           <xsl:text>    }
 11179 </xsl:text>
 12299 </xsl:text>
 11180           <xsl:text>        rearm = false;
 12300           <xsl:text>
 11181 </xsl:text>
 12301 </xsl:text>
 11182           <xsl:text>    } while(0);
 12302           <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
 11183 </xsl:text>
 12303 </xsl:text>
 11184           <xsl:text>
 12304           <xsl:text>    current_visible_page = page_name;
 11185 </xsl:text>
 12305 </xsl:text>
 11186           <xsl:text>    requestAnimationFrameID = null;
 12306           <xsl:text>};
 11187 </xsl:text>
 12307 </xsl:text>
 11188           <xsl:text>
 12308           <xsl:text>
 11189 </xsl:text>
 12309 </xsl:text>
 11190           <xsl:text>    if(rearm) requestHMIAnimation();
 12310           <xsl:text>/* From https://jsfiddle.net/ibowankenobi/1mmh7rs6/6/ */
       
 12311 </xsl:text>
       
 12312           <xsl:text>function getAbsoluteCTM(element){
       
 12313 </xsl:text>
       
 12314           <xsl:text>	var height = svg_root.height.baseVal.value,
       
 12315 </xsl:text>
       
 12316           <xsl:text>		width = svg_root.width.baseVal.value,
       
 12317 </xsl:text>
       
 12318           <xsl:text>		viewBoxRect = svg_root.viewBox.baseVal,
       
 12319 </xsl:text>
       
 12320           <xsl:text>		vHeight = viewBoxRect.height,
       
 12321 </xsl:text>
       
 12322           <xsl:text>		vWidth = viewBoxRect.width;
       
 12323 </xsl:text>
       
 12324           <xsl:text>	if(!vWidth || !vHeight){
       
 12325 </xsl:text>
       
 12326           <xsl:text>		return element.getCTM();
       
 12327 </xsl:text>
       
 12328           <xsl:text>	}
       
 12329 </xsl:text>
       
 12330           <xsl:text>	var sH = height/vHeight,
       
 12331 </xsl:text>
       
 12332           <xsl:text>		sW = width/vWidth,
       
 12333 </xsl:text>
       
 12334           <xsl:text>		matrix = svg_root.createSVGMatrix();
       
 12335 </xsl:text>
       
 12336           <xsl:text>	matrix.a = sW;
       
 12337 </xsl:text>
       
 12338           <xsl:text>	matrix.d = sH
       
 12339 </xsl:text>
       
 12340           <xsl:text>	var realCTM = element.getCTM().multiply(matrix.inverse());
       
 12341 </xsl:text>
       
 12342           <xsl:text>	realCTM.e = realCTM.e/sW + viewBoxRect.x;
       
 12343 </xsl:text>
       
 12344           <xsl:text>	realCTM.f = realCTM.f/sH + viewBoxRect.y;
       
 12345 </xsl:text>
       
 12346           <xsl:text>	return realCTM;
 11191 </xsl:text>
 12347 </xsl:text>
 11192           <xsl:text>}
 12348           <xsl:text>}
 11193 </xsl:text>
 12349 </xsl:text>
 11194           <xsl:text>
 12350           <xsl:text>
 11195 </xsl:text>
 12351 </xsl:text>
 11196           <xsl:text>function requestHMIAnimation() {
 12352           <xsl:text>function apply_reference_frames(){
 11197 </xsl:text>
 12353 </xsl:text>
 11198           <xsl:text>    if(requestAnimationFrameID == null){
 12354           <xsl:text>    const matches = svg_root.querySelectorAll("g[svghmi_x_offset]");
 11199 </xsl:text>
 12355 </xsl:text>
 11200           <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
 12356           <xsl:text>    matches.forEach((group) =&gt; {
 11201 </xsl:text>
 12357 </xsl:text>
 11202           <xsl:text>    }
 12358           <xsl:text>        let [x,y] = ["x", "y"].map((axis) =&gt; Number(group.getAttribute("svghmi_"+axis+"_offset")));
       
 12359 </xsl:text>
       
 12360           <xsl:text>        let ctm = getAbsoluteCTM(group);
       
 12361 </xsl:text>
       
 12362           <xsl:text>        // zero translation part of CTM
       
 12363 </xsl:text>
       
 12364           <xsl:text>        // to only apply rotation/skewing to offset vector
       
 12365 </xsl:text>
       
 12366           <xsl:text>        ctm.e = 0;
       
 12367 </xsl:text>
       
 12368           <xsl:text>        ctm.f = 0;
       
 12369 </xsl:text>
       
 12370           <xsl:text>        let invctm = ctm.inverse();
       
 12371 </xsl:text>
       
 12372           <xsl:text>        let vect = new DOMPoint(x, y);
       
 12373 </xsl:text>
       
 12374           <xsl:text>        let newvect = vect.matrixTransform(invctm);
       
 12375 </xsl:text>
       
 12376           <xsl:text>        let transform = svg_root.createSVGTransform();
       
 12377 </xsl:text>
       
 12378           <xsl:text>        transform.setTranslate(newvect.x, newvect.y);
       
 12379 </xsl:text>
       
 12380           <xsl:text>        group.transform.baseVal.appendItem(transform);
       
 12381 </xsl:text>
       
 12382           <xsl:text>        ["x", "y"].forEach((axis) =&gt; group.removeAttribute("svghmi_"+axis+"_offset"));
       
 12383 </xsl:text>
       
 12384           <xsl:text>    });
 11203 </xsl:text>
 12385 </xsl:text>
 11204           <xsl:text>}
 12386           <xsl:text>}
 11205 </xsl:text>
 12387 </xsl:text>
 11206           <xsl:text>
 12388           <xsl:text>
 11207 </xsl:text>
 12389 </xsl:text>
 11208           <xsl:text>// Message reception handler
       
 11209 </xsl:text>
       
 11210           <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
 11211 </xsl:text>
       
 11212           <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
 11213 </xsl:text>
       
 11214           <xsl:text>ws.onmessage = function (evt) {
       
 11215 </xsl:text>
       
 11216           <xsl:text>
       
 11217 </xsl:text>
       
 11218           <xsl:text>    let data = evt.data;
       
 11219 </xsl:text>
       
 11220           <xsl:text>    let dv = new DataView(data);
       
 11221 </xsl:text>
       
 11222           <xsl:text>    let i = 0;
       
 11223 </xsl:text>
       
 11224           <xsl:text>    try {
       
 11225 </xsl:text>
       
 11226           <xsl:text>        for(let hash_int of hmi_hash) {
       
 11227 </xsl:text>
       
 11228           <xsl:text>            if(hash_int != dv.getUint8(i)){
       
 11229 </xsl:text>
       
 11230           <xsl:text>                throw new Error("Hash doesn't match");
       
 11231 </xsl:text>
       
 11232           <xsl:text>            };
       
 11233 </xsl:text>
       
 11234           <xsl:text>            i++;
       
 11235 </xsl:text>
       
 11236           <xsl:text>        };
       
 11237 </xsl:text>
       
 11238           <xsl:text>
       
 11239 </xsl:text>
       
 11240           <xsl:text>        while(i &lt; data.byteLength){
       
 11241 </xsl:text>
       
 11242           <xsl:text>            let index = dv.getUint32(i, true);
       
 11243 </xsl:text>
       
 11244           <xsl:text>            i += 4;
       
 11245 </xsl:text>
       
 11246           <xsl:text>            let iectype = hmitree_types[index];
       
 11247 </xsl:text>
       
 11248           <xsl:text>            if(iectype != undefined){
       
 11249 </xsl:text>
       
 11250           <xsl:text>                let dvgetter = dvgetters[iectype];
       
 11251 </xsl:text>
       
 11252           <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
 11253 </xsl:text>
       
 11254           <xsl:text>                updates.set(index, value);
       
 11255 </xsl:text>
       
 11256           <xsl:text>                i += bytesize;
       
 11257 </xsl:text>
       
 11258           <xsl:text>            } else {
       
 11259 </xsl:text>
       
 11260           <xsl:text>                throw new Error("Unknown index "+index);
       
 11261 </xsl:text>
       
 11262           <xsl:text>            }
       
 11263 </xsl:text>
       
 11264           <xsl:text>        };
       
 11265 </xsl:text>
       
 11266           <xsl:text>
       
 11267 </xsl:text>
       
 11268           <xsl:text>        apply_updates();
       
 11269 </xsl:text>
       
 11270           <xsl:text>        // register for rendering on next frame, since there are updates
       
 11271 </xsl:text>
       
 11272           <xsl:text>    } catch(err) {
       
 11273 </xsl:text>
       
 11274           <xsl:text>        // 1003 is for "Unsupported Data"
       
 11275 </xsl:text>
       
 11276           <xsl:text>        // ws.close(1003, err.message);
       
 11277 </xsl:text>
       
 11278           <xsl:text>
       
 11279 </xsl:text>
       
 11280           <xsl:text>        // TODO : remove debug alert ?
       
 11281 </xsl:text>
       
 11282           <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
 11283 </xsl:text>
       
 11284           <xsl:text>
       
 11285 </xsl:text>
       
 11286           <xsl:text>        // force reload ignoring cache
       
 11287 </xsl:text>
       
 11288           <xsl:text>        location.reload(true);
       
 11289 </xsl:text>
       
 11290           <xsl:text>    }
       
 11291 </xsl:text>
       
 11292           <xsl:text>};
       
 11293 </xsl:text>
       
 11294           <xsl:text>
       
 11295 </xsl:text>
       
 11296           <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash);
       
 11297 </xsl:text>
       
 11298           <xsl:text>
       
 11299 </xsl:text>
       
 11300           <xsl:text>function send_blob(data) {
       
 11301 </xsl:text>
       
 11302           <xsl:text>    if(data.length &gt; 0) {
       
 11303 </xsl:text>
       
 11304           <xsl:text>        ws.send(new Blob([hmi_hash_u8].concat(data)));
       
 11305 </xsl:text>
       
 11306           <xsl:text>    };
       
 11307 </xsl:text>
       
 11308           <xsl:text>};
       
 11309 </xsl:text>
       
 11310           <xsl:text>
       
 11311 </xsl:text>
       
 11312           <xsl:text>const typedarray_types = {
       
 11313 </xsl:text>
       
 11314           <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
 11315 </xsl:text>
       
 11316           <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
 11317 </xsl:text>
       
 11318           <xsl:text>    NODE: (truth) =&gt; new Int16Array([truth]),
       
 11319 </xsl:text>
       
 11320           <xsl:text>    REAL: (number) =&gt; new Float32Array([number]),
       
 11321 </xsl:text>
       
 11322           <xsl:text>    STRING: (str) =&gt; {
       
 11323 </xsl:text>
       
 11324           <xsl:text>        // beremiz default string max size is 128
       
 11325 </xsl:text>
       
 11326           <xsl:text>        str = str.slice(0,128);
       
 11327 </xsl:text>
       
 11328           <xsl:text>        binary = new Uint8Array(str.length + 1);
       
 11329 </xsl:text>
       
 11330           <xsl:text>        binary[0] = str.length;
       
 11331 </xsl:text>
       
 11332           <xsl:text>        for(let i = 0; i &lt; str.length; i++){
       
 11333 </xsl:text>
       
 11334           <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
 11335 </xsl:text>
       
 11336           <xsl:text>        }
       
 11337 </xsl:text>
       
 11338           <xsl:text>        return binary;
       
 11339 </xsl:text>
       
 11340           <xsl:text>    }
       
 11341 </xsl:text>
       
 11342           <xsl:text>    /* TODO */
       
 11343 </xsl:text>
       
 11344           <xsl:text>};
       
 11345 </xsl:text>
       
 11346           <xsl:text>
       
 11347 </xsl:text>
       
 11348           <xsl:text>function send_reset() {
       
 11349 </xsl:text>
       
 11350           <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
 11351 </xsl:text>
       
 11352           <xsl:text>};
       
 11353 </xsl:text>
       
 11354           <xsl:text>
       
 11355 </xsl:text>
       
 11356           <xsl:text>var subscriptions = [];
       
 11357 </xsl:text>
       
 11358           <xsl:text>
       
 11359 </xsl:text>
       
 11360           <xsl:text>function subscribers(index) {
       
 11361 </xsl:text>
       
 11362           <xsl:text>    let entry = subscriptions[index];
       
 11363 </xsl:text>
       
 11364           <xsl:text>    let res;
       
 11365 </xsl:text>
       
 11366           <xsl:text>    if(entry == undefined){
       
 11367 </xsl:text>
       
 11368           <xsl:text>        res = new Set();
       
 11369 </xsl:text>
       
 11370           <xsl:text>        subscriptions[index] = [res,0];
       
 11371 </xsl:text>
       
 11372           <xsl:text>    }else{
       
 11373 </xsl:text>
       
 11374           <xsl:text>        [res, _ign] = entry;
       
 11375 </xsl:text>
       
 11376           <xsl:text>    }
       
 11377 </xsl:text>
       
 11378           <xsl:text>    return res
       
 11379 </xsl:text>
       
 11380           <xsl:text>}
       
 11381 </xsl:text>
       
 11382           <xsl:text>
       
 11383 </xsl:text>
       
 11384           <xsl:text>function get_subscription_period(index) {
       
 11385 </xsl:text>
       
 11386           <xsl:text>    let entry = subscriptions[index];
       
 11387 </xsl:text>
       
 11388           <xsl:text>    if(entry == undefined)
       
 11389 </xsl:text>
       
 11390           <xsl:text>        return 0;
       
 11391 </xsl:text>
       
 11392           <xsl:text>    let [_ign, period] = entry;
       
 11393 </xsl:text>
       
 11394           <xsl:text>    return period;
       
 11395 </xsl:text>
       
 11396           <xsl:text>}
       
 11397 </xsl:text>
       
 11398           <xsl:text>
       
 11399 </xsl:text>
       
 11400           <xsl:text>function set_subscription_period(index, period) {
       
 11401 </xsl:text>
       
 11402           <xsl:text>    let entry = subscriptions[index];
       
 11403 </xsl:text>
       
 11404           <xsl:text>    if(entry == undefined){
       
 11405 </xsl:text>
       
 11406           <xsl:text>        subscriptions[index] = [new Set(), period];
       
 11407 </xsl:text>
       
 11408           <xsl:text>    } else {
       
 11409 </xsl:text>
       
 11410           <xsl:text>        entry[1] = period;
       
 11411 </xsl:text>
       
 11412           <xsl:text>    }
       
 11413 </xsl:text>
       
 11414           <xsl:text>}
       
 11415 </xsl:text>
       
 11416           <xsl:text>
       
 11417 </xsl:text>
       
 11418           <xsl:text>if(has_watchdog){
       
 11419 </xsl:text>
       
 11420           <xsl:text>    // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
 11421 </xsl:text>
       
 11422           <xsl:text>    // Since dispatch directly calls change_hmi_value,
       
 11423 </xsl:text>
       
 11424           <xsl:text>    // PLC will periodically send variable at given frequency
       
 11425 </xsl:text>
       
 11426           <xsl:text>    subscribers(heartbeat_index).add({
       
 11427 </xsl:text>
       
 11428           <xsl:text>        /* type: "Watchdog", */
       
 11429 </xsl:text>
       
 11430           <xsl:text>        frequency: 1,
       
 11431 </xsl:text>
       
 11432           <xsl:text>        indexes: [heartbeat_index],
       
 11433 </xsl:text>
       
 11434           <xsl:text>        new_hmi_value: function(index, value, oldval) {
       
 11435 </xsl:text>
       
 11436           <xsl:text>            apply_hmi_value(heartbeat_index, value+1);
       
 11437 </xsl:text>
       
 11438           <xsl:text>        }
       
 11439 </xsl:text>
       
 11440           <xsl:text>    });
       
 11441 </xsl:text>
       
 11442           <xsl:text>}
       
 11443 </xsl:text>
       
 11444           <xsl:text>
       
 11445 </xsl:text>
       
 11446           <xsl:text>
       
 11447 </xsl:text>
       
 11448           <xsl:text>var page_fading = "off";
       
 11449 </xsl:text>
       
 11450           <xsl:text>var page_fading_args = "off";
       
 11451 </xsl:text>
       
 11452           <xsl:text>function fading_page_switch(...args){
       
 11453 </xsl:text>
       
 11454           <xsl:text>    if(page_fading == "in_progress")
       
 11455 </xsl:text>
       
 11456           <xsl:text>        page_fading = "forced";
       
 11457 </xsl:text>
       
 11458           <xsl:text>    else
       
 11459 </xsl:text>
       
 11460           <xsl:text>        page_fading = "pending";
       
 11461 </xsl:text>
       
 11462           <xsl:text>    page_fading_args = args;
       
 11463 </xsl:text>
       
 11464           <xsl:text>
       
 11465 </xsl:text>
       
 11466           <xsl:text>    requestHMIAnimation();
       
 11467 </xsl:text>
       
 11468           <xsl:text>
       
 11469 </xsl:text>
       
 11470           <xsl:text>}
       
 11471 </xsl:text>
       
 11472           <xsl:text>document.body.style.backgroundColor = "black";
       
 11473 </xsl:text>
       
 11474           <xsl:text>
       
 11475 </xsl:text>
       
 11476           <xsl:text>// subscribe to per instance current page hmi variable
       
 11477 </xsl:text>
       
 11478           <xsl:text>// PLC must prefix page name with "!" for page switch to happen
       
 11479 </xsl:text>
       
 11480           <xsl:text>subscribers(current_page_var_index).add({
       
 11481 </xsl:text>
       
 11482           <xsl:text>    frequency: 1,
       
 11483 </xsl:text>
       
 11484           <xsl:text>    indexes: [current_page_var_index],
       
 11485 </xsl:text>
       
 11486           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11487 </xsl:text>
       
 11488           <xsl:text>        if(value.startsWith("!"))
       
 11489 </xsl:text>
       
 11490           <xsl:text>            fading_page_switch(value.slice(1));
       
 11491 </xsl:text>
       
 11492           <xsl:text>    }
       
 11493 </xsl:text>
       
 11494           <xsl:text>});
       
 11495 </xsl:text>
       
 11496           <xsl:text>
       
 11497 </xsl:text>
       
 11498           <xsl:text>function svg_text_to_multiline(elt) {
       
 11499 </xsl:text>
       
 11500           <xsl:text>    return(Array.prototype.map.call(elt.children, x=&gt;x.textContent).join("\n")); 
       
 11501 </xsl:text>
       
 11502           <xsl:text>}
       
 11503 </xsl:text>
       
 11504           <xsl:text>
       
 11505 </xsl:text>
       
 11506           <xsl:text>function multiline_to_svg_text(elt, str, blank) {
       
 11507 </xsl:text>
       
 11508           <xsl:text>    str.split('\n').map((line,i) =&gt; {elt.children[i].textContent = blank?"":line;});
       
 11509 </xsl:text>
       
 11510           <xsl:text>}
       
 11511 </xsl:text>
       
 11512           <xsl:text>
       
 11513 </xsl:text>
       
 11514           <xsl:text>function switch_langnum(langnum) {
       
 11515 </xsl:text>
       
 11516           <xsl:text>    langnum = Math.max(0, Math.min(langs.length - 1, langnum));
       
 11517 </xsl:text>
       
 11518           <xsl:text>
       
 11519 </xsl:text>
       
 11520           <xsl:text>    for (let translation of translations) {
       
 11521 </xsl:text>
       
 11522           <xsl:text>        let [objs, msgs] = translation;
       
 11523 </xsl:text>
       
 11524           <xsl:text>        let msg = msgs[langnum];
       
 11525 </xsl:text>
       
 11526           <xsl:text>        for (let obj of objs) {
       
 11527 </xsl:text>
       
 11528           <xsl:text>            multiline_to_svg_text(obj, msg);
       
 11529 </xsl:text>
       
 11530           <xsl:text>            obj.setAttribute("lang",langnum);
       
 11531 </xsl:text>
       
 11532           <xsl:text>        }
       
 11533 </xsl:text>
       
 11534           <xsl:text>    }
       
 11535 </xsl:text>
       
 11536           <xsl:text>    return langnum;
       
 11537 </xsl:text>
       
 11538           <xsl:text>}
       
 11539 </xsl:text>
       
 11540           <xsl:text>
       
 11541 </xsl:text>
       
 11542           <xsl:text>// backup original texts
       
 11543 </xsl:text>
       
 11544           <xsl:text>for (let translation of translations) {
       
 11545 </xsl:text>
       
 11546           <xsl:text>    let [objs, msgs] = translation;
       
 11547 </xsl:text>
       
 11548           <xsl:text>    msgs.unshift(svg_text_to_multiline(objs[0])); 
       
 11549 </xsl:text>
       
 11550           <xsl:text>}
       
 11551 </xsl:text>
       
 11552           <xsl:text>
       
 11553 </xsl:text>
       
 11554           <xsl:text>var lang_local_index = hmi_local_index("lang");
       
 11555 </xsl:text>
       
 11556           <xsl:text>var langcode_local_index = hmi_local_index("lang_code");
       
 11557 </xsl:text>
       
 11558           <xsl:text>var langname_local_index = hmi_local_index("lang_name");
       
 11559 </xsl:text>
       
 11560           <xsl:text>subscribers(lang_local_index).add({
       
 11561 </xsl:text>
       
 11562           <xsl:text>    indexes: [lang_local_index],
       
 11563 </xsl:text>
       
 11564           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11565 </xsl:text>
       
 11566           <xsl:text>        let current_lang =  switch_langnum(value);
       
 11567 </xsl:text>
       
 11568           <xsl:text>        let [langname,langcode] = langs[current_lang];
       
 11569 </xsl:text>
       
 11570           <xsl:text>        apply_hmi_value(langcode_local_index, langcode);
       
 11571 </xsl:text>
       
 11572           <xsl:text>        apply_hmi_value(langname_local_index, langname);
       
 11573 </xsl:text>
       
 11574           <xsl:text>        switch_page();
       
 11575 </xsl:text>
       
 11576           <xsl:text>    }
       
 11577 </xsl:text>
       
 11578           <xsl:text>});
       
 11579 </xsl:text>
       
 11580           <xsl:text>
       
 11581 </xsl:text>
       
 11582           <xsl:text>// returns en_US, fr_FR or en_UK depending on selected language
       
 11583 </xsl:text>
       
 11584           <xsl:text>function get_current_lang_code(){
       
 11585 </xsl:text>
       
 11586           <xsl:text>    return cache[langcode_local_index];
       
 11587 </xsl:text>
       
 11588           <xsl:text>}
       
 11589 </xsl:text>
       
 11590           <xsl:text>
       
 11591 </xsl:text>
       
 11592           <xsl:text>function setup_lang(){
       
 11593 </xsl:text>
       
 11594           <xsl:text>    let current_lang = cache[lang_local_index];
       
 11595 </xsl:text>
       
 11596           <xsl:text>    let new_lang = switch_langnum(current_lang);
       
 11597 </xsl:text>
       
 11598           <xsl:text>    if(current_lang != new_lang){
       
 11599 </xsl:text>
       
 11600           <xsl:text>        apply_hmi_value(lang_local_index, new_lang);
       
 11601 </xsl:text>
       
 11602           <xsl:text>    }
       
 11603 </xsl:text>
       
 11604           <xsl:text>}
       
 11605 </xsl:text>
       
 11606           <xsl:text>
       
 11607 </xsl:text>
       
 11608           <xsl:text>setup_lang();
       
 11609 </xsl:text>
       
 11610           <xsl:text>
       
 11611 </xsl:text>
       
 11612           <xsl:text>function update_subscriptions() {
       
 11613 </xsl:text>
       
 11614           <xsl:text>    let delta = [];
       
 11615 </xsl:text>
       
 11616           <xsl:text>    for(let index in subscriptions){
       
 11617 </xsl:text>
       
 11618           <xsl:text>        let widgets = subscribers(index);
       
 11619 </xsl:text>
       
 11620           <xsl:text>
       
 11621 </xsl:text>
       
 11622           <xsl:text>        // periods are in ms
       
 11623 </xsl:text>
       
 11624           <xsl:text>        let previous_period = get_subscription_period(index);
       
 11625 </xsl:text>
       
 11626           <xsl:text>
       
 11627 </xsl:text>
       
 11628           <xsl:text>        // subscribing with a zero period is unsubscribing
       
 11629 </xsl:text>
       
 11630           <xsl:text>        let new_period = 0;
       
 11631 </xsl:text>
       
 11632           <xsl:text>        if(widgets.size &gt; 0) {
       
 11633 </xsl:text>
       
 11634           <xsl:text>            let maxfreq = 0;
       
 11635 </xsl:text>
       
 11636           <xsl:text>            for(let widget of widgets){
       
 11637 </xsl:text>
       
 11638           <xsl:text>                let wf = widget.frequency;
       
 11639 </xsl:text>
       
 11640           <xsl:text>                if(wf != undefined &amp;&amp; maxfreq &lt; wf)
       
 11641 </xsl:text>
       
 11642           <xsl:text>                    maxfreq = wf;
       
 11643 </xsl:text>
       
 11644           <xsl:text>            }
       
 11645 </xsl:text>
       
 11646           <xsl:text>
       
 11647 </xsl:text>
       
 11648           <xsl:text>            if(maxfreq != 0)
       
 11649 </xsl:text>
       
 11650           <xsl:text>                new_period = 1000/maxfreq;
       
 11651 </xsl:text>
       
 11652           <xsl:text>        }
       
 11653 </xsl:text>
       
 11654           <xsl:text>
       
 11655 </xsl:text>
       
 11656           <xsl:text>        if(previous_period != new_period) {
       
 11657 </xsl:text>
       
 11658           <xsl:text>            set_subscription_period(index, new_period);
       
 11659 </xsl:text>
       
 11660           <xsl:text>            if(index &lt;= last_remote_index){
       
 11661 </xsl:text>
       
 11662           <xsl:text>                delta.push(
       
 11663 </xsl:text>
       
 11664           <xsl:text>                    new Uint8Array([2]), /* subscribe = 2 */
       
 11665 </xsl:text>
       
 11666           <xsl:text>                    new Uint32Array([index]),
       
 11667 </xsl:text>
       
 11668           <xsl:text>                    new Uint16Array([new_period]));
       
 11669 </xsl:text>
       
 11670           <xsl:text>            }
       
 11671 </xsl:text>
       
 11672           <xsl:text>        }
       
 11673 </xsl:text>
       
 11674           <xsl:text>    }
       
 11675 </xsl:text>
       
 11676           <xsl:text>    send_blob(delta);
       
 11677 </xsl:text>
       
 11678           <xsl:text>};
       
 11679 </xsl:text>
       
 11680           <xsl:text>
       
 11681 </xsl:text>
       
 11682           <xsl:text>function send_hmi_value(index, value) {
       
 11683 </xsl:text>
       
 11684           <xsl:text>    if(index &gt; last_remote_index){
       
 11685 </xsl:text>
       
 11686           <xsl:text>        dispatch_value(index, value);
       
 11687 </xsl:text>
       
 11688           <xsl:text>
       
 11689 </xsl:text>
       
 11690           <xsl:text>        if(persistent_indexes.has(index)){
       
 11691 </xsl:text>
       
 11692           <xsl:text>            let varname = persistent_indexes.get(index);
       
 11693 </xsl:text>
       
 11694           <xsl:text>            document.cookie = varname+"="+value+"; max-age=3153600000";
       
 11695 </xsl:text>
       
 11696           <xsl:text>        }
       
 11697 </xsl:text>
       
 11698           <xsl:text>
       
 11699 </xsl:text>
       
 11700           <xsl:text>        return;
       
 11701 </xsl:text>
       
 11702           <xsl:text>    }
       
 11703 </xsl:text>
       
 11704           <xsl:text>
       
 11705 </xsl:text>
       
 11706           <xsl:text>    let iectype = hmitree_types[index];
       
 11707 </xsl:text>
       
 11708           <xsl:text>    let tobinary = typedarray_types[iectype];
       
 11709 </xsl:text>
       
 11710           <xsl:text>    send_blob([
       
 11711 </xsl:text>
       
 11712           <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
 11713 </xsl:text>
       
 11714           <xsl:text>        new Uint32Array([index]),
       
 11715 </xsl:text>
       
 11716           <xsl:text>        tobinary(value)]);
       
 11717 </xsl:text>
       
 11718           <xsl:text>
       
 11719 </xsl:text>
       
 11720           <xsl:text>    // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
       
 11721 </xsl:text>
       
 11722           <xsl:text>    // cache[index] = value;
       
 11723 </xsl:text>
       
 11724           <xsl:text>};
       
 11725 </xsl:text>
       
 11726           <xsl:text>
       
 11727 </xsl:text>
       
 11728           <xsl:text>function apply_hmi_value(index, new_val) {
       
 11729 </xsl:text>
       
 11730           <xsl:text>    // Similarly to previous comment, taking decision to update based 
       
 11731 </xsl:text>
       
 11732           <xsl:text>    // on cache content is bad and can lead to inconsistency
       
 11733 </xsl:text>
       
 11734           <xsl:text>    /*let old_val = cache[index];*/
       
 11735 </xsl:text>
       
 11736           <xsl:text>    if(new_val != undefined /*&amp;&amp; old_val != new_val*/)
       
 11737 </xsl:text>
       
 11738           <xsl:text>        send_hmi_value(index, new_val);
       
 11739 </xsl:text>
       
 11740           <xsl:text>    return new_val;
       
 11741 </xsl:text>
       
 11742           <xsl:text>}
       
 11743 </xsl:text>
       
 11744           <xsl:text>
       
 11745 </xsl:text>
       
 11746           <xsl:text>const quotes = {"'":null, '"':null};
       
 11747 </xsl:text>
       
 11748           <xsl:text>
       
 11749 </xsl:text>
       
 11750           <xsl:text>function eval_operation_string(old_val, opstr) {
       
 11751 </xsl:text>
       
 11752           <xsl:text>    let op = opstr[0];
       
 11753 </xsl:text>
       
 11754           <xsl:text>    let given_val;
       
 11755 </xsl:text>
       
 11756           <xsl:text>    if(opstr.length &lt; 2) 
       
 11757 </xsl:text>
       
 11758           <xsl:text>        return undefined;
       
 11759 </xsl:text>
       
 11760           <xsl:text>    if(opstr[1] in quotes){
       
 11761 </xsl:text>
       
 11762           <xsl:text>        if(opstr.length &lt; 3) 
       
 11763 </xsl:text>
       
 11764           <xsl:text>            return undefined;
       
 11765 </xsl:text>
       
 11766           <xsl:text>        if(opstr[opstr.length-1] == opstr[1]){
       
 11767 </xsl:text>
       
 11768           <xsl:text>            given_val = opstr.slice(2,opstr.length-1);
       
 11769 </xsl:text>
       
 11770           <xsl:text>        }
       
 11771 </xsl:text>
       
 11772           <xsl:text>    } else {
       
 11773 </xsl:text>
       
 11774           <xsl:text>        given_val = Number(opstr.slice(1));
       
 11775 </xsl:text>
       
 11776           <xsl:text>    }
       
 11777 </xsl:text>
       
 11778           <xsl:text>    let new_val;
       
 11779 </xsl:text>
       
 11780           <xsl:text>    switch(op){
       
 11781 </xsl:text>
       
 11782           <xsl:text>      case "=":
       
 11783 </xsl:text>
       
 11784           <xsl:text>        new_val = given_val;
       
 11785 </xsl:text>
       
 11786           <xsl:text>        break;
       
 11787 </xsl:text>
       
 11788           <xsl:text>      case "+":
       
 11789 </xsl:text>
       
 11790           <xsl:text>        new_val = old_val + given_val;
       
 11791 </xsl:text>
       
 11792           <xsl:text>        break;
       
 11793 </xsl:text>
       
 11794           <xsl:text>      case "-":
       
 11795 </xsl:text>
       
 11796           <xsl:text>        new_val = old_val - given_val;
       
 11797 </xsl:text>
       
 11798           <xsl:text>        break;
       
 11799 </xsl:text>
       
 11800           <xsl:text>      case "*":
       
 11801 </xsl:text>
       
 11802           <xsl:text>        new_val = old_val * given_val;
       
 11803 </xsl:text>
       
 11804           <xsl:text>        break;
       
 11805 </xsl:text>
       
 11806           <xsl:text>      case "/":
       
 11807 </xsl:text>
       
 11808           <xsl:text>        new_val = old_val / given_val;
       
 11809 </xsl:text>
       
 11810           <xsl:text>        break;
       
 11811 </xsl:text>
       
 11812           <xsl:text>    }
       
 11813 </xsl:text>
       
 11814           <xsl:text>    return new_val;
       
 11815 </xsl:text>
       
 11816           <xsl:text>}
       
 11817 </xsl:text>
       
 11818           <xsl:text>
       
 11819 </xsl:text>
       
 11820           <xsl:text>var current_visible_page;
       
 11821 </xsl:text>
       
 11822           <xsl:text>var current_subscribed_page;
       
 11823 </xsl:text>
       
 11824           <xsl:text>var current_page_index;
       
 11825 </xsl:text>
       
 11826           <xsl:text>var page_node_local_index = hmi_local_index("page_node");
       
 11827 </xsl:text>
       
 11828           <xsl:text>var page_switch_in_progress = false;
       
 11829 </xsl:text>
       
 11830           <xsl:text>
       
 11831 </xsl:text>
       
 11832           <xsl:text>function toggleFullscreen() {
       
 11833 </xsl:text>
       
 11834           <xsl:text>  let elem = document.documentElement;
       
 11835 </xsl:text>
       
 11836           <xsl:text>
       
 11837 </xsl:text>
       
 11838           <xsl:text>  if (!document.fullscreenElement) {
       
 11839 </xsl:text>
       
 11840           <xsl:text>    elem.requestFullscreen().catch(err =&gt; {
       
 11841 </xsl:text>
       
 11842           <xsl:text>      console.log("Error attempting to enable full-screen mode: "+err.message+" ("+err.name+")");
       
 11843 </xsl:text>
       
 11844           <xsl:text>    });
       
 11845 </xsl:text>
       
 11846           <xsl:text>  } else {
       
 11847 </xsl:text>
       
 11848           <xsl:text>    document.exitFullscreen();
       
 11849 </xsl:text>
       
 11850           <xsl:text>  }
       
 11851 </xsl:text>
       
 11852           <xsl:text>}
       
 11853 </xsl:text>
       
 11854           <xsl:text>
       
 11855 </xsl:text>
       
 11856           <xsl:text>function prepare_svg() {
       
 11857 </xsl:text>
       
 11858           <xsl:text>    // prevents context menu from appearing on right click and long touch
       
 11859 </xsl:text>
       
 11860           <xsl:text>    document.body.addEventListener('contextmenu', e =&gt; {
       
 11861 </xsl:text>
       
 11862           <xsl:text>        toggleFullscreen();
       
 11863 </xsl:text>
       
 11864           <xsl:text>        e.preventDefault();
       
 11865 </xsl:text>
       
 11866           <xsl:text>    });
       
 11867 </xsl:text>
       
 11868           <xsl:text>
       
 11869 </xsl:text>
       
 11870           <xsl:text>    for(let eltid in detachable_elements){
       
 11871 </xsl:text>
       
 11872           <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
 11873 </xsl:text>
       
 11874           <xsl:text>        parent.removeChild(element);
       
 11875 </xsl:text>
       
 11876           <xsl:text>    }
       
 11877 </xsl:text>
       
 11878           <xsl:text>};
       
 11879 </xsl:text>
       
 11880           <xsl:text>
       
 11881 </xsl:text>
       
 11882           <xsl:text>function switch_page(page_name, page_index) {
       
 11883 </xsl:text>
       
 11884           <xsl:text>    if(page_switch_in_progress){
       
 11885 </xsl:text>
       
 11886           <xsl:text>        /* page switch already going */
       
 11887 </xsl:text>
       
 11888           <xsl:text>        /* TODO LOG ERROR */
       
 11889 </xsl:text>
       
 11890           <xsl:text>        return false;
       
 11891 </xsl:text>
       
 11892           <xsl:text>    }
       
 11893 </xsl:text>
       
 11894           <xsl:text>    page_switch_in_progress = true;
       
 11895 </xsl:text>
       
 11896           <xsl:text>
       
 11897 </xsl:text>
       
 11898           <xsl:text>    if(page_name == undefined)
       
 11899 </xsl:text>
       
 11900           <xsl:text>        page_name = current_subscribed_page;
       
 11901 </xsl:text>
       
 11902           <xsl:text>    else if(page_index == undefined){
       
 11903 </xsl:text>
       
 11904           <xsl:text>        [page_name, page_index] = page_name.split('@')
       
 11905 </xsl:text>
       
 11906           <xsl:text>    }
       
 11907 </xsl:text>
       
 11908           <xsl:text>
       
 11909 </xsl:text>
       
 11910           <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
 11911 </xsl:text>
       
 11912           <xsl:text>    let new_desc = page_desc[page_name];
       
 11913 </xsl:text>
       
 11914           <xsl:text>
       
 11915 </xsl:text>
       
 11916           <xsl:text>    if(new_desc == undefined){
       
 11917 </xsl:text>
       
 11918           <xsl:text>        /* TODO LOG ERROR */
       
 11919 </xsl:text>
       
 11920           <xsl:text>        return false;
       
 11921 </xsl:text>
       
 11922           <xsl:text>    }
       
 11923 </xsl:text>
       
 11924           <xsl:text>
       
 11925 </xsl:text>
       
 11926           <xsl:text>    if(page_index == undefined)
       
 11927 </xsl:text>
       
 11928           <xsl:text>        page_index = new_desc.page_index;
       
 11929 </xsl:text>
       
 11930           <xsl:text>    else if(typeof(page_index) == "string") {
       
 11931 </xsl:text>
       
 11932           <xsl:text>        let hmitree_node = hmitree_nodes[page_index];
       
 11933 </xsl:text>
       
 11934           <xsl:text>        if(hmitree_node !== undefined){
       
 11935 </xsl:text>
       
 11936           <xsl:text>            let [int_index, hmiclass] = hmitree_node;
       
 11937 </xsl:text>
       
 11938           <xsl:text>            if(hmiclass == new_desc.page_class)
       
 11939 </xsl:text>
       
 11940           <xsl:text>                page_index = int_index;
       
 11941 </xsl:text>
       
 11942           <xsl:text>            else
       
 11943 </xsl:text>
       
 11944           <xsl:text>                page_index = new_desc.page_index;
       
 11945 </xsl:text>
       
 11946           <xsl:text>        } else {
       
 11947 </xsl:text>
       
 11948           <xsl:text>            page_index = new_desc.page_index;
       
 11949 </xsl:text>
       
 11950           <xsl:text>        }
       
 11951 </xsl:text>
       
 11952           <xsl:text>    }
       
 11953 </xsl:text>
       
 11954           <xsl:text>
       
 11955 </xsl:text>
       
 11956           <xsl:text>    if(old_desc){
       
 11957 </xsl:text>
       
 11958           <xsl:text>        old_desc.widgets.map(([widget,relativeness])=&gt;widget.unsub());
       
 11959 </xsl:text>
       
 11960           <xsl:text>    }
       
 11961 </xsl:text>
       
 11962           <xsl:text>    const new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
 11963 </xsl:text>
       
 11964           <xsl:text>
       
 11965 </xsl:text>
       
 11966           <xsl:text>    const container_id = page_name + (page_index != undefined ? page_index : "");
       
 11967 </xsl:text>
       
 11968           <xsl:text>
       
 11969 </xsl:text>
       
 11970           <xsl:text>    new_desc.widgets.map(([widget,relativeness])=&gt;widget.sub(new_offset,relativeness,container_id));
       
 11971 </xsl:text>
       
 11972           <xsl:text>
       
 11973 </xsl:text>
       
 11974           <xsl:text>    update_subscriptions();
       
 11975 </xsl:text>
       
 11976           <xsl:text>
       
 11977 </xsl:text>
       
 11978           <xsl:text>    current_subscribed_page = page_name;
       
 11979 </xsl:text>
       
 11980           <xsl:text>    current_page_index = page_index;
       
 11981 </xsl:text>
       
 11982           <xsl:text>    let page_node;
       
 11983 </xsl:text>
       
 11984           <xsl:text>    if(page_index != undefined){
       
 11985 </xsl:text>
       
 11986           <xsl:text>        page_node = hmitree_paths[page_index];
       
 11987 </xsl:text>
       
 11988           <xsl:text>    }else{
       
 11989 </xsl:text>
       
 11990           <xsl:text>        page_node = "";
       
 11991 </xsl:text>
       
 11992           <xsl:text>    }
       
 11993 </xsl:text>
       
 11994           <xsl:text>    apply_hmi_value(page_node_local_index, page_node);
       
 11995 </xsl:text>
       
 11996           <xsl:text>
       
 11997 </xsl:text>
       
 11998           <xsl:text>    jumps_need_update = true;
       
 11999 </xsl:text>
       
 12000           <xsl:text>
       
 12001 </xsl:text>
       
 12002           <xsl:text>    requestHMIAnimation();
       
 12003 </xsl:text>
       
 12004           <xsl:text>    jump_history.push([page_name, page_index]);
       
 12005 </xsl:text>
       
 12006           <xsl:text>    if(jump_history.length &gt; 42)
       
 12007 </xsl:text>
       
 12008           <xsl:text>        jump_history.shift();
       
 12009 </xsl:text>
       
 12010           <xsl:text>
       
 12011 </xsl:text>
       
 12012           <xsl:text>    apply_hmi_value(current_page_var_index, page_index == undefined
       
 12013 </xsl:text>
       
 12014           <xsl:text>        ? page_name
       
 12015 </xsl:text>
       
 12016           <xsl:text>        : page_name + "@" + hmitree_paths[page_index]);
       
 12017 </xsl:text>
       
 12018           <xsl:text>
       
 12019 </xsl:text>
       
 12020           <xsl:text>    return true;
       
 12021 </xsl:text>
       
 12022           <xsl:text>};
       
 12023 </xsl:text>
       
 12024           <xsl:text>
       
 12025 </xsl:text>
       
 12026           <xsl:text>function switch_visible_page(page_name) {
       
 12027 </xsl:text>
       
 12028           <xsl:text>
       
 12029 </xsl:text>
       
 12030           <xsl:text>    let old_desc = page_desc[current_visible_page];
       
 12031 </xsl:text>
       
 12032           <xsl:text>    let new_desc = page_desc[page_name];
       
 12033 </xsl:text>
       
 12034           <xsl:text>
       
 12035 </xsl:text>
       
 12036           <xsl:text>    if(old_desc){
       
 12037 </xsl:text>
       
 12038           <xsl:text>        for(let eltid in old_desc.required_detachables){
       
 12039 </xsl:text>
       
 12040           <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
 12041 </xsl:text>
       
 12042           <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
 12043 </xsl:text>
       
 12044           <xsl:text>                parent.removeChild(element);
       
 12045 </xsl:text>
       
 12046           <xsl:text>            }
       
 12047 </xsl:text>
       
 12048           <xsl:text>        }
       
 12049 </xsl:text>
       
 12050           <xsl:text>        for(let eltid in new_desc.required_detachables){
       
 12051 </xsl:text>
       
 12052           <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
 12053 </xsl:text>
       
 12054           <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
 12055 </xsl:text>
       
 12056           <xsl:text>                parent.appendChild(element);
       
 12057 </xsl:text>
       
 12058           <xsl:text>            }
       
 12059 </xsl:text>
       
 12060           <xsl:text>        }
       
 12061 </xsl:text>
       
 12062           <xsl:text>    }else{
       
 12063 </xsl:text>
       
 12064           <xsl:text>        for(let eltid in new_desc.required_detachables){
       
 12065 </xsl:text>
       
 12066           <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
 12067 </xsl:text>
       
 12068           <xsl:text>            parent.appendChild(element);
       
 12069 </xsl:text>
       
 12070           <xsl:text>        }
       
 12071 </xsl:text>
       
 12072           <xsl:text>    }
       
 12073 </xsl:text>
       
 12074           <xsl:text>
       
 12075 </xsl:text>
       
 12076           <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
 12077 </xsl:text>
       
 12078           <xsl:text>    current_visible_page = page_name;
       
 12079 </xsl:text>
       
 12080           <xsl:text>};
       
 12081 </xsl:text>
       
 12082           <xsl:text>
       
 12083 </xsl:text>
       
 12084           <xsl:text>// Once connection established
 12390           <xsl:text>// Once connection established
 12085 </xsl:text>
 12391 </xsl:text>
 12086           <xsl:text>ws.onopen = function (evt) {
 12392           <xsl:text>ws.onopen = function (evt) {
       
 12393 </xsl:text>
       
 12394           <xsl:text>    apply_reference_frames();
 12087 </xsl:text>
 12395 </xsl:text>
 12088           <xsl:text>    init_widgets();
 12396           <xsl:text>    init_widgets();
 12089 </xsl:text>
 12397 </xsl:text>
 12090           <xsl:text>    send_reset();
 12398           <xsl:text>    send_reset();
 12091 </xsl:text>
 12399 </xsl:text>