Laurent@814: #!/usr/bin/env python Laurent@814: # -*- coding: utf-8 -*- Laurent@814: andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. Laurent@814: # andrej@1571: # Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD Laurent@814: # andrej@1571: # See COPYING file for copyrights details. Laurent@814: # andrej@1571: # This program is free software; you can redistribute it and/or andrej@1571: # modify it under the terms of the GNU General Public License andrej@1571: # as published by the Free Software Foundation; either version 2 andrej@1571: # of the License, or (at your option) any later version. Laurent@814: # andrej@1571: # This program is distributed in the hope that it will be useful, andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1571: # GNU General Public License for more details. Laurent@814: # andrej@1571: # You should have received a copy of the GNU General Public License andrej@1571: # along with this program; if not, write to the Free Software andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Laurent@814: Laurent@1200: from collections import OrderedDict Laurent@814: Laurent@814: import wx Laurent@1198: from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap Laurent@1200: Laurent@814: from dialogs.ForceVariableDialog import ForceVariableDialog Laurent@1200: Laurent@1200: # Viewer highlight types Laurent@1198: [HIGHLIGHT_NONE, Laurent@1198: HIGHLIGHT_BEFORE, Laurent@1198: HIGHLIGHT_AFTER, Laurent@1198: HIGHLIGHT_LEFT, Laurent@1198: HIGHLIGHT_RIGHT, Laurent@1198: HIGHLIGHT_RESIZE] = range(6) Laurent@1198: Laurent@1200: # Viewer highlight styles andrej@1823: HIGHLIGHT = { andrej@1823: } Laurent@1198: andrej@1782: # ------------------------------------------------------------------------------- Laurent@1200: # Base Debug Variable Viewer Class andrej@1782: # ------------------------------------------------------------------------------- Laurent@1200: Laurent@1198: andrej@1831: class DebugVariableViewer(object): andrej@1736: """ andrej@1736: Class that implements a generic viewer that display a list of variable values andrej@1736: This class has to be inherited to effectively display variable values andrej@1736: """ andrej@1730: andrej@1852: def __init__(self, window, items=None): Laurent@1200: """ Laurent@1200: Constructor Laurent@1200: @param window: Reference to the Debug Variable Panel Laurent@1200: @param items: List of DebugVariableItem displayed by Viewer Laurent@1200: """ Laurent@1198: self.ParentWindow = window andrej@1852: items = [] if items is None else items Laurent@1200: self.ItemsDict = OrderedDict([(item.GetVariable(), item) Laurent@1200: for item in items]) Laurent@1200: self.Items = self.ItemsDict.viewvalues() andrej@1730: Laurent@1200: # Variable storing current highlight displayed in Viewer Laurent@1198: self.Highlight = HIGHLIGHT_NONE Laurent@1200: # List of buttons Laurent@1198: self.Buttons = [] andrej@1807: self.InitHighlightPensBrushes() andrej@1730: Laurent@1198: def __del__(self): Laurent@1200: """ Laurent@1200: Destructor Laurent@1200: """ Laurent@1200: # Remove reference to Debug Variable Panel Laurent@1198: self.ParentWindow = None andrej@1730: andrej@1807: def InitHighlightPensBrushes(self): andrej@1807: """ andrej@1807: Init global pens and brushes andrej@1807: """ andrej@1823: if not HIGHLIGHT: andrej@1823: HIGHLIGHT['DROP_PEN'] = wx.Pen(wx.Colour(0, 128, 255)) andrej@1823: HIGHLIGHT['DROP_BRUSH'] = wx.Brush(wx.Colour(0, 128, 255, 128)) andrej@1823: HIGHLIGHT['RESIZE_PEN'] = wx.Pen(wx.Colour(200, 200, 200)) andrej@1823: HIGHLIGHT['RESIZE_BRUSH'] = wx.Brush(wx.Colour(200, 200, 200)) andrej@1807: Laurent@1198: def GetIndex(self): Laurent@1200: """ Laurent@1200: Return position of Viewer in Debug Variable Panel Laurent@1200: @return: Position of Viewer Laurent@1200: """ Laurent@1198: return self.ParentWindow.GetViewerIndex(self) andrej@1730: Laurent@1198: def GetItem(self, variable): Laurent@1200: """ Laurent@1200: Return item storing values of a variable Laurent@1200: @param variable: Variable path Laurent@1200: @return: Item storing values of this variable Laurent@1200: """ Laurent@1200: return self.ItemsDict.get(variable, None) andrej@1730: Laurent@1198: def GetItems(self): Laurent@1200: """ Laurent@1200: Return items displayed by Viewer Laurent@1200: @return: List of items displayed in Viewer Laurent@1200: """ Laurent@1202: return self.ItemsDict.values() andrej@1730: Laurent@1198: def AddItem(self, item): Laurent@1200: """ Laurent@1200: Add an item to the list of items displayed by Viewer Laurent@1200: @param item: Item to add to the list Laurent@1200: """ Laurent@1200: self.ItemsDict[item.GetVariable()] = item andrej@1730: Laurent@1198: def RemoveItem(self, item): Laurent@1200: """ Laurent@1200: Remove an item from the list of items displayed by Viewer Laurent@1200: @param item: Item to remove from the list Laurent@1200: """ Laurent@1200: self.ItemsDict.pop(item.GetVariable(), None) andrej@1730: Laurent@1200: def ClearItems(self): Laurent@1200: """ Laurent@1200: Clear list of items displayed by Viewer Laurent@1200: """ Laurent@1200: # Unsubscribe every items of the list Laurent@1198: for item in self.Items: Laurent@1198: self.ParentWindow.RemoveDataConsumer(item) andrej@1730: Laurent@1200: # Clear list Laurent@1206: self.ItemsDict.clear() andrej@1730: Laurent@1200: def ItemsIsEmpty(self): Laurent@1200: """ Laurent@1200: Return if list of items displayed by Viewer is empty Laurent@1200: @return: True if list is empty Laurent@1200: """ Laurent@1198: return len(self.Items) == 0 andrej@1730: Laurent@1207: def SubscribeAllDataConsumers(self): Laurent@1200: """ Laurent@1200: Function that unsubscribe and remove every item that store values of Laurent@1200: a variable that doesn't exist in PLC anymore Laurent@1200: """ Laurent@1200: for item in self.ItemsDict.values()[:]: Laurent@1198: iec_path = item.GetVariable() andrej@1730: Laurent@1200: # Check that variablepath exist in PLC Laurent@1198: if self.ParentWindow.GetDataType(iec_path) is None: Laurent@1200: # If not, unsubscribe and remove it Laurent@1198: self.ParentWindow.RemoveDataConsumer(item) Laurent@1198: self.RemoveItem(item) Laurent@1198: else: Laurent@1200: # If it exist, resubscribe and refresh data type Laurent@1365: self.ParentWindow.AddDataConsumer(iec_path.upper(), item, True) Laurent@1198: item.RefreshVariableType() andrej@1730: Laurent@1200: def ResetItemsData(self): Laurent@1200: """ Laurent@1200: Reset data stored in every items displayed in Viewer Laurent@1200: """ Laurent@1198: for item in self.Items: Laurent@1198: item.ResetData() andrej@1730: Laurent@1212: def GetItemsMinCommonTick(self): Laurent@1212: """ Laurent@1212: Return the minimum tick common to all iems displayed in Viewer Laurent@1212: @return: Minimum common tick between items Laurent@1212: """ andrej@1730: return reduce(max, [item.GetData()[0, 0] Laurent@1212: for item in self.Items Laurent@1212: if len(item.GetData()) > 0], 0) andrej@1730: Laurent@1198: def RefreshViewer(self): Laurent@1200: """ Laurent@1200: Method that refresh the content displayed by Viewer Laurent@1200: Need to be overridden by inherited classes Laurent@1200: """ Laurent@1198: pass andrej@1730: Laurent@1198: def SetHighlight(self, highlight): Laurent@1200: """ Laurent@1200: Set Highlight type displayed in Viewer Laurent@1200: @return: True if highlight has changed Laurent@1200: """ Laurent@1200: # Return immediately if highlight don't change Laurent@1200: if self.Highlight == highlight: Laurent@1200: return False andrej@1730: Laurent@1200: self.Highlight = highlight Laurent@1200: return True andrej@1730: Laurent@1198: def GetButtons(self): Laurent@1200: """ Laurent@1200: Return list of buttons defined in Viewer Laurent@1200: @return: List of buttons Laurent@1200: """ Laurent@1198: return self.Buttons andrej@1730: Laurent@1200: def IsOverButton(self, x, y): Laurent@1200: """ Laurent@1200: Return if point is over one button of Viewer Laurent@1200: @param x: X coordinate of point Laurent@1200: @param y: Y coordinate of point Laurent@1200: @return: button where point is over Laurent@1200: """ Laurent@1198: for button in self.GetButtons(): Laurent@1198: if button.HitTest(x, y): Laurent@1200: return button Laurent@1200: return None andrej@1730: Laurent@1200: def HandleButton(self, x, y): Laurent@1200: """ Laurent@1200: Search for the button under point and if found execute associated Laurent@1200: callback Laurent@1200: @param x: X coordinate of point Laurent@1200: @param y: Y coordinate of point Laurent@1200: @return: True if a button was found and callback executed Laurent@1200: """ Laurent@1200: button = self.IsOverButton(x, y) Laurent@1200: if button is None: Laurent@1200: return False andrej@1730: Laurent@1200: button.ProcessCallback() Laurent@1200: return True andrej@1730: Laurent@1198: def ShowButtons(self, show): Laurent@1200: """ Laurent@1200: Set display state of buttons in Viewer Laurent@1200: @param show: Display state (True if buttons must be displayed) Laurent@1200: """ Laurent@1200: # Change display of every buttons Laurent@1198: for button in self.Buttons: Laurent@1200: button.Show(show) andrej@1730: Laurent@1200: # Refresh button positions Laurent@1200: self.RefreshButtonsPosition() Laurent@1200: self.RefreshViewer() andrej@1730: Laurent@1200: def RefreshButtonsPosition(self): Laurent@1200: """ Laurent@1200: Function that refresh buttons position in Viewer Laurent@1200: """ Laurent@1200: # Get Viewer size andrej@1847: width, _height = self.GetSize() andrej@1730: Laurent@1200: # Buttons are align right so we calculate buttons positions in Laurent@1200: # reverse order Laurent@1200: buttons = self.Buttons[:] Laurent@1200: buttons.reverse() andrej@1730: Laurent@1200: # Position offset on x coordinate Laurent@1200: x_offset = 0 Laurent@1200: for button in buttons: andrej@1730: # Buttons are stacked right, removing those that are not active Laurent@1200: if button.IsEnabled(): Laurent@1200: # Update button position according to button width and offset Laurent@1200: # on x coordinate andrej@1847: w, _h = button.GetSize() Laurent@1200: button.SetPosition(width - 5 - w - x_offset, 5) Laurent@1200: # Update offset on x coordinate Laurent@1200: x_offset += w + 2 andrej@1730: Laurent@1198: def DrawCommonElements(self, dc, buttons=None): Laurent@1200: """ Laurent@1200: Function that draw common graphics for every Viewers Laurent@1200: @param dc: wx.DC object corresponding to Device context where drawing Laurent@1200: common graphics Laurent@1200: @param buttons: List of buttons to draw if different from default Laurent@1200: (default None) Laurent@1200: """ Laurent@1200: # Get Viewer size Laurent@1198: width, height = self.GetSize() andrej@1730: Laurent@1200: # Set dc styling for drop before or drop after highlight andrej@1823: dc.SetPen(HIGHLIGHT['DROP_PEN']) andrej@1823: dc.SetBrush(HIGHLIGHT['DROP_BRUSH']) andrej@1730: Laurent@1200: # Draw line at upper side of Viewer if highlight is drop before Laurent@1200: if self.Highlight == HIGHLIGHT_BEFORE: Laurent@1198: dc.DrawLine(0, 1, width - 1, 1) andrej@1730: Laurent@1200: # Draw line at lower side of Viewer if highlight is drop before Laurent@1200: elif self.Highlight == HIGHLIGHT_AFTER: Laurent@1198: dc.DrawLine(0, height - 1, width - 1, height - 1) andrej@1730: Laurent@1200: # If no specific buttons are defined, get default buttons Laurent@1198: if buttons is None: Laurent@1198: buttons = self.Buttons Laurent@1200: # Draw buttons Laurent@1198: for button in buttons: Laurent@1198: button.Draw(dc) andrej@1730: Laurent@1200: # If graph dragging is processing Laurent@1198: if self.ParentWindow.IsDragging(): Laurent@1198: destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self) Laurent@1198: srcPos = self.ParentWindow.GetDraggingAxesPosition(self) Laurent@1198: if destBBox.width > 0 and destBBox.height > 0: Laurent@1198: srcPanel = self.ParentWindow.DraggingAxesPanel Laurent@1198: srcBBox = srcPanel.GetAxesBoundingBox() andrej@1730: Laurent@1200: srcX = srcBBox.x - (srcPos.x if destBBox.x == 0 else 0) Laurent@1200: srcY = srcBBox.y - (srcPos.y if destBBox.y == 0 else 0) andrej@1730: Laurent@1200: srcBmp = _convert_agg_to_wx_bitmap( Laurent@1200: srcPanel.get_renderer(), None) Laurent@1198: srcDC = wx.MemoryDC() Laurent@1198: srcDC.SelectObject(srcBmp) andrej@1730: andrej@1730: dc.Blit(destBBox.x, destBBox.y, andrej@1730: int(destBBox.width), int(destBBox.height), Laurent@1198: srcDC, srcX, srcY) andrej@1730: Laurent@1198: def OnEnter(self, event): Laurent@1200: """ Laurent@1200: Function called when entering Viewer andrej@1730: @param event: wx.MouseEvent Laurent@1200: """ Laurent@1200: # Display buttons Laurent@1198: self.ShowButtons(True) Laurent@1198: event.Skip() andrej@1730: Laurent@1198: def OnLeave(self, event): Laurent@1200: """ Laurent@1200: Function called when leaving Viewer andrej@1730: @param event: wx.MouseEvent Laurent@1200: """ Laurent@1209: # Hide buttons Laurent@1209: self.ShowButtons(False) Laurent@1198: event.Skip() andrej@1730: Laurent@1198: def OnCloseButton(self): Laurent@1200: """ Laurent@1200: Function called when Close button is pressed Laurent@1200: """ Laurent@1198: wx.CallAfter(self.ParentWindow.DeleteValue, self) andrej@1730: Laurent@1198: def OnForceButton(self): Laurent@1200: """ Laurent@1200: Function called when Force button is pressed Laurent@1200: """ Laurent@1200: self.ForceValue(self.ItemsDict.values()[0]) andrej@1730: Laurent@1198: def OnReleaseButton(self): Laurent@1200: """ Laurent@1200: Function called when Release button is pressed Laurent@1200: """ Laurent@1200: self.ReleaseValue(self.ItemsDict.values()[0]) andrej@1730: Laurent@1198: def OnMouseDragging(self, x, y): Laurent@1200: """ Laurent@1200: Function called when mouse is dragged over Viewer Laurent@1200: @param x: X coordinate of mouse pointer Laurent@1200: @param y: Y coordinate of mouse pointer Laurent@1200: """ Laurent@1198: xw, yw = self.GetPosition() Laurent@1200: # Refresh highlight in Debug Variable Panel (highlight can be displayed Laurent@1200: # in another Viewer Laurent@1198: self.ParentWindow.RefreshHighlight(x + xw, y + yw) andrej@1730: Laurent@1200: def RefreshHighlight(self, x, y): Laurent@1200: """ Laurent@1200: Function called by Debug Variable Panel asking Viewer to refresh Laurent@1200: highlight according to mouse position Laurent@1200: @param x: X coordinate of mouse pointer Laurent@1200: @param y: Y coordinate of mouse pointer Laurent@1200: """ Laurent@1200: # Get Viewer size andrej@1847: _width, height = self.GetSize() andrej@1730: Laurent@1200: # Mouse is in the first half of Viewer Laurent@1198: if y < height / 2: Laurent@1200: # If Viewer is the upper one, draw drop before highlight Laurent@1198: if self.ParentWindow.IsViewerFirst(self): Laurent@1198: self.SetHighlight(HIGHLIGHT_BEFORE) andrej@1730: Laurent@1200: # Else draw drop after highlight in previous Viewer Laurent@1198: else: Laurent@1198: self.SetHighlight(HIGHLIGHT_NONE) Laurent@1198: self.ParentWindow.HighlightPreviousViewer(self) andrej@1730: andrej@1730: # Mouse is in the second half of Viewer, draw drop after highlight Laurent@1198: else: Laurent@1198: self.SetHighlight(HIGHLIGHT_AFTER) andrej@1730: Laurent@1198: def OnEraseBackground(self, event): Laurent@1200: """ Laurent@1200: Function called when Viewer background is going to be erase Laurent@1200: @param event: wx.EraseEvent Laurent@1200: """ Laurent@1200: # Prevent flicker on Windows Laurent@1198: pass andrej@1730: Laurent@1198: def OnResize(self, event): Laurent@1200: """ Laurent@1200: Function called when Viewer size changed Laurent@1200: @param event: wx.ResizeEvent Laurent@1200: """ Laurent@1200: # Refresh button positions Laurent@1200: self.RefreshButtonsPosition() Laurent@1200: self.ParentWindow.ForceRefresh() Laurent@1198: event.Skip() andrej@1730: Laurent@1198: def ForceValue(self, item): Laurent@1200: """ Laurent@1200: Force value of item given Laurent@1200: @param item: Item to force value Laurent@1200: """ Laurent@1200: # Check variable data type Laurent@1198: iec_path = item.GetVariable() Laurent@1198: iec_type = self.ParentWindow.GetDataType(iec_path) Laurent@1200: # Return immediately if not found Laurent@1200: if iec_type is None: Laurent@1200: return andrej@1730: Laurent@1200: # Open a dialog to enter varaible forced value Laurent@1200: dialog = ForceVariableDialog(self, iec_type, str(item.GetValue())) Laurent@1200: if dialog.ShowModal() == wx.ID_OK: Laurent@1200: self.ParentWindow.ForceDataValue(iec_path.upper(), Laurent@1200: dialog.GetValue()) andrej@1730: Laurent@1198: def ReleaseValue(self, item): Laurent@1200: """ Laurent@1200: Release value of item given Laurent@1200: @param item: Item to release value Laurent@1200: """ Laurent@1200: self.ParentWindow.ReleaseDataValue( Laurent@1200: item.GetVariable().upper())