controls/DebugVariablePanel/DebugVariableGraphicPanel.py
changeset 1200 501cb0bb4c05
parent 1199 fc0e7d80494f
child 1202 3d8c87ab2b5d
equal deleted inserted replaced
1199:fc0e7d80494f 1200:501cb0bb4c05
    21 #You should have received a copy of the GNU General Public
    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
    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
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    24 
    24 
    25 from types import TupleType
    25 from types import TupleType
    26 from time import time as gettime
       
    27 import math
    26 import math
    28 import numpy
    27 import numpy
    29 
    28 
    30 import wx
    29 import wx
    31 import wx.lib.buttons
    30 import wx.lib.buttons
    32 
    31 
    33 import matplotlib
    32 import matplotlib
    34 matplotlib.use('WX')
    33 matplotlib.use('WX')
    35 import matplotlib.pyplot
    34 import matplotlib.pyplot
    36 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
       
    37 from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
    35 from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
    38 from matplotlib.backends.backend_agg import FigureCanvasAgg
    36 
    39 from mpl_toolkits.mplot3d import Axes3D
    37 from editors.DebugViewer import DebugViewer
    40 color_cycle = ['r', 'b', 'g', 'm', 'y', 'k']
       
    41 cursor_color = '#800080'
       
    42 
       
    43 from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
       
    44 from dialogs.ForceVariableDialog import ForceVariableDialog
       
    45 from util.BitmapLibrary import GetBitmap
    38 from util.BitmapLibrary import GetBitmap
    46 
    39 
    47 from DebugVariableItem import DebugVariableItem
    40 from DebugVariableItem import DebugVariableItem
    48 from GraphButton import GraphButton
    41 from DebugVariableTextViewer import DebugVariableTextViewer
       
    42 from DebugVariableGraphicViewer import *
    49 
    43 
    50 class DebugVariableDropTarget(wx.TextDropTarget):
    44 class DebugVariableDropTarget(wx.TextDropTarget):
    51     
    45     
    52     def __init__(self, parent, control=None):
    46     def __init__(self, parent):
    53         wx.TextDropTarget.__init__(self)
    47         wx.TextDropTarget.__init__(self)
    54         self.ParentWindow = parent
    48         self.ParentWindow = parent
    55         self.ParentControl = control
       
    56     
    49     
    57     def __del__(self):
    50     def __del__(self):
    58         self.ParentWindow = None
    51         self.ParentWindow = None
    59         self.ParentControl = None
       
    60     
    52     
    61     def OnDragOver(self, x, y, d):
    53     def OnDragOver(self, x, y, d):
    62         if self.ParentControl is not None:
    54         self.ParentControl.OnMouseDragging(x, y)
    63             self.ParentControl.OnMouseDragging(x, y)
    55         self.ParentWindow.RefreshHighlight(x, y)
    64         else:
       
    65             self.ParentWindow.RefreshHighlight(x, y)
       
    66         return wx.TextDropTarget.OnDragOver(self, x, y, d)
    56         return wx.TextDropTarget.OnDragOver(self, x, y, d)
    67         
    57         
    68     def OnDropText(self, x, y, data):
    58     def OnDropText(self, x, y, data):
    69         message = None
    59         message = None
    70         try:
    60         try:
    71             values = eval(data)
    61             values = eval(data)
       
    62             if not isinstance(values, TupleType):
       
    63                 raise
    72         except:
    64         except:
    73             message = _("Invalid value \"%s\" for debug variable")%data
    65             message = _("Invalid value \"%s\" for debug variable")%data
    74             values = None
    66             values = None
    75         if not isinstance(values, TupleType):
    67             
    76             message = _("Invalid value \"%s\" for debug variable")%data
       
    77             values = None
       
    78         
       
    79         if message is not None:
    68         if message is not None:
    80             wx.CallAfter(self.ShowMessage, message)
    69             wx.CallAfter(self.ShowMessage, message)
    81         elif values is not None and values[1] == "debug":
    70         
    82             if self.ParentControl is not None:
    71         elif values[1] == "debug":
    83                 width, height = self.ParentControl.GetSize()
    72             if len(values) > 2 and values[2] == "move":
    84                 target_idx = self.ParentControl.GetIndex()
       
    85                 merge_type = GRAPH_PARALLEL
       
    86                 if isinstance(self.ParentControl, DebugVariableGraphic):
       
    87                     if self.ParentControl.Is3DCanvas():
       
    88                         if y > height / 2:
       
    89                             target_idx += 1
       
    90                         if len(values) > 1 and values[2] == "move":
       
    91                             self.ParentWindow.MoveValue(values[0], target_idx)
       
    92                         else:
       
    93                             self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
    94                         
       
    95                     else:
       
    96                         rect = self.ParentControl.GetAxesBoundingBox()
       
    97                         if rect.InsideXY(x, y):
       
    98                             merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
       
    99                             if merge_rect.InsideXY(x, y):
       
   100                                 merge_type = GRAPH_ORTHOGONAL
       
   101                             wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
       
   102                         else:
       
   103                             if y > height / 2:
       
   104                                 target_idx += 1
       
   105                             if len(values) > 2 and values[2] == "move":
       
   106                                 self.ParentWindow.MoveValue(values[0], target_idx)
       
   107                             else:
       
   108                                 self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
   109                 else:
       
   110                     if y > height / 2:
       
   111                         target_idx += 1
       
   112                     if len(values) > 2 and values[2] == "move":
       
   113                         self.ParentWindow.MoveValue(values[0], target_idx)
       
   114                     else:
       
   115                         self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
   116                     
       
   117             elif len(values) > 2 and values[2] == "move":
       
   118                 self.ParentWindow.MoveValue(values[0])
    73                 self.ParentWindow.MoveValue(values[0])
   119             else:
    74             else:
   120                 self.ParentWindow.InsertValue(values[0], force=True)
    75                 self.ParentWindow.InsertValue(values[0], force=True)
   121     
    76     
   122     def OnLeave(self):
    77     def OnLeave(self):
   139     [("%dms" % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \
    94     [("%dms" % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \
   140     [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
    95     [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
   141     [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
    96     [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
   142     [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
    97     [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
   143 
    98 
   144 GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
       
   145 
       
   146 SCROLLBAR_UNIT = 10
    99 SCROLLBAR_UNIT = 10
   147 
       
   148 #CANVAS_HIGHLIGHT_TYPES
       
   149 [HIGHLIGHT_NONE,
       
   150  HIGHLIGHT_BEFORE,
       
   151  HIGHLIGHT_AFTER,
       
   152  HIGHLIGHT_LEFT,
       
   153  HIGHLIGHT_RIGHT,
       
   154  HIGHLIGHT_RESIZE] = range(6)
       
   155 
       
   156 HIGHLIGHT_DROP_PEN = wx.Pen(wx.Colour(0, 128, 255))
       
   157 HIGHLIGHT_DROP_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128))
       
   158 HIGHLIGHT_RESIZE_PEN = wx.Pen(wx.Colour(200, 200, 200))
       
   159 HIGHLIGHT_RESIZE_BRUSH = wx.Brush(wx.Colour(200, 200, 200))
       
   160 
       
   161 #CANVAS_SIZE_TYPES
       
   162 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
       
   163 
       
   164 DEFAULT_CANVAS_HEIGHT = 200.
       
   165 CANVAS_BORDER = (20., 10.)
       
   166 CANVAS_PADDING = 8.5
       
   167 VALUE_LABEL_HEIGHT = 17.
       
   168 AXES_LABEL_HEIGHT = 12.75
       
   169 
   100 
   170 def compute_mask(x, y):
   101 def compute_mask(x, y):
   171     mask = []
   102     mask = []
   172     for xp, yp in zip(x, y):
   103     for xp, yp in zip(x, y):
   173         if xp == yp:
   104         if xp == yp:
   184                 next_tick = data[0][0]
   115                 next_tick = data[0][0]
   185             else:
   116             else:
   186                 next_tick = min(next_tick, data[0][0])
   117                 next_tick = min(next_tick, data[0][0])
   187     return next_tick
   118     return next_tick
   188 
   119 
   189 def OrthogonalData(item, start_tick, end_tick):
       
   190     data = item.GetData(start_tick, end_tick)
       
   191     min_value, max_value = item.GetValueRange()
       
   192     if min_value is not None and max_value is not None:
       
   193         center = (min_value + max_value) / 2.
       
   194         range = max(1.0, max_value - min_value)
       
   195     else:
       
   196         center = 0.5
       
   197         range = 1.0
       
   198     return data, center - range * 0.55, center + range * 0.55
       
   199 
       
   200 class DebugVariableViewer:
       
   201     
       
   202     def __init__(self, window, items=[]):
       
   203         self.ParentWindow = window
       
   204         self.Items = items
       
   205         
       
   206         self.Highlight = HIGHLIGHT_NONE
       
   207         self.Buttons = []
       
   208     
       
   209     def __del__(self):
       
   210         self.ParentWindow = None
       
   211     
       
   212     def GetIndex(self):
       
   213         return self.ParentWindow.GetViewerIndex(self)
       
   214     
       
   215     def GetItem(self, variable):
       
   216         for item in self.Items:
       
   217             if item.GetVariable() == variable:
       
   218                 return item
       
   219         return None
       
   220     
       
   221     def GetItems(self):
       
   222         return self.Items
       
   223     
       
   224     def GetVariables(self):
       
   225         if len(self.Items) > 1:
       
   226             variables = [item.GetVariable() for item in self.Items]
       
   227             if self.GraphType == GRAPH_ORTHOGONAL:
       
   228                 return tuple(variables)
       
   229             return variables
       
   230         return self.Items[0].GetVariable()
       
   231     
       
   232     def AddItem(self, item):
       
   233         self.Items.append(item)
       
   234         
       
   235     def RemoveItem(self, item):
       
   236         if item in self.Items:
       
   237             self.Items.remove(item)
       
   238         
       
   239     def Clear(self):
       
   240         for item in self.Items:
       
   241             self.ParentWindow.RemoveDataConsumer(item)
       
   242         self.Items = []
       
   243         
       
   244     def IsEmpty(self):
       
   245         return len(self.Items) == 0
       
   246     
       
   247     def UnregisterObsoleteData(self):
       
   248         for item in self.Items[:]:
       
   249             iec_path = item.GetVariable()
       
   250             if self.ParentWindow.GetDataType(iec_path) is None:
       
   251                 self.ParentWindow.RemoveDataConsumer(item)
       
   252                 self.RemoveItem(item)
       
   253             else:
       
   254                 self.ParentWindow.AddDataConsumer(iec_path.upper(), item)
       
   255                 item.RefreshVariableType()
       
   256         
       
   257     def ResetData(self):
       
   258         for item in self.Items:
       
   259             item.ResetData()
       
   260     
       
   261     def RefreshViewer(self):
       
   262         pass
       
   263     
       
   264     def SetHighlight(self, highlight):
       
   265         if self.Highlight != highlight:
       
   266             self.Highlight = highlight
       
   267             return True
       
   268         return False
       
   269     
       
   270     def GetButtons(self):
       
   271         return self.Buttons
       
   272     
       
   273     def HandleButtons(self, x, y):
       
   274         for button in self.GetButtons():
       
   275             if button.HitTest(x, y):
       
   276                 button.ProcessCallback()
       
   277                 return True
       
   278         return False
       
   279     
       
   280     def IsOverButton(self, x, y):
       
   281         for button in self.GetButtons():
       
   282             if button.HitTest(x, y):
       
   283                 return True
       
   284         return False
       
   285     
       
   286     def ShowButtons(self, show):
       
   287         for button in self.Buttons:
       
   288             if show:
       
   289                 button.Show()
       
   290             else:
       
   291                 button.Hide()
       
   292         self.RefreshButtonsState()
       
   293         self.ParentWindow.ForceRefresh()
       
   294     
       
   295     def RefreshButtonsState(self, refresh_positions=False):
       
   296         if self:
       
   297             width, height = self.GetSize()
       
   298             if refresh_positions:
       
   299                 offset = 0
       
   300                 buttons = self.Buttons[:]
       
   301                 buttons.reverse()
       
   302                 for button in buttons:
       
   303                     if button.IsEnabled():
       
   304                         w, h = button.GetSize()
       
   305                         button.SetPosition(width - 5 - w - offset, 5)
       
   306                         offset += w + 2
       
   307                 self.ParentWindow.ForceRefresh()
       
   308     
       
   309     def DrawCommonElements(self, dc, buttons=None):
       
   310         width, height = self.GetSize()
       
   311         
       
   312         dc.SetPen(HIGHLIGHT_DROP_PEN)
       
   313         dc.SetBrush(HIGHLIGHT_DROP_BRUSH)
       
   314         if self.Highlight in [HIGHLIGHT_BEFORE]:
       
   315             dc.DrawLine(0, 1, width - 1, 1)
       
   316         elif self.Highlight in [HIGHLIGHT_AFTER]:
       
   317             dc.DrawLine(0, height - 1, width - 1, height - 1)
       
   318         
       
   319         if buttons is None:
       
   320             buttons = self.Buttons
       
   321         for button in buttons:
       
   322             button.Draw(dc)
       
   323             
       
   324         if self.ParentWindow.IsDragging():
       
   325             destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
       
   326             srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
       
   327             if destBBox.width > 0 and destBBox.height > 0:
       
   328                 srcPanel = self.ParentWindow.DraggingAxesPanel
       
   329                 srcBBox = srcPanel.GetAxesBoundingBox()
       
   330                 
       
   331                 if destBBox.x == 0:
       
   332                     srcX = srcBBox.x - srcPos.x
       
   333                 else:
       
   334                     srcX = srcBBox.x
       
   335                 if destBBox.y == 0:
       
   336                     srcY = srcBBox.y - srcPos.y
       
   337                 else:
       
   338                     srcY = srcBBox.y
       
   339                 
       
   340                 srcBmp = _convert_agg_to_wx_bitmap(srcPanel.get_renderer(), None)
       
   341                 srcDC = wx.MemoryDC()
       
   342                 srcDC.SelectObject(srcBmp)
       
   343                 
       
   344                 dc.Blit(destBBox.x, destBBox.y, 
       
   345                         int(destBBox.width), int(destBBox.height), 
       
   346                         srcDC, srcX, srcY)
       
   347     
       
   348     def OnEnter(self, event):
       
   349         self.ShowButtons(True)
       
   350         event.Skip()
       
   351         
       
   352     def OnLeave(self, event):
       
   353         if self.Highlight != HIGHLIGHT_RESIZE or self.CanvasStartSize is None:
       
   354             x, y = event.GetPosition()
       
   355             width, height = self.GetSize()
       
   356             if (x <= 0 or x >= width - 1 or
       
   357                 y <= 0 or y >= height - 1):
       
   358                 self.ShowButtons(False)
       
   359         event.Skip()
       
   360     
       
   361     def OnCloseButton(self):
       
   362         wx.CallAfter(self.ParentWindow.DeleteValue, self)
       
   363     
       
   364     def OnForceButton(self):
       
   365         wx.CallAfter(self.ForceValue, self.Items[0])
       
   366         
       
   367     def OnReleaseButton(self):
       
   368         wx.CallAfter(self.ReleaseValue, self.Items[0])
       
   369     
       
   370     def OnResizeWindow(self, event):
       
   371         wx.CallAfter(self.RefreshButtonsState, True)
       
   372         event.Skip()
       
   373     
       
   374     def OnMouseDragging(self, x, y):
       
   375         xw, yw = self.GetPosition()
       
   376         self.ParentWindow.RefreshHighlight(x + xw, y + yw)
       
   377     
       
   378     def OnDragging(self, x, y):
       
   379         width, height = self.GetSize()
       
   380         if y < height / 2:
       
   381             if self.ParentWindow.IsViewerFirst(self):
       
   382                 self.SetHighlight(HIGHLIGHT_BEFORE)
       
   383             else:
       
   384                 self.SetHighlight(HIGHLIGHT_NONE)
       
   385                 self.ParentWindow.HighlightPreviousViewer(self)
       
   386         else:
       
   387             self.SetHighlight(HIGHLIGHT_AFTER)
       
   388     
       
   389     def OnEraseBackground(self, event):
       
   390         pass
       
   391     
       
   392     def OnResize(self, event):
       
   393         wx.CallAfter(self.RefreshButtonsState, True)
       
   394         event.Skip()
       
   395     
       
   396     def ForceValue(self, item):
       
   397         iec_path = item.GetVariable()
       
   398         iec_type = self.ParentWindow.GetDataType(iec_path)
       
   399         if iec_type is not None:
       
   400             dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
       
   401             if dialog.ShowModal() == wx.ID_OK:
       
   402                 self.ParentWindow.ForceDataValue(iec_path.upper(), dialog.GetValue())
       
   403     
       
   404     def ReleaseValue(self, item):
       
   405         iec_path = item.GetVariable().upper()
       
   406         self.ParentWindow.ReleaseDataValue(iec_path)
       
   407 
       
   408 
       
   409 class DebugVariableText(DebugVariableViewer, wx.Panel):
       
   410     
       
   411     def __init__(self, parent, window, items=[]):
       
   412         DebugVariableViewer.__init__(self, window, items)
       
   413         
       
   414         wx.Panel.__init__(self, parent)
       
   415         self.SetBackgroundColour(wx.WHITE)
       
   416         self.SetDropTarget(DebugVariableDropTarget(window, self))
       
   417         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
       
   418         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
       
   419         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
       
   420         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
       
   421         self.Bind(wx.EVT_SIZE, self.OnResize)
       
   422         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
       
   423         self.Bind(wx.EVT_PAINT, self.OnPaint)
       
   424         
       
   425         self.SetMinSize(wx.Size(0, 25))
       
   426         
       
   427         for bitmap, callback in [("force", self.OnForceButton),
       
   428                                  ("release", self.OnReleaseButton),
       
   429                                  ("delete_graph", self.OnCloseButton)]:
       
   430             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
       
   431         
       
   432         self.ShowButtons(False)
       
   433         
       
   434     def RefreshViewer(self):
       
   435         width, height = self.GetSize()
       
   436         bitmap = wx.EmptyBitmap(width, height)
       
   437         
       
   438         dc = wx.BufferedDC(wx.ClientDC(self), bitmap)
       
   439         dc.Clear()
       
   440         dc.BeginDrawing()
       
   441         
       
   442         gc = wx.GCDC(dc)
       
   443         
       
   444         item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
       
   445         w, h = gc.GetTextExtent(item_name)
       
   446         gc.DrawText(item_name, 20, (height - h) / 2)
       
   447         
       
   448         if self.Items[0].IsForced():
       
   449             gc.SetTextForeground(wx.BLUE)
       
   450             self.Buttons[0].Disable()
       
   451             self.Buttons[1].Enable()
       
   452         else:
       
   453             self.Buttons[1].Disable()
       
   454             self.Buttons[0].Enable()
       
   455         self.RefreshButtonsState(True)
       
   456         
       
   457         item_value = self.Items[0].GetValue()
       
   458         w, h = gc.GetTextExtent(item_value)
       
   459         gc.DrawText(item_value, width - 40 - w, (height - h) / 2)
       
   460         
       
   461         self.DrawCommonElements(gc)
       
   462         
       
   463         gc.EndDrawing()
       
   464     
       
   465     def OnLeftDown(self, event):
       
   466         width, height = self.GetSize()
       
   467         item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
       
   468         w, h = self.GetTextExtent(item_name)
       
   469         x, y = event.GetPosition()
       
   470         rect = wx.Rect(20, (height - h) / 2, w, h)
       
   471         if rect.InsideXY(x, y):
       
   472             data = wx.TextDataObject(str((self.Items[0].GetVariable(), "debug", "move")))
       
   473             dragSource = wx.DropSource(self)
       
   474             dragSource.SetData(data)
       
   475             dragSource.DoDragDrop()
       
   476         else:
       
   477             event.Skip()
       
   478     
       
   479     def OnLeftUp(self, event):
       
   480         x, y = event.GetPosition()
       
   481         wx.CallAfter(self.HandleButtons, x, y)
       
   482         event.Skip()
       
   483     
       
   484     def OnPaint(self, event):
       
   485         self.RefreshViewer()
       
   486         event.Skip()
       
   487 
       
   488 class DebugVariableGraphic(DebugVariableViewer, FigureCanvas):
       
   489     
       
   490     def __init__(self, parent, window, items, graph_type):
       
   491         DebugVariableViewer.__init__(self, window, items)
       
   492         
       
   493         self.CanvasSize = SIZE_MINI
       
   494         self.GraphType = graph_type
       
   495         self.CursorTick = None
       
   496         self.MouseStartPos = None
       
   497         self.StartCursorTick = None
       
   498         self.CanvasStartSize = None
       
   499         self.ContextualButtons = []
       
   500         self.ContextualButtonsItem = None
       
   501         
       
   502         self.Figure = matplotlib.figure.Figure(facecolor='w')
       
   503         self.Figure.subplotpars.update(top=0.95, left=0.1, bottom=0.1, right=0.95)
       
   504         
       
   505         FigureCanvas.__init__(self, parent, -1, self.Figure)
       
   506         self.SetWindowStyle(wx.WANTS_CHARS)
       
   507         self.SetBackgroundColour(wx.WHITE)
       
   508         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
       
   509         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
       
   510         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
       
   511         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
       
   512         self.Bind(wx.EVT_SIZE, self.OnResize)
       
   513         
       
   514         canvas_size = self.GetCanvasMinSize()
       
   515         self.SetMinSize(canvas_size)
       
   516         self.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self))
       
   517         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
       
   518         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
       
   519         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
       
   520         self.mpl_connect('scroll_event', self.OnCanvasScroll)
       
   521         
       
   522         for size, bitmap in zip([SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
       
   523                                 ["minimize_graph", "middle_graph", "maximize_graph"]):
       
   524             self.Buttons.append(GraphButton(0, 0, bitmap, self.GetOnChangeSizeButton(size)))
       
   525         for bitmap, callback in [("export_graph_mini", self.OnExportGraphButton),
       
   526                                  ("delete_graph", self.OnCloseButton)]:
       
   527             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
       
   528         
       
   529         self.ResetGraphics()
       
   530         self.RefreshLabelsPosition(canvas_size.height)
       
   531         self.ShowButtons(False)
       
   532     
       
   533     def draw(self, drawDC=None):
       
   534         FigureCanvasAgg.draw(self)
       
   535 
       
   536         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
       
   537         self.bitmap.UseAlpha() 
       
   538         width, height = self.GetSize()
       
   539         bbox = self.GetAxesBoundingBox()
       
   540         
       
   541         destDC = wx.MemoryDC()
       
   542         destDC.SelectObject(self.bitmap)
       
   543         
       
   544         destGC = wx.GCDC(destDC)
       
   545         
       
   546         destGC.BeginDrawing()
       
   547         if self.Highlight == HIGHLIGHT_RESIZE:
       
   548             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
       
   549             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
       
   550             destGC.DrawRectangle(0, height - 5, width, 5)
       
   551         else:
       
   552             destGC.SetPen(HIGHLIGHT_DROP_PEN)
       
   553             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
       
   554             if self.Highlight == HIGHLIGHT_LEFT:
       
   555                 destGC.DrawRectangle(bbox.x, bbox.y, 
       
   556                                      bbox.width / 2, bbox.height)
       
   557             elif self.Highlight == HIGHLIGHT_RIGHT:
       
   558                 destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y, 
       
   559                                      bbox.width / 2, bbox.height)
       
   560         
       
   561         self.DrawCommonElements(destGC, self.Buttons + self.ContextualButtons)
       
   562         
       
   563         destGC.EndDrawing()
       
   564         
       
   565         self._isDrawn = True
       
   566         self.gui_repaint(drawDC=drawDC)
       
   567     
       
   568     def GetButtons(self):
       
   569         return self.Buttons + self.ContextualButtons
       
   570     
       
   571     def PopupContextualButtons(self, item, rect, style=wx.RIGHT):
       
   572         if self.ContextualButtonsItem is not None and item != self.ContextualButtonsItem:
       
   573             self.DismissContextualButtons()
       
   574         
       
   575         if self.ContextualButtonsItem is None:
       
   576             self.ContextualButtonsItem = item
       
   577             
       
   578             if self.ContextualButtonsItem.IsForced():
       
   579                 self.ContextualButtons.append(
       
   580                     GraphButton(0, 0, "release", self.OnReleaseButton))
       
   581             for bitmap, callback in [("force", self.OnForceButton),
       
   582                                      ("export_graph_mini", self.OnExportItemGraphButton),
       
   583                                      ("delete_graph", self.OnRemoveItemButton)]:
       
   584                 self.ContextualButtons.append(GraphButton(0, 0, bitmap, callback))
       
   585             
       
   586             offset = 0
       
   587             buttons = self.ContextualButtons[:]
       
   588             if style in [wx.TOP, wx.LEFT]:
       
   589                  buttons.reverse()
       
   590             for button in buttons:
       
   591                 w, h = button.GetSize()
       
   592                 if style in [wx.LEFT, wx.RIGHT]:
       
   593                     if style == wx.LEFT:
       
   594                         x = rect.x - w - offset
       
   595                     else:
       
   596                         x = rect.x + rect.width + offset
       
   597                     y = rect.y + (rect.height - h) / 2
       
   598                     offset += w
       
   599                 else:
       
   600                     x = rect.x + (rect.width - w ) / 2
       
   601                     if style == wx.TOP:
       
   602                         y = rect.y - h - offset
       
   603                     else:
       
   604                         y = rect.y + rect.height + offset
       
   605                     offset += h
       
   606                 button.SetPosition(x, y)
       
   607             self.ParentWindow.ForceRefresh()
       
   608     
       
   609     def DismissContextualButtons(self):
       
   610         if self.ContextualButtonsItem is not None:
       
   611             self.ContextualButtonsItem = None
       
   612             self.ContextualButtons = []
       
   613             self.ParentWindow.ForceRefresh()
       
   614     
       
   615     def IsOverContextualButton(self, x, y):
       
   616         for button in self.ContextualButtons:
       
   617             if button.HitTest(x, y):
       
   618                 return True
       
   619         return False
       
   620     
       
   621     def SetMinSize(self, size):
       
   622         wx.Window.SetMinSize(self, size)
       
   623         wx.CallAfter(self.RefreshButtonsState)
       
   624     
       
   625     def GetOnChangeSizeButton(self, size):
       
   626         def OnChangeSizeButton():
       
   627             self.CanvasSize = size
       
   628             self.SetCanvasSize(200, self.CanvasSize)
       
   629         return OnChangeSizeButton
       
   630     
       
   631     def OnExportGraphButton(self):
       
   632         self.ExportGraph()
       
   633     
       
   634     def OnForceButton(self):
       
   635         wx.CallAfter(self.ForceValue, 
       
   636                      self.ContextualButtonsItem)
       
   637         self.DismissContextualButtons()
       
   638         
       
   639     def OnReleaseButton(self):
       
   640         wx.CallAfter(self.ReleaseValue, 
       
   641                      self.ContextualButtonsItem)
       
   642         self.DismissContextualButtons()
       
   643     
       
   644     def OnExportItemGraphButton(self):
       
   645         wx.CallAfter(self.ExportGraph, 
       
   646                      self.ContextualButtonsItem)
       
   647         self.DismissContextualButtons()
       
   648         
       
   649     def OnRemoveItemButton(self):            
       
   650         wx.CallAfter(self.ParentWindow.DeleteValue, self, 
       
   651                      self.ContextualButtonsItem)
       
   652         self.DismissContextualButtons()
       
   653     
       
   654     def RefreshLabelsPosition(self, height):
       
   655         canvas_ratio = 1. / height
       
   656         graph_ratio = 1. / ((1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) * height)
       
   657         
       
   658         self.Figure.subplotpars.update(
       
   659             top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
       
   660             bottom= CANVAS_BORDER[0] * canvas_ratio)
       
   661         
       
   662         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
       
   663             num_item = len(self.Items)
       
   664             for idx in xrange(num_item):
       
   665                 if not self.Is3DCanvas():
       
   666                     self.AxesLabels[idx].set_position(
       
   667                         (0.05, 
       
   668                          1.0 - (CANVAS_PADDING + AXES_LABEL_HEIGHT * idx) * graph_ratio))
       
   669                 self.Labels[idx].set_position(
       
   670                     (0.95, 
       
   671                      CANVAS_PADDING * graph_ratio + 
       
   672                      (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
       
   673         else:
       
   674             self.AxesLabels[0].set_position((0.1, CANVAS_PADDING * graph_ratio))
       
   675             self.Labels[0].set_position((0.95, CANVAS_PADDING * graph_ratio))
       
   676             self.AxesLabels[1].set_position((0.05, 2 * CANVAS_PADDING * graph_ratio))
       
   677             self.Labels[1].set_position((0.05, 1.0 - CANVAS_PADDING * graph_ratio))
       
   678     
       
   679         self.Figure.subplots_adjust()
       
   680     
       
   681     def GetCanvasMinSize(self):
       
   682         return wx.Size(200, 
       
   683                        CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
       
   684                        2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
       
   685     
       
   686     def SetCanvasSize(self, width, height):
       
   687         height = max(height, self.GetCanvasMinSize()[1])
       
   688         self.SetMinSize(wx.Size(width, height))
       
   689         self.RefreshLabelsPosition(height)
       
   690         self.RefreshButtonsState()
       
   691         self.ParentWindow.RefreshGraphicsSizer()
       
   692         
       
   693     def GetAxesBoundingBox(self, absolute=False):
       
   694         width, height = self.GetSize()
       
   695         ax, ay, aw, ah = self.figure.gca().get_position().bounds
       
   696         bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
       
   697                        aw * width + 2, ah * height + 1)
       
   698         if absolute:
       
   699             xw, yw = self.GetPosition()
       
   700             bbox.x += xw
       
   701             bbox.y += yw
       
   702         return bbox
       
   703     
       
   704     def OnCanvasButtonPressed(self, event):
       
   705         width, height = self.GetSize()
       
   706         x, y = event.x, height - event.y
       
   707         if not self.IsOverButton(x, y):
       
   708             if event.inaxes == self.Axes:
       
   709                 item_idx = None
       
   710                 for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
       
   711                              [pair for pair in enumerate(self.Labels)]):
       
   712                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   713                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   714                     if rect.InsideXY(x, y):
       
   715                         item_idx = i
       
   716                         break
       
   717                 if item_idx is not None:
       
   718                     self.ShowButtons(False)
       
   719                     self.DismissContextualButtons()
       
   720                     xw, yw = self.GetPosition()
       
   721                     self.ParentWindow.StartDragNDrop(self, 
       
   722                         self.Items[item_idx], x + xw, y + yw, x + xw, y + yw)
       
   723                 elif not self.Is3DCanvas():
       
   724                     self.MouseStartPos = wx.Point(x, y)
       
   725                     if event.button == 1 and event.inaxes == self.Axes:
       
   726                         self.StartCursorTick = self.CursorTick
       
   727                         self.HandleCursorMove(event)
       
   728                     elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
       
   729                         width, height = self.GetSize()
       
   730                         start_tick, end_tick = self.ParentWindow.GetRange()
       
   731                         self.StartCursorTick = start_tick
       
   732             
       
   733             elif event.button == 1 and event.y <= 5:
       
   734                 self.MouseStartPos = wx.Point(x, y)
       
   735                 self.CanvasStartSize = self.GetSize()
       
   736     
       
   737     def OnCanvasButtonReleased(self, event):
       
   738         if self.ParentWindow.IsDragging():
       
   739             width, height = self.GetSize()
       
   740             xw, yw = self.GetPosition()
       
   741             self.ParentWindow.StopDragNDrop(
       
   742                 self.ParentWindow.DraggingAxesPanel.Items[0].GetVariable(),
       
   743                 xw + event.x, 
       
   744                 yw + height - event.y)
       
   745         else:
       
   746             self.MouseStartPos = None
       
   747             self.StartCursorTick = None
       
   748             self.CanvasStartSize = None
       
   749             width, height = self.GetSize()
       
   750             self.HandleButtons(event.x, height - event.y)
       
   751             if event.y > 5 and self.SetHighlight(HIGHLIGHT_NONE):
       
   752                 self.SetCursor(wx.NullCursor)
       
   753                 self.ParentWindow.ForceRefresh()
       
   754     
       
   755     def OnCanvasMotion(self, event):
       
   756         width, height = self.GetSize()
       
   757         if self.ParentWindow.IsDragging():
       
   758             xw, yw = self.GetPosition()
       
   759             self.ParentWindow.MoveDragNDrop(
       
   760                 xw + event.x, 
       
   761                 yw + height - event.y)
       
   762         else:
       
   763             if not self.Is3DCanvas():
       
   764                 if event.button == 1 and self.CanvasStartSize is None:
       
   765                     if event.inaxes == self.Axes:
       
   766                         if self.MouseStartPos is not None:
       
   767                             self.HandleCursorMove(event)
       
   768                     elif self.MouseStartPos is not None and len(self.Items) == 1:
       
   769                         xw, yw = self.GetPosition()
       
   770                         self.ParentWindow.SetCursorTick(self.StartCursorTick)
       
   771                         self.ParentWindow.StartDragNDrop(self, 
       
   772                             self.Items[0],
       
   773                             event.x + xw, height - event.y + yw, 
       
   774                             self.MouseStartPos.x + xw, self.MouseStartPos.y + yw)
       
   775                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and self.MouseStartPos is not None:
       
   776                     start_tick, end_tick = self.ParentWindow.GetRange()
       
   777                     rect = self.GetAxesBoundingBox()
       
   778                     self.ParentWindow.SetCanvasPosition(
       
   779                         self.StartCursorTick + (self.MouseStartPos.x - event.x) *
       
   780                         (end_tick - start_tick) / rect.width)    
       
   781             
       
   782             if event.button == 1 and self.CanvasStartSize is not None:
       
   783                 width, height = self.GetSize()
       
   784                 self.SetCanvasSize(width, 
       
   785                     self.CanvasStartSize.height + height - event.y - self.MouseStartPos.y)
       
   786                 
       
   787             elif event.button in [None, "up", "down"]:
       
   788                 if self.GraphType == GRAPH_PARALLEL:
       
   789                     orientation = [wx.RIGHT] * len(self.AxesLabels) + [wx.LEFT] * len(self.Labels)
       
   790                 elif len(self.AxesLabels) > 0:
       
   791                     orientation = [wx.RIGHT, wx.TOP, wx.LEFT, wx.BOTTOM]
       
   792                 else:
       
   793                     orientation = [wx.LEFT] * len(self.Labels)
       
   794                 item_idx = None
       
   795                 item_style = None
       
   796                 for (i, t), style in zip([pair for pair in enumerate(self.AxesLabels)] + 
       
   797                                          [pair for pair in enumerate(self.Labels)], 
       
   798                                          orientation):
       
   799                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   800                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   801                     if rect.InsideXY(event.x, height - event.y):
       
   802                         item_idx = i
       
   803                         item_style = style
       
   804                         break
       
   805                 if item_idx is not None:
       
   806                     self.PopupContextualButtons(self.Items[item_idx], rect, item_style)
       
   807                     return 
       
   808                 if not self.IsOverContextualButton(event.x, height - event.y):
       
   809                     self.DismissContextualButtons()
       
   810                 
       
   811                 if event.y <= 5:
       
   812                     if self.SetHighlight(HIGHLIGHT_RESIZE):
       
   813                         self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
       
   814                         self.ParentWindow.ForceRefresh()
       
   815                 else:
       
   816                     if self.SetHighlight(HIGHLIGHT_NONE):
       
   817                         self.SetCursor(wx.NullCursor)
       
   818                         self.ParentWindow.ForceRefresh()
       
   819     
       
   820     def OnCanvasScroll(self, event):
       
   821         if event.inaxes is not None and event.guiEvent.ControlDown():
       
   822             if self.GraphType == GRAPH_ORTHOGONAL:
       
   823                 start_tick, end_tick = self.ParentWindow.GetRange()
       
   824                 tick = (start_tick + end_tick) / 2.
       
   825             else:
       
   826                 tick = event.xdata
       
   827             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
       
   828             self.ParentWindow.VetoScrollEvent = True
       
   829     
       
   830     def OnDragging(self, x, y):
       
   831         width, height = self.GetSize()
       
   832         bbox = self.GetAxesBoundingBox()
       
   833         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
       
   834             rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
       
   835             if rect.InsideXY(x, y):
       
   836                 self.SetHighlight(HIGHLIGHT_LEFT)
       
   837             else:
       
   838                 self.SetHighlight(HIGHLIGHT_RIGHT)
       
   839         elif y < height / 2:
       
   840             if self.ParentWindow.IsViewerFirst(self):
       
   841                 self.SetHighlight(HIGHLIGHT_BEFORE)
       
   842             else:
       
   843                 self.SetHighlight(HIGHLIGHT_NONE)
       
   844                 self.ParentWindow.HighlightPreviousViewer(self)
       
   845         else:
       
   846             self.SetHighlight(HIGHLIGHT_AFTER)
       
   847     
       
   848     def OnLeave(self, event):
       
   849         if self.CanvasStartSize is None and self.SetHighlight(HIGHLIGHT_NONE):
       
   850             self.SetCursor(wx.NullCursor)
       
   851             self.ParentWindow.ForceRefresh()
       
   852         DebugVariableViewer.OnLeave(self, event)
       
   853     
       
   854     KEY_CURSOR_INCREMENT = {
       
   855         wx.WXK_LEFT: -1,
       
   856         wx.WXK_RIGHT: 1,
       
   857         wx.WXK_UP: -10,
       
   858         wx.WXK_DOWN: 10}
       
   859     def OnKeyDown(self, event):
       
   860         if self.CursorTick is not None:
       
   861             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
       
   862             if move is not None:
       
   863                 self.ParentWindow.MoveCursorTick(move)
       
   864         event.Skip()
       
   865     
       
   866     def HandleCursorMove(self, event):
       
   867         start_tick, end_tick = self.ParentWindow.GetRange()
       
   868         cursor_tick = None
       
   869         if self.GraphType == GRAPH_ORTHOGONAL:
       
   870             x_data = self.Items[0].GetData(start_tick, end_tick)
       
   871             y_data = self.Items[1].GetData(start_tick, end_tick)
       
   872             if len(x_data) > 0 and len(y_data) > 0:
       
   873                 length = min(len(x_data), len(y_data))
       
   874                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
       
   875                 cursor_tick = x_data[numpy.argmin(d), 0]
       
   876         else:
       
   877             data = self.Items[0].GetData(start_tick, end_tick)
       
   878             if len(data) > 0:
       
   879                 cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0]
       
   880         if cursor_tick is not None:
       
   881             self.ParentWindow.SetCursorTick(cursor_tick)
       
   882     
       
   883     def OnAxesMotion(self, event):
       
   884         if self.Is3DCanvas():
       
   885             current_time = gettime()
       
   886             if current_time - self.LastMotionTime > REFRESH_PERIOD:
       
   887                 self.LastMotionTime = current_time
       
   888                 Axes3D._on_move(self.Axes, event)
       
   889     
       
   890     def ResetGraphics(self):
       
   891         self.Figure.clear()
       
   892         if self.Is3DCanvas():
       
   893             self.Axes = self.Figure.gca(projection='3d')
       
   894             self.Axes.set_color_cycle(['b'])
       
   895             self.LastMotionTime = gettime()
       
   896             setattr(self.Axes, "_on_move", self.OnAxesMotion)
       
   897             self.Axes.mouse_init()
       
   898             self.Axes.tick_params(axis='z', labelsize='small')
       
   899         else:
       
   900             self.Axes = self.Figure.gca()
       
   901             self.Axes.set_color_cycle(color_cycle)
       
   902         self.Axes.tick_params(axis='x', labelsize='small')
       
   903         self.Axes.tick_params(axis='y', labelsize='small')
       
   904         self.Plots = []
       
   905         self.VLine = None
       
   906         self.HLine = None
       
   907         self.Labels = []
       
   908         self.AxesLabels = []
       
   909         if not self.Is3DCanvas():
       
   910             text_func = self.Axes.text
       
   911         else:
       
   912             text_func = self.Axes.text2D
       
   913         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
       
   914             num_item = len(self.Items)
       
   915             for idx in xrange(num_item):
       
   916                 if num_item == 1:
       
   917                     color = 'k'
       
   918                 else:
       
   919                     color = color_cycle[idx % len(color_cycle)]
       
   920                 if not self.Is3DCanvas():
       
   921                     self.AxesLabels.append(
       
   922                         text_func(0, 0, "", size='small',
       
   923                                   verticalalignment='top', 
       
   924                                   color=color,
       
   925                                   transform=self.Axes.transAxes))
       
   926                 self.Labels.append(
       
   927                     text_func(0, 0, "", size='large', 
       
   928                               horizontalalignment='right',
       
   929                               color=color,
       
   930                               transform=self.Axes.transAxes))
       
   931         else:
       
   932             self.AxesLabels.append(
       
   933                 self.Axes.text(0, 0, "", size='small',
       
   934                                transform=self.Axes.transAxes))
       
   935             self.Labels.append(
       
   936                 self.Axes.text(0, 0, "", size='large',
       
   937                                horizontalalignment='right',
       
   938                                transform=self.Axes.transAxes))
       
   939             self.AxesLabels.append(
       
   940                 self.Axes.text(0, 0, "", size='small',
       
   941                                rotation='vertical',
       
   942                                verticalalignment='bottom',
       
   943                                transform=self.Axes.transAxes))
       
   944             self.Labels.append(
       
   945                 self.Axes.text(0, 0, "", size='large',
       
   946                                rotation='vertical',
       
   947                                verticalalignment='top',
       
   948                                transform=self.Axes.transAxes))
       
   949         width, height = self.GetSize()
       
   950         self.RefreshLabelsPosition(height)
       
   951         
       
   952     def AddItem(self, item):
       
   953         DebugVariableViewer.AddItem(self, item)
       
   954         self.ResetGraphics()
       
   955         
       
   956     def RemoveItem(self, item):
       
   957         DebugVariableViewer.RemoveItem(self, item)
       
   958         if not self.IsEmpty():
       
   959             if len(self.Items) == 1:
       
   960                 self.GraphType = GRAPH_PARALLEL
       
   961             self.ResetGraphics()
       
   962     
       
   963     def UnregisterObsoleteData(self):
       
   964         DebugVariableViewer.UnregisterObsoleteData(self)
       
   965         if not self.IsEmpty():
       
   966             self.ResetGraphics()
       
   967     
       
   968     def Is3DCanvas(self):
       
   969         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
       
   970     
       
   971     def SetCursorTick(self, cursor_tick):
       
   972         self.CursorTick = cursor_tick
       
   973         
       
   974     def RefreshViewer(self, refresh_graphics=True):
       
   975         
       
   976         if refresh_graphics:
       
   977             start_tick, end_tick = self.ParentWindow.GetRange()
       
   978             
       
   979             if self.GraphType == GRAPH_PARALLEL:    
       
   980                 min_value = max_value = None
       
   981                 
       
   982                 for idx, item in enumerate(self.Items):
       
   983                     data = item.GetData(start_tick, end_tick)
       
   984                     if data is not None:
       
   985                         item_min_value, item_max_value = item.GetValueRange()
       
   986                         if min_value is None:
       
   987                             min_value = item_min_value
       
   988                         elif item_min_value is not None:
       
   989                             min_value = min(min_value, item_min_value)
       
   990                         if max_value is None:
       
   991                             max_value = item_max_value
       
   992                         elif item_max_value is not None:
       
   993                             max_value = max(max_value, item_max_value)
       
   994                         
       
   995                         if len(self.Plots) <= idx:
       
   996                             self.Plots.append(
       
   997                                 self.Axes.plot(data[:, 0], data[:, 1])[0])
       
   998                         else:
       
   999                             self.Plots[idx].set_data(data[:, 0], data[:, 1])
       
  1000                     
       
  1001                 if min_value is not None and max_value is not None:
       
  1002                     y_center = (min_value + max_value) / 2.
       
  1003                     y_range = max(1.0, max_value - min_value)
       
  1004                 else:
       
  1005                     y_center = 0.5
       
  1006                     y_range = 1.0
       
  1007                 x_min, x_max = start_tick, end_tick
       
  1008                 y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
       
  1009                 
       
  1010                 if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
  1011                     if self.VLine is None:
       
  1012                         self.VLine = self.Axes.axvline(self.CursorTick, color=cursor_color)
       
  1013                     else:
       
  1014                         self.VLine.set_xdata((self.CursorTick, self.CursorTick))
       
  1015                     self.VLine.set_visible(True)
       
  1016                 else:
       
  1017                     if self.VLine is not None:
       
  1018                         self.VLine.set_visible(False)
       
  1019             else:
       
  1020                 min_start_tick = reduce(max, [item.GetData()[0, 0] 
       
  1021                                               for item in self.Items
       
  1022                                               if len(item.GetData()) > 0], 0)
       
  1023                 start_tick = max(start_tick, min_start_tick)
       
  1024                 end_tick = max(end_tick, min_start_tick)
       
  1025                 x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick)
       
  1026                 y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick)
       
  1027                 if self.CursorTick is not None:
       
  1028                     x_cursor, x_forced = self.Items[0].GetValue(self.CursorTick, raw=True)
       
  1029                     y_cursor, y_forced = self.Items[1].GetValue(self.CursorTick, raw=True)
       
  1030                 length = 0
       
  1031                 if x_data is not None and y_data is not None:  
       
  1032                     length = min(len(x_data), len(y_data))
       
  1033                 if len(self.Items) < 3:
       
  1034                     if x_data is not None and y_data is not None:
       
  1035                         if len(self.Plots) == 0:
       
  1036                             self.Plots.append(
       
  1037                                 self.Axes.plot(x_data[:, 1][:length], 
       
  1038                                                y_data[:, 1][:length])[0])
       
  1039                         else:
       
  1040                             self.Plots[0].set_data(
       
  1041                                 x_data[:, 1][:length], 
       
  1042                                 y_data[:, 1][:length])
       
  1043                     
       
  1044                     if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
  1045                         if self.VLine is None:
       
  1046                             self.VLine = self.Axes.axvline(x_cursor, color=cursor_color)
       
  1047                         else:
       
  1048                             self.VLine.set_xdata((x_cursor, x_cursor))
       
  1049                         if self.HLine is None:
       
  1050                             self.HLine = self.Axes.axhline(y_cursor, color=cursor_color)
       
  1051                         else:
       
  1052                             self.HLine.set_ydata((y_cursor, y_cursor))
       
  1053                         self.VLine.set_visible(True)
       
  1054                         self.HLine.set_visible(True)
       
  1055                     else:
       
  1056                         if self.VLine is not None:
       
  1057                             self.VLine.set_visible(False)
       
  1058                         if self.HLine is not None:
       
  1059                             self.HLine.set_visible(False)
       
  1060                 else:
       
  1061                     while len(self.Axes.lines) > 0:
       
  1062                         self.Axes.lines.pop()
       
  1063                     z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick)
       
  1064                     if self.CursorTick is not None:
       
  1065                         z_cursor, z_forced = self.Items[2].GetValue(self.CursorTick, raw=True)
       
  1066                     if x_data is not None and y_data is not None and z_data is not None:
       
  1067                         length = min(length, len(z_data))
       
  1068                         self.Axes.plot(x_data[:, 1][:length],
       
  1069                                        y_data[:, 1][:length],
       
  1070                                        zs = z_data[:, 1][:length])
       
  1071                     self.Axes.set_zlim(z_min, z_max)
       
  1072                     if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
  1073                         for kwargs in [{"xs": numpy.array([x_min, x_max])},
       
  1074                                        {"ys": numpy.array([y_min, y_max])},
       
  1075                                        {"zs": numpy.array([z_min, z_max])}]:
       
  1076                             for param, value in [("xs", numpy.array([x_cursor, x_cursor])),
       
  1077                                                  ("ys", numpy.array([y_cursor, y_cursor])),
       
  1078                                                  ("zs", numpy.array([z_cursor, z_cursor]))]:
       
  1079                                 kwargs.setdefault(param, value)
       
  1080                             kwargs["color"] = cursor_color
       
  1081                             self.Axes.plot(**kwargs)
       
  1082                 
       
  1083             self.Axes.set_xlim(x_min, x_max)
       
  1084             self.Axes.set_ylim(y_min, y_max)
       
  1085         
       
  1086         variable_name_mask = self.ParentWindow.GetVariableNameMask()
       
  1087         if self.CursorTick is not None:
       
  1088             values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items])
       
  1089         else:
       
  1090             values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items])
       
  1091         labels = [item.GetVariable(variable_name_mask) for item in self.Items]
       
  1092         styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
       
  1093         if self.Is3DCanvas():
       
  1094             for idx, label_func in enumerate([self.Axes.set_xlabel, 
       
  1095                                               self.Axes.set_ylabel,
       
  1096                                               self.Axes.set_zlabel]):
       
  1097                 label_func(labels[idx], fontdict={'size': 'small','color': color_cycle[idx]})
       
  1098         else:
       
  1099             for label, text in zip(self.AxesLabels, labels):
       
  1100                 label.set_text(text)
       
  1101         for label, value, style in zip(self.Labels, values, styles):
       
  1102             label.set_text(value)
       
  1103             label.set_style(style)
       
  1104         
       
  1105         self.draw()
       
  1106 
       
  1107     def ExportGraph(self, item=None):
       
  1108         if item is not None:
       
  1109             variables = [(item, [entry for entry in item.GetData()])]
       
  1110         else:
       
  1111             variables = [(item, [entry for entry in item.GetData()])
       
  1112                          for item in self.Items]
       
  1113         self.ParentWindow.CopyDataToClipboard(variables)
       
  1114     
       
  1115 class DebugVariableGraphicPanel(wx.Panel, DebugViewer):
   120 class DebugVariableGraphicPanel(wx.Panel, DebugViewer):
  1116     
   121     
  1117     def __init__(self, parent, producer, window):
   122     def __init__(self, parent, producer, window):
  1118         wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
   123         wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
  1119         
   124         
  1288         self.CursorTick = None
   293         self.CursorTick = None
  1289         self.UpdateCursorTick()
   294         self.UpdateCursorTick()
  1290     
   295     
  1291     def UpdateCursorTick(self):
   296     def UpdateCursorTick(self):
  1292         for panel in self.GraphicPanels:
   297         for panel in self.GraphicPanels:
  1293             if isinstance(panel, DebugVariableGraphic):
   298             if isinstance(panel, DebugVariableGraphicViewer):
  1294                 panel.SetCursorTick(self.CursorTick)
   299                 panel.SetCursorTick(self.CursorTick)
  1295         self.ForceRefresh()
   300         self.ForceRefresh()
  1296     
   301     
  1297     def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
   302     def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
  1298         if len(panel.GetItems()) > 1:
   303         if len(panel.GetItems()) > 1:
  1299             self.DraggingAxesPanel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   304             self.DraggingAxesPanel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
  1300             self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
   305             self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
  1301             width, height = panel.GetSize()
   306             width, height = panel.GetSize()
  1302             self.DraggingAxesPanel.SetSize(wx.Size(width, height))
   307             self.DraggingAxesPanel.SetSize(wx.Size(width, height))
  1303             self.DraggingAxesPanel.ResetGraphics()
   308             self.DraggingAxesPanel.ResetGraphics()
  1304             self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
   309             self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
  1321             width, height = panel.GetSize()
   326             width, height = panel.GetSize()
  1322             rect = wx.Rect(x, y, width, height)
   327             rect = wx.Rect(x, y, width, height)
  1323             if (rect.InsideXY(x_mouse, y_mouse) or 
   328             if (rect.InsideXY(x_mouse, y_mouse) or 
  1324                 idx == 0 and y_mouse < 0 or
   329                 idx == 0 and y_mouse < 0 or
  1325                 idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
   330                 idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
  1326                 panel.OnDragging(x_mouse - x, y_mouse - y)
   331                 panel.RefreshHighlight(x_mouse - x, y_mouse - y)
  1327             else:
   332             else:
  1328                 panel.SetHighlight(HIGHLIGHT_NONE)
   333                 panel.SetHighlight(HIGHLIGHT_NONE)
  1329         if wx.Platform == "__WXMSW__":
   334         if wx.Platform == "__WXMSW__":
  1330             self.RefreshView()
   335             self.RefreshView()
  1331         else:
   336         else:
  1368             width, height = panel.GetSize()
   373             width, height = panel.GetSize()
  1369             bbox = wx.Rect(xw, yw, width, height)
   374             bbox = wx.Rect(xw, yw, width, height)
  1370             if bbox.InsideXY(x_mouse, y_mouse):
   375             if bbox.InsideXY(x_mouse, y_mouse):
  1371                 panel.ShowButtons(True)
   376                 panel.ShowButtons(True)
  1372                 merge_type = GRAPH_PARALLEL
   377                 merge_type = GRAPH_PARALLEL
  1373                 if isinstance(panel, DebugVariableText) or panel.Is3DCanvas():
   378                 if isinstance(panel, DebugVariableTextViewer) or panel.Is3DCanvas():
  1374                     if y_mouse > yw + height / 2:
   379                     if y_mouse > yw + height / 2:
  1375                         idx += 1
   380                         idx += 1
  1376                     wx.CallAfter(self.MoveValue, variable, idx)
   381                     wx.CallAfter(self.MoveValue, variable, idx)
  1377                 else:
   382                 else:
  1378                     rect = panel.GetAxesBoundingBox(True)
   383                     rect = panel.GetAxesBoundingBox(True)
  1421             refresh_graphics = False
   426             refresh_graphics = False
  1422         
   427         
  1423         if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
   428         if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
  1424             self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
   429             self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
  1425         for panel in self.GraphicPanels:
   430         for panel in self.GraphicPanels:
  1426             if isinstance(panel, DebugVariableGraphic):
   431             if isinstance(panel, DebugVariableGraphicViewer):
  1427                 panel.RefreshViewer(refresh_graphics)
   432                 panel.RefreshViewer(refresh_graphics)
  1428             else:
   433             else:
  1429                 panel.RefreshViewer()
   434                 panel.RefreshViewer()
  1430         
   435         
  1431         if self.CursorTick is not None:
   436         if self.CursorTick is not None:
  1461         if self.DataProducer is not None:
   466         if self.DataProducer is not None:
  1462             if self.DataProducer is not None:
   467             if self.DataProducer is not None:
  1463                 self.SetTickTime(self.DataProducer.GetTicktime())
   468                 self.SetTickTime(self.DataProducer.GetTicktime())
  1464         
   469         
  1465         for panel in self.GraphicPanels:
   470         for panel in self.GraphicPanels:
  1466             panel.UnregisterObsoleteData()
   471             panel.UnsubscribeObsoleteData()
  1467             if panel.IsEmpty():
   472             if panel.ItemsIsEmpty():
  1468                 if panel.HasCapture():
   473                 if panel.HasCapture():
  1469                     panel.ReleaseMouse()
   474                     panel.ReleaseMouse()
  1470                 self.GraphicPanels.remove(panel)
   475                 self.GraphicPanels.remove(panel)
  1471                 panel.Destroy()
   476                 panel.Destroy()
  1472         
   477         
  1623         item = DebugVariableItem(self, iec_path, True)
   628         item = DebugVariableItem(self, iec_path, True)
  1624         result = self.AddDataConsumer(iec_path.upper(), item)
   629         result = self.AddDataConsumer(iec_path.upper(), item)
  1625         if result is not None or force:
   630         if result is not None or force:
  1626             
   631             
  1627             if item.IsNumVariable():
   632             if item.IsNumVariable():
  1628                 panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   633                 panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
  1629                 if self.CursorTick is not None:
   634                 if self.CursorTick is not None:
  1630                     panel.SetCursorTick(self.CursorTick)
   635                     panel.SetCursorTick(self.CursorTick)
  1631             else:
   636             else:
  1632                 panel = DebugVariableText(self.GraphicsWindow, self, [item])
   637                 panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
  1633             if idx is not None:
   638             if idx is not None:
  1634                 self.GraphicPanels.insert(idx, panel)
   639                 self.GraphicPanels.insert(idx, panel)
  1635             else:
   640             else:
  1636                 self.GraphicPanels.append(panel)
   641                 self.GraphicPanels.append(panel)
  1637             self.ResetVariableNameMask()
   642             self.ResetVariableNameMask()
  1649                 source_panel = panel
   654                 source_panel = panel
  1650                 break
   655                 break
  1651         if source_panel is not None:
   656         if source_panel is not None:
  1652             source_panel.RemoveItem(item)
   657             source_panel.RemoveItem(item)
  1653             source_size = source_panel.GetSize()
   658             source_size = source_panel.GetSize()
  1654             if source_panel.IsEmpty():
   659             if source_panel.ItemsIsEmpty():
  1655                 if source_panel.HasCapture():
   660                 if source_panel.HasCapture():
  1656                     source_panel.ReleaseMouse()
   661                     source_panel.ReleaseMouse()
  1657                 self.GraphicPanels.remove(source_panel)
   662                 self.GraphicPanels.remove(source_panel)
  1658                 source_panel.Destroy()
   663                 source_panel.Destroy()
  1659             
   664             
  1660             if item.IsNumVariable():
   665             if item.IsNumVariable():
  1661                 panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   666                 panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
  1662                 panel.SetCanvasSize(source_size.width, source_size.height)
   667                 panel.SetCanvasSize(source_size.width, source_size.height)
  1663                 if self.CursorTick is not None:
   668                 if self.CursorTick is not None:
  1664                     panel.SetCursorTick(self.CursorTick)
   669                     panel.SetCursorTick(self.CursorTick)
  1665             else:
   670             else:
  1666                 panel = DebugVariableText(self.GraphicsWindow, self, [item])
   671                 panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
  1667             self.GraphicPanels.insert(idx, panel)
   672             self.GraphicPanels.insert(idx, panel)
  1668             self.ResetVariableNameMask()
   673             self.ResetVariableNameMask()
  1669             self.RefreshGraphicsSizer()
   674             self.RefreshGraphicsSizer()
  1670             self.ForceRefresh()
   675             self.ForceRefresh()
  1671     
   676     
  1697                      graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
   702                      graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
  1698                     return
   703                     return
  1699                 
   704                 
  1700                 if source_panel is not None:
   705                 if source_panel is not None:
  1701                     source_panel.RemoveItem(source_item)
   706                     source_panel.RemoveItem(source_item)
  1702                     if source_panel.IsEmpty():
   707                     if source_panel.ItemsIsEmpty():
  1703                         if source_panel.HasCapture():
   708                         if source_panel.HasCapture():
  1704                             source_panel.ReleaseMouse()
   709                             source_panel.ReleaseMouse()
  1705                         self.GraphicPanels.remove(source_panel)
   710                         self.GraphicPanels.remove(source_panel)
  1706                         source_panel.Destroy()
   711                         source_panel.Destroy()
  1707             elif (merge_type != graph_type and len(target_panel.Items) == 2):
   712             elif (merge_type != graph_type and len(target_panel.Items) == 2):
  1728     def DeleteValue(self, source_panel, item=None):
   733     def DeleteValue(self, source_panel, item=None):
  1729         source_idx = self.GetViewerIndex(source_panel)
   734         source_idx = self.GetViewerIndex(source_panel)
  1730         if source_idx is not None:
   735         if source_idx is not None:
  1731             
   736             
  1732             if item is None:
   737             if item is None:
  1733                 source_panel.Clear()
   738                 source_panel.ClearItems()
  1734                 source_panel.Destroy()
   739                 source_panel.Destroy()
  1735                 self.GraphicPanels.remove(source_panel)
   740                 self.GraphicPanels.remove(source_panel)
  1736                 self.ResetVariableNameMask()
   741                 self.ResetVariableNameMask()
  1737                 self.RefreshGraphicsSizer()
   742                 self.RefreshGraphicsSizer()
  1738             else:
   743             else:
  1739                 source_panel.RemoveItem(item)
   744                 source_panel.RemoveItem(item)
  1740                 if source_panel.IsEmpty():
   745                 if source_panel.ItemsIsEmpty():
  1741                     source_panel.Destroy()
   746                     source_panel.Destroy()
  1742                     self.GraphicPanels.remove(source_panel)
   747                     self.GraphicPanels.remove(source_panel)
  1743                     self.ResetVariableNameMask()
   748                     self.ResetVariableNameMask()
  1744                     self.RefreshGraphicsSizer()
   749                     self.RefreshGraphicsSizer()
  1745             self.ForceRefresh()
   750             self.ForceRefresh()
  1747     def ResetGraphicsValues(self):
   752     def ResetGraphicsValues(self):
  1748         self.Ticks = numpy.array([])
   753         self.Ticks = numpy.array([])
  1749         self.StartTick = 0
   754         self.StartTick = 0
  1750         self.Fixed = False
   755         self.Fixed = False
  1751         for panel in self.GraphicPanels:
   756         for panel in self.GraphicPanels:
  1752             panel.ResetData()
   757             panel.ResetItemsData()
  1753         self.ResetCursorTick()
   758         self.ResetCursorTick()
  1754 
   759 
  1755     def RefreshGraphicsWindowScrollbars(self):
   760     def RefreshGraphicsWindowScrollbars(self):
  1756         xstart, ystart = self.GraphicsWindow.GetViewStart()
   761         xstart, ystart = self.GraphicsWindow.GetViewStart()
  1757         window_size = self.GraphicsWindow.GetClientSize()
   762         window_size = self.GraphicsWindow.GetClientSize()
  1771     
   776     
  1772     def OnGraphicsWindowResize(self, event):
   777     def OnGraphicsWindowResize(self, event):
  1773         size = self.GetSize()
   778         size = self.GetSize()
  1774         for panel in self.GraphicPanels:
   779         for panel in self.GraphicPanels:
  1775             panel_size = panel.GetSize()
   780             panel_size = panel.GetSize()
  1776             if (isinstance(panel, DebugVariableGraphic) and 
   781             if (isinstance(panel, DebugVariableGraphicViewer) and 
  1777                 panel.GraphType == GRAPH_ORTHOGONAL and 
   782                 panel.GraphType == GRAPH_ORTHOGONAL and 
  1778                 panel_size.width == panel_size.height):
   783                 panel_size.width == panel_size.height):
  1779                 panel.SetCanvasSize(size.width, size.width)
   784                 panel.SetCanvasSize(size.width, size.width)
  1780         self.RefreshGraphicsWindowScrollbars()
   785         self.RefreshGraphicsWindowScrollbars()
  1781         self.GraphicsSizer.Layout()
   786         self.GraphicsSizer.Layout()