controls/VariablePanel.py
changeset 814 5743cbdff669
child 830 4b9df5bea400
equal deleted inserted replaced
813:1460273f40ed 814:5743cbdff669
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 import os
       
    26 import re
       
    27 from types import TupleType, StringType, UnicodeType
       
    28 
       
    29 import wx
       
    30 import wx.grid
       
    31 import wx.lib.buttons
       
    32 
       
    33 from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS
       
    34 from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT
       
    35 from dialogs.ArrayTypeDialog import ArrayTypeDialog
       
    36 from CustomGrid import CustomGrid
       
    37 from CustomTable import CustomTable
       
    38 from LocationCellEditor import LocationCellEditor
       
    39 from util.BitmapLibrary import GetBitmap
       
    40 
       
    41 #-------------------------------------------------------------------------------
       
    42 #                                 Helpers
       
    43 #-------------------------------------------------------------------------------
       
    44 
       
    45 def AppendMenu(parent, help, id, kind, text):
       
    46     if wx.VERSION >= (2, 6, 0):
       
    47         parent.Append(help=help, id=id, kind=kind, text=text)
       
    48     else:
       
    49         parent.Append(helpString=help, id=id, kind=kind, item=text)
       
    50 
       
    51 [TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE, 
       
    52  POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
       
    53 ] = range(10)
       
    54 
       
    55 def GetVariableTableColnames(location):
       
    56     _ = lambda x : x
       
    57     if location:
       
    58     	return ["#", _("Name"), _("Class"), _("Type"), _("Location"), _("Initial Value"), _("Option"), _("Documentation")]
       
    59     return ["#", _("Name"), _("Class"), _("Type"), _("Initial Value"), _("Option"), _("Documentation")]
       
    60 
       
    61 def GetOptions(constant=True, retain=True, non_retain=True):
       
    62     _ = lambda x : x
       
    63     options = [""]
       
    64     if constant:
       
    65         options.append(_("Constant"))
       
    66     if retain:
       
    67         options.append(_("Retain"))
       
    68     if non_retain:
       
    69         options.append(_("Non-Retain"))
       
    70     return options
       
    71 OPTIONS_DICT = dict([(_(option), option) for option in GetOptions()])
       
    72 
       
    73 def GetFilterChoiceTransfer():
       
    74     _ = lambda x : x
       
    75     return {_("All"): _("All"), _("Interface"): _("Interface"), 
       
    76             _("   Input"): _("Input"), _("   Output"): _("Output"), _("   InOut"): _("InOut"), 
       
    77             _("   External"): _("External"), _("Variables"): _("Variables"), _("   Local"): _("Local"),
       
    78             _("   Temp"): _("Temp"), _("Global"): _("Global")}#, _("Access") : _("Access")}
       
    79 VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().iterkeys()])
       
    80 VARIABLE_CLASSES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().itervalues()])
       
    81 
       
    82 CheckOptionForClass = {"Local": lambda x: x,
       
    83                        "Temp": lambda x: "",
       
    84                        "Input": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
       
    85                        "InOut": lambda x: "",
       
    86                        "Output": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
       
    87                        "Global": lambda x: {"Constant": "Constant", "Retain": "Retain"}.get(x, ""),
       
    88                        "External": lambda x: {"Constant": "Constant"}.get(x, "")
       
    89                       }
       
    90 
       
    91 LOCATION_MODEL = re.compile("((?:%[IQM](?:\*|(?:[XBWLD]?[0-9]+(?:\.[0-9]+)*)))?)$")
       
    92 
       
    93 #-------------------------------------------------------------------------------
       
    94 #                            Variables Panel Table
       
    95 #-------------------------------------------------------------------------------
       
    96 
       
    97 class VariableTable(CustomTable):
       
    98     
       
    99     """
       
   100     A custom wx.grid.Grid Table using user supplied data
       
   101     """
       
   102     def __init__(self, parent, data, colnames):
       
   103         # The base class must be initialized *first*
       
   104         CustomTable.__init__(self, parent, data, colnames)
       
   105         self.old_value = None
       
   106     
       
   107     def GetValue(self, row, col):
       
   108         if row < self.GetNumberRows():
       
   109             if col == 0:
       
   110                 return self.data[row]["Number"]
       
   111             colname = self.GetColLabelValue(col, False)
       
   112             value = self.data[row].get(colname, "")
       
   113             if colname == "Type" and isinstance(value, TupleType):
       
   114                 if value[0] == "array":
       
   115                     return "ARRAY [%s] OF %s" % (",".join(map(lambda x : "..".join(x), value[2])), value[1])
       
   116             if not isinstance(value, (StringType, UnicodeType)):
       
   117                 value = str(value)
       
   118             if colname in ["Class", "Option"]:
       
   119                 return _(value)
       
   120             return value
       
   121     
       
   122     def SetValue(self, row, col, value):
       
   123         if col < len(self.colnames):
       
   124             colname = self.GetColLabelValue(col, False)
       
   125             if colname == "Name":
       
   126                 self.old_value = self.data[row][colname]
       
   127             elif colname == "Class":
       
   128                 value = VARIABLE_CLASSES_DICT[value]
       
   129                 self.SetValueByName(row, "Option", CheckOptionForClass[value](self.GetValueByName(row, "Option")))
       
   130                 if value == "External":
       
   131                     self.SetValueByName(row, "Initial Value", "")
       
   132             elif colname == "Option":
       
   133                 value = OPTIONS_DICT[value]
       
   134             self.data[row][colname] = value
       
   135 
       
   136     def GetOldValue(self):
       
   137         return self.old_value
       
   138 
       
   139     def _updateColAttrs(self, grid):
       
   140         """
       
   141         wx.grid.Grid -> update the column attributes to add the
       
   142         appropriate renderer given the column name.
       
   143 
       
   144         Otherwise default to the default renderer.
       
   145         """
       
   146         for row in range(self.GetNumberRows()):
       
   147             var_class = self.GetValueByName(row, "Class")
       
   148             var_type = self.GetValueByName(row, "Type")
       
   149             row_highlights = self.Highlights.get(row, {})
       
   150             for col in range(self.GetNumberCols()):
       
   151                 editor = None
       
   152                 renderer = None
       
   153                 colname = self.GetColLabelValue(col, False)
       
   154                 if self.Parent.Debug:
       
   155                     grid.SetReadOnly(row, col, True)
       
   156                 else:
       
   157                     if colname == "Option":
       
   158                         options = GetOptions(constant = var_class in ["Local", "External", "Global"],
       
   159                                              retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"],
       
   160                                              non_retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"])
       
   161                         if len(options) > 1:
       
   162                             editor = wx.grid.GridCellChoiceEditor()
       
   163                             editor.SetParameters(",".join(map(_, options)))
       
   164                         else:
       
   165                             grid.SetReadOnly(row, col, True)
       
   166                     elif col != 0 and self.GetValueByName(row, "Edit"):
       
   167                         grid.SetReadOnly(row, col, False)
       
   168                         if colname == "Name":
       
   169                             if self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
       
   170                                 grid.SetReadOnly(row, col, True)
       
   171                             else:
       
   172                                 editor = wx.grid.GridCellTextEditor()
       
   173                                 renderer = wx.grid.GridCellStringRenderer()
       
   174                         elif colname == "Initial Value":
       
   175                             if var_class not in ["External", "InOut"]:
       
   176                                 if self.Parent.Controler.IsEnumeratedType(var_type):
       
   177                                     editor = wx.grid.GridCellChoiceEditor()
       
   178                                     editor.SetParameters(",".join(self.Parent.Controler.GetEnumeratedDataValues(var_type)))
       
   179                                 else:
       
   180                                     editor = wx.grid.GridCellTextEditor()
       
   181                                 renderer = wx.grid.GridCellStringRenderer()
       
   182                             else:
       
   183                                 grid.SetReadOnly(row, col, True)
       
   184                         elif colname == "Location":
       
   185                             if var_class in ["Local", "Global"] and self.Parent.Controler.IsLocatableType(var_type):
       
   186                                 editor = LocationCellEditor(self, self.Parent.Controler)
       
   187                                 renderer = wx.grid.GridCellStringRenderer()
       
   188                             else:
       
   189                                 grid.SetReadOnly(row, col, True)
       
   190                         elif colname == "Class":
       
   191                             if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
       
   192                                 grid.SetReadOnly(row, col, True)
       
   193                             else:
       
   194                                 editor = wx.grid.GridCellChoiceEditor()
       
   195                                 excluded = []
       
   196                                 if self.Parent.PouIsUsed:
       
   197                                     excluded.extend(["Input","Output","InOut"])
       
   198                                 if self.Parent.IsFunctionBlockType(var_type):
       
   199                                     excluded.extend(["Local","Temp"])
       
   200                                 editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
       
   201                     elif colname != "Documentation":
       
   202                         grid.SetReadOnly(row, col, True)
       
   203                 
       
   204                 grid.SetCellEditor(row, col, editor)
       
   205                 grid.SetCellRenderer(row, col, renderer)
       
   206                 
       
   207                 if colname == "Location" and LOCATION_MODEL.match(self.GetValueByName(row, "Location")) is None:
       
   208                     highlight_colours = ERROR_HIGHLIGHT
       
   209                 else:
       
   210                     highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
       
   211                 grid.SetCellBackgroundColour(row, col, highlight_colours[0])
       
   212                 grid.SetCellTextColour(row, col, highlight_colours[1])
       
   213             self.ResizeRow(grid, row)
       
   214 
       
   215 #-------------------------------------------------------------------------------
       
   216 #                         Variable Panel Drop Target
       
   217 #-------------------------------------------------------------------------------   
       
   218 
       
   219 class VariableDropTarget(wx.TextDropTarget):
       
   220     '''
       
   221     This allows dragging a variable location from somewhere to the Location
       
   222     column of a variable row.
       
   223     
       
   224     The drag source should be a TextDataObject containing a Python tuple like:
       
   225         ('%ID0.0.0', 'location', 'REAL')
       
   226     
       
   227     c_ext/CFileEditor.py has an example of this (you can drag a C extension
       
   228     variable to the Location column of the variable panel).
       
   229     '''
       
   230     def __init__(self, parent):
       
   231         wx.TextDropTarget.__init__(self)
       
   232         self.ParentWindow = parent
       
   233     
       
   234     def OnDropText(self, x, y, data):
       
   235         self.ParentWindow.ParentWindow.Select()
       
   236         x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y)
       
   237         col = self.ParentWindow.VariablesGrid.XToCol(x)
       
   238         row = self.ParentWindow.VariablesGrid.YToRow(y - self.ParentWindow.VariablesGrid.GetColLabelSize())
       
   239         message = None
       
   240         element_type = self.ParentWindow.ElementType
       
   241         try:
       
   242             values = eval(data)    
       
   243         except:
       
   244             message = _("Invalid value \"%s\" for variable grid element")%data
       
   245             values = None
       
   246         if not isinstance(values, TupleType):
       
   247             message = _("Invalid value \"%s\" for variable grid element")%data
       
   248             values = None
       
   249         if values is not None:
       
   250             if col != wx.NOT_FOUND and row != wx.NOT_FOUND:
       
   251                 colname = self.ParentWindow.Table.GetColLabelValue(col, False)
       
   252                 if colname == "Location" and values[1] == "location":
       
   253                     if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
       
   254                         message = _("Can't give a location to a function block instance")
       
   255                     elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]:
       
   256                         message = _("Can only give a location to local or global variables")
       
   257                     else:
       
   258                         location = values[0]
       
   259                         variable_type = self.ParentWindow.Table.GetValueByName(row, "Type")
       
   260                         base_type = self.ParentWindow.Controler.GetBaseType(variable_type)
       
   261                         if location.startswith("%"):
       
   262                             if base_type != values[2]:
       
   263                                 message = _("Incompatible data types between \"%s\" and \"%s\"")%(values[2], variable_type)
       
   264                             else:
       
   265                                 self.ParentWindow.Table.SetValue(row, col, location)
       
   266                                 self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
       
   267                                 self.ParentWindow.SaveValues()
       
   268                         else:
       
   269                             if location[0].isdigit() and base_type != "BOOL":
       
   270                                 message = _("Incompatible size of data between \"%s\" and \"BOOL\"")%location
       
   271                             elif location[0] not in LOCATIONDATATYPES:
       
   272                                 message = _("Unrecognized data size \"%s\"")%location[0]
       
   273                             elif base_type not in LOCATIONDATATYPES[location[0]]:
       
   274                                 message = _("Incompatible size of data between \"%s\" and \"%s\"")%(location, variable_type)
       
   275                             else:
       
   276                                 dialog = wx.SingleChoiceDialog(self.ParentWindow.ParentWindow.ParentWindow, 
       
   277                                       _("Select a variable class:"), _("Variable class"), 
       
   278                                       ["Input", "Output", "Memory"], 
       
   279                                       wx.DEFAULT_DIALOG_STYLE|wx.OK|wx.CANCEL)
       
   280                                 if dialog.ShowModal() == wx.ID_OK:
       
   281                                     selected = dialog.GetSelection()
       
   282                                 else:
       
   283                                     selected = None
       
   284                                 dialog.Destroy()
       
   285                                 if selected is None:
       
   286                                     return
       
   287                                 if selected == 0:
       
   288                                     location = "%I" + location
       
   289                                 elif selected == 1:
       
   290                                     location = "%Q" + location
       
   291                                 else:
       
   292                                     location = "%M" + location
       
   293                                 self.ParentWindow.Table.SetValue(row, col, location)
       
   294                                 self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
       
   295                                 self.ParentWindow.SaveValues()
       
   296                 elif colname == "Initial Value" and values[1] == "Constant":
       
   297                     if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
       
   298                         message = _("Can't set an initial value to a function block instance")
       
   299                     else:
       
   300                         self.ParentWindow.Table.SetValue(row, col, values[0])
       
   301                         self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
       
   302                         self.ParentWindow.SaveValues()
       
   303             elif (element_type not in ["config", "resource"] and values[1] == "Global" and self.ParentWindow.Filter in ["All", "Interface", "External"] or
       
   304                   element_type in ["config", "resource"] and values[1] == "location"):
       
   305                 if values[1] == "location":
       
   306                     var_name = values[3]
       
   307                 else:
       
   308                     var_name = values[0]
       
   309                 tagname = self.ParentWindow.GetTagName()
       
   310                 if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
       
   311                     message = _("\"%s\" pou already exists!")%var_name
       
   312                 elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
       
   313                     var_infos = self.ParentWindow.DefaultValue.copy()
       
   314                     var_infos["Name"] = var_name
       
   315                     var_infos["Type"] = values[2]
       
   316                     if values[1] == "location":
       
   317                         var_infos["Class"] = "Global"
       
   318                         var_infos["Location"] = values[0]
       
   319                     else:
       
   320                         var_infos["Class"] = "External"
       
   321                     var_infos["Number"] = len(self.ParentWindow.Values)
       
   322                     self.ParentWindow.Values.append(var_infos)
       
   323                     self.ParentWindow.SaveValues()
       
   324                     self.ParentWindow.RefreshValues()
       
   325         
       
   326         if message is not None:
       
   327             wx.CallAfter(self.ShowMessage, message)
       
   328     
       
   329     def ShowMessage(self, message):
       
   330         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   331         message.ShowModal()
       
   332         message.Destroy()
       
   333 
       
   334 #-------------------------------------------------------------------------------
       
   335 #                               Variable Panel
       
   336 #-------------------------------------------------------------------------------   
       
   337 
       
   338 class VariablePanel(wx.Panel):
       
   339     
       
   340     def __init__(self, parent, window, controler, element_type, debug=False):
       
   341         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
       
   342         
       
   343         self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
       
   344         self.MainSizer.AddGrowableCol(0)
       
   345         self.MainSizer.AddGrowableRow(1)
       
   346         
       
   347         controls_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5)
       
   348         controls_sizer.AddGrowableCol(5)
       
   349         controls_sizer.AddGrowableRow(0)
       
   350         self.MainSizer.AddSizer(controls_sizer, border=5, flag=wx.GROW|wx.ALL)
       
   351         
       
   352         self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:'))
       
   353         controls_sizer.AddWindow(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)
       
   354         
       
   355         self.ReturnType = wx.ComboBox(self,
       
   356               size=wx.Size(145, -1), style=wx.CB_READONLY)
       
   357         self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType)
       
   358         controls_sizer.AddWindow(self.ReturnType)
       
   359         
       
   360         self.DescriptionLabel = wx.StaticText(self, label=_('Description:'))
       
   361         controls_sizer.AddWindow(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)
       
   362         
       
   363         self.Description = wx.TextCtrl(self,
       
   364               size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER)
       
   365         self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description)
       
   366         self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged)
       
   367         controls_sizer.AddWindow(self.Description) 
       
   368         
       
   369         class_filter_label = wx.StaticText(self, label=_('Class Filter:'))
       
   370         controls_sizer.AddWindow(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)
       
   371         
       
   372         self.ClassFilter = wx.ComboBox(self, 
       
   373               size=wx.Size(145, -1), style=wx.CB_READONLY)
       
   374         self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter)
       
   375         controls_sizer.AddWindow(self.ClassFilter)
       
   376         
       
   377         for name, bitmap, help in [
       
   378                 ("AddButton", "add_element", _("Add variable")),
       
   379                 ("DeleteButton", "remove_element", _("Remove variable")),
       
   380                 ("UpButton", "up", _("Move variable up")),
       
   381                 ("DownButton", "down", _("Move variable down"))]:
       
   382             button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), 
       
   383                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   384             button.SetToolTipString(help)
       
   385             setattr(self, name, button)
       
   386             controls_sizer.AddWindow(button)
       
   387         
       
   388         self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL)
       
   389         self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
       
   390         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, 
       
   391               self.OnVariablesGridCellChange)
       
   392         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
       
   393               self.OnVariablesGridCellLeftClick)
       
   394         self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, 
       
   395               self.OnVariablesGridEditorShown)
       
   396         self.MainSizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
       
   397         
       
   398         self.SetSizer(self.MainSizer)
       
   399         
       
   400         self.ParentWindow = window
       
   401         self.Controler = controler
       
   402         self.ElementType = element_type
       
   403         self.Debug = debug
       
   404         
       
   405         self.RefreshHighlightsTimer = wx.Timer(self, -1)
       
   406         self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, 
       
   407               self.RefreshHighlightsTimer)
       
   408         
       
   409         self.Filter = "All"
       
   410         self.FilterChoices = []
       
   411         self.FilterChoiceTransfer = GetFilterChoiceTransfer()
       
   412         
       
   413         self.DefaultValue = {
       
   414              "Name" : "", 
       
   415              "Class" : "", 
       
   416              "Type" : "INT", 
       
   417              "Location" : "",
       
   418               "Initial Value" : "", 
       
   419               "Option" : "",
       
   420               "Documentation" : "", 
       
   421               "Edit" : True
       
   422         }
       
   423 
       
   424         if element_type in ["config", "resource"]:
       
   425             self.DefaultTypes = {"All" : "Global"}
       
   426         else:
       
   427             self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"}
       
   428 
       
   429         if element_type in ["config", "resource"] \
       
   430         or element_type in ["program", "transition", "action"]:
       
   431             # this is an element that can have located variables
       
   432             self.Table = VariableTable(self, [], GetVariableTableColnames(True))
       
   433 
       
   434             if element_type in ["config", "resource"]:
       
   435                 self.FilterChoices = ["All", "Global"]#,"Access"]
       
   436             else:
       
   437                 self.FilterChoices = ["All",
       
   438                                         "Interface", "   Input", "   Output", "   InOut", "   External",
       
   439                                         "Variables", "   Local", "   Temp"]#,"Access"]
       
   440 
       
   441             # these condense the ColAlignements list
       
   442             l = wx.ALIGN_LEFT
       
   443             c = wx.ALIGN_CENTER 
       
   444 
       
   445             #                      Num  Name    Class   Type    Loc     Init    Option   Doc
       
   446             self.ColSizes       = [40,  80,     70,     80,     80,     80,     100,     80]
       
   447             self.ColAlignements = [c,   l,      l,      l,      l,      l,      l,       l]
       
   448 
       
   449         else:
       
   450             # this is an element that cannot have located variables
       
   451             self.Table = VariableTable(self, [], GetVariableTableColnames(False))
       
   452 
       
   453             if element_type == "function":
       
   454                 self.FilterChoices = ["All",
       
   455                                         "Interface", "   Input", "   Output", "   InOut",
       
   456                                         "Variables", "   Local"]
       
   457             else:
       
   458                 self.FilterChoices = ["All",
       
   459                                         "Interface", "   Input", "   Output", "   InOut", "   External",
       
   460                                         "Variables", "   Local", "   Temp"]
       
   461 
       
   462             # these condense the ColAlignements list
       
   463             l = wx.ALIGN_LEFT
       
   464             c = wx.ALIGN_CENTER 
       
   465 
       
   466             #                      Num  Name    Class   Type    Init    Option   Doc
       
   467             self.ColSizes       = [40,  80,     70,     80,     80,     100,     160]
       
   468             self.ColAlignements = [c,   l,      l,      l,      l,      l,       l]
       
   469 
       
   470         for choice in self.FilterChoices:
       
   471             self.ClassFilter.Append(_(choice))
       
   472 
       
   473         reverse_transfer = {}
       
   474         for filter, choice in self.FilterChoiceTransfer.items():
       
   475             reverse_transfer[choice] = filter
       
   476         self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter]))
       
   477         self.RefreshTypeList()
       
   478 
       
   479         self.VariablesGrid.SetTable(self.Table)
       
   480         self.VariablesGrid.SetButtons({"Add": self.AddButton,
       
   481                                        "Delete": self.DeleteButton,
       
   482                                        "Up": self.UpButton,
       
   483                                        "Down": self.DownButton})
       
   484         self.VariablesGrid.SetEditable(not self.Debug)
       
   485         
       
   486         def _AddVariable(new_row):
       
   487             if not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]:
       
   488                 row_content = self.DefaultValue.copy()
       
   489                 if self.Filter in self.DefaultTypes:
       
   490                     row_content["Class"] = self.DefaultTypes[self.Filter]
       
   491                 else:
       
   492                     row_content["Class"] = self.Filter
       
   493                 if self.Filter == "All" and len(self.Values) > 0:
       
   494                     self.Values.insert(new_row, row_content)
       
   495                 else:
       
   496                     self.Values.append(row_content)
       
   497                     new_row = self.Table.GetNumberRows()
       
   498                 self.SaveValues()
       
   499                 self.RefreshValues()
       
   500                 return new_row
       
   501             return self.VariablesGrid.GetGridCursorRow()
       
   502         setattr(self.VariablesGrid, "_AddRow", _AddVariable)
       
   503         
       
   504         def _DeleteVariable(row):
       
   505             if (self.Table.GetValueByName(row, "Edit") and 
       
   506                 (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
       
   507                 self.Values.remove(self.Table.GetRow(row))
       
   508                 self.SaveValues()
       
   509                 self.RefreshValues()
       
   510         setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
       
   511             
       
   512         def _MoveVariable(row, move):
       
   513             if (self.Filter == "All" and 
       
   514                 (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
       
   515                 new_row = max(0, min(row + move, len(self.Values) - 1))
       
   516                 if new_row != row:
       
   517                     self.Values.insert(new_row, self.Values.pop(row))
       
   518                     self.SaveValues()
       
   519                     self.RefreshValues()
       
   520                 return new_row
       
   521             return row
       
   522         setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
       
   523         
       
   524         def _RefreshButtons():
       
   525             if self:
       
   526                 table_length = len(self.Table.data)
       
   527                 row_class = None
       
   528                 row_edit = True
       
   529                 row = 0
       
   530                 if table_length > 0:
       
   531                     row = self.VariablesGrid.GetGridCursorRow()
       
   532                     row_edit = self.Table.GetValueByName(row, "Edit")
       
   533                     if self.PouIsUsed:
       
   534                         row_class = self.Table.GetValueByName(row, "Class")
       
   535                 self.AddButton.Enable(not self.Debug and (not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]))
       
   536                 self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"]))
       
   537                 self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
       
   538                 self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
       
   539         setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons)
       
   540         
       
   541         self.VariablesGrid.SetRowLabelSize(0)
       
   542         for col in range(self.Table.GetNumberCols()):
       
   543             attr = wx.grid.GridCellAttr()
       
   544             attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
       
   545             self.VariablesGrid.SetColAttr(col, attr)
       
   546             self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col])
       
   547             self.VariablesGrid.AutoSizeColumn(col, False)
       
   548     
       
   549     def __del__(self):
       
   550         self.RefreshHighlightsTimer.Stop()
       
   551     
       
   552     def SetTagName(self, tagname):
       
   553         self.TagName = tagname
       
   554     
       
   555     def GetTagName(self):
       
   556         return self.TagName
       
   557     
       
   558     def IsFunctionBlockType(self, name):
       
   559         bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
       
   560         pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
       
   561         if poutype != "function" and bodytype in ["ST", "IL"]:
       
   562             return False
       
   563         else:
       
   564             return name in self.Controler.GetFunctionBlockTypes(self.TagName)
       
   565     
       
   566     def RefreshView(self):
       
   567         self.PouNames = self.Controler.GetProjectPouNames(self.Debug)
       
   568         returnType = None
       
   569         description = None
       
   570         
       
   571         words = self.TagName.split("::")
       
   572         if self.ElementType == "config":
       
   573             self.PouIsUsed = False
       
   574             self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
       
   575         elif self.ElementType == "resource":
       
   576             self.PouIsUsed = False
       
   577             self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
       
   578         else:
       
   579             if self.ElementType == "function":
       
   580                 self.ReturnType.Clear()
       
   581                 for base_type in self.Controler.GetDataTypes(self.TagName, True, debug=self.Debug):
       
   582                     self.ReturnType.Append(base_type)
       
   583                 returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
       
   584             description = self.Controler.GetPouDescription(words[1])
       
   585             self.PouIsUsed = self.Controler.PouIsUsed(words[1])
       
   586             self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
       
   587         
       
   588         if returnType is not None:
       
   589             self.ReturnType.SetStringSelection(returnType)
       
   590             self.ReturnType.Enable(not self.Debug)
       
   591             self.ReturnTypeLabel.Show()
       
   592             self.ReturnType.Show()
       
   593         else:
       
   594             self.ReturnType.Enable(False)
       
   595             self.ReturnTypeLabel.Hide()
       
   596             self.ReturnType.Hide()
       
   597         
       
   598         if description is not None:
       
   599             self.Description.SetValue(description)
       
   600             self.Description.Enable(not self.Debug)
       
   601             self.DescriptionLabel.Show()
       
   602             self.Description.Show()
       
   603         else:
       
   604             self.Description.Enable(False)
       
   605             self.DescriptionLabel.Hide()
       
   606             self.Description.Hide()
       
   607         
       
   608         self.RefreshValues()
       
   609         self.VariablesGrid.RefreshButtons()
       
   610         self.MainSizer.Layout()
       
   611     
       
   612     def OnReturnTypeChanged(self, event):
       
   613         words = self.TagName.split("::")
       
   614         self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
       
   615         self.Controler.BufferProject()
       
   616         self.ParentWindow.RefreshView(variablepanel = False)
       
   617         self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   618         event.Skip()
       
   619     
       
   620     def OnDescriptionChanged(self, event):
       
   621         words = self.TagName.split("::")
       
   622         old_description = self.Controler.GetPouDescription(words[1])
       
   623         new_description = self.Description.GetValue()
       
   624         if new_description != old_description:
       
   625             self.Controler.SetPouDescription(words[1], new_description)
       
   626             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   627         event.Skip()
       
   628     
       
   629     def OnClassFilter(self, event):
       
   630         self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]]
       
   631         self.RefreshTypeList()
       
   632         self.RefreshValues()
       
   633         self.VariablesGrid.RefreshButtons()
       
   634         event.Skip()
       
   635 
       
   636     def RefreshTypeList(self):
       
   637         if self.Filter == "All":
       
   638             self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[choice] not in ["All","Interface","Variables"]]
       
   639         elif self.Filter == "Interface":
       
   640             self.ClassList = ["Input","Output","InOut","External"]
       
   641         elif self.Filter == "Variables":
       
   642             self.ClassList = ["Local","Temp"]
       
   643         else:
       
   644             self.ClassList = [self.Filter]
       
   645 
       
   646     def OnVariablesGridCellChange(self, event):
       
   647         row, col = event.GetRow(), event.GetCol()
       
   648         colname = self.Table.GetColLabelValue(col, False)
       
   649         value = self.Table.GetValue(row, col)
       
   650         message = None
       
   651         
       
   652         if colname == "Name" and value != "":
       
   653             if not TestIdentifier(value):
       
   654                 message = _("\"%s\" is not a valid identifier!") % value
       
   655             elif value.upper() in IEC_KEYWORDS:
       
   656                 message = _("\"%s\" is a keyword. It can't be used!") % value
       
   657             elif value.upper() in self.PouNames:
       
   658                 message = _("A POU named \"%s\" already exists!") % value
       
   659             elif value.upper() in [var["Name"].upper() for var in self.Values if var != self.Table.data[row]]:
       
   660                 message = _("A variable with \"%s\" as name already exists in this pou!") % value
       
   661             else:
       
   662                 self.SaveValues(False)
       
   663                 old_value = self.Table.GetOldValue()
       
   664                 if old_value != "":
       
   665                     self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
       
   666                 self.Controler.BufferProject()
       
   667                 self.ParentWindow.RefreshView(variablepanel = False)
       
   668                 self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   669                 event.Skip()
       
   670         else:
       
   671             self.SaveValues()
       
   672             if colname == "Class":
       
   673                 self.ParentWindow.RefreshView(variablepanel = False)
       
   674             elif colname == "Location":
       
   675                 wx.CallAfter(self.ParentWindow.RefreshView)
       
   676             
       
   677         if message is not None:
       
   678             dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   679             dialog.ShowModal()
       
   680             dialog.Destroy()
       
   681             event.Veto()
       
   682         else:
       
   683             event.Skip()
       
   684     
       
   685     def OnVariablesGridEditorShown(self, event):
       
   686         row, col = event.GetRow(), event.GetCol() 
       
   687 
       
   688         label_value = self.Table.GetColLabelValue(col, False)
       
   689         if label_value == "Type":
       
   690             type_menu = wx.Menu(title='')   # the root menu
       
   691 
       
   692             # build a submenu containing standard IEC types
       
   693             base_menu = wx.Menu(title='')
       
   694             for base_type in self.Controler.GetBaseTypes():
       
   695                 new_id = wx.NewId()
       
   696                 AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
       
   697                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
       
   698 
       
   699             type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
       
   700 
       
   701             # build a submenu containing user-defined types
       
   702             datatype_menu = wx.Menu(title='')
       
   703             datatypes = self.Controler.GetDataTypes(basetypes = False)
       
   704             for datatype in datatypes:
       
   705                 new_id = wx.NewId()
       
   706                 AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
       
   707                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
       
   708 
       
   709             type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
       
   710             
       
   711             for category in self.Controler.GetConfNodeDataTypes():
       
   712                
       
   713                if len(category["list"]) > 0:
       
   714                    # build a submenu containing confnode types
       
   715                    confnode_datatype_menu = wx.Menu(title='')
       
   716                    for datatype in category["list"]:
       
   717                        new_id = wx.NewId()
       
   718                        AppendMenu(confnode_datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
       
   719                        self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
       
   720                    
       
   721                    type_menu.AppendMenu(wx.NewId(), category["name"], confnode_datatype_menu)
       
   722 
       
   723             # build a submenu containing function block types
       
   724             bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
       
   725             pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
       
   726             classtype = self.Table.GetValueByName(row, "Class")
       
   727             
       
   728             new_id = wx.NewId()
       
   729             AppendMenu(type_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Array"))
       
   730             self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id)
       
   731             
       
   732             if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
       
   733             poutype != "function" and bodytype in ["ST", "IL"]:
       
   734                 functionblock_menu = wx.Menu(title='')
       
   735                 fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
       
   736                 for functionblock_type in fbtypes:
       
   737                     new_id = wx.NewId()
       
   738                     AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
       
   739                     self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
       
   740 
       
   741                 type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
       
   742 
       
   743             rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
       
   744             corner_x = rect.x + rect.width
       
   745             corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
       
   746 
       
   747             # pop up this new menu
       
   748             self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
       
   749             type_menu.Destroy()
       
   750             event.Veto()
       
   751         else:
       
   752             event.Skip()
       
   753     
       
   754     def GetVariableTypeFunction(self, base_type):
       
   755         def VariableTypeFunction(event):
       
   756             row = self.VariablesGrid.GetGridCursorRow()
       
   757             self.Table.SetValueByName(row, "Type", base_type)
       
   758             self.Table.ResetView(self.VariablesGrid)
       
   759             self.SaveValues(False)
       
   760             self.ParentWindow.RefreshView(variablepanel = False)
       
   761             self.Controler.BufferProject()
       
   762             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   763         return VariableTypeFunction
       
   764     
       
   765     def VariableArrayTypeFunction(self, event):
       
   766         row = self.VariablesGrid.GetGridCursorRow()
       
   767         dialog = ArrayTypeDialog(self, 
       
   768                                  self.Controler.GetDataTypes(self.TagName), 
       
   769                                  self.Table.GetValueByName(row, "Type"))
       
   770         if dialog.ShowModal() == wx.ID_OK:
       
   771             self.Table.SetValueByName(row, "Type", dialog.GetValue())
       
   772             self.Table.ResetView(self.VariablesGrid)
       
   773             self.SaveValues(False)
       
   774             self.ParentWindow.RefreshView(variablepanel = False)
       
   775             self.Controler.BufferProject()
       
   776             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   777         dialog.Destroy()
       
   778     
       
   779     def OnVariablesGridCellLeftClick(self, event):
       
   780         row = event.GetRow()
       
   781         if not self.Debug and (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 RefreshValues(self):
       
   793         data = []
       
   794         for num, variable in enumerate(self.Values):
       
   795             if variable["Class"] in self.ClassList:
       
   796                 variable["Number"] = num + 1
       
   797                 data.append(variable)
       
   798         self.Table.SetData(data)
       
   799         self.Table.ResetView(self.VariablesGrid)
       
   800             
       
   801     def SaveValues(self, buffer = True):
       
   802         words = self.TagName.split("::")
       
   803         if self.ElementType == "config":
       
   804             self.Controler.SetConfigurationGlobalVars(words[1], self.Values)
       
   805         elif self.ElementType == "resource":
       
   806             self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values)
       
   807         else:
       
   808             if self.ReturnType.IsEnabled():
       
   809                 self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
       
   810             self.Controler.SetPouInterfaceVars(words[1], self.Values)
       
   811         if buffer:
       
   812             self.Controler.BufferProject()
       
   813             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)            
       
   814 
       
   815 #-------------------------------------------------------------------------------
       
   816 #                        Highlights showing functions
       
   817 #-------------------------------------------------------------------------------
       
   818 
       
   819     def OnRefreshHighlightsTimer(self, event):
       
   820         self.Table.ResetView(self.VariablesGrid)
       
   821         event.Skip()
       
   822 
       
   823     def AddVariableHighlight(self, infos, highlight_type):
       
   824         if isinstance(infos[0], TupleType):
       
   825             for i in xrange(*infos[0]):
       
   826                 self.Table.AddHighlight((i,) + infos[1:], highlight_type)
       
   827             cell_visible = infos[0][0]
       
   828         else:
       
   829             self.Table.AddHighlight(infos, highlight_type)
       
   830             cell_visible = infos[0]
       
   831         colnames = [colname.lower() for colname in self.Table.colnames]
       
   832         self.VariablesGrid.MakeCellVisible(cell_visible, colnames.index(infos[1]))
       
   833         self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
       
   834 
       
   835     def RemoveVariableHighlight(self, infos, highlight_type):
       
   836         if isinstance(infos[0], TupleType):
       
   837             for i in xrange(*infos[0]):
       
   838                 self.Table.RemoveHighlight((i,) + infos[1:], highlight_type)
       
   839         else:
       
   840             self.Table.RemoveHighlight(infos, highlight_type)
       
   841         self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
       
   842 
       
   843     def ClearHighlights(self, highlight_type=None):
       
   844         self.Table.ClearHighlights(highlight_type)
       
   845         self.Table.ResetView(self.VariablesGrid)