Adding support for editing and using struct data types
authorlbessard
Fri, 19 Dec 2008 15:07:54 +0100
changeset 295 c6ef6d92ce16
parent 294 4a36f2ec8967
child 296 919f72861bfb
Adding support for editing and using struct data types
DataTypeEditor.py
PLCControler.py
PLCGenerator.py
TextViewer.py
plcopen/plcopen.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()
--- 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
--- 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():
--- 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)
--- 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):