--- a/VariablePanel.py Thu Sep 03 09:37:23 2009 -0600
+++ b/VariablePanel.py Fri Sep 04 16:37:52 2009 -0600
@@ -177,7 +177,7 @@
renderer = wx.grid.GridCellStringRenderer()
elif colname == "Location":
if self.GetValueByName(row, "Class") in ["Local", "Global"]:
- editor = wx.grid.GridCellTextEditor()
+ editor = LocationCellEditor(self.Parent)
renderer = wx.grid.GridCellStringRenderer()
else:
grid.SetReadOnly(row, col, True)
@@ -638,6 +638,7 @@
row, col = event.GetRow(), event.GetCol()
colname = self.Table.GetColLabelValue(col)
value = self.Table.GetValue(row, col)
+
if colname == "Name" and value != "":
if not TestIdentifier(value):
message = wx.MessageDialog(self, _("\"%s\" is not a valid identifier!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
@@ -679,32 +680,53 @@
def OnVariablesGridEditorShown(self, event):
row, col = event.GetRow(), event.GetCol()
- classtype = self.Table.GetValueByName(row, "Class")
- if self.Table.GetColLabelValue(col) == "Type":
- type_menu = wx.Menu(title='')
+
+ label_value = self.Table.GetColLabelValue(col)
+ if label_value == "Type":
+ debug = self.ParentWindow.Debug
+ type_menu = wx.Menu(title='') # the root menu
+
+ # build a submenu containing standard IEC types
base_menu = wx.Menu(title='')
for base_type in self.Controler.GetBaseTypes():
new_id = wx.NewId()
AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)
+
type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)
+
+ # build a submenu containing user-defined types
datatype_menu = wx.Menu(title='')
- for datatype in self.Controler.GetDataTypes(basetypes = False, debug = self.ParentWindow.Debug):
+ datatypes = self.Controler.GetDataTypes(basetypes = False, debug = debug)
+ for datatype in datatypes:
new_id = wx.NewId()
AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)
+
type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)
- functionblock_menu = wx.Menu(title='')
- bodytype = self.Controler.GetEditedElementBodyType(self.TagName, self.ParentWindow.Debug)
- pouname, poutype = self.Controler.GetEditedElementType(self.TagName, self.ParentWindow.Debug)
- if classtype in ["Input","Output","InOut","External","Global"] or poutype != "function" and bodytype in ["ST", "IL"]:
- for functionblock_type in self.Controler.GetFunctionBlockTypes(self.TagName, self.ParentWindow.Debug):
+
+ # build a submenu containing function block types
+ bodytype = self.Controler.GetEditedElementBodyType(self.TagName, debug)
+ pouname, poutype = self.Controler.GetEditedElementType(self.TagName, debug)
+ classtype = self.Table.GetValueByName(row, "Class")
+
+ if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
+ poutype != "function" and bodytype in ["ST", "IL"]:
+ functionblock_menu = wx.Menu(title='')
+ fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName, debug)
+ for functionblock_type in fbtypes:
new_id = wx.NewId()
AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)
+
type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)
+
rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
- self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
+ corner_x = rect.x + rect.width
+ corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
+
+ # pop up this new menu
+ self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
event.Veto()
else:
event.Skip()
@@ -803,3 +825,317 @@
def ClearErrors(self):
self.Table.ClearErrors()
self.Table.ResetView(self.VariablesGrid)
+
+class LocationCellControl(wx.PyControl):
+ '''
+ Custom cell editor control with a text box and a button that launches
+ the BrowseVariableLocationsDialog.
+ '''
+ def __init__(self, parent, var_panel):
+ wx.Control.__init__(self, parent, -1)
+ self.ParentWindow = parent
+ self.VarPanel = var_panel
+ self.Row = -1
+
+ self.Bind(wx.EVT_SIZE, self.OnSize)
+
+ # create text control
+ self.txt = wx.TextCtrl(self, -1, '')
+
+ # create browse button
+ self.btn = wx.Button(self, -1, '...', size=(20,20))
+ self.btn.Bind(wx.EVT_BUTTON, self.OnBtnBrowseClick)
+
+ szr = wx.BoxSizer(wx.HORIZONTAL)
+ szr.Add(self.txt, proportion=1, flag=wx.EXPAND)
+ szr.Add(self.btn, proportion=0, flag=wx.ALIGN_RIGHT)
+
+ szr.SetSizeHints(self)
+ self.SetSizer(szr)
+ self.Layout()
+
+ def SetRow(self, row):
+ '''set the grid row that we're working on'''
+ self.Row = row
+
+ def OnSize(self, event):
+ '''resize the button and text control to fit'''
+ overall_width = self.GetSize()[0]
+ btn_width, btn_height = self.btn.GetSize()
+ new_txt_width = overall_width - btn_width
+
+ self.txt.SetSize(wx.Size(new_txt_width, -1))
+ self.btn.SetDimensions(new_txt_width, -1, btn_width, btn_height)
+
+ def OnBtnBrowseClick(self, event):
+ # pop up the location browser dialog
+ dia = BrowseVariableLocationsDialog(self.ParentWindow, self.VarPanel)
+ dia.ShowModal()
+
+ if dia.Selection:
+ loc, iec_type = dia.Selection
+
+ # set the location
+ self.SetText(loc)
+
+ # set the variable type
+ # NOTE: this update won't be displayed until editing is complete
+ # (when EndEdit is called).
+ # we can't call VarPanel.RefreshValues() here because it causes
+ # an exception.
+ self.VarPanel.Table.SetValueByName(self.Row, 'Type', iec_type)
+
+ self.txt.SetFocus()
+
+ def SetText(self, text):
+ self.txt.SetValue(text)
+
+ def SetInsertionPoint(self, i):
+ self.txt.SetInsertionPoint(i)
+
+ def GetText(self):
+ return self.txt.GetValue()
+
+ def SetFocus(self):
+ self.txt.SetFocus()
+
+class LocationCellEditor(wx.grid.PyGridCellEditor):
+ '''
+ Grid cell editor that uses LocationCellControl to display a browse button.
+ '''
+ def __init__(self, var_panel):
+ wx.grid.PyGridCellEditor.__init__(self)
+ self.VarPanel = var_panel
+
+ def Create(self, parent, id, evt_handler):
+ self.text_browse = LocationCellControl(parent, self.VarPanel)
+ self.SetControl(self.text_browse)
+ if evt_handler:
+ self.text_browse.PushEventHandler(evt_handler)
+
+ def BeginEdit(self, row, col, grid):
+ loc = self.VarPanel.Table.GetValueByName(row, 'Location')
+ self.text_browse.SetText(loc)
+ self.text_browse.SetRow(row)
+ self.text_browse.SetFocus()
+
+ def EndEdit(self, row, col, grid):
+ loc = self.text_browse.GetText()
+ old_loc = self.VarPanel.Table.GetValueByName(row, 'Location')
+
+ if loc != old_loc:
+ self.VarPanel.Table.SetValueByName(row, 'Location', loc)
+
+ # NOTE: this is a really lame hack to force this row's
+ # 'Type' cell to redraw (since it may have changed).
+ # There's got to be a better way than this.
+ self.VarPanel.VariablesGrid.AutoSizeRow(row)
+ return True
+
+ def SetSize(self, rect):
+ # -2 and +5 give this some extra vertical padding
+ self.text_browse.SetDimensions(rect.x, rect.y-2,
+ rect.width, rect.height+5,
+ wx.SIZE_ALLOW_MINUS_ONE)
+
+ def Clone(self):
+ return LocationCellEditor(self.VarPanel)
+
+class BrowseVariableLocationsDialog(wx.Dialog):
+ # turn LOCATIONDATATYPES inside-out
+ LOCATION_SIZES = {}
+ for size, types in LOCATIONDATATYPES.iteritems():
+ for type in types:
+ LOCATION_SIZES[type] = size
+
+ class PluginData:
+ '''contains a plugin's VariableLocationTree'''
+ def __init__(self, plugin):
+ self.subtree = plugin.GetVariableLocationTree()
+
+ class SubtreeData:
+ '''contains a subtree of a plugin's VariableLocationTree'''
+ def __init__(self, subtree):
+ self.subtree = subtree
+
+ class VariableData:
+ '''contains all the information about a valid variable location'''
+ def __init__(self, type, dir, loc):
+ self.type = type
+
+ loc_suffix = '.'.join([str(x) for x in loc])
+
+ size = BrowseVariableLocationsDialog.LOCATION_SIZES[type]
+ self.loc = size + loc_suffix
+
+ # if a direction was given, use it
+ # (if not we'll prompt the user to select one)
+ if dir:
+ self.loc = '%' + dir + self.loc
+
+ def __init__(self, parent, var_panel):
+ self.VarPanel = var_panel
+ self.Selection = None
+
+ # create the dialog
+ wx.Dialog.__init__(self, parent=parent, title=_('Browse Variables'),
+ style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER,
+ size=(-1, 400))
+
+ # create the root sizer
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ self.SetSizer(sizer)
+
+ # create the tree control
+ self.tree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT)
+ sizer.Add(self.tree, 1, wx.EXPAND|wx.ALL, border=20)
+
+ # create the Direction sizer and field
+ dsizer = wx.BoxSizer(wx.HORIZONTAL)
+ sizer.Add(dsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND, border=20)
+
+ # direction label
+ ltext = wx.StaticText(self, -1, _('Direction:'))
+ dsizer.Add(ltext, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=20)
+
+ # direction choice
+ self.DirChoice = wx.Choice(id=-1, parent=self, choices=[_('Input'), _('Output'), _('Memory')])
+ dsizer.Add(self.DirChoice, flag=wx.EXPAND)
+ # set a default for the choice
+ self.SetSelectedDirection('I')
+
+ # create the button sizer
+ btsizer = wx.BoxSizer(wx.HORIZONTAL)
+ sizer.Add(btsizer, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.ALIGN_RIGHT, border=20)
+
+ # add plugins to the tree
+ root = self.tree.AddRoot(_('Plugins'))
+ ctrl = self.VarPanel.Controler
+ self.AddChildPluginsToTree(ctrl.PluginsRoot, root)
+
+ # -- buttons --
+
+ # ok button
+ self.OkButton = wx.Button(self, wx.ID_OK, _('Use Location'))
+ self.OkButton.SetDefault()
+ btsizer.Add(self.OkButton, flag=wx.RIGHT, border=5)
+
+ # cancel button
+ b = wx.Button(self, wx.ID_CANCEL, _('Cancel'))
+ btsizer.Add(b)
+
+ # -- event handlers --
+
+ # accept the location on doubleclick or clicking the Use Location button
+ self.Bind(wx.EVT_BUTTON, self.OnOk, self.OkButton)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnOk, self.tree)
+
+ # disable the Add button when we're not on a valid variable
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChange, self.tree)
+
+ # handle the Expand event. this lets us create the tree as it's expanded
+ # (trying to create it all at once is slow since plugin variable location
+ # trees can be big)
+ wx.EVT_TREE_ITEM_EXPANDING(self.tree, self.tree.GetId(), self.OnExpand)
+
+ def OnExpand(self, event):
+ item = event.GetItem()
+ if not item.IsOk():
+ item = self.tree.GetSelection()
+
+ data = self.tree.GetPyData(item)
+ self.ExpandTree(item, data.subtree)
+
+ def AddChildPluginsToTree(self, plugin, root):
+ for p in plugin.IECSortedChilds():
+ plug_name = p.BaseParams.getName()
+ new_item = self.tree.AppendItem(root, plug_name)
+
+ # make it look like the tree item has children (since it doesn't yet)
+ self.tree.SetItemHasChildren(new_item)
+
+ # attach the plugin data to the tree item
+ self.tree.SetPyData(new_item, BrowseVariableLocationsDialog.PluginData(p))
+
+ # add child plugins recursively
+ self.AddChildPluginsToTree(p, new_item)
+
+ def ExpandTree(self, root, subtree):
+ items = subtree.items()
+ items.sort()
+
+ for node_name, data in items:
+ if isinstance(data, dict):
+ # this is a new subtree
+
+ new_item = self.tree.AppendItem(root, node_name)
+ self.tree.SetItemHasChildren(new_item)
+
+ # attach the new subtree's data to the tree item
+ new_data = BrowseVariableLocationsDialog.SubtreeData(data)
+ self.tree.SetPyData(new_item, new_data)
+ else:
+ # this is a new leaf.
+
+ # data is a tuple containing (IEC type, I/Q/M/None, IEC path tuple)
+ type, dir, loc = data
+
+ node_name = '%s (%s)' % (node_name, type)
+ new_item = self.tree.AppendItem(root, node_name)
+
+ vd = BrowseVariableLocationsDialog.VariableData(type, dir, loc)
+ self.tree.SetPyData(new_item, vd)
+
+ def OnSelChange(self, event):
+ '''updates the text field and the "Use Location" button.'''
+ item = self.tree.GetSelection()
+ data = self.tree.GetPyData(item)
+
+ if isinstance(data, BrowseVariableLocationsDialog.VariableData):
+ self.OkButton.Enable()
+
+ location = data.loc
+ if location[0] == '%':
+ # location has a fixed direction
+ self.SetSelectedDirection(location[1])
+ self.DirChoice.Disable()
+ else:
+ # this location can have any direction (user selects)
+ self.DirChoice.Enable()
+ else:
+ self.OkButton.Disable()
+ self.DirChoice.Disable()
+
+ def GetSelectedDirection(self):
+ selected = self.DirChoice.GetSelection()
+ if selected == 0:
+ return 'I'
+ elif selected == 1:
+ return 'Q'
+ else:
+ return 'M'
+
+ def SetSelectedDirection(self, dir_letter):
+ if dir_letter == 'I':
+ self.DirChoice.SetSelection(0)
+ elif dir_letter == 'Q':
+ self.DirChoice.SetSelection(1)
+ else:
+ self.DirChoice.SetSelection(2)
+
+ def OnOk(self, event):
+ item = self.tree.GetSelection()
+ data = self.tree.GetPyData(item)
+
+ if not isinstance(data, BrowseVariableLocationsDialog.VariableData):
+ return
+
+ location = data.loc
+
+ if location[0] != '%':
+ # no direction was given, grab the one from the wxChoice
+ dir = self.GetSelectedDirection()
+ location = '%' + dir + location
+
+ self.Selection = (location, data.type)
+ self.Destroy()