laurent@566: #!/usr/bin/env python b@418: # -*- coding: utf-8 -*- b@418: b@418: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor b@418: #based on the plcopen standard. b@418: # b@418: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD b@418: # b@418: #See COPYING file for copyrights details. b@418: # b@418: #This library is free software; you can redistribute it and/or b@418: #modify it under the terms of the GNU General Public b@418: #License as published by the Free Software Foundation; either b@418: #version 2.1 of the License, or (at your option) any later version. b@418: # b@418: #This library is distributed in the hope that it will be useful, b@418: #but WITHOUT ANY WARRANTY; without even the implied warranty of b@418: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU b@418: #General Public License for more details. b@418: # b@418: #You should have received a copy of the GNU General Public b@418: #License along with this library; if not, write to the Free Software b@418: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA b@418: laurent@436: import os b@418: import wx, wx.grid laurent@623: import re b@418: laurent@580: from types import TupleType, StringType, UnicodeType b@419: b@418: from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS laurent@625: from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT laurent@591: from dialogs.ArrayTypeDialog import ArrayTypeDialog laurent@586: from CustomGrid import CustomGrid laurent@604: from CustomTable import CustomTable laurent@586: from LocationCellEditor import LocationCellEditor laurent@586: laurent@431: # Compatibility function for wx versions < 2.6 laurent@431: def AppendMenu(parent, help, id, kind, text): laurent@431: if wx.VERSION >= (2, 6, 0): laurent@431: parent.Append(help=help, id=id, kind=kind, text=text) laurent@431: else: laurent@431: parent.Append(helpString=help, id=id, kind=kind, item=text) laurent@431: laurent@431: [TITLE, TOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, TYPESTREE, laurent@431: INSTANCESTREE, LIBRARYTREE, SCALING laurent@431: ] = range(9) b@418: b@418: #------------------------------------------------------------------------------- b@418: # Variables Editor Panel b@418: #------------------------------------------------------------------------------- b@418: b@418: def GetVariableTableColnames(location): b@418: _ = lambda x : x b@418: if location: laurent@483: return ["#", _("Name"), _("Class"), _("Type"), _("Location"), _("Initial Value"), _("Option"), _("Documentation")] laurent@483: return ["#", _("Name"), _("Class"), _("Type"), _("Initial Value"), _("Option"), _("Documentation")] laurent@483: laurent@484: def GetOptions(constant=True, retain=True, non_retain=True): b@418: _ = lambda x : x laurent@484: options = [""] laurent@484: if constant: laurent@484: options.append(_("Constant")) laurent@484: if retain: laurent@484: options.append(_("Retain")) laurent@484: if non_retain: laurent@484: options.append(_("Non-Retain")) laurent@484: return options laurent@483: OPTIONS_DICT = dict([(_(option), option) for option in GetOptions()]) b@418: b@418: def GetFilterChoiceTransfer(): b@418: _ = lambda x : x b@418: return {_("All"): _("All"), _("Interface"): _("Interface"), b@418: _(" Input"): _("Input"), _(" Output"): _("Output"), _(" InOut"): _("InOut"), b@418: _(" External"): _("External"), _("Variables"): _("Variables"), _(" Local"): _("Local"), b@418: _(" Temp"): _("Temp"), _("Global"): _("Global")}#, _("Access") : _("Access")} laurent@509: VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().iterkeys()]) b@418: VARIABLE_CLASSES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().itervalues()]) b@418: laurent@484: CheckOptionForClass = {"Local": lambda x: x, laurent@484: "Temp": lambda x: "", laurent@484: "Input": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""), laurent@484: "InOut": lambda x: "", laurent@484: "Output": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""), laurent@484: "Global": lambda x: {"Constant": "Constant", "Retain": "Retain"}.get(x, ""), laurent@484: "External": lambda x: {"Constant": "Constant"}.get(x, "") laurent@484: } laurent@484: laurent@625: LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$") laurent@623: laurent@604: class VariableTable(CustomTable): b@418: b@418: """ b@418: A custom wx.grid.Grid Table using user supplied data b@418: """ b@418: def __init__(self, parent, data, colnames): b@418: # The base class must be initialized *first* laurent@604: CustomTable.__init__(self, parent, data, colnames) b@418: self.old_value = None laurent@604: b@418: def GetValue(self, row, col): b@418: if row < self.GetNumberRows(): b@418: if col == 0: b@418: return self.data[row]["Number"] b@418: colname = self.GetColLabelValue(col, False) laurent@507: value = self.data[row].get(colname, "") laurent@507: if colname == "Type" and isinstance(value, TupleType): laurent@507: if value[0] == "array": laurent@507: return "ARRAY [%s] OF %s" % (",".join(map(lambda x : "..".join(x), value[2])), value[1]) laurent@580: if not isinstance(value, (StringType, UnicodeType)): laurent@580: value = str(value) laurent@483: if colname in ["Class", "Option"]: b@418: return _(value) b@418: return value b@418: b@418: def SetValue(self, row, col, value): b@418: if col < len(self.colnames): b@418: colname = self.GetColLabelValue(col, False) b@418: if colname == "Name": b@418: self.old_value = self.data[row][colname] b@418: elif colname == "Class": b@418: value = VARIABLE_CLASSES_DICT[value] laurent@484: self.SetValueByName(row, "Option", CheckOptionForClass[value](self.GetValueByName(row, "Option"))) laurent@484: if value == "External": laurent@484: self.SetValueByName(row, "Initial Value", "") laurent@483: elif colname == "Option": laurent@483: value = OPTIONS_DICT[value] b@418: self.data[row][colname] = value b@418: b@418: def GetOldValue(self): b@418: return self.old_value b@418: b@418: def _updateColAttrs(self, grid): b@418: """ b@418: wx.grid.Grid -> update the column attributes to add the b@418: appropriate renderer given the column name. b@418: b@418: Otherwise default to the default renderer. b@418: """ b@418: for row in range(self.GetNumberRows()): laurent@484: var_class = self.GetValueByName(row, "Class") laurent@552: var_type = self.GetValueByName(row, "Type") laurent@566: row_highlights = self.Highlights.get(row, {}) b@418: for col in range(self.GetNumberCols()): b@418: editor = None b@418: renderer = None b@418: colname = self.GetColLabelValue(col, False) laurent@600: if self.Parent.Debug: laurent@600: grid.SetReadOnly(row, col, True) laurent@600: else: laurent@600: if colname == "Option": laurent@600: options = GetOptions(constant = var_class in ["Local", "External", "Global"], laurent@600: retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"], laurent@600: non_retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"]) laurent@600: if len(options) > 1: laurent@600: editor = wx.grid.GridCellChoiceEditor() laurent@600: editor.SetParameters(",".join(map(_, options))) laurent@600: else: b@418: grid.SetReadOnly(row, col, True) laurent@600: elif col != 0 and self.GetValueByName(row, "Edit"): laurent@600: grid.SetReadOnly(row, col, False) laurent@600: if colname == "Name": laurent@600: if self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]: laurent@600: grid.SetReadOnly(row, col, True) laurent@552: else: laurent@552: editor = wx.grid.GridCellTextEditor() laurent@600: renderer = wx.grid.GridCellStringRenderer() laurent@600: elif colname == "Initial Value": laurent@600: if var_class != "External": laurent@600: if self.Parent.Controler.IsEnumeratedType(var_type): laurent@600: editor = wx.grid.GridCellChoiceEditor() laurent@600: editor.SetParameters(",".join(self.Parent.Controler.GetEnumeratedDataValues(var_type))) laurent@600: else: laurent@600: editor = wx.grid.GridCellTextEditor() laurent@600: renderer = wx.grid.GridCellStringRenderer() laurent@600: else: laurent@600: grid.SetReadOnly(row, col, True) laurent@600: elif colname == "Location": laurent@600: if var_class in ["Local", "Global"] and self.Parent.Controler.IsLocatableType(var_type): laurent@600: editor = LocationCellEditor(self, self.Parent.Controler) laurent@600: renderer = wx.grid.GridCellStringRenderer() laurent@600: else: laurent@600: grid.SetReadOnly(row, col, True) laurent@600: elif colname == "Class": laurent@600: if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]: laurent@600: grid.SetReadOnly(row, col, True) laurent@600: else: laurent@600: editor = wx.grid.GridCellChoiceEditor() laurent@600: excluded = [] laurent@600: if self.Parent.PouIsUsed: laurent@600: excluded.extend(["Input","Output","InOut"]) laurent@600: if self.Parent.IsFunctionBlockType(var_type): laurent@600: excluded.extend(["Local","Temp"]) laurent@600: editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded])) laurent@600: elif colname != "Documentation": laurent@600: grid.SetReadOnly(row, col, True) b@418: b@418: grid.SetCellEditor(row, col, editor) b@418: grid.SetCellRenderer(row, col, renderer) b@418: laurent@625: if colname == "Location" and LOCATION_MODEL.match(self.GetValueByName(row, "Location")) is None: laurent@625: highlight_colours = ERROR_HIGHLIGHT laurent@625: else: laurent@625: highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1] laurent@566: grid.SetCellBackgroundColour(row, col, highlight_colours[0]) laurent@566: grid.SetCellTextColour(row, col, highlight_colours[1]) laurent@604: self.ResizeRow(grid, row) laurent@566: b@418: b@418: class VariableDropTarget(wx.TextDropTarget): b@419: ''' b@419: This allows dragging a variable location from somewhere to the Location b@419: column of a variable row. b@419: b@419: The drag source should be a TextDataObject containing a Python tuple like: b@419: ('%ID0.0.0', 'location', 'REAL') b@419: b@419: c_ext/CFileEditor.py has an example of this (you can drag a C extension b@419: variable to the Location column of the variable panel). b@419: ''' b@418: def __init__(self, parent): b@418: wx.TextDropTarget.__init__(self) b@418: self.ParentWindow = parent b@418: b@418: def OnDropText(self, x, y, data): b@418: x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y) b@418: col = self.ParentWindow.VariablesGrid.XToCol(x) b@418: row = self.ParentWindow.VariablesGrid.YToRow(y - self.ParentWindow.VariablesGrid.GetColLabelSize()) laurent@616: message = None laurent@616: element_type = self.ParentWindow.ElementType laurent@616: try: laurent@616: values = eval(data) laurent@616: except: laurent@616: message = _("Invalid value \"%s\" for variable grid element")%data laurent@616: values = None laurent@616: if not isinstance(values, TupleType): laurent@616: message = _("Invalid value \"%s\" for variable grid element")%data laurent@616: values = None laurent@616: if values is not None: laurent@616: if col != wx.NOT_FOUND and row != wx.NOT_FOUND: laurent@616: if self.ParentWindow.Table.GetColLabelValue(col, False) != "Location": laurent@616: return laurent@616: if not self.ParentWindow.Table.GetValueByName(row, "Edit"): laurent@616: message = _("Can't give a location to a function block instance") laurent@616: elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]: laurent@616: message = _("Can only give a location to local or global variables") laurent@616: elif values is not None and values[1] == "location": b@418: location = values[0] b@418: variable_type = self.ParentWindow.Table.GetValueByName(row, "Type") b@418: base_type = self.ParentWindow.Controler.GetBaseType(variable_type) b@418: if location.startswith("%"): b@418: if base_type != values[2]: b@418: message = _("Incompatible data types between \"%s\" and \"%s\"")%(values[2], variable_type) b@418: else: b@418: self.ParentWindow.Table.SetValue(row, col, location) b@418: self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid) b@418: self.ParentWindow.SaveValues() b@418: else: b@418: if location[0].isdigit() and base_type != "BOOL": b@418: message = _("Incompatible size of data between \"%s\" and \"BOOL\"")%location b@418: elif location[0] not in LOCATIONDATATYPES: b@418: message = _("Unrecognized data size \"%s\"")%location[0] b@418: elif base_type not in LOCATIONDATATYPES[location[0]]: b@418: message = _("Incompatible size of data between \"%s\" and \"%s\"")%(location, variable_type) b@418: else: b@418: dialog = wx.SingleChoiceDialog(self.ParentWindow, _("Select a variable class:"), _("Variable class"), ["Input", "Output", "Memory"], wx.OK|wx.CANCEL) b@418: if dialog.ShowModal() == wx.ID_OK: b@418: selected = dialog.GetSelection() b@418: if selected == 0: b@418: location = "%I" + location b@418: elif selected == 1: b@418: location = "%Q" + location b@418: else: b@418: location = "%M" + location b@418: self.ParentWindow.Table.SetValue(row, col, location) b@418: self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid) b@418: self.ParentWindow.SaveValues() b@418: dialog.Destroy() laurent@616: elif (element_type not in ["config", "resource"] and values[1] == "Global" and self.ParentWindow.Filter in ["All", "Interface", "External"] or laurent@616: element_type in ["config", "resource"] and values[1] == "location"): laurent@616: if values[1] == "location": laurent@616: var_name = values[3] laurent@616: else: laurent@616: var_name = values[0] laurent@616: tagname = self.ParentWindow.GetTagName() laurent@616: if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]: laurent@616: message = _("\"%s\" pou already exists!")%var_name laurent@616: elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]: laurent@616: var_infos = self.ParentWindow.DefaultValue.copy() laurent@616: var_infos["Name"] = var_name laurent@616: var_infos["Type"] = values[2] laurent@616: if values[1] == "location": laurent@616: var_infos["Class"] = "Global" laurent@616: var_infos["Location"] = values[0] laurent@616: else: laurent@616: var_infos["Class"] = "External" laurent@616: var_infos["Number"] = len(self.ParentWindow.Values) laurent@616: self.ParentWindow.Values.append(var_infos) laurent@616: self.ParentWindow.SaveValues() laurent@616: self.ParentWindow.RefreshValues() laurent@616: laurent@616: if message is not None: laurent@616: wx.CallAfter(self.ShowMessage, message) b@418: laurent@616: b@418: def ShowMessage(self, message): b@418: message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) b@418: message.ShowModal() b@418: message.Destroy() b@418: b@418: [ID_VARIABLEEDITORPANEL, ID_VARIABLEEDITORPANELVARIABLESGRID, laurent@625: ID_VARIABLEEDITORCONTROLPANEL, ID_VARIABLEEDITORPANELRETURNTYPELABEL, laurent@625: ID_VARIABLEEDITORPANELRETURNTYPE, ID_VARIABLEEDITORPANELDESCRIPTIONLABEL, laurent@625: ID_VARIABLEEDITORPANELDESCRIPTION, ID_VARIABLEEDITORPANELCLASSFILTERLABEL, b@418: ID_VARIABLEEDITORPANELCLASSFILTER, ID_VARIABLEEDITORPANELADDBUTTON, b@418: ID_VARIABLEEDITORPANELDELETEBUTTON, ID_VARIABLEEDITORPANELUPBUTTON, laurent@625: ID_VARIABLEEDITORPANELDOWNBUTTON, laurent@625: ] = [wx.NewId() for _init_ctrls in range(13)] b@418: b@418: class VariablePanel(wx.Panel): b@418: b@418: if wx.VERSION < (2, 6, 0): b@418: def Bind(self, event, function, id = None): b@418: if id is not None: b@418: event(self, id, function) b@418: else: b@418: event(self, function) b@418: b@418: def _init_coll_MainSizer_Items(self, parent): laurent@586: parent.AddWindow(self.ControlPanel, 0, border=5, flag=wx.GROW|wx.ALL) b@418: parent.AddWindow(self.VariablesGrid, 0, border=0, flag=wx.GROW) laurent@586: b@418: def _init_coll_MainSizer_Growables(self, parent): b@418: parent.AddGrowableCol(0) laurent@586: parent.AddGrowableRow(1) b@418: b@418: def _init_coll_ControlPanelSizer_Items(self, parent): laurent@625: parent.AddWindow(self.ReturnTypeLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) laurent@586: parent.AddWindow(self.ReturnType, 0, border=0, flag=0) laurent@625: parent.AddWindow(self.DescriptionLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) laurent@625: parent.AddWindow(self.Description, 0, border=0, flag=0) laurent@625: parent.AddWindow(self.ClassFilterLabel, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) laurent@586: parent.AddWindow(self.ClassFilter, 0, border=0, flag=0) laurent@586: parent.AddWindow(self.AddButton, 0, border=0, flag=0) laurent@586: parent.AddWindow(self.DeleteButton, 0, border=0, flag=0) laurent@586: parent.AddWindow(self.UpButton, 0, border=0, flag=0) laurent@586: parent.AddWindow(self.DownButton, 0, border=0, flag=0) laurent@586: b@418: def _init_coll_ControlPanelSizer_Growables(self, parent): laurent@627: parent.AddGrowableCol(5) b@418: parent.AddGrowableRow(0) b@418: b@418: def _init_sizers(self): laurent@586: self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0) laurent@625: self.ControlPanelSizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5) b@418: b@418: self._init_coll_MainSizer_Items(self.MainSizer) b@418: self._init_coll_MainSizer_Growables(self.MainSizer) b@418: self._init_coll_ControlPanelSizer_Items(self.ControlPanelSizer) b@418: self._init_coll_ControlPanelSizer_Growables(self.ControlPanelSizer) laurent@586: laurent@586: self.ControlPanel.SetSizer(self.ControlPanelSizer) b@418: self.SetSizer(self.MainSizer) laurent@586: b@418: def _init_ctrls(self, prnt): b@418: wx.Panel.__init__(self, id=ID_VARIABLEEDITORPANEL, b@418: name='VariableEditorPanel', parent=prnt, pos=wx.Point(0, 0), b@418: size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) b@418: laurent@577: self.VariablesGrid = CustomGrid(id=ID_VARIABLEEDITORPANELVARIABLESGRID, b@418: name='VariablesGrid', parent=self, pos=wx.Point(0, 0), b@418: size=wx.Size(0, 0), style=wx.VSCROLL) laurent@595: self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) b@418: if wx.VERSION >= (2, 6, 0): b@418: self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange) b@418: self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick) b@418: self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown) b@418: else: b@418: wx.grid.EVT_GRID_CELL_CHANGE(self.VariablesGrid, self.OnVariablesGridCellChange) b@418: wx.grid.EVT_GRID_CELL_LEFT_CLICK(self.VariablesGrid, self.OnVariablesGridCellLeftClick) b@418: wx.grid.EVT_GRID_EDITOR_SHOWN(self.VariablesGrid, self.OnVariablesGridEditorShown) b@418: b@418: self.ControlPanel = wx.ScrolledWindow(id=ID_VARIABLEEDITORCONTROLPANEL, b@418: name='ControlPanel', parent=self, pos=wx.Point(0, 0), b@418: size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) laurent@586: self.ControlPanel.SetScrollRate(10, 0) b@418: laurent@625: self.ReturnTypeLabel = wx.StaticText(id=ID_VARIABLEEDITORPANELRETURNTYPELABEL, laurent@625: label=_('Return Type:'), name='ReturnTypeLabel', parent=self.ControlPanel, laurent@586: pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) b@418: b@418: self.ReturnType = wx.ComboBox(id=ID_VARIABLEEDITORPANELRETURNTYPE, b@418: name='ReturnType', parent=self.ControlPanel, pos=wx.Point(0, 0), laurent@627: size=wx.Size(145, -1), style=wx.CB_READONLY) b@418: self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, id=ID_VARIABLEEDITORPANELRETURNTYPE) b@418: laurent@625: self.DescriptionLabel = wx.StaticText(id=ID_VARIABLEEDITORPANELDESCRIPTIONLABEL, laurent@625: label=_('Description:'), name='DescriptionLabel', parent=self.ControlPanel, laurent@625: pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) laurent@625: laurent@625: self.Description = wx.TextCtrl(id=ID_VARIABLEEDITORPANELDESCRIPTION, laurent@625: name='Description', parent=self.ControlPanel, pos=wx.Point(0, 0), laurent@627: size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER) laurent@625: self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, id=ID_VARIABLEEDITORPANELDESCRIPTION) laurent@625: self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged) laurent@625: laurent@625: self.ClassFilterLabel = wx.StaticText(id=ID_VARIABLEEDITORPANELCLASSFILTERLABEL, laurent@625: label=_('Class Filter:'), name='ClassFilterLabel', parent=self.ControlPanel, laurent@586: pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) b@418: b@418: self.ClassFilter = wx.ComboBox(id=ID_VARIABLEEDITORPANELCLASSFILTER, b@418: name='ClassFilter', parent=self.ControlPanel, pos=wx.Point(0, 0), laurent@627: size=wx.Size(145, -1), style=wx.CB_READONLY) b@418: self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, id=ID_VARIABLEEDITORPANELCLASSFILTER) b@418: b@418: self.AddButton = wx.Button(id=ID_VARIABLEEDITORPANELADDBUTTON, label=_('Add'), b@418: name='AddButton', parent=self.ControlPanel, pos=wx.Point(0, 0), b@418: size=wx.DefaultSize, style=0) laurent@577: b@418: self.DeleteButton = wx.Button(id=ID_VARIABLEEDITORPANELDELETEBUTTON, label=_('Delete'), b@418: name='DeleteButton', parent=self.ControlPanel, pos=wx.Point(0, 0), b@418: size=wx.DefaultSize, style=0) laurent@577: b@418: self.UpButton = wx.Button(id=ID_VARIABLEEDITORPANELUPBUTTON, label='^', b@418: name='UpButton', parent=self.ControlPanel, pos=wx.Point(0, 0), laurent@627: size=wx.Size(28, -1), style=0) laurent@577: b@418: self.DownButton = wx.Button(id=ID_VARIABLEEDITORPANELDOWNBUTTON, label='v', b@418: name='DownButton', parent=self.ControlPanel, pos=wx.Point(0, 0), laurent@627: size=wx.Size(28, -1), style=0) laurent@577: b@418: self._init_sizers() b@418: laurent@600: def __init__(self, parent, window, controler, element_type, debug=False): b@418: self._init_ctrls(parent) b@418: self.ParentWindow = window b@418: self.Controler = controler b@418: self.ElementType = element_type laurent@600: self.Debug = debug b@418: laurent@566: self.RefreshHighlightsTimer = wx.Timer(self, -1) laurent@566: self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer) laurent@566: b@418: self.Filter = "All" b@418: self.FilterChoices = [] b@418: self.FilterChoiceTransfer = GetFilterChoiceTransfer() b@418: b@424: self.DefaultValue = { "Name" : "", "Class" : "", "Type" : "INT", "Location" : "", laurent@483: "Initial Value" : "", "Option" : "", b@424: "Documentation" : "", "Edit" : True b@424: } b@424: b@418: if element_type in ["config", "resource"]: b@418: self.DefaultTypes = {"All" : "Global"} b@418: else: b@418: self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"} b@424: b@424: if element_type in ["config", "resource"] \ b@424: or element_type in ["program", "transition", "action"]: b@424: # this is an element that can have located variables b@418: self.Table = VariableTable(self, [], GetVariableTableColnames(True)) b@424: b@424: if element_type in ["config", "resource"]: b@424: self.FilterChoices = ["All", "Global"]#,"Access"] b@418: else: b@424: self.FilterChoices = ["All", b@424: "Interface", " Input", " Output", " InOut", " External", b@424: "Variables", " Local", " Temp"]#,"Access"] b@424: b@424: # these condense the ColAlignements list b@424: l = wx.ALIGN_LEFT b@424: c = wx.ALIGN_CENTER b@424: laurent@483: # Num Name Class Type Loc Init Option Doc laurent@483: self.ColSizes = [40, 80, 70, 80, 80, 80, 100, 80] laurent@483: self.ColAlignements = [c, l, l, l, l, l, l, l] b@424: b@424: else: b@424: # this is an element that cannot have located variables b@418: self.Table = VariableTable(self, [], GetVariableTableColnames(False)) b@424: b@418: if element_type == "function": b@424: self.FilterChoices = ["All", b@424: "Interface", " Input", " Output", " InOut", laurent@484: "Variables", " Local"] b@418: else: b@424: self.FilterChoices = ["All", b@424: "Interface", " Input", " Output", " InOut", " External", b@424: "Variables", " Local", " Temp"] b@424: b@424: # these condense the ColAlignements list b@424: l = wx.ALIGN_LEFT b@424: c = wx.ALIGN_CENTER b@424: laurent@483: # Num Name Class Type Init Option Doc laurent@483: self.ColSizes = [40, 80, 70, 80, 80, 100, 160] laurent@483: self.ColAlignements = [c, l, l, l, l, l, l] b@424: b@418: for choice in self.FilterChoices: b@418: self.ClassFilter.Append(_(choice)) b@424: b@418: reverse_transfer = {} b@418: for filter, choice in self.FilterChoiceTransfer.items(): b@418: reverse_transfer[choice] = filter b@418: self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter])) b@418: self.RefreshTypeList() b@418: b@418: self.VariablesGrid.SetTable(self.Table) laurent@577: self.VariablesGrid.SetButtons({"Add": self.AddButton, laurent@577: "Delete": self.DeleteButton, laurent@577: "Up": self.UpButton, laurent@577: "Down": self.DownButton}) laurent@600: self.VariablesGrid.SetEditable(not self.Debug) laurent@577: laurent@577: def _AddVariable(new_row): laurent@577: if not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]: laurent@577: row_content = self.DefaultValue.copy() laurent@577: if self.Filter in self.DefaultTypes: laurent@577: row_content["Class"] = self.DefaultTypes[self.Filter] laurent@577: else: laurent@577: row_content["Class"] = self.Filter laurent@577: if self.Filter == "All" and len(self.Values) > 0: laurent@577: self.Values.insert(new_row, row_content) laurent@577: else: laurent@577: self.Values.append(row_content) laurent@577: new_row = self.Table.GetNumberRows() laurent@577: self.SaveValues() laurent@577: self.RefreshValues() laurent@577: return new_row laurent@577: return self.VariablesGrid.GetGridCursorRow() laurent@577: setattr(self.VariablesGrid, "_AddRow", _AddVariable) laurent@577: laurent@577: def _DeleteVariable(row): laurent@577: if (self.Table.GetValueByName(row, "Edit") and laurent@577: (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])): laurent@577: self.Values.remove(self.Table.GetRow(row)) laurent@577: self.SaveValues() laurent@577: self.RefreshValues() laurent@577: setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable) laurent@577: laurent@577: def _MoveVariable(row, move): laurent@577: if (self.Filter == "All" and laurent@577: (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])): laurent@577: new_row = max(0, min(row + move, len(self.Values) - 1)) laurent@577: if new_row != row: laurent@577: self.Values.insert(new_row, self.Values.pop(row)) laurent@577: self.SaveValues() laurent@577: self.RefreshValues() laurent@577: return new_row laurent@577: return row laurent@577: setattr(self.VariablesGrid, "_MoveRow", _MoveVariable) laurent@577: laurent@577: def _RefreshButtons(): b@418: table_length = len(self.Table.data) b@418: row_class = None b@418: row_edit = True laurent@496: row = 0 b@418: if table_length > 0: b@418: row = self.VariablesGrid.GetGridCursorRow() b@418: row_edit = self.Table.GetValueByName(row, "Edit") b@418: if self.PouIsUsed: b@418: row_class = self.Table.GetValueByName(row, "Class") laurent@600: self.AddButton.Enable(not self.Debug and (not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"])) laurent@600: self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"])) laurent@600: self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])) laurent@600: self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])) laurent@577: setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons) laurent@577: laurent@577: self.VariablesGrid.SetRowLabelSize(0) laurent@577: for col in range(self.Table.GetNumberCols()): laurent@577: attr = wx.grid.GridCellAttr() laurent@577: attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE) laurent@577: self.VariablesGrid.SetColAttr(col, attr) laurent@577: self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col]) laurent@577: self.VariablesGrid.AutoSizeColumn(col, False) laurent@577: laurent@577: def __del__(self): laurent@577: self.RefreshHighlightsTimer.Stop() laurent@577: laurent@577: def SetTagName(self, tagname): laurent@577: self.TagName = tagname laurent@577: laurent@616: def GetTagName(self): laurent@616: return self.TagName laurent@616: laurent@577: def IsFunctionBlockType(self, name): laurent@577: bodytype = self.Controler.GetEditedElementBodyType(self.TagName) laurent@577: pouname, poutype = self.Controler.GetEditedElementType(self.TagName) laurent@577: if poutype != "function" and bodytype in ["ST", "IL"]: laurent@577: return False laurent@577: else: laurent@577: return name in self.Controler.GetFunctionBlockTypes(self.TagName) laurent@577: laurent@577: def RefreshView(self): laurent@600: self.PouNames = self.Controler.GetProjectPouNames(self.Debug) laurent@625: returnType = None laurent@625: description = None laurent@577: laurent@577: words = self.TagName.split("::") laurent@577: if self.ElementType == "config": laurent@577: self.PouIsUsed = False laurent@600: self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug) laurent@577: elif self.ElementType == "resource": laurent@577: self.PouIsUsed = False laurent@600: self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug) laurent@577: else: laurent@577: if self.ElementType == "function": laurent@577: self.ReturnType.Clear() laurent@600: for base_type in self.Controler.GetDataTypes(self.TagName, True, debug=self.Debug): laurent@577: self.ReturnType.Append(base_type) laurent@577: returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName) laurent@625: description = self.Controler.GetPouDescription(words[1]) laurent@577: self.PouIsUsed = self.Controler.PouIsUsed(words[1]) laurent@600: self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug) laurent@577: laurent@577: if returnType is not None: laurent@577: self.ReturnType.SetStringSelection(returnType) laurent@625: self.ReturnType.Enable(not self.Debug) laurent@625: self.ReturnTypeLabel.Show() laurent@577: self.ReturnType.Show() laurent@577: else: laurent@577: self.ReturnType.Enable(False) laurent@625: self.ReturnTypeLabel.Hide() laurent@577: self.ReturnType.Hide() laurent@577: laurent@625: if description is not None: laurent@625: self.Description.SetValue(description) laurent@625: self.Description.Enable(not self.Debug) laurent@625: self.DescriptionLabel.Show() laurent@625: self.Description.Show() laurent@625: else: laurent@625: self.Description.Enable(False) laurent@625: self.DescriptionLabel.Hide() laurent@625: self.Description.Hide() laurent@625: laurent@577: self.RefreshValues() laurent@577: self.VariablesGrid.RefreshButtons() laurent@627: self.ControlPanelSizer.Layout() laurent@627: self.MainSizer.Layout() laurent@577: laurent@577: def OnReturnTypeChanged(self, event): laurent@577: words = self.TagName.split("::") laurent@577: self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection()) laurent@577: self.Controler.BufferProject() laurent@586: self.ParentWindow.RefreshView(variablepanel = False) laurent@577: self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE) b@418: event.Skip() laurent@577: laurent@625: def OnDescriptionChanged(self, event): laurent@625: words = self.TagName.split("::") laurent@625: old_description = self.Controler.GetPouDescription(words[1]) laurent@625: new_description = self.Description.GetValue() laurent@625: if new_description != old_description: laurent@625: self.Controler.SetPouDescription(words[1], new_description) laurent@625: self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE) laurent@625: event.Skip() laurent@625: laurent@577: def OnClassFilter(self, event): laurent@577: self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]] laurent@577: self.RefreshTypeList() laurent@577: self.RefreshValues() laurent@577: self.VariablesGrid.RefreshButtons() b@418: event.Skip() b@418: laurent@577: def RefreshTypeList(self): laurent@577: if self.Filter == "All": laurent@577: self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[choice] not in ["All","Interface","Variables"]] laurent@577: elif self.Filter == "Interface": laurent@577: self.ClassList = ["Input","Output","InOut","External"] laurent@577: elif self.Filter == "Variables": laurent@577: self.ClassList = ["Local","Temp"] laurent@577: else: laurent@577: self.ClassList = [self.Filter] b@418: b@418: def OnVariablesGridCellChange(self, event): b@418: row, col = event.GetRow(), event.GetCol() laurent@431: colname = self.Table.GetColLabelValue(col, False) b@418: value = self.Table.GetValue(row, col) laurent@623: message = None laurent@623: b@418: if colname == "Name" and value != "": b@418: if not TestIdentifier(value): laurent@623: message = _("\"%s\" is not a valid identifier!") % value b@418: elif value.upper() in IEC_KEYWORDS: laurent@623: message = _("\"%s\" is a keyword. It can't be used!") % value b@418: elif value.upper() in self.PouNames: laurent@623: message = _("A POU named \"%s\" already exists!") % value b@418: elif value.upper() in [var["Name"].upper() for var in self.Values if var != self.Table.data[row]]: laurent@623: message = _("A variable with \"%s\" as name already exists in this pou!") % value b@418: else: b@418: self.SaveValues(False) b@418: old_value = self.Table.GetOldValue() b@418: if old_value != "": b@418: self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value) b@418: self.Controler.BufferProject() laurent@586: self.ParentWindow.RefreshView(variablepanel = False) laurent@483: self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE) b@418: event.Skip() b@418: else: b@418: self.SaveValues() b@418: if colname == "Class": laurent@586: self.ParentWindow.RefreshView(variablepanel = False) laurent@625: elif colname == "Location": laurent@625: wx.CallAfter(self.ParentWindow.RefreshView) laurent@623: laurent@623: if message is not None: laurent@623: dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR) laurent@623: dialog.ShowModal() laurent@623: dialog.Destroy() laurent@623: event.Veto() laurent@623: else: b@418: event.Skip() b@418: b@418: def OnVariablesGridEditorShown(self, event): b@418: row, col = event.GetRow(), event.GetCol() b@422: b@422: label_value = self.Table.GetColLabelValue(col) b@422: if label_value == "Type": b@422: type_menu = wx.Menu(title='') # the root menu b@422: b@422: # build a submenu containing standard IEC types b@418: base_menu = wx.Menu(title='') b@418: for base_type in self.Controler.GetBaseTypes(): b@418: new_id = wx.NewId() b@418: AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type) b@418: self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id) b@422: b@418: type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu) b@422: b@422: # build a submenu containing user-defined types b@418: datatype_menu = wx.Menu(title='') laurent@491: datatypes = self.Controler.GetDataTypes(basetypes = False) b@422: for datatype in datatypes: b@418: new_id = wx.NewId() b@418: AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) b@418: self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) b@422: b@418: type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu) laurent@630: laurent@630: for category in self.Controler.GetPluginDataTypes(): laurent@630: laurent@630: if len(category["list"]) > 0: laurent@630: # build a submenu containing plugin types laurent@630: plugin_datatype_menu = wx.Menu(title='') laurent@630: for datatype in category["list"]: laurent@630: new_id = wx.NewId() laurent@630: AppendMenu(plugin_datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) laurent@630: self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id) laurent@630: laurent@630: type_menu.AppendMenu(wx.NewId(), category["name"], plugin_datatype_menu) b@422: b@422: # build a submenu containing function block types laurent@431: bodytype = self.Controler.GetEditedElementBodyType(self.TagName) laurent@431: pouname, poutype = self.Controler.GetEditedElementType(self.TagName) b@422: classtype = self.Table.GetValueByName(row, "Class") laurent@507: laurent@556: new_id = wx.NewId() laurent@556: AppendMenu(type_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Array")) laurent@556: self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id) laurent@507: b@422: if classtype in ["Input", "Output", "InOut", "External", "Global"] or \ b@422: poutype != "function" and bodytype in ["ST", "IL"]: b@422: functionblock_menu = wx.Menu(title='') laurent@431: fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName) b@422: for functionblock_type in fbtypes: b@418: new_id = wx.NewId() b@418: AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type) b@418: self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id) b@422: b@418: type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu) b@422: b@418: rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col)) b@422: corner_x = rect.x + rect.width b@422: corner_y = rect.y + self.VariablesGrid.GetColLabelSize() b@422: b@422: # pop up this new menu b@422: self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y) b@418: event.Veto() b@418: else: b@418: event.Skip() b@418: b@418: def GetVariableTypeFunction(self, base_type): b@418: def VariableTypeFunction(event): b@418: row = self.VariablesGrid.GetGridCursorRow() b@418: self.Table.SetValueByName(row, "Type", base_type) b@418: self.Table.ResetView(self.VariablesGrid) b@418: self.SaveValues(False) laurent@586: self.ParentWindow.RefreshView(variablepanel = False) b@418: self.Controler.BufferProject() laurent@494: self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE) b@418: return VariableTypeFunction b@418: laurent@507: def VariableArrayTypeFunction(self, event): laurent@507: row = self.VariablesGrid.GetGridCursorRow() laurent@507: dialog = ArrayTypeDialog(self, laurent@507: self.Controler.GetDataTypes(self.TagName), laurent@507: self.Table.GetValueByName(row, "Type")) laurent@507: if dialog.ShowModal() == wx.ID_OK: laurent@507: self.Table.SetValueByName(row, "Type", dialog.GetValue()) laurent@507: self.Table.ResetView(self.VariablesGrid) laurent@507: self.SaveValues(False) laurent@586: self.ParentWindow.RefreshView(variablepanel = False) laurent@507: self.Controler.BufferProject() laurent@507: self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE) laurent@507: dialog.Destroy() laurent@507: b@418: def OnVariablesGridCellLeftClick(self, event): b@418: row = event.GetRow() laurent@600: if not self.Debug and (event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit")): b@418: row = event.GetRow() b@418: var_name = self.Table.GetValueByName(row, "Name") b@418: var_class = self.Table.GetValueByName(row, "Class") b@418: var_type = self.Table.GetValueByName(row, "Type") b@418: data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName))) b@418: dragSource = wx.DropSource(self.VariablesGrid) b@418: dragSource.SetData(data) b@418: dragSource.DoDragDrop() b@418: event.Skip() b@418: laurent@577: def RefreshValues(self): b@418: data = [] b@418: for num, variable in enumerate(self.Values): b@418: if variable["Class"] in self.ClassList: b@418: variable["Number"] = num + 1 b@418: data.append(variable) b@418: self.Table.SetData(data) laurent@561: self.Table.ResetView(self.VariablesGrid) laurent@577: b@418: def SaveValues(self, buffer = True): b@418: words = self.TagName.split("::") b@418: if self.ElementType == "config": b@418: self.Controler.SetConfigurationGlobalVars(words[1], self.Values) b@418: elif self.ElementType == "resource": b@418: self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values) b@418: else: b@418: if self.ReturnType.IsEnabled(): b@418: self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection()) b@418: self.Controler.SetPouInterfaceVars(words[1], self.Values) b@418: if buffer: b@418: self.Controler.BufferProject() laurent@494: self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE) b@418: laurent@566: #------------------------------------------------------------------------------- laurent@566: # Highlights showing functions laurent@566: #------------------------------------------------------------------------------- laurent@566: laurent@566: def OnRefreshHighlightsTimer(self, event): laurent@566: self.Table.ResetView(self.VariablesGrid) laurent@566: event.Skip() laurent@566: laurent@566: def AddVariableHighlight(self, infos, highlight_type): b@418: if isinstance(infos[0], TupleType): b@418: for i in xrange(*infos[0]): laurent@566: self.Table.AddHighlight((i,) + infos[1:], highlight_type) laurent@566: else: laurent@566: self.Table.AddHighlight(infos, highlight_type) laurent@566: self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True) laurent@566: laurent@566: def ClearHighlights(self, highlight_type=None): laurent@566: self.Table.ClearHighlights(highlight_type) b@418: self.Table.ResetView(self.VariablesGrid)