# HG changeset patch # User Edouard Tisserant <edouard.tisserant@gmail.com> # Date 1669209505 -3600 # Node ID c2992796a8597b89dff29c5e36bfa5cd9f22841a # Parent b6bca75bf3faeb0e639a9062a717a4713dba8333# Parent 747ffdafbe31c09334afd6b46dd264c07776b137 Marged default in wxPython4 diff -r b6bca75bf3fa -r c2992796a859 editors/TextViewer.py --- a/editors/TextViewer.py Sun Nov 20 18:36:13 2022 +0100 +++ b/editors/TextViewer.py Wed Nov 23 14:18:25 2022 +0100 @@ -130,8 +130,7 @@ self.Editor.SetUseTabs(0) self.Editor.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT | - wx.stc.STC_MOD_BEFOREDELETE | - wx.stc.STC_PERFORMED_USER) + wx.stc.STC_MOD_BEFOREDELETE) self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded, self.Editor) self.Editor.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick) @@ -221,25 +220,24 @@ self.SearchResults = None self.CurrentFindHighlight = None + Buffering = "Off" def OnModification(self, event): if not self.DisableEvents: mod_type = event.GetModificationType() if mod_type & wx.stc.STC_MOD_BEFOREINSERT: if self.CurrentAction is None: - self.StartBuffering() + self.Buffering = "ShouldStart" elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1: - self.Controler.EndBuffering() - self.StartBuffering() + self.Buffering = "ShouldRestart" self.CurrentAction = ("Add", event.GetPosition()) - wx.CallAfter(self.RefreshModel) + self.RefreshModelAfter() elif mod_type & wx.stc.STC_MOD_BEFOREDELETE: if self.CurrentAction is None: - self.StartBuffering() + self.Buffering = "ShouldStart" elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1: - self.Controler.EndBuffering() - self.StartBuffering() + self.Buffering = "ShouldRestart" self.CurrentAction = ("Delete", event.GetPosition()) - wx.CallAfter(self.RefreshModel) + self.RefreshModelAfter() event.Skip() def OnDoDrop(self, event): @@ -387,7 +385,7 @@ elif values[3] == self.TagName: self.ResetBuffer() event.SetDragText(values[0]) - wx.CallAfter(self.RefreshModel) + self.RefreshModelAfter() else: message = _("Variable don't belong to this POU!") if message is not None: @@ -437,10 +435,14 @@ self.ParentWindow.RefreshFileMenu() self.ParentWindow.RefreshEditMenu() + def EndBuffering(self): + self.Controler.EndBuffering() + def ResetBuffer(self): if self.CurrentAction is not None: - self.Controler.EndBuffering() + self.EndBuffering() self.CurrentAction = None + self.Buffering == "Off" def GetBufferState(self): if not self.Debug and self.TextSyntax != "ALL": @@ -840,12 +842,29 @@ self.RemoveHighlight(*self.CurrentFindHighlight) self.CurrentFindHighlight = None + pending_model_refresh=False def RefreshModel(self): + self.pending_model_refresh=False self.RefreshJumpList() self.Colourise(0, -1) + + if self.Buffering == "ShouldStart": + self.StartBuffering() + self.Buffering == "On" + elif self.Buffering == "ShouldRestart": + self.EndBuffering() + self.StartBuffering() + self.Buffering == "On" + self.Controler.SetEditedElementText(self.TagName, self.GetText()) self.ResetSearchResults() + def RefreshModelAfter(self): + if self.pending_model_refresh: + return + self.pending_model_refresh=True + wx.CallAfter(self.RefreshModel) + def OnKeyDown(self, event): key = event.GetKeyCode() if self.Controler is not None: diff -r b6bca75bf3fa -r c2992796a859 exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg --- a/exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg Sun Nov 20 18:36:13 2022 +0100 +++ b/exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg Wed Nov 23 14:18:25 2022 +0100 @@ -25,7 +25,7 @@ <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> + <dc:title /> </cc:Work> </rdf:RDF> </metadata> @@ -40,17 +40,17 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1600" - inkscape:window-height="836" + inkscape:window-width="1850" + inkscape:window-height="1036" id="namedview4" showgrid="false" - inkscape:zoom="0.23177389" - inkscape:cx="1999.5317" - inkscape:cy="-682.74047" + inkscape:zoom="0.46354778" + inkscape:cx="-544.27948" + inkscape:cy="655.56978" inkscape:window-x="0" inkscape:window-y="27" inkscape:window-maximized="1" - inkscape:current-layer="hmi0" + inkscape:current-layer="g2496" showguides="true" inkscape:guide-bbox="true" borderlayer="true" @@ -67,7 +67,7 @@ transform="translate(1320,1520)" width="100%" height="100%" - inkscape:label="HMI:Page:RelativePage@/FB_ZERO" /> + inkscape:label="HMI:Page:RelativePage:p=6@p=page_number@/FB_ZERO" /> <use sodipodi:insensitive="true" x="0" @@ -77,7 +77,7 @@ transform="translate(2640,759.99998)" width="100%" height="100%" - inkscape:label="HMI:Page:Relative" /> + inkscape:label="HMI:Page:Relative:p=5@p=page_number" /> <use x="0" y="0" @@ -86,10 +86,10 @@ transform="translate(3940,-2.1367187e-5)" width="100%" height="100%" - inkscape:label="HMI:Page:Conditional" + inkscape:label="HMI:Page:Conditional:p=4@p=page_number" sodipodi:insensitive="true" /> <use - inkscape:label="HMI:Page:Unconditional" + inkscape:label="HMI:Page:Unconditional:p=3@p=page_number" height="100%" width="100%" transform="translate(2640,-2.1367187e-5)" @@ -99,7 +99,7 @@ x="0" sodipodi:insensitive="true" /> <use - inkscape:label="HMI:Page:AbsolutePage" + inkscape:label="HMI:Page:AbsolutePage:p=2@p=page_number" height="100%" width="100%" transform="translate(1320,759.99998)" @@ -116,7 +116,7 @@ transform="translate(1320,-2.1367187e-5)" width="100%" height="100%" - inkscape:label="HMI:Page:Home" + inkscape:label="HMI:Page:Home:p=1@p=page_number" sodipodi:insensitive="true" /> <text id="text837" @@ -621,6 +621,18 @@ id="tspan1849" x="-1166.3386" y="598.98303">HMI:Jump:RelativePage</tspan></text> + <text + inkscape:label="HMI:Display@page_number" + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.03883362px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-75.162941" + y="702.24023" + id="text296"><tspan + sodipodi:role="line" + id="tspan294" + x="-75.162941" + y="702.24023" + style="stroke-width:1px">0</tspan></text> </g> <g id="g2585" @@ -1200,7 +1212,7 @@ <g id="g1711" inkscape:label="MYNODE:+1" - transform="matrix(1.3729714,0,0,1.3729714,-299.47126,-800.64485)" + transform="matrix(1.3729714,0,0,1.3729714,-359.47134,-800.64485)" style="stroke-width:0.7283473"> <rect rx="15.554536" @@ -1226,7 +1238,7 @@ <g inkscape:label="MYNODE:3" id="g1837" - transform="translate(665.54481,-11.353461)"> + transform="translate(620.54487,-11.353461)"> <g transform="translate(1466.6549,2099.2529)" inkscape:label="HMI:Jump:RelativePage@/FB_TWO@enable=/FB_TWO/SOME_BOOL#enable" @@ -1286,7 +1298,7 @@ </g> </g> <g - transform="translate(342.39289,-11.353461)" + transform="translate(312.39295,-11.353461)" id="g1823" inkscape:label="MYNODE:2"> <g @@ -1350,7 +1362,7 @@ <g inkscape:label="MYNODE:1" id="g1717" - transform="translate(19.240974,-11.353461)"> + transform="translate(4.2410198,-11.353461)"> <g transform="translate(1466.6549,2099.2529)" inkscape:label="HMI:Jump:RelativePage@/FB_ZERO@enable=/FB_ZERO/SOME_BOOL#enable" @@ -1815,4 +1827,23 @@ x="44.283585" y="-39.185921" id="tspan2966">- Press Ctrl+X to edit SVG elements directly with XML editor</tspan></text> + <g + transform="translate(-4020,2.1367187e-5)" + inkscape:label="HMI:VarInit:0@page_number" + id="g304"> + <text + id="text302" + y="-108.39357" + x="3726.6924" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.66666603px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="-108.39357" + x="3726.6924" + id="tspan298" + sodipodi:role="line">declaration of user_level HMI local variable</tspan><tspan + id="tspan300" + y="-85.060234" + x="3726.6924" + sodipodi:role="line">(not a PLC variable)</tspan></text> + </g> </svg> diff -r b6bca75bf3fa -r c2992796a859 svghmi/analyse_widget.xslt --- a/svghmi/analyse_widget.xslt Sun Nov 20 18:36:13 2022 +0100 +++ b/svghmi/analyse_widget.xslt Wed Nov 23 14:18:25 2022 +0100 @@ -854,6 +854,46 @@ <xsl:text>Value to display</xsl:text> </path> </xsl:template> + <xsl:template match="widget[@type='Page']" mode="widget_desc"> + <type> + <xsl:value-of select="@type"/> + </type> + <longdesc> + <xsl:text> +</xsl:text> + <xsl:text>Arguments are either: +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>- XXX reference path TODO +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>- name=value: setting variable with literal value. +</xsl:text> + <xsl:text>- name=other_name: copy variable content into another +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>"active"+"inactive" labeled elements can be provided to show feedback when pressed +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>Exemples: +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>HMI:Page:notify=1@notify=/PLCVAR +</xsl:text> + <xsl:text>HMI:Page:ack=2:notify=1@ack=.local_var@notify=/PLCVAR +</xsl:text> + <xsl:text> +</xsl:text> + </longdesc> + <shortdesc> + <xsl:text>Page </xsl:text> + </shortdesc> + </xsl:template> <xsl:template match="widget[@type='PathSlider']" mode="widget_desc"> <type> <xsl:value-of select="@type"/> diff -r b6bca75bf3fa -r c2992796a859 svghmi/detachable_pages.ysl2 --- a/svghmi/detachable_pages.ysl2 Sun Nov 20 18:36:13 2022 +0100 +++ b/svghmi/detachable_pages.ysl2 Wed Nov 23 14:18:25 2022 +0100 @@ -137,6 +137,10 @@ const "_detachable_elements", "func:detachable_elements($hmi_pages | $keypads)"; const "detachable_elements", "$_detachable_elements[not(ancestor::*/@id = $_detachable_elements/@id)]"; +emit "declarations:page-class" { + | class PageWidget extends Widget{} +} + emit "declarations:detachable-elements" { | | var detachable_elements = { @@ -165,8 +169,13 @@ const "all_page_widgets","$hmi_widgets[@id = $page_all_elements/@id and @id != $page/@id]"; const "page_managed_widgets","$all_page_widgets[not(@id=$in_forEach_widget_ids)]"; + + const "page_root_path", "$desc/path[not(@assign)]"; + if "count($page_root_path)>1" + error > Page id="«$page/@id»" : only one root path can be declared + const "page_relative_widgets", - "$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $desc/path/@value)]"; + "$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $page_root_path/@value)]"; // Take closest ancestor in detachable_elements // since nested detachable elements are filtered out @@ -178,19 +187,19 @@ ancestor-or-self::*[@id = $detachable_elements/@id]"""; | "«$pagename»": { - //| widget: hmi_widgets["«@id»"], | bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»], - if "$desc/path/@value" { - if "count($desc/path/@index)=0" - warning > Page id="«$page/@id»" : No match for path "«$desc/path/@value»" in HMI tree - | page_index: «$desc/path/@index», - | page_class: "«$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class»", + if "count($page_root_path)=1"{ + if "count($page_root_path/@index)=0" + warning > Page id="«$page/@id»" : No match for path "«$page_root_path/@value»" in HMI tree + | page_index: «$page_root_path/@index», + | page_class: "«$indexed_hmitree/*[@hmipath = $page_root_path/@value]/@class»", } | widgets: [ + | [hmi_widgets["«$page/@id»"], []], foreach "$page_managed_widgets" { const "widget_paths_relativeness" foreach "func:widget(@id)/path" { - value "func:is_descendant_path(@value, $desc/path/@value)"; + value "func:is_descendant_path(@value, $page_root_path/@value)"; if "position()!=last()" > , } | [hmi_widgets["«@id»"], [«$widget_paths_relativeness»]]`if "position()!=last()" > ,` diff -r b6bca75bf3fa -r c2992796a859 svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Sun Nov 20 18:36:13 2022 +0100 +++ b/svghmi/gen_index_xhtml.xslt Wed Nov 23 14:18:25 2022 +0100 @@ -734,6 +734,21 @@ </func:function> <xsl:variable name="_detachable_elements" select="func:detachable_elements($hmi_pages | $keypads)"/> <xsl:variable name="detachable_elements" select="$_detachable_elements[not(ancestor::*/@id = $_detachable_elements/@id)]"/> + <declarations:page-class/> + <xsl:template match="declarations:page-class"> + <xsl:text> +</xsl:text> + <xsl:text>/* </xsl:text> + <xsl:value-of select="local-name()"/> + <xsl:text> */ +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>class PageWidget extends Widget{} +</xsl:text> + <xsl:text> +</xsl:text> + </xsl:template> <declarations:detachable-elements/> <xsl:template match="declarations:detachable-elements"> <xsl:text> @@ -787,7 +802,15 @@ <xsl:variable name="page_all_elements" select="func:all_related_elements($page)"/> <xsl:variable name="all_page_widgets" select="$hmi_widgets[@id = $page_all_elements/@id and @id != $page/@id]"/> <xsl:variable name="page_managed_widgets" select="$all_page_widgets[not(@id=$in_forEach_widget_ids)]"/> - <xsl:variable name="page_relative_widgets" select="$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $desc/path/@value)]"/> + <xsl:variable name="page_root_path" select="$desc/path[not(@assign)]"/> + <xsl:if test="count($page_root_path)>1"> + <xsl:message terminate="yes"> + <xsl:text>Page id="</xsl:text> + <xsl:value-of select="$page/@id"/> + <xsl:text>" : only one root path can be declared</xsl:text> + </xsl:message> + </xsl:if> + <xsl:variable name="page_relative_widgets" select="$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $page_root_path/@value)]"/> <xsl:variable name="sumarized_page" select="func:sumarized_elements($page_all_elements)"/> <xsl:variable name="required_detachables" select="$sumarized_page/ ancestor-or-self::*[@id = $detachable_elements/@id]"/> <xsl:text> "</xsl:text> @@ -804,31 +827,35 @@ <xsl:value-of select="$p/@h"/> <xsl:text>], </xsl:text> - <xsl:if test="$desc/path/@value"> - <xsl:if test="count($desc/path/@index)=0"> + <xsl:if test="count($page_root_path)=1"> + <xsl:if test="count($page_root_path/@index)=0"> <xsl:message terminate="no"> <xsl:text>Page id="</xsl:text> <xsl:value-of select="$page/@id"/> <xsl:text>" : No match for path "</xsl:text> - <xsl:value-of select="$desc/path/@value"/> + <xsl:value-of select="$page_root_path/@value"/> <xsl:text>" in HMI tree</xsl:text> </xsl:message> </xsl:if> <xsl:text> page_index: </xsl:text> - <xsl:value-of select="$desc/path/@index"/> + <xsl:value-of select="$page_root_path/@index"/> <xsl:text>, </xsl:text> <xsl:text> page_class: "</xsl:text> - <xsl:value-of select="$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class"/> + <xsl:value-of select="$indexed_hmitree/*[@hmipath = $page_root_path/@value]/@class"/> <xsl:text>", </xsl:text> </xsl:if> <xsl:text> widgets: [ </xsl:text> + <xsl:text> [hmi_widgets["</xsl:text> + <xsl:value-of select="$page/@id"/> + <xsl:text>"], []], +</xsl:text> <xsl:for-each select="$page_managed_widgets"> <xsl:variable name="widget_paths_relativeness"> <xsl:for-each select="func:widget(@id)/path"> - <xsl:value-of select="func:is_descendant_path(@value, $desc/path/@value)"/> + <xsl:value-of select="func:is_descendant_path(@value, $page_root_path/@value)"/> <xsl:if test="position()!=last()"> <xsl:text>,</xsl:text> </xsl:if> @@ -1440,7 +1467,7 @@ <xsl:text>,{ </xsl:text> <xsl:if test="$widget/@enable_expr"> - <xsl:text> assignments: [], + <xsl:text> enable_assignments: [], </xsl:text> <xsl:text> compute_enable: function(value, oldval, varnum) { </xsl:text> @@ -1456,13 +1483,13 @@ <xsl:if test="$varid = generate-id()"> <xsl:text> if(varnum == </xsl:text> <xsl:value-of select="$varnum"/> - <xsl:text>) this.assignments[</xsl:text> + <xsl:text>) this.enable_assignments[</xsl:text> <xsl:value-of select="position()-1"/> <xsl:text>] = value; </xsl:text> <xsl:text> let </xsl:text> <xsl:value-of select="@assign"/> - <xsl:text> = this.assignments[</xsl:text> + <xsl:text> = this.enable_assignments[</xsl:text> <xsl:value-of select="position()-1"/> <xsl:text>]; </xsl:text> @@ -2388,7 +2415,9 @@ </xsl:message> </xsl:template> <xsl:variable name="included_ids" select="$parsed_widgets/widget[not(@type = $excluded_types) and not(@id = $discardable_elements/@id)]/@id"/> + <xsl:variable name="page_ids" select="$parsed_widgets/widget[@type = 'Page']/@id"/> <xsl:variable name="hmi_widgets" select="$hmi_elements[@id = $included_ids]"/> + <xsl:variable name="page_widgets" select="$hmi_elements[@id = $page_ids]"/> <xsl:variable name="result_widgets" select="$result_svg_ns//*[@id = $hmi_widgets/@id]"/> <declarations:hmi-elements/> <xsl:template match="declarations:hmi-elements"> @@ -2402,7 +2431,7 @@ </xsl:text> <xsl:text>var hmi_widgets = { </xsl:text> - <xsl:apply-templates mode="hmi_widgets" select="$hmi_widgets"/> + <xsl:apply-templates mode="hmi_widgets" select="$hmi_widgets | $page_widgets"/> <xsl:text>} </xsl:text> <xsl:text> @@ -6367,10 +6396,10 @@ <xsl:variable name="target_page_path"> <xsl:choose> <xsl:when test="arg"> - <xsl:value-of select="$hmi_pages_descs[arg[1]/@value = $target_page_name]/path[1]/@value"/> + <xsl:value-of select="$hmi_pages_descs[arg[1]/@value = $target_page_name]/path[not(@assign)]/@value"/> </xsl:when> <xsl:otherwise> - <xsl:value-of select="$page_desc/path[1]/@value"/> + <xsl:value-of select="$page_desc/path[not(@assign)]/@value"/> </xsl:otherwise> </xsl:choose> </xsl:variable> @@ -7195,6 +7224,128 @@ <xsl:text> ], </xsl:text> </xsl:template> + <xsl:template match="widget[@type='Page']" mode="widget_desc"> + <type> + <xsl:value-of select="@type"/> + </type> + <longdesc> + <xsl:text> +</xsl:text> + <xsl:text>Arguments are either: +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>- XXX reference path TODO +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>- name=value: setting variable with literal value. +</xsl:text> + <xsl:text>- name=other_name: copy variable content into another +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>"active"+"inactive" labeled elements can be provided to show feedback when pressed +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>Exemples: +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>HMI:Page:notify=1@notify=/PLCVAR +</xsl:text> + <xsl:text>HMI:Page:ack=2:notify=1@ack=.local_var@notify=/PLCVAR +</xsl:text> + <xsl:text> +</xsl:text> + </longdesc> + <shortdesc> + <xsl:text>Page </xsl:text> + </shortdesc> + </xsl:template> + <xsl:template match="widget[@type='Page']" mode="widget_defs"> + <xsl:param name="hmi_element"/> + <xsl:variable name="disability"> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>/disabled</xsl:text> + </xsl:with-param> + <xsl:with-param name="mandatory" select="'no'"/> + </xsl:call-template> + </xsl:variable> + <xsl:value-of select="$disability"/> + <xsl:variable name="has_disability" select="string-length($disability)>0"/> + <xsl:text> assignments: {}, +</xsl:text> + <xsl:text> dispatch: function(value, oldval, varnum) { +</xsl:text> + <xsl:variable name="widget" select="."/> + <xsl:for-each select="path"> + <xsl:variable name="varid" select="generate-id()"/> + <xsl:variable name="varnum" select="position()-1"/> + <xsl:if test="@assign"> + <xsl:for-each select="$widget/path[@assign]"> + <xsl:if test="$varid = generate-id()"> + <xsl:text> if(varnum == </xsl:text> + <xsl:value-of select="$varnum"/> + <xsl:text>) this.assignments["</xsl:text> + <xsl:value-of select="@assign"/> + <xsl:text>"] = value; +</xsl:text> + </xsl:if> + </xsl:for-each> + </xsl:if> + </xsl:for-each> + <xsl:text> }, +</xsl:text> + <xsl:text> assign: function() { +</xsl:text> + <xsl:variable name="paths" select="path"/> + <xsl:for-each select="arg[contains(@value,'=')]"> + <xsl:variable name="name" select="substring-before(@value,'=')"/> + <xsl:variable name="value" select="substring-after(@value,'=')"/> + <xsl:variable name="index"> + <xsl:for-each select="$paths"> + <xsl:if test="@assign = $name"> + <xsl:value-of select="position()-1"/> + </xsl:if> + </xsl:for-each> + </xsl:variable> + <xsl:variable name="isVarName" select="regexp:test($value,'^[a-zA-Z_][a-zA-Z0-9_]+$')"/> + <xsl:choose> + <xsl:when test="$isVarName"> + <xsl:text> const </xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> = this.assignments["</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text>"]; +</xsl:text> + <xsl:text> if(</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> != undefined) +</xsl:text> + <xsl:text> this.apply_hmi_value(</xsl:text> + <xsl:value-of select="$index"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$value"/> + <xsl:text>); +</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text> this.apply_hmi_value(</xsl:text> + <xsl:value-of select="$index"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$value"/> + <xsl:text>); +</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:text> }, +</xsl:text> + </xsl:template> <xsl:template match="widget[@type='PathSlider']" mode="widget_desc"> <type> <xsl:value-of select="@type"/> @@ -12128,29 +12279,37 @@ </xsl:text> <xsl:text> </xsl:text> - <xsl:text>var screensaver_timer = null; -</xsl:text> - <xsl:text>function reset_screensaver_timer() { -</xsl:text> - <xsl:text> if(screensaver_timer){ -</xsl:text> - <xsl:text> window.clearTimeout(screensaver_timer); + <xsl:text>if(screensaver_delay){ +</xsl:text> + <xsl:text> var screensaver_timer = null; +</xsl:text> + <xsl:text> function reset_screensaver_timer() { +</xsl:text> + <xsl:text> if(screensaver_timer){ +</xsl:text> + <xsl:text> window.clearTimeout(screensaver_timer); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> screensaver_timer = window.setTimeout(() => { +</xsl:text> + <xsl:text> switch_page("ScreenSaver"); +</xsl:text> + <xsl:text> screensaver_timer = null; +</xsl:text> + <xsl:text> }, screensaver_delay*1000); </xsl:text> <xsl:text> } </xsl:text> - <xsl:text> screensaver_timer = window.setTimeout(() => { -</xsl:text> - <xsl:text> switch_page("ScreenSaver"); -</xsl:text> - <xsl:text> screensaver_timer = null; -</xsl:text> - <xsl:text> }, screensaver_delay*1000); + <xsl:text> document.body.addEventListener('pointerdown', reset_screensaver_timer); +</xsl:text> + <xsl:text> // initialize screensaver +</xsl:text> + <xsl:text> reset_screensaver_timer(); </xsl:text> <xsl:text>} </xsl:text> - <xsl:text>if(screensaver_delay) -</xsl:text> - <xsl:text> document.body.addEventListener('pointerdown', reset_screensaver_timer); + <xsl:text> </xsl:text> <xsl:text> </xsl:text> @@ -12314,6 +12473,12 @@ </xsl:text> <xsl:text> </xsl:text> + <xsl:text> // when entering a page, assignments are evaluated +</xsl:text> + <xsl:text> new_desc.widgets[0][0].assign(); +</xsl:text> + <xsl:text> +</xsl:text> <xsl:text> return true; </xsl:text> <xsl:text>}; @@ -12474,16 +12639,12 @@ </xsl:text> <xsl:text> </xsl:text> - <xsl:text>// initialize screensaver -</xsl:text> - <xsl:text>reset_screensaver_timer(); -</xsl:text> - <xsl:text> -</xsl:text> <xsl:text>var reconnect_delay = 0; </xsl:text> <xsl:text>var periodic_reconnect_timer; </xsl:text> + <xsl:text>var force_reconnect = false; +</xsl:text> <xsl:text> </xsl:text> <xsl:text>// Once connection established @@ -12504,6 +12665,8 @@ </xsl:text> <xsl:text> periodic_reconnect_timer = window.setTimeout(() => { </xsl:text> + <xsl:text> force_reconnect = true; +</xsl:text> <xsl:text> ws.close(); </xsl:text> <xsl:text> periodic_reconnect_timer = null; @@ -12540,14 +12703,26 @@ </xsl:text> <xsl:text> ws = null; </xsl:text> - <xsl:text> // reconect -</xsl:text> - <xsl:text> // TODO : add visible notification while waiting for reload + <xsl:text> // Do not attempt to reconnect immediately in case: +</xsl:text> + <xsl:text> // - connection was closed by server (PLC stop) +</xsl:text> + <xsl:text> // - connection was closed locally with an intention to reconnect +</xsl:text> + <xsl:text> if(evt.code=1000 && !force_reconnect){ +</xsl:text> + <xsl:text> window.alert("Connection closed by server"); +</xsl:text> + <xsl:text> location.reload(); +</xsl:text> + <xsl:text> } </xsl:text> <xsl:text> window.setTimeout(create_ws, reconnect_delay); </xsl:text> <xsl:text> reconnect_delay += 500; </xsl:text> + <xsl:text> force_reconnect = false; +</xsl:text> <xsl:text>}; </xsl:text> <xsl:text> diff -r b6bca75bf3fa -r c2992796a859 svghmi/svghmi.js --- a/svghmi/svghmi.js Sun Nov 20 18:36:13 2022 +0100 +++ b/svghmi/svghmi.js Wed Nov 23 14:18:25 2022 +0100 @@ -525,6 +525,9 @@ ? page_name : page_name + "@" + hmitree_paths[page_index]); + // when entering a page, assignments are evaluated + new_desc.widgets[0][0].assign(); + return true; }; @@ -607,6 +610,7 @@ var reconnect_delay = 0; var periodic_reconnect_timer; +var force_reconnect = false; // Once connection established function ws_onopen(evt) { @@ -617,6 +621,7 @@ window.clearTimeout(periodic_reconnect_timer); } periodic_reconnect_timer = window.setTimeout(() => { + force_reconnect = true; ws.close(); periodic_reconnect_timer = null; }, 3600000); @@ -635,10 +640,16 @@ function ws_onclose(evt) { console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in "+reconnect_delay+"ms."); ws = null; - // reconect - // TODO : add visible notification while waiting for reload + // Do not attempt to reconnect immediately in case: + // - connection was closed by server (PLC stop) + // - connection was closed locally with an intention to reconnect + if(evt.code=1000 && !force_reconnect){ + window.alert("Connection closed by server"); + location.reload(); + } window.setTimeout(create_ws, reconnect_delay); reconnect_delay += 500; + force_reconnect = false; }; var ws_url = diff -r b6bca75bf3fa -r c2992796a859 svghmi/svghmi.py --- a/svghmi/svghmi.py Sun Nov 20 18:36:13 2022 +0100 +++ b/svghmi/svghmi.py Wed Nov 23 14:18:25 2022 +0100 @@ -655,8 +655,8 @@ def svghmi_{location}_watchdog_trigger(): global browser_proc - restart_proc = {svghmi_cmds[Watchdog]} - waitpid_timeout(restart_proc, "SVGHMI watchdog triggered command") + watchdog_proc = {svghmi_cmds[Watchdog]} + waitpid_timeout(watchdog_proc, "SVGHMI watchdog triggered command") stop_proc = {svghmi_cmds[Stop]} waitpid_timeout(stop_proc, "SVGHMI stop command") waitpid_timeout(browser_proc, "SVGHMI browser process") diff -r b6bca75bf3fa -r c2992796a859 svghmi/widget_jump.ysl2 --- a/svghmi/widget_jump.ysl2 Sun Nov 20 18:36:13 2022 +0100 +++ b/svghmi/widget_jump.ysl2 Wed Nov 23 14:18:25 2022 +0100 @@ -143,8 +143,8 @@ otherwise value "$page_desc/arg[1]/@value"; } const "target_page_path" choose { - when "arg" value "$hmi_pages_descs[arg[1]/@value = $target_page_name]/path[1]/@value"; - otherwise value "$page_desc/path[1]/@value"; + when "arg" value "$hmi_pages_descs[arg[1]/@value = $target_page_name]/path[not(@assign)]/@value"; + otherwise value "$page_desc/path[not(@assign)]/@value"; } if "not(func:same_class_paths($target_page_path, path[1]/@value))" diff -r b6bca75bf3fa -r c2992796a859 svghmi/widget_page.ysl2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/widget_page.ysl2 Wed Nov 23 14:18:25 2022 +0100 @@ -0,0 +1,60 @@ +// widget_page.ysl2 + +widget_desc("Page") { + longdesc + || + + Arguments are either: + + - XXX reference path TODO + + - name=value: setting variable with literal value. + - name=other_name: copy variable content into another + + "active"+"inactive" labeled elements can be provided to show feedback when pressed + + Exemples: + + HMI:Page:notify=1@notify=/PLCVAR + HMI:Page:ack=2:notify=1@ack=.local_var@notify=/PLCVAR + + || + + shortdesc > Page + +} + +widget_defs("Page") { + + | assignments: {}, + | dispatch: function(value, oldval, varnum) { + const "widget", "."; + foreach "path" { + const "varid","generate-id()"; + const "varnum","position()-1"; + if "@assign" foreach "$widget/path[@assign]" if "$varid = generate-id()" { + | if(varnum == «$varnum») this.assignments["«@assign»"] = value; + } + } + | }, + | assign: function() { + const "paths","path"; + foreach "arg[contains(@value,'=')]"{ + const "name","substring-before(@value,'=')"; + const "value","substring-after(@value,'=')"; + const "index" foreach "$paths" if "@assign = $name" value "position()-1"; + const "isVarName", "regexp:test($value,'^[a-zA-Z_][a-zA-Z0-9_]+$')"; + choose { + when "$isVarName"{ + | const «$value» = this.assignments["«$value»"]; + | if(«$value» != undefined) + | this.apply_hmi_value(«$index», «$value»); + } + otherwise { + | this.apply_hmi_value(«$index», «$value»); + } + } + } + | }, +} + diff -r b6bca75bf3fa -r c2992796a859 svghmi/widgets_common.ysl2 --- a/svghmi/widgets_common.ysl2 Sun Nov 20 18:36:13 2022 +0100 +++ b/svghmi/widgets_common.ysl2 Wed Nov 23 14:18:25 2022 +0100 @@ -134,7 +134,7 @@ | "«@id»": new «$widget/@type»Widget ("«@id»",«$freq»,[«$args»],[«$variables»],«$enable_expr»,{ if "$widget/@enable_expr" { - | assignments: [], + | enable_assignments: [], | compute_enable: function(value, oldval, varnum) { | let result = false; | do { @@ -142,8 +142,8 @@ const "varid","generate-id()"; const "varnum","position()-1"; if "@assign" foreach "$widget/path[@assign]" if "$varid = generate-id()" { - | if(varnum == «$varnum») this.assignments[«position()-1»] = value; - | let «@assign» = this.assignments[«position()-1»]; + | if(varnum == «$varnum») this.enable_assignments[«position()-1»] = value; + | let «@assign» = this.enable_assignments[«position()-1»]; | if(«@assign» == undefined) break; } } @@ -600,12 +600,14 @@ } const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types) and not(@id = $discardable_elements/@id)]/@id"; +const "page_ids","$parsed_widgets/widget[@type = 'Page']/@id"; const "hmi_widgets","$hmi_elements[@id = $included_ids]"; +const "page_widgets","$hmi_elements[@id = $page_ids]"; const "result_widgets","$result_svg_ns//*[@id = $hmi_widgets/@id]"; emit "declarations:hmi-elements" { | var hmi_widgets = { - apply "$hmi_widgets", mode="hmi_widgets"; + apply "$hmi_widgets | $page_widgets", mode="hmi_widgets"; | } | }