diff -r fc0e7d80494f -r 501cb0bb4c05 controls/DebugVariablePanel/DebugVariableViewer.py --- /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())