controls/DebugVariablePanel/DebugVariableTablePanel.py
branch1.1 Korean release
changeset 1384 02fe382c4511
parent 1280 72a826dfcfbb
parent 1375 dc94c71a2f25
child 1640 437d258179e1
child 2563 18b6352e096a
equal deleted inserted replaced
1280:72a826dfcfbb 1384:02fe382c4511
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from types import TupleType
       
    26 
       
    27 import wx
       
    28 import wx.lib.buttons
       
    29 
       
    30 from editors.DebugViewer import DebugViewer
       
    31 from controls import CustomGrid, CustomTable
       
    32 from dialogs.ForceVariableDialog import ForceVariableDialog
       
    33 from util.BitmapLibrary import GetBitmap
       
    34 
       
    35 from DebugVariableItem import DebugVariableItem
       
    36 
       
    37 def GetDebugVariablesTableColnames():
       
    38     """
       
    39     Function returning table column header labels
       
    40     @return: List of labels [col_label,...]
       
    41     """
       
    42     _ = lambda x : x
       
    43     return [_("Variable"), _("Value")]
       
    44 
       
    45 #-------------------------------------------------------------------------------
       
    46 #                        Debug Variable Table Panel
       
    47 #-------------------------------------------------------------------------------
       
    48 
       
    49 """
       
    50 Class that implements a custom table storing value to display in Debug Variable
       
    51 Table Panel grid
       
    52 """
       
    53 
       
    54 class DebugVariableTable(CustomTable):
       
    55     
       
    56     def GetValue(self, row, col):
       
    57         if row < self.GetNumberRows():
       
    58             return self.GetValueByName(row, self.GetColLabelValue(col, False))
       
    59         return ""
       
    60     
       
    61     def SetValue(self, row, col, value):
       
    62         if col < len(self.colnames):
       
    63             self.SetValueByName(row, self.GetColLabelValue(col, False), value)
       
    64             
       
    65     def GetValueByName(self, row, colname):
       
    66         if row < self.GetNumberRows():
       
    67             if colname == "Variable":
       
    68                 return self.data[row].GetVariable()
       
    69             elif colname == "Value":
       
    70                 return self.data[row].GetValue()
       
    71         return ""
       
    72 
       
    73     def SetValueByName(self, row, colname, value):
       
    74         if row < self.GetNumberRows():
       
    75             if colname == "Variable":
       
    76                 self.data[row].SetVariable(value)
       
    77             elif colname == "Value":
       
    78                 self.data[row].SetValue(value)
       
    79     
       
    80     def IsForced(self, row):
       
    81         if row < self.GetNumberRows():
       
    82             return self.data[row].IsForced()
       
    83         return False
       
    84     
       
    85     def _updateColAttrs(self, grid):
       
    86         """
       
    87         wx.grid.Grid -> update the column attributes to add the
       
    88         appropriate renderer given the column name.
       
    89 
       
    90         Otherwise default to the default renderer.
       
    91         """
       
    92         
       
    93         for row in range(self.GetNumberRows()):
       
    94             for col in range(self.GetNumberCols()):
       
    95                 colname = self.GetColLabelValue(col, False)
       
    96                 if colname == "Value":
       
    97                     if self.IsForced(row):
       
    98                         grid.SetCellTextColour(row, col, wx.BLUE)
       
    99                     else:
       
   100                         grid.SetCellTextColour(row, col, wx.BLACK)
       
   101                 grid.SetReadOnly(row, col, True)
       
   102             self.ResizeRow(grid, row)
       
   103     
       
   104     def RefreshValues(self, grid):
       
   105         for col in xrange(self.GetNumberCols()):
       
   106             colname = self.GetColLabelValue(col, False)
       
   107             if colname == "Value":
       
   108                 for row in xrange(self.GetNumberRows()):
       
   109                     grid.SetCellValue(row, col, str(self.data[row].GetValue()))
       
   110                     if self.IsForced(row):
       
   111                         grid.SetCellTextColour(row, col, wx.BLUE)
       
   112                     else:
       
   113                         grid.SetCellTextColour(row, col, wx.BLACK)
       
   114       
       
   115     def AppendItem(self, item):
       
   116         self.data.append(item)
       
   117     
       
   118     def InsertItem(self, idx, item):
       
   119         self.data.insert(idx, item)
       
   120     
       
   121     def RemoveItem(self, item):
       
   122         self.data.remove(item)
       
   123     
       
   124     def MoveItem(self, idx, new_idx):
       
   125         self.data.insert(new_idx, self.data.pop(idx))
       
   126         
       
   127     def GetItem(self, idx):
       
   128         return self.data[idx]
       
   129 
       
   130 
       
   131 #-------------------------------------------------------------------------------
       
   132 #                  Debug Variable Table Panel Drop Target
       
   133 #-------------------------------------------------------------------------------
       
   134 
       
   135 """
       
   136 Class that implements a custom drop target class for Debug Variable Table Panel
       
   137 """
       
   138 
       
   139 class DebugVariableTableDropTarget(wx.TextDropTarget):
       
   140     
       
   141     def __init__(self, parent):
       
   142         """
       
   143         Constructor
       
   144         @param window: Reference to the Debug Variable Panel
       
   145         """
       
   146         wx.TextDropTarget.__init__(self)
       
   147         self.ParentWindow = parent
       
   148         
       
   149     def __del__(self):
       
   150         """
       
   151         Destructor
       
   152         """
       
   153         # Remove reference to Debug Variable Panel
       
   154         self.ParentWindow = None
       
   155         
       
   156     def OnDropText(self, x, y, data):
       
   157         """
       
   158         Function called when mouse is dragged over Drop Target
       
   159         @param x: X coordinate of mouse pointer
       
   160         @param y: Y coordinate of mouse pointer
       
   161         @param data: Text associated to drag'n drop
       
   162         """
       
   163         message = None
       
   164         
       
   165         # Check that data is valid regarding DebugVariablePanel
       
   166         try:
       
   167             values = eval(data)
       
   168             if not isinstance(values, TupleType):
       
   169                 raise ValueError
       
   170         except:
       
   171             message = _("Invalid value \"%s\" for debug variable") % data
       
   172             values = None
       
   173         
       
   174         # Display message if data is invalid
       
   175         if message is not None:
       
   176             wx.CallAfter(self.ShowMessage, message)
       
   177         
       
   178         # Data contain a reference to a variable to debug
       
   179         elif values[1] == "debug":
       
   180             grid = self.ParentWindow.VariablesGrid
       
   181             
       
   182             # Get row where variable was dropped
       
   183             x, y = grid.CalcUnscrolledPosition(x, y)
       
   184             row = grid.YToRow(y - grid.GetColLabelSize())
       
   185             
       
   186             # If no row found add variable at table end
       
   187             if row == wx.NOT_FOUND:
       
   188                 row = self.ParentWindow.Table.GetNumberRows()
       
   189             
       
   190             # Add variable to table
       
   191             self.ParentWindow.InsertValue(values[0], row, force=True)
       
   192             
       
   193     def ShowMessage(self, message):
       
   194         """
       
   195         Show error message in Error Dialog
       
   196         @param message: Error message to display
       
   197         """
       
   198         dialog = wx.MessageDialog(self.ParentWindow, 
       
   199                                   message, 
       
   200                                   _("Error"), 
       
   201                                   wx.OK|wx.ICON_ERROR)
       
   202         dialog.ShowModal()
       
   203         dialog.Destroy()
       
   204 
       
   205 
       
   206 #-------------------------------------------------------------------------------
       
   207 #                       Debug Variable Table Panel
       
   208 #-------------------------------------------------------------------------------
       
   209 
       
   210 """
       
   211 Class that implements a panel displaying debug variable values in a table
       
   212 """
       
   213 
       
   214 class DebugVariableTablePanel(wx.Panel, DebugViewer):
       
   215     
       
   216     def __init__(self, parent, producer, window):
       
   217         """
       
   218         Constructor
       
   219         @param parent: Reference to the parent wx.Window
       
   220         @param producer: Object receiving debug value and dispatching them to
       
   221         consumers
       
   222         @param window: Reference to Beremiz frame
       
   223         """
       
   224         wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
       
   225         
       
   226         # Save Reference to Beremiz frame
       
   227         self.ParentWindow = window
       
   228         
       
   229         # Variable storing flag indicating that variable displayed in table
       
   230         # received new value and then table need to be refreshed
       
   231         self.HasNewData = False
       
   232         
       
   233         DebugViewer.__init__(self, producer, True)
       
   234         
       
   235         # Construction of window layout by creating controls and sizers
       
   236         
       
   237         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
       
   238         main_sizer.AddGrowableCol(0)
       
   239         main_sizer.AddGrowableRow(1)
       
   240         
       
   241         button_sizer = wx.BoxSizer(wx.HORIZONTAL)
       
   242         main_sizer.AddSizer(button_sizer, border=5, 
       
   243               flag=wx.ALIGN_RIGHT|wx.ALL)
       
   244         
       
   245         # Creation of buttons for navigating in table
       
   246         
       
   247         for name, bitmap, help in [
       
   248                 ("DeleteButton", "remove_element", _("Remove debug variable")),
       
   249                 ("UpButton", "up", _("Move debug variable up")),
       
   250                 ("DownButton", "down", _("Move debug variable down"))]:
       
   251             button = wx.lib.buttons.GenBitmapButton(self, 
       
   252                   bitmap=GetBitmap(bitmap), 
       
   253                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   254             button.SetToolTipString(help)
       
   255             setattr(self, name, button)
       
   256             button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
       
   257         
       
   258         # Creation of grid and associated table
       
   259         
       
   260         self.VariablesGrid = CustomGrid(self, 
       
   261                 size=wx.Size(-1, 150), style=wx.VSCROLL)
       
   262         # Define grid drop target
       
   263         self.VariablesGrid.SetDropTarget(DebugVariableTableDropTarget(self))
       
   264         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, 
       
   265               self.OnVariablesGridCellRightClick)
       
   266         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
       
   267               self.OnVariablesGridCellLeftClick)
       
   268         main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
       
   269     
       
   270         self.Table = DebugVariableTable(self, [], 
       
   271                 GetDebugVariablesTableColnames())
       
   272         self.VariablesGrid.SetTable(self.Table)
       
   273         self.VariablesGrid.SetButtons({"Delete": self.DeleteButton,
       
   274                                        "Up": self.UpButton,
       
   275                                        "Down": self.DownButton})
       
   276         
       
   277         # Definition of function associated to navigation buttons
       
   278         
       
   279         def _AddVariable(new_row):
       
   280             return self.VariablesGrid.GetGridCursorRow()
       
   281         setattr(self.VariablesGrid, "_AddRow", _AddVariable)
       
   282     
       
   283         def _DeleteVariable(row):
       
   284             item = self.Table.GetItem(row)
       
   285             self.RemoveDataConsumer(item)
       
   286             self.Table.RemoveItem(item)
       
   287             self.RefreshView()
       
   288         setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
       
   289         
       
   290         def _MoveVariable(row, move):
       
   291             new_row = max(0, min(row + move, self.Table.GetNumberRows() - 1))
       
   292             if new_row != row:
       
   293                 self.Table.MoveItem(row, new_row)
       
   294                 self.RefreshView()
       
   295             return new_row
       
   296         setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
       
   297         
       
   298         # Initialization of grid layout
       
   299         
       
   300         self.VariablesGrid.SetRowLabelSize(0)
       
   301         
       
   302         self.GridColSizes = [200, 100]
       
   303         
       
   304         for col in range(self.Table.GetNumberCols()):
       
   305             attr = wx.grid.GridCellAttr()
       
   306             attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
       
   307             self.VariablesGrid.SetColAttr(col, attr)
       
   308             self.VariablesGrid.SetColSize(col, self.GridColSizes[col])
       
   309         
       
   310         self.Table.ResetView(self.VariablesGrid)
       
   311         self.VariablesGrid.RefreshButtons()
       
   312         
       
   313         self.SetSizer(main_sizer)
       
   314     
       
   315     def RefreshNewData(self, *args, **kwargs):
       
   316         """
       
   317         Called to refresh Table according to values received by variables
       
   318         Can receive any parameters (not used here)
       
   319         """
       
   320         # Refresh 'Value' column of table if new data have been received since
       
   321         # last refresh
       
   322         if self.HasNewData:
       
   323             self.HasNewData = False
       
   324             self.RefreshView(only_values=True)
       
   325         DebugViewer.RefreshNewData(self, *args, **kwargs)
       
   326     
       
   327     def RefreshView(self, only_values=False):
       
   328         """
       
   329         Function refreshing table layout and values
       
   330         @param only_values: True if only 'Value' column need to be updated
       
   331         """
       
   332         # Block refresh until table layout and values are completely updated
       
   333         self.Freeze()
       
   334         
       
   335         # Update only 'value' column from table 
       
   336         if only_values:
       
   337             self.Table.RefreshValues(self.VariablesGrid)
       
   338         
       
   339         # Update complete table layout refreshing table navigation buttons
       
   340         # state according to 
       
   341         else:
       
   342             self.Table.ResetView(self.VariablesGrid)
       
   343             self.VariablesGrid.RefreshButtons()
       
   344         
       
   345         self.Thaw()
       
   346         
       
   347     def ResetView(self):
       
   348         """
       
   349         Function removing all variables denugged from table
       
   350         @param only_values: True if only 'Value' column need to be updated
       
   351         """
       
   352         # Unsubscribe all variables debugged
       
   353         self.UnsubscribeAllDataConsumers()
       
   354         
       
   355         # Clear table content
       
   356         self.Table.Empty()
       
   357         
       
   358         # Update table layout
       
   359         self.Freeze()
       
   360         self.Table.ResetView(self.VariablesGrid)
       
   361         self.VariablesGrid.RefreshButtons()
       
   362         self.Thaw()
       
   363     
       
   364     def SubscribeAllDataConsumers(self):
       
   365         """
       
   366         Function refreshing table layout and values
       
   367         @param only_values: True if only 'Value' column need to be updated
       
   368         """
       
   369         DebugViewer.SubscribeAllDataConsumers(self)
       
   370         
       
   371         # Navigate through variable displayed in table, removing those that
       
   372         # doesn't exist anymore in PLC
       
   373         for item in self.Table.GetData()[:]:
       
   374             iec_path = item.GetVariable()
       
   375             if self.GetDataType(iec_path) is None:
       
   376                 self.RemoveDataConsumer(item)
       
   377                 self.Table.RemoveItem(idx)
       
   378             else:
       
   379                 self.AddDataConsumer(iec_path.upper(), item)
       
   380                 item.RefreshVariableType()
       
   381         
       
   382         # Update table layout
       
   383         self.Freeze()
       
   384         self.Table.ResetView(self.VariablesGrid)
       
   385         self.VariablesGrid.RefreshButtons()
       
   386         self.Thaw()
       
   387     
       
   388     def GetForceVariableMenuFunction(self, item):
       
   389         """
       
   390         Function returning callback function for contextual menu 'Force' item
       
   391         @param item: Debug Variable item where contextual menu was opened 
       
   392         @return: Callback function
       
   393         """
       
   394         def ForceVariableFunction(event):
       
   395             # Get variable path and data type
       
   396             iec_path = item.GetVariable()
       
   397             iec_type = self.GetDataType(iec_path)
       
   398             
       
   399             # Return immediately if not data type found
       
   400             if iec_type is None:
       
   401                 return
       
   402             
       
   403             # Open dialog for entering value to force variable
       
   404             dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
       
   405             
       
   406             # If valid value entered, force variable
       
   407             if dialog.ShowModal() == wx.ID_OK:
       
   408                 self.ForceDataValue(iec_path.upper(), dialog.GetValue())
       
   409         
       
   410         return ForceVariableFunction
       
   411 
       
   412     def GetReleaseVariableMenuFunction(self, iec_path):
       
   413         """
       
   414         Function returning callback function for contextual menu 'Release' item
       
   415         @param iec_path: Debug Variable path where contextual menu was opened
       
   416         @return: Callback function
       
   417         """
       
   418         def ReleaseVariableFunction(event):
       
   419             # Release variable
       
   420             self.ReleaseDataValue(iec_path)
       
   421         return ReleaseVariableFunction
       
   422     
       
   423     def OnVariablesGridCellLeftClick(self, event):
       
   424         """
       
   425         Called when left mouse button is pressed on a table cell
       
   426         @param event: wx.grid.GridEvent
       
   427         """
       
   428         # Initiate a drag and drop if the cell clicked was in 'Variable' column
       
   429         if self.Table.GetColLabelValue(event.GetCol(), False) == "Variable":
       
   430             item = self.Table.GetItem(event.GetRow())
       
   431             data = wx.TextDataObject(str((item.GetVariable(), "debug")))
       
   432             dragSource = wx.DropSource(self.VariablesGrid)
       
   433             dragSource.SetData(data)
       
   434             dragSource.DoDragDrop()
       
   435         
       
   436         event.Skip()
       
   437     
       
   438     def OnVariablesGridCellRightClick(self, event):
       
   439         """
       
   440         Called when right mouse button is pressed on a table cell
       
   441         @param event: wx.grid.GridEvent
       
   442         """
       
   443         # Open a contextual menu if the cell clicked was in 'Value' column
       
   444         if self.Table.GetColLabelValue(event.GetCol(), False) == "Value":
       
   445             row = event.GetRow()
       
   446             
       
   447             # Get variable path
       
   448             item = self.Table.GetItem(row)
       
   449             iec_path = item.GetVariable().upper()
       
   450             
       
   451             # Create contextual menu
       
   452             menu = wx.Menu(title='')
       
   453             
       
   454             # Add menu items
       
   455             for text, enable, callback in [
       
   456                 (_("Force value"), True,
       
   457                  self.GetForceVariableMenuFunction(item)),
       
   458                 # Release menu item is enabled only if variable is forced 
       
   459                 (_("Release value"), self.Table.IsForced(row),
       
   460                  self.GetReleaseVariableMenuFunction(iec_path))]:
       
   461                 
       
   462                 new_id = wx.NewId()
       
   463                 menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=text)
       
   464                 menu.Enable(new_id, enable)
       
   465                 self.Bind(wx.EVT_MENU, callback, id=new_id)
       
   466             
       
   467             # Popup contextual menu
       
   468             self.PopupMenu(menu)
       
   469             
       
   470             menu.Destroy()
       
   471         event.Skip()
       
   472     
       
   473     def InsertValue(self, iec_path, index=None, force=False, graph=False):
       
   474         """
       
   475         Insert a new variable to debug in table
       
   476         @param iec_path: Variable path to debug
       
   477         @param index: Row where insert the variable in table (default None,
       
   478         insert at last position)
       
   479         @param force: Force insertion of variable even if not defined in
       
   480         producer side
       
   481         @param graph: Values must be displayed in graph canvas (Do nothing,
       
   482         here for compatibility with Debug Variable Graphic Panel)
       
   483         """
       
   484         # Return immediately if variable is already debugged
       
   485         for item in self.Table.GetData():
       
   486             if iec_path == item.GetVariable():
       
   487                 return
       
   488             
       
   489         # Insert at last position if index not defined
       
   490         if index is None:
       
   491             index = self.Table.GetNumberRows()
       
   492         
       
   493         # Subscribe variable to producer
       
   494         item = DebugVariableItem(self, iec_path)
       
   495         result = self.AddDataConsumer(iec_path.upper(), item)
       
   496         
       
   497         # Insert variable in table if subscription done or insertion forced
       
   498         if result is not None or force:
       
   499             self.Table.InsertItem(index, item)
       
   500             self.RefreshView()
       
   501     
       
   502     def ResetGraphicsValues(self):
       
   503         """
       
   504         Called to reset graphics values when PLC is started
       
   505         (Nothing to do because no graphic values here. Defined for
       
   506         compatibility with Debug Variable Graphic Panel)
       
   507         """
       
   508         pass
       
   509