# HG changeset patch # User Laurent Bessard # Date 1370017971 -7200 # Node ID fb9799a0c0f724f1c53c72f18218b9dd9382392e # Parent 35046bc74554fbc0d047680cee143774cb39458b Rewrite DebugVariableTablePanel diff -r 35046bc74554 -r fb9799a0c0f7 IDEFrame.py --- a/IDEFrame.py Fri May 31 14:22:15 2013 +0200 +++ b/IDEFrame.py Fri May 31 18:32:51 2013 +0200 @@ -2027,7 +2027,7 @@ editor.SubscribeAllDataConsumers() elif editor.IsDebugging(): editor.SubscribeAllDataConsumers() - self.DebugVariablePanel.UnsubscribeObsoleteData() + self.DebugVariablePanel.SubscribeAllDataConsumers() def AddDebugVariable(self, iec_path, force=False): if self.EnableDebug: diff -r 35046bc74554 -r fb9799a0c0f7 controls/DebugVariablePanel/DebugVariableGraphicPanel.py --- a/controls/DebugVariablePanel/DebugVariableGraphicPanel.py Fri May 31 14:22:15 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableGraphicPanel.py Fri May 31 18:32:51 2013 +0200 @@ -60,7 +60,7 @@ try: values = eval(data) if not isinstance(values, TupleType): - raise + raise ValueError except: message = _("Invalid value \"%s\" for debug variable")%data values = None @@ -460,15 +460,15 @@ self.TickTimeLabel.SetLabel("") self.TickSizer.Layout() - def UnsubscribeObsoleteData(self): - self.SubscribeAllDataConsumers() + def SubscribeAllDataConsumers(self): + DebugViewer.SubscribeAllDataConsumers(self) if self.DataProducer is not None: if self.DataProducer is not None: self.SetTickTime(self.DataProducer.GetTicktime()) for panel in self.GraphicPanels: - panel.UnsubscribeObsoleteData() + panel.SubscribeAllDataConsumers() if panel.ItemsIsEmpty(): if panel.HasCapture(): panel.ReleaseMouse() diff -r 35046bc74554 -r fb9799a0c0f7 controls/DebugVariablePanel/DebugVariableGraphicViewer.py --- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Fri May 31 14:22:15 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Fri May 31 18:32:51 2013 +0200 @@ -87,7 +87,7 @@ try: values = eval(data) if not isinstance(values, TupleType): - raise + raise ValueError except: message = _("Invalid value \"%s\" for debug variable")%data values = None @@ -609,8 +609,8 @@ self.GraphType = GRAPH_PARALLEL self.ResetGraphics() - def UnsubscribeObsoleteData(self): - DebugVariableViewer.UnsubscribeObsoleteData(self) + def SubscribeAllDataConsumers(self): + DebugVariableViewer.SubscribeAllDataConsumers(self) if not self.ItemsIsEmpty(): self.ResetGraphics() diff -r 35046bc74554 -r fb9799a0c0f7 controls/DebugVariablePanel/DebugVariableTablePanel.py --- a/controls/DebugVariablePanel/DebugVariableTablePanel.py Fri May 31 14:22:15 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableTablePanel.py Fri May 31 18:32:51 2013 +0200 @@ -35,9 +35,22 @@ from DebugVariableItem import DebugVariableItem def GetDebugVariablesTableColnames(): + """ + Function returning table column header labels + @return: List of labels [col_label,...] + """ _ = lambda x : x return [_("Variable"), _("Value")] +#------------------------------------------------------------------------------- +# Debug Variable Table Panel +#------------------------------------------------------------------------------- + +""" +Class that implements a custom table storing value to display in Debug Variable +Table Panel grid +""" + class DebugVariableTable(CustomTable): def GetValue(self, row, col): @@ -69,11 +82,6 @@ return self.data[row].IsForced() return False - def IsNumVariable(self, row): - if row < self.GetNumberRows(): - return self.data[row].IsNumVariable() - return False - def _updateColAttrs(self, grid): """ wx.grid.Grid -> update the column attributes to add the @@ -92,15 +100,26 @@ grid.SetCellTextColour(row, col, wx.BLACK) grid.SetReadOnly(row, col, True) self.ResizeRow(grid, row) - - def AppendItem(self, data): - self.data.append(data) - - def InsertItem(self, idx, data): - self.data.insert(idx, data) - - def RemoveItem(self, idx): - self.data.pop(idx) + + def RefreshValues(self, grid): + for col in xrange(self.GetNumberCols()): + colname = self.GetColLabelValue(col, False) + if colname == "Value": + for row in xrange(self.GetNumberRows()): + grid.SetCellValue(row, col, str(self.data[row].GetValue())) + if self.IsForced(row): + grid.SetCellTextColour(row, col, wx.BLUE) + else: + grid.SetCellTextColour(row, col, wx.BLACK) + + def AppendItem(self, item): + self.data.append(item) + + def InsertItem(self, idx, item): + self.data.insert(idx, item) + + def RemoveItem(self, item): + self.data.remove(item) def MoveItem(self, idx, new_idx): self.data.insert(new_idx, self.data.pop(idx)) @@ -108,52 +127,109 @@ def GetItem(self, idx): return self.data[idx] + +#------------------------------------------------------------------------------- +# Debug Variable Table Panel Drop Target +#------------------------------------------------------------------------------- + +""" +Class that implements a custom drop target class for Debug Variable Table Panel +""" + class DebugVariableTableDropTarget(wx.TextDropTarget): def __init__(self, parent): + """ + Constructor + @param window: Reference to the Debug Variable Panel + """ wx.TextDropTarget.__init__(self) self.ParentWindow = parent def __del__(self): + """ + Destructor + """ + # Remove reference to Debug Variable Panel self.ParentWindow = None def OnDropText(self, x, y, data): + """ + Function called when mouse is dragged over Drop Target + @param x: X coordinate of mouse pointer + @param y: Y coordinate of mouse pointer + @param data: Text associated to drag'n drop + """ message = None try: values = eval(data) + if not isinstance(values, TupleType): + raise ValueError except: - message = _("Invalid value \"%s\" for debug variable")%data - values = None - if not isinstance(values, TupleType): - message = _("Invalid value \"%s\" for debug variable")%data + message = _("Invalid value \"%s\" for debug variable") % data values = None if message is not None: wx.CallAfter(self.ShowMessage, message) - elif values is not None and values[1] == "debug": + + elif values[1] == "debug": grid = self.ParentWindow.VariablesGrid + + # Get row where variable was dropped x, y = grid.CalcUnscrolledPosition(x, y) row = grid.YToRow(y - grid.GetColLabelSize()) + + # If no row found add variable at table end if row == wx.NOT_FOUND: row = self.ParentWindow.Table.GetNumberRows() + + # Add variable to table self.ParentWindow.InsertValue(values[0], row, force=True) def ShowMessage(self, message): - dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) + """ + Show error message in Error Dialog + @param message: Error message to display + """ + dialog = wx.MessageDialog(self.ParentWindow, + message, + _("Error"), + wx.OK|wx.ICON_ERROR) dialog.ShowModal() dialog.Destroy() - + + +#------------------------------------------------------------------------------- +# Debug Variable Table Panel +#------------------------------------------------------------------------------- + +""" +Class that implements a panel displaying debug variable values in a table +""" + class DebugVariableTablePanel(wx.Panel, DebugViewer): def __init__(self, parent, producer, window): + """ + Constructor + @param parent: Reference to the parent wx.Window + @param producer: Object receiving debug value and dispatching them to + consumers + @param window: Reference to Beremiz frame + """ wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL) + # Save Reference to Beremiz frame self.ParentWindow = window + # Variable storing flag indicating that variable displayed in table + # received new value and then table need to be refreshed self.HasNewData = False DebugViewer.__init__(self, producer, True) + # Construction of window layout by creating controls and sizers + main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) main_sizer.AddGrowableCol(0) main_sizer.AddGrowableRow(1) @@ -162,17 +238,24 @@ main_sizer.AddSizer(button_sizer, border=5, flag=wx.ALIGN_RIGHT|wx.ALL) + # Creation of buttons for navigating in table + for name, bitmap, help in [ ("DeleteButton", "remove_element", _("Remove debug variable")), ("UpButton", "up", _("Move debug variable up")), ("DownButton", "down", _("Move debug variable down"))]: - button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap), + button = wx.lib.buttons.GenBitmapButton(self, + bitmap=GetBitmap(bitmap), size=wx.Size(28, 28), style=wx.NO_BORDER) button.SetToolTipString(help) setattr(self, name, button) button_sizer.AddWindow(button, border=5, flag=wx.LEFT) - self.VariablesGrid = CustomGrid(self, size=wx.Size(-1, 150), style=wx.VSCROLL) + # Creation of grid and associated table + + self.VariablesGrid = CustomGrid(self, + size=wx.Size(-1, 150), style=wx.VSCROLL) + # Define grid drop target self.VariablesGrid.SetDropTarget(DebugVariableTableDropTarget(self)) self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnVariablesGridCellRightClick) @@ -180,12 +263,15 @@ self.OnVariablesGridCellLeftClick) main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW) - self.Table = DebugVariableTable(self, [], GetDebugVariablesTableColnames()) + self.Table = DebugVariableTable(self, [], + GetDebugVariablesTableColnames()) self.VariablesGrid.SetTable(self.Table) self.VariablesGrid.SetButtons({"Delete": self.DeleteButton, "Up": self.UpButton, "Down": self.DownButton}) - + + # Definition of function associated to navigation buttons + def _AddVariable(new_row): return self.VariablesGrid.GetGridCursorRow() setattr(self.VariablesGrid, "_AddRow", _AddVariable) @@ -193,7 +279,7 @@ def _DeleteVariable(row): item = self.Table.GetItem(row) self.RemoveDataConsumer(item) - self.Table.RemoveItem(row) + self.Table.RemoveItem(item) self.RefreshView() setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable) @@ -205,6 +291,8 @@ return new_row setattr(self.VariablesGrid, "_MoveRow", _MoveVariable) + # Initialization of grid layout + self.VariablesGrid.SetRowLabelSize(0) self.GridColSizes = [200, 100] @@ -221,35 +309,64 @@ self.SetSizer(main_sizer) def RefreshNewData(self, *args, **kwargs): + """ + Called to refresh Table according to values received by variables + Can receive any parameters (not used here) + """ + # Refresh 'Value' column of table if new data have been received since + # last refresh if self.HasNewData: self.HasNewData = False self.RefreshView(only_values=True) DebugViewer.RefreshNewData(self, *args, **kwargs) def RefreshView(self, only_values=False): + """ + Function refreshing table layout and values + @param only_values: True if only 'Value' column need to be updated + """ + # Block refresh until table layout and values are completely updated self.Freeze() + # Update only 'value' column from table if only_values: - for col in xrange(self.Table.GetNumberCols()): - if self.Table.GetColLabelValue(col, False) == "Value": - for row in xrange(self.Table.GetNumberRows()): - self.VariablesGrid.SetCellValue(row, col, str(self.Table.GetValueByName(row, "Value"))) - if self.Table.IsForced(row): - self.VariablesGrid.SetCellTextColour(row, col, wx.BLUE) - else: - self.VariablesGrid.SetCellTextColour(row, col, wx.BLACK) + self.Table.RefreshValues(self.VariablesGrid) + + # Update complete table layout refreshing table navigation buttons + # state according to else: self.Table.ResetView(self.VariablesGrid) + self.VariablesGrid.RefreshButtons() + + self.Thaw() + + def ResetView(self): + """ + Function removing all variables denugged from table + @param only_values: True if only 'Value' column need to be updated + """ + # Unsubscribe all variables debugged + self.UnsubscribeAllDataConsumers() + + # Clear table content + self.Table.Empty() + + # Update table layout + self.Freeze() + self.Table.ResetView(self.VariablesGrid) self.VariablesGrid.RefreshButtons() - self.Thaw() - - def UnsubscribeObsoleteData(self): - self.SubscribeAllDataConsumers() - - items = [(idx, item) for idx, item in enumerate(self.Table.GetData())] - items.reverse() - for idx, item in items: + + def SubscribeAllDataConsumers(self): + """ + Function refreshing table layout and values + @param only_values: True if only 'Value' column need to be updated + """ + DebugViewer.SubscribeAllDataConsumers(self) + + # Navigate through variable displayed in table, removing those that + # doesn't exist anymore in PLC + for item in self.Table.GetData()[:]: iec_path = item.GetVariable() if self.GetDataType(iec_path) is None: self.RemoveDataConsumer(item) @@ -257,80 +374,130 @@ else: self.AddDataConsumer(iec_path.upper(), item) item.RefreshVariableType() + + # Update table layout self.Freeze() self.Table.ResetView(self.VariablesGrid) self.VariablesGrid.RefreshButtons() self.Thaw() - def ResetView(self): - self.UnsubscribeAllDataConsumers() - - self.Table.Empty() - self.Freeze() - self.Table.ResetView(self.VariablesGrid) - self.VariablesGrid.RefreshButtons() - self.Thaw() - - def GetForceVariableMenuFunction(self, iec_path, item): - iec_type = self.GetDataType(iec_path) + def GetForceVariableMenuFunction(self, item): + """ + Function returning callback function for contextual menu 'Force' item + @param item: Debug Variable item where contextual menu was opened + @return: Callback function + """ def ForceVariableFunction(event): - if iec_type is not None: - dialog = ForceVariableDialog(self, iec_type, str(item.GetValue())) - if dialog.ShowModal() == wx.ID_OK: - self.ForceDataValue(iec_path.upper(), dialog.GetValue()) + # Get variable path and data type + iec_path = item.GetVariable() + iec_type = self.GetDataType(iec_path) + + # Return immediately if not data type found + if iec_type is None: + return + + # Open dialog for entering value to force variable + dialog = ForceVariableDialog(self, iec_type, str(item.GetValue())) + + # If valid value entered, force variable + if dialog.ShowModal() == wx.ID_OK: + self.ForceDataValue(iec_path.upper(), dialog.GetValue()) + return ForceVariableFunction def GetReleaseVariableMenuFunction(self, iec_path): + """ + Function returning callback function for contextual menu 'Release' item + @param iec_path: Debug Variable path where contextual menu was opened + @return: Callback function + """ def ReleaseVariableFunction(event): + # Release variable self.ReleaseDataValue(iec_path) return ReleaseVariableFunction def OnVariablesGridCellLeftClick(self, event): - if event.GetCol() == 0: - row = event.GetRow() - data = wx.TextDataObject(str((self.Table.GetValueByName(row, "Variable"), "debug"))) + """ + Called when left mouse button is pressed on a table cell + @param event: wx.grid.GridEvent + """ + # Initiate a drag and drop if the cell clicked was in 'Variable' column + if self.Table.GetColLabelValue(event.GetCol(), False) == "Variable": + item = self.Table.GetItem(event.GetRow()) + data = wx.TextDataObject(str((item.GetVariable(), "debug"))) dragSource = wx.DropSource(self.VariablesGrid) dragSource.SetData(data) dragSource.DoDragDrop() + event.Skip() def OnVariablesGridCellRightClick(self, event): - row, col = event.GetRow(), event.GetCol() - if self.Table.GetColLabelValue(col, False) == "Value": - iec_path = self.Table.GetValueByName(row, "Variable").upper() - + """ + Called when right mouse button is pressed on a table cell + @param event: wx.grid.GridEvent + """ + # Open a contextual menu if the cell clicked was in 'Value' column + if self.Table.GetColLabelValue(event.GetCol(), False) == "Value": + row = event.GetRow() + + # Get variable path + item = self.Table.GetItem(row) + iec_path = item.GetVariable().upper() + + # Create contextual menu menu = wx.Menu(title='') - new_id = wx.NewId() - menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Force value")) - self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(iec_path.upper(), self.Table.GetItem(row)), id=new_id) - - new_id = wx.NewId() - menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Release value")) - self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(iec_path.upper()), id=new_id) - - if self.Table.IsForced(row): - menu.Enable(new_id, True) - else: - menu.Enable(new_id, False) - + # Add menu items + for text, enable, callback in [ + (_("Force value"), True, + self.GetForceVariableMenuFunction(item)), + # Release menu item is enabled only if variable is forced + (_("Release value"), self.Table.IsForced(row), + self.GetReleaseVariableMenuFunction(iec_path))]: + + new_id = wx.NewId() + menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=text) + menu.Enable(new_id, enable) + self.Bind(wx.EVT_MENU, callback, id=new_id) + + # Popup contextual menu self.PopupMenu(menu) menu.Destroy() event.Skip() - def InsertValue(self, iec_path, idx = None, force=False): + def InsertValue(self, iec_path, index=None, force=False): + """ + Insert a new variable to debug in table + @param iec_path: Variable path to debug + @param index: Row where insert the variable in table (default None, + insert at last position) + @param force: Force insertion of variable even if not defined in + producer side + """ + # Return immediately if variable is already debugged for item in self.Table.GetData(): if iec_path == item.GetVariable(): return - if idx is None: - idx = self.Table.GetNumberRows() + + # Insert at last position if index not defined + if index is None: + index = self.Table.GetNumberRows() + + # Subscribe variable to producer item = DebugVariableItem(self, iec_path) result = self.AddDataConsumer(iec_path.upper(), item) + + # Insert variable in table if subscription done or insertion forced if result is not None or force: - self.Table.InsertItem(idx, item) + self.Table.InsertItem(index, item) self.RefreshView() def ResetGraphicsValues(self): + """ + Called to reset graphics values when PLC is started + (Nothing to do because no graphic values here. Defined for + compatibility with Debug Variable Graphic Panel) + """ pass \ No newline at end of file diff -r 35046bc74554 -r fb9799a0c0f7 controls/DebugVariablePanel/DebugVariableTextViewer.py --- a/controls/DebugVariablePanel/DebugVariableTextViewer.py Fri May 31 14:22:15 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableTextViewer.py Fri May 31 18:32:51 2013 +0200 @@ -84,7 +84,7 @@ try: values = eval(data) if not isinstance(values, TupleType): - raise + raise ValueError except: message = _("Invalid value \"%s\" for debug variable") % data values = None diff -r 35046bc74554 -r fb9799a0c0f7 controls/DebugVariablePanel/DebugVariableViewer.py --- a/controls/DebugVariablePanel/DebugVariableViewer.py Fri May 31 14:22:15 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableViewer.py Fri May 31 18:32:51 2013 +0200 @@ -135,7 +135,7 @@ """ return len(self.Items) == 0 - def UnsubscribeObsoleteData(self): + def SubscribeAllDataConsumers(self): """ Function that unsubscribe and remove every item that store values of a variable that doesn't exist in PLC anymore