--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/controls/DebugVariablePanel/DebugVariableViewer.py Fri May 31 00:07:21 2013 +0200
@@ -0,0 +1,420 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#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 collections import OrderedDict
+
+import wx
+
+import matplotlib
+matplotlib.use('WX')
+import matplotlib.pyplot
+from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
+
+from dialogs.ForceVariableDialog import ForceVariableDialog
+
+# Viewer highlight types
+[HIGHLIGHT_NONE,
+ HIGHLIGHT_BEFORE,
+ HIGHLIGHT_AFTER,
+ HIGHLIGHT_LEFT,
+ HIGHLIGHT_RIGHT,
+ HIGHLIGHT_RESIZE] = range(6)
+
+# Viewer highlight styles
+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))
+
+#-------------------------------------------------------------------------------
+# Base Debug Variable Viewer Class
+#-------------------------------------------------------------------------------
+
+"""
+Class that implements a generic viewer that display a list of variable values
+This class has to be inherited to effectively display variable values
+"""
+
+class DebugVariableViewer:
+
+ def __init__(self, window, items=[]):
+ """
+ Constructor
+ @param window: Reference to the Debug Variable Panel
+ @param items: List of DebugVariableItem displayed by Viewer
+ """
+ self.ParentWindow = window
+ self.ItemsDict = OrderedDict([(item.GetVariable(), item)
+ for item in items])
+ self.Items = self.ItemsDict.viewvalues()
+
+ # Variable storing current highlight displayed in Viewer
+ self.Highlight = HIGHLIGHT_NONE
+ # List of buttons
+ self.Buttons = []
+
+ def __del__(self):
+ """
+ Destructor
+ """
+ # Remove reference to Debug Variable Panel
+ self.ParentWindow = None
+
+ def GetIndex(self):
+ """
+ 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)
+
+ def GetItems(self):
+ """
+ Return items displayed by Viewer
+ @return: List of items displayed in Viewer
+ """
+ return self.Items
+
+ def AddItem(self, item):
+ """
+ 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)
+
+ def ClearItems(self):
+ """
+ Clear list of items displayed by Viewer
+ """
+ # Unsubscribe every items of the list
+ for item in self.Items:
+ self.ParentWindow.RemoveDataConsumer(item)
+
+ # Clear list
+ self.Items.clear()
+
+ def ItemsIsEmpty(self):
+ """
+ Return if list of items displayed by Viewer is empty
+ @return: True if list is empty
+ """
+ return len(self.Items) == 0
+
+ def UnsubscribeObsoleteData(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)
+ self.RemoveItem(item)
+ else:
+ # If it exist, resubscribe and refresh data type
+ self.ParentWindow.AddDataConsumer(iec_path.upper(), item)
+ item.RefreshVariableType()
+
+ def ResetItemsData(self):
+ """
+ Reset data stored in every items displayed in Viewer
+ """
+ for item in self.Items:
+ item.ResetData()
+
+ def RefreshViewer(self):
+ """
+ Method that refresh the content displayed by Viewer
+ Need to be overridden by inherited classes
+ """
+ pass
+
+ 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:
+ return False
+
+ self.Highlight = highlight
+ return True
+
+ def GetButtons(self):
+ """
+ Return list of buttons defined in Viewer
+ @return: List of buttons
+ """
+ return self.Buttons
+
+ 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():
+ if button.HitTest(x, y):
+ return button
+ return None
+
+ def HandleButton(self, x, y):
+ """
+ Search for the button under point and if found execute associated
+ callback
+ @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)
+ if button is None:
+ return False
+
+ button.ProcessCallback()
+ return True
+
+ 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:
+ button.Show(show)
+
+ # Refresh button positions
+ self.RefreshButtonsPosition()
+ self.RefreshViewer()
+
+ def RefreshButtonsPosition(self):
+ """
+ Function that refresh buttons position in Viewer
+ """
+ # Get Viewer size
+ width, height = self.GetSize()
+
+ # Buttons are align right so we calculate buttons positions in
+ # reverse order
+ buttons = self.Buttons[:]
+ buttons.reverse()
+
+ # Position offset on x coordinate
+ x_offset = 0
+ for button in buttons:
+ # Buttons are stacked right, removing those that are not active
+ if button.IsEnabled():
+ # Update button position according to button width and offset
+ # on x coordinate
+ w, h = button.GetSize()
+ button.SetPosition(width - 5 - w - x_offset, 5)
+ # Update offset on x coordinate
+ x_offset += w + 2
+
+ 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
+ common graphics
+ @param buttons: List of buttons to draw if different from default
+ (default None)
+ """
+ # Get Viewer size
+ 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 buttons is None:
+ buttons = self.Buttons
+ # Draw buttons
+ for button in buttons:
+ button.Draw(dc)
+
+ # 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 = wx.MemoryDC()
+ srcDC.SelectObject(srcBmp)
+
+ dc.Blit(destBBox.x, destBBox.y,
+ int(destBBox.width), int(destBBox.height),
+ srcDC, srcX, srcY)
+
+ def OnEnter(self, event):
+ """
+ Function called when entering Viewer
+ @param event: wx.MouseEvent
+ """
+ # Display buttons
+ self.ShowButtons(True)
+ event.Skip()
+
+ def OnLeave(self, event):
+ """
+ Function called when leaving Viewer
+ @param event: wx.MouseEvent
+ """
+ # Check that mouse position is inside Viewer and hide buttons
+ x, y = event.GetPosition()
+ width, height = self.GetSize()
+ if not (0 < x < width - 1 and 0 < y < height - 1):
+ self.ShowButtons(False)
+ event.Skip()
+
+ def OnCloseButton(self):
+ """
+ Function called when Close button is pressed
+ """
+ wx.CallAfter(self.ParentWindow.DeleteValue, self)
+
+ def OnForceButton(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
+ # in another Viewer
+ 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
+ """
+ # Get Viewer size
+ width, height = self.GetSize()
+
+ # Mouse is in the first half of Viewer
+ if y < height / 2:
+ # 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
+ else:
+ self.SetHighlight(HIGHLIGHT_NONE)
+ self.ParentWindow.HighlightPreviousViewer(self)
+
+ # Mouse is in the second half of Viewer, draw drop after highlight
+ else:
+ 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
+ pass
+
+ def OnResize(self, event):
+ """
+ Function called when Viewer size changed
+ @param event: wx.ResizeEvent
+ """
+ # Refresh button positions
+ self.RefreshButtonsPosition()
+ self.ParentWindow.ForceRefresh()
+ event.Skip()
+
+ 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
+ if iec_type is None:
+ return
+
+ # 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(),
+ dialog.GetValue())
+
+ def ReleaseValue(self, item):
+ """
+ Release value of item given
+ @param item: Item to release value
+ """
+ self.ParentWindow.ReleaseDataValue(
+ item.GetVariable().upper())