# HG changeset patch # User Edouard Tisserant # 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 @@ image/svg+xml - + @@ -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" /> + inkscape:label="HMI:Page:Relative:p=5@p=page_number" /> HMI:Jump:RelativePage + 0 + transform="translate(620.54487,-11.353461)"> + transform="translate(4.2410198,-11.353461)"> - Press Ctrl+X to edit SVG elements directly with XML editor + + declaration of user_level HMI local variable(not a PLC variable) + 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 @@ Value to display + + + + + + + + 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 + + + + + + Page + + 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 @@ + + + + + /* + + */ + + + + class PageWidget extends Widget{} + + + + @@ -787,7 +802,15 @@ - + + + + Page id=" + + " : only one root path can be declared + + + " @@ -804,31 +827,35 @@ ], - - + + Page id=" " : No match for path " - + " in HMI tree page_index: - + , page_class: " - + ", widgets: [ + [hmi_widgets[" + + "], []], + - + , @@ -1440,7 +1467,7 @@ ,{ - assignments: [], + enable_assignments: [], compute_enable: function(value, oldval, varnum) { @@ -1456,13 +1483,13 @@ if(varnum == - ) this.assignments[ + ) this.enable_assignments[ ] = value; let - = this.assignments[ + = this.enable_assignments[ ]; @@ -2388,7 +2415,9 @@ + + @@ -2402,7 +2431,7 @@ var hmi_widgets = { - + } @@ -6367,10 +6396,10 @@ - + - + @@ -7195,6 +7224,128 @@ ], + + + + + + + + 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 + + + + + + Page + + + + + + + + + /disabled + + + + + + + assignments: {}, + + dispatch: function(value, oldval, varnum) { + + + + + + + + + if(varnum == + + ) this.assignments[" + + "] = value; + + + + + + }, + + assign: function() { + + + + + + + + + + + + + + + + const + + = this.assignments[" + + "]; + + if( + + != undefined) + + this.apply_hmi_value( + + , + + ); + + + + this.apply_hmi_value( + + , + + ); + + + + + }, + + @@ -12128,29 +12279,37 @@ - var screensaver_timer = null; - - function reset_screensaver_timer() { - - if(screensaver_timer){ - - window.clearTimeout(screensaver_timer); + if(screensaver_delay){ + + var screensaver_timer = null; + + function reset_screensaver_timer() { + + if(screensaver_timer){ + + window.clearTimeout(screensaver_timer); + + } + + screensaver_timer = window.setTimeout(() => { + + switch_page("ScreenSaver"); + + screensaver_timer = null; + + }, screensaver_delay*1000); } - screensaver_timer = window.setTimeout(() => { - - switch_page("ScreenSaver"); - - screensaver_timer = null; - - }, screensaver_delay*1000); + document.body.addEventListener('pointerdown', reset_screensaver_timer); + + // initialize screensaver + + reset_screensaver_timer(); } - if(screensaver_delay) - - document.body.addEventListener('pointerdown', reset_screensaver_timer); + @@ -12314,6 +12473,12 @@ + // when entering a page, assignments are evaluated + + new_desc.widgets[0][0].assign(); + + + return true; }; @@ -12474,16 +12639,12 @@ - // initialize screensaver - - reset_screensaver_timer(); - - - var reconnect_delay = 0; var periodic_reconnect_timer; + var force_reconnect = false; + // Once connection established @@ -12504,6 +12665,8 @@ periodic_reconnect_timer = window.setTimeout(() => { + force_reconnect = true; + ws.close(); periodic_reconnect_timer = null; @@ -12540,14 +12703,26 @@ 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; + }; 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"; | } | }