VariablePanel.py
changeset 422 31c3dc45cfab
parent 419 8f97ed01a6a6
child 424 d19c4a6460ab
equal deleted inserted replaced
421:9855343da6fc 422:31c3dc45cfab
   175                     elif colname == "Initial Value":
   175                     elif colname == "Initial Value":
   176                         editor = wx.grid.GridCellTextEditor()
   176                         editor = wx.grid.GridCellTextEditor()
   177                         renderer = wx.grid.GridCellStringRenderer()
   177                         renderer = wx.grid.GridCellStringRenderer()
   178                     elif colname == "Location":
   178                     elif colname == "Location":
   179                         if self.GetValueByName(row, "Class") in ["Local", "Global"]:
   179                         if self.GetValueByName(row, "Class") in ["Local", "Global"]:
   180                             editor = wx.grid.GridCellTextEditor()
   180                             editor = LocationCellEditor(self.Parent)
   181                             renderer = wx.grid.GridCellStringRenderer()
   181                             renderer = wx.grid.GridCellStringRenderer()
   182                         else:
   182                         else:
   183                             grid.SetReadOnly(row, col, True)
   183                             grid.SetReadOnly(row, col, True)
   184                     elif colname == "Class":
   184                     elif colname == "Class":
   185                         if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and self.GetValueByName(row, "Class") in ["Input", "Output", "InOut"]:
   185                         if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and self.GetValueByName(row, "Class") in ["Input", "Output", "InOut"]:
   636 
   636 
   637     def OnVariablesGridCellChange(self, event):
   637     def OnVariablesGridCellChange(self, event):
   638         row, col = event.GetRow(), event.GetCol()
   638         row, col = event.GetRow(), event.GetCol()
   639         colname = self.Table.GetColLabelValue(col)
   639         colname = self.Table.GetColLabelValue(col)
   640         value = self.Table.GetValue(row, col)
   640         value = self.Table.GetValue(row, col)
       
   641 
   641         if colname == "Name" and value != "":
   642         if colname == "Name" and value != "":
   642             if not TestIdentifier(value):
   643             if not TestIdentifier(value):
   643                 message = wx.MessageDialog(self, _("\"%s\" is not a valid identifier!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
   644                 message = wx.MessageDialog(self, _("\"%s\" is not a valid identifier!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
   644                 message.ShowModal()
   645                 message.ShowModal()
   645                 message.Destroy()
   646                 message.Destroy()
   677                 self.Table.ResetView(self.VariablesGrid)
   678                 self.Table.ResetView(self.VariablesGrid)
   678             event.Skip()
   679             event.Skip()
   679     
   680     
   680     def OnVariablesGridEditorShown(self, event):
   681     def OnVariablesGridEditorShown(self, event):
   681         row, col = event.GetRow(), event.GetCol() 
   682         row, col = event.GetRow(), event.GetCol() 
   682         classtype = self.Table.GetValueByName(row, "Class")
   683 
   683         if self.Table.GetColLabelValue(col) == "Type":
   684         label_value = self.Table.GetColLabelValue(col)
   684             type_menu = wx.Menu(title='')
   685         if label_value == "Type":
       
   686             debug = self.ParentWindow.Debug
       
   687             type_menu = wx.Menu(title='')   # the root menu
       
   688 
       
   689             # build a submenu containing standard IEC types
   685             base_menu = wx.Menu(title='')
   690             base_menu = wx.Menu(title='')
   686             for base_type in self.Controler.GetBaseTypes():
   691             for base_type in self.Controler.GetBaseTypes():
   687                 new_id = wx.NewId()
   692                 new_id = wx.NewId()
   688                 AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
   693                 AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
   689                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
   694                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
       
   695 
   690             type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
   696             type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
       
   697 
       
   698             # build a submenu containing user-defined types
   691             datatype_menu = wx.Menu(title='')
   699             datatype_menu = wx.Menu(title='')
   692             for datatype in self.Controler.GetDataTypes(basetypes = False, debug = self.ParentWindow.Debug):
   700             datatypes = self.Controler.GetDataTypes(basetypes = False, debug = debug)
       
   701             for datatype in datatypes:
   693                 new_id = wx.NewId()
   702                 new_id = wx.NewId()
   694                 AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
   703                 AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
   695                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
   704                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
       
   705 
   696             type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
   706             type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
   697             functionblock_menu = wx.Menu(title='')
   707 
   698             bodytype = self.Controler.GetEditedElementBodyType(self.TagName, self.ParentWindow.Debug)
   708             # build a submenu containing function block types
   699             pouname, poutype = self.Controler.GetEditedElementType(self.TagName, self.ParentWindow.Debug)
   709             bodytype = self.Controler.GetEditedElementBodyType(self.TagName, debug)
   700             if classtype in ["Input","Output","InOut","External","Global"] or poutype != "function" and bodytype in ["ST", "IL"]:
   710             pouname, poutype = self.Controler.GetEditedElementType(self.TagName, debug)
   701                 for functionblock_type in self.Controler.GetFunctionBlockTypes(self.TagName, self.ParentWindow.Debug):
   711             classtype = self.Table.GetValueByName(row, "Class")
       
   712 
       
   713             if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
       
   714             poutype != "function" and bodytype in ["ST", "IL"]:
       
   715                 functionblock_menu = wx.Menu(title='')
       
   716                 fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName, debug)
       
   717                 for functionblock_type in fbtypes:
   702                     new_id = wx.NewId()
   718                     new_id = wx.NewId()
   703                     AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
   719                     AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
   704                     self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
   720                     self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
       
   721 
   705                 type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
   722                 type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
       
   723 
   706             rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
   724             rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
   707             self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
   725             corner_x = rect.x + rect.width
       
   726             corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
       
   727 
       
   728             # pop up this new menu
       
   729             self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
   708             event.Veto()
   730             event.Veto()
   709         else:
   731         else:
   710             event.Skip()
   732             event.Skip()
   711     
   733     
   712     def GetVariableTypeFunction(self, base_type):
   734     def GetVariableTypeFunction(self, base_type):
   801         self.Table.ResetView(self.VariablesGrid)
   823         self.Table.ResetView(self.VariablesGrid)
   802 
   824 
   803     def ClearErrors(self):
   825     def ClearErrors(self):
   804         self.Table.ClearErrors()
   826         self.Table.ClearErrors()
   805         self.Table.ResetView(self.VariablesGrid)
   827         self.Table.ResetView(self.VariablesGrid)
       
   828 
       
   829 class LocationCellControl(wx.PyControl):
       
   830     '''
       
   831     Custom cell editor control with a text box and a button that launches
       
   832     the BrowseVariableLocationsDialog.
       
   833     '''
       
   834     def __init__(self, parent, var_panel):
       
   835         wx.Control.__init__(self, parent, -1)
       
   836         self.ParentWindow = parent
       
   837         self.VarPanel = var_panel
       
   838         self.Row = -1
       
   839 
       
   840         self.Bind(wx.EVT_SIZE, self.OnSize)
       
   841 
       
   842         # create text control
       
   843         self.txt = wx.TextCtrl(self, -1, '')
       
   844 
       
   845         # create browse button
       
   846         self.btn = wx.Button(self, -1, '...', size=(20,20))
       
   847         self.btn.Bind(wx.EVT_BUTTON, self.OnBtnBrowseClick)
       
   848 
       
   849         szr = wx.BoxSizer(wx.HORIZONTAL)
       
   850         szr.Add(self.txt, proportion=1, flag=wx.EXPAND)
       
   851         szr.Add(self.btn, proportion=0, flag=wx.ALIGN_RIGHT)
       
   852 
       
   853         szr.SetSizeHints(self)
       
   854         self.SetSizer(szr)
       
   855         self.Layout()
       
   856 
       
   857     def SetRow(self, row):
       
   858         '''set the grid row that we're working on'''
       
   859         self.Row = row
       
   860 
       
   861     def OnSize(self, event):
       
   862         '''resize the button and text control to fit'''
       
   863         overall_width = self.GetSize()[0]
       
   864         btn_width, btn_height = self.btn.GetSize()
       
   865         new_txt_width = overall_width - btn_width
       
   866 
       
   867         self.txt.SetSize(wx.Size(new_txt_width, -1))
       
   868         self.btn.SetDimensions(new_txt_width, -1, btn_width, btn_height)
       
   869 
       
   870     def OnBtnBrowseClick(self, event):
       
   871         # pop up the location browser dialog
       
   872         dia = BrowseVariableLocationsDialog(self.ParentWindow, self.VarPanel)
       
   873         dia.ShowModal()
       
   874 
       
   875         if dia.Selection:
       
   876             loc, iec_type = dia.Selection
       
   877 
       
   878             # set the location
       
   879             self.SetText(loc)
       
   880 
       
   881             # set the variable type
       
   882             # NOTE: this update won't be displayed until editing is complete
       
   883             # (when EndEdit is called).
       
   884             # we can't call VarPanel.RefreshValues() here because it causes
       
   885             # an exception. 
       
   886             self.VarPanel.Table.SetValueByName(self.Row, 'Type', iec_type)
       
   887 
       
   888         self.txt.SetFocus()
       
   889 
       
   890     def SetText(self, text):
       
   891         self.txt.SetValue(text)
       
   892 
       
   893     def SetInsertionPoint(self, i):
       
   894         self.txt.SetInsertionPoint(i)
       
   895 
       
   896     def GetText(self):
       
   897         return self.txt.GetValue()
       
   898 
       
   899     def SetFocus(self):
       
   900         self.txt.SetFocus()
       
   901 
       
   902 class LocationCellEditor(wx.grid.PyGridCellEditor):
       
   903     '''
       
   904     Grid cell editor that uses LocationCellControl to display a browse button.
       
   905     '''
       
   906     def __init__(self, var_panel):
       
   907         wx.grid.PyGridCellEditor.__init__(self)
       
   908         self.VarPanel = var_panel
       
   909 
       
   910     def Create(self, parent, id, evt_handler):
       
   911         self.text_browse = LocationCellControl(parent, self.VarPanel)
       
   912         self.SetControl(self.text_browse)
       
   913         if evt_handler:
       
   914             self.text_browse.PushEventHandler(evt_handler)
       
   915 
       
   916     def BeginEdit(self, row, col, grid):
       
   917         loc = self.VarPanel.Table.GetValueByName(row, 'Location')
       
   918         self.text_browse.SetText(loc)
       
   919         self.text_browse.SetRow(row)
       
   920         self.text_browse.SetFocus()
       
   921 
       
   922     def EndEdit(self, row, col, grid):
       
   923         loc = self.text_browse.GetText()
       
   924         old_loc = self.VarPanel.Table.GetValueByName(row, 'Location')
       
   925 
       
   926         if loc != old_loc:
       
   927             self.VarPanel.Table.SetValueByName(row, 'Location', loc)
       
   928             
       
   929             # NOTE: this is a really lame hack to force this row's
       
   930             # 'Type' cell to redraw (since it may have changed). 
       
   931             # There's got to be a better way than this.
       
   932             self.VarPanel.VariablesGrid.AutoSizeRow(row)
       
   933             return True
       
   934 
       
   935     def SetSize(self, rect):
       
   936         # -2 and +5 give this some extra vertical padding
       
   937         self.text_browse.SetDimensions(rect.x, rect.y-2,
       
   938                                         rect.width, rect.height+5,
       
   939                                         wx.SIZE_ALLOW_MINUS_ONE)
       
   940 
       
   941     def Clone(self):
       
   942         return LocationCellEditor(self.VarPanel)
       
   943 
       
   944 class BrowseVariableLocationsDialog(wx.Dialog):
       
   945     # turn LOCATIONDATATYPES inside-out
       
   946     LOCATION_SIZES = {}
       
   947     for size, types in LOCATIONDATATYPES.iteritems():
       
   948         for type in types:
       
   949             LOCATION_SIZES[type] = size
       
   950 
       
   951     class PluginData:
       
   952         '''contains a plugin's VariableLocationTree'''
       
   953         def __init__(self, plugin):
       
   954             self.subtree  = plugin.GetVariableLocationTree()
       
   955 
       
   956     class SubtreeData:
       
   957         '''contains a subtree of a plugin's VariableLocationTree'''
       
   958         def __init__(self, subtree):
       
   959             self.subtree = subtree
       
   960 
       
   961     class VariableData:
       
   962         '''contains all the information about a valid variable location'''
       
   963         def __init__(self, type, dir, loc):
       
   964             self.type   = type
       
   965 
       
   966             loc_suffix = '.'.join([str(x) for x in loc])
       
   967 
       
   968             size = BrowseVariableLocationsDialog.LOCATION_SIZES[type]
       
   969             self.loc = size + loc_suffix
       
   970 
       
   971             # if a direction was given, use it
       
   972             # (if not we'll prompt the user to select one)
       
   973             if dir:
       
   974                 self.loc = '%' + dir + self.loc
       
   975 
       
   976     def __init__(self, parent, var_panel):
       
   977         self.VarPanel   = var_panel
       
   978         self.Selection  = None
       
   979 
       
   980         # create the dialog
       
   981         wx.Dialog.__init__(self, parent=parent, title=_('Browse Variables'),
       
   982                             style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER,
       
   983                             size=(-1, 400))
       
   984 
       
   985         # create the root sizer
       
   986         sizer = wx.BoxSizer(wx.VERTICAL)
       
   987         self.SetSizer(sizer)
       
   988 
       
   989         # create the tree control
       
   990         self.tree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT)
       
   991         sizer.Add(self.tree, 1, wx.EXPAND|wx.ALL, border=20)
       
   992 
       
   993         # create the Direction sizer and field
       
   994         dsizer = wx.BoxSizer(wx.HORIZONTAL)
       
   995         sizer.Add(dsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, border=20)
       
   996 
       
   997         #   direction label
       
   998         ltext = wx.StaticText(self, -1, _('Direction:'))
       
   999         dsizer.Add(ltext, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=20)
       
  1000 
       
  1001         #   direction choice
       
  1002         self.DirChoice = wx.Choice(id=-1, parent=self, choices=[_('Input'), _('Output'), _('Memory')])
       
  1003         dsizer.Add(self.DirChoice, flag=wx.EXPAND)
       
  1004         #   set a default for the choice   
       
  1005         self.SetSelectedDirection('I')
       
  1006 
       
  1007         # create the button sizer
       
  1008         btsizer = wx.BoxSizer(wx.HORIZONTAL)
       
  1009         sizer.Add(btsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT, border=20)
       
  1010 
       
  1011         # add plugins to the tree
       
  1012         root = self.tree.AddRoot(_('Plugins'))
       
  1013         ctrl = self.VarPanel.Controler
       
  1014         self.AddChildPluginsToTree(ctrl.PluginsRoot, root)
       
  1015 
       
  1016         #       -- buttons --
       
  1017 
       
  1018         # ok button
       
  1019         self.OkButton = wx.Button(self, wx.ID_OK, _('Use Location'))
       
  1020         self.OkButton.SetDefault()
       
  1021         btsizer.Add(self.OkButton, flag=wx.RIGHT, border=5)
       
  1022 
       
  1023         # cancel button
       
  1024         b = wx.Button(self, wx.ID_CANCEL, _('Cancel'))
       
  1025         btsizer.Add(b)
       
  1026 
       
  1027         #       -- event handlers --
       
  1028 
       
  1029         # accept the location on doubleclick or clicking the Use Location button
       
  1030         self.Bind(wx.EVT_BUTTON, self.OnOk, self.OkButton)
       
  1031         self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnOk, self.tree)
       
  1032 
       
  1033         # disable the Add button when we're not on a valid variable
       
  1034         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChange, self.tree)
       
  1035 
       
  1036         # handle the Expand event. this lets us create the tree as it's expanded
       
  1037         # (trying to create it all at once is slow since plugin variable location
       
  1038         # trees can be big)
       
  1039         wx.EVT_TREE_ITEM_EXPANDING(self.tree, self.tree.GetId(), self.OnExpand)
       
  1040 
       
  1041     def OnExpand(self, event):
       
  1042         item = event.GetItem()
       
  1043         if not item.IsOk():
       
  1044             item = self.tree.GetSelection()
       
  1045         
       
  1046         data = self.tree.GetPyData(item)
       
  1047         self.ExpandTree(item, data.subtree)
       
  1048 
       
  1049     def AddChildPluginsToTree(self, plugin, root):
       
  1050         for p in plugin.IECSortedChilds():
       
  1051             plug_name = p.BaseParams.getName()
       
  1052             new_item = self.tree.AppendItem(root, plug_name)
       
  1053 
       
  1054             # make it look like the tree item has children (since it doesn't yet)
       
  1055             self.tree.SetItemHasChildren(new_item) 
       
  1056 
       
  1057             # attach the plugin data to the tree item
       
  1058             self.tree.SetPyData(new_item, BrowseVariableLocationsDialog.PluginData(p))
       
  1059 
       
  1060             # add child plugins recursively
       
  1061             self.AddChildPluginsToTree(p, new_item)
       
  1062 
       
  1063     def ExpandTree(self, root, subtree):
       
  1064             items = subtree.items()
       
  1065             items.sort()
       
  1066 
       
  1067             for node_name, data in items:
       
  1068                 if isinstance(data, dict):
       
  1069                     # this is a new subtree
       
  1070 
       
  1071                     new_item = self.tree.AppendItem(root, node_name)
       
  1072                     self.tree.SetItemHasChildren(new_item)
       
  1073 
       
  1074                     # attach the new subtree's data to the tree item
       
  1075                     new_data = BrowseVariableLocationsDialog.SubtreeData(data)
       
  1076                     self.tree.SetPyData(new_item, new_data)
       
  1077                 else:
       
  1078                     # this is a new leaf.
       
  1079 
       
  1080                     # data is a tuple containing (IEC type, I/Q/M/None, IEC path tuple)
       
  1081                     type, dir, loc = data
       
  1082 
       
  1083                     node_name = '%s (%s)' % (node_name, type)
       
  1084                     new_item = self.tree.AppendItem(root, node_name)
       
  1085 
       
  1086                     vd = BrowseVariableLocationsDialog.VariableData(type, dir, loc)
       
  1087                     self.tree.SetPyData(new_item, vd)
       
  1088 
       
  1089     def OnSelChange(self, event):
       
  1090         '''updates the text field and the "Use Location"  button.'''
       
  1091         item = self.tree.GetSelection()
       
  1092         data = self.tree.GetPyData(item)
       
  1093 
       
  1094         if isinstance(data, BrowseVariableLocationsDialog.VariableData):
       
  1095             self.OkButton.Enable()
       
  1096 
       
  1097             location = data.loc
       
  1098             if location[0] == '%':
       
  1099                 # location has a fixed direction
       
  1100                 self.SetSelectedDirection(location[1])
       
  1101                 self.DirChoice.Disable()
       
  1102             else:
       
  1103                 # this location can have any direction (user selects)
       
  1104                 self.DirChoice.Enable()
       
  1105         else:
       
  1106             self.OkButton.Disable()
       
  1107             self.DirChoice.Disable()
       
  1108 
       
  1109     def GetSelectedDirection(self):
       
  1110         selected = self.DirChoice.GetSelection()
       
  1111         if selected == 0:
       
  1112             return 'I'
       
  1113         elif selected == 1:
       
  1114             return 'Q'
       
  1115         else:
       
  1116             return 'M'
       
  1117 
       
  1118     def SetSelectedDirection(self, dir_letter):
       
  1119         if dir_letter == 'I':
       
  1120             self.DirChoice.SetSelection(0)
       
  1121         elif dir_letter == 'Q':
       
  1122             self.DirChoice.SetSelection(1)
       
  1123         else:
       
  1124             self.DirChoice.SetSelection(2)
       
  1125 
       
  1126     def OnOk(self, event):
       
  1127         item = self.tree.GetSelection()
       
  1128         data = self.tree.GetPyData(item)
       
  1129 
       
  1130         if not isinstance(data, BrowseVariableLocationsDialog.VariableData):
       
  1131             return
       
  1132 
       
  1133         location = data.loc
       
  1134 
       
  1135         if location[0] != '%':
       
  1136             # no direction was given, grab the one from the wxChoice
       
  1137             dir = self.GetSelectedDirection()
       
  1138             location = '%' + dir + location
       
  1139 
       
  1140         self.Selection = (location, data.type)
       
  1141         self.Destroy()