Rewrite DebugVariableTablePanel
authorLaurent Bessard
Fri, 31 May 2013 18:32:51 +0200
changeset 1207 fb9799a0c0f7
parent 1206 35046bc74554
child 1208 d22fcdfae8d0
Rewrite DebugVariableTablePanel
IDEFrame.py
controls/DebugVariablePanel/DebugVariableGraphicPanel.py
controls/DebugVariablePanel/DebugVariableGraphicViewer.py
controls/DebugVariablePanel/DebugVariableTablePanel.py
controls/DebugVariablePanel/DebugVariableTextViewer.py
controls/DebugVariablePanel/DebugVariableViewer.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:
--- 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()
--- 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()
     
--- 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
--- 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
--- 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