# HG changeset patch # User lbessard # Date 1229695674 -3600 # Node ID c6ef6d92ce16366e37b8dce22a90839298478e4b # Parent 4a36f2ec8967aa91d0bd392baeb2559bbbe28358 Adding support for editing and using struct data types diff -r 4a36f2ec8967 -r c6ef6d92ce16 DataTypeEditor.py --- a/DataTypeEditor.py Mon Dec 15 09:45:16 2008 +0100 +++ b/DataTypeEditor.py Fri Dec 19 15:07:54 2008 +0100 @@ -25,12 +25,183 @@ import wx import wx.grid import wx.gizmos -from plcopen.structures import IEC_KEYWORDS +from plcopen.structures import IEC_KEYWORDS, TestIdentifier import re DIMENSION_MODEL = re.compile("([0-9]+)\.\.([0-9]+)$") +def AppendMenu(parent, help, id, kind, text): + if wx.VERSION >= (2, 6, 0): + parent.Append(help=help, id=id, kind=kind, text=text) + else: + parent.Append(helpString=help, id=id, kind=kind, item=text) + +#------------------------------------------------------------------------------- +# Structure Elements Table +#------------------------------------------------------------------------------- + +class ElementsTable(wx.grid.PyGridTableBase): + + """ + A custom wx.grid.Grid Table using user supplied data + """ + def __init__(self, parent, data, colnames): + # The base class must be initialized *first* + wx.grid.PyGridTableBase.__init__(self) + self.data = data + self.old_value = None + self.colnames = colnames + self.Errors = {} + self.Parent = parent + # XXX + # we need to store the row length and collength to + # see if the table has changed size + self._rows = self.GetNumberRows() + self._cols = self.GetNumberCols() + + def GetNumberCols(self): + return len(self.colnames) + + def GetNumberRows(self): + return len(self.data) + + def GetColLabelValue(self, col): + if col < len(self.colnames): + return self.colnames[col] + + def GetRowLabelValues(self, row): + return row + + def GetValue(self, row, col): + if row < self.GetNumberRows(): + if col == 0: + return row + 1 + name = str(self.data[row].get(self.GetColLabelValue(col), "")) + return name + + def SetValue(self, row, col, value): + if col < len(self.colnames): + colname = self.GetColLabelValue(col) + if colname == "Name": + self.old_value = self.data[row][colname] + self.data[row][colname] = value + + def GetValueByName(self, row, colname): + if row < self.GetNumberRows(): + return self.data[row].get(colname) + + def SetValueByName(self, row, colname, value): + if row < self.GetNumberRows(): + self.data[row][colname] = value + + def GetOldValue(self): + return self.old_value + + def ResetView(self, grid): + """ + (wx.grid.Grid) -> Reset the grid view. Call this to + update the grid if rows and columns have been added or deleted + """ + grid.BeginBatch() + for current, new, delmsg, addmsg in [ + (self._rows, self.GetNumberRows(), wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED), + (self._cols, self.GetNumberCols(), wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED), + ]: + if new < current: + msg = wx.grid.GridTableMessage(self,delmsg,new,current-new) + grid.ProcessTableMessage(msg) + elif new > current: + msg = wx.grid.GridTableMessage(self,addmsg,new-current) + grid.ProcessTableMessage(msg) + self.UpdateValues(grid) + grid.EndBatch() + + self._rows = self.GetNumberRows() + self._cols = self.GetNumberCols() + # update the column rendering scheme + self._updateColAttrs(grid) + + # update the scrollbars and the displayed part of the grid + grid.AdjustScrollbars() + grid.ForceRefresh() + + def UpdateValues(self, grid): + """Update all displayed values""" + # This sends an event to the grid table to update all of the values + msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES) + grid.ProcessTableMessage(msg) + + def _updateColAttrs(self, grid): + """ + wx.grid.Grid -> update the column attributes to add the + appropriate renderer given the column name. + + Otherwise default to the default renderer. + """ + + typelist = None + accesslist = None + for row in range(self.GetNumberRows()): + for col in range(self.GetNumberCols()): + editor = None + renderer = None + colname = self.GetColLabelValue(col) + if col != 0: + grid.SetReadOnly(row, col, False) + if colname == "Name": + editor = wx.grid.GridCellTextEditor() + renderer = wx.grid.GridCellStringRenderer() + elif colname == "Initial Value": + editor = wx.grid.GridCellTextEditor() + renderer = wx.grid.GridCellStringRenderer() + elif colname == "Type": + editor = wx.grid.GridCellTextEditor() + else: + grid.SetReadOnly(row, col, True) + + grid.SetCellEditor(row, col, editor) + grid.SetCellRenderer(row, col, renderer) + + if row in self.Errors and self.Errors[row][0] == colname.lower(): + grid.SetCellBackgroundColour(row, col, wx.Colour(255, 255, 0)) + grid.SetCellTextColour(row, col, wx.RED) + grid.MakeCellVisible(row, col) + else: + grid.SetCellTextColour(row, col, wx.BLACK) + grid.SetCellBackgroundColour(row, col, wx.WHITE) + + def SetData(self, data): + self.data = data + + def GetData(self): + return self.data + + def AppendRow(self, row_content): + self.data.append(row_content) + + def RemoveRow(self, row_index): + self.data.pop(row_index) + + def MoveRow(self, idx, move): + new_idx = max(0, min(idx + move, len(self.data) - 1)) + if new_idx != idx: + self.data.insert(new_idx, self.data.pop(idx)) + return new_idx + return None + + def GetRow(self, row_index): + return self.data[row_index] + + def Empty(self): + self.data = [] + self.editors = [] + + def AddError(self, infos): + self.Errors[infos[0]] = infos[1:] + + def ClearErrors(self): + self.Errors = {} #------------------------------------------------------------------------------- # Configuration Editor class @@ -45,12 +216,16 @@ ID_DATATYPEEDITORENUMERATEDVALUES, ID_DATATYPEEDITORENUMERATEDINITIALVALUE, ID_DATATYPEEDITORARRAYPANEL, ID_DATATYPEEDITORARRAYBASETYPE, ID_DATATYPEEDITORARRAYDIMENSIONS, ID_DATATYPEEDITORARRAYINITIALVALUE, + ID_DATATYPEEDITORSTRUCTUREPANEL, ID_DATATYPEEDITORSTRUCTUREELEMENTSGRID, + ID_DATATYPEEDITORSTRUCTUREADDBUTTON, ID_DATATYPEEDITORSTRUCTUREDELETEBUTTON, + ID_DATATYPEEDITORSTRUCTUREUPBUTTON, ID_DATATYPEEDITORSTRUCTUREDOWNBUTTON, ID_DATATYPEEDITORSTATICTEXT1, ID_DATATYPEEDITORSTATICTEXT2, ID_DATATYPEEDITORSTATICTEXT3, ID_DATATYPEEDITORSTATICTEXT4, ID_DATATYPEEDITORSTATICTEXT5, ID_DATATYPEEDITORSTATICTEXT6, ID_DATATYPEEDITORSTATICTEXT7, ID_DATATYPEEDITORSTATICTEXT8, ID_DATATYPEEDITORSTATICTEXT9, ID_DATATYPEEDITORSTATICTEXT10, -] = [wx.NewId() for _init_ctrls in range(28)] + ID_DATATYPEEDITORSTATICTEXT11, +] = [wx.NewId() for _init_ctrls in range(35)] class DataTypeEditor(wx.Panel): @@ -71,6 +246,7 @@ parent.AddWindow(self.SubrangePanel, 1, border=0, flag=wx.ALL) parent.AddWindow(self.EnumeratedPanel, 1, border=0, flag=wx.GROW|wx.ALL) parent.AddWindow(self.ArrayPanel, 1, border=0, flag=wx.ALL) + parent.AddWindow(self.StructurePanel, 1, border=0, flag=wx.GROW|wx.ALL) def _init_coll_DirectlyPanelSizer_Items(self, parent): parent.AddWindow(self.staticText2, 1, border=5, flag=wx.GROW|wx.ALL) @@ -113,6 +289,21 @@ parent.AddWindow(self.staticText10, 1, border=5, flag=wx.GROW|wx.ALL) parent.AddWindow(self.ArrayInitialValue, 1, border=5, flag=wx.GROW|wx.ALL) + def _init_coll_StructurePanelSizer_Items(self, parent): + parent.AddWindow(self.staticText11, 0, border=5, flag=wx.GROW|wx.ALL) + parent.AddWindow(self.StructureElementsGrid, 0, border=0, flag=wx.GROW) + parent.AddSizer(self.StructurePanelButtonSizer, 0, border=0, flag=wx.ALIGN_RIGHT) + + def _init_coll_StructurePanelSizer_Growables(self, parent): + parent.AddGrowableCol(0) + parent.AddGrowableRow(1) + + def _init_coll_StructurePanelButtonSizer_Items(self, parent): + parent.AddWindow(self.StructureAddButton, 1, border=5, flag=wx.GROW|wx.ALL) + parent.AddWindow(self.StructureDeleteButton, 1, border=5, flag=wx.GROW|wx.ALL) + parent.AddWindow(self.StructureUpButton, 1, border=5, flag=wx.GROW|wx.ALL) + parent.AddWindow(self.StructureDownButton, 1, border=5, flag=wx.GROW|wx.ALL) + def _init_sizers(self): self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10) self.TopSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -123,7 +314,8 @@ self.ArrayPanelSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=2, vgap=0) self.ArrayPanelLeftSizer = wx.BoxSizer(wx.HORIZONTAL) self.ArrayPanelRightSizer = wx.BoxSizer(wx.HORIZONTAL) - + self.StructurePanelSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0) + self.StructurePanelButtonSizer = wx.BoxSizer(wx.HORIZONTAL) self._init_coll_MainSizer_Items(self.MainSizer) self._init_coll_MainSizer_Growables(self.MainSizer) self._init_coll_TopSizer_Items(self.TopSizer) @@ -135,12 +327,16 @@ self._init_coll_ArrayPanelSizer_Growables(self.ArrayPanelSizer) self._init_coll_ArrayPanelLeftSizer_Items(self.ArrayPanelLeftSizer) self._init_coll_ArrayPanelRightSizer_Items(self.ArrayPanelRightSizer) + self._init_coll_StructurePanelSizer_Items(self.StructurePanelSizer) + self._init_coll_StructurePanelSizer_Growables(self.StructurePanelSizer) + self._init_coll_StructurePanelButtonSizer_Items(self.StructurePanelButtonSizer) self.SetSizer(self.MainSizer) self.DirectlyPanel.SetSizer(self.DirectlyPanelSizer) self.SubrangePanel.SetSizer(self.SubrangePanelSizer) self.EnumeratedPanel.SetSizer(self.EnumeratedPanelSizer) self.ArrayPanel.SetSizer(self.ArrayPanelSizer) + self.StructurePanel.SetSizer(self.StructurePanelSizer) def _init_ctrls(self, prnt): wx.Panel.__init__(self, id=ID_DATATYPEEDITOR, name='', parent=prnt, @@ -282,18 +478,79 @@ size=wx.Size(0, 24), style=wx.TAB_TRAVERSAL|wx.TE_PROCESS_ENTER|wx.TE_MULTILINE|wx.TE_RICH) self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, id=ID_DATATYPEEDITORARRAYINITIALVALUE) + # Panel for Structure data types + + self.StructurePanel = wx.Panel(id=ID_DATATYPEEDITORSTRUCTUREPANEL, + name='StructurePanel', parent=self, pos=wx.Point(0, 0), + size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + + self.staticText11 = wx.StaticText(id=ID_DATATYPEEDITORSTATICTEXT11, + label='Elements :', name='staticText11', parent=self.StructurePanel, + pos=wx.Point(0, 0), size=wx.Size(150, 17), style=0) + + self.StructureElementsGrid = wx.grid.Grid(id=ID_DATATYPEEDITORSTRUCTUREELEMENTSGRID, + name='StructureElementsGrid', parent=self.StructurePanel, pos=wx.Point(0, 0), + size=wx.Size(0, 150), style=wx.VSCROLL) + self.StructureElementsGrid.SetFont(wx.Font(12, 77, wx.NORMAL, wx.NORMAL, False, + 'Sans')) + self.StructureElementsGrid.SetLabelFont(wx.Font(10, 77, wx.NORMAL, wx.NORMAL, + False, 'Sans')) + self.StructureElementsGrid.SetSelectionBackground(wx.WHITE) + self.StructureElementsGrid.SetSelectionForeground(wx.BLACK) + if wx.VERSION >= (2, 6, 0): + self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnStructureElementsGridCellChange) + self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnStructureElementsGridEditorShown) + else: + wx.grid.EVT_GRID_CELL_CHANGE(self.StructureElementsGrid, self.OnStructureElementsGridCellChange) + wx.grid.EVT_GRID_EDITOR_SHOWN(self.StructureElementsGrid, self.OnStructureElementsGridEditorShown) + + self.StructureAddButton = wx.Button(id=ID_DATATYPEEDITORSTRUCTUREADDBUTTON, label='Add', + name='StructureAddButton', parent=self.StructurePanel, pos=wx.Point(0, 0), + size=wx.Size(72, 32), style=0) + self.Bind(wx.EVT_BUTTON, self.OnStructureAddButton, id=ID_DATATYPEEDITORSTRUCTUREADDBUTTON) + + self.StructureDeleteButton = wx.Button(id=ID_DATATYPEEDITORSTRUCTUREDELETEBUTTON, label='Delete', + name='StructureDeleteButton', parent=self.StructurePanel, pos=wx.Point(0, 0), + size=wx.Size(72, 32), style=0) + self.Bind(wx.EVT_BUTTON, self.OnStructureDeleteButton, id=ID_DATATYPEEDITORSTRUCTUREDELETEBUTTON) + + self.StructureUpButton = wx.Button(id=ID_DATATYPEEDITORSTRUCTUREUPBUTTON, label='^', + name='StructureUpButton', parent=self.StructurePanel, pos=wx.Point(0, 0), + size=wx.Size(32, 32), style=0) + self.Bind(wx.EVT_BUTTON, self.OnStructureUpButton, id=ID_DATATYPEEDITORSTRUCTUREUPBUTTON) + + self.StructureDownButton = wx.Button(id=ID_DATATYPEEDITORSTRUCTUREDOWNBUTTON, label='v', + name='StructureDownButton', parent=self.StructurePanel, pos=wx.Point(0, 0), + size=wx.Size(32, 32), style=0) + self.Bind(wx.EVT_BUTTON, self.OnStructureDownButton, id=ID_DATATYPEEDITORSTRUCTUREDOWNBUTTON) + self._init_sizers() def __init__(self, parent, tagname, window, controler): self._init_ctrls(parent) + self.StructureElementDefaultValue = {"Name" : "", "Type" : "INT", "Initial Value" : ""} + self.StructureElementsTable = ElementsTable(self, [], ["#", "Name", "Type", "Initial Value"]) + self.StructureColSizes = [40, 150, 100, 250] + self.StructureColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT] + + self.StructureElementsGrid.SetTable(self.StructureElementsTable) + self.StructureElementsGrid.SetRowLabelSize(0) + for col in range(self.StructureElementsTable.GetNumberCols()): + attr = wx.grid.GridCellAttr() + attr.SetAlignment(self.StructureColAlignements[col], wx.ALIGN_CENTRE) + self.StructureElementsGrid.SetColAttr(col, attr) + self.StructureElementsGrid.SetColSize(col, self.StructureColSizes[col]) + self.DerivationType.Append("Directly") self.DerivationType.Append("Subrange") self.DerivationType.Append("Enumerated") self.DerivationType.Append("Array") + self.DerivationType.Append("Structure") self.SubrangePanel.Hide() self.EnumeratedPanel.Hide() self.ArrayPanel.Hide() + self.StructurePanel.Hide() self.CurrentPanel = "Directly" self.Errors = [] self.Initializing = False @@ -302,34 +559,6 @@ self.Controler = controler self.TagName = tagname - def OnEnumeratedValueEndEdit(self, event): - text = event.GetText() - values = self.EnumeratedValues.GetStrings() - index = event.GetIndex() - if index >= len(values) or values[index].upper() != text.upper(): - if text.upper() in [value.upper() for value in values]: - message = wx.MessageDialog(self, "\"%s\" value already defined!"%text, "Error", wx.OK|wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - event.Veto() - elif text.upper() in IEC_KEYWORDS: - message = wx.MessageDialog(self, "\"%s\" is a keyword. It can't be used!"%text, "Error", wx.OK|wx.ICON_ERROR) - message.ShowModal() - message.Destroy() - else: - wx.CallAfter(self.RefreshEnumeratedValues) - wx.CallAfter(self.RefreshTypeInfos) - event.Skip() - else: - wx.CallAfter(self.RefreshEnumeratedValues) - wx.CallAfter(self.RefreshTypeInfos) - event.Skip() - - def OnEnumeratedValuesChanged(self, event): - wx.CallAfter(self.RefreshEnumeratedValues) - wx.CallAfter(self.RefreshTypeInfos) - event.Skip() - def SetTagName(self, tagname): self.TagName = tagname @@ -383,6 +612,9 @@ self.ArrayBaseType.SetStringSelection(type_infos["base_type"]) self.ArrayDimensions.SetStrings(map(lambda x : "..".join(map(str, x)), type_infos["dimensions"])) self.ArrayInitialValue.SetValue(type_infos["initial"]) + elif type_infos["type"] == "Structure": + self.StructureElementsTable.SetData(type_infos["elements"]) + self.StructureElementsTable.ResetView(self.StructureElementsGrid) self.RefreshDisplayedInfos() self.ShowErrors() self.Initializing = False @@ -425,6 +657,144 @@ wx.CallAfter(self.RefreshTypeInfos) event.Skip() + def OnEnumeratedValueEndEdit(self, event): + text = event.GetText() + values = self.EnumeratedValues.GetStrings() + index = event.GetIndex() + if index >= len(values) or values[index].upper() != text.upper(): + if text.upper() in [value.upper() for value in values]: + message = wx.MessageDialog(self, "\"%s\" value already defined!"%text, "Error", wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + event.Veto() + elif text.upper() in IEC_KEYWORDS: + message = wx.MessageDialog(self, "\"%s\" is a keyword. It can't be used!"%text, "Error", wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + else: + wx.CallAfter(self.RefreshEnumeratedValues) + wx.CallAfter(self.RefreshTypeInfos) + event.Skip() + else: + wx.CallAfter(self.RefreshEnumeratedValues) + wx.CallAfter(self.RefreshTypeInfos) + event.Skip() + + def OnEnumeratedValuesChanged(self, event): + wx.CallAfter(self.RefreshEnumeratedValues) + wx.CallAfter(self.RefreshTypeInfos) + event.Skip() + + def OnStructureAddButton(self, event): + new_row = self.StructureElementDefaultValue.copy() + self.StructureElementsTable.AppendRow(new_row) + self.RefreshTypeInfos() + self.StructureElementsTable.ResetView(self.StructureElementsGrid) + event.Skip() + + def OnStructureDeleteButton(self, event): + row = self.StructureElementsGrid.GetGridCursorRow() + self.StructureElementsTable.RemoveRow(row) + self.RefreshTypeInfos() + self.StructureElementsTable.ResetView(self.StructureElementsGrid) + event.Skip() + + def OnStructureUpButton(self, event): + row = self.StructureElementsGrid.GetGridCursorRow() + new_index = self.StructureElementsTable.MoveRow(row, -1) + if new_index is not None: + self.RefreshTypeInfos() + self.StructureElementsTable.ResetView(self.StructureElementsGrid) + self.StructureElementsGrid.SetGridCursor(new_index, self.StructureElementsGrid.GetGridCursorCol()) + event.Skip() + + def OnStructureDownButton(self, event): + row = self.StructureElementsGrid.GetGridCursorRow() + new_index = self.StructureElementsTable.MoveRow(row, 1) + if new_index is not None: + self.RefreshTypeInfos() + self.StructureElementsTable.ResetView(self.StructureElementsGrid) + self.StructureElementsGrid.SetGridCursor(new_index, self.StructureElementsGrid.GetGridCursorCol()) + event.Skip() + + def OnStructureElementsGridCellChange(self, event): + row, col = event.GetRow(), event.GetCol() + colname = self.StructureElementsTable.GetColLabelValue(col) + value = self.StructureElementsTable.GetValue(row, col) + if colname == "Name": + if not TestIdentifier(value): + message = wx.MessageDialog(self, "\"%s\" is not a valid identifier!"%value, "Error", wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + event.Veto() + elif value.upper() in IEC_KEYWORDS: + message = wx.MessageDialog(self, "\"%s\" is a keyword. It can't be used!"%value, "Error", wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + event.Veto() +## elif value.upper() in self.PouNames: +## message = wx.MessageDialog(self, "A pou with \"%s\" as name exists!"%value, "Error", wx.OK|wx.ICON_ERROR) +## message.ShowModal() +## message.Destroy() +## event.Veto() + elif value.upper() in [var["Name"].upper() for idx, var in enumerate(self.StructureElementsTable.GetData()) if idx != row]: + message = wx.MessageDialog(self, "A element with \"%s\" as name exists in this structure!"%value, "Error", wx.OK|wx.ICON_ERROR) + message.ShowModal() + message.Destroy() + event.Veto() + else: + self.RefreshTypeInfos() + self.StructureElementsTable.ResetView(self.StructureElementsGrid) +## old_value = self.Table.GetOldValue() +## if old_value != "": +## self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value) +## self.Controler.BufferProject() + event.Skip() + else: + self.RefreshTypeInfos() + self.StructureElementsTable.ResetView(self.StructureElementsGrid) + event.Skip() + + def OnStructureElementsGridEditorShown(self, event): + row, col = event.GetRow(), event.GetCol() + if self.StructureElementsTable.GetColLabelValue(col) == "Type": + type_menu = wx.Menu(title='') + base_menu = wx.Menu(title='') + for base_type in self.Controler.GetBaseTypes(): + new_id = wx.NewId() + AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type) + self.Bind(wx.EVT_MENU, self.GetElementTypeFunction(base_type), id=new_id) + type_menu.AppendMenu(wx.NewId(), "Base Types", base_menu) + datatype_menu = wx.Menu(title='') + for datatype in self.Controler.GetDataTypes(self.TagName, False, self.ParentWindow.Debug): + new_id = wx.NewId() + AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype) + self.Bind(wx.EVT_MENU, self.GetElementTypeFunction(datatype), id=new_id) + type_menu.AppendMenu(wx.NewId(), "User Data Types", datatype_menu) +## functionblock_menu = wx.Menu(title='') +## bodytype = self.Controler.GetEditedElementBodyType(self.TagName, self.ParentWindow.Debug) +## pouname, poutype = self.Controler.GetEditedElementType(self.TagName, self.ParentWindow.Debug) +## if classtype in ["Input","Output","InOut","External","Global"] or poutype != "function" and bodytype in ["ST", "IL"]: +## for functionblock_type in self.Controler.GetFunctionBlockTypes(self.TagName, self.ParentWindow.Debug): +## new_id = wx.NewId() +## AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type) +## self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id) +## type_menu.AppendMenu(wx.NewId(), "Function Block Types", functionblock_menu) + rect = self.StructureElementsGrid.BlockToDeviceRect((row, col), (row, col)) + self.StructureElementsGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.StructureElementsGrid.GetColLabelSize()) + event.Veto() + else: + event.Skip() + + def GetElementTypeFunction(self, base_type): + def ElementTypeFunction(event): + row = self.StructureElementsGrid.GetGridCursorRow() + self.StructureElementsTable.SetValueByName(row, "Type", base_type) + self.RefreshTypeInfos() + self.StructureElementsTable.ResetView(self.StructureElementsGrid) + event.Skip() + return ElementTypeFunction + def RefreshDisplayedInfos(self): selected = self.DerivationType.GetStringSelection() if selected != self.CurrentPanel: @@ -436,6 +806,8 @@ self.EnumeratedPanel.Hide() elif self.CurrentPanel == "Array": self.ArrayPanel.Hide() + elif self.CurrentPanel == "Structure": + self.StructurePanel.Hide() self.CurrentPanel = selected if selected == "Directly": self.DirectlyPanel.Show() @@ -445,6 +817,8 @@ self.EnumeratedPanel.Show() elif selected == "Array": self.ArrayPanel.Show() + elif selected == "Structure": + self.StructurePanel.Show() self.MainSizer.Layout() def RefreshEnumeratedValues(self): @@ -505,6 +879,9 @@ return infos["dimensions"].append(map(int, bounds)) infos["initial"] = self.ArrayInitialValue.GetValue() + elif selected == "Structure": + infos["elements"] = self.StructureElementsTable.GetData() + infos["initial"] = "" self.Controler.SetDataTypeInfos(self.TagName, infos) self.ParentWindow.RefreshTitle() self.ParentWindow.RefreshEditMenu() diff -r 4a36f2ec8967 -r c6ef6d92ce16 PLCControler.py --- a/PLCControler.py Mon Dec 15 09:45:16 2008 +0100 +++ b/PLCControler.py Fri Dec 19 15:07:54 2008 +0100 @@ -247,7 +247,7 @@ if project is not None: for pou in project.getpous(): if pou_name is None or pou_name == pou.getname(): - variables.extend([var["Name"] for var in self.GetPouInterfaceVars(pou)]) + variables.extend([var["Name"] for var in self.GetPouInterfaceVars(pou, debug)]) for transition in pou.gettransitionList(): variables.append(transition.getname()) for action in pou.getactionList(): @@ -964,22 +964,53 @@ # Found the pou correponding to name and return the interface vars pou = project.getpou(name) if pou is not None: - return self.GetPouInterfaceVars(pou) + return self.GetPouInterfaceVars(pou, debug) return None + # Recursively generate element name tree for a structured variable + def GenerateVarTree(self, typename, debug = False): + project = self.GetProject(debug) + if project is not None: + blocktype = self.GetBlockType(typename, debug = debug) + if blocktype is not None: + tree = {} + for var_name, var_type, var_modifier in blocktype["inputs"] + blocktype["outputs"]: + tree[var_name] = self.GenerateVarTree(var_type, debug) + return tree + datatype = project.getdataType(typename) + if datatype is not None: + tree = {} + basetype_content = datatype.baseType.getcontent() + if basetype_content["name"] == "derived": + tree = self.GenerateVarTree(basetype_content["value"].getname()) + elif basetype_content["name"] == "array": + base_type = basetype_content["value"].baseType.getcontent() + if base_type["name"] == "derived": + tree = self.GenerateVarTree(base_type["value"].getname()) + elif basetype_content["name"] == "struct": + for element in basetype_content["value"].getvariable(): + element_type = element.type.getcontent() + if element_type["name"] == "derived": + tree[element.getname()] = self.GenerateVarTree(element_type["value"].getname()) + else: + tree[element.getname()] = {} + return tree + return {} + # Return the interface for the given pou - def GetPouInterfaceVars(self, pou): + def GetPouInterfaceVars(self, pou, debug = False): vars = [] # Verify that the pou has an interface if pou.interface is not None: # Extract variables from every varLists for type, varlist in pou.getvars(): for var in varlist.getvariable(): - tempvar = {"Name" : var.getname(), "Class" : type} + tempvar = {"Name" : var.getname(), "Class" : type, "Tree" : {}} vartype_content = var.gettype().getcontent() if vartype_content["name"] == "derived": tempvar["Type"] = vartype_content["value"].getname() tempvar["Edit"] = not pou.hasblock(tempvar["Name"]) + tempvar["Tree"] = self.GenerateVarTree(tempvar["Type"], debug) else: if vartype_content["name"] in ["string", "wstring"]: tempvar["Type"] = vartype_content["name"].upper() @@ -1303,6 +1334,22 @@ infos["base_type"] = base_type["name"] else: infos["base_type"] = base_type["value"].getname() + elif basetype_content["name"] == "struct": + infos["type"] = "Structure" + infos["elements"] = [] + for element in basetype_content["value"].getvariable(): + element_infos = {} + element_infos["Name"] = element.getname() + element_type = element.type.getcontent() + if element_type["value"] is None: + element_infos["Type"] = element_type["name"] + else: + element_infos["Type"] = element_type["value"].getname() + if element.initialValue is not None: + element_infos["Initial Value"] = str(element.initialValue.getvalue()) + else: + element_infos["Initial Value"] = "" + infos["elements"].append(element_infos) if datatype.initialValue is not None: infos["initial"] = str(datatype.initialValue.getvalue()) else: @@ -1374,6 +1421,31 @@ derived_datatype.setname(infos["base_type"]) array.baseType.setcontent({"name" : "derived", "value" : derived_datatype}) datatype.baseType.setcontent({"name" : "array", "value" : array}) + elif infos["type"] == "Structure": + struct = plcopen.varListPlain() + for i, element_infos in enumerate(infos["elements"]): + element = plcopen.varListPlain_variable() + element.setname(element_infos["Name"]) + if element_infos["Type"] in self.GetBaseTypes(): + if element_infos["Type"] == "STRING": + element.type.setcontent({"name" : "string", "value" : plcopen.elementaryTypes_string()}) + elif element_infos["Type"] == "WSTRING": + element.type.setcontent({"name" : "wstring", "value" : plcopen.wstring()}) + else: + element.type.setcontent({"name" : element_infos["Type"], "value" : None}) + else: + derived_datatype = plcopen.derivedTypes_derived() + derived_datatype.setname(element_infos["Type"]) + element.type.setcontent({"name" : "derived", "value" : derived_datatype}) + if element_infos["Initial Value"] != "": + value = plcopen.value() + value.setvalue(element_infos["Initial Value"]) + element.setinitialValue(value) + if i == 0: + struct.setvariable([element]) + else: + struct.appendvariable(element) + datatype.baseType.setcontent({"name" : "struct", "value" : struct}) if infos["initial"] != "": if datatype.initialValue is None: datatype.initialValue = plcopen.value() @@ -1381,6 +1453,7 @@ else: datatype.initialValue = None self.Project.RefreshDataTypeHierarchy() + self.Project.RefreshElementUsingTree() self.BufferProject() #------------------------------------------------------------------------------- @@ -1444,7 +1517,7 @@ if project is not None: pou = project.getpou(words[1]) if pou is not None: - return self.GetPouInterfaceVars(pou) + return self.GetPouInterfaceVars(pou, debug) return [] # Return the edited element return type diff -r 4a36f2ec8967 -r c6ef6d92ce16 PLCGenerator.py --- a/PLCGenerator.py Mon Dec 15 09:45:16 2008 +0100 +++ b/PLCGenerator.py Fri Dec 19 15:07:54 2008 +0100 @@ -172,6 +172,33 @@ datatype_def += JoinList([(",", ())], dimensions) datatype_def += [("] OF " , ()), (basetype_name, (tagname, "base"))] + # Data type is a structure + elif basetype_content["name"] == "struct": + elements = [] + for i, element in enumerate(basetype_content["value"].getvariable()): + element_type = element.type.getcontent() + # Structure element derived directly from a user defined type + if element_type["name"] == "derived": + elementtype_name = element_type["value"].getname() + self.GenerateDataType(elementtype_name) + # Structure element derived directly from a string type + elif element_type["name"] in ["string", "wstring"]: + elementtype_name = element_type["name"].upper() + # Structure element derived directly from an elementary type + else: + elementtype_name = element_type["name"] + element_text = [("\n ", ()), + (element.getname(), (tagname, "struct", i, "name")), + (" : ", ()), + (elementtype_name, (tagname, "struct", i, "type"))] + if element.initialValue is not None: + element_text.extend([(" := ", ()), + (self.ComputeValue(element.initialValue.getvalue(), elementtype_name), (tagname, "struct", i, "initial"))]) + element_text.append((";", ())) + elements.append(element_text) + datatype_def += [("STRUCT", ())] + datatype_def += JoinList([("", ())], elements) + datatype_def += [("\n END_STRUCT", ())] # Data type derived directly from a elementary type else: datatype_def += [(basetype_content["name"], (tagname, "base"))] @@ -180,7 +207,7 @@ datatype_def += [(" := ", ()), (self.ComputeValue(datatype.initialValue.getvalue(), datatype_name), (tagname, "initial"))] datatype_def += [(";\n", ())] - return datatype_def + self.Program += datatype_def # Generate a POU from its name def GeneratePouProgram(self, pou_name): @@ -371,7 +398,7 @@ self.Program += [("TYPE\n", ())] # Generate every data types defined for datatype_name in self.DatatypeComputed.keys(): - self.Program += self.GenerateDataType(datatype_name) + self.GenerateDataType(datatype_name) self.Program += [("END_TYPE\n\n", ())] # Generate every POUs defined for pou_name in self.PouComputed.keys(): diff -r 4a36f2ec8967 -r c6ef6d92ce16 TextViewer.py --- a/TextViewer.py Mon Dec 15 09:45:16 2008 +0100 +++ b/TextViewer.py Fri Dec 19 15:07:54 2008 +0100 @@ -141,7 +141,7 @@ self.SetUseTabs(0) self.Keywords = [] - self.Variables = [] + self.Variables = {} self.Functions = [] self.Jumps = [] self.EnumeratedValues = [] @@ -152,6 +152,7 @@ self.Errors = [] self.Debug = debug self.InstancePath = instancepath + self.StructElementsStack = [] self.ParentWindow = window self.Controler = controler @@ -272,14 +273,15 @@ self.DisableEvents = False words = self.TagName.split("::") - self.Variables = [variable["Name"].upper() for variable in self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)] + + self.Variables = dict([(variable["Name"], variable["Tree"]) for variable in self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)]) if self.Controler.GetEditedElementType(self.TagName, self.Debug)[1] == "function" or words[0] == "T" and self.TextSyntax == "IL": - self.Variables.append(words[-1].upper()) + self.Variables[words[-1]] = {} self.Functions = [] for category in self.Controler.GetBlockTypes(self.TagName, self.Debug): for blocktype in category["list"]: - if blocktype["type"] == "function" and blocktype["name"] not in self.Keywords and blocktype["name"] not in self.Variables: + if blocktype["type"] == "function" and blocktype["name"] not in self.Keywords and blocktype["name"] not in self.Variables.keys(): self.Functions.append(blocktype["name"].upper()) self.EnumeratedValues = [] @@ -291,6 +293,15 @@ def RefreshScaling(self, refresh=True): pass + def IsValidVariable(self, name_list, var_tree): + if len(name_list) == 0: + return True + else: + sub_tree = var_tree.get(name_list[0].upper(), None) + if sub_tree is not None: + return self.IsValidVariable(name_list[1:], sub_tree) + return False + def OnStyleNeeded(self, event): self.TextChanged = True line = self.LineFromPosition(self.GetEndStyled()) @@ -301,6 +312,8 @@ end_pos = event.GetPosition() self.StartStyling(start_pos, 0xff) + struct_elements = [] + current_pos = last_styled_pos state = SPACE line = "" @@ -316,7 +329,7 @@ elif state == WORD: if word in self.Keywords: self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD) - elif word in self.Variables: + elif self.IsValidVariable(struct_elements + [word], self.Variables): self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE) elif word in self.Functions: self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION) @@ -326,10 +339,11 @@ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) else: self.SetStyling(current_pos - last_styled_pos, 31) - if self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos: + if word != "]" and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos): self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK) self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK) self.StartStyling(current_pos, 0xff) + struct_elements = [] else: self.SetStyling(current_pos - last_styled_pos, 31) last_styled_pos = current_pos @@ -338,6 +352,8 @@ elif line.endswith("(*") and state != COMMENT: self.SetStyling(current_pos - last_styled_pos - 1, 31) last_styled_pos = current_pos + if state == WORD: + struct_elements = [] state = COMMENT elif state == COMMENT: if line.endswith("*)"): @@ -366,7 +382,7 @@ if state == WORD: if word in self.Keywords: self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD) - elif word in self.Variables: + elif self.IsValidVariable(struct_elements + [word], self.Variables): self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE) elif word in self.Functions: self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION) @@ -376,10 +392,17 @@ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) else: self.SetStyling(current_pos - last_styled_pos, 31) - if self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos: + if word != "]" and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos): self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK) self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK) self.StartStyling(current_pos, 0xff) + if char == '.': + if word != "]": + struct_elements.append(word) + else: + if char == '[': + self.StructElementsStack.append(struct_elements + [word]) + struct_elements = [] word = "" last_styled_pos = current_pos state = SPACE @@ -387,6 +410,10 @@ self.SetStyling(current_pos - last_styled_pos, STC_PLC_NUMBER) last_styled_pos = current_pos state = SPACE + if char == ']': + struct_elements = self.StructElementsStack.pop(-1) + word = char + state = WORD current_pos += 1 if state == COMMENT: self.SetStyling(current_pos - last_styled_pos + 2, STC_PLC_COMMENT) @@ -395,7 +422,7 @@ elif state == WORD: if word in self.Keywords: self.SetStyling(current_pos - last_styled_pos, STC_PLC_WORD) - elif word in self.Variables: + elif self.IsValidVariable(struct_elements + [word], self.Variables): self.SetStyling(current_pos - last_styled_pos, STC_PLC_VARIABLE) elif word in self.Functions: self.SetStyling(current_pos - last_styled_pos, STC_PLC_FUNCTION) @@ -459,9 +486,9 @@ elif words[0].upper() in ["JMP", "JMPC", "JMPNC"]: kw = self.Jumps else: - kw = self.Variables - else: - kw = self.Keywords + self.Variables + self.Functions + kw = self.Variables.keys() + else: + kw = self.Keywords + self.Variables.keys() + self.Functions if len(kw) > 0: kw.sort() self.AutoCompSetIgnoreCase(True) diff -r 4a36f2ec8967 -r c6ef6d92ce16 plcopen/plcopen.py --- a/plcopen/plcopen.py Mon Dec 15 09:45:16 2008 +0100 +++ b/plcopen/plcopen.py Fri Dec 19 15:07:54 2008 +0100 @@ -365,11 +365,34 @@ # Reset the tree of user-defined element cross-use self.ElementUsingTree = {} pous = self.getpous() + datatypes = self.getdataTypes() # Reference all the user-defined elementu names and initialize the tree of # user-defined elemnt cross-use - pounames = [pou.getname() for pou in pous] - for name in pounames: + elementnames = [datatype.getname() for datatype in datatypes] + \ + [pou.getname() for pou in pous] + for name in elementnames: self.ElementUsingTree[name] = [] + # Analyze each datatype + for datatype in datatypes: + name = datatype.getname() + basetype_content = datatype.baseType.getcontent() + if basetype_content["name"] == "derived": + typename = basetype_content["value"].getname() + if typename in elementnames and name not in self.ElementUsingTree[typename]: + self.ElementUsingTree[typename].append(name) + elif basetype_content["name"] in ["subrangeSigned", "subrangeUnsigned", "array"]: + base_type = basetype_content["value"].baseType.getcontent() + if base_type["name"] == "derived": + typename = base_type["value"].getname() + if typename in elementnames and name not in self.ElementUsingTree[typename]: + self.ElementUsingTree[typename].append(name) + elif basetype_content["name"] == "struct": + for element in basetype_content["value"].getvariable(): + type_content = element.type.getcontent() + if type_content["name"] == "derived": + typename = type_content["value"].getname() + if typename in elementnames and name not in self.ElementUsingTree[typename]: + self.ElementUsingTree[typename].append(name) # Analyze each pou for pou in pous: name = pou.getname() @@ -380,7 +403,7 @@ vartype_content = var.gettype().getcontent() if vartype_content["name"] == "derived": typename = vartype_content["value"].getname() - if typename in pounames and name not in self.ElementUsingTree[typename]: + if typename in elementnames and name not in self.ElementUsingTree[typename]: self.ElementUsingTree[typename].append(name) setattr(cls, "RefreshElementUsingTree", RefreshElementUsingTree) @@ -1760,13 +1783,13 @@ def setvalue(self, value): self.value = [] for item in extractValues(value[1:-1]): - result = arrayValue_model.match(item) + result = structValue_model.match(item) if result is not None: groups = result.groups() element = PLCOpenClasses["structValue_value"]() element.setmember(groups[0].strip()) element.setvalue(groups[1].strip()) - self.value.append(element) + self.value.append(element) setattr(cls, "setvalue", setvalue) def getvalue(self):