controls/DebugVariablePanel/DebugVariableGraphicPanel.py
changeset 1198 8b4e6bd0aa92
parent 1194 0cf48602ee24
child 1199 fc0e7d80494f
equal deleted inserted replaced
1197:eb118ed5cbe0 1198:8b4e6bd0aa92
       
     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 types import TupleType
       
    26 from time import time as gettime
       
    27 import math
       
    28 import numpy
       
    29 
       
    30 import wx
       
    31 import wx.lib.buttons
       
    32 
       
    33 import matplotlib
       
    34 matplotlib.use('WX')
       
    35 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
       
    38 from matplotlib.backends.backend_agg import FigureCanvasAgg
       
    39 from mpl_toolkits.mplot3d import Axes3D
       
    40 color_cycle = ['r', 'b', 'g', 'm', 'y', 'k']
       
    41 cursor_color = '#800080'
       
    42 
       
    43 from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
       
    44 from controls.DebugVariablePanel.DebugVariableItem import DebugVariableItem
       
    45 from dialogs.ForceVariableDialog import ForceVariableDialog
       
    46 from util.BitmapLibrary import GetBitmap
       
    47 
       
    48 class DebugVariableDropTarget(wx.TextDropTarget):
       
    49     
       
    50     def __init__(self, parent, control=None):
       
    51         wx.TextDropTarget.__init__(self)
       
    52         self.ParentWindow = parent
       
    53         self.ParentControl = control
       
    54     
       
    55     def __del__(self):
       
    56         self.ParentWindow = None
       
    57         self.ParentControl = None
       
    58     
       
    59     def OnDragOver(self, x, y, d):
       
    60         if self.ParentControl is not None:
       
    61             self.ParentControl.OnMouseDragging(x, y)
       
    62         else:
       
    63             self.ParentWindow.RefreshHighlight(x, y)
       
    64         return wx.TextDropTarget.OnDragOver(self, x, y, d)
       
    65         
       
    66     def OnDropText(self, x, y, data):
       
    67         message = None
       
    68         try:
       
    69             values = eval(data)
       
    70         except:
       
    71             message = _("Invalid value \"%s\" for debug variable")%data
       
    72             values = None
       
    73         if not isinstance(values, TupleType):
       
    74             message = _("Invalid value \"%s\" for debug variable")%data
       
    75             values = None
       
    76         
       
    77         if message is not None:
       
    78             wx.CallAfter(self.ShowMessage, message)
       
    79         elif values is not None and values[1] == "debug":
       
    80             if self.ParentControl is not None:
       
    81                 width, height = self.ParentControl.GetSize()
       
    82                 target_idx = self.ParentControl.GetIndex()
       
    83                 merge_type = GRAPH_PARALLEL
       
    84                 if isinstance(self.ParentControl, DebugVariableGraphic):
       
    85                     if self.ParentControl.Is3DCanvas():
       
    86                         if y > height / 2:
       
    87                             target_idx += 1
       
    88                         if len(values) > 1 and values[2] == "move":
       
    89                             self.ParentWindow.MoveValue(values[0], target_idx)
       
    90                         else:
       
    91                             self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
    92                         
       
    93                     else:
       
    94                         rect = self.ParentControl.GetAxesBoundingBox()
       
    95                         if rect.InsideXY(x, y):
       
    96                             merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
       
    97                             if merge_rect.InsideXY(x, y):
       
    98                                 merge_type = GRAPH_ORTHOGONAL
       
    99                             wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
       
   100                         else:
       
   101                             if y > height / 2:
       
   102                                 target_idx += 1
       
   103                             if len(values) > 2 and values[2] == "move":
       
   104                                 self.ParentWindow.MoveValue(values[0], target_idx)
       
   105                             else:
       
   106                                 self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
   107                 else:
       
   108                     if y > height / 2:
       
   109                         target_idx += 1
       
   110                     if len(values) > 2 and values[2] == "move":
       
   111                         self.ParentWindow.MoveValue(values[0], target_idx)
       
   112                     else:
       
   113                         self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
   114                     
       
   115             elif len(values) > 2 and values[2] == "move":
       
   116                 self.ParentWindow.MoveValue(values[0])
       
   117             else:
       
   118                 self.ParentWindow.InsertValue(values[0], force=True)
       
   119     
       
   120     def OnLeave(self):
       
   121         self.ParentWindow.ResetHighlight()
       
   122         return wx.TextDropTarget.OnLeave(self)
       
   123     
       
   124     def ShowMessage(self, message):
       
   125         dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   126         dialog.ShowModal()
       
   127         dialog.Destroy()
       
   128 
       
   129 MILLISECOND = 1000000
       
   130 SECOND = 1000 * MILLISECOND
       
   131 MINUTE = 60 * SECOND
       
   132 HOUR = 60 * MINUTE
       
   133 DAY = 24 * HOUR
       
   134 
       
   135 ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)])
       
   136 RANGE_VALUES = \
       
   137     [("%dms" % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \
       
   138     [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
       
   139     [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
       
   140     [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
       
   141 
       
   142 GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
       
   143 
       
   144 SCROLLBAR_UNIT = 10
       
   145 
       
   146 #CANVAS_HIGHLIGHT_TYPES
       
   147 [HIGHLIGHT_NONE,
       
   148  HIGHLIGHT_BEFORE,
       
   149  HIGHLIGHT_AFTER,
       
   150  HIGHLIGHT_LEFT,
       
   151  HIGHLIGHT_RIGHT,
       
   152  HIGHLIGHT_RESIZE] = range(6)
       
   153 
       
   154 HIGHLIGHT_DROP_PEN = wx.Pen(wx.Colour(0, 128, 255))
       
   155 HIGHLIGHT_DROP_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128))
       
   156 HIGHLIGHT_RESIZE_PEN = wx.Pen(wx.Colour(200, 200, 200))
       
   157 HIGHLIGHT_RESIZE_BRUSH = wx.Brush(wx.Colour(200, 200, 200))
       
   158 
       
   159 #CANVAS_SIZE_TYPES
       
   160 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
       
   161 
       
   162 DEFAULT_CANVAS_HEIGHT = 200.
       
   163 CANVAS_BORDER = (20., 10.)
       
   164 CANVAS_PADDING = 8.5
       
   165 VALUE_LABEL_HEIGHT = 17.
       
   166 AXES_LABEL_HEIGHT = 12.75
       
   167 
       
   168 def compute_mask(x, y):
       
   169     mask = []
       
   170     for xp, yp in zip(x, y):
       
   171         if xp == yp:
       
   172             mask.append(xp)
       
   173         else:
       
   174             mask.append("*")
       
   175     return mask
       
   176 
       
   177 def NextTick(variables):
       
   178     next_tick = None
       
   179     for item, data in variables:
       
   180         if len(data) > 0:
       
   181             if next_tick is None:
       
   182                 next_tick = data[0][0]
       
   183             else:
       
   184                 next_tick = min(next_tick, data[0][0])
       
   185     return next_tick
       
   186 
       
   187 def OrthogonalData(item, start_tick, end_tick):
       
   188     data = item.GetData(start_tick, end_tick)
       
   189     min_value, max_value = item.GetValueRange()
       
   190     if min_value is not None and max_value is not None:
       
   191         center = (min_value + max_value) / 2.
       
   192         range = max(1.0, max_value - min_value)
       
   193     else:
       
   194         center = 0.5
       
   195         range = 1.0
       
   196     return data, center - range * 0.55, center + range * 0.55
       
   197 
       
   198 class GraphButton():
       
   199     
       
   200     def __init__(self, x, y, bitmap, callback):
       
   201         self.Position = wx.Point(x, y)
       
   202         self.Bitmap = bitmap
       
   203         self.Shown = True
       
   204         self.Enabled = True
       
   205         self.Callback = callback
       
   206     
       
   207     def __del__(self):
       
   208         self.callback = None
       
   209     
       
   210     def GetSize(self):
       
   211         return self.Bitmap.GetSize()
       
   212     
       
   213     def SetPosition(self, x, y):
       
   214         self.Position = wx.Point(x, y)
       
   215     
       
   216     def Show(self):
       
   217         self.Shown = True
       
   218         
       
   219     def Hide(self):
       
   220         self.Shown = False
       
   221     
       
   222     def IsShown(self):
       
   223         return self.Shown
       
   224     
       
   225     def Enable(self):
       
   226         self.Enabled = True
       
   227     
       
   228     def Disable(self):
       
   229         self.Enabled = False
       
   230     
       
   231     def IsEnabled(self):
       
   232         return self.Enabled
       
   233     
       
   234     def HitTest(self, x, y):
       
   235         if self.Shown and self.Enabled:
       
   236             w, h = self.Bitmap.GetSize()
       
   237             rect = wx.Rect(self.Position.x, self.Position.y, w, h)
       
   238             if rect.InsideXY(x, y):
       
   239                 return True
       
   240         return False
       
   241     
       
   242     def ProcessCallback(self):
       
   243         if self.Callback is not None:
       
   244             wx.CallAfter(self.Callback)
       
   245             
       
   246     def Draw(self, dc):
       
   247         if self.Shown and self.Enabled:
       
   248             dc.DrawBitmap(self.Bitmap, self.Position.x, self.Position.y, True)
       
   249 
       
   250 class DebugVariableViewer:
       
   251     
       
   252     def __init__(self, window, items=[]):
       
   253         self.ParentWindow = window
       
   254         self.Items = items
       
   255         
       
   256         self.Highlight = HIGHLIGHT_NONE
       
   257         self.Buttons = []
       
   258     
       
   259     def __del__(self):
       
   260         self.ParentWindow = None
       
   261     
       
   262     def GetIndex(self):
       
   263         return self.ParentWindow.GetViewerIndex(self)
       
   264     
       
   265     def GetItem(self, variable):
       
   266         for item in self.Items:
       
   267             if item.GetVariable() == variable:
       
   268                 return item
       
   269         return None
       
   270     
       
   271     def GetItems(self):
       
   272         return self.Items
       
   273     
       
   274     def GetVariables(self):
       
   275         if len(self.Items) > 1:
       
   276             variables = [item.GetVariable() for item in self.Items]
       
   277             if self.GraphType == GRAPH_ORTHOGONAL:
       
   278                 return tuple(variables)
       
   279             return variables
       
   280         return self.Items[0].GetVariable()
       
   281     
       
   282     def AddItem(self, item):
       
   283         self.Items.append(item)
       
   284         
       
   285     def RemoveItem(self, item):
       
   286         if item in self.Items:
       
   287             self.Items.remove(item)
       
   288         
       
   289     def Clear(self):
       
   290         for item in self.Items:
       
   291             self.ParentWindow.RemoveDataConsumer(item)
       
   292         self.Items = []
       
   293         
       
   294     def IsEmpty(self):
       
   295         return len(self.Items) == 0
       
   296     
       
   297     def UnregisterObsoleteData(self):
       
   298         for item in self.Items[:]:
       
   299             iec_path = item.GetVariable()
       
   300             if self.ParentWindow.GetDataType(iec_path) is None:
       
   301                 self.ParentWindow.RemoveDataConsumer(item)
       
   302                 self.RemoveItem(item)
       
   303             else:
       
   304                 self.ParentWindow.AddDataConsumer(iec_path.upper(), item)
       
   305                 item.RefreshVariableType()
       
   306         
       
   307     def ResetData(self):
       
   308         for item in self.Items:
       
   309             item.ResetData()
       
   310     
       
   311     def RefreshViewer(self):
       
   312         pass
       
   313     
       
   314     def SetHighlight(self, highlight):
       
   315         if self.Highlight != highlight:
       
   316             self.Highlight = highlight
       
   317             return True
       
   318         return False
       
   319     
       
   320     def GetButtons(self):
       
   321         return self.Buttons
       
   322     
       
   323     def HandleButtons(self, x, y):
       
   324         for button in self.GetButtons():
       
   325             if button.HitTest(x, y):
       
   326                 button.ProcessCallback()
       
   327                 return True
       
   328         return False
       
   329     
       
   330     def IsOverButton(self, x, y):
       
   331         for button in self.GetButtons():
       
   332             if button.HitTest(x, y):
       
   333                 return True
       
   334         return False
       
   335     
       
   336     def ShowButtons(self, show):
       
   337         for button in self.Buttons:
       
   338             if show:
       
   339                 button.Show()
       
   340             else:
       
   341                 button.Hide()
       
   342         self.RefreshButtonsState()
       
   343         self.ParentWindow.ForceRefresh()
       
   344     
       
   345     def RefreshButtonsState(self, refresh_positions=False):
       
   346         if self:
       
   347             width, height = self.GetSize()
       
   348             if refresh_positions:
       
   349                 offset = 0
       
   350                 buttons = self.Buttons[:]
       
   351                 buttons.reverse()
       
   352                 for button in buttons:
       
   353                     w, h = button.GetSize()
       
   354                     button.SetPosition(width - 5 - w - offset, 5)
       
   355                     offset += w + 2
       
   356                 self.ParentWindow.ForceRefresh()
       
   357     
       
   358     def DrawCommonElements(self, dc, buttons=None):
       
   359         width, height = self.GetSize()
       
   360         
       
   361         dc.SetPen(HIGHLIGHT_DROP_PEN)
       
   362         dc.SetBrush(HIGHLIGHT_DROP_BRUSH)
       
   363         if self.Highlight in [HIGHLIGHT_BEFORE]:
       
   364             dc.DrawLine(0, 1, width - 1, 1)
       
   365         elif self.Highlight in [HIGHLIGHT_AFTER]:
       
   366             dc.DrawLine(0, height - 1, width - 1, height - 1)
       
   367         
       
   368         if buttons is None:
       
   369             buttons = self.Buttons
       
   370         for button in buttons:
       
   371             button.Draw(dc)
       
   372             
       
   373         if self.ParentWindow.IsDragging():
       
   374             destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self)
       
   375             srcPos = self.ParentWindow.GetDraggingAxesPosition(self)
       
   376             if destBBox.width > 0 and destBBox.height > 0:
       
   377                 srcPanel = self.ParentWindow.DraggingAxesPanel
       
   378                 srcBBox = srcPanel.GetAxesBoundingBox()
       
   379                 
       
   380                 if destBBox.x == 0:
       
   381                     srcX = srcBBox.x - srcPos.x
       
   382                 else:
       
   383                     srcX = srcBBox.x
       
   384                 if destBBox.y == 0:
       
   385                     srcY = srcBBox.y - srcPos.y
       
   386                 else:
       
   387                     srcY = srcBBox.y
       
   388                 
       
   389                 srcBmp = _convert_agg_to_wx_bitmap(srcPanel.get_renderer(), None)
       
   390                 srcDC = wx.MemoryDC()
       
   391                 srcDC.SelectObject(srcBmp)
       
   392                 
       
   393                 dc.Blit(destBBox.x, destBBox.y, 
       
   394                         int(destBBox.width), int(destBBox.height), 
       
   395                         srcDC, srcX, srcY)
       
   396     
       
   397     def OnEnter(self, event):
       
   398         self.ShowButtons(True)
       
   399         event.Skip()
       
   400         
       
   401     def OnLeave(self, event):
       
   402         if self.Highlight != HIGHLIGHT_RESIZE or self.CanvasStartSize is None:
       
   403             x, y = event.GetPosition()
       
   404             width, height = self.GetSize()
       
   405             if (x <= 0 or x >= width - 1 or
       
   406                 y <= 0 or y >= height - 1):
       
   407                 self.ShowButtons(False)
       
   408         event.Skip()
       
   409     
       
   410     def OnCloseButton(self):
       
   411         wx.CallAfter(self.ParentWindow.DeleteValue, self)
       
   412     
       
   413     def OnForceButton(self):
       
   414         wx.CallAfter(self.ForceValue, self.Items[0])
       
   415         
       
   416     def OnReleaseButton(self):
       
   417         wx.CallAfter(self.ReleaseValue, self.Items[0])
       
   418     
       
   419     def OnResizeWindow(self, event):
       
   420         wx.CallAfter(self.RefreshButtonsState, True)
       
   421         event.Skip()
       
   422     
       
   423     def OnMouseDragging(self, x, y):
       
   424         xw, yw = self.GetPosition()
       
   425         self.ParentWindow.RefreshHighlight(x + xw, y + yw)
       
   426     
       
   427     def OnDragging(self, x, y):
       
   428         width, height = self.GetSize()
       
   429         if y < height / 2:
       
   430             if self.ParentWindow.IsViewerFirst(self):
       
   431                 self.SetHighlight(HIGHLIGHT_BEFORE)
       
   432             else:
       
   433                 self.SetHighlight(HIGHLIGHT_NONE)
       
   434                 self.ParentWindow.HighlightPreviousViewer(self)
       
   435         else:
       
   436             self.SetHighlight(HIGHLIGHT_AFTER)
       
   437     
       
   438     def OnEraseBackground(self, event):
       
   439         pass
       
   440     
       
   441     def OnResize(self, event):
       
   442         wx.CallAfter(self.RefreshButtonsState, True)
       
   443         event.Skip()
       
   444     
       
   445     def ForceValue(self, item):
       
   446         iec_path = item.GetVariable()
       
   447         iec_type = self.ParentWindow.GetDataType(iec_path)
       
   448         if iec_type is not None:
       
   449             dialog = ForceVariableDialog(self, iec_type, str(item.GetValue()))
       
   450             if dialog.ShowModal() == wx.ID_OK:
       
   451                 self.ParentWindow.ForceDataValue(iec_path.upper(), dialog.GetValue())
       
   452     
       
   453     def ReleaseValue(self, item):
       
   454         iec_path = item.GetVariable().upper()
       
   455         self.ParentWindow.ReleaseDataValue(iec_path)
       
   456 
       
   457 
       
   458 class DebugVariableText(DebugVariableViewer, wx.Panel):
       
   459     
       
   460     def __init__(self, parent, window, items=[]):
       
   461         DebugVariableViewer.__init__(self, window, items)
       
   462         
       
   463         wx.Panel.__init__(self, parent)
       
   464         self.SetBackgroundColour(wx.WHITE)
       
   465         self.SetDropTarget(DebugVariableDropTarget(window, self))
       
   466         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
       
   467         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
       
   468         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
       
   469         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
       
   470         self.Bind(wx.EVT_SIZE, self.OnResize)
       
   471         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
       
   472         self.Bind(wx.EVT_PAINT, self.OnPaint)
       
   473         
       
   474         self.SetMinSize(wx.Size(0, 25))
       
   475         
       
   476         self.Buttons.append(
       
   477             GraphButton(0, 0, GetBitmap("force"), self.OnForceButton))
       
   478         self.Buttons.append(
       
   479             GraphButton(0, 0, GetBitmap("release"), self.OnReleaseButton))
       
   480         self.Buttons.append(
       
   481             GraphButton(0, 0, GetBitmap("delete_graph"), self.OnCloseButton))
       
   482         
       
   483         self.ShowButtons(False)
       
   484         
       
   485     def RefreshViewer(self):
       
   486         width, height = self.GetSize()
       
   487         bitmap = wx.EmptyBitmap(width, height)
       
   488         
       
   489         dc = wx.BufferedDC(wx.ClientDC(self), bitmap)
       
   490         dc.Clear()
       
   491         dc.BeginDrawing()
       
   492         
       
   493         gc = wx.GCDC(dc)
       
   494         
       
   495         item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
       
   496         w, h = gc.GetTextExtent(item_name)
       
   497         gc.DrawText(item_name, 20, (height - h) / 2)
       
   498         
       
   499         if self.Items[0].IsForced():
       
   500             gc.SetTextForeground(wx.BLUE)
       
   501             self.Buttons[0].Disable()
       
   502             self.Buttons[1].Enable()
       
   503         else:
       
   504             self.Buttons[1].Disable()
       
   505             self.Buttons[0].Enable()
       
   506         self.RefreshButtonsState(True)
       
   507         
       
   508         item_value = self.Items[0].GetValue()
       
   509         w, h = gc.GetTextExtent(item_value)
       
   510         gc.DrawText(item_value, width - 40 - w, (height - h) / 2)
       
   511         
       
   512         self.DrawCommonElements(gc)
       
   513         
       
   514         gc.EndDrawing()
       
   515     
       
   516     def OnLeftDown(self, event):
       
   517         width, height = self.GetSize()
       
   518         item_name = self.Items[0].GetVariable(self.ParentWindow.GetVariableNameMask())
       
   519         w, h = self.GetTextExtent(item_name)
       
   520         x, y = event.GetPosition()
       
   521         rect = wx.Rect(20, (height - h) / 2, w, h)
       
   522         if rect.InsideXY(x, y):
       
   523             data = wx.TextDataObject(str((self.Items[0].GetVariable(), "debug", "move")))
       
   524             dragSource = wx.DropSource(self)
       
   525             dragSource.SetData(data)
       
   526             dragSource.DoDragDrop()
       
   527         else:
       
   528             event.Skip()
       
   529     
       
   530     def OnLeftUp(self, event):
       
   531         x, y = event.GetPosition()
       
   532         wx.CallAfter(self.HandleButtons, x, y)
       
   533         event.Skip()
       
   534     
       
   535     def OnPaint(self, event):
       
   536         self.RefreshViewer()
       
   537         event.Skip()
       
   538 
       
   539 class DebugVariableGraphic(DebugVariableViewer, FigureCanvas):
       
   540     
       
   541     def __init__(self, parent, window, items, graph_type):
       
   542         DebugVariableViewer.__init__(self, window, items)
       
   543         
       
   544         self.CanvasSize = SIZE_MINI
       
   545         self.GraphType = graph_type
       
   546         self.CursorTick = None
       
   547         self.MouseStartPos = None
       
   548         self.StartCursorTick = None
       
   549         self.CanvasStartSize = None
       
   550         self.ContextualButtons = []
       
   551         self.ContextualButtonsItem = None
       
   552         
       
   553         self.Figure = matplotlib.figure.Figure(facecolor='w')
       
   554         self.Figure.subplotpars.update(top=0.95, left=0.1, bottom=0.1, right=0.95)
       
   555         
       
   556         FigureCanvas.__init__(self, parent, -1, self.Figure)
       
   557         self.SetWindowStyle(wx.WANTS_CHARS)
       
   558         self.SetBackgroundColour(wx.WHITE)
       
   559         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
       
   560         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
       
   561         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
       
   562         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
       
   563         self.Bind(wx.EVT_SIZE, self.OnResize)
       
   564         
       
   565         canvas_size = self.GetCanvasMinSize()
       
   566         self.SetMinSize(canvas_size)
       
   567         self.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self))
       
   568         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
       
   569         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
       
   570         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
       
   571         self.mpl_connect('scroll_event', self.OnCanvasScroll)
       
   572         
       
   573         for size, bitmap in zip([SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
       
   574                                 ["minimize_graph", "middle_graph", "maximize_graph"]):
       
   575             self.Buttons.append(GraphButton(0, 0, GetBitmap(bitmap), self.GetOnChangeSizeButton(size)))
       
   576         self.Buttons.append(
       
   577             GraphButton(0, 0, GetBitmap("export_graph_mini"), self.OnExportGraphButton))
       
   578         self.Buttons.append(
       
   579             GraphButton(0, 0, GetBitmap("delete_graph"), self.OnCloseButton))
       
   580         
       
   581         self.ResetGraphics()
       
   582         self.RefreshLabelsPosition(canvas_size.height)
       
   583         self.ShowButtons(False)
       
   584     
       
   585     def draw(self, drawDC=None):
       
   586         FigureCanvasAgg.draw(self)
       
   587 
       
   588         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
       
   589         self.bitmap.UseAlpha() 
       
   590         width, height = self.GetSize()
       
   591         bbox = self.GetAxesBoundingBox()
       
   592         
       
   593         destDC = wx.MemoryDC()
       
   594         destDC.SelectObject(self.bitmap)
       
   595         
       
   596         destGC = wx.GCDC(destDC)
       
   597         
       
   598         destGC.BeginDrawing()
       
   599         if self.Highlight == HIGHLIGHT_RESIZE:
       
   600             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
       
   601             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
       
   602             destGC.DrawRectangle(0, height - 5, width, 5)
       
   603         else:
       
   604             destGC.SetPen(HIGHLIGHT_DROP_PEN)
       
   605             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
       
   606             if self.Highlight == HIGHLIGHT_LEFT:
       
   607                 destGC.DrawRectangle(bbox.x, bbox.y, 
       
   608                                      bbox.width / 2, bbox.height)
       
   609             elif self.Highlight == HIGHLIGHT_RIGHT:
       
   610                 destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y, 
       
   611                                      bbox.width / 2, bbox.height)
       
   612         
       
   613         self.DrawCommonElements(destGC, self.Buttons + self.ContextualButtons)
       
   614         
       
   615         destGC.EndDrawing()
       
   616         
       
   617         self._isDrawn = True
       
   618         self.gui_repaint(drawDC=drawDC)
       
   619     
       
   620     def GetButtons(self):
       
   621         return self.Buttons + self.ContextualButtons
       
   622     
       
   623     def PopupContextualButtons(self, item, rect, style=wx.RIGHT):
       
   624         if self.ContextualButtonsItem is not None and item != self.ContextualButtonsItem:
       
   625             self.DismissContextualButtons()
       
   626         
       
   627         if self.ContextualButtonsItem is None:
       
   628             self.ContextualButtonsItem = item
       
   629             
       
   630             if self.ContextualButtonsItem.IsForced():
       
   631                 self.ContextualButtons.append(
       
   632                     GraphButton(0, 0, GetBitmap("release"), self.OnReleaseButton))
       
   633             self.ContextualButtons.append(
       
   634                 GraphButton(0, 0, GetBitmap("force"), self.OnForceButton))
       
   635             self.ContextualButtons.append(
       
   636                 GraphButton(0, 0, GetBitmap("export_graph_mini"), self.OnExportItemGraphButton))
       
   637             self.ContextualButtons.append(
       
   638                 GraphButton(0, 0, GetBitmap("delete_graph"), self.OnRemoveItemButton))
       
   639             
       
   640             offset = 0
       
   641             buttons = self.ContextualButtons[:]
       
   642             if style in [wx.TOP, wx.LEFT]:
       
   643                  buttons.reverse()
       
   644             for button in buttons:
       
   645                 w, h = button.GetSize()
       
   646                 if style in [wx.LEFT, wx.RIGHT]:
       
   647                     if style == wx.LEFT:
       
   648                         x = rect.x - w - offset
       
   649                     else:
       
   650                         x = rect.x + rect.width + offset
       
   651                     y = rect.y + (rect.height - h) / 2
       
   652                     offset += w
       
   653                 else:
       
   654                     x = rect.x + (rect.width - w ) / 2
       
   655                     if style == wx.TOP:
       
   656                         y = rect.y - h - offset
       
   657                     else:
       
   658                         y = rect.y + rect.height + offset
       
   659                     offset += h
       
   660                 button.SetPosition(x, y)
       
   661             self.ParentWindow.ForceRefresh()
       
   662     
       
   663     def DismissContextualButtons(self):
       
   664         if self.ContextualButtonsItem is not None:
       
   665             self.ContextualButtonsItem = None
       
   666             self.ContextualButtons = []
       
   667             self.ParentWindow.ForceRefresh()
       
   668     
       
   669     def IsOverContextualButton(self, x, y):
       
   670         for button in self.ContextualButtons:
       
   671             if button.HitTest(x, y):
       
   672                 return True
       
   673         return False
       
   674     
       
   675     def SetMinSize(self, size):
       
   676         wx.Window.SetMinSize(self, size)
       
   677         wx.CallAfter(self.RefreshButtonsState)
       
   678     
       
   679     def GetOnChangeSizeButton(self, size):
       
   680         def OnChangeSizeButton():
       
   681             self.CanvasSize = size
       
   682             self.SetCanvasSize(200, self.CanvasSize)
       
   683         return OnChangeSizeButton
       
   684     
       
   685     def OnExportGraphButton(self):
       
   686         self.ExportGraph()
       
   687     
       
   688     def OnForceButton(self):
       
   689         wx.CallAfter(self.ForceValue, 
       
   690                      self.ContextualButtonsItem)
       
   691         self.DismissContextualButtons()
       
   692         
       
   693     def OnReleaseButton(self):
       
   694         wx.CallAfter(self.ReleaseValue, 
       
   695                      self.ContextualButtonsItem)
       
   696         self.DismissContextualButtons()
       
   697     
       
   698     def OnExportItemGraphButton(self):
       
   699         wx.CallAfter(self.ExportGraph, 
       
   700                      self.ContextualButtonsItem)
       
   701         self.DismissContextualButtons()
       
   702         
       
   703     def OnRemoveItemButton(self):            
       
   704         wx.CallAfter(self.ParentWindow.DeleteValue, self, 
       
   705                      self.ContextualButtonsItem)
       
   706         self.DismissContextualButtons()
       
   707     
       
   708     def RefreshLabelsPosition(self, height):
       
   709         canvas_ratio = 1. / height
       
   710         graph_ratio = 1. / ((1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) * height)
       
   711         
       
   712         self.Figure.subplotpars.update(
       
   713             top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
       
   714             bottom= CANVAS_BORDER[0] * canvas_ratio)
       
   715         
       
   716         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
       
   717             num_item = len(self.Items)
       
   718             for idx in xrange(num_item):
       
   719                 if not self.Is3DCanvas():
       
   720                     self.AxesLabels[idx].set_position(
       
   721                         (0.05, 
       
   722                          1.0 - (CANVAS_PADDING + AXES_LABEL_HEIGHT * idx) * graph_ratio))
       
   723                 self.Labels[idx].set_position(
       
   724                     (0.95, 
       
   725                      CANVAS_PADDING * graph_ratio + 
       
   726                      (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
       
   727         else:
       
   728             self.AxesLabels[0].set_position((0.1, CANVAS_PADDING * graph_ratio))
       
   729             self.Labels[0].set_position((0.95, CANVAS_PADDING * graph_ratio))
       
   730             self.AxesLabels[1].set_position((0.05, 2 * CANVAS_PADDING * graph_ratio))
       
   731             self.Labels[1].set_position((0.05, 1.0 - CANVAS_PADDING * graph_ratio))
       
   732     
       
   733         self.Figure.subplots_adjust()
       
   734     
       
   735     def GetCanvasMinSize(self):
       
   736         return wx.Size(200, 
       
   737                        CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
       
   738                        2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
       
   739     
       
   740     def SetCanvasSize(self, width, height):
       
   741         height = max(height, self.GetCanvasMinSize()[1])
       
   742         self.SetMinSize(wx.Size(width, height))
       
   743         self.RefreshLabelsPosition(height)
       
   744         self.RefreshButtonsState()
       
   745         self.ParentWindow.RefreshGraphicsSizer()
       
   746         
       
   747     def GetAxesBoundingBox(self, absolute=False):
       
   748         width, height = self.GetSize()
       
   749         ax, ay, aw, ah = self.figure.gca().get_position().bounds
       
   750         bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
       
   751                        aw * width + 2, ah * height + 1)
       
   752         if absolute:
       
   753             xw, yw = self.GetPosition()
       
   754             bbox.x += xw
       
   755             bbox.y += yw
       
   756         return bbox
       
   757     
       
   758     def OnCanvasButtonPressed(self, event):
       
   759         width, height = self.GetSize()
       
   760         x, y = event.x, height - event.y
       
   761         if not self.IsOverButton(x, y):
       
   762             if event.inaxes == self.Axes:
       
   763                 item_idx = None
       
   764                 for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
       
   765                              [pair for pair in enumerate(self.Labels)]):
       
   766                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   767                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   768                     if rect.InsideXY(x, y):
       
   769                         item_idx = i
       
   770                         break
       
   771                 if item_idx is not None:
       
   772                     self.ShowButtons(False)
       
   773                     self.DismissContextualButtons()
       
   774                     xw, yw = self.GetPosition()
       
   775                     self.ParentWindow.StartDragNDrop(self, 
       
   776                         self.Items[item_idx], x + xw, y + yw, x + xw, y + yw)
       
   777                 elif not self.Is3DCanvas():
       
   778                     self.MouseStartPos = wx.Point(x, y)
       
   779                     if event.button == 1 and event.inaxes == self.Axes:
       
   780                         self.StartCursorTick = self.CursorTick
       
   781                         self.HandleCursorMove(event)
       
   782                     elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
       
   783                         width, height = self.GetSize()
       
   784                         start_tick, end_tick = self.ParentWindow.GetRange()
       
   785                         self.StartCursorTick = start_tick
       
   786             
       
   787             elif event.button == 1 and event.y <= 5:
       
   788                 self.MouseStartPos = wx.Point(x, y)
       
   789                 self.CanvasStartSize = self.GetSize()
       
   790     
       
   791     def OnCanvasButtonReleased(self, event):
       
   792         if self.ParentWindow.IsDragging():
       
   793             width, height = self.GetSize()
       
   794             xw, yw = self.GetPosition()
       
   795             self.ParentWindow.StopDragNDrop(
       
   796                 self.ParentWindow.DraggingAxesPanel.Items[0].GetVariable(),
       
   797                 xw + event.x, 
       
   798                 yw + height - event.y)
       
   799         else:
       
   800             self.MouseStartPos = None
       
   801             self.StartCursorTick = None
       
   802             self.CanvasStartSize = None
       
   803             width, height = self.GetSize()
       
   804             self.HandleButtons(event.x, height - event.y)
       
   805             if event.y > 5 and self.SetHighlight(HIGHLIGHT_NONE):
       
   806                 self.SetCursor(wx.NullCursor)
       
   807                 self.ParentWindow.ForceRefresh()
       
   808     
       
   809     def OnCanvasMotion(self, event):
       
   810         width, height = self.GetSize()
       
   811         if self.ParentWindow.IsDragging():
       
   812             xw, yw = self.GetPosition()
       
   813             self.ParentWindow.MoveDragNDrop(
       
   814                 xw + event.x, 
       
   815                 yw + height - event.y)
       
   816         else:
       
   817             if not self.Is3DCanvas():
       
   818                 if event.button == 1 and self.CanvasStartSize is None:
       
   819                     if event.inaxes == self.Axes:
       
   820                         if self.MouseStartPos is not None:
       
   821                             self.HandleCursorMove(event)
       
   822                     elif self.MouseStartPos is not None and len(self.Items) == 1:
       
   823                         xw, yw = self.GetPosition()
       
   824                         self.ParentWindow.SetCursorTick(self.StartCursorTick)
       
   825                         self.ParentWindow.StartDragNDrop(self, 
       
   826                             self.Items[0],
       
   827                             event.x + xw, height - event.y + yw, 
       
   828                             self.MouseStartPos.x + xw, self.MouseStartPos.y + yw)
       
   829                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and self.MouseStartPos is not None:
       
   830                     start_tick, end_tick = self.ParentWindow.GetRange()
       
   831                     rect = self.GetAxesBoundingBox()
       
   832                     self.ParentWindow.SetCanvasPosition(
       
   833                         self.StartCursorTick + (self.MouseStartPos.x - event.x) *
       
   834                         (end_tick - start_tick) / rect.width)    
       
   835             
       
   836             if event.button == 1 and self.CanvasStartSize is not None:
       
   837                 width, height = self.GetSize()
       
   838                 self.SetCanvasSize(width, 
       
   839                     self.CanvasStartSize.height + height - event.y - self.MouseStartPos.y)
       
   840                 
       
   841             elif event.button in [None, "up", "down"]:
       
   842                 if self.GraphType == GRAPH_PARALLEL:
       
   843                     orientation = [wx.RIGHT] * len(self.AxesLabels) + [wx.LEFT] * len(self.Labels)
       
   844                 elif len(self.AxesLabels) > 0:
       
   845                     orientation = [wx.RIGHT, wx.TOP, wx.LEFT, wx.BOTTOM]
       
   846                 else:
       
   847                     orientation = [wx.LEFT] * len(self.Labels)
       
   848                 item_idx = None
       
   849                 item_style = None
       
   850                 for (i, t), style in zip([pair for pair in enumerate(self.AxesLabels)] + 
       
   851                                          [pair for pair in enumerate(self.Labels)], 
       
   852                                          orientation):
       
   853                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   854                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   855                     if rect.InsideXY(event.x, height - event.y):
       
   856                         item_idx = i
       
   857                         item_style = style
       
   858                         break
       
   859                 if item_idx is not None:
       
   860                     self.PopupContextualButtons(self.Items[item_idx], rect, item_style)
       
   861                     return 
       
   862                 if not self.IsOverContextualButton(event.x, height - event.y):
       
   863                     self.DismissContextualButtons()
       
   864                 
       
   865                 if event.y <= 5:
       
   866                     if self.SetHighlight(HIGHLIGHT_RESIZE):
       
   867                         self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
       
   868                         self.ParentWindow.ForceRefresh()
       
   869                 else:
       
   870                     if self.SetHighlight(HIGHLIGHT_NONE):
       
   871                         self.SetCursor(wx.NullCursor)
       
   872                         self.ParentWindow.ForceRefresh()
       
   873     
       
   874     def OnCanvasScroll(self, event):
       
   875         if event.inaxes is not None and event.guiEvent.ControlDown():
       
   876             if self.GraphType == GRAPH_ORTHOGONAL:
       
   877                 start_tick, end_tick = self.ParentWindow.GetRange()
       
   878                 tick = (start_tick + end_tick) / 2.
       
   879             else:
       
   880                 tick = event.xdata
       
   881             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
       
   882             self.ParentWindow.VetoScrollEvent = True
       
   883     
       
   884     def OnDragging(self, x, y):
       
   885         width, height = self.GetSize()
       
   886         bbox = self.GetAxesBoundingBox()
       
   887         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
       
   888             rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
       
   889             if rect.InsideXY(x, y):
       
   890                 self.SetHighlight(HIGHLIGHT_LEFT)
       
   891             else:
       
   892                 self.SetHighlight(HIGHLIGHT_RIGHT)
       
   893         elif y < height / 2:
       
   894             if self.ParentWindow.IsViewerFirst(self):
       
   895                 self.SetHighlight(HIGHLIGHT_BEFORE)
       
   896             else:
       
   897                 self.SetHighlight(HIGHLIGHT_NONE)
       
   898                 self.ParentWindow.HighlightPreviousViewer(self)
       
   899         else:
       
   900             self.SetHighlight(HIGHLIGHT_AFTER)
       
   901     
       
   902     def OnLeave(self, event):
       
   903         if self.CanvasStartSize is None and self.SetHighlight(HIGHLIGHT_NONE):
       
   904             self.SetCursor(wx.NullCursor)
       
   905             self.ParentWindow.ForceRefresh()
       
   906         DebugVariableViewer.OnLeave(self, event)
       
   907     
       
   908     KEY_CURSOR_INCREMENT = {
       
   909         wx.WXK_LEFT: -1,
       
   910         wx.WXK_RIGHT: 1,
       
   911         wx.WXK_UP: -10,
       
   912         wx.WXK_DOWN: 10}
       
   913     def OnKeyDown(self, event):
       
   914         if self.CursorTick is not None:
       
   915             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
       
   916             if move is not None:
       
   917                 self.ParentWindow.MoveCursorTick(move)
       
   918         event.Skip()
       
   919     
       
   920     def HandleCursorMove(self, event):
       
   921         start_tick, end_tick = self.ParentWindow.GetRange()
       
   922         cursor_tick = None
       
   923         if self.GraphType == GRAPH_ORTHOGONAL:
       
   924             x_data = self.Items[0].GetData(start_tick, end_tick)
       
   925             y_data = self.Items[1].GetData(start_tick, end_tick)
       
   926             if len(x_data) > 0 and len(y_data) > 0:
       
   927                 length = min(len(x_data), len(y_data))
       
   928                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
       
   929                 cursor_tick = x_data[numpy.argmin(d), 0]
       
   930         else:
       
   931             data = self.Items[0].GetData(start_tick, end_tick)
       
   932             if len(data) > 0:
       
   933                 cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0]
       
   934         if cursor_tick is not None:
       
   935             self.ParentWindow.SetCursorTick(cursor_tick)
       
   936     
       
   937     def OnAxesMotion(self, event):
       
   938         if self.Is3DCanvas():
       
   939             current_time = gettime()
       
   940             if current_time - self.LastMotionTime > REFRESH_PERIOD:
       
   941                 self.LastMotionTime = current_time
       
   942                 Axes3D._on_move(self.Axes, event)
       
   943     
       
   944     def ResetGraphics(self):
       
   945         self.Figure.clear()
       
   946         if self.Is3DCanvas():
       
   947             self.Axes = self.Figure.gca(projection='3d')
       
   948             self.Axes.set_color_cycle(['b'])
       
   949             self.LastMotionTime = gettime()
       
   950             setattr(self.Axes, "_on_move", self.OnAxesMotion)
       
   951             self.Axes.mouse_init()
       
   952             self.Axes.tick_params(axis='z', labelsize='small')
       
   953         else:
       
   954             self.Axes = self.Figure.gca()
       
   955             self.Axes.set_color_cycle(color_cycle)
       
   956         self.Axes.tick_params(axis='x', labelsize='small')
       
   957         self.Axes.tick_params(axis='y', labelsize='small')
       
   958         self.Plots = []
       
   959         self.VLine = None
       
   960         self.HLine = None
       
   961         self.Labels = []
       
   962         self.AxesLabels = []
       
   963         if not self.Is3DCanvas():
       
   964             text_func = self.Axes.text
       
   965         else:
       
   966             text_func = self.Axes.text2D
       
   967         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
       
   968             num_item = len(self.Items)
       
   969             for idx in xrange(num_item):
       
   970                 if num_item == 1:
       
   971                     color = 'k'
       
   972                 else:
       
   973                     color = color_cycle[idx % len(color_cycle)]
       
   974                 if not self.Is3DCanvas():
       
   975                     self.AxesLabels.append(
       
   976                         text_func(0, 0, "", size='small',
       
   977                                   verticalalignment='top', 
       
   978                                   color=color,
       
   979                                   transform=self.Axes.transAxes))
       
   980                 self.Labels.append(
       
   981                     text_func(0, 0, "", size='large', 
       
   982                               horizontalalignment='right',
       
   983                               color=color,
       
   984                               transform=self.Axes.transAxes))
       
   985         else:
       
   986             self.AxesLabels.append(
       
   987                 self.Axes.text(0, 0, "", size='small',
       
   988                                transform=self.Axes.transAxes))
       
   989             self.Labels.append(
       
   990                 self.Axes.text(0, 0, "", size='large',
       
   991                                horizontalalignment='right',
       
   992                                transform=self.Axes.transAxes))
       
   993             self.AxesLabels.append(
       
   994                 self.Axes.text(0, 0, "", size='small',
       
   995                                rotation='vertical',
       
   996                                verticalalignment='bottom',
       
   997                                transform=self.Axes.transAxes))
       
   998             self.Labels.append(
       
   999                 self.Axes.text(0, 0, "", size='large',
       
  1000                                rotation='vertical',
       
  1001                                verticalalignment='top',
       
  1002                                transform=self.Axes.transAxes))
       
  1003         width, height = self.GetSize()
       
  1004         self.RefreshLabelsPosition(height)
       
  1005         
       
  1006     def AddItem(self, item):
       
  1007         DebugVariableViewer.AddItem(self, item)
       
  1008         self.ResetGraphics()
       
  1009         
       
  1010     def RemoveItem(self, item):
       
  1011         DebugVariableViewer.RemoveItem(self, item)
       
  1012         if not self.IsEmpty():
       
  1013             if len(self.Items) == 1:
       
  1014                 self.GraphType = GRAPH_PARALLEL
       
  1015             self.ResetGraphics()
       
  1016     
       
  1017     def UnregisterObsoleteData(self):
       
  1018         DebugVariableViewer.UnregisterObsoleteData(self)
       
  1019         if not self.IsEmpty():
       
  1020             self.ResetGraphics()
       
  1021     
       
  1022     def Is3DCanvas(self):
       
  1023         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
       
  1024     
       
  1025     def SetCursorTick(self, cursor_tick):
       
  1026         self.CursorTick = cursor_tick
       
  1027         
       
  1028     def RefreshViewer(self, refresh_graphics=True):
       
  1029         
       
  1030         if refresh_graphics:
       
  1031             start_tick, end_tick = self.ParentWindow.GetRange()
       
  1032             
       
  1033             if self.GraphType == GRAPH_PARALLEL:    
       
  1034                 min_value = max_value = None
       
  1035                 
       
  1036                 for idx, item in enumerate(self.Items):
       
  1037                     data = item.GetData(start_tick, end_tick)
       
  1038                     if data is not None:
       
  1039                         item_min_value, item_max_value = item.GetValueRange()
       
  1040                         if min_value is None:
       
  1041                             min_value = item_min_value
       
  1042                         elif item_min_value is not None:
       
  1043                             min_value = min(min_value, item_min_value)
       
  1044                         if max_value is None:
       
  1045                             max_value = item_max_value
       
  1046                         elif item_max_value is not None:
       
  1047                             max_value = max(max_value, item_max_value)
       
  1048                         
       
  1049                         if len(self.Plots) <= idx:
       
  1050                             self.Plots.append(
       
  1051                                 self.Axes.plot(data[:, 0], data[:, 1])[0])
       
  1052                         else:
       
  1053                             self.Plots[idx].set_data(data[:, 0], data[:, 1])
       
  1054                     
       
  1055                 if min_value is not None and max_value is not None:
       
  1056                     y_center = (min_value + max_value) / 2.
       
  1057                     y_range = max(1.0, max_value - min_value)
       
  1058                 else:
       
  1059                     y_center = 0.5
       
  1060                     y_range = 1.0
       
  1061                 x_min, x_max = start_tick, end_tick
       
  1062                 y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
       
  1063                 
       
  1064                 if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
  1065                     if self.VLine is None:
       
  1066                         self.VLine = self.Axes.axvline(self.CursorTick, color=cursor_color)
       
  1067                     else:
       
  1068                         self.VLine.set_xdata((self.CursorTick, self.CursorTick))
       
  1069                     self.VLine.set_visible(True)
       
  1070                 else:
       
  1071                     if self.VLine is not None:
       
  1072                         self.VLine.set_visible(False)
       
  1073             else:
       
  1074                 min_start_tick = reduce(max, [item.GetData()[0, 0] 
       
  1075                                               for item in self.Items
       
  1076                                               if len(item.GetData()) > 0], 0)
       
  1077                 start_tick = max(start_tick, min_start_tick)
       
  1078                 end_tick = max(end_tick, min_start_tick)
       
  1079                 x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick)
       
  1080                 y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick)
       
  1081                 if self.CursorTick is not None:
       
  1082                     x_cursor, x_forced = self.Items[0].GetValue(self.CursorTick, raw=True)
       
  1083                     y_cursor, y_forced = self.Items[1].GetValue(self.CursorTick, raw=True)
       
  1084                 length = 0
       
  1085                 if x_data is not None and y_data is not None:  
       
  1086                     length = min(len(x_data), len(y_data))
       
  1087                 if len(self.Items) < 3:
       
  1088                     if x_data is not None and y_data is not None:
       
  1089                         if len(self.Plots) == 0:
       
  1090                             self.Plots.append(
       
  1091                                 self.Axes.plot(x_data[:, 1][:length], 
       
  1092                                                y_data[:, 1][:length])[0])
       
  1093                         else:
       
  1094                             self.Plots[0].set_data(
       
  1095                                 x_data[:, 1][:length], 
       
  1096                                 y_data[:, 1][:length])
       
  1097                     
       
  1098                     if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
  1099                         if self.VLine is None:
       
  1100                             self.VLine = self.Axes.axvline(x_cursor, color=cursor_color)
       
  1101                         else:
       
  1102                             self.VLine.set_xdata((x_cursor, x_cursor))
       
  1103                         if self.HLine is None:
       
  1104                             self.HLine = self.Axes.axhline(y_cursor, color=cursor_color)
       
  1105                         else:
       
  1106                             self.HLine.set_ydata((y_cursor, y_cursor))
       
  1107                         self.VLine.set_visible(True)
       
  1108                         self.HLine.set_visible(True)
       
  1109                     else:
       
  1110                         if self.VLine is not None:
       
  1111                             self.VLine.set_visible(False)
       
  1112                         if self.HLine is not None:
       
  1113                             self.HLine.set_visible(False)
       
  1114                 else:
       
  1115                     while len(self.Axes.lines) > 0:
       
  1116                         self.Axes.lines.pop()
       
  1117                     z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick)
       
  1118                     if self.CursorTick is not None:
       
  1119                         z_cursor, z_forced = self.Items[2].GetValue(self.CursorTick, raw=True)
       
  1120                     if x_data is not None and y_data is not None and z_data is not None:
       
  1121                         length = min(length, len(z_data))
       
  1122                         self.Axes.plot(x_data[:, 1][:length],
       
  1123                                        y_data[:, 1][:length],
       
  1124                                        zs = z_data[:, 1][:length])
       
  1125                     self.Axes.set_zlim(z_min, z_max)
       
  1126                     if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
  1127                         for kwargs in [{"xs": numpy.array([x_min, x_max])},
       
  1128                                        {"ys": numpy.array([y_min, y_max])},
       
  1129                                        {"zs": numpy.array([z_min, z_max])}]:
       
  1130                             for param, value in [("xs", numpy.array([x_cursor, x_cursor])),
       
  1131                                                  ("ys", numpy.array([y_cursor, y_cursor])),
       
  1132                                                  ("zs", numpy.array([z_cursor, z_cursor]))]:
       
  1133                                 kwargs.setdefault(param, value)
       
  1134                             kwargs["color"] = cursor_color
       
  1135                             self.Axes.plot(**kwargs)
       
  1136                 
       
  1137             self.Axes.set_xlim(x_min, x_max)
       
  1138             self.Axes.set_ylim(y_min, y_max)
       
  1139         
       
  1140         variable_name_mask = self.ParentWindow.GetVariableNameMask()
       
  1141         if self.CursorTick is not None:
       
  1142             values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items])
       
  1143         else:
       
  1144             values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items])
       
  1145         labels = [item.GetVariable(variable_name_mask) for item in self.Items]
       
  1146         styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
       
  1147         if self.Is3DCanvas():
       
  1148             for idx, label_func in enumerate([self.Axes.set_xlabel, 
       
  1149                                               self.Axes.set_ylabel,
       
  1150                                               self.Axes.set_zlabel]):
       
  1151                 label_func(labels[idx], fontdict={'size': 'small','color': color_cycle[idx]})
       
  1152         else:
       
  1153             for label, text in zip(self.AxesLabels, labels):
       
  1154                 label.set_text(text)
       
  1155         for label, value, style in zip(self.Labels, values, styles):
       
  1156             label.set_text(value)
       
  1157             label.set_style(style)
       
  1158         
       
  1159         self.draw()
       
  1160 
       
  1161     def ExportGraph(self, item=None):
       
  1162         if item is not None:
       
  1163             variables = [(item, [entry for entry in item.GetData()])]
       
  1164         else:
       
  1165             variables = [(item, [entry for entry in item.GetData()])
       
  1166                          for item in self.Items]
       
  1167         self.ParentWindow.CopyDataToClipboard(variables)
       
  1168     
       
  1169 class DebugVariableGraphicPanel(wx.Panel, DebugViewer):
       
  1170     
       
  1171     def __init__(self, parent, producer, window):
       
  1172         wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
       
  1173         
       
  1174         self.ParentWindow = window
       
  1175         
       
  1176         self.HasNewData = False
       
  1177         self.Force = False
       
  1178         
       
  1179         self.SetBackgroundColour(wx.WHITE)
       
  1180         
       
  1181         main_sizer = wx.BoxSizer(wx.VERTICAL)
       
  1182         
       
  1183         self.Ticks = numpy.array([])
       
  1184         self.StartTick = 0
       
  1185         self.Fixed = False
       
  1186         self.CursorTick = None
       
  1187         self.DraggingAxesPanel = None
       
  1188         self.DraggingAxesBoundingBox = None
       
  1189         self.DraggingAxesMousePos = None
       
  1190         self.VetoScrollEvent = False
       
  1191         self.VariableNameMask = []
       
  1192         
       
  1193         self.GraphicPanels = []
       
  1194         
       
  1195         graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
       
  1196         main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
       
  1197         
       
  1198         range_label = wx.StaticText(self, label=_('Range:'))
       
  1199         graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
       
  1200         
       
  1201         self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
       
  1202         self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
       
  1203         graphics_button_sizer.AddWindow(self.CanvasRange, 1, 
       
  1204               border=5, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
       
  1205         
       
  1206         self.CanvasRange.Clear()
       
  1207         default_range_idx = 0
       
  1208         for idx, (text, value) in enumerate(RANGE_VALUES):
       
  1209             self.CanvasRange.Append(text)
       
  1210             if text == "1s":
       
  1211                 default_range_idx = idx
       
  1212         self.CanvasRange.SetSelection(default_range_idx)
       
  1213         
       
  1214         for name, bitmap, help in [
       
  1215             ("CurrentButton", "current", _("Go to current value")),
       
  1216             ("ExportGraphButton", "export_graph", _("Export graph values to clipboard"))]:
       
  1217             button = wx.lib.buttons.GenBitmapButton(self, 
       
  1218                   bitmap=GetBitmap(bitmap), 
       
  1219                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
  1220             button.SetToolTipString(help)
       
  1221             setattr(self, name, button)
       
  1222             self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
       
  1223             graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
       
  1224         
       
  1225         self.CanvasPosition = wx.ScrollBar(self, 
       
  1226               size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
       
  1227         self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK, 
       
  1228               self.OnPositionChanging, self.CanvasPosition)
       
  1229         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP, 
       
  1230               self.OnPositionChanging, self.CanvasPosition)
       
  1231         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN, 
       
  1232               self.OnPositionChanging, self.CanvasPosition)
       
  1233         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP, 
       
  1234               self.OnPositionChanging, self.CanvasPosition)
       
  1235         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN, 
       
  1236               self.OnPositionChanging, self.CanvasPosition)
       
  1237         main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
  1238         
       
  1239         self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
       
  1240         main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL|wx.GROW)
       
  1241         
       
  1242         self.TickLabel = wx.StaticText(self)
       
  1243         self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
       
  1244         
       
  1245         self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY|wx.TE_CENTER|wx.NO_BORDER)
       
  1246         self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT|wx.GROW)
       
  1247         
       
  1248         self.TickTimeLabel = wx.StaticText(self)
       
  1249         self.TickSizer.AddWindow(self.TickTimeLabel)
       
  1250         
       
  1251         self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL|wx.VSCROLL)
       
  1252         self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
       
  1253         self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self))
       
  1254         self.GraphicsWindow.Bind(wx.EVT_ERASE_BACKGROUND, self.OnGraphicsWindowEraseBackground)
       
  1255         self.GraphicsWindow.Bind(wx.EVT_PAINT, self.OnGraphicsWindowPaint)
       
  1256         self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
       
  1257         self.GraphicsWindow.Bind(wx.EVT_MOUSEWHEEL, self.OnGraphicsWindowMouseWheel)
       
  1258         
       
  1259         main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
       
  1260         
       
  1261         self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
       
  1262         self.GraphicsWindow.SetSizer(self.GraphicsSizer)
       
  1263     
       
  1264         DebugViewer.__init__(self, producer, True)
       
  1265         
       
  1266         self.SetSizer(main_sizer)
       
  1267     
       
  1268     def SetTickTime(self, ticktime=0):
       
  1269         self.Ticktime = ticktime
       
  1270         if self.Ticktime == 0:
       
  1271             self.Ticktime = MILLISECOND
       
  1272         self.CurrentRange = RANGE_VALUES[
       
  1273             self.CanvasRange.GetSelection()][1] / self.Ticktime
       
  1274     
       
  1275     def SetDataProducer(self, producer):
       
  1276         DebugViewer.SetDataProducer(self, producer)
       
  1277         
       
  1278         if self.DataProducer is not None:
       
  1279             self.SetTickTime(self.DataProducer.GetTicktime())
       
  1280     
       
  1281     def RefreshNewData(self, *args, **kwargs):
       
  1282         if self.HasNewData or self.Force:
       
  1283             self.HasNewData = False
       
  1284             self.RefreshView()
       
  1285         DebugViewer.RefreshNewData(self, *args, **kwargs)
       
  1286     
       
  1287     def NewDataAvailable(self, tick, *args, **kwargs):
       
  1288         if tick is not None:
       
  1289             if len(self.Ticks) == 0:
       
  1290                 self.StartTick = tick 
       
  1291             self.Ticks = numpy.append(self.Ticks, [tick])
       
  1292             if not self.Fixed or tick < self.StartTick + self.CurrentRange:
       
  1293                 self.StartTick = max(self.StartTick, tick - self.CurrentRange)
       
  1294             if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
       
  1295                 self.Force = True
       
  1296         DebugViewer.NewDataAvailable(self, tick, *args, **kwargs)
       
  1297     
       
  1298     def ForceRefresh(self):
       
  1299         self.Force = True
       
  1300         wx.CallAfter(self.NewDataAvailable, None, True)
       
  1301     
       
  1302     def RefreshGraphicsSizer(self):
       
  1303         self.GraphicsSizer.Clear()
       
  1304         
       
  1305         for panel in self.GraphicPanels:
       
  1306             self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
       
  1307             
       
  1308         self.GraphicsSizer.Layout()
       
  1309         self.RefreshGraphicsWindowScrollbars()
       
  1310     
       
  1311     def SetCanvasPosition(self, tick):
       
  1312         tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
       
  1313         self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
       
  1314         self.Fixed = True
       
  1315         self.RefreshCanvasPosition()
       
  1316         self.ForceRefresh()
       
  1317     
       
  1318     def SetCursorTick(self, cursor_tick):
       
  1319         self.CursorTick = cursor_tick
       
  1320         self.Fixed = True
       
  1321         self.UpdateCursorTick() 
       
  1322     
       
  1323     def MoveCursorTick(self, move):
       
  1324         if self.CursorTick is not None:
       
  1325             cursor_tick = max(self.Ticks[0], 
       
  1326                           min(self.CursorTick + move, 
       
  1327                               self.Ticks[-1]))
       
  1328             cursor_tick_idx = numpy.argmin(numpy.abs(self.Ticks - cursor_tick))
       
  1329             if self.Ticks[cursor_tick_idx] == self.CursorTick:
       
  1330                 cursor_tick_idx = max(0, 
       
  1331                                   min(cursor_tick_idx + abs(move) / move, 
       
  1332                                       len(self.Ticks) - 1))
       
  1333             self.CursorTick = self.Ticks[cursor_tick_idx]
       
  1334             self.StartTick = max(self.Ticks[
       
  1335                                 numpy.argmin(numpy.abs(self.Ticks - 
       
  1336                                         self.CursorTick + self.CurrentRange))],
       
  1337                              min(self.StartTick, self.CursorTick))
       
  1338             self.RefreshCanvasPosition()
       
  1339             self.UpdateCursorTick() 
       
  1340             
       
  1341     def ResetCursorTick(self):
       
  1342         self.CursorTick = None
       
  1343         self.UpdateCursorTick()
       
  1344     
       
  1345     def UpdateCursorTick(self):
       
  1346         for panel in self.GraphicPanels:
       
  1347             if isinstance(panel, DebugVariableGraphic):
       
  1348                 panel.SetCursorTick(self.CursorTick)
       
  1349         self.ForceRefresh()
       
  1350     
       
  1351     def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
       
  1352         if len(panel.GetItems()) > 1:
       
  1353             self.DraggingAxesPanel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
       
  1354             self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
       
  1355             width, height = panel.GetSize()
       
  1356             self.DraggingAxesPanel.SetSize(wx.Size(width, height))
       
  1357             self.DraggingAxesPanel.ResetGraphics()
       
  1358             self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
       
  1359         else:
       
  1360             self.DraggingAxesPanel = panel
       
  1361         self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(absolute=True)
       
  1362         self.DraggingAxesMousePos = wx.Point(
       
  1363             x_mouse_start - self.DraggingAxesBoundingBox.x, 
       
  1364             y_mouse_start - self.DraggingAxesBoundingBox.y)
       
  1365         self.MoveDragNDrop(x_mouse, y_mouse)
       
  1366         
       
  1367     def MoveDragNDrop(self, x_mouse, y_mouse):
       
  1368         self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
       
  1369         self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
       
  1370         self.RefreshHighlight(x_mouse, y_mouse)
       
  1371     
       
  1372     def RefreshHighlight(self, x_mouse, y_mouse):
       
  1373         for idx, panel in enumerate(self.GraphicPanels):
       
  1374             x, y = panel.GetPosition()
       
  1375             width, height = panel.GetSize()
       
  1376             rect = wx.Rect(x, y, width, height)
       
  1377             if (rect.InsideXY(x_mouse, y_mouse) or 
       
  1378                 idx == 0 and y_mouse < 0 or
       
  1379                 idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
       
  1380                 panel.OnDragging(x_mouse - x, y_mouse - y)
       
  1381             else:
       
  1382                 panel.SetHighlight(HIGHLIGHT_NONE)
       
  1383         if wx.Platform == "__WXMSW__":
       
  1384             self.RefreshView()
       
  1385         else:
       
  1386             self.ForceRefresh()
       
  1387     
       
  1388     def ResetHighlight(self):
       
  1389         for panel in self.GraphicPanels:
       
  1390             panel.SetHighlight(HIGHLIGHT_NONE)
       
  1391         if wx.Platform == "__WXMSW__":
       
  1392             self.RefreshView()
       
  1393         else:
       
  1394             self.ForceRefresh()
       
  1395     
       
  1396     def IsDragging(self):
       
  1397         return self.DraggingAxesPanel is not None
       
  1398     
       
  1399     def GetDraggingAxesClippingRegion(self, panel):
       
  1400         x, y = panel.GetPosition()
       
  1401         width, height = panel.GetSize()
       
  1402         bbox = wx.Rect(x, y, width, height)
       
  1403         bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
       
  1404         bbox.x -= x
       
  1405         bbox.y -= y
       
  1406         return bbox
       
  1407     
       
  1408     def GetDraggingAxesPosition(self, panel):
       
  1409         x, y = panel.GetPosition()
       
  1410         return wx.Point(self.DraggingAxesBoundingBox.x - x,
       
  1411                         self.DraggingAxesBoundingBox.y - y)
       
  1412     
       
  1413     def StopDragNDrop(self, variable, x_mouse, y_mouse):
       
  1414         if self.DraggingAxesPanel not in self.GraphicPanels:
       
  1415             self.DraggingAxesPanel.Destroy()
       
  1416         self.DraggingAxesPanel = None
       
  1417         self.DraggingAxesBoundingBox = None
       
  1418         self.DraggingAxesMousePos = None
       
  1419         for idx, panel in enumerate(self.GraphicPanels):
       
  1420             panel.SetHighlight(HIGHLIGHT_NONE)
       
  1421             xw, yw = panel.GetPosition()
       
  1422             width, height = panel.GetSize()
       
  1423             bbox = wx.Rect(xw, yw, width, height)
       
  1424             if bbox.InsideXY(x_mouse, y_mouse):
       
  1425                 panel.ShowButtons(True)
       
  1426                 merge_type = GRAPH_PARALLEL
       
  1427                 if isinstance(panel, DebugVariableText) or panel.Is3DCanvas():
       
  1428                     if y_mouse > yw + height / 2:
       
  1429                         idx += 1
       
  1430                     wx.CallAfter(self.MoveValue, variable, idx)
       
  1431                 else:
       
  1432                     rect = panel.GetAxesBoundingBox(True)
       
  1433                     if rect.InsideXY(x_mouse, y_mouse):
       
  1434                         merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
       
  1435                         if merge_rect.InsideXY(x_mouse, y_mouse):
       
  1436                             merge_type = GRAPH_ORTHOGONAL
       
  1437                         wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
       
  1438                     else:
       
  1439                         if y_mouse > yw + height / 2:
       
  1440                             idx += 1
       
  1441                         wx.CallAfter(self.MoveValue, variable, idx)
       
  1442                 self.ForceRefresh()
       
  1443                 return 
       
  1444         width, height = self.GraphicsWindow.GetVirtualSize()
       
  1445         rect = wx.Rect(0, 0, width, height)
       
  1446         if rect.InsideXY(x_mouse, y_mouse):
       
  1447             wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels))
       
  1448         self.ForceRefresh()
       
  1449     
       
  1450     def RefreshView(self):
       
  1451         self.RefreshCanvasPosition()
       
  1452         
       
  1453         width, height = self.GraphicsWindow.GetVirtualSize()
       
  1454         bitmap = wx.EmptyBitmap(width, height)
       
  1455         dc = wx.BufferedDC(wx.ClientDC(self.GraphicsWindow), bitmap)
       
  1456         dc.Clear()
       
  1457         dc.BeginDrawing()
       
  1458         if self.DraggingAxesPanel is not None:
       
  1459             destBBox = self.DraggingAxesBoundingBox
       
  1460             srcBBox = self.DraggingAxesPanel.GetAxesBoundingBox()
       
  1461             
       
  1462             srcBmp = _convert_agg_to_wx_bitmap(self.DraggingAxesPanel.get_renderer(), None)
       
  1463             srcDC = wx.MemoryDC()
       
  1464             srcDC.SelectObject(srcBmp)
       
  1465                 
       
  1466             dc.Blit(destBBox.x, destBBox.y, 
       
  1467                     int(destBBox.width), int(destBBox.height), 
       
  1468                     srcDC, srcBBox.x, srcBBox.y)
       
  1469         dc.EndDrawing()
       
  1470         
       
  1471         if not self.Fixed or self.Force:
       
  1472             self.Force = False
       
  1473             refresh_graphics = True
       
  1474         else:
       
  1475             refresh_graphics = False
       
  1476         
       
  1477         if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
       
  1478             self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
       
  1479         for panel in self.GraphicPanels:
       
  1480             if isinstance(panel, DebugVariableGraphic):
       
  1481                 panel.RefreshViewer(refresh_graphics)
       
  1482             else:
       
  1483                 panel.RefreshViewer()
       
  1484         
       
  1485         if self.CursorTick is not None:
       
  1486             tick = self.CursorTick
       
  1487         elif len(self.Ticks) > 0:
       
  1488             tick = self.Ticks[-1]
       
  1489         else:
       
  1490             tick = None
       
  1491         if tick is not None:
       
  1492             self.TickLabel.SetLabel("Tick: %d" % tick)
       
  1493             tick_duration = int(tick * self.Ticktime)
       
  1494             not_null = False
       
  1495             duration = ""
       
  1496             for value, format in [(tick_duration / DAY, "%dd"),
       
  1497                                   ((tick_duration % DAY) / HOUR, "%dh"),
       
  1498                                   ((tick_duration % HOUR) / MINUTE, "%dm"),
       
  1499                                   ((tick_duration % MINUTE) / SECOND, "%ds")]:
       
  1500                 
       
  1501                 if value > 0 or not_null:
       
  1502                     duration += format % value
       
  1503                     not_null = True
       
  1504             
       
  1505             duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND) 
       
  1506             self.TickTimeLabel.SetLabel("t: %s" % duration)
       
  1507         else:
       
  1508             self.TickLabel.SetLabel("")
       
  1509             self.TickTimeLabel.SetLabel("")
       
  1510         self.TickSizer.Layout()
       
  1511     
       
  1512     def UnregisterObsoleteData(self):
       
  1513         self.SubscribeAllDataConsumers()
       
  1514         
       
  1515         if self.DataProducer is not None:
       
  1516             if self.DataProducer is not None:
       
  1517                 self.SetTickTime(self.DataProducer.GetTicktime())
       
  1518         
       
  1519         for panel in self.GraphicPanels:
       
  1520             panel.UnregisterObsoleteData()
       
  1521             if panel.IsEmpty():
       
  1522                 if panel.HasCapture():
       
  1523                     panel.ReleaseMouse()
       
  1524                 self.GraphicPanels.remove(panel)
       
  1525                 panel.Destroy()
       
  1526         
       
  1527         self.ResetVariableNameMask()
       
  1528         self.RefreshGraphicsSizer()
       
  1529         self.ForceRefresh()
       
  1530     
       
  1531     def ResetView(self):
       
  1532         self.UnsubscribeAllDataConsumers()
       
  1533         
       
  1534         self.Fixed = False
       
  1535         for panel in self.GraphicPanels:
       
  1536             panel.Destroy()
       
  1537         self.GraphicPanels = []
       
  1538         self.ResetVariableNameMask()
       
  1539         self.RefreshGraphicsSizer()
       
  1540     
       
  1541     def RefreshCanvasPosition(self):
       
  1542         if len(self.Ticks) > 0:
       
  1543             pos = int(self.StartTick - self.Ticks[0])
       
  1544             range = int(self.Ticks[-1] - self.Ticks[0])
       
  1545         else:
       
  1546             pos = 0
       
  1547             range = 0
       
  1548         self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
       
  1549     
       
  1550     def ChangeRange(self, dir, tick=None):
       
  1551         current_range = self.CurrentRange
       
  1552         current_range_idx = self.CanvasRange.GetSelection()
       
  1553         new_range_idx = max(0, min(current_range_idx + dir, len(RANGE_VALUES) - 1))
       
  1554         if new_range_idx != current_range_idx:
       
  1555             self.CanvasRange.SetSelection(new_range_idx)
       
  1556             self.CurrentRange = RANGE_VALUES[new_range_idx][1] / self.Ticktime
       
  1557             if len(self.Ticks) > 0:
       
  1558                 if tick is None:
       
  1559                     tick = self.StartTick + self.CurrentRange / 2.
       
  1560                 new_start_tick = tick - (tick - self.StartTick) * self.CurrentRange / current_range 
       
  1561                 self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
       
  1562                 self.Fixed = self.StartTick < self.Ticks[-1] - self.CurrentRange
       
  1563             self.ForceRefresh()
       
  1564     
       
  1565     def RefreshRange(self):
       
  1566         if len(self.Ticks) > 0:
       
  1567             if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
       
  1568                 self.Fixed = False
       
  1569             if self.Fixed:
       
  1570                 self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
       
  1571             else:
       
  1572                 self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
       
  1573         self.ForceRefresh()
       
  1574     
       
  1575     def OnRangeChanged(self, event):
       
  1576         try:
       
  1577             self.CurrentRange = RANGE_VALUES[self.CanvasRange.GetSelection()][1] / self.Ticktime
       
  1578         except ValueError, e:
       
  1579             self.CanvasRange.SetValue(str(self.CurrentRange))
       
  1580         wx.CallAfter(self.RefreshRange)
       
  1581         event.Skip()
       
  1582     
       
  1583     def OnCurrentButton(self, event):
       
  1584         if len(self.Ticks) > 0:
       
  1585             self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
       
  1586             self.Fixed = False
       
  1587             self.ResetCursorTick()
       
  1588         event.Skip()
       
  1589     
       
  1590     def CopyDataToClipboard(self, variables):
       
  1591         text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
       
  1592         next_tick = NextTick(variables)
       
  1593         while next_tick is not None:
       
  1594             values = []
       
  1595             for item, data in variables:
       
  1596                 if len(data) > 0:
       
  1597                     if next_tick == data[0][0]:
       
  1598                         var_type = item.GetVariableType()
       
  1599                         if var_type in ["STRING", "WSTRING"]:
       
  1600                             value = item.GetRawValue(int(data.pop(0)[2]))
       
  1601                             if var_type == "STRING":
       
  1602                                 values.append("'%s'" % value)
       
  1603                             else:
       
  1604                                 values.append('"%s"' % value)
       
  1605                         else:
       
  1606                             values.append("%.3f" % data.pop(0)[1])
       
  1607                     else:
       
  1608                         values.append("")
       
  1609                 else:
       
  1610                     values.append("")
       
  1611             text += "%d;%s;\n" % (next_tick, ";".join(values))
       
  1612             next_tick = NextTick(variables)
       
  1613         self.ParentWindow.SetCopyBuffer(text)
       
  1614     
       
  1615     def OnExportGraphButton(self, event):
       
  1616         items = reduce(lambda x, y: x + y,
       
  1617                        [panel.GetItems() for panel in self.GraphicPanels],
       
  1618                        [])
       
  1619         variables = [(item, [entry for entry in item.GetData()])
       
  1620                      for item in items
       
  1621                      if item.IsNumVariable()]
       
  1622         wx.CallAfter(self.CopyDataToClipboard, variables)
       
  1623         event.Skip()
       
  1624     
       
  1625     def OnPositionChanging(self, event):
       
  1626         if len(self.Ticks) > 0:
       
  1627             self.StartTick = self.Ticks[0] + event.GetPosition()
       
  1628             self.Fixed = True
       
  1629             self.ForceRefresh()
       
  1630         event.Skip()
       
  1631     
       
  1632     def GetRange(self):
       
  1633         return self.StartTick, self.StartTick + self.CurrentRange
       
  1634     
       
  1635     def GetViewerIndex(self, viewer):
       
  1636         if viewer in self.GraphicPanels:
       
  1637             return self.GraphicPanels.index(viewer)
       
  1638         return None
       
  1639     
       
  1640     def IsViewerFirst(self, viewer):
       
  1641         return viewer == self.GraphicPanels[0]
       
  1642     
       
  1643     def IsViewerLast(self, viewer):
       
  1644         return viewer == self.GraphicPanels[-1]
       
  1645     
       
  1646     def HighlightPreviousViewer(self, viewer):
       
  1647         if self.IsViewerFirst(viewer):
       
  1648             return
       
  1649         idx = self.GetViewerIndex(viewer)
       
  1650         if idx is None:
       
  1651             return
       
  1652         self.GraphicPanels[idx-1].SetHighlight(HIGHLIGHT_AFTER)
       
  1653     
       
  1654     def ResetVariableNameMask(self):
       
  1655         items = []
       
  1656         for panel in self.GraphicPanels:
       
  1657             items.extend(panel.GetItems())
       
  1658         if len(items) > 1:
       
  1659             self.VariableNameMask = reduce(compute_mask,
       
  1660                 [item.GetVariable().split('.') for item in items])
       
  1661         elif len(items) > 0:
       
  1662             self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*']
       
  1663         else:
       
  1664             self.VariableNameMask = []
       
  1665         self.MaskLabel.ChangeValue(".".join(self.VariableNameMask))
       
  1666         self.MaskLabel.SetInsertionPoint(self.MaskLabel.GetLastPosition())
       
  1667             
       
  1668     def GetVariableNameMask(self):
       
  1669         return self.VariableNameMask
       
  1670     
       
  1671     def InsertValue(self, iec_path, idx = None, force=False):
       
  1672         for panel in self.GraphicPanels:
       
  1673             if panel.GetItem(iec_path) is not None:
       
  1674                 return
       
  1675         if idx is None:
       
  1676             idx = len(self.GraphicPanels)
       
  1677         item = DebugVariableItem(self, iec_path, True)
       
  1678         result = self.AddDataConsumer(iec_path.upper(), item)
       
  1679         if result is not None or force:
       
  1680             
       
  1681             if item.IsNumVariable():
       
  1682                 panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
       
  1683                 if self.CursorTick is not None:
       
  1684                     panel.SetCursorTick(self.CursorTick)
       
  1685             else:
       
  1686                 panel = DebugVariableText(self.GraphicsWindow, self, [item])
       
  1687             if idx is not None:
       
  1688                 self.GraphicPanels.insert(idx, panel)
       
  1689             else:
       
  1690                 self.GraphicPanels.append(panel)
       
  1691             self.ResetVariableNameMask()
       
  1692             self.RefreshGraphicsSizer()
       
  1693             self.ForceRefresh()
       
  1694     
       
  1695     def MoveValue(self, iec_path, idx = None):
       
  1696         if idx is None:
       
  1697             idx = len(self.GraphicPanels)
       
  1698         source_panel = None
       
  1699         item = None
       
  1700         for panel in self.GraphicPanels:
       
  1701             item = panel.GetItem(iec_path)
       
  1702             if item is not None:
       
  1703                 source_panel = panel
       
  1704                 break
       
  1705         if source_panel is not None:
       
  1706             source_panel.RemoveItem(item)
       
  1707             source_size = source_panel.GetSize()
       
  1708             if source_panel.IsEmpty():
       
  1709                 if source_panel.HasCapture():
       
  1710                     source_panel.ReleaseMouse()
       
  1711                 self.GraphicPanels.remove(source_panel)
       
  1712                 source_panel.Destroy()
       
  1713             
       
  1714             if item.IsNumVariable():
       
  1715                 panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
       
  1716                 panel.SetCanvasSize(source_size.width, source_size.height)
       
  1717                 if self.CursorTick is not None:
       
  1718                     panel.SetCursorTick(self.CursorTick)
       
  1719             else:
       
  1720                 panel = DebugVariableText(self.GraphicsWindow, self, [item])
       
  1721             self.GraphicPanels.insert(idx, panel)
       
  1722             self.ResetVariableNameMask()
       
  1723             self.RefreshGraphicsSizer()
       
  1724             self.ForceRefresh()
       
  1725     
       
  1726     def MergeGraphs(self, source, target_idx, merge_type, force=False):
       
  1727         source_item = None
       
  1728         source_panel = None
       
  1729         for panel in self.GraphicPanels:
       
  1730             source_item = panel.GetItem(source)
       
  1731             if source_item is not None:
       
  1732                 source_panel = panel
       
  1733                 break
       
  1734         if source_item is None:
       
  1735             item = DebugVariableItem(self, source, True)
       
  1736             if item.IsNumVariable():
       
  1737                 result = self.AddDataConsumer(source.upper(), item)
       
  1738                 if result is not None or force:
       
  1739                     source_item = item
       
  1740         if source_item is not None and source_item.IsNumVariable():
       
  1741             if source_panel is not None:
       
  1742                 source_size = source_panel.GetSize()
       
  1743             else:
       
  1744                 source_size = None
       
  1745             target_panel = self.GraphicPanels[target_idx]
       
  1746             graph_type = target_panel.GraphType
       
  1747             if target_panel != source_panel:
       
  1748                 if (merge_type == GRAPH_PARALLEL and graph_type != merge_type or
       
  1749                     merge_type == GRAPH_ORTHOGONAL and 
       
  1750                     (graph_type == GRAPH_PARALLEL and len(target_panel.Items) > 1 or
       
  1751                      graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
       
  1752                     return
       
  1753                 
       
  1754                 if source_panel is not None:
       
  1755                     source_panel.RemoveItem(source_item)
       
  1756                     if source_panel.IsEmpty():
       
  1757                         if source_panel.HasCapture():
       
  1758                             source_panel.ReleaseMouse()
       
  1759                         self.GraphicPanels.remove(source_panel)
       
  1760                         source_panel.Destroy()
       
  1761             elif (merge_type != graph_type and len(target_panel.Items) == 2):
       
  1762                 target_panel.RemoveItem(source_item)
       
  1763             else:
       
  1764                 target_panel = None
       
  1765                 
       
  1766             if target_panel is not None:
       
  1767                 target_panel.AddItem(source_item)
       
  1768                 target_panel.GraphType = merge_type
       
  1769                 size = target_panel.GetSize()
       
  1770                 if merge_type == GRAPH_ORTHOGONAL:
       
  1771                     target_panel.SetCanvasSize(size.width, size.width)
       
  1772                 elif source_size is not None:
       
  1773                     target_panel.SetCanvasSize(size.width, size.height + source_size.height)
       
  1774                 else:
       
  1775                     target_panel.SetCanvasSize(size.width, size.height)
       
  1776                 target_panel.ResetGraphics()
       
  1777                 
       
  1778                 self.ResetVariableNameMask()
       
  1779                 self.RefreshGraphicsSizer()
       
  1780                 self.ForceRefresh()
       
  1781     
       
  1782     def DeleteValue(self, source_panel, item=None):
       
  1783         source_idx = self.GetViewerIndex(source_panel)
       
  1784         if source_idx is not None:
       
  1785             
       
  1786             if item is None:
       
  1787                 source_panel.Clear()
       
  1788                 source_panel.Destroy()
       
  1789                 self.GraphicPanels.remove(source_panel)
       
  1790                 self.ResetVariableNameMask()
       
  1791                 self.RefreshGraphicsSizer()
       
  1792             else:
       
  1793                 source_panel.RemoveItem(item)
       
  1794                 if source_panel.IsEmpty():
       
  1795                     source_panel.Destroy()
       
  1796                     self.GraphicPanels.remove(source_panel)
       
  1797                     self.ResetVariableNameMask()
       
  1798                     self.RefreshGraphicsSizer()
       
  1799             self.ForceRefresh()
       
  1800     
       
  1801     def ResetGraphicsValues(self):
       
  1802         self.Ticks = numpy.array([])
       
  1803         self.StartTick = 0
       
  1804         self.Fixed = False
       
  1805         for panel in self.GraphicPanels:
       
  1806             panel.ResetData()
       
  1807         self.ResetCursorTick()
       
  1808 
       
  1809     def RefreshGraphicsWindowScrollbars(self):
       
  1810         xstart, ystart = self.GraphicsWindow.GetViewStart()
       
  1811         window_size = self.GraphicsWindow.GetClientSize()
       
  1812         vwidth, vheight = self.GraphicsSizer.GetMinSize()
       
  1813         posx = max(0, min(xstart, (vwidth - window_size[0]) / SCROLLBAR_UNIT))
       
  1814         posy = max(0, min(ystart, (vheight - window_size[1]) / SCROLLBAR_UNIT))
       
  1815         self.GraphicsWindow.Scroll(posx, posy)
       
  1816         self.GraphicsWindow.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
  1817                 vwidth / SCROLLBAR_UNIT, vheight / SCROLLBAR_UNIT, posx, posy)
       
  1818     
       
  1819     def OnGraphicsWindowEraseBackground(self, event):
       
  1820         pass
       
  1821     
       
  1822     def OnGraphicsWindowPaint(self, event):
       
  1823         self.RefreshView()
       
  1824         event.Skip()
       
  1825     
       
  1826     def OnGraphicsWindowResize(self, event):
       
  1827         size = self.GetSize()
       
  1828         for panel in self.GraphicPanels:
       
  1829             panel_size = panel.GetSize()
       
  1830             if (isinstance(panel, DebugVariableGraphic) and 
       
  1831                 panel.GraphType == GRAPH_ORTHOGONAL and 
       
  1832                 panel_size.width == panel_size.height):
       
  1833                 panel.SetCanvasSize(size.width, size.width)
       
  1834         self.RefreshGraphicsWindowScrollbars()
       
  1835         self.GraphicsSizer.Layout()
       
  1836         event.Skip()
       
  1837 
       
  1838     def OnGraphicsWindowMouseWheel(self, event):
       
  1839         if self.VetoScrollEvent:
       
  1840             self.VetoScrollEvent = False
       
  1841         else:
       
  1842             event.Skip()