lpcmanager

Parents 58b63de9a08f
Children fecb8381e105
Fix options handling : real space resistant parsing. Values surrounded by double quotes in key-value pairs. Dialog stops messing with other columnns. OnChange code overriden in PythonFileCTNMixin so that content of options can add Alaram and StoredValue calls. + various rework in SetOption GetOption to avoid being poisoned by user input
--- a/LPCExtension.py Mon Nov 02 11:39:40 2020 +0100
+++ b/LPCExtension.py Thu Nov 05 14:31:30 2020 +0100
@@ -101,3 +101,12 @@
from WxGladeEditor import WxGladeEditor
WxGladeHMI.EditorType = WxGladeEditor
PythonEditor.COLUMNS_TYPE = {'Options': WampOptionsCellEditor}
+
+
+#
+# --------- special OnChange behavior ------------
+#
+
+from py_ext.PythonFileCTNMixin import PythonFileCTNMixin
+from OnChangeFromOptions import GetVarOnChangeContent
+PythonFileCTNMixin.GetVarOnChangeContent = GetVarOnChangeContent
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/OnChangeFromOptions.py Thu Nov 05 14:31:30 2020 +0100
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+
+from OptionsParsing import ParseOptions
+
+@staticmethod
+def GetVarOnChangeContent(var):
+ opts = variable.getopts()
+ parsed_opts = re.findall(opt_parser,opts)
+ needs_onChange = ('onchange', '') in parsed_opts
+ if needs_onchange:
+ existing_onchanges = [onchange.strip() for onchange in var.getonchange().split(',')]
+ for unwanted in ["Alarm", "StoredValue"]:
+ existing_onchanges.remove[unwanted]
+
+ new_onchange = existing_onchange[:]
+ if ('Static', '') in parsed_opts :
+ new_onchange += ["StoredValue"]
+ elif ('Alarm', '') in parsed_opts :
+ new_onchange += ["Alarm"]
+ return ','.join(new_onchange)
+
+ return var.getonchange()
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/OptionsParsing.py Thu Nov 05 14:31:30 2020 +0100
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+import re
+
+VARIABLETYPE = ["None", "Static", "Session", "Alarm"]
+opt_parser = re.compile(r'(\w+)\s*(?:=\s*"([^"]+)"\s*)?')
+
+def ParseOptions(opts):
+
+ class AttrDict(dict):
+ def __init__(self, *args, **kwargs):
+ dict.__init__(self, *args, **kwargs)
+ self.__dict__ = self
+
+ res = AttrDict(
+ is_onchange = False,
+ is_scada = False,
+ is_static = False,
+ variable_type_selection = 0,
+ unit = None,
+ min = None,
+ max = None,
+ precision = None,
+ subgroup = None,
+ other = None,
+ tags = None)
+
+ options = re.findall(opt_parser,opts)
+
+ for key,value in options:
+ if value == "":
+ if key == "onchange":
+ res.is_onchange = True
+ elif key in VARIABLETYPE[1:]:
+ res.variable_type_selection = VARIABLETYPE.index(key)
+ elif key == "scada":
+ res.is_scada = True
+ elif key == "static":
+ res.is_static = True
+ else:
+ if key in ["precision", "min","max"]:
+ value = int(value)
+
+ if key in res:
+ res[key] = value
+ else:
+ raise Exception("Unknown key in options")
+ return res
+
+
--- a/WampOptionsEditor.py Mon Nov 02 11:39:40 2020 +0100
+++ b/WampOptionsEditor.py Thu Nov 05 14:31:30 2020 +0100
@@ -1,10 +1,18 @@
from __future__ import absolute_import
+import re
import wx
import wx.grid
import controls
+from collections import namedtuple
+from OptionsParsing import ParseOptions, VARIABLETYPE
+
[ID_OPTIONSWIZARDDIALOG,ID_ONCHANGE,ID_OPTIONSTYPECHOICE,ID_SUBGROUPTEXT,ID_UNITTEXT,ID_VALUECHECKBOX,ID_MINSPIN,ID_MAXSPIN,ID_PRECISIONSPIN, ID_INITIALSPIN,ID_SCADACHECKBOX,ID_OTHERTEXT,ID_DESCRIPTION,ID_STATIC] = [wx.NewId() for _init_ctrls in range(14)]
-VARIABLETYPE = ["None", "Static", "Session", "Alarm"]
+
+
+excluded_chars = [ord(i) for i in '\n"']
+sanitizer = "".join([chr(i if i not in excluded_chars else ord(' ')) for i in xrange(256)])
+eraser = '\r'
class WampOptionsEditor(wx.Dialog):
def _init_sizers(self):
@@ -34,7 +42,6 @@
self.minValue=wx.StaticText(self, wx.ID_ANY, _("Min value:"))
self.maxValue=wx.StaticText(self, wx.ID_ANY, _("Max value:"))
self.PrecisionValue = wx.StaticText(self, wx.ID_ANY, _("Precision:"))
- self.InitialValue = wx.StaticText(self, wx.ID_ANY, _("Initial:"))
self.PropertySizer.AddWindow(self.minValue, flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=15)
self.PropertySizer.AddWindow(self.MinSpin, flag=wx.GROW)
@@ -48,12 +55,8 @@
self.PropertySizer.AddWindow(wx.StaticText(self, wx.ID_ANY, _("Other:")), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=15)
self.PropertySizer.AddWindow(self.OtherText, flag=wx.GROW)
- self.PropertySizer.AddWindow(self.InitialValue, flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=15)
- self.PropertySizer.AddWindow(self.InitialText, flag=wx.GROW)
-
- self.PropertySizer.AddWindow(wx.StaticText(self, wx.ID_ANY, _("Description:")), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=15)
- self.PropertySizer.AddWindow(self.DescriptionText, flag=wx.GROW)
-
+ self.PropertySizer.AddWindow(wx.StaticText(self, wx.ID_ANY, _("Tags:")), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=15)
+ self.PropertySizer.AddWindow(self.Tags, flag=wx.GROW)
self.PropertySizer.AddGrowableCol(1, 1)
self.MainSizer.AddSizer(self.PropertySizer, flag=wx.GROW | wx.UP | wx.RIGHT, border=15)
self.ButtonSizer.AddWindow(self.ClearButton)
@@ -79,13 +82,12 @@
self.MinSpin = wx.SpinCtrl(parent=self, id=ID_MINSPIN, style=wx.SP_VERTICAL)
self.MaxSpin = wx.SpinCtrl(parent=self, id=ID_MAXSPIN, style=wx.SP_VERTICAL)
self.PrecisionSpin = wx.SpinCtrl(parent=self, id=ID_PRECISIONSPIN, style=wx.SP_VERTICAL)
- self.InitialText = wx.TextCtrl(parent=self, id=ID_INITIALSPIN)
self.MinSpin.SetRange(-1000000, 1000000)
self.MaxSpin.SetRange(-1000000, 1000000)
self.ScadaCheckbox = wx.CheckBox(parent=self, id=ID_SCADACHECKBOX)
self.StaticCheckbox = wx.CheckBox(parent=self, id=ID_STATIC)
self.OtherText = wx.TextCtrl(parent=self, id=ID_OTHERTEXT)
- self.DescriptionText = wx.TextCtrl(parent=self, id=ID_DESCRIPTION, size=(-1, 100), style=wx.TE_MULTILINE | wx.SUNKEN_BORDER)
+ self.Tags = wx.TextCtrl(parent=self, id=ID_DESCRIPTION, size=(-1, 100), style=wx.TE_MULTILINE|wx.SUNKEN_BORDER)
self.ClearButton = wx.Button(self, wx.ID_CLEAR, _("Clear"))
self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL)
self.ValueCheckbox.Bind(wx.EVT_CHECKBOX, self.EnableValue)
@@ -98,8 +100,6 @@
self.MinSpin.Enable(False)
self.MaxSpin.Enable(False)
self.PrecisionSpin.Enable(False)
- self.InitialText.Enable(False)
- self.DescriptionText.Enable(False)
self.StaticCheckbox.Enable(False)
self.Bind(wx.EVT_CHOICE, self.Enabler, self.OptionsTypeChoice)
@@ -108,16 +108,13 @@
- def __init__(self, parent, opt, desc):
- self.TypeSelected = 0
+ def __init__(self, parent, opt):
self.options = opt
self.value = ""
- self.Description = ""
self.OnChange = False
self._init_ctrls(parent, opt)
self._init_sizers()
- self.SetOptions(opt, desc)
- self.GetOptions()
+ self.SetOptions(opt)
self.Enabler(None)
@@ -132,8 +129,6 @@
self.OnChangeCheckbox.Enable(True)
self.ValueCheckbox.Enable(True)
self.PrecisionSpin.Enable(True)
- self.InitialText.Enable(True)
- self.DescriptionText.Enable(True)
else:
self.ScadaCheckbox.Enable(True)
self.OtherText.Enable(True)
@@ -143,8 +138,6 @@
self.OnChangeCheckbox.Enable(False)
self.ValueCheckbox.Enable(True)
self.PrecisionSpin.Enable(True)
- self.InitialText.Enable(True)
- self.DescriptionText.Enable(True)
if self.OptionsTypeChoice.GetSelection() == 3 and self.ScadaCheckbox.GetValue():
self.StaticCheckbox.Enable(True)
@@ -171,9 +164,8 @@
self.MaxSpin.Enable(False)
self.StaticCheckbox.Enable(False)
self.PrecisionSpin.SetValue(0)
- self.InitialText.SetValue("")
self.OnChangeCheckbox.SetValue(False)
- self.DescriptionText.SetValue("")
+ self.Tags.SetValue("")
self.OnChangeCheckbox.Enable(False)
self.OtherText.Enable(False)
self.SubgroupText.Enable(False)
@@ -183,8 +175,7 @@
self.MinSpin.Enable(False)
self.MaxSpin.Enable(False)
self.PrecisionSpin.Enable(False)
- self.InitialText.Enable(False)
- self.DescriptionText.Enable(False)
+ self.Tags.Enable(False)
self.StaticCheckbox.Enable(False)
def OnTypeChoice(self, event):
@@ -193,93 +184,74 @@
def EnableValue(self, event):
self.MinSpin.Enable(self.ValueCheckbox.GetValue())
self.MaxSpin.Enable(self.ValueCheckbox.GetValue())
+ event.Skip()
- def SetOptions(self, opt, desc):
- opt1 = opt[0]
- opt2 = opt[1]
- opt3 = opt[2]
- optionsTemp = opt1.split(" ")
- options = []
- for el in optionsTemp:
- options.append(el.split("="))
- for el in options:
- if len(el) == 1:
- if el[0] == "":
- self.OptionsTypeChoice.SetSelection(0)
- elif el[0] == "Static":
- self.OptionsTypeChoice.SetSelection(1)
- elif el[0] == "Session":
- self.OptionsTypeChoice.SetSelection(2)
- elif el[0] == "Alarm":
- self.OptionsTypeChoice.SetSelection(3)
- if el[0] == "scada":
- self.ScadaCheckbox.SetValue(True)
- if el[0] == "static":
- self.StaticCheckbox.Enable(True)
- self.StaticCheckbox.SetValue(True)
- else:
- if el[0] == "subgroup":
- self.SubgroupText.SetValue(el[1])
- elif el[0] == "unit":
- self.UnitText.SetValue(el[1])
- elif el[0] == "min":
- self.ValueCheckbox.SetValue(True)
- self.MinSpin.SetValue(int(el[1]))
- self.MinSpin.Enable(True)
- elif el[0] == "max":
- self.ValueCheckbox.SetValue(True)
- self.MaxSpin.SetValue(int(el[1]))
- self.MaxSpin.Enable(True)
- elif el[0] == "other":
- self.OtherText.SetValue(el[1])
- elif el[0] == "precision":
- self.PrecisionSpin.SetValue(int(el[1]))
- if len(opt2)>0:
- self.OnChangeCheckbox.Enable(True)
- self.OnChangeCheckbox.SetValue(True)
- if self.OptionsTypeChoice.GetSelection() == 3 and self.ScadaCheckbox.GetValue():
- self.StaticCheckbox.Enable(True)
- if opt3 != "":
- self.InitialText.SetValue(opt3)
- self.DescriptionText.SetValue(desc)
+
+ def SetOptions(self, opts):
+
+ res = ParseOptions(opts)
+
+ self.OptionsTypeChoice.SetSelection(res.variable_type_selection)
+ self.OnChangeCheckbox.SetValue(res.is_onchange)
+ self.ScadaCheckbox.SetValue(res.is_scada)
+ self.StaticCheckbox.SetValue(res.is_static)
+
+ self.StaticCheckbox.Enable(
+ res.is_static or (res.variable_type_selection == 3 and res.is_scada))
+
+ if res.subgroup is not None:
+ self.SubgroupText.SetValue(res.subgroup)
+ if res.unit is not None:
+ self.UnitText.SetValue(res.unit)
+ if res.min is not None or res.max is not None:
+ self.ValueCheckbox.SetValue(True)
+ self.MinSpin.Enable(True)
+ self.MaxSpin.Enable(True)
+ self.MinSpin.SetValue(res.min if res.min else 0)
+ self.MaxSpin.SetValue(res.max if res.max else 0)
+
+ if res.other is not None:
+ self.OtherText.SetValue(res.other)
+ if res.precision is not None:
+ self.PrecisionSpin.SetValue(res.precision)
+
+ if res.tags is not None:
+ self.Tags.SetValue(res.tags)
def GetOptions(self):
- self.TypeSelected = self.OptionsTypeChoice.GetSelection()
- if self.TypeSelected<0:
- self.TypeSelected = 0
- options = VARIABLETYPE[self.TypeSelected]
+ TypeSelected = self.OptionsTypeChoice.GetSelection()
+ if TypeSelected<0:
+ TypeSelected = 0
+ options = VARIABLETYPE[TypeSelected]
if options == "None":
options = ""
+
+ if self.OnChangeCheckbox.GetValue():
+ options += " onchange"
+
if self.ScadaCheckbox.GetValue():
options += " scada"
+
if self.StaticCheckbox.GetValue():
options += " static"
- if self.SubgroupText.GetLineText(0) != "":
- options += " subgroup=" + self.SubgroupText.GetLineText(0)
- if self.UnitText.GetLineText(0) != "":
- options += " unit=" + self.UnitText.GetLineText(0)
+
if self.MinSpin.IsEnabled():
- options += " min=" + str(self.MinSpin.GetValue())
- options += " max=" + str(self.MaxSpin.GetValue())
+ options += ' min="' + str(self.MinSpin.GetValue()) + '"'
+ options += ' max="' + str(self.MaxSpin.GetValue()) + '"'
+
if self.PrecisionSpin.GetValue() != 0:
- options += " precision=" + str(self.PrecisionSpin.GetValue())
- if self.OtherText.GetLineText(0) != "":
- options += " other=" + self.OtherText.GetLineText(0)
- if self.OnChangeCheckbox.GetValue():
- self.OnChange = True
- else:
- self.OnChange = False
- if VARIABLETYPE[self.TypeSelected] == "Alarm":
- self.value = "Alarm"
- elif VARIABLETYPE[self.TypeSelected] == "Static":
- self.value = "StoredValue"
- else:
- self.value = ""
- self.OnChange = True
- self.Description = self.DescriptionText.GetValue()
- self.Initial = self.InitialText.GetValue()
- return options, self.OnChange,self.value, self.Description, self.Initial
+ options += ' precision=' + str(self.PrecisionSpin.GetValue()) + '"'
+
+ for name, ctrl in [('subgroup', self.SubgroupText),
+ ('unit', self.UnitText),
+ ('other', self.OtherText),
+ ('tags', self.Tags)]:
+ content = ctrl.GetValue().encode('ascii','ignore')
+ if content:
+ options += ' ' + name + '="' + content.translate(sanitizer, eraser) + '"'
+
+ return options
class WampOptionsCellControl(wx.PyControl):
@@ -300,7 +272,7 @@
# create location text control
self.Options = wx.TextCtrl(self, size=wx.Size(0, -1),
style=wx.TE_PROCESS_ENTER)
- self.Options.Bind(wx.EVT_KEY_DOWN, self.OnDurationChar)
+ self.Options.Bind(wx.EVT_KEY_DOWN, self.OnChar)
main_sizer.AddWindow(self.Options, flag=wx.GROW)
# create browse button
@@ -316,16 +288,6 @@
self.table = table
self.row = row
- def OnDurationChar(self, event):
- keycode = event.GetKeyCode()
- if keycode in [wx.WXK_RETURN, wx.WXK_TAB]:
- self.Parent.Parent.ProcessEvent(event)
- elif keycode == wx.WXK_ESCAPE:
- self.Duration.SetValue(self.Default)
- self.Parent.Parent.CloseEditControl()
- else:
- event.Skip()
-
def SetValue(self, value):
self.Default = value
self.Options.SetValue(value)
@@ -338,19 +300,12 @@
def OnEditButtonClick(self, event):
# pop up the Duration Editor dialog
- options = [self.GetValue(), self.table.GetValueByName(self.row, _("OnChange")), self.table.GetValueByName(self.row, "Initial")]
- desc = self.table.GetValueByName(self.row, _("Description"))
- dialog = WampOptionsEditor(self.parent, options, desc)
+ options = self.GetValue()
+ dialog = WampOptionsEditor(self.parent, options)
answer = dialog.ShowModal()
- opt, OnChange, value, description, initial = dialog.GetOptions()
+ opt = dialog.GetOptions()
if answer == wx.ID_OK:
self.SetValue(opt)
- if OnChange:
- self.table.SetValueByName(self.row, _("OnChange"), value)
- else:
- self.table.SetValueByName(self.row, _("OnChange"), "")
- self.table.SetValueByName(self.row, _("Description"), description)
- self.table.SetValueByName(self.row, _("Initial"), str(initial))
dialog.Destroy()