VariablePanel.py
changeset 431 c1c92d068ac5
parent 427 22d16c457d87
child 436 f3bb091f803f
equal deleted inserted replaced
411:9ab97d517ae8 431:c1c92d068ac5
       
     1 # -*- coding: utf-8 -*-
       
     2 
       
     3 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     4 #based on the plcopen standard. 
       
     5 #
       
     6 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     7 #
       
     8 #See COPYING file for copyrights details.
       
     9 #
       
    10 #This library is free software; you can redistribute it and/or
       
    11 #modify it under the terms of the GNU General Public
       
    12 #License as published by the Free Software Foundation; either
       
    13 #version 2.1 of the License, or (at your option) any later version.
       
    14 #
       
    15 #This library is distributed in the hope that it will be useful,
       
    16 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    17 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    18 #General Public License for more details.
       
    19 #
       
    20 #You should have received a copy of the GNU General Public
       
    21 #License along with this library; if not, write to the Free Software
       
    22 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    23 
       
    24 import wx, wx.grid
       
    25 
       
    26 from types import TupleType
       
    27 
       
    28 from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS
       
    29 
       
    30 # Compatibility function for wx versions < 2.6
       
    31 def AppendMenu(parent, help, id, kind, text):
       
    32     if wx.VERSION >= (2, 6, 0):
       
    33         parent.Append(help=help, id=id, kind=kind, text=text)
       
    34     else:
       
    35         parent.Append(helpString=help, id=id, kind=kind, item=text)
       
    36 
       
    37 [TITLE, TOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, TYPESTREE, 
       
    38  INSTANCESTREE, LIBRARYTREE, SCALING
       
    39 ] = range(9)
       
    40 
       
    41 #-------------------------------------------------------------------------------
       
    42 #                            Variables Editor Panel
       
    43 #-------------------------------------------------------------------------------
       
    44 
       
    45 def GetVariableTableColnames(location):
       
    46     _ = lambda x : x
       
    47     if location:
       
    48     	return ["#", _("Name"), _("Class"), _("Type"), _("Location"), _("Initial Value"), _("Retain"), _("Constant"), _("Documentation")]
       
    49     return ["#", _("Name"), _("Class"), _("Type"), _("Initial Value"), _("Retain"), _("Constant"), _("Documentation")]
       
    50 
       
    51 def GetAlternativeOptions():
       
    52     _ = lambda x : x
       
    53     return [_("Yes"), _("No")]
       
    54 ALTERNATIVE_OPTIONS_DICT = dict([(_(option), option) for option in GetAlternativeOptions()])
       
    55 
       
    56 def GetFilterChoiceTransfer():
       
    57     _ = lambda x : x
       
    58     return {_("All"): _("All"), _("Interface"): _("Interface"), 
       
    59             _("   Input"): _("Input"), _("   Output"): _("Output"), _("   InOut"): _("InOut"), 
       
    60             _("   External"): _("External"), _("Variables"): _("Variables"), _("   Local"): _("Local"),
       
    61             _("   Temp"): _("Temp"), _("Global"): _("Global")}#, _("Access") : _("Access")}
       
    62 VARIABLE_CLASSES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().itervalues()])
       
    63 
       
    64 class VariableTable(wx.grid.PyGridTableBase):
       
    65     
       
    66     """
       
    67     A custom wx.grid.Grid Table using user supplied data
       
    68     """
       
    69     def __init__(self, parent, data, colnames):
       
    70         # The base class must be initialized *first*
       
    71         wx.grid.PyGridTableBase.__init__(self)
       
    72         self.data = data
       
    73         self.old_value = None
       
    74         self.colnames = colnames
       
    75         self.Errors = {}
       
    76         self.Parent = parent
       
    77         # XXX
       
    78         # we need to store the row length and collength to
       
    79         # see if the table has changed size
       
    80         self._rows = self.GetNumberRows()
       
    81         self._cols = self.GetNumberCols()
       
    82     
       
    83     def GetNumberCols(self):
       
    84         return len(self.colnames)
       
    85         
       
    86     def GetNumberRows(self):
       
    87         return len(self.data)
       
    88 
       
    89     def GetColLabelValue(self, col, translate=True):
       
    90         if col < len(self.colnames):
       
    91             if translate:
       
    92                 return _(self.colnames[col])
       
    93             return self.colnames[col]
       
    94 
       
    95     def GetRowLabelValues(self, row, translate=True):
       
    96         return row
       
    97 
       
    98     def GetValue(self, row, col):
       
    99         if row < self.GetNumberRows():
       
   100             if col == 0:
       
   101                 return self.data[row]["Number"]
       
   102             colname = self.GetColLabelValue(col, False)
       
   103             value = str(self.data[row].get(colname, ""))
       
   104             if colname in ["Class", "Retain", "Constant"]:
       
   105                 return _(value)
       
   106             return value
       
   107     
       
   108     def SetValue(self, row, col, value):
       
   109         if col < len(self.colnames):
       
   110             colname = self.GetColLabelValue(col, False)
       
   111             if colname == "Name":
       
   112                 self.old_value = self.data[row][colname]
       
   113             elif colname == "Class":
       
   114                 value = VARIABLE_CLASSES_DICT[value]
       
   115             elif colname in ["Retain", "Constant"]:
       
   116                 value = ALTERNATIVE_OPTIONS_DICT[value]
       
   117             self.data[row][colname] = value
       
   118     
       
   119     def GetValueByName(self, row, colname):
       
   120         if row < self.GetNumberRows():
       
   121             return self.data[row].get(colname)
       
   122 
       
   123     def SetValueByName(self, row, colname, value):
       
   124         if row < self.GetNumberRows():
       
   125             self.data[row][colname] = value
       
   126 
       
   127     def GetOldValue(self):
       
   128         return self.old_value
       
   129     
       
   130     def ResetView(self, grid):
       
   131         """
       
   132         (wx.grid.Grid) -> Reset the grid view.   Call this to
       
   133         update the grid if rows and columns have been added or deleted
       
   134         """
       
   135         grid.BeginBatch()
       
   136         for current, new, delmsg, addmsg in [
       
   137             (self._rows, self.GetNumberRows(), wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED),
       
   138             (self._cols, self.GetNumberCols(), wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED),
       
   139         ]:
       
   140             if new < current:
       
   141                 msg = wx.grid.GridTableMessage(self,delmsg,new,current-new)
       
   142                 grid.ProcessTableMessage(msg)
       
   143             elif new > current:
       
   144                 msg = wx.grid.GridTableMessage(self,addmsg,new-current)
       
   145                 grid.ProcessTableMessage(msg)
       
   146                 self.UpdateValues(grid)
       
   147         grid.EndBatch()
       
   148 
       
   149         self._rows = self.GetNumberRows()
       
   150         self._cols = self.GetNumberCols()
       
   151         # update the column rendering scheme
       
   152         self._updateColAttrs(grid)
       
   153 
       
   154         # update the scrollbars and the displayed part of the grid
       
   155         grid.AdjustScrollbars()
       
   156         grid.ForceRefresh()
       
   157 
       
   158     def UpdateValues(self, grid):
       
   159         """Update all displayed values"""
       
   160         # This sends an event to the grid table to update all of the values
       
   161         msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
       
   162         grid.ProcessTableMessage(msg)
       
   163 
       
   164     def _updateColAttrs(self, grid):
       
   165         """
       
   166         wx.grid.Grid -> update the column attributes to add the
       
   167         appropriate renderer given the column name.
       
   168 
       
   169         Otherwise default to the default renderer.
       
   170         """
       
   171         
       
   172         for row in range(self.GetNumberRows()):
       
   173             for col in range(self.GetNumberCols()):
       
   174                 editor = None
       
   175                 renderer = None
       
   176                 colname = self.GetColLabelValue(col, False)
       
   177                 if col != 0 and self.GetValueByName(row, "Edit"):
       
   178                     grid.SetReadOnly(row, col, False)
       
   179                     if colname == "Name":
       
   180                         if self.Parent.PouIsUsed and self.GetValueByName(row, "Class") in ["Input", "Output", "InOut"]:
       
   181                             grid.SetReadOnly(row, col, True)
       
   182                         else:
       
   183                             editor = wx.grid.GridCellTextEditor()
       
   184                             renderer = wx.grid.GridCellStringRenderer()
       
   185                     elif colname == "Initial Value":
       
   186                         editor = wx.grid.GridCellTextEditor()
       
   187                         renderer = wx.grid.GridCellStringRenderer()
       
   188                     elif colname == "Location":
       
   189                         if self.GetValueByName(row, "Class") in ["Local", "Global"]:
       
   190                             editor = LocationCellEditor(self.Parent)
       
   191                             renderer = wx.grid.GridCellStringRenderer()
       
   192                         else:
       
   193                             grid.SetReadOnly(row, col, True)
       
   194                     elif colname == "Class":
       
   195                         if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and self.GetValueByName(row, "Class") in ["Input", "Output", "InOut"]:
       
   196                             grid.SetReadOnly(row, col, True)
       
   197                         else:
       
   198                             editor = wx.grid.GridCellChoiceEditor()
       
   199                             excluded = []
       
   200                             if self.Parent.PouIsUsed:
       
   201                                 excluded.extend(["Input","Output","InOut"])
       
   202                             if self.Parent.IsFunctionBlockType(self.data[row]["Type"]):
       
   203                                 excluded.extend(["Local","Temp"])
       
   204                             editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
       
   205                     elif colname in ["Retain", "Constant"]:
       
   206                         editor = wx.grid.GridCellChoiceEditor()
       
   207                         editor.SetParameters(",".join(map(_, self.Parent.OptionList)))
       
   208                     elif colname == "Type":
       
   209                         editor = wx.grid.GridCellTextEditor()
       
   210                 else:
       
   211                     grid.SetReadOnly(row, col, True)
       
   212                 
       
   213                 grid.SetCellEditor(row, col, editor)
       
   214                 grid.SetCellRenderer(row, col, renderer)
       
   215                 
       
   216                 if row in self.Errors and self.Errors[row][0] == colname.lower():
       
   217                     grid.SetCellBackgroundColour(row, col, wx.Colour(255, 255, 0))
       
   218                     grid.SetCellTextColour(row, col, wx.RED)
       
   219                     grid.MakeCellVisible(row, col)
       
   220                 else:
       
   221                     grid.SetCellTextColour(row, col, wx.BLACK)
       
   222                     grid.SetCellBackgroundColour(row, col, wx.WHITE)
       
   223     
       
   224     def SetData(self, data):
       
   225         self.data = data
       
   226     
       
   227     def GetData(self):
       
   228         return self.data
       
   229     
       
   230     def GetCurrentIndex(self):
       
   231         return self.CurrentIndex
       
   232     
       
   233     def SetCurrentIndex(self, index):
       
   234         self.CurrentIndex = index
       
   235     
       
   236     def AppendRow(self, row_content):
       
   237         self.data.append(row_content)
       
   238 
       
   239     def RemoveRow(self, row_index):
       
   240         self.data.pop(row_index)
       
   241 
       
   242     def GetRow(self, row_index):
       
   243         return self.data[row_index]
       
   244 
       
   245     def Empty(self):
       
   246         self.data = []
       
   247         self.editors = []
       
   248 
       
   249     def AddError(self, infos):
       
   250         self.Errors[infos[0]] = infos[1:]
       
   251 
       
   252     def ClearErrors(self):
       
   253         self.Errors = {}
       
   254 
       
   255 class VariableDropTarget(wx.TextDropTarget):
       
   256     '''
       
   257     This allows dragging a variable location from somewhere to the Location
       
   258     column of a variable row.
       
   259     
       
   260     The drag source should be a TextDataObject containing a Python tuple like:
       
   261         ('%ID0.0.0', 'location', 'REAL')
       
   262     
       
   263     c_ext/CFileEditor.py has an example of this (you can drag a C extension
       
   264     variable to the Location column of the variable panel).
       
   265     '''
       
   266     def __init__(self, parent):
       
   267         wx.TextDropTarget.__init__(self)
       
   268         self.ParentWindow = parent
       
   269     
       
   270     def OnDropText(self, x, y, data):
       
   271         x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y)
       
   272         col = self.ParentWindow.VariablesGrid.XToCol(x)
       
   273         row = self.ParentWindow.VariablesGrid.YToRow(y - self.ParentWindow.VariablesGrid.GetColLabelSize())
       
   274         if col != wx.NOT_FOUND and row != wx.NOT_FOUND:
       
   275             if self.ParentWindow.Table.GetColLabelValue(col, False) != "Location":
       
   276                 return
       
   277             message = None
       
   278             if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
       
   279                 message = _("Can't give a location to a function block instance")
       
   280             elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]:
       
   281                 message = _("Can only give a location to local or global variables")
       
   282             else:
       
   283                 try:
       
   284                     values = eval(data)    
       
   285                 except:
       
   286                     message = _("Invalid value \"%s\" for location")%data
       
   287                     values = None
       
   288                 if not isinstance(values, TupleType):
       
   289                     message = _("Invalid value \"%s\" for location")%data
       
   290                     values = None
       
   291                 if values is not None and values[1] == "location":
       
   292                     location = values[0]
       
   293                     variable_type = self.ParentWindow.Table.GetValueByName(row, "Type")
       
   294                     base_type = self.ParentWindow.Controler.GetBaseType(variable_type)
       
   295                     message = None
       
   296                     if location.startswith("%"):
       
   297                         if base_type != values[2]:
       
   298                             message = _("Incompatible data types between \"%s\" and \"%s\"")%(values[2], variable_type)
       
   299                         else:
       
   300                             self.ParentWindow.Table.SetValue(row, col, location)
       
   301                             self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
       
   302                             self.ParentWindow.SaveValues()
       
   303                     else:
       
   304                         if location[0].isdigit() and base_type != "BOOL":
       
   305                             message = _("Incompatible size of data between \"%s\" and \"BOOL\"")%location
       
   306                         elif location[0] not in LOCATIONDATATYPES:
       
   307                             message = _("Unrecognized data size \"%s\"")%location[0]
       
   308                         elif base_type not in LOCATIONDATATYPES[location[0]]:
       
   309                             message = _("Incompatible size of data between \"%s\" and \"%s\"")%(location, variable_type)
       
   310                         else:
       
   311                             dialog = wx.SingleChoiceDialog(self.ParentWindow, _("Select a variable class:"), _("Variable class"), ["Input", "Output", "Memory"], wx.OK|wx.CANCEL)
       
   312                             if dialog.ShowModal() == wx.ID_OK:
       
   313                                 selected = dialog.GetSelection()
       
   314                                 if selected == 0:
       
   315                                     location = "%I" + location
       
   316                                 elif selected == 1:
       
   317                                     location = "%Q" + location
       
   318                                 else:
       
   319                                     location = "%M" + location
       
   320                                 self.ParentWindow.Table.SetValue(row, col, location)
       
   321                                 self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
       
   322                                 self.ParentWindow.SaveValues()
       
   323                             dialog.Destroy()
       
   324             if message is not None:
       
   325                 wx.CallAfter(self.ShowMessage, message)
       
   326             
       
   327     def ShowMessage(self, message):
       
   328         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   329         message.ShowModal()
       
   330         message.Destroy()
       
   331 
       
   332 [ID_VARIABLEEDITORPANEL, ID_VARIABLEEDITORPANELVARIABLESGRID, 
       
   333  ID_VARIABLEEDITORCONTROLPANEL, ID_VARIABLEEDITORPANELRETURNTYPE, 
       
   334  ID_VARIABLEEDITORPANELCLASSFILTER, ID_VARIABLEEDITORPANELADDBUTTON, 
       
   335  ID_VARIABLEEDITORPANELDELETEBUTTON, ID_VARIABLEEDITORPANELUPBUTTON, 
       
   336  ID_VARIABLEEDITORPANELDOWNBUTTON, ID_VARIABLEEDITORPANELSTATICTEXT1, 
       
   337  ID_VARIABLEEDITORPANELSTATICTEXT2, ID_VARIABLEEDITORPANELSTATICTEXT3,
       
   338 ] = [wx.NewId() for _init_ctrls in range(12)]
       
   339 
       
   340 class VariablePanel(wx.Panel):
       
   341     
       
   342     if wx.VERSION < (2, 6, 0):
       
   343         def Bind(self, event, function, id = None):
       
   344             if id is not None:
       
   345                 event(self, id, function)
       
   346             else:
       
   347                 event(self, function)
       
   348     
       
   349     def _init_coll_MainSizer_Items(self, parent):
       
   350         parent.AddWindow(self.VariablesGrid, 0, border=0, flag=wx.GROW)
       
   351         parent.AddWindow(self.ControlPanel, 0, border=5, flag=wx.GROW|wx.ALL)
       
   352     
       
   353     def _init_coll_MainSizer_Growables(self, parent):
       
   354         parent.AddGrowableCol(0)
       
   355         parent.AddGrowableRow(0)
       
   356     
       
   357     def _init_coll_ControlPanelSizer_Items(self, parent):
       
   358         parent.AddSizer(self.ChoicePanelSizer, 0, border=0, flag=wx.GROW)
       
   359         parent.AddSizer(self.ButtonPanelSizer, 0, border=0, flag=wx.ALIGN_CENTER)
       
   360 
       
   361     def _init_coll_ControlPanelSizer_Growables(self, parent):
       
   362         parent.AddGrowableCol(0)
       
   363         parent.AddGrowableRow(0)
       
   364         parent.AddGrowableRow(1)
       
   365 
       
   366     def _init_coll_ChoicePanelSizer_Items(self, parent):
       
   367         parent.AddWindow(self.staticText1, 0, border=0, flag=wx.ALIGN_BOTTOM)
       
   368         parent.AddWindow(self.ReturnType, 0, border=0, flag=0)
       
   369         parent.AddWindow(self.staticText2, 0, border=0, flag=wx.ALIGN_BOTTOM)
       
   370         parent.AddWindow(self.ClassFilter, 0, border=0, flag=0)
       
   371 
       
   372     def _init_coll_ButtonPanelSizer_Items(self, parent):
       
   373         parent.AddWindow(self.UpButton, 0, border=0, flag=0)
       
   374         parent.AddWindow(self.AddButton, 0, border=0, flag=0)
       
   375         parent.AddWindow(self.DownButton, 0, border=0, flag=0)
       
   376         parent.AddWindow(self.DeleteButton, 0, border=0, flag=0)
       
   377         
       
   378     def _init_coll_ButtonPanelSizer_Growables(self, parent):
       
   379         parent.AddGrowableCol(0)
       
   380         parent.AddGrowableCol(1)
       
   381         parent.AddGrowableCol(2)
       
   382         parent.AddGrowableCol(3)
       
   383         parent.AddGrowableRow(0)
       
   384 
       
   385     def _init_sizers(self):
       
   386         self.MainSizer = wx.FlexGridSizer(cols=2, hgap=10, rows=1, vgap=0)
       
   387         self.ControlPanelSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
       
   388         self.ChoicePanelSizer = wx.GridSizer(cols=1, hgap=5, rows=4, vgap=5)
       
   389         self.ButtonPanelSizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
       
   390         
       
   391         self._init_coll_MainSizer_Items(self.MainSizer)
       
   392         self._init_coll_MainSizer_Growables(self.MainSizer)
       
   393         self._init_coll_ControlPanelSizer_Items(self.ControlPanelSizer)
       
   394         self._init_coll_ControlPanelSizer_Growables(self.ControlPanelSizer)
       
   395         self._init_coll_ChoicePanelSizer_Items(self.ChoicePanelSizer)
       
   396         self._init_coll_ButtonPanelSizer_Items(self.ButtonPanelSizer)
       
   397         self._init_coll_ButtonPanelSizer_Growables(self.ButtonPanelSizer)
       
   398         
       
   399         self.SetSizer(self.MainSizer)
       
   400         self.ControlPanel.SetSizer(self.ControlPanelSizer)
       
   401 
       
   402     def _init_ctrls(self, prnt):
       
   403         wx.Panel.__init__(self, id=ID_VARIABLEEDITORPANEL,
       
   404               name='VariableEditorPanel', parent=prnt, pos=wx.Point(0, 0),
       
   405               size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
       
   406 
       
   407         self.VariablesGrid = wx.grid.Grid(id=ID_VARIABLEEDITORPANELVARIABLESGRID,
       
   408               name='VariablesGrid', parent=self, pos=wx.Point(0, 0), 
       
   409               size=wx.Size(0, 0), style=wx.VSCROLL)
       
   410         self.VariablesGrid.SetFont(wx.Font(12, 77, wx.NORMAL, wx.NORMAL, False,
       
   411               'Sans'))
       
   412         self.VariablesGrid.SetLabelFont(wx.Font(10, 77, wx.NORMAL, wx.NORMAL,
       
   413               False, 'Sans'))
       
   414         self.VariablesGrid.SetSelectionBackground(wx.WHITE)
       
   415         self.VariablesGrid.SetSelectionForeground(wx.BLACK)
       
   416         if wx.VERSION >= (2, 6, 0):
       
   417             self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
       
   418             self.VariablesGrid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnVariablesGridSelectCell)
       
   419             self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
       
   420             self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
       
   421             #self.VariablesGrid.Bind(wx.EVT_KEY_DOWN, self.OnChar)
       
   422         else:
       
   423             wx.grid.EVT_GRID_CELL_CHANGE(self.VariablesGrid, self.OnVariablesGridCellChange)
       
   424             wx.grid.EVT_GRID_SELECT_CELL(self.VariablesGrid, self.OnVariablesGridSelectCell)
       
   425             wx.grid.EVT_GRID_CELL_LEFT_CLICK(self.VariablesGrid, self.OnVariablesGridCellLeftClick)
       
   426             wx.grid.EVT_GRID_EDITOR_SHOWN(self.VariablesGrid, self.OnVariablesGridEditorShown)
       
   427             #wx.EVT_KEY_DOWN(self.VariablesGrid, self.OnChar)
       
   428         self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
       
   429         
       
   430         self.ControlPanel = wx.ScrolledWindow(id=ID_VARIABLEEDITORCONTROLPANEL,
       
   431               name='ControlPanel', parent=self, pos=wx.Point(0, 0),
       
   432               size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
       
   433         self.ControlPanel.SetScrollRate(0, 10)
       
   434         
       
   435         self.staticText1 = wx.StaticText(id=ID_VARIABLEEDITORPANELSTATICTEXT1,
       
   436               label=_('Return Type:'), name='staticText1', parent=self.ControlPanel,
       
   437               pos=wx.Point(0, 0), size=wx.Size(145, 17), style=0)
       
   438 
       
   439         self.ReturnType = wx.ComboBox(id=ID_VARIABLEEDITORPANELRETURNTYPE,
       
   440               name='ReturnType', parent=self.ControlPanel, pos=wx.Point(0, 0),
       
   441               size=wx.Size(145, 28), style=wx.CB_READONLY)
       
   442         self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, id=ID_VARIABLEEDITORPANELRETURNTYPE)
       
   443 
       
   444         self.staticText2 = wx.StaticText(id=ID_VARIABLEEDITORPANELSTATICTEXT2,
       
   445               label=_('Class Filter:'), name='staticText2', parent=self.ControlPanel,
       
   446               pos=wx.Point(0, 0), size=wx.Size(145, 17), style=0)
       
   447 
       
   448         self.ClassFilter = wx.ComboBox(id=ID_VARIABLEEDITORPANELCLASSFILTER,
       
   449               name='ClassFilter', parent=self.ControlPanel, pos=wx.Point(0, 0),
       
   450               size=wx.Size(145, 28), style=wx.CB_READONLY)
       
   451         self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, id=ID_VARIABLEEDITORPANELCLASSFILTER)
       
   452 
       
   453         self.AddButton = wx.Button(id=ID_VARIABLEEDITORPANELADDBUTTON, label=_('Add'),
       
   454               name='AddButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
       
   455               size=wx.DefaultSize, style=0)
       
   456         self.Bind(wx.EVT_BUTTON, self.OnAddButton, id=ID_VARIABLEEDITORPANELADDBUTTON)
       
   457 
       
   458         self.DeleteButton = wx.Button(id=ID_VARIABLEEDITORPANELDELETEBUTTON, label=_('Delete'),
       
   459               name='DeleteButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
       
   460               size=wx.DefaultSize, style=0)
       
   461         self.Bind(wx.EVT_BUTTON, self.OnDeleteButton, id=ID_VARIABLEEDITORPANELDELETEBUTTON)
       
   462 
       
   463         self.UpButton = wx.Button(id=ID_VARIABLEEDITORPANELUPBUTTON, label='^',
       
   464               name='UpButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
       
   465               size=wx.Size(32, 32), style=0)
       
   466         self.Bind(wx.EVT_BUTTON, self.OnUpButton, id=ID_VARIABLEEDITORPANELUPBUTTON)
       
   467 
       
   468         self.DownButton = wx.Button(id=ID_VARIABLEEDITORPANELDOWNBUTTON, label='v',
       
   469               name='DownButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
       
   470               size=wx.Size(32, 32), style=0)
       
   471         self.Bind(wx.EVT_BUTTON, self.OnDownButton, id=ID_VARIABLEEDITORPANELDOWNBUTTON)
       
   472 
       
   473         self._init_sizers()
       
   474 
       
   475     def __init__(self, parent, window, controler, element_type):
       
   476         self._init_ctrls(parent)
       
   477         self.ParentWindow = window
       
   478         self.Controler = controler
       
   479         self.ElementType = element_type
       
   480         
       
   481         self.Filter = "All"
       
   482         self.FilterChoices = []
       
   483         self.FilterChoiceTransfer = GetFilterChoiceTransfer()
       
   484         
       
   485         self.DefaultValue = {   "Name" : "", "Class" : "", "Type" : "INT", "Location" : "",
       
   486                                 "Initial Value" : "", "Retain" : "No", "Constant" : "No",
       
   487                                 "Documentation" : "", "Edit" : True
       
   488                             }
       
   489 
       
   490         if element_type in ["config", "resource"]:
       
   491             self.DefaultTypes = {"All" : "Global"}
       
   492         else:
       
   493             self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"}
       
   494 
       
   495         if element_type in ["config", "resource"] \
       
   496         or element_type in ["program", "transition", "action"]:
       
   497             # this is an element that can have located variables
       
   498             self.Table = VariableTable(self, [], GetVariableTableColnames(True))
       
   499 
       
   500             if element_type in ["config", "resource"]:
       
   501                 self.FilterChoices = ["All", "Global"]#,"Access"]
       
   502             else:
       
   503                 self.FilterChoices = ["All",
       
   504                                         "Interface", "   Input", "   Output", "   InOut", "   External",
       
   505                                         "Variables", "   Local", "   Temp"]#,"Access"]
       
   506 
       
   507             # these condense the ColAlignements list
       
   508             l = wx.ALIGN_LEFT
       
   509             c = wx.ALIGN_CENTER 
       
   510 
       
   511             #                      Num  Name    Class   Type    Loc     Init    Retain  Const   Doc
       
   512             self.ColSizes       = [40,  80,     70,     80,     80,     80,     60,     70,     80]
       
   513             self.ColAlignements = [c,   l,      l,      l,      l,      l,      c,      c,      l]
       
   514 
       
   515         else:
       
   516             # this is an element that cannot have located variables
       
   517             self.Table = VariableTable(self, [], GetVariableTableColnames(False))
       
   518 
       
   519             if element_type == "function":
       
   520                 self.FilterChoices = ["All",
       
   521                                         "Interface", "   Input", "   Output", "   InOut",
       
   522                                         "Variables", "   Local", "   Temp"]
       
   523             else:
       
   524                 self.FilterChoices = ["All",
       
   525                                         "Interface", "   Input", "   Output", "   InOut", "   External",
       
   526                                         "Variables", "   Local", "   Temp"]
       
   527 
       
   528             # these condense the ColAlignements list
       
   529             l = wx.ALIGN_LEFT
       
   530             c = wx.ALIGN_CENTER 
       
   531 
       
   532             #                      Num  Name    Class   Type    Init    Retain  Const   Doc
       
   533             self.ColSizes       = [40,  80,     70,     80,     80,     60,     70,     160]
       
   534             self.ColAlignements = [c,   l,      l,      l,      l,      c,      c,      l]
       
   535 
       
   536         for choice in self.FilterChoices:
       
   537             self.ClassFilter.Append(_(choice))
       
   538 
       
   539         reverse_transfer = {}
       
   540         for filter, choice in self.FilterChoiceTransfer.items():
       
   541             reverse_transfer[choice] = filter
       
   542         self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter]))
       
   543         self.RefreshTypeList()
       
   544 
       
   545         self.OptionList = GetAlternativeOptions()
       
   546         
       
   547         if element_type == "function":
       
   548             for base_type in self.Controler.GetBaseTypes():
       
   549                 self.ReturnType.Append(base_type)
       
   550             self.ReturnType.Enable(True)
       
   551         else:
       
   552             self.ReturnType.Enable(False)
       
   553             self.staticText1.Hide()
       
   554             self.ReturnType.Hide()
       
   555             
       
   556         self.VariablesGrid.SetTable(self.Table)
       
   557         self.VariablesGrid.SetRowLabelSize(0)
       
   558         for col in range(self.Table.GetNumberCols()):
       
   559             attr = wx.grid.GridCellAttr()
       
   560             attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
       
   561             self.VariablesGrid.SetColAttr(col, attr)
       
   562             self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col])
       
   563             self.VariablesGrid.AutoSizeColumn(col, False)
       
   564     
       
   565     def SetTagName(self, tagname):
       
   566         self.TagName = tagname
       
   567     
       
   568     def IsFunctionBlockType(self, name):
       
   569         bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
       
   570         pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
       
   571         if poutype != "function" and bodytype in ["ST", "IL"]:
       
   572             return False
       
   573         else:
       
   574             return name in self.Controler.GetFunctionBlockTypes(self.TagName)
       
   575     
       
   576     def RefreshView(self):
       
   577         self.PouNames = self.Controler.GetProjectPouNames()
       
   578         
       
   579         words = self.TagName.split("::")
       
   580         if self.ElementType == "config":
       
   581             self.PouIsUsed = False
       
   582             returnType = None
       
   583             self.Values = self.Controler.GetConfigurationGlobalVars(words[1])
       
   584         elif self.ElementType == "resource":
       
   585             self.PouIsUsed = False
       
   586             returnType = None
       
   587             self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2])
       
   588         else:
       
   589             self.PouIsUsed = self.Controler.PouIsUsed(words[1])
       
   590             returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
       
   591             self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName)
       
   592         
       
   593         if returnType and self.ReturnType.IsEnabled():
       
   594             self.ReturnType.SetStringSelection(returnType)
       
   595         
       
   596         self.RefreshValues()
       
   597         self.RefreshButtons()
       
   598     
       
   599     def OnReturnTypeChanged(self, event):
       
   600         words = self.TagName.split("::")
       
   601         self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
       
   602         self.Controler.BufferProject()
       
   603         self.ParentWindow.RefreshEditor(variablepanel = False)
       
   604         self.ParentWindow._Refresh(TITLE, EDITMENU, INSTANCETREE, LIBRARYTREE)
       
   605         event.Skip()
       
   606     
       
   607     def OnClassFilter(self, event):
       
   608         self.Filter = self.FilterChoiceTransfer[self.ClassFilter.GetStringSelection()]
       
   609         self.RefreshTypeList()
       
   610         self.RefreshValues()
       
   611         self.RefreshButtons()
       
   612         event.Skip()
       
   613 
       
   614     def RefreshTypeList(self):
       
   615         if self.Filter == "All":
       
   616             self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[choice] not in ["All","Interface","Variables"]]
       
   617         elif self.Filter == "Interface":
       
   618             self.ClassList = ["Input","Output","InOut","External"]
       
   619         elif self.Filter == "Variables":
       
   620             self.ClassList = ["Local","Temp"]
       
   621         else:
       
   622             self.ClassList = [self.Filter]
       
   623 
       
   624     def RefreshButtons(self):
       
   625         if getattr(self, "Table", None):
       
   626             table_length = len(self.Table.data)
       
   627             row_class = None
       
   628             row_edit = True
       
   629             if table_length > 0:
       
   630                 row = self.VariablesGrid.GetGridCursorRow()
       
   631                 row_edit = self.Table.GetValueByName(row, "Edit")
       
   632                 if self.PouIsUsed:
       
   633                     row_class = self.Table.GetValueByName(row, "Class")
       
   634             self.AddButton.Enable(not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"])
       
   635             self.DeleteButton.Enable(table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"])
       
   636             self.UpButton.Enable(table_length > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])
       
   637             self.DownButton.Enable(table_length > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"])
       
   638 
       
   639     def OnAddButton(self, event):
       
   640         new_row = self.DefaultValue.copy()
       
   641         if self.Filter in self.DefaultTypes:
       
   642             new_row["Class"] = self.DefaultTypes[self.Filter]
       
   643         else:
       
   644             new_row["Class"] = self.Filter
       
   645         if self.Filter == "All" and len(self.Values) > 0:
       
   646             row_index = self.VariablesGrid.GetGridCursorRow() + 1
       
   647             self.Values.insert(row_index, new_row)
       
   648         else:
       
   649             row_index = -1
       
   650             self.Values.append(new_row)
       
   651         self.SaveValues()
       
   652         self.RefreshValues(row_index)
       
   653         self.RefreshButtons()
       
   654         event.Skip()
       
   655 
       
   656     def OnDeleteButton(self, event):
       
   657         row = self.Table.GetRow(self.VariablesGrid.GetGridCursorRow())
       
   658         self.Values.remove(row)
       
   659         self.SaveValues()
       
   660         self.RefreshValues()
       
   661         self.RefreshButtons()
       
   662         event.Skip()
       
   663 
       
   664     def OnUpButton(self, event):
       
   665         self.MoveValue(self.VariablesGrid.GetGridCursorRow(), -1)
       
   666         self.RefreshButtons()
       
   667         event.Skip()
       
   668 
       
   669     def OnDownButton(self, event):
       
   670         self.MoveValue(self.VariablesGrid.GetGridCursorRow(), 1)
       
   671         self.RefreshButtons()
       
   672         event.Skip()
       
   673 
       
   674     def OnVariablesGridCellChange(self, event):
       
   675         row, col = event.GetRow(), event.GetCol()
       
   676         colname = self.Table.GetColLabelValue(col, False)
       
   677         value = self.Table.GetValue(row, col)
       
   678 
       
   679         if colname == "Name" and value != "":
       
   680             if not TestIdentifier(value):
       
   681                 message = wx.MessageDialog(self, _("\"%s\" is not a valid identifier!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
       
   682                 message.ShowModal()
       
   683                 message.Destroy()
       
   684                 event.Veto()
       
   685             elif value.upper() in IEC_KEYWORDS:
       
   686                 message = wx.MessageDialog(self, _("\"%s\" is a keyword. It can't be used!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
       
   687                 message.ShowModal()
       
   688                 message.Destroy()
       
   689                 event.Veto()
       
   690             elif value.upper() in self.PouNames:
       
   691                 message = wx.MessageDialog(self, _("A pou with \"%s\" as name exists!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
       
   692                 message.ShowModal()
       
   693                 message.Destroy()
       
   694                 event.Veto()
       
   695             elif value.upper() in [var["Name"].upper() for var in self.Values if var != self.Table.data[row]]:
       
   696                 message = wx.MessageDialog(self, _("A variable with \"%s\" as name already exists in this pou!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
       
   697                 message.ShowModal()
       
   698                 message.Destroy()
       
   699                 event.Veto()
       
   700             else:
       
   701                 self.SaveValues(False)
       
   702                 old_value = self.Table.GetOldValue()
       
   703                 if old_value != "":
       
   704                     self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
       
   705                 self.Controler.BufferProject()
       
   706                 self.ParentWindow.RefreshEditor(variablepanel = False)
       
   707                 self.ParentWindow._Refresh(TITLE, EDITMENU, INSTANCETREE, LIBRARYTREE)            
       
   708                 event.Skip()
       
   709         else:
       
   710             self.SaveValues()
       
   711             if colname == "Class":
       
   712                 self.Table.ResetView(self.VariablesGrid)
       
   713             event.Skip()
       
   714     
       
   715     def OnVariablesGridEditorShown(self, event):
       
   716         row, col = event.GetRow(), event.GetCol() 
       
   717 
       
   718         label_value = self.Table.GetColLabelValue(col)
       
   719         if label_value == "Type":
       
   720             type_menu = wx.Menu(title='')   # the root menu
       
   721 
       
   722             # build a submenu containing standard IEC types
       
   723             base_menu = wx.Menu(title='')
       
   724             for base_type in self.Controler.GetBaseTypes():
       
   725                 new_id = wx.NewId()
       
   726                 AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
       
   727                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
       
   728 
       
   729             type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
       
   730 
       
   731             # build a submenu containing user-defined types
       
   732             datatype_menu = wx.Menu(title='')
       
   733             datatypes = self.Controler.GetDataTypes(basetypes = False)
       
   734             for datatype in datatypes:
       
   735                 new_id = wx.NewId()
       
   736                 AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
       
   737                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
       
   738 
       
   739             type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
       
   740 
       
   741             # build a submenu containing function block types
       
   742             bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
       
   743             pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
       
   744             classtype = self.Table.GetValueByName(row, "Class")
       
   745 
       
   746             if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
       
   747             poutype != "function" and bodytype in ["ST", "IL"]:
       
   748                 functionblock_menu = wx.Menu(title='')
       
   749                 fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
       
   750                 for functionblock_type in fbtypes:
       
   751                     new_id = wx.NewId()
       
   752                     AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
       
   753                     self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
       
   754 
       
   755                 type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
       
   756 
       
   757             rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
       
   758             corner_x = rect.x + rect.width
       
   759             corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
       
   760 
       
   761             # pop up this new menu
       
   762             self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
       
   763             event.Veto()
       
   764         else:
       
   765             event.Skip()
       
   766     
       
   767     def GetVariableTypeFunction(self, base_type):
       
   768         def VariableTypeFunction(event):
       
   769             row = self.VariablesGrid.GetGridCursorRow()
       
   770             self.Table.SetValueByName(row, "Type", base_type)
       
   771             self.Table.ResetView(self.VariablesGrid)
       
   772             self.SaveValues(False)
       
   773             self.ParentWindow.RefreshEditor(variablepanel = False)
       
   774             self.Controler.BufferProject()
       
   775             self.ParentWindow._Refresh(TITLE, EDITMENU, INSTANCESTREE, LIBRARYTREE)            
       
   776             event.Skip()
       
   777         return VariableTypeFunction
       
   778     
       
   779     def OnVariablesGridCellLeftClick(self, event):
       
   780         row = event.GetRow()
       
   781         if event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit"):
       
   782             row = event.GetRow()
       
   783             var_name = self.Table.GetValueByName(row, "Name")
       
   784             var_class = self.Table.GetValueByName(row, "Class")
       
   785             var_type = self.Table.GetValueByName(row, "Type")
       
   786             data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName)))
       
   787             dragSource = wx.DropSource(self.VariablesGrid)
       
   788             dragSource.SetData(data)
       
   789             dragSource.DoDragDrop()
       
   790         event.Skip()
       
   791     
       
   792     def OnVariablesGridSelectCell(self, event):
       
   793         wx.CallAfter(self.RefreshButtons)
       
   794         event.Skip()
       
   795 
       
   796     def OnChar(self, event):
       
   797         keycode = event.GetKeyCode()
       
   798         if keycode == wx.WXK_DELETE:
       
   799             row = self.Table.GetRow(self.VariablesGrid.GetGridCursorRow())
       
   800             self.Values.remove(row)
       
   801             self.SaveValues()
       
   802             self.RefreshValues()
       
   803             self.RefreshButtons()
       
   804         event.Skip()
       
   805 
       
   806     def MoveValue(self, value_index, move):
       
   807         new_index = max(0, min(value_index + move, len(self.Values) - 1))
       
   808         if new_index != value_index:
       
   809             self.Values.insert(new_index, self.Values.pop(value_index))
       
   810             self.SaveValues()
       
   811             self.RefreshValues()
       
   812             self.VariablesGrid.SetGridCursor(new_index, self.VariablesGrid.GetGridCursorCol())
       
   813         
       
   814     def RefreshValues(self, select=0):
       
   815         if len(self.Table.data) > 0:
       
   816             self.VariablesGrid.SetGridCursor(0, 1)
       
   817         data = []
       
   818         for num, variable in enumerate(self.Values):
       
   819             if variable["Class"] in self.ClassList:
       
   820                 variable["Number"] = num + 1
       
   821                 data.append(variable)
       
   822         self.Table.SetData(data)
       
   823         if len(self.Table.data) > 0:
       
   824             if select == -1:
       
   825                 select = len(self.Table.data) - 1
       
   826             self.VariablesGrid.SetGridCursor(select, 1)
       
   827             self.VariablesGrid.MakeCellVisible(select, 1)
       
   828         self.Table.ResetView(self.VariablesGrid)
       
   829 
       
   830     def SaveValues(self, buffer = True):
       
   831         words = self.TagName.split("::")
       
   832         if self.ElementType == "config":
       
   833             self.Controler.SetConfigurationGlobalVars(words[1], self.Values)
       
   834         elif self.ElementType == "resource":
       
   835             self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values)
       
   836         else:
       
   837             if self.ReturnType.IsEnabled():
       
   838                 self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
       
   839             self.Controler.SetPouInterfaceVars(words[1], self.Values)
       
   840         if buffer:
       
   841             self.Controler.BufferProject()
       
   842             self.ParentWindow._Refresh(TITLE, EDITMENU, INSTANCESTREE, LIBRARYTREE)            
       
   843 
       
   844     def AddVariableError(self, infos):
       
   845         if isinstance(infos[0], TupleType):
       
   846             for i in xrange(*infos[0]):
       
   847                 self.Table.AddError((i,) + infos[1:])
       
   848         else:
       
   849             self.Table.AddError(infos)
       
   850         self.Table.ResetView(self.VariablesGrid)
       
   851 
       
   852     def ClearErrors(self):
       
   853         self.Table.ClearErrors()
       
   854         self.Table.ResetView(self.VariablesGrid)
       
   855 
       
   856 class LocationCellControl(wx.PyControl):
       
   857     '''
       
   858     Custom cell editor control with a text box and a button that launches
       
   859     the BrowseVariableLocationsDialog.
       
   860     '''
       
   861     def __init__(self, parent, var_panel):
       
   862         wx.Control.__init__(self, parent, -1)
       
   863         self.ParentWindow = parent
       
   864         self.VarPanel = var_panel
       
   865         self.Row = -1
       
   866 
       
   867         self.Bind(wx.EVT_SIZE, self.OnSize)
       
   868 
       
   869         # create text control
       
   870         self.txt = wx.TextCtrl(self, -1, '', size=wx.Size(0, 0))
       
   871 
       
   872         # create browse button
       
   873         self.btn = wx.Button(self, -1, label='...', size=wx.Size(30, 0))
       
   874         self.btn.Bind(wx.EVT_BUTTON, self.OnBtnBrowseClick)
       
   875 
       
   876         self.Sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
       
   877         self.Sizer.AddWindow(self.txt, 0, border=0, flag=wx.ALL|wx.GROW)
       
   878         self.Sizer.AddWindow(self.btn, 0, border=0, flag=wx.ALL|wx.GROW)
       
   879         self.Sizer.AddGrowableCol(0)
       
   880         self.Sizer.AddGrowableRow(0)
       
   881 
       
   882         self.SetSizer(self.Sizer)
       
   883 
       
   884     def SetRow(self, row):
       
   885         '''set the grid row that we're working on'''
       
   886         self.Row = row
       
   887 
       
   888     def OnSize(self, event):
       
   889         '''resize the button and text control to fit'''
       
   890         #overall_width = self.GetSize()[0]
       
   891         #btn_width, btn_height = self.btn.GetSize()
       
   892         #new_txt_width = overall_width - btn_width
       
   893 
       
   894         #self.txt.SetSize(wx.Size(new_txt_width, -1))
       
   895         #self.btn.SetDimensions(new_txt_width, -1, btn_width, btn_height)
       
   896         self.Layout()
       
   897 
       
   898     def OnBtnBrowseClick(self, event):
       
   899         # pop up the location browser dialog
       
   900         dia = BrowseVariableLocationsDialog(self.ParentWindow, self.VarPanel)
       
   901         dia.ShowModal()
       
   902 
       
   903         if dia.Selection:
       
   904             loc, iec_type, doc = dia.Selection
       
   905 
       
   906             # set the location
       
   907             self.SetText(loc)
       
   908 
       
   909             # set the variable type and documentation
       
   910             # NOTE: this update won't be displayed until editing is complete
       
   911             # (when EndEdit is called).
       
   912             # we can't call VarPanel.RefreshValues() here because it causes
       
   913             # an exception. 
       
   914             self.VarPanel.Table.SetValueByName(self.Row, 'Type', iec_type)
       
   915             self.VarPanel.Table.SetValueByName(self.Row, 'Documentation', doc)
       
   916 
       
   917         self.txt.SetFocus()
       
   918 
       
   919     def SetText(self, text):
       
   920         self.txt.SetValue(text)
       
   921 
       
   922     def SetInsertionPoint(self, i):
       
   923         self.txt.SetInsertionPoint(i)
       
   924 
       
   925     def GetText(self):
       
   926         return self.txt.GetValue()
       
   927 
       
   928     def SetFocus(self):
       
   929         self.txt.SetFocus()
       
   930 
       
   931 class LocationCellEditor(wx.grid.PyGridCellEditor):
       
   932     '''
       
   933     Grid cell editor that uses LocationCellControl to display a browse button.
       
   934     '''
       
   935     def __init__(self, var_panel):
       
   936         wx.grid.PyGridCellEditor.__init__(self)
       
   937         self.VarPanel = var_panel
       
   938 
       
   939     def Create(self, parent, id, evt_handler):
       
   940         self.text_browse = LocationCellControl(parent, self.VarPanel)
       
   941         self.SetControl(self.text_browse)
       
   942         if evt_handler:
       
   943             self.text_browse.PushEventHandler(evt_handler)
       
   944 
       
   945     def BeginEdit(self, row, col, grid):
       
   946         loc = self.VarPanel.Table.GetValueByName(row, 'Location')
       
   947         self.text_browse.SetText(loc)
       
   948         self.text_browse.SetRow(row)
       
   949         self.text_browse.SetFocus()
       
   950 
       
   951     def EndEdit(self, row, col, grid):
       
   952         loc = self.text_browse.GetText()
       
   953         old_loc = self.VarPanel.Table.GetValueByName(row, 'Location')
       
   954 
       
   955         if loc != old_loc:
       
   956             self.VarPanel.Table.SetValueByName(row, 'Location', loc)
       
   957             
       
   958             # NOTE: this is a really lame hack to force this row's
       
   959             # 'Type' cell to redraw (since it may have changed). 
       
   960             # There's got to be a better way than this.
       
   961             self.VarPanel.VariablesGrid.AutoSizeRow(row)
       
   962             return True
       
   963 
       
   964     def SetSize(self, rect):
       
   965         self.text_browse.SetDimensions(rect.x + 1, rect.y,
       
   966                                         rect.width, rect.height,
       
   967                                         wx.SIZE_ALLOW_MINUS_ONE)
       
   968 
       
   969     def Clone(self):
       
   970         return LocationCellEditor(self.VarPanel)
       
   971 
       
   972 class BrowseVariableLocationsDialog(wx.Dialog):
       
   973     # turn LOCATIONDATATYPES inside-out
       
   974     LOCATION_SIZES = {}
       
   975     for size, types in LOCATIONDATATYPES.iteritems():
       
   976         for type in types:
       
   977             LOCATION_SIZES[type] = size
       
   978 
       
   979     class PluginData:
       
   980         '''contains a plugin's VariableLocationTree'''
       
   981         def __init__(self, plugin):
       
   982             self.subtree  = plugin.GetVariableLocationTree()
       
   983 
       
   984     class SubtreeData:
       
   985         '''contains a subtree of a plugin's VariableLocationTree'''
       
   986         def __init__(self, subtree):
       
   987             self.subtree = subtree
       
   988 
       
   989     class VariableData:
       
   990         '''contains all the information about a valid variable location'''
       
   991         def __init__(self, type, dir, loc):
       
   992             self.type   = type
       
   993 
       
   994             loc_suffix = '.'.join([str(x) for x in loc])
       
   995 
       
   996             size = BrowseVariableLocationsDialog.LOCATION_SIZES[type]
       
   997             self.loc = size + loc_suffix
       
   998 
       
   999             # if a direction was given, use it
       
  1000             # (if not we'll prompt the user to select one)
       
  1001             if dir:
       
  1002                 self.loc = '%' + dir + self.loc
       
  1003 
       
  1004     def __init__(self, parent, var_panel):
       
  1005         self.VarPanel   = var_panel
       
  1006         self.Selection  = None
       
  1007 
       
  1008         # create the dialog
       
  1009         wx.Dialog.__init__(self, parent=parent, title=_('Browse Variables'),
       
  1010                             style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER,
       
  1011                             size=(-1, 400))
       
  1012 
       
  1013         # create the root sizer
       
  1014         sizer = wx.BoxSizer(wx.VERTICAL)
       
  1015         self.SetSizer(sizer)
       
  1016 
       
  1017         # create the tree control
       
  1018         self.tree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT)
       
  1019         sizer.Add(self.tree, 1, wx.EXPAND|wx.ALL, border=20)
       
  1020 
       
  1021         # create the Direction sizer and field
       
  1022         dsizer = wx.BoxSizer(wx.HORIZONTAL)
       
  1023         sizer.Add(dsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, border=20)
       
  1024 
       
  1025         #   direction label
       
  1026         ltext = wx.StaticText(self, -1, _('Direction:'))
       
  1027         dsizer.Add(ltext, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=20)
       
  1028 
       
  1029         #   direction choice
       
  1030         self.DirChoice = wx.Choice(id=-1, parent=self, choices=[_('Input'), _('Output'), _('Memory')])
       
  1031         dsizer.Add(self.DirChoice, flag=wx.EXPAND)
       
  1032         #   set a default for the choice   
       
  1033         self.SetSelectedDirection('I')
       
  1034 
       
  1035         # create the button sizer
       
  1036         btsizer = wx.BoxSizer(wx.HORIZONTAL)
       
  1037         sizer.Add(btsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT, border=20)
       
  1038 
       
  1039         # add plugins to the tree
       
  1040         root = self.tree.AddRoot(_('Plugins'))
       
  1041         ctrl = self.VarPanel.Controler
       
  1042         self.AddChildPluginsToTree(ctrl, root)
       
  1043 
       
  1044         #       -- buttons --
       
  1045 
       
  1046         # ok button
       
  1047         self.OkButton = wx.Button(self, wx.ID_OK, _('Use Location'))
       
  1048         self.OkButton.SetDefault()
       
  1049         btsizer.Add(self.OkButton, flag=wx.RIGHT, border=5)
       
  1050 
       
  1051         # cancel button
       
  1052         b = wx.Button(self, wx.ID_CANCEL, _('Cancel'))
       
  1053         btsizer.Add(b)
       
  1054 
       
  1055         #       -- event handlers --
       
  1056 
       
  1057         # accept the location on doubleclick or clicking the Use Location button
       
  1058         self.Bind(wx.EVT_BUTTON, self.OnOk, self.OkButton)
       
  1059         self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnOk, self.tree)
       
  1060 
       
  1061         # disable the Add button when we're not on a valid variable
       
  1062         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChange, self.tree)
       
  1063 
       
  1064         # handle the Expand event. this lets us create the tree as it's expanded
       
  1065         # (trying to create it all at once is slow since plugin variable location
       
  1066         # trees can be big)
       
  1067         wx.EVT_TREE_ITEM_EXPANDING(self.tree, self.tree.GetId(), self.OnExpand)
       
  1068 
       
  1069     def OnExpand(self, event):
       
  1070         item = event.GetItem()
       
  1071         if not item.IsOk():
       
  1072             item = self.tree.GetSelection()
       
  1073         
       
  1074         data = self.tree.GetPyData(item)
       
  1075         self.ExpandTree(item, data.subtree)
       
  1076 
       
  1077     def AddChildPluginsToTree(self, plugin, root):
       
  1078         for p in plugin.IECSortedChilds():
       
  1079             plug_name = p.BaseParams.getName()
       
  1080             new_item = self.tree.AppendItem(root, plug_name)
       
  1081 
       
  1082             # make it look like the tree item has children (since it doesn't yet)
       
  1083             self.tree.SetItemHasChildren(new_item) 
       
  1084 
       
  1085             # attach the plugin data to the tree item
       
  1086             self.tree.SetPyData(new_item, BrowseVariableLocationsDialog.PluginData(p))
       
  1087 
       
  1088             # add child plugins recursively
       
  1089             self.AddChildPluginsToTree(p, new_item)
       
  1090 
       
  1091     def ExpandTree(self, root, subtree):
       
  1092             items = subtree.items()
       
  1093             items.sort()
       
  1094 
       
  1095             for node_name, data in items:
       
  1096                 if isinstance(data, dict):
       
  1097                     # this is a new subtree
       
  1098 
       
  1099                     new_item = self.tree.AppendItem(root, node_name)
       
  1100                     self.tree.SetItemHasChildren(new_item)
       
  1101 
       
  1102                     # attach the new subtree's data to the tree item
       
  1103                     new_data = BrowseVariableLocationsDialog.SubtreeData(data)
       
  1104                     self.tree.SetPyData(new_item, new_data)
       
  1105                 else:
       
  1106                     # this is a new leaf.
       
  1107 
       
  1108                     # data is a tuple containing (IEC type, I/Q/M/None, IEC path tuple)
       
  1109                     type, dir, loc = data
       
  1110 
       
  1111                     node_name = '%s (%s)' % (node_name, type)
       
  1112                     new_item = self.tree.AppendItem(root, node_name)
       
  1113 
       
  1114                     vd = BrowseVariableLocationsDialog.VariableData(type, dir, loc)
       
  1115                     self.tree.SetPyData(new_item, vd)
       
  1116 
       
  1117     def OnSelChange(self, event):
       
  1118         '''updates the text field and the "Use Location"  button.'''
       
  1119         item = self.tree.GetSelection()
       
  1120         data = self.tree.GetPyData(item)
       
  1121 
       
  1122         if isinstance(data, BrowseVariableLocationsDialog.VariableData):
       
  1123             self.OkButton.Enable()
       
  1124 
       
  1125             location = data.loc
       
  1126             if location[0] == '%':
       
  1127                 # location has a fixed direction
       
  1128                 self.SetSelectedDirection(location[1])
       
  1129                 self.DirChoice.Disable()
       
  1130             else:
       
  1131                 # this location can have any direction (user selects)
       
  1132                 self.DirChoice.Enable()
       
  1133         else:
       
  1134             self.OkButton.Disable()
       
  1135             self.DirChoice.Disable()
       
  1136 
       
  1137     def GetSelectedDirection(self):
       
  1138         selected = self.DirChoice.GetSelection()
       
  1139         if selected == 0:
       
  1140             return 'I'
       
  1141         elif selected == 1:
       
  1142             return 'Q'
       
  1143         else:
       
  1144             return 'M'
       
  1145 
       
  1146     def SetSelectedDirection(self, dir_letter):
       
  1147         if dir_letter == 'I':
       
  1148             self.DirChoice.SetSelection(0)
       
  1149         elif dir_letter == 'Q':
       
  1150             self.DirChoice.SetSelection(1)
       
  1151         else:
       
  1152             self.DirChoice.SetSelection(2)
       
  1153 
       
  1154     def OnOk(self, event):
       
  1155         item = self.tree.GetSelection()
       
  1156         data = self.tree.GetPyData(item)
       
  1157 
       
  1158         if not isinstance(data, BrowseVariableLocationsDialog.VariableData):
       
  1159             return
       
  1160 
       
  1161         location = data.loc
       
  1162 
       
  1163         if location[0] != '%':
       
  1164             # no direction was given, grab the one from the wxChoice
       
  1165             dir = self.GetSelectedDirection()
       
  1166             location = '%' + dir + location
       
  1167 
       
  1168         # walk up the tree, building documentation for the variable
       
  1169         documentation = self.tree.GetItemText(item)
       
  1170         parent = self.tree.GetItemParent(item)
       
  1171         root = self.tree.GetRootItem()
       
  1172         while parent != root:
       
  1173             text = self.tree.GetItemText(parent)
       
  1174             documentation = text + ":" + documentation
       
  1175             parent = self.tree.GetItemParent(parent)
       
  1176 
       
  1177         self.Selection = (location, data.type, documentation)
       
  1178         self.Destroy()