#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
#based on the plcopen standard.
#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
#See COPYING file for copyrights details.
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#General Public License for more details.
#You should have received a copy of the GNU General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from types import TupleType
from editors.DebugViewer import DebugViewer
from controls import CustomGrid, CustomTable
from dialogs.ForceVariableDialog import ForceVariableDialog
from util.BitmapLibrary import GetBitmap
from DebugVariableItem import DebugVariableItem
def GetDebugVariablesTableColnames():
Function returning table column header labels
@return: List of labels [col_label,...]
return [_("Variable"), _("Value")]
#-------------------------------------------------------------------------------
# Debug Variable Table Panel
#-------------------------------------------------------------------------------
Class that implements a custom table storing value to display in Debug Variable
class DebugVariableTable(CustomTable):
def GetValue(self, row, col):
if row < self.GetNumberRows():
return self.GetValueByName(row, self.GetColLabelValue(col, False))
def SetValue(self, row, col, value):
if col < len(self.colnames):
self.SetValueByName(row, self.GetColLabelValue(col, False), value)
def GetValueByName(self, row, colname):
if row < self.GetNumberRows():
if colname == "Variable":
return self.data[row].GetVariable()
return self.data[row].GetValue()
def SetValueByName(self, row, colname, value):
if row < self.GetNumberRows():
if colname == "Variable":
self.data[row].SetVariable(value)
self.data[row].SetValue(value)
if row < self.GetNumberRows():
return self.data[row].IsForced()
def _updateColAttrs(self, grid):
wx.grid.Grid -> update the column attributes to add the
appropriate renderer given the column name.
Otherwise default to the default renderer.
for row in range(self.GetNumberRows()):
for col in range(self.GetNumberCols()):
colname = self.GetColLabelValue(col, False)
grid.SetCellTextColour(row, col, wx.BLUE)
grid.SetCellTextColour(row, col, wx.BLACK)
grid.SetReadOnly(row, col, True)
self.ResizeRow(grid, row)
def RefreshValues(self, grid):
for col in xrange(self.GetNumberCols()):
colname = self.GetColLabelValue(col, False)
for row in xrange(self.GetNumberRows()):
grid.SetCellValue(row, col, str(self.data[row].GetValue()))
grid.SetCellTextColour(row, col, wx.BLUE)
grid.SetCellTextColour(row, col, wx.BLACK)
def AppendItem(self, item):
def InsertItem(self, idx, item):
self.data.insert(idx, item)
def RemoveItem(self, item):
def MoveItem(self, idx, new_idx):
self.data.insert(new_idx, self.data.pop(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):
@param window: Reference to the Debug Variable Panel
wx.TextDropTarget.__init__(self)
self.ParentWindow = parent
# Remove reference to Debug Variable Panel
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
# Check that data is valid regarding DebugVariablePanel
if not isinstance(values, TupleType):
message = _("Invalid value \"%s\" for debug variable") % data
# Display message if data is invalid
wx.CallAfter(self.ShowMessage, message)
# Data contain a reference to a variable to 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
row = self.ParentWindow.Table.GetNumberRows()
self.ParentWindow.InsertValue(values[0], row, force=True)
def ShowMessage(self, message):
Show error message in Error Dialog
@param message: Error message to display
dialog = wx.MessageDialog(self.ParentWindow,
#-------------------------------------------------------------------------------
# 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):
@param parent: Reference to the parent wx.Window
@param producer: Object receiving debug value and dispatching them to
@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
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)
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
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),
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)
# 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)
self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
self.OnVariablesGridCellLeftClick)
main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
self.Table = DebugVariableTable(self, [],
GetDebugVariablesTableColnames())
self.VariablesGrid.SetTable(self.Table)
self.VariablesGrid.SetButtons({"Delete": self.DeleteButton,
"Down": self.DownButton})
# Definition of function associated to navigation buttons
def _AddVariable(new_row):
return self.VariablesGrid.GetGridCursorRow()
setattr(self.VariablesGrid, "_AddRow", _AddVariable)
def _DeleteVariable(row):
item = self.Table.GetItem(row)
self.RemoveDataConsumer(item)
self.Table.RemoveItem(item)
setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
def _MoveVariable(row, move):
new_row = max(0, min(row + move, self.Table.GetNumberRows() - 1))
self.Table.MoveItem(row, new_row)
setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
# Initialization of grid layout
self.VariablesGrid.SetRowLabelSize(0)
self.GridColSizes = [200, 100]
for col in range(self.Table.GetNumberCols()):
attr = wx.grid.GridCellAttr()
attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
self.VariablesGrid.SetColAttr(col, attr)
self.VariablesGrid.SetColSize(col, self.GridColSizes[col])
self.Table.ResetView(self.VariablesGrid)
self.VariablesGrid.RefreshButtons()
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
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
# Update only 'value' column from table
self.Table.RefreshValues(self.VariablesGrid)
# Update complete table layout refreshing table navigation buttons
self.Table.ResetView(self.VariablesGrid)
self.VariablesGrid.RefreshButtons()
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()
self.Table.ResetView(self.VariablesGrid)
self.VariablesGrid.RefreshButtons()
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)
self.Table.RemoveItem(idx)
self.AddDataConsumer(iec_path.upper(), item)
item.RefreshVariableType()
self.Table.ResetView(self.VariablesGrid)
self.VariablesGrid.RefreshButtons()
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):
# Get variable path and data type
iec_path = item.GetVariable()
iec_type = self.GetDataType(iec_path)
# Return immediately if not data type found
# 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):
self.ReleaseDataValue(iec_path)
return ReleaseVariableFunction
def OnVariablesGridCellLeftClick(self, event):
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)
def OnVariablesGridCellRightClick(self, event):
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":
item = self.Table.GetItem(row)
iec_path = item.GetVariable().upper()
for text, enable, callback in [
self.GetForceVariableMenuFunction(item)),
# Release menu item is enabled only if variable is forced
(_("Release value"), self.Table.IsForced(row),
self.GetReleaseVariableMenuFunction(iec_path))]:
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)
def InsertValue(self, iec_path, index=None, force=False, graph=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,
@param force: Force insertion of variable even if not defined in
@param graph: Values must be displayed in graph canvas (Do nothing,
here for compatibility with Debug Variable Graphic Panel)
# Return immediately if variable is already debugged
for item in self.Table.GetData():
if iec_path == item.GetVariable():
# Insert at last position if index not defined
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(index, item)
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)