controls/VariablePanel.py
branch1.1 Korean release
changeset 968 eee7625de1f7
parent 894 a4919f228924
child 1009 741fbce41ec2
equal deleted inserted replaced
808:6e205c1f05a0 968:eee7625de1f7
       
     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, colname)) 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                         
       
   262                         if values[2] is not None:
       
   263                             base_location_type = self.ParentWindow.Controler.GetBaseType(values[2])
       
   264                             if values[2] != variable_type and base_type != base_location_type:
       
   265                                 message = _("Incompatible data types between \"%s\" and \"%s\"")%(values[2], variable_type)
       
   266                         
       
   267                         if message is None:
       
   268                             if not location.startswith("%"):
       
   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                                         
       
   294                             if message is None:
       
   295                                 self.ParentWindow.Table.SetValue(row, col, location)
       
   296                                 self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
       
   297                                 self.ParentWindow.SaveValues()
       
   298                 elif colname == "Initial Value" and values[1] == "Constant":
       
   299                     if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
       
   300                         message = _("Can't set an initial value to a function block instance")
       
   301                     else:
       
   302                         self.ParentWindow.Table.SetValue(row, col, values[0])
       
   303                         self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
       
   304                         self.ParentWindow.SaveValues()
       
   305             elif (element_type not in ["config", "resource"] and values[1] == "Global" and self.ParentWindow.Filter in ["All", "Interface", "External"] or
       
   306                   element_type in ["config", "resource"] and values[1] == "location"):
       
   307                 if values[1] == "location":
       
   308                     var_name = values[3]
       
   309                 else:
       
   310                     var_name = values[0]
       
   311                 tagname = self.ParentWindow.GetTagName()
       
   312                 if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
       
   313                     message = _("\"%s\" pou already exists!")%var_name
       
   314                 elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
       
   315                     var_infos = self.ParentWindow.DefaultValue.copy()
       
   316                     var_infos["Name"] = var_name
       
   317                     var_infos["Type"] = values[2]
       
   318                     if values[1] == "location":
       
   319                         var_infos["Class"] = "Global"
       
   320                         var_infos["Location"] = values[0]
       
   321                     else:
       
   322                         var_infos["Class"] = "External"
       
   323                     var_infos["Number"] = len(self.ParentWindow.Values)
       
   324                     self.ParentWindow.Values.append(var_infos)
       
   325                     self.ParentWindow.SaveValues()
       
   326                     self.ParentWindow.RefreshValues()
       
   327         
       
   328         if message is not None:
       
   329             wx.CallAfter(self.ShowMessage, message)
       
   330     
       
   331     def ShowMessage(self, message):
       
   332         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   333         message.ShowModal()
       
   334         message.Destroy()
       
   335 
       
   336 #-------------------------------------------------------------------------------
       
   337 #                               Variable Panel
       
   338 #-------------------------------------------------------------------------------   
       
   339 
       
   340 class VariablePanel(wx.Panel):
       
   341     
       
   342     def __init__(self, parent, window, controler, element_type, debug=False):
       
   343         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
       
   344         
       
   345         self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
       
   346         self.MainSizer.AddGrowableCol(0)
       
   347         self.MainSizer.AddGrowableRow(1)
       
   348         
       
   349         controls_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5)
       
   350         controls_sizer.AddGrowableCol(5)
       
   351         controls_sizer.AddGrowableRow(0)
       
   352         self.MainSizer.AddSizer(controls_sizer, border=5, flag=wx.GROW|wx.ALL)
       
   353         
       
   354         self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:'))
       
   355         controls_sizer.AddWindow(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)
       
   356         
       
   357         self.ReturnType = wx.ComboBox(self,
       
   358               size=wx.Size(145, -1), style=wx.CB_READONLY)
       
   359         self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType)
       
   360         controls_sizer.AddWindow(self.ReturnType)
       
   361         
       
   362         self.DescriptionLabel = wx.StaticText(self, label=_('Description:'))
       
   363         controls_sizer.AddWindow(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)
       
   364         
       
   365         self.Description = wx.TextCtrl(self,
       
   366               size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER)
       
   367         self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description)
       
   368         self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged)
       
   369         controls_sizer.AddWindow(self.Description) 
       
   370         
       
   371         class_filter_label = wx.StaticText(self, label=_('Class Filter:'))
       
   372         controls_sizer.AddWindow(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)
       
   373         
       
   374         self.ClassFilter = wx.ComboBox(self, 
       
   375               size=wx.Size(145, -1), style=wx.CB_READONLY)
       
   376         self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter)
       
   377         controls_sizer.AddWindow(self.ClassFilter)
       
   378         
       
   379         for name, bitmap, help in [
       
   380                 ("AddButton", "add_element", _("Add variable")),
       
   381                 ("DeleteButton", "remove_element", _("Remove variable")),
       
   382                 ("UpButton", "up", _("Move variable up")),
       
   383                 ("DownButton", "down", _("Move variable down"))]:
       
   384             button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), 
       
   385                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   386             button.SetToolTipString(help)
       
   387             setattr(self, name, button)
       
   388             controls_sizer.AddWindow(button)
       
   389         
       
   390         self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL)
       
   391         self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
       
   392         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, 
       
   393               self.OnVariablesGridCellChange)
       
   394         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
       
   395               self.OnVariablesGridCellLeftClick)
       
   396         self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, 
       
   397               self.OnVariablesGridEditorShown)
       
   398         self.MainSizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
       
   399         
       
   400         self.SetSizer(self.MainSizer)
       
   401         
       
   402         self.ParentWindow = window
       
   403         self.Controler = controler
       
   404         self.ElementType = element_type
       
   405         self.Debug = debug
       
   406         
       
   407         self.RefreshHighlightsTimer = wx.Timer(self, -1)
       
   408         self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, 
       
   409               self.RefreshHighlightsTimer)
       
   410         
       
   411         self.Filter = "All"
       
   412         self.FilterChoices = []
       
   413         self.FilterChoiceTransfer = GetFilterChoiceTransfer()
       
   414         
       
   415         self.DefaultValue = {
       
   416              "Name" : "", 
       
   417              "Class" : "", 
       
   418              "Type" : "INT", 
       
   419              "Location" : "",
       
   420              "Initial Value" : "", 
       
   421              "Option" : "",
       
   422              "Documentation" : "", 
       
   423              "Edit" : True
       
   424         }
       
   425 
       
   426         if element_type in ["config", "resource"]:
       
   427             self.DefaultTypes = {"All" : "Global"}
       
   428         else:
       
   429             self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"}
       
   430 
       
   431         if element_type in ["config", "resource"] \
       
   432         or element_type in ["program", "transition", "action"]:
       
   433             # this is an element that can have located variables
       
   434             self.Table = VariableTable(self, [], GetVariableTableColnames(True))
       
   435 
       
   436             if element_type in ["config", "resource"]:
       
   437                 self.FilterChoices = ["All", "Global"]#,"Access"]
       
   438             else:
       
   439                 self.FilterChoices = ["All",
       
   440                                         "Interface", "   Input", "   Output", "   InOut", "   External",
       
   441                                         "Variables", "   Local", "   Temp"]#,"Access"]
       
   442 
       
   443             # these condense the ColAlignements list
       
   444             l = wx.ALIGN_LEFT
       
   445             c = wx.ALIGN_CENTER 
       
   446 
       
   447             #                      Num  Name    Class   Type    Loc     Init    Option   Doc
       
   448             self.ColSizes       = [40,  80,     70,     80,     80,     80,     100,     80]
       
   449             self.ColAlignements = [c,   l,      l,      l,      l,      l,      l,       l]
       
   450 
       
   451         else:
       
   452             # this is an element that cannot have located variables
       
   453             self.Table = VariableTable(self, [], GetVariableTableColnames(False))
       
   454 
       
   455             if element_type == "function":
       
   456                 self.FilterChoices = ["All",
       
   457                                         "Interface", "   Input", "   Output", "   InOut",
       
   458                                         "Variables", "   Local"]
       
   459             else:
       
   460                 self.FilterChoices = ["All",
       
   461                                         "Interface", "   Input", "   Output", "   InOut", "   External",
       
   462                                         "Variables", "   Local", "   Temp"]
       
   463 
       
   464             # these condense the ColAlignements list
       
   465             l = wx.ALIGN_LEFT
       
   466             c = wx.ALIGN_CENTER 
       
   467 
       
   468             #                      Num  Name    Class   Type    Init    Option   Doc
       
   469             self.ColSizes       = [40,  80,     70,     80,     80,     100,     160]
       
   470             self.ColAlignements = [c,   l,      l,      l,      l,      l,       l]
       
   471 
       
   472         for choice in self.FilterChoices:
       
   473             self.ClassFilter.Append(_(choice))
       
   474 
       
   475         reverse_transfer = {}
       
   476         for filter, choice in self.FilterChoiceTransfer.items():
       
   477             reverse_transfer[choice] = filter
       
   478         self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter]))
       
   479         self.RefreshTypeList()
       
   480 
       
   481         self.VariablesGrid.SetTable(self.Table)
       
   482         self.VariablesGrid.SetButtons({"Add": self.AddButton,
       
   483                                        "Delete": self.DeleteButton,
       
   484                                        "Up": self.UpButton,
       
   485                                        "Down": self.DownButton})
       
   486         self.VariablesGrid.SetEditable(not self.Debug)
       
   487         
       
   488         def _AddVariable(new_row):
       
   489             if not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]:
       
   490                 row_content = self.DefaultValue.copy()
       
   491                 if self.Filter in self.DefaultTypes:
       
   492                     row_content["Class"] = self.DefaultTypes[self.Filter]
       
   493                 else:
       
   494                     row_content["Class"] = self.Filter
       
   495                 if self.Filter == "All" and len(self.Values) > 0:
       
   496                     self.Values.insert(new_row, row_content)
       
   497                 else:
       
   498                     self.Values.append(row_content)
       
   499                     new_row = self.Table.GetNumberRows()
       
   500                 self.SaveValues()
       
   501                 self.RefreshValues()
       
   502                 return new_row
       
   503             return self.VariablesGrid.GetGridCursorRow()
       
   504         setattr(self.VariablesGrid, "_AddRow", _AddVariable)
       
   505         
       
   506         def _DeleteVariable(row):
       
   507             if (self.Table.GetValueByName(row, "Edit") and 
       
   508                 (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
       
   509                 self.Values.remove(self.Table.GetRow(row))
       
   510                 self.SaveValues()
       
   511                 self.RefreshValues()
       
   512         setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
       
   513             
       
   514         def _MoveVariable(row, move):
       
   515             if (self.Filter == "All" and 
       
   516                 (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
       
   517                 new_row = max(0, min(row + move, len(self.Values) - 1))
       
   518                 if new_row != row:
       
   519                     self.Values.insert(new_row, self.Values.pop(row))
       
   520                     self.SaveValues()
       
   521                     self.RefreshValues()
       
   522                 return new_row
       
   523             return row
       
   524         setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
       
   525         
       
   526         def _RefreshButtons():
       
   527             if self:
       
   528                 table_length = len(self.Table.data)
       
   529                 row_class = None
       
   530                 row_edit = True
       
   531                 row = 0
       
   532                 if table_length > 0:
       
   533                     row = self.VariablesGrid.GetGridCursorRow()
       
   534                     row_edit = self.Table.GetValueByName(row, "Edit")
       
   535                     if self.PouIsUsed:
       
   536                         row_class = self.Table.GetValueByName(row, "Class")
       
   537                 self.AddButton.Enable(not self.Debug and (not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]))
       
   538                 self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"]))
       
   539                 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"]))
       
   540                 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"]))
       
   541         setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons)
       
   542         
       
   543         self.VariablesGrid.SetRowLabelSize(0)
       
   544         for col in range(self.Table.GetNumberCols()):
       
   545             attr = wx.grid.GridCellAttr()
       
   546             attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
       
   547             self.VariablesGrid.SetColAttr(col, attr)
       
   548             self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col])
       
   549             self.VariablesGrid.AutoSizeColumn(col, False)
       
   550     
       
   551     def __del__(self):
       
   552         self.RefreshHighlightsTimer.Stop()
       
   553     
       
   554     def SetTagName(self, tagname):
       
   555         self.TagName = tagname
       
   556     
       
   557     def GetTagName(self):
       
   558         return self.TagName
       
   559     
       
   560     def IsFunctionBlockType(self, name):
       
   561         bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
       
   562         pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
       
   563         if poutype != "function" and bodytype in ["ST", "IL"]:
       
   564             return False
       
   565         else:
       
   566             return name in self.Controler.GetFunctionBlockTypes(self.TagName)
       
   567     
       
   568     def RefreshView(self):
       
   569         self.PouNames = self.Controler.GetProjectPouNames(self.Debug)
       
   570         returnType = None
       
   571         description = None
       
   572         
       
   573         words = self.TagName.split("::")
       
   574         if self.ElementType == "config":
       
   575             self.PouIsUsed = False
       
   576             self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
       
   577         elif self.ElementType == "resource":
       
   578             self.PouIsUsed = False
       
   579             self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
       
   580         else:
       
   581             if self.ElementType == "function":
       
   582                 self.ReturnType.Clear()
       
   583                 for data_type in self.Controler.GetDataTypes(self.TagName, debug=self.Debug):
       
   584                     self.ReturnType.Append(data_type)
       
   585                 returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
       
   586             description = self.Controler.GetPouDescription(words[1])
       
   587             self.PouIsUsed = self.Controler.PouIsUsed(words[1])
       
   588             self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
       
   589         
       
   590         if returnType is not None:
       
   591             self.ReturnType.SetStringSelection(returnType)
       
   592             self.ReturnType.Enable(not self.Debug)
       
   593             self.ReturnTypeLabel.Show()
       
   594             self.ReturnType.Show()
       
   595         else:
       
   596             self.ReturnType.Enable(False)
       
   597             self.ReturnTypeLabel.Hide()
       
   598             self.ReturnType.Hide()
       
   599         
       
   600         if description is not None:
       
   601             self.Description.SetValue(description)
       
   602             self.Description.Enable(not self.Debug)
       
   603             self.DescriptionLabel.Show()
       
   604             self.Description.Show()
       
   605         else:
       
   606             self.Description.Enable(False)
       
   607             self.DescriptionLabel.Hide()
       
   608             self.Description.Hide()
       
   609         
       
   610         self.RefreshValues()
       
   611         self.VariablesGrid.RefreshButtons()
       
   612         self.MainSizer.Layout()
       
   613     
       
   614     def OnReturnTypeChanged(self, event):
       
   615         words = self.TagName.split("::")
       
   616         self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
       
   617         self.Controler.BufferProject()
       
   618         self.ParentWindow.RefreshView(variablepanel = False)
       
   619         self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   620         event.Skip()
       
   621     
       
   622     def OnDescriptionChanged(self, event):
       
   623         words = self.TagName.split("::")
       
   624         old_description = self.Controler.GetPouDescription(words[1])
       
   625         new_description = self.Description.GetValue()
       
   626         if new_description != old_description:
       
   627             self.Controler.SetPouDescription(words[1], new_description)
       
   628             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   629         event.Skip()
       
   630     
       
   631     def OnClassFilter(self, event):
       
   632         self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]]
       
   633         self.RefreshTypeList()
       
   634         self.RefreshValues()
       
   635         self.VariablesGrid.RefreshButtons()
       
   636         event.Skip()
       
   637 
       
   638     def RefreshTypeList(self):
       
   639         if self.Filter == "All":
       
   640             self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[choice] not in ["All","Interface","Variables"]]
       
   641         elif self.Filter == "Interface":
       
   642             self.ClassList = ["Input","Output","InOut","External"]
       
   643         elif self.Filter == "Variables":
       
   644             self.ClassList = ["Local","Temp"]
       
   645         else:
       
   646             self.ClassList = [self.Filter]
       
   647 
       
   648     def OnVariablesGridCellChange(self, event):
       
   649         row, col = event.GetRow(), event.GetCol()
       
   650         colname = self.Table.GetColLabelValue(col, False)
       
   651         value = self.Table.GetValue(row, col)
       
   652         message = None
       
   653         
       
   654         if colname == "Name" and value != "":
       
   655             if not TestIdentifier(value):
       
   656                 message = _("\"%s\" is not a valid identifier!") % value
       
   657             elif value.upper() in IEC_KEYWORDS:
       
   658                 message = _("\"%s\" is a keyword. It can't be used!") % value
       
   659             elif value.upper() in self.PouNames:
       
   660                 message = _("A POU named \"%s\" already exists!") % value
       
   661             elif value.upper() in [var["Name"].upper() for var in self.Values if var != self.Table.data[row]]:
       
   662                 message = _("A variable with \"%s\" as name already exists in this pou!") % value
       
   663             else:
       
   664                 self.SaveValues(False)
       
   665                 old_value = self.Table.GetOldValue()
       
   666                 if old_value != "":
       
   667                     self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
       
   668                 self.Controler.BufferProject()
       
   669                 self.ParentWindow.RefreshView(variablepanel = False)
       
   670                 self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   671                 event.Skip()
       
   672         else:
       
   673             self.SaveValues()
       
   674             if colname == "Class":
       
   675                 self.ParentWindow.RefreshView(variablepanel = False)
       
   676             elif colname == "Location":
       
   677                 wx.CallAfter(self.ParentWindow.RefreshView)
       
   678             
       
   679         if message is not None:
       
   680             dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   681             dialog.ShowModal()
       
   682             dialog.Destroy()
       
   683             event.Veto()
       
   684         else:
       
   685             event.Skip()
       
   686     
       
   687     def OnVariablesGridEditorShown(self, event):
       
   688         row, col = event.GetRow(), event.GetCol() 
       
   689 
       
   690         label_value = self.Table.GetColLabelValue(col, False)
       
   691         if label_value == "Type":
       
   692             type_menu = wx.Menu(title='')   # the root menu
       
   693 
       
   694             # build a submenu containing standard IEC types
       
   695             base_menu = wx.Menu(title='')
       
   696             for base_type in self.Controler.GetBaseTypes():
       
   697                 new_id = wx.NewId()
       
   698                 AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
       
   699                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
       
   700 
       
   701             type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
       
   702 
       
   703             # build a submenu containing user-defined types
       
   704             datatype_menu = wx.Menu(title='')
       
   705             datatypes = self.Controler.GetDataTypes(basetypes = False, confnodetypes = False)
       
   706             for datatype in datatypes:
       
   707                 new_id = wx.NewId()
       
   708                 AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
       
   709                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
       
   710 
       
   711             type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
       
   712             
       
   713             for category in self.Controler.GetConfNodeDataTypes():
       
   714                
       
   715                if len(category["list"]) > 0:
       
   716                    # build a submenu containing confnode types
       
   717                    confnode_datatype_menu = wx.Menu(title='')
       
   718                    for datatype in category["list"]:
       
   719                        new_id = wx.NewId()
       
   720                        AppendMenu(confnode_datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
       
   721                        self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
       
   722                    
       
   723                    type_menu.AppendMenu(wx.NewId(), category["name"], confnode_datatype_menu)
       
   724 
       
   725             # build a submenu containing function block types
       
   726             bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
       
   727             pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
       
   728             classtype = self.Table.GetValueByName(row, "Class")
       
   729             
       
   730             new_id = wx.NewId()
       
   731             AppendMenu(type_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Array"))
       
   732             self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id)
       
   733             
       
   734             if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
       
   735             poutype != "function" and bodytype in ["ST", "IL"]:
       
   736                 functionblock_menu = wx.Menu(title='')
       
   737                 fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
       
   738                 for functionblock_type in fbtypes:
       
   739                     new_id = wx.NewId()
       
   740                     AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
       
   741                     self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
       
   742 
       
   743                 type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
       
   744 
       
   745             rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
       
   746             corner_x = rect.x + rect.width
       
   747             corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
       
   748 
       
   749             # pop up this new menu
       
   750             self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
       
   751             type_menu.Destroy()
       
   752             event.Veto()
       
   753         else:
       
   754             event.Skip()
       
   755     
       
   756     def GetVariableTypeFunction(self, base_type):
       
   757         def VariableTypeFunction(event):
       
   758             row = self.VariablesGrid.GetGridCursorRow()
       
   759             self.Table.SetValueByName(row, "Type", base_type)
       
   760             self.Table.ResetView(self.VariablesGrid)
       
   761             self.SaveValues(False)
       
   762             self.ParentWindow.RefreshView(variablepanel = False)
       
   763             self.Controler.BufferProject()
       
   764             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   765         return VariableTypeFunction
       
   766     
       
   767     def VariableArrayTypeFunction(self, event):
       
   768         row = self.VariablesGrid.GetGridCursorRow()
       
   769         dialog = ArrayTypeDialog(self, 
       
   770                                  self.Controler.GetDataTypes(self.TagName), 
       
   771                                  self.Table.GetValueByName(row, "Type"))
       
   772         if dialog.ShowModal() == wx.ID_OK:
       
   773             self.Table.SetValueByName(row, "Type", dialog.GetValue())
       
   774             self.Table.ResetView(self.VariablesGrid)
       
   775             self.SaveValues(False)
       
   776             self.ParentWindow.RefreshView(variablepanel = False)
       
   777             self.Controler.BufferProject()
       
   778             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
       
   779         dialog.Destroy()
       
   780     
       
   781     def OnVariablesGridCellLeftClick(self, event):
       
   782         row = event.GetRow()
       
   783         if not self.Debug and (event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit")):
       
   784             var_name = self.Table.GetValueByName(row, "Name")
       
   785             var_class = self.Table.GetValueByName(row, "Class")
       
   786             var_type = self.Table.GetValueByName(row, "Type")
       
   787             data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName)))
       
   788             dragSource = wx.DropSource(self.VariablesGrid)
       
   789             dragSource.SetData(data)
       
   790             dragSource.DoDragDrop()
       
   791         event.Skip()
       
   792     
       
   793     def RefreshValues(self):
       
   794         data = []
       
   795         for num, variable in enumerate(self.Values):
       
   796             if variable["Class"] in self.ClassList:
       
   797                 variable["Number"] = num + 1
       
   798                 data.append(variable)
       
   799         self.Table.SetData(data)
       
   800         self.Table.ResetView(self.VariablesGrid)
       
   801             
       
   802     def SaveValues(self, buffer = True):
       
   803         words = self.TagName.split("::")
       
   804         if self.ElementType == "config":
       
   805             self.Controler.SetConfigurationGlobalVars(words[1], self.Values)
       
   806         elif self.ElementType == "resource":
       
   807             self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values)
       
   808         else:
       
   809             if self.ReturnType.IsEnabled():
       
   810                 self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
       
   811             self.Controler.SetPouInterfaceVars(words[1], self.Values)
       
   812         if buffer:
       
   813             self.Controler.BufferProject()
       
   814             self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)            
       
   815 
       
   816 #-------------------------------------------------------------------------------
       
   817 #                        Highlights showing functions
       
   818 #-------------------------------------------------------------------------------
       
   819 
       
   820     def OnRefreshHighlightsTimer(self, event):
       
   821         self.Table.ResetView(self.VariablesGrid)
       
   822         event.Skip()
       
   823 
       
   824     def AddVariableHighlight(self, infos, highlight_type):
       
   825         if isinstance(infos[0], TupleType):
       
   826             for i in xrange(*infos[0]):
       
   827                 self.Table.AddHighlight((i,) + infos[1:], highlight_type)
       
   828             cell_visible = infos[0][0]
       
   829         else:
       
   830             self.Table.AddHighlight(infos, highlight_type)
       
   831             cell_visible = infos[0]
       
   832         colnames = [colname.lower() for colname in self.Table.colnames]
       
   833         self.VariablesGrid.MakeCellVisible(cell_visible, colnames.index(infos[1]))
       
   834         self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
       
   835 
       
   836     def RemoveVariableHighlight(self, infos, highlight_type):
       
   837         if isinstance(infos[0], TupleType):
       
   838             for i in xrange(*infos[0]):
       
   839                 self.Table.RemoveHighlight((i,) + infos[1:], highlight_type)
       
   840         else:
       
   841             self.Table.RemoveHighlight(infos, highlight_type)
       
   842         self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)
       
   843 
       
   844     def ClearHighlights(self, highlight_type=None):
       
   845         self.Table.ClearHighlights(highlight_type)
       
   846         self.Table.ResetView(self.VariablesGrid)