# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
# Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
# See COPYING file for copyrights details.
# This program 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
# of the License, or (at your option) any later version.
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from __future__ import absolute_import
from __future__ import division
from collections import OrderedDict
from functools import reduce
from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
from dialogs.ForceVariableDialog import ForceVariableDialog
HIGHLIGHT_RESIZE] = range(6)
# Viewer highlight styles
# -------------------------------------------------------------------------------
# Base Debug Variable Viewer Class
# -------------------------------------------------------------------------------
class DebugVariableViewer(object):
Class that implements a generic viewer that display a list of variable values
This class has to be inherited to effectively display variable values
def __init__(self, window, items=None):
@param window: Reference to the Debug Variable Panel
@param items: List of DebugVariableItem displayed by Viewer
self.ParentWindow = window
items = [] if items is None else items
self.ItemsDict = OrderedDict([(item.GetVariable(), item)
self.Items = self.ItemsDict.viewvalues()
# Variable storing current highlight displayed in Viewer
self.Highlight = HIGHLIGHT_NONE
self.InitHighlightPensBrushes()
# Remove reference to Debug Variable Panel
def InitHighlightPensBrushes(self):
Init global pens and brushes
HIGHLIGHT['DROP_PEN'] = wx.Pen(wx.Colour(0, 128, 255))
HIGHLIGHT['DROP_BRUSH'] = wx.Brush(wx.Colour(0, 128, 255, 128))
HIGHLIGHT['RESIZE_PEN'] = wx.Pen(wx.Colour(200, 200, 200))
HIGHLIGHT['RESIZE_BRUSH'] = wx.Brush(wx.Colour(200, 200, 200))
Return position of Viewer in Debug Variable Panel
@return: Position of Viewer
return self.ParentWindow.GetViewerIndex(self)
def GetItem(self, variable):
Return item storing values of a variable
@param variable: Variable path
@return: Item storing values of this variable
return self.ItemsDict.get(variable, None)
Return items displayed by Viewer
@return: List of items displayed in Viewer
return self.ItemsDict.values()
Add an item to the list of items displayed by Viewer
@param item: Item to add to the list
self.ItemsDict[item.GetVariable()] = item
def RemoveItem(self, item):
Remove an item from the list of items displayed by Viewer
@param item: Item to remove from the list
self.ItemsDict.pop(item.GetVariable(), None)
Clear list of items displayed by Viewer
# Unsubscribe every items of the list
self.ParentWindow.RemoveDataConsumer(item)
Return if list of items displayed by Viewer is empty
@return: True if list is empty
return len(self.Items) == 0
def SubscribeAllDataConsumers(self):
Function that unsubscribe and remove every item that store values of
a variable that doesn't exist in PLC anymore
for item in self.ItemsDict.values()[:]:
iec_path = item.GetVariable()
# Check that variablepath exist in PLC
if self.ParentWindow.GetDataType(iec_path) is None:
# If not, unsubscribe and remove it
self.ParentWindow.RemoveDataConsumer(item)
# If it exist, resubscribe and refresh data type
self.ParentWindow.AddDataConsumer(iec_path.upper(), item, True)
item.RefreshVariableType()
def ResetItemsData(self):
Reset data stored in every items displayed in Viewer
def GetItemsMinCommonTick(self):
Return the minimum tick common to all iems displayed in Viewer
@return: Minimum common tick between items
return reduce(max, [item.GetData()[0, 0]
if len(item.GetData()) > 0], 0)
Method that refresh the content displayed by Viewer
Need to be overridden by inherited classes
def SetHighlight(self, highlight):
Set Highlight type displayed in Viewer
@return: True if highlight has changed
# Return immediately if highlight don't change
if self.Highlight == highlight:
self.Highlight = highlight
Return list of buttons defined in Viewer
def IsOverButton(self, x, y):
Return if point is over one button of Viewer
@param x: X coordinate of point
@param y: Y coordinate of point
@return: button where point is over
for button in self.GetButtons():
def HandleButton(self, x, y):
Search for the button under point and if found execute associated
@param x: X coordinate of point
@param y: Y coordinate of point
@return: True if a button was found and callback executed
button = self.IsOverButton(x, y)
def ShowButtons(self, show):
Set display state of buttons in Viewer
@param show: Display state (True if buttons must be displayed)
# Change display of every buttons
for button in self.Buttons:
# Refresh button positions
self.RefreshButtonsPosition()
def RefreshButtonsPosition(self):
Function that refresh buttons position in Viewer
width, _height = self.GetSize()
# Buttons are align right so we calculate buttons positions in
buttons = self.Buttons[:]
# Position offset on x coordinate
# Buttons are stacked right, removing those that are not active
# Update button position according to button width and offset
button.SetPosition(width - 5 - w - x_offset, 5)
# Update offset on x coordinate
def DrawCommonElements(self, dc, buttons=None):
Function that draw common graphics for every Viewers
@param dc: wx.DC object corresponding to Device context where drawing
@param buttons: List of buttons to draw if different from default
width, height = self.GetSize()
# Set dc styling for drop before or drop after highlight
dc.SetPen(HIGHLIGHT['DROP_PEN'])
dc.SetBrush(HIGHLIGHT['DROP_BRUSH'])
# Draw line at upper side of Viewer if highlight is drop before
if self.Highlight == HIGHLIGHT_BEFORE:
dc.DrawLine(0, 1, width - 1, 1)
# Draw line at lower side of Viewer if highlight is drop before
elif self.Highlight == HIGHLIGHT_AFTER:
dc.DrawLine(0, height - 1, width - 1, height - 1)
# If no specific buttons are defined, get default buttons
# If graph dragging is processing
if self.ParentWindow.IsDragging():
destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
if destBBox.width > 0 and destBBox.height > 0:
srcPanel = self.ParentWindow.DraggingAxesPanel
srcBBox = srcPanel.GetAxesBoundingBox()
srcX = srcBBox.x - (srcPos.x if destBBox.x == 0 else 0)
srcY = srcBBox.y - (srcPos.y if destBBox.y == 0 else 0)
srcBmp = _convert_agg_to_wx_bitmap(
srcPanel.get_renderer(), None)
srcDC.SelectObject(srcBmp)
dc.Blit(destBBox.x, destBBox.y,
int(destBBox.width), int(destBBox.height),
def OnEnter(self, event):
Function called when entering Viewer
@param event: wx.MouseEvent
def OnLeave(self, event):
Function called when leaving Viewer
@param event: wx.MouseEvent
Function called when Close button is pressed
wx.CallAfter(self.ParentWindow.DeleteValue, self)
Function called when Force button is pressed
self.ForceValue(self.ItemsDict.values()[0])
def OnReleaseButton(self):
Function called when Release button is pressed
self.ReleaseValue(self.ItemsDict.values()[0])
def OnMouseDragging(self, x, y):
Function called when mouse is dragged over Viewer
@param x: X coordinate of mouse pointer
@param y: Y coordinate of mouse pointer
xw, yw = self.GetPosition()
# Refresh highlight in Debug Variable Panel (highlight can be displayed
self.ParentWindow.RefreshHighlight(x + xw, y + yw)
def RefreshHighlight(self, x, y):
Function called by Debug Variable Panel asking Viewer to refresh
highlight according to mouse position
@param x: X coordinate of mouse pointer
@param y: Y coordinate of mouse pointer
_width, height = self.GetSize()
# Mouse is in the first half of Viewer
# If Viewer is the upper one, draw drop before highlight
if self.ParentWindow.IsViewerFirst(self):
self.SetHighlight(HIGHLIGHT_BEFORE)
# Else draw drop after highlight in previous Viewer
self.SetHighlight(HIGHLIGHT_NONE)
self.ParentWindow.HighlightPreviousViewer(self)
# Mouse is in the second half of Viewer, draw drop after highlight
self.SetHighlight(HIGHLIGHT_AFTER)
def OnEraseBackground(self, event):
Function called when Viewer background is going to be erase
@param event: wx.EraseEvent
# Prevent flicker on Windows
def OnResize(self, event):
Function called when Viewer size changed
@param event: wx.ResizeEvent
# Refresh button positions
self.RefreshButtonsPosition()
self.ParentWindow.ForceRefresh()
def ForceValue(self, item):
Force value of item given
@param item: Item to force value
# Check variable data type
iec_path = item.GetVariable()
iec_type = self.ParentWindow.GetDataType(iec_path)
# Return immediately if not found
# Open a dialog to enter varaible forced value
dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
if dialog.ShowModal() == wx.ID_OK:
self.ParentWindow.ForceDataValue(iec_path.upper(),
def ReleaseValue(self, item):
Release value of item given
@param item: Item to release value
self.ParentWindow.ReleaseDataValue(
item.GetVariable().upper())