controls/DebugVariablePanel/DebugVariableViewer.py
changeset 1200 501cb0bb4c05
parent 1199 fc0e7d80494f
child 1202 3d8c87ab2b5d
equal deleted inserted replaced
1199:fc0e7d80494f 1200:501cb0bb4c05
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from collections import OrderedDict
       
    26 
       
    27 import wx
       
    28 
       
    29 import matplotlib
       
    30 matplotlib.use('WX')
       
    31 import matplotlib.pyplot
       
    32 from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
       
    33 
       
    34 from dialogs.ForceVariableDialog import ForceVariableDialog
       
    35 
       
    36 # Viewer highlight types
       
    37 [HIGHLIGHT_NONE,
       
    38  HIGHLIGHT_BEFORE,
       
    39  HIGHLIGHT_AFTER,
       
    40  HIGHLIGHT_LEFT,
       
    41  HIGHLIGHT_RIGHT,
       
    42  HIGHLIGHT_RESIZE] = range(6)
       
    43 
       
    44 # Viewer highlight styles
       
    45 HIGHLIGHT_DROP_PEN = wx.Pen(wx.Colour(0, 128, 255))
       
    46 HIGHLIGHT_DROP_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128))
       
    47 HIGHLIGHT_RESIZE_PEN = wx.Pen(wx.Colour(200, 200, 200))
       
    48 HIGHLIGHT_RESIZE_BRUSH = wx.Brush(wx.Colour(200, 200, 200))
       
    49 
       
    50 #-------------------------------------------------------------------------------
       
    51 #                        Base Debug Variable Viewer Class
       
    52 #-------------------------------------------------------------------------------
       
    53 
       
    54 """
       
    55 Class that implements a generic viewer that display a list of variable values
       
    56 This class has to be inherited to effectively display variable values
       
    57 """
       
    58 
       
    59 class DebugVariableViewer:
       
    60     
       
    61     def __init__(self, window, items=[]):
       
    62         """
       
    63         Constructor
       
    64         @param window: Reference to the Debug Variable Panel
       
    65         @param items: List of DebugVariableItem displayed by Viewer
       
    66         """
       
    67         self.ParentWindow = window
       
    68         self.ItemsDict = OrderedDict([(item.GetVariable(), item)
       
    69                                       for item in items])
       
    70         self.Items = self.ItemsDict.viewvalues()
       
    71         
       
    72         # Variable storing current highlight displayed in Viewer
       
    73         self.Highlight = HIGHLIGHT_NONE
       
    74         # List of buttons
       
    75         self.Buttons = []
       
    76     
       
    77     def __del__(self):
       
    78         """
       
    79         Destructor
       
    80         """
       
    81         # Remove reference to Debug Variable Panel
       
    82         self.ParentWindow = None
       
    83     
       
    84     def GetIndex(self):
       
    85         """
       
    86         Return position of Viewer in Debug Variable Panel
       
    87         @return: Position of Viewer
       
    88         """
       
    89         return self.ParentWindow.GetViewerIndex(self)
       
    90     
       
    91     def GetItem(self, variable):
       
    92         """
       
    93         Return item storing values of a variable
       
    94         @param variable: Variable path
       
    95         @return: Item storing values of this variable
       
    96         """
       
    97         return self.ItemsDict.get(variable, None)
       
    98     
       
    99     def GetItems(self):
       
   100         """
       
   101         Return items displayed by Viewer
       
   102         @return: List of items displayed in Viewer
       
   103         """
       
   104         return self.Items
       
   105     
       
   106     def AddItem(self, item):
       
   107         """
       
   108         Add an item to the list of items displayed by Viewer
       
   109         @param item: Item to add to the list
       
   110         """
       
   111         self.ItemsDict[item.GetVariable()] = item
       
   112         
       
   113     def RemoveItem(self, item):
       
   114         """
       
   115         Remove an item from the list of items displayed by Viewer
       
   116         @param item: Item to remove from the list
       
   117         """
       
   118         self.ItemsDict.pop(item.GetVariable(), None)
       
   119     
       
   120     def ClearItems(self):
       
   121         """
       
   122         Clear list of items displayed by Viewer
       
   123         """
       
   124         # Unsubscribe every items of the list
       
   125         for item in self.Items:
       
   126             self.ParentWindow.RemoveDataConsumer(item)
       
   127         
       
   128         # Clear list
       
   129         self.Items.clear()
       
   130         
       
   131     def ItemsIsEmpty(self):
       
   132         """
       
   133         Return if list of items displayed by Viewer is empty
       
   134         @return: True if list is empty
       
   135         """
       
   136         return len(self.Items) == 0
       
   137     
       
   138     def UnsubscribeObsoleteData(self):
       
   139         """
       
   140         Function that unsubscribe and remove every item that store values of
       
   141         a variable that doesn't exist in PLC anymore
       
   142         """
       
   143         for item in self.ItemsDict.values()[:]:
       
   144             iec_path = item.GetVariable()
       
   145             
       
   146             # Check that variablepath exist in PLC
       
   147             if self.ParentWindow.GetDataType(iec_path) is None:
       
   148                 # If not, unsubscribe and remove it
       
   149                 self.ParentWindow.RemoveDataConsumer(item)
       
   150                 self.RemoveItem(item)
       
   151             else:
       
   152                 # If it exist, resubscribe and refresh data type
       
   153                 self.ParentWindow.AddDataConsumer(iec_path.upper(), item)
       
   154                 item.RefreshVariableType()
       
   155     
       
   156     def ResetItemsData(self):
       
   157         """
       
   158         Reset data stored in every items displayed in Viewer
       
   159         """
       
   160         for item in self.Items:
       
   161             item.ResetData()
       
   162     
       
   163     def RefreshViewer(self):
       
   164         """
       
   165         Method that refresh the content displayed by Viewer
       
   166         Need to be overridden by inherited classes
       
   167         """
       
   168         pass
       
   169     
       
   170     def SetHighlight(self, highlight):
       
   171         """
       
   172         Set Highlight type displayed in Viewer
       
   173         @return: True if highlight has changed
       
   174         """
       
   175         # Return immediately if highlight don't change
       
   176         if self.Highlight == highlight:
       
   177             return False
       
   178         
       
   179         self.Highlight = highlight
       
   180         return True
       
   181     
       
   182     def GetButtons(self):
       
   183         """
       
   184         Return list of buttons defined in Viewer
       
   185         @return: List of buttons
       
   186         """
       
   187         return self.Buttons
       
   188     
       
   189     def IsOverButton(self, x, y):
       
   190         """
       
   191         Return if point is over one button of Viewer
       
   192         @param x: X coordinate of point
       
   193         @param y: Y coordinate of point
       
   194         @return: button where point is over
       
   195         """
       
   196         for button in self.GetButtons():
       
   197             if button.HitTest(x, y):
       
   198                 return button
       
   199         return None
       
   200     
       
   201     def HandleButton(self, x, y):
       
   202         """
       
   203         Search for the button under point and if found execute associated
       
   204         callback
       
   205         @param x: X coordinate of point
       
   206         @param y: Y coordinate of point
       
   207         @return: True if a button was found and callback executed
       
   208         """
       
   209         button = self.IsOverButton(x, y)
       
   210         if button is None:
       
   211             return False
       
   212         
       
   213         button.ProcessCallback()
       
   214         return True
       
   215     
       
   216     def ShowButtons(self, show):
       
   217         """
       
   218         Set display state of buttons in Viewer
       
   219         @param show: Display state (True if buttons must be displayed)
       
   220         """
       
   221         # Change display of every buttons
       
   222         for button in self.Buttons:
       
   223             button.Show(show)
       
   224         
       
   225         # Refresh button positions
       
   226         self.RefreshButtonsPosition()
       
   227         self.RefreshViewer()
       
   228     
       
   229     def RefreshButtonsPosition(self):
       
   230         """
       
   231         Function that refresh buttons position in Viewer
       
   232         """
       
   233         # Get Viewer size
       
   234         width, height = self.GetSize()
       
   235         
       
   236         # Buttons are align right so we calculate buttons positions in
       
   237         # reverse order
       
   238         buttons = self.Buttons[:]
       
   239         buttons.reverse()
       
   240         
       
   241         # Position offset on x coordinate
       
   242         x_offset = 0
       
   243         for button in buttons:
       
   244             # Buttons are stacked right, removing those that are not active 
       
   245             if button.IsEnabled():
       
   246                 # Update button position according to button width and offset
       
   247                 # on x coordinate
       
   248                 w, h = button.GetSize()
       
   249                 button.SetPosition(width - 5 - w - x_offset, 5)
       
   250                 # Update offset on x coordinate
       
   251                 x_offset += w + 2
       
   252     
       
   253     def DrawCommonElements(self, dc, buttons=None):
       
   254         """
       
   255         Function that draw common graphics for every Viewers
       
   256         @param dc: wx.DC object corresponding to Device context where drawing
       
   257         common graphics
       
   258         @param buttons: List of buttons to draw if different from default
       
   259         (default None)
       
   260         """
       
   261         # Get Viewer size
       
   262         width, height = self.GetSize()
       
   263         
       
   264         # Set dc styling for drop before or drop after highlight
       
   265         dc.SetPen(HIGHLIGHT_DROP_PEN)
       
   266         dc.SetBrush(HIGHLIGHT_DROP_BRUSH)
       
   267         
       
   268         # Draw line at upper side of Viewer if highlight is drop before
       
   269         if self.Highlight == HIGHLIGHT_BEFORE:
       
   270             dc.DrawLine(0, 1, width - 1, 1)
       
   271         
       
   272         # Draw line at lower side of Viewer if highlight is drop before
       
   273         elif self.Highlight == HIGHLIGHT_AFTER:
       
   274             dc.DrawLine(0, height - 1, width - 1, height - 1)
       
   275         
       
   276         # If no specific buttons are defined, get default buttons
       
   277         if buttons is None:
       
   278             buttons = self.Buttons
       
   279         # Draw buttons
       
   280         for button in buttons:
       
   281             button.Draw(dc)
       
   282         
       
   283         # If graph dragging is processing
       
   284         if self.ParentWindow.IsDragging():
       
   285             destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
       
   286             srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
       
   287             if destBBox.width > 0 and destBBox.height > 0:
       
   288                 srcPanel = self.ParentWindow.DraggingAxesPanel
       
   289                 srcBBox = srcPanel.GetAxesBoundingBox()
       
   290                 
       
   291                 srcX = srcBBox.x - (srcPos.x if destBBox.x == 0 else 0)
       
   292                 srcY = srcBBox.y - (srcPos.y if destBBox.y == 0 else 0)
       
   293                 
       
   294                 srcBmp = _convert_agg_to_wx_bitmap(
       
   295                             srcPanel.get_renderer(), None)
       
   296                 srcDC = wx.MemoryDC()
       
   297                 srcDC.SelectObject(srcBmp)
       
   298                 
       
   299                 dc.Blit(destBBox.x, destBBox.y, 
       
   300                         int(destBBox.width), int(destBBox.height), 
       
   301                         srcDC, srcX, srcY)
       
   302     
       
   303     def OnEnter(self, event):
       
   304         """
       
   305         Function called when entering Viewer
       
   306         @param event: wx.MouseEvent 
       
   307         """
       
   308         # Display buttons
       
   309         self.ShowButtons(True)
       
   310         event.Skip()
       
   311         
       
   312     def OnLeave(self, event):
       
   313         """
       
   314         Function called when leaving Viewer
       
   315         @param event: wx.MouseEvent 
       
   316         """
       
   317         # Check that mouse position is inside Viewer and hide buttons
       
   318         x, y = event.GetPosition()
       
   319         width, height = self.GetSize()
       
   320         if not (0 < x < width - 1 and 0 < y < height - 1):
       
   321             self.ShowButtons(False)
       
   322         event.Skip()
       
   323     
       
   324     def OnCloseButton(self):
       
   325         """
       
   326         Function called when Close button is pressed
       
   327         """
       
   328         wx.CallAfter(self.ParentWindow.DeleteValue, self)
       
   329     
       
   330     def OnForceButton(self):
       
   331         """
       
   332         Function called when Force button is pressed
       
   333         """
       
   334         self.ForceValue(self.ItemsDict.values()[0])
       
   335         
       
   336     def OnReleaseButton(self):
       
   337         """
       
   338         Function called when Release button is pressed
       
   339         """
       
   340         self.ReleaseValue(self.ItemsDict.values()[0])
       
   341     
       
   342     def OnMouseDragging(self, x, y):
       
   343         """
       
   344         Function called when mouse is dragged over Viewer
       
   345         @param x: X coordinate of mouse pointer
       
   346         @param y: Y coordinate of mouse pointer
       
   347         """
       
   348         xw, yw = self.GetPosition()
       
   349         # Refresh highlight in Debug Variable Panel (highlight can be displayed
       
   350         # in another Viewer
       
   351         self.ParentWindow.RefreshHighlight(x + xw, y + yw)
       
   352     
       
   353     def RefreshHighlight(self, x, y):
       
   354         """
       
   355         Function called by Debug Variable Panel asking Viewer to refresh
       
   356         highlight according to mouse position
       
   357         @param x: X coordinate of mouse pointer
       
   358         @param y: Y coordinate of mouse pointer
       
   359         """
       
   360         # Get Viewer size
       
   361         width, height = self.GetSize()
       
   362         
       
   363         # Mouse is in the first half of Viewer
       
   364         if y < height / 2:
       
   365             # If Viewer is the upper one, draw drop before highlight
       
   366             if self.ParentWindow.IsViewerFirst(self):
       
   367                 self.SetHighlight(HIGHLIGHT_BEFORE)
       
   368             
       
   369             # Else draw drop after highlight in previous Viewer
       
   370             else:
       
   371                 self.SetHighlight(HIGHLIGHT_NONE)
       
   372                 self.ParentWindow.HighlightPreviousViewer(self)
       
   373         
       
   374         # Mouse is in the second half of Viewer, draw drop after highlight 
       
   375         else:
       
   376             self.SetHighlight(HIGHLIGHT_AFTER)
       
   377     
       
   378     def OnEraseBackground(self, event):
       
   379         """
       
   380         Function called when Viewer background is going to be erase
       
   381         @param event: wx.EraseEvent
       
   382         """
       
   383         # Prevent flicker on Windows
       
   384         pass
       
   385     
       
   386     def OnResize(self, event):
       
   387         """
       
   388         Function called when Viewer size changed
       
   389         @param event: wx.ResizeEvent
       
   390         """
       
   391         # Refresh button positions
       
   392         self.RefreshButtonsPosition()
       
   393         self.ParentWindow.ForceRefresh()
       
   394         event.Skip()
       
   395     
       
   396     def ForceValue(self, item):
       
   397         """
       
   398         Force value of item given
       
   399         @param item: Item to force value
       
   400         """
       
   401         # Check variable data type
       
   402         iec_path = item.GetVariable()
       
   403         iec_type = self.ParentWindow.GetDataType(iec_path)
       
   404         # Return immediately if not found
       
   405         if iec_type is None:
       
   406             return
       
   407         
       
   408         # Open a dialog to enter varaible forced value
       
   409         dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
       
   410         if dialog.ShowModal() == wx.ID_OK:
       
   411             self.ParentWindow.ForceDataValue(iec_path.upper(),
       
   412                                              dialog.GetValue())
       
   413     
       
   414     def ReleaseValue(self, item):
       
   415         """
       
   416         Release value of item given
       
   417         @param item: Item to release value
       
   418         """
       
   419         self.ParentWindow.ReleaseDataValue(
       
   420                 item.GetVariable().upper())