--- a/svghmi/parse_labels.ysl2 Wed Jun 16 12:15:02 2021 +0200
+++ b/svghmi/parse_labels.ysl2 Wed Jun 16 18:27:27 2021 +0200
@@ -19,6 +19,7 @@
{
const "label","@inkscape:label";
const "id","@id";
+
const "description", "substring-after($label,'HMI:')";
const "_args", "substring-before($description,'@')";
@@ -86,6 +87,7 @@
}
}
}
+ if "svg:desc" desc value "svg:desc/text()";
}
}
--- a/svghmi/ui.py Wed Jun 16 12:15:02 2021 +0200
+++ b/svghmi/ui.py Wed Jun 16 18:27:27 2021 +0200
@@ -10,6 +10,10 @@
import os
import hashlib
import weakref
+import re
+from functools import reduce
+from itertools import izip
+from operator import or_
from tempfile import NamedTemporaryFile
import wx
@@ -200,19 +204,41 @@
self.SetSizer(self.main_sizer)
self.main_sizer.Fit(self)
- def setValidityNOK(self):
- self.validity_sbmp.SetBitmap(self.invalid_bmp)
- self.validity_sbmp.Show(True)
-
- def setValidityOK(self):
- self.validity_sbmp.SetBitmap(self.valid_bmp)
- self.validity_sbmp.Show(True)
-
- def setValidityUnknown(self):
- self.validity_sbmp.Show(False)
+ def setValidity(self, validity):
+ if validity is not None:
+ bmp = self.valid_bmp if validity else self.invalid_bmp
+ self.validity_sbmp.SetBitmap(bmp)
+ self.validity_sbmp.Show(True)
+ else :
+ self.validity_sbmp.Show(False)
+
+models = { typename: re.compile(regex) for typename, regex in [
+ ("string", r".*"),
+ ("int", r"^-?([1-9][0-9]|0)*$"),
+ ("real", r"^-?([1-9][0-9]|0)*(\.[0-9]+)?$")]}
class ArgEditor(ParamEditor):
- pass
+ def __init__(self, parent, argdesc, prefillargdesc):
+ ParamEditor.__init__(self, parent, argdesc)
+ self.ParentObj = parent
+ self.argdesc = argdesc
+ self.Bind(wx.EVT_TEXT, self.OnArgChanged, self.edit)
+ prefill = "" if prefillargdesc is None else prefillargdesc.get("value")
+ self.edit.SetValue(prefill)
+ # TODO add a button to add more ArgEditror instance
+ # when ordinality is multiple
+
+ def OnArgChanged(self, event):
+ txt = self.edit.GetValue()
+ accepts = self.argdesc.get("accepts").split(',')
+ self.setValidity(
+ reduce(or_,
+ map(lambda typename:
+ models[typename].match(txt) is not None,
+ accepts),
+ False)
+ if accepts and txt else None)
+ event.Skip()
class PathEditor(ParamEditor):
def __init__(self, parent, pathdesc):
@@ -228,17 +254,19 @@
def SetPath(self, hmitree_node):
self.edit.ChangeValue(hmitree_node.hmi_path())
- if hmitree_node.nodetype in self.pathdesc.get("accepts").split(","):
- self.setValidityOK()
- else:
- self.setValidityNOK()
+ self.setValidity(
+ hmitree_node.nodetype in self.pathdesc.get("accepts").split(","))
def OnPathChanged(self, event):
# TODO : find corresponding hmitre node and type to update validity
# Lazy way : hide validity
- self.setValidityUnknown()
+ self.setValidity(None)
event.Skip()
+def KeepDoubleNewLines(txt):
+ return "\n\n".join(map(
+ lambda s:re.sub(r'\s+',' ',s),
+ txt.split("\n\n")))
_conf_key = "SVGHMIWidgetLib"
_preview_height = 200
@@ -292,11 +320,11 @@
self.signature_sizer = wx.BoxSizer(wx.VERTICAL)
self.args_box = wx.StaticBox(self.main_panel, -1,
_("Widget's arguments"),
- style = wx.ALIGN_RIGHT)
+ style = wx.ALIGN_CENTRE_HORIZONTAL)
self.args_sizer = wx.StaticBoxSizer(self.args_box, wx.VERTICAL)
self.paths_box = wx.StaticBox(self.main_panel, -1,
_("Widget's variables"),
- style = wx.ALIGN_RIGHT)
+ style = wx.ALIGN_CENTRE_HORIZONTAL)
self.paths_sizer = wx.StaticBoxSizer(self.paths_box, wx.VERTICAL)
self.signature_sizer.Add(self.args_sizer, flag=wx.GROW)
self.signature_sizer.AddSpacer(5)
@@ -333,8 +361,8 @@
editor.Destroy()
self.paths_editors = []
- def AddArgToSignature(self, arg):
- new_editor = ArgEditor(self, arg)
+ def AddArgToSignature(self, arg, prefillarg):
+ new_editor = ArgEditor(self, arg, prefillarg)
self.args_editors.append(new_editor)
self.args_sizer.Add(new_editor, flag=wx.GROW)
@@ -512,41 +540,56 @@
except Exception as e:
self.msg += str(e)
+ return
except XSLTApplyError as e:
self.msg += "Widget " + fname + " analysis error: " + e.message
- else:
+ return
- self.msg += "Widget " + fname + ": OK"
-
- print(etree.tostring(signature, pretty_print=True))
- widgets = signature.getroot()
- for defs in widgets.iter("defs"):
-
- # Keep double newlines (to mark paragraphs)
- self.desc.SetValue(defs.find("type").text + ":\n" + "\n\n".join(map(
- lambda s:s.replace("\n"," ").replace(" ", " "),
- defs.find("longdesc").text.split("\n\n"))))
- args = [arg for arg in defs.iter("arg")]
- self.args_box.Show(len(args)!=0)
- for arg in args:
- self.AddArgToSignature(arg)
- print(arg.get("name"))
- print(arg.get("accepts"))
- paths = [path for path in defs.iter("path")]
- self.paths_box.Show(len(paths)!=0)
- for path in paths:
- self.AddPathToSignature(path)
- print(path.get("name"))
- print(path.get("accepts"))
-
- for widget in widgets:
- widget_type = widget.get("type")
- print(widget_type)
- for path in widget.iterchildren("path"):
- path_value = path.get("value")
- path_accepts = map(
- str.strip, path.get("accepts", '')[1:-1].split(','))
- print(path, path_value, path_accepts)
+ self.msg += "Widget " + fname + ": OK"
+
+ print(etree.tostring(signature, pretty_print=True))
+ widgets = signature.getroot()
+ widget = widgets.find("widget")
+ defs = widget.find("defs")
+ # Keep double newlines (to mark paragraphs)
+ widget_desc = widget.find("desc")
+ self.desc.SetValue(
+ fname + ":\n" + (
+ _("No description given") if widget_desc is None else
+ KeepDoubleNewLines(widget_desc.text)
+ ) + "\n\n" +
+ defs.find("type").text + ":\n" +
+ KeepDoubleNewLines(defs.find("longdesc").text))
+ prefillargs = widget.findall("arg")
+ args = defs.findall("arg")
+ # extend args description in prefilled args in longer
+ # (case of variable list of args)
+ if len(prefillargs) < len(args):
+ prefillargs += [None]*(len(args)-len(prefillargs))
+ if args and len(prefillargs) > len(args):
+ # TODO: check ordinality of last arg
+ # TODO: check that only last arg has multiple ordinality
+ args += [args[-1]]*(len(prefillargs)-len(args))
+ self.args_box.Show(len(args)!=0)
+ for arg, prefillarg in izip(args,prefillargs):
+ self.AddArgToSignature(arg, prefillarg)
+ print(arg.get("name"))
+ print(arg.get("accepts"))
+ paths = defs.findall("path")
+ self.paths_box.Show(len(paths)!=0)
+ for path in paths:
+ self.AddPathToSignature(path)
+ print(path.get("name"))
+ print(path.get("accepts"))
+
+ for widget in widgets:
+ widget_type = widget.get("type")
+ print(widget_type)
+ for path in widget.iterchildren("path"):
+ path_value = path.get("value")
+ path_accepts = map(
+ str.strip, path.get("accepts", '')[1:-1].split(','))
+ print(path, path_value, path_accepts)
self.main_panel.SetupScrolling(scroll_x=False)
--- a/svghmi/widgetlib/voltmeter.svg Wed Jun 16 12:15:02 2021 +0200
+++ b/svghmi/widgetlib/voltmeter.svg Wed Jun 16 18:27:27 2021 +0200
@@ -93,9 +93,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="3.312923"
- inkscape:cx="554.2857"
- inkscape:cy="554.2857"
+ inkscape:zoom="1.1712952"
+ inkscape:cx="601.92253"
+ inkscape:cy="607.41638"
inkscape:document-units="mm"
inkscape:current-layer="svg2354"
showgrid="false"
@@ -438,9 +438,11 @@
id="path4304" />
</g>
<g
- inkscape:label="HMI:Meter@/PUMP0/SLOTH,0,666"
+ inkscape:label="HMI:Meter:0:500@/PUMP0/SLOTH,0,666"
transform="matrix(0.57180538,0,0,0.57180538,88.118425,163.79208)"
id="g19348">
+ <desc
+ id="desc132">Old Style Russian Voltmeter, from openclipart https://openclipart.org/detail/205486/voltmeter-and-ammeter</desc>
<path
inkscape:label="range"
sodipodi:open="true"