# HG changeset patch # User b.taylor@willowglen.ca # Date 1252103872 21600 # Node ID 31c3dc45cfab6e8c4614b91233727a17b627f19d # Parent 9855343da6fc887f430219f588ef2ae3c8f978eb introduced BrowseVariableLocationsDialog and the GridCellEditor that launches it. diff -r 9855343da6fc -r 31c3dc45cfab VariablePanel.py --- 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()