controls/DebugVariablePanel.py
changeset 926 2323981f5d41
parent 925 5f9dd88a605b
child 927 bd3e5b65e8be
equal deleted inserted replaced
923:6ef6e0b3a908 926:2323981f5d41
    24 
    24 
    25 from types import TupleType, ListType, FloatType
    25 from types import TupleType, ListType, FloatType
    26 from time import time as gettime
    26 from time import time as gettime
    27 import math
    27 import math
    28 import numpy
    28 import numpy
       
    29 import binascii
    29 
    30 
    30 import wx
    31 import wx
    31 import wx.lib.buttons
    32 import wx.lib.buttons
    32 
    33 
    33 try:
    34 try:
    34     import matplotlib
    35     import matplotlib
    35     matplotlib.use('WX')
    36     matplotlib.use('WX')
    36     import matplotlib.pyplot
    37     import matplotlib.pyplot
    37     from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
    38     from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
       
    39     from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
       
    40     from matplotlib.backends.backend_agg import FigureCanvasAgg
    38     from mpl_toolkits.mplot3d import Axes3D
    41     from mpl_toolkits.mplot3d import Axes3D
    39     USE_MPL = True
    42     USE_MPL = True
    40 except:
    43 except:
    41     USE_MPL = False
    44     USE_MPL = False
    42 
    45 
    49     parent.Append(help=help, id=id, kind=kind, text=text)
    52     parent.Append(help=help, id=id, kind=kind, text=text)
    50 
    53 
    51 def GetDebugVariablesTableColnames():
    54 def GetDebugVariablesTableColnames():
    52     _ = lambda x : x
    55     _ = lambda x : x
    53     return [_("Variable"), _("Value")]
    56     return [_("Variable"), _("Value")]
    54     
    57 
       
    58 CRC_SIZE = 8
       
    59 CRC_MASK = 2 ** CRC_SIZE - 1
       
    60 
    55 class VariableTableItem(DebugDataConsumer):
    61 class VariableTableItem(DebugDataConsumer):
    56     
    62     
    57     def __init__(self, parent, variable):
    63     def __init__(self, parent, variable):
    58         DebugDataConsumer.__init__(self)
    64         DebugDataConsumer.__init__(self)
    59         self.Parent = parent
    65         self.Parent = parent
    68         if self.Parent and self.Variable != variable:
    74         if self.Parent and self.Variable != variable:
    69             self.Variable = variable
    75             self.Variable = variable
    70             self.RefreshVariableType()
    76             self.RefreshVariableType()
    71             self.Parent.RefreshView()
    77             self.Parent.RefreshView()
    72     
    78     
    73     def GetVariable(self, max_size=None):
    79     def GetVariable(self, mask=None):
    74         variable = self.Variable
    80         variable = self.Variable
    75         if max_size is not None:
    81         if mask is not None:
    76             max_size = max(max_size, 10)
    82             parts = variable.split('.')
    77             if len(variable) > max_size:
    83             mask = mask + ['*'] * max(0, len(parts) - len(mask))
    78                 variable = "..." + variable[-(max_size - 3):]
    84             last = None
       
    85             variable = ""
       
    86             for m, v in zip(mask, parts):
       
    87                 if m == '*':
       
    88                     if last == '*':
       
    89                         variable += '.'
       
    90                     variable += v
       
    91                 elif last is None or last == '*':
       
    92                     variable += '..'
       
    93                 last = m
    79         return variable
    94         return variable
    80     
    95     
    81     def RefreshVariableType(self):
    96     def RefreshVariableType(self):
    82         self.VariableType = self.Parent.GetDataType(self.Variable)
    97         self.VariableType = self.Parent.GetDataType(self.Variable)
    83         if USE_MPL:
    98         if USE_MPL:
   103             else:
   118             else:
   104                 return self.Data[start_idx:]
   119                 return self.Data[start_idx:]
   105             
   120             
   106         return None
   121         return None
   107     
   122     
       
   123     def GetRawValue(self, idx):
       
   124         if self.VariableType in ["STRING", "WSTRING"] and idx < len(self.RawData):
       
   125             return self.RawData[idx][0]
       
   126         return ""
       
   127     
   108     def GetRange(self):
   128     def GetRange(self):
   109         return self.MinValue, self.MaxValue
   129         return self.MinValue, self.MaxValue
   110     
   130     
   111     def ResetData(self):
   131     def ResetData(self):
   112         if self.IsNumVariable():
   132         if self.IsNumVariable():
   113             self.Data = numpy.array([]).reshape(0, 2)
   133             self.Data = numpy.array([]).reshape(0, 3)
       
   134             if self.VariableType in ["STRING", "WSTRING"]:
       
   135                 self.RawData = []
   114             self.MinValue = None
   136             self.MinValue = None
   115             self.MaxValue = None
   137             self.MaxValue = None
   116         else:
   138         else:
   117             self.Data = None
   139             self.Data = None
   118     
   140     
   119     def IsNumVariable(self):
   141     def IsNumVariable(self):
   120         return self.Parent.IsNumType(self.VariableType)
   142         return (self.Parent.IsNumType(self.VariableType) or 
       
   143                 self.VariableType in ["STRING", "WSTRING"])
   121     
   144     
   122     def NewValue(self, tick, value, forced=False):
   145     def NewValue(self, tick, value, forced=False):
   123         if USE_MPL and self.IsNumVariable():
   146         if USE_MPL and self.IsNumVariable():
   124             num_value = {True:1., False:0.}.get(value, float(value))
   147             if self.VariableType in ["STRING", "WSTRING"]:
       
   148                 num_value = binascii.crc32(value) & CRC_MASK
       
   149             else:
       
   150                 num_value = float(value)
   125             if self.MinValue is None:
   151             if self.MinValue is None:
   126                 self.MinValue = num_value
   152                 self.MinValue = num_value
   127             else:
   153             else:
   128                 self.MinValue = min(self.MinValue, num_value)
   154                 self.MinValue = min(self.MinValue, num_value)
   129             if self.MaxValue is None:
   155             if self.MaxValue is None:
   130                 self.MaxValue = num_value
   156                 self.MaxValue = num_value
   131             else:
   157             else:
   132                 self.MaxValue = max(self.MaxValue, num_value)
   158                 self.MaxValue = max(self.MaxValue, num_value)
   133             self.Data = numpy.append(self.Data, [[float(tick), num_value]], axis=0)
   159             forced_value = float(forced)
       
   160             if self.VariableType in ["STRING", "WSTRING"]:
       
   161                 raw_data = (value, forced_value)
       
   162                 if len(self.RawData) == 0 or self.RawData[-1] != raw_data:
       
   163                     extra_value = len(self.RawData)
       
   164                     self.RawData.append(raw_data)
       
   165                 else:
       
   166                     extra_value = len(self.RawData) - 1
       
   167             else:
       
   168                 extra_value = forced_value
       
   169             self.Data = numpy.append(self.Data, [[float(tick), num_value, extra_value]], axis=0)
   134             self.Parent.HasNewData = True
   170             self.Parent.HasNewData = True
   135         DebugDataConsumer.NewValue(self, tick, value, forced)
   171         DebugDataConsumer.NewValue(self, tick, value, forced)
   136     
   172     
   137     def SetForced(self, forced):
   173     def SetForced(self, forced):
   138         if self.Forced != forced:
   174         if self.Forced != forced:
   145             value = value[1:-1]
   181             value = value[1:-1]
   146         if self.Value != value:
   182         if self.Value != value:
   147             self.Value = value
   183             self.Value = value
   148             self.Parent.HasNewData = True
   184             self.Parent.HasNewData = True
   149             
   185             
   150     def GetValue(self):
   186     def GetValue(self, tick=None, raw=False):
   151         if self.VariableType == "STRING":
   187         if tick is not None and self.IsNumVariable() and len(self.Data) > 0:
   152             return "'%s'" % self.Value
   188             idx = numpy.argmin(abs(self.Data[:, 0] - tick))
   153         elif self.VariableType == "WSTRING":
   189             if self.VariableType in ["STRING", "WSTRING"]:
   154             return "\"%s\"" % self.Value
   190                 value, forced = self.RawData[int(self.Data[idx, 2])]
   155         elif isinstance(self.Value, FloatType):
   191                 if not raw:
   156             return "%.6g" % self.Value
   192                     if self.VariableType == "STRING":
       
   193                         value = "'%s'" % value
       
   194                     else:
       
   195                         value = '"%s"' % value
       
   196                 return value, forced
       
   197             else:
       
   198                 value = self.Data[idx, 1]
       
   199                 if not raw and isinstance(value, FloatType):
       
   200                     value = "%.6g" % value
       
   201                 return value, self.IsForced()
       
   202         elif not raw:
       
   203             if self.VariableType == "STRING":
       
   204                 return "'%s'" % self.Value
       
   205             elif self.VariableType == "WSTRING":
       
   206                 return '"%s"' % self.Value
       
   207             elif isinstance(self.Value, FloatType):
       
   208                 return "%.6g" % self.Value
   157         return self.Value
   209         return self.Value
   158 
   210 
   159     def GetNearestData(self, tick, adjust):
   211     def GetNearestData(self, tick, adjust):
   160         if USE_MPL and self.IsNumVariable():
   212         if USE_MPL and self.IsNumVariable():
   161             ticks = self.Data[:, 0]
   213             ticks = self.Data[:, 0]
   278                     if len(values) > 2 and values[2] == "move":
   330                     if len(values) > 2 and values[2] == "move":
   279                         self.ParentWindow.MoveGraph(values[0], target_idx)
   331                         self.ParentWindow.MoveGraph(values[0], target_idx)
   280                     else:
   332                     else:
   281                         self.ParentWindow.InsertValue(values[0], target_idx, force=True)
   333                         self.ParentWindow.InsertValue(values[0], target_idx, force=True)
   282                 else:
   334                 else:
   283                     ax, ay, aw, ah = self.ParentControl.Axes.get_position().bounds
   335                     rect = self.ParentControl.GetAxesBoundingBox()
   284                     rect = wx.Rect(ax * width, height - (ay + ah) * height,
       
   285                                    aw * width, ah * height)
       
   286                     if rect.InsideXY(x, y):
   336                     if rect.InsideXY(x, y):
   287                         merge_rect = wx.Rect(ax * width, height - (ay + ah) * height,
   337                         merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
   288                                              aw * width / 2., ah * height)
       
   289                         if merge_rect.InsideXY(x, y):
   338                         if merge_rect.InsideXY(x, y):
   290                             merge_type = GRAPH_ORTHOGONAL
   339                             merge_type = GRAPH_ORTHOGONAL
   291                         wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
   340                         wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
   292                     else:
   341                     else:
   293                         if y > height / 2:
   342                         if y > height / 2:
   305         dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
   354         dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
   306         dialog.ShowModal()
   355         dialog.ShowModal()
   307         dialog.Destroy()
   356         dialog.Destroy()
   308 
   357 
   309 if USE_MPL:
   358 if USE_MPL:
   310     SECOND = 1000000000
   359     MILLISECOND = 1000000
       
   360     SECOND = 1000 * MILLISECOND
   311     MINUTE = 60 * SECOND
   361     MINUTE = 60 * SECOND
   312     HOUR = 60 * MINUTE
   362     HOUR = 60 * MINUTE
       
   363     DAY = 24 * HOUR
   313     
   364     
   314     ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)])
   365     ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)])
   315     RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)])
   366     RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)])
   316     TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
   367     TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
   317                         [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
   368                         [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
   321     
   372     
   322     SCROLLBAR_UNIT = 10
   373     SCROLLBAR_UNIT = 10
   323     
   374     
   324     def NextTick(variables):
   375     def NextTick(variables):
   325         next_tick = None
   376         next_tick = None
   326         for var_name, data in variables:
   377         for item, data in variables:
   327             if len(data) > 0:
   378             if len(data) > 0:
   328                 if next_tick is None:
   379                 if next_tick is None:
   329                     next_tick = data[0][0]
   380                     next_tick = data[0][0]
   330                 else:
   381                 else:
   331                     next_tick = min(next_tick, data[0][0])
   382                     next_tick = min(next_tick, data[0][0])
   355             self.SetBackgroundColour(wx.WHITE)
   406             self.SetBackgroundColour(wx.WHITE)
   356             self.SetDropTarget(DebugVariableDropTarget(window, self))
   407             self.SetDropTarget(DebugVariableDropTarget(window, self))
   357             
   408             
   358             self.ParentWindow = window
   409             self.ParentWindow = window
   359             self.Items = items
   410             self.Items = items
       
   411             self.ResetVariableNameMask()
   360             
   412             
   361             self.MainSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
   413             self.MainSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
   362             self.AddViewer()
   414             self.AddViewer()
   363             self.AddButtons()
   415             self.AddButtons()
   364             self.MainSizer.AddGrowableCol(0)
   416             self.MainSizer.AddGrowableCol(0)
   386                 if self.GraphType == GRAPH_ORTHOGONAL:
   438                 if self.GraphType == GRAPH_ORTHOGONAL:
   387                     return tuple(variables)
   439                     return tuple(variables)
   388                 return variables
   440                 return variables
   389             return self.Items[0].GetVariable()
   441             return self.Items[0].GetVariable()
   390         
   442         
       
   443         def ResetVariableNameMask(self):
       
   444             if len(self.Items) > 1:
       
   445                 self.VariableNameMask = reduce(compute_mask,
       
   446                     [item.GetVariable().split('.') for item in self.Items])
       
   447             elif len(self.Items) > 0:
       
   448                 self.VariableNameMask = self.Items[0].GetVariable().split('.')[:-1] + ['*']
       
   449             else:
       
   450                 self.VariableNameMask = []
       
   451         
   391         def AddItem(self, item):
   452         def AddItem(self, item):
   392             self.Items.append(item)
   453             self.Items.append(item)
       
   454             self.ResetVariableNameMask()
   393     
   455     
   394         def RemoveItem(self, item):
   456         def RemoveItem(self, item):
   395             if item in self.Items:
   457             if item in self.Items:
   396                 self.Items.remove(item)
   458                 self.Items.remove(item)
       
   459             self.ResetVariableNameMask()
   397             
   460             
   398         def Clear(self):
   461         def Clear(self):
   399             for item in self.Items:
   462             for item in self.Items:
   400                 self.ParentWindow.RemoveDataConsumer(item)
   463                 self.ParentWindow.RemoveDataConsumer(item)
   401             self.Items = []
   464             self.Items = []
       
   465             self.ResetVariableNameMask()
   402         
   466         
   403         def IsEmpty(self):
   467         def IsEmpty(self):
   404             return len(self.Items) == 0
   468             return len(self.Items) == 0
   405         
   469         
   406         def UnregisterObsoleteData(self):
   470         def UnregisterObsoleteData(self):
   410                     self.ParentWindow.RemoveDataConsumer(item)
   474                     self.ParentWindow.RemoveDataConsumer(item)
   411                     self.RemoveItem(item)
   475                     self.RemoveItem(item)
   412                 else:
   476                 else:
   413                     self.ParentWindow.AddDataConsumer(iec_path, item)
   477                     self.ParentWindow.AddDataConsumer(iec_path, item)
   414                     item.RefreshVariableType()
   478                     item.RefreshVariableType()
       
   479             self.ResetVariableNameMask()
   415         
   480         
   416         def ResetData(self):
   481         def ResetData(self):
   417             for item in self.Items:
   482             for item in self.Items:
   418                 item.ResetData()
   483                 item.ResetData()
   419         
   484         
   426             else:
   491             else:
   427                 menu = wx.Menu(title='')
   492                 menu = wx.Menu(title='')
   428                 for item in self.Items:
   493                 for item in self.Items:
   429                     new_id = wx.NewId()
   494                     new_id = wx.NewId()
   430                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   495                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   431                         text=item.GetVariable(20))
   496                         text=item.GetVariable(self.VariableNameMask))
   432                     self.Bind(wx.EVT_MENU, 
   497                     self.Bind(wx.EVT_MENU, 
   433                         self.GetForceVariableMenuFunction(item),
   498                         self.GetForceVariableMenuFunction(item),
   434                         id=new_id)
   499                         id=new_id)
   435                 self.PopupMenu(menu)
   500                 self.PopupMenu(menu)
   436             event.Skip()
   501             event.Skip()
   441             else:
   506             else:
   442                 menu = wx.Menu(title='')
   507                 menu = wx.Menu(title='')
   443                 for item in self.Items:
   508                 for item in self.Items:
   444                     new_id = wx.NewId()
   509                     new_id = wx.NewId()
   445                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   510                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   446                         text=item.GetVariable(20))
   511                         text=item.GetVariable(self.VariableNameMask))
   447                     self.Bind(wx.EVT_MENU, 
   512                     self.Bind(wx.EVT_MENU, 
   448                         self.GetReleaseVariableMenuFunction(item),
   513                         self.GetReleaseVariableMenuFunction(item),
   449                         id=new_id)
   514                         id=new_id)
   450                 
   515                 
   451                 self.PopupMenu(menu)
   516                 self.PopupMenu(menu)
   457             else:
   522             else:
   458                 menu = wx.Menu(title='')
   523                 menu = wx.Menu(title='')
   459                 for item in self.Items:
   524                 for item in self.Items:
   460                     new_id = wx.NewId()
   525                     new_id = wx.NewId()
   461                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   526                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   462                         text=item.GetVariable(20))
   527                         text=item.GetVariable(self.VariableNameMask))
   463                     self.Bind(wx.EVT_MENU, 
   528                     self.Bind(wx.EVT_MENU, 
   464                         self.GetDeleteValueMenuFunction(item),
   529                         self.GetDeleteValueMenuFunction(item),
   465                         id=new_id)
   530                         id=new_id)
   466                 
   531                 
   467                 new_id = wx.NewId()
   532                 new_id = wx.NewId()
   543                 self.ValueLabel.SetForegroundColour(wx.BLUE)
   608                 self.ValueLabel.SetForegroundColour(wx.BLUE)
   544             else:
   609             else:
   545                 self.ValueLabel.SetForegroundColour(wx.BLACK)
   610                 self.ValueLabel.SetForegroundColour(wx.BLACK)
   546             self.ValueLabel.SetSelection(self.ValueLabel.GetLastPosition(), -1)
   611             self.ValueLabel.SetSelection(self.ValueLabel.GetLastPosition(), -1)
   547     
   612     
       
   613     def compute_mask(x, y):
       
   614         mask = []
       
   615         for xp, yp in zip(x, y):
       
   616             if xp == yp:
       
   617                 mask.append(xp)
       
   618             else:
       
   619                 mask.append("*")
       
   620         return mask
       
   621     
       
   622     class DraggingFigureCanvas(FigureCanvas):
       
   623         
       
   624         def __init__(self, parent, window, *args, **kwargs):
       
   625             FigureCanvas.__init__(self, parent, *args, **kwargs)
       
   626             
       
   627             self.ParentWindow = window
       
   628             
       
   629         def draw(self, drawDC=None):
       
   630             FigureCanvasAgg.draw(self)
       
   631     
       
   632             self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
       
   633             if self.ParentWindow.IsDragging():
       
   634                 destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self.Parent)
       
   635                 if destBBox.width > 0 and destBBox.height > 0:
       
   636                     srcPanel = self.ParentWindow.DraggingAxesPanel
       
   637                     srcBBox = srcPanel.GetAxesBoundingBox()
       
   638                     
       
   639                     if destBBox.x == 0:
       
   640                         srcX = srcBBox.x + srcBBox.width - destBBox.width
       
   641                     else:
       
   642                         srcX = srcBBox.x
       
   643                     if destBBox.y == 0:
       
   644                         srcY = srcBBox.y + srcBBox.height - destBBox.height
       
   645                     else:
       
   646                         srcY = srcBBox.y
       
   647                     
       
   648                     srcBmp = _convert_agg_to_wx_bitmap(srcPanel.Canvas.get_renderer(), None)
       
   649                     srcDC = wx.MemoryDC()
       
   650                     srcDC.SelectObject(srcBmp)
       
   651                     
       
   652                     destDC = wx.MemoryDC()
       
   653                     destDC.SelectObject(self.bitmap)
       
   654                     
       
   655                     destDC.BeginDrawing()
       
   656                     destDC.Blit(destBBox.x, destBBox.y, 
       
   657                                 int(destBBox.width), int(destBBox.height), 
       
   658                                 srcDC, srcX, srcY)
       
   659                     destDC.EndDrawing()
       
   660                     
       
   661             self._isDrawn = True
       
   662             self.gui_repaint(drawDC=drawDC)
       
   663     
   548     class DebugVariableGraphic(DebugVariableViewer):
   664     class DebugVariableGraphic(DebugVariableViewer):
   549         
   665         
   550         def __init__(self, parent, window, items, graph_type):
   666         def __init__(self, parent, window, items, graph_type):
   551             DebugVariableViewer.__init__(self, parent, window, items)
   667             DebugVariableViewer.__init__(self, parent, window, items)
   552         
   668         
   553             self.GraphType = graph_type
   669             self.GraphType = graph_type
       
   670             self.CursorTick = None
   554             
   671             
   555             self.ResetGraphics()
   672             self.ResetGraphics()
   556         
   673         
   557         def AddViewer(self):
   674         def AddViewer(self):
   558             self.Figure = matplotlib.figure.Figure(facecolor='w')
   675             self.Figure = matplotlib.figure.Figure(facecolor='w')
   559             
   676             
   560             self.Canvas = FigureCanvas(self, -1, self.Figure)
   677             self.Canvas = DraggingFigureCanvas(self, self.ParentWindow, -1, self.Figure)
   561             self.Canvas.SetMinSize(wx.Size(200, 200))
   678             self.Canvas.SetMinSize(wx.Size(200, 200))
   562             self.Canvas.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self))
   679             self.Canvas.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self))
   563             self.Canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasClick)
   680             self.Canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasLeftDown)
       
   681             self.Canvas.mpl_connect('motion_notify_event', self.OnCanvasMotion)
       
   682             self.Canvas.mpl_connect('button_release_event', self.OnCanvasLeftUp)
   564             
   683             
   565             self.MainSizer.AddWindow(self.Canvas, flag=wx.GROW)
   684             self.MainSizer.AddWindow(self.Canvas, flag=wx.GROW)
   566         
   685         
   567         def AddButtons(self):
   686         def AddButtons(self):
   568             button_sizer = wx.BoxSizer(wx.VERTICAL)
   687             button_sizer = wx.BoxSizer(wx.VERTICAL)
   580                       size=wx.Size(28, 28), style=wx.NO_BORDER)
   699                       size=wx.Size(28, 28), style=wx.NO_BORDER)
   581                 button.SetToolTipString(help)
   700                 button.SetToolTipString(help)
   582                 setattr(self, name, button)
   701                 setattr(self, name, button)
   583                 self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
   702                 self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
   584                 button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
   703                 button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
   585     
   704         
   586         def OnCanvasClick(self, event):
   705         def GetAxesBoundingBox(self, absolute=False):
       
   706             width, height = self.Canvas.GetSize()
       
   707             ax, ay, aw, ah = self.Axes.get_position().bounds
       
   708             bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
       
   709                            aw * width + 2, ah * height + 1)
       
   710             if absolute:
       
   711                 xw, yw = self.GetPosition()
       
   712                 bbox.x += xw
       
   713                 bbox.y += yw
       
   714             return bbox
       
   715         
       
   716         def OnCanvasLeftDown(self, event):
   587             x, y = event.GetPosition()
   717             x, y = event.GetPosition()
   588             width, height = self.Canvas.GetSize()
   718             width, height = self.Canvas.GetSize()
   589             if len(self.Items) == 1:
   719             if len(self.Items) == 1:
   590                 ax, ay, aw, ah = self.Axes.get_position().bounds
   720                 rect = self.GetAxesBoundingBox()
   591                 rect = wx.Rect(ax * width, height - (ay + ah) * height,
       
   592                                aw * width, ah * height)
       
   593                 if rect.InsideXY(x, y):
   721                 if rect.InsideXY(x, y):
   594                     self.DoDragDrop(0)
   722                     xw, yw = self.GetPosition()
       
   723                     self.ParentWindow.StartDragNDrop(self, x + xw, y + yw)
   595                     return
   724                     return
   596             elif self.Legend is not None:
   725             elif self.Legend is not None:
   597                 item_idx = None
   726                 item_idx = None
   598                 for i, t in enumerate(self.Legend.get_texts()):
   727                 for i, t in enumerate(self.Legend.get_texts()):
   599                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
   728                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
   604                 if item_idx is not None:
   733                 if item_idx is not None:
   605                     self.DoDragDrop(item_idx)
   734                     self.DoDragDrop(item_idx)
   606                     return
   735                     return
   607             event.Skip()
   736             event.Skip()
   608         
   737         
       
   738         def OnCanvasLeftUp(self, event):
       
   739             if self.ParentWindow.IsDragging():
       
   740                 width, height = self.Canvas.GetSize()
       
   741                 xw, yw = self.GetPosition()
       
   742                 self.ParentWindow.StopDragNDrop(
       
   743                     self.Items[0].GetVariable(),
       
   744                     xw + event.x, 
       
   745                     yw + height - event.y)
       
   746         
   609         def DoDragDrop(self, item_idx):
   747         def DoDragDrop(self, item_idx):
       
   748             self.ParentWindow.ResetCursorTickRatio()
   610             data = wx.TextDataObject(str((self.Items[item_idx].GetVariable(), "debug", "move")))
   749             data = wx.TextDataObject(str((self.Items[item_idx].GetVariable(), "debug", "move")))
   611             dragSource = wx.DropSource(self.Canvas)
   750             dragSource = wx.DropSource(self.Canvas)
   612             dragSource.SetData(data)
   751             dragSource.SetData(data)
   613             dragSource.DoDragDrop()
   752             dragSource.DoDragDrop()
   614             
   753         
   615         def OnMotion(self, event):
   754         def OnAxesMotion(self, event):
   616             if self.Is3DCanvas():
   755             if self.Is3DCanvas():
   617                 current_time = gettime()
   756                 current_time = gettime()
   618                 if current_time - self.LastMotionTime > REFRESH_PERIOD:
   757                 if current_time - self.LastMotionTime > REFRESH_PERIOD:
   619                     self.LastMotionTime = current_time
   758                     self.LastMotionTime = current_time
   620                     Axes3D._on_move(self.Axes, event)
   759                     Axes3D._on_move(self.Axes, event)
   621         
   760         
       
   761         def OnCanvasMotion(self, event):
       
   762             if self.ParentWindow.IsDragging():
       
   763                 width, height = self.Canvas.GetSize()
       
   764                 xw, yw = self.GetPosition()
       
   765                 self.ParentWindow.MoveDragNDrop(
       
   766                     xw + event.x, 
       
   767                     yw + height - event.y)
       
   768             elif not self.Is3DCanvas():
       
   769                 if event.inaxes == self.Axes:
       
   770                     start_tick, end_tick = self.ParentWindow.GetRange()
       
   771                     cursor_tick_ratio = None
       
   772                     if self.GraphType == GRAPH_ORTHOGONAL:
       
   773                         x_data = self.Items[0].GetData(start_tick, end_tick)
       
   774                         y_data = self.Items[1].GetData(start_tick, end_tick)
       
   775                         if len(x_data) > 0 and len(y_data) > 0:
       
   776                             length = min(len(x_data), len(y_data))
       
   777                             d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
       
   778                             cursor_tick_ratio = float(x_data[numpy.argmin(d), 0] - start_tick) / (end_tick - start_tick)
       
   779                     else:
       
   780                         data = self.Items[0].GetData(start_tick, end_tick)
       
   781                         if len(data) > 0:
       
   782                             x_min, x_max = self.Axes.get_xlim()
       
   783                             cursor_tick_ratio = float(event.xdata - x_min) / (x_max - x_min)
       
   784                     if cursor_tick_ratio is not None:
       
   785                         self.ParentWindow.SetCursorTickRatio(cursor_tick_ratio)
       
   786                 else:
       
   787                     self.ParentWindow.ResetCursorTickRatio()
       
   788         
   622         def OnSplitButton(self, event):
   789         def OnSplitButton(self, event):
   623             if len(self.Items) == 2 or self.GraphType == GRAPH_ORTHOGONAL:
   790             if len(self.Items) == 2 or self.GraphType == GRAPH_ORTHOGONAL:
   624                 wx.CallAfter(self.ParentWindow.SplitGraphs, self)
   791                 wx.CallAfter(self.ParentWindow.SplitGraphs, self)
   625             else:
   792             else:
   626                 menu = wx.Menu(title='')
   793                 menu = wx.Menu(title='')
   627                 for item in self.Items:
   794                 for item in self.Items:
   628                     new_id = wx.NewId()
   795                     new_id = wx.NewId()
   629                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   796                     AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, 
   630                         text=item.GetVariable(20))
   797                         text=item.GetVariable(self.VariableNameMask))
   631                     self.Bind(wx.EVT_MENU, 
   798                     self.Bind(wx.EVT_MENU, 
   632                         self.GetSplitGraphMenuFunction(item),
   799                         self.GetSplitGraphMenuFunction(item),
   633                         id=new_id)
   800                         id=new_id)
   634                 
   801                 
   635                 new_id = wx.NewId()
   802                 new_id = wx.NewId()
   643             self.Figure.clear()
   810             self.Figure.clear()
   644             if self.Is3DCanvas():
   811             if self.Is3DCanvas():
   645                 self.Axes = self.Figure.gca(projection='3d')
   812                 self.Axes = self.Figure.gca(projection='3d')
   646                 self.Axes.set_color_cycle(['b'])
   813                 self.Axes.set_color_cycle(['b'])
   647                 self.LastMotionTime = gettime()
   814                 self.LastMotionTime = gettime()
   648                 setattr(self.Axes, "_on_move", self.OnMotion)
   815                 setattr(self.Axes, "_on_move", self.OnAxesMotion)
   649                 self.Axes.mouse_init()
   816                 self.Axes.mouse_init()
   650             else:
   817             else:
   651                 self.Axes = self.Figure.gca()
   818                 self.Axes = self.Figure.gca()
   652                 if self.GraphType == GRAPH_ORTHOGONAL:
   819                 if self.GraphType == GRAPH_ORTHOGONAL:
   653                     self.Figure.subplotpars.update(bottom=0.15)
   820                     self.Figure.subplotpars.update(bottom=0.15)
       
   821             self.Axes.set_title('.'.join(self.VariableNameMask))
   654             self.Plots = []
   822             self.Plots = []
       
   823             self.VLine = None
       
   824             self.HLine = None
       
   825             self.TickLabel = None
   655             self.SplitButton.Enable(len(self.Items) > 1)
   826             self.SplitButton.Enable(len(self.Items) > 1)
   656             
   827         
   657         def AddItem(self, item):
   828         def AddItem(self, item):
   658             DebugVariableViewer.AddItem(self, item)
   829             DebugVariableViewer.AddItem(self, item)
   659             self.ResetGraphics()
   830             self.ResetGraphics()
   660             
   831             
   661         def RemoveItem(self, item):
   832         def RemoveItem(self, item):
   662             DebugVariableViewer.RemoveItem(self, item)
   833             DebugVariableViewer.RemoveItem(self, item)
   663             if not self.IsEmpty():
   834             if not self.IsEmpty():
       
   835                 self.ResetVariableNameMask()
   664                 self.ResetGraphics()
   836                 self.ResetGraphics()
   665         
   837         
   666         def UnregisterObsoleteData(self):
   838         def UnregisterObsoleteData(self):
   667             DebugVariableViewer.UnregisterObsoleteData(self)
   839             DebugVariableViewer.UnregisterObsoleteData(self)
   668             if not self.IsEmpty():
   840             if not self.IsEmpty():
       
   841                 self.ResetVariableNameMask()
   669                 self.ResetGraphics()
   842                 self.ResetGraphics()
   670         
   843         
   671         def Is3DCanvas(self):
   844         def Is3DCanvas(self):
   672             return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
   845             return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
   673         
   846         
       
   847         def SetCursorTick(self, cursor_tick):
       
   848             self.CursorTick = cursor_tick
       
   849             
   674         def Refresh(self, refresh_graphics=True):
   850         def Refresh(self, refresh_graphics=True):
   675             
   851             
   676             if refresh_graphics:
   852             if refresh_graphics:
   677                 start_tick, end_tick = self.ParentWindow.GetRange()
   853                 start_tick, end_tick = self.ParentWindow.GetRange()
   678                 
   854                 
   705                         y_center = 0.5
   881                         y_center = 0.5
   706                         y_range = 1.0
   882                         y_range = 1.0
   707                     x_min, x_max = start_tick, end_tick
   883                     x_min, x_max = start_tick, end_tick
   708                     y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
   884                     y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
   709                     
   885                     
       
   886                     if self.CursorTick is not None:
       
   887                         if self.VLine is None:
       
   888                             self.VLine = self.Axes.axvline(self.CursorTick, color='r')
       
   889                         else:
       
   890                             self.VLine.set_xdata((self.CursorTick, self.CursorTick))
       
   891                         self.VLine.set_visible(True)
       
   892                         tick_label = self.ParentWindow.GetTickLabel(self.CursorTick)
       
   893                         if self.TickLabel is None:
       
   894                             self.TickLabel = self.Axes.text(0.5, 0.05, tick_label, 
       
   895                                 size = 'small', transform = self.Axes.transAxes)
       
   896                         else:
       
   897                             self.TickLabel.set_text(tick_label)
       
   898                     else:
       
   899                         if self.VLine is not None:
       
   900                             self.VLine.set_visible(False)
       
   901                         if self.TickLabel is not None:
       
   902                             self.TickLabel.set_text("")
   710                 else:
   903                 else:
   711                     min_start_tick = reduce(max, [item.GetData()[0, 0] 
   904                     min_start_tick = reduce(max, [item.GetData()[0, 0] 
   712                                                   for item in self.Items
   905                                                   for item in self.Items
   713                                                   if len(item.GetData()) > 0], 0)
   906                                                   if len(item.GetData()) > 0], 0)
   714                     start_tick = max(start_tick, min_start_tick)
   907                     start_tick = max(start_tick, min_start_tick)
   715                     end_tick = max(end_tick, min_start_tick)
   908                     end_tick = max(end_tick, min_start_tick)
   716                     x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick)
   909                     x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick)
   717                     y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick)
   910                     y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick)
       
   911                     if self.CursorTick is not None:
       
   912                         x_cursor, x_forced = self.Items[0].GetValue(self.CursorTick, raw=True)
       
   913                         y_cursor, y_forced = self.Items[1].GetValue(self.CursorTick, raw=True)
   718                     length = 0
   914                     length = 0
   719                     if x_data is not None and y_data is not None:  
   915                     if x_data is not None and y_data is not None:  
   720                         length = min(len(x_data), len(y_data))
   916                         length = min(len(x_data), len(y_data))
   721                     if len(self.Items) < 3:
   917                     if len(self.Items) < 3:
   722                         if x_data is not None and y_data is not None:
   918                         if x_data is not None and y_data is not None:
   726                                                    y_data[:, 1][:length])[0])
   922                                                    y_data[:, 1][:length])[0])
   727                             else:
   923                             else:
   728                                 self.Plots[0].set_data(
   924                                 self.Plots[0].set_data(
   729                                     x_data[:, 1][:length], 
   925                                     x_data[:, 1][:length], 
   730                                     y_data[:, 1][:length])
   926                                     y_data[:, 1][:length])
       
   927                         
       
   928                         if self.CursorTick is not None:
       
   929                             if self.VLine is None:
       
   930                                 self.VLine = self.Axes.axvline(x_cursor, color='r')
       
   931                             else:
       
   932                                 self.VLine.set_xdata((x_cursor, x_cursor))
       
   933                             if self.HLine is None:
       
   934                                 self.HLine = self.Axes.axhline(y_cursor, color='r')
       
   935                             else:
       
   936                                 self.HLine.set_ydata((y_cursor, y_cursor))
       
   937                             self.VLine.set_visible(True)
       
   938                             self.HLine.set_visible(True)
       
   939                             tick_label = self.ParentWindow.GetTickLabel(self.CursorTick)
       
   940                             if self.TickLabel is None:
       
   941                                 self.TickLabel = self.Axes.text(0.05, 0.90, tick_label, 
       
   942                                     size = 'small', transform = self.Axes.transAxes)
       
   943                             else:
       
   944                                 self.TickLabel.set_text(tick_label)
       
   945                         else:
       
   946                             if self.VLine is not None:
       
   947                                 self.VLine.set_visible(False)
       
   948                             if self.HLine is not None:
       
   949                                 self.HLine.set_visible(False)
       
   950                             if self.TickLabel is not None:
       
   951                                 self.TickLabel.set_text("")
   731                     else:
   952                     else:
   732                         while len(self.Axes.lines) > 0:
   953                         while len(self.Axes.lines) > 0:
   733                             self.Axes.lines.pop()
   954                             self.Axes.lines.pop()
   734                         z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick)
   955                         z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick)
       
   956                         if self.CursorTick is not None:
       
   957                             z_cursor, z_forced = self.Items[2].GetValue(self.CursorTick, raw=True)
   735                         if x_data is not None and y_data is not None and z_data is not None:
   958                         if x_data is not None and y_data is not None and z_data is not None:
   736                             length = min(length, len(z_data))
   959                             length = min(length, len(z_data))
   737                             self.Axes.plot(x_data[:, 1][:length],
   960                             self.Axes.plot(x_data[:, 1][:length],
   738                                            y_data[:, 1][:length],
   961                                            y_data[:, 1][:length],
   739                                            zs = z_data[:, 1][:length])
   962                                            zs = z_data[:, 1][:length])
   740                         self.Axes.set_zlim(z_min, z_max)
   963                         self.Axes.set_zlim(z_min, z_max)
   741                 
   964                         if self.CursorTick is not None:
       
   965                             for kwargs in [{"xs": numpy.array([x_min, x_max])},
       
   966                                            {"ys": numpy.array([y_min, y_max])},
       
   967                                            {"zs": numpy.array([z_min, z_max])}]:
       
   968                                 for param, value in [("xs", numpy.array([x_cursor, x_cursor])),
       
   969                                                      ("ys", numpy.array([y_cursor, y_cursor])),
       
   970                                                      ("zs", numpy.array([z_cursor, z_cursor]))]:
       
   971                                     kwargs.setdefault(param, value)
       
   972                                 kwargs["color"] = 'r'
       
   973                                 self.Axes.plot(**kwargs)
       
   974                     
   742                 self.Axes.set_xlim(x_min, x_max)
   975                 self.Axes.set_xlim(x_min, x_max)
   743                 self.Axes.set_ylim(y_min, y_max)
   976                 self.Axes.set_ylim(y_min, y_max)
   744             
   977             
   745             labels = ["%s: %s" % (item.GetVariable(40), item.GetValue())
   978             if self.CursorTick is not None:
   746                       for item in self.Items]
   979                 values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items])
   747             colors = [{True: 'b', False: 'k'}[item.IsForced()] for item in self.Items]
   980             else:
       
   981                 values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items])
       
   982             names = [item.GetVariable(self.VariableNameMask) for item in self.Items]
       
   983             labels = map(lambda x: "%s: %s" % x, zip(names, values))
       
   984             colors = map(lambda x: {True: 'b', False: 'k'}[x], forced)
   748             if self.GraphType == GRAPH_PARALLEL:
   985             if self.GraphType == GRAPH_PARALLEL:
   749                 self.Legend = self.Axes.legend(self.Plots, labels, 
   986                 self.Legend = self.Axes.legend(self.Plots, labels, 
   750                     loc="upper left", frameon=False,
   987                     loc="upper left", frameon=False,
   751                     prop={'size':'small'})
   988                     prop={'size':'small'})
   752                 for t, color in zip(self.Legend.get_texts(), colors):
   989                 for t, color in zip(self.Legend.get_texts(), colors):
   784             self.Ticks = numpy.array([])
  1021             self.Ticks = numpy.array([])
   785             self.RangeValues = None
  1022             self.RangeValues = None
   786             self.StartTick = 0
  1023             self.StartTick = 0
   787             self.Fixed = False
  1024             self.Fixed = False
   788             self.Force = False
  1025             self.Force = False
       
  1026             self.CursorTickRatio = None
       
  1027             self.DraggingAxesPanel = None
       
  1028             self.DraggingAxesBoundingBox = None
       
  1029             self.DraggingAxesMousePos = None
       
  1030             
   789             self.GraphicPanels = []
  1031             self.GraphicPanels = []
   790             
  1032             
   791             graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
  1033             graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
   792             main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
  1034             main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
   793             
  1035             
   921     def NewDataAvailable(self, tick, *args, **kwargs):
  1163     def NewDataAvailable(self, tick, *args, **kwargs):
   922         if USE_MPL and tick is not None:
  1164         if USE_MPL and tick is not None:
   923             self.Ticks = numpy.append(self.Ticks, [tick])
  1165             self.Ticks = numpy.append(self.Ticks, [tick])
   924             if not self.Fixed or tick < self.StartTick + self.CurrentRange:
  1166             if not self.Fixed or tick < self.StartTick + self.CurrentRange:
   925                 self.StartTick = max(self.StartTick, tick - self.CurrentRange)
  1167                 self.StartTick = max(self.StartTick, tick - self.CurrentRange)
       
  1168                 self.ResetCursorTick(False)
   926         DebugViewer.NewDataAvailable(self, tick, *args, **kwargs)
  1169         DebugViewer.NewDataAvailable(self, tick, *args, **kwargs)
       
  1170     
       
  1171     def ForceRefresh(self):
       
  1172         self.Force = True
       
  1173         wx.CallAfter(self.NewDataAvailable, None, True)
   927     
  1174     
   928     def RefreshGraphicsSizer(self):
  1175     def RefreshGraphicsSizer(self):
   929         self.GraphicsSizer.Clear()
  1176         self.GraphicsSizer.Clear()
   930         
  1177         
   931         for panel in self.GraphicPanels:
  1178         for panel in self.GraphicPanels:
   932             self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
  1179             self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
   933             
  1180             
   934         self.GraphicsSizer.Layout()
  1181         self.GraphicsSizer.Layout()
   935         self.RefreshGraphicsWindowScrollbars()
  1182         self.RefreshGraphicsWindowScrollbars()
       
  1183     
       
  1184     def SetCursorTickRatio(self, cursor_tick_ratio):
       
  1185         self.CursorTickRatio = cursor_tick_ratio
       
  1186         self.ResetCursorTick() 
       
  1187     
       
  1188     def ResetCursorTickRatio(self):
       
  1189         self.CursorTickRatio = None
       
  1190         self.ResetCursorTick()
       
  1191     
       
  1192     def ResetCursorTick(self, force_refresh=True):
       
  1193         if self.CursorTickRatio is not None and len(self.Ticks) > 0:
       
  1194             raw_tick = self.StartTick + self.CursorTickRatio * self.CurrentRange
       
  1195             cursor_tick = self.Ticks[numpy.argmin(abs(self.Ticks - raw_tick))]
       
  1196         else:
       
  1197             cursor_tick = None
       
  1198         for panel in self.GraphicPanels:
       
  1199             if isinstance(panel, DebugVariableGraphic):
       
  1200                 panel.SetCursorTick(cursor_tick)
       
  1201         if force_refresh:
       
  1202             self.ForceRefresh()
       
  1203     
       
  1204     def GetTickLabel(self, tick):
       
  1205         label = "Tick: %d" % tick
       
  1206         if self.Ticktime > 0:
       
  1207             tick_duration = int(tick * self.Ticktime)
       
  1208             not_null = False
       
  1209             duration = ""
       
  1210             for value, format in [(tick_duration / DAY, "%dd"),
       
  1211                                   ((tick_duration % DAY) / HOUR, "%dh"),
       
  1212                                   ((tick_duration % HOUR) / MINUTE, "%dm"),
       
  1213                                   ((tick_duration % MINUTE) / SECOND, "%ds")]:
       
  1214                 
       
  1215                 if value > 0 or not_null:
       
  1216                     duration += format % value
       
  1217                     not_null = True
       
  1218             
       
  1219             duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND) 
       
  1220             label += "(%s)" % duration
       
  1221         return label
       
  1222     
       
  1223     def StartDragNDrop(self, panel, x_mouse, y_mouse):
       
  1224         self.DraggingAxesPanel = panel
       
  1225         self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(absolute=True)
       
  1226         self.DraggingAxesMousePos = wx.Point(
       
  1227             x_mouse - self.DraggingAxesBoundingBox.x, 
       
  1228             y_mouse - self.DraggingAxesBoundingBox.y)
       
  1229         self.ResetCursorTickRatio()
       
  1230         
       
  1231     def MoveDragNDrop(self, x_mouse, y_mouse):
       
  1232         self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
       
  1233         self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
       
  1234         self.ForceRefresh()
       
  1235     
       
  1236     def IsDragging(self):
       
  1237         return self.DraggingAxesPanel is not None
       
  1238     
       
  1239     def GetDraggingAxesClippingRegion(self, panel):
       
  1240         x, y = panel.GetPosition()
       
  1241         width, height = panel.Canvas.GetSize()
       
  1242         bbox = wx.Rect(x, y, width, height)
       
  1243         bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
       
  1244         bbox.x -= x
       
  1245         bbox.y -= y
       
  1246         return bbox
       
  1247     
       
  1248     def StopDragNDrop(self, variable, x_mouse, y_mouse):
       
  1249         self.DraggingAxesPanel = None
       
  1250         self.DraggingAxesBoundingBox = None
       
  1251         self.DraggingAxesMousePos = None
       
  1252         for idx, panel in enumerate(self.GraphicPanels):
       
  1253             xw, yw = panel.GetPosition()
       
  1254             width, height = panel.Canvas.GetSize()
       
  1255             bbox = wx.Rect(xw, yw, width, height)
       
  1256             if bbox.InsideXY(x_mouse, y_mouse):
       
  1257                 merge_type = GRAPH_PARALLEL
       
  1258                 if panel.Is3DCanvas():
       
  1259                     if y_mouse > yw + height / 2:
       
  1260                         idx += 1
       
  1261                     wx.CallAfter(self.MoveGraph, variable, idx)
       
  1262                     return
       
  1263                 else:
       
  1264                     rect = panel.GetAxesBoundingBox(True)
       
  1265                     if rect.InsideXY(x_mouse, y_mouse):
       
  1266                         merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
       
  1267                         if merge_rect.InsideXY(x_mouse, y_mouse):
       
  1268                             merge_type = GRAPH_ORTHOGONAL
       
  1269                         wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
       
  1270                     else:
       
  1271                         if y_mouse > yw + height / 2:
       
  1272                             idx += 1
       
  1273                         wx.CallAfter(self.MoveGraph, variable, idx)
       
  1274                 break
       
  1275         self.ForceRefresh()
   936     
  1276     
   937     def RefreshView(self, only_values=False):
  1277     def RefreshView(self, only_values=False):
   938         if USE_MPL:
  1278         if USE_MPL:
   939             self.RefreshCanvasPosition()
  1279             self.RefreshCanvasPosition()
   940             
  1280             
   970                 self.Ticktime = self.DataProducer.GetTicktime()
  1310                 self.Ticktime = self.DataProducer.GetTicktime()
   971                 self.RefreshCanvasRange()
  1311                 self.RefreshCanvasRange()
   972             
  1312             
   973             for panel in self.GraphicPanels:
  1313             for panel in self.GraphicPanels:
   974                 panel.UnregisterObsoleteData()
  1314                 panel.UnregisterObsoleteData()
       
  1315                 if panel.IsEmpty():
       
  1316                     if panel.Canvas.HasCapture():
       
  1317                         panel.Canvas.ReleaseMouse()
       
  1318                     self.GraphicPanels.remove(panel)
       
  1319                     panel.Destroy()
       
  1320             
       
  1321             self.RefreshGraphicsSizer()
       
  1322             self.ForceRefresh()
   975             
  1323             
   976         else:
  1324         else:
   977             items = [(idx, item) for idx, item in enumerate(self.Table.GetData())]
  1325             items = [(idx, item) for idx, item in enumerate(self.Table.GetData())]
   978             items.reverse()
  1326             items.reverse()
   979             for idx, item in items:
  1327             for idx, item in items:
  1084                 self.Fixed = False
  1432                 self.Fixed = False
  1085             if self.Fixed:
  1433             if self.Fixed:
  1086                 self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
  1434                 self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
  1087             else:
  1435             else:
  1088                 self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
  1436                 self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
  1089         self.Force = True
  1437         self.ForceRefresh()
  1090         self.RefreshView(True)
       
  1091     
  1438     
  1092     def OnRangeChanged(self, event):
  1439     def OnRangeChanged(self, event):
  1093         try:
  1440         try:
  1094             if self.Ticktime == 0:
  1441             if self.Ticktime == 0:
  1095                 self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1]
  1442                 self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1]
  1103     def OnResetButton(self, event):
  1450     def OnResetButton(self, event):
  1104         self.StartTick = 0
  1451         self.StartTick = 0
  1105         self.Fixed = False
  1452         self.Fixed = False
  1106         for panel in self.GraphicPanels:
  1453         for panel in self.GraphicPanels:
  1107             panel.ResetData()
  1454             panel.ResetData()
  1108         self.RefreshView(True)
  1455         self.ForceRefresh()
  1109         event.Skip()
  1456         event.Skip()
  1110 
  1457 
  1111     def OnCurrentButton(self, event):
  1458     def OnCurrentButton(self, event):
  1112         if len(self.Ticks) > 0:
  1459         if len(self.Ticks) > 0:
  1113             self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
  1460             self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
  1114             self.Fixed = False
  1461             self.Fixed = False
  1115             self.Force = True
  1462             self.ForceRefresh()
  1116             self.RefreshView(True)
       
  1117         event.Skip()
  1463         event.Skip()
  1118     
  1464     
  1119     def CopyDataToClipboard(self, variables):
  1465     def CopyDataToClipboard(self, variables):
  1120         text = "tick;%s;\n" % ";".join([var_name for var_name, data in variables])
  1466         text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
  1121         next_tick = NextTick(variables)
  1467         next_tick = NextTick(variables)
  1122         while next_tick is not None:
  1468         while next_tick is not None:
  1123             values = []
  1469             values = []
  1124             for var_name, data in variables:
  1470             for item, data in variables:
  1125                 if len(data) > 0:
  1471                 if len(data) > 0:
  1126                     if next_tick == data[0][0]:
  1472                     if next_tick == data[0][0]:
  1127                         values.append("%.3f" % data.pop(0)[1])
  1473                         var_type = item.GetVariableType()
       
  1474                         if var_type in ["STRING", "WSTRING"]:
       
  1475                             value = item.GetRawValue(int(data.pop(0)[2]))
       
  1476                             if var_type == "STRING":
       
  1477                                 values.append("'%s'" % value)
       
  1478                             else:
       
  1479                                 values.append('"%s"' % value)
       
  1480                         else:
       
  1481                             values.append("%.3f" % data.pop(0)[1])
  1128                     else:
  1482                     else:
  1129                         values.append("")
  1483                         values.append("")
  1130                 else:
  1484                 else:
  1131                     values.append("")
  1485                     values.append("")
  1132             text += "%d;%s;\n" % (next_tick, ";".join(values))
  1486             text += "%d;%s;\n" % (next_tick, ";".join(values))
  1133             next_tick = NextTick(variables)
  1487             next_tick = NextTick(variables)
  1134         self.ParentWindow.SetCopyBuffer(text)
  1488         self.ParentWindow.SetCopyBuffer(text)
  1135     
  1489     
  1136     def OnExportGraphButton(self, event):
  1490     def OnExportGraphButton(self, event):
  1137         variables = []
  1491         variables = []
  1138         for item in self.Table.GetData():
  1492         if USE_MPL:
       
  1493             items = []
       
  1494             for panel in self.GraphicPanels:
       
  1495                 items.extend(panel.GetItems())
       
  1496         else:
       
  1497             items = self.Table.GetData()
       
  1498         for item in items:
  1139             if item.IsNumVariable():
  1499             if item.IsNumVariable():
  1140                 variables.append((item.GetVariable(), [entry for entry in item.GetData()]))
  1500                 variables.append((item, [entry for entry in item.GetData()]))
  1141         wx.CallAfter(self.CopyDataToClipboard, variables)
  1501         wx.CallAfter(self.CopyDataToClipboard, variables)
  1142         event.Skip()
  1502         event.Skip()
  1143     
  1503     
  1144     def OnPositionChanging(self, event):
  1504     def OnPositionChanging(self, event):
  1145         if len(self.Ticks) > 0:
  1505         if len(self.Ticks) > 0:
  1146             self.StartTick = self.Ticks[0] + event.GetPosition()
  1506             self.StartTick = self.Ticks[0] + event.GetPosition()
  1147             self.Fixed = True
  1507             self.Fixed = True
  1148             self.Force = True
  1508             self.ForceRefresh()
  1149             wx.CallAfter(self.NewDataAvailable, None, True)
       
  1150         event.Skip()
  1509         event.Skip()
  1151     
  1510     
  1152     def GetRange(self):
  1511     def GetRange(self):
  1153         return self.StartTick, self.StartTick + self.CurrentRange
  1512         return self.StartTick, self.StartTick + self.CurrentRange
  1154     
  1513     
  1185                     self.GraphicPanels.append(panel)
  1544                     self.GraphicPanels.append(panel)
  1186                 self.RefreshGraphicsSizer()
  1545                 self.RefreshGraphicsSizer()
  1187             else:
  1546             else:
  1188                 self.Table.InsertItem(idx, item)
  1547                 self.Table.InsertItem(idx, item)
  1189             
  1548             
  1190             self.RefreshView()
  1549             self.ForceRefresh()
  1191     
  1550     
  1192     def MoveGraph(self, iec_path, idx = None):
  1551     def MoveGraph(self, iec_path, idx = None):
  1193         if idx is None:
  1552         if idx is None:
  1194             idx = len(self.GraphicPanels)
  1553             idx = len(self.GraphicPanels)
  1195         source_panel = None
  1554         source_panel = None
  1208                 source_panel.Destroy()
  1567                 source_panel.Destroy()
  1209             
  1568             
  1210             panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
  1569             panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
  1211             self.GraphicPanels.insert(idx, panel)
  1570             self.GraphicPanels.insert(idx, panel)
  1212             self.RefreshGraphicsSizer()
  1571             self.RefreshGraphicsSizer()
  1213             self.RefreshView()
  1572             self.ForceRefresh()
  1214     
  1573     
  1215     def SplitGraphs(self, source_panel, item=None):
  1574     def SplitGraphs(self, source_panel, item=None):
  1216         source_idx = self.GetViewerIndex(source_panel)
  1575         source_idx = self.GetViewerIndex(source_panel)
  1217         if source_idx is not None:
  1576         if source_idx is not None:
  1218             
  1577             
  1236                 else:
  1595                 else:
  1237                     panel = DebugVariableText(self.GraphicsWindow, self, [item])
  1596                     panel = DebugVariableText(self.GraphicsWindow, self, [item])
  1238                 self.GraphicPanels.insert(source_idx + 1, panel)
  1597                 self.GraphicPanels.insert(source_idx + 1, panel)
  1239             
  1598             
  1240             self.RefreshGraphicsSizer()
  1599             self.RefreshGraphicsSizer()
  1241             self.RefreshView()
  1600             self.ForceRefresh()
  1242     
  1601     
  1243     def MergeGraphs(self, source, target_idx, merge_type, force=False):
  1602     def MergeGraphs(self, source, target_idx, merge_type, force=False):
  1244         source_item = None
  1603         source_item = None
  1245         source_panel = None
  1604         source_panel = None
  1246         for panel in self.GraphicPanels:
  1605         for panel in self.GraphicPanels:
  1275                 target_panel.AddItem(source_item)
  1634                 target_panel.AddItem(source_item)
  1276                 target_panel.GraphType = merge_type
  1635                 target_panel.GraphType = merge_type
  1277                 target_panel.ResetGraphics()
  1636                 target_panel.ResetGraphics()
  1278                 
  1637                 
  1279                 self.RefreshGraphicsSizer()
  1638                 self.RefreshGraphicsSizer()
  1280                 self.RefreshView()
  1639                 self.ForceRefresh()
  1281     
  1640     
  1282     def DeleteValue(self, source_panel, item=None):
  1641     def DeleteValue(self, source_panel, item=None):
  1283         source_idx = self.GetViewerIndex(source_panel)
  1642         source_idx = self.GetViewerIndex(source_panel)
  1284         if source_idx is not None:
  1643         if source_idx is not None:
  1285             
  1644             
  1292                 source_panel.RemoveItem(item)
  1651                 source_panel.RemoveItem(item)
  1293                 if source_panel.IsEmpty():
  1652                 if source_panel.IsEmpty():
  1294                     source_panel.Destroy()
  1653                     source_panel.Destroy()
  1295                     self.GraphicPanels.remove(source_panel)
  1654                     self.GraphicPanels.remove(source_panel)
  1296                     self.RefreshGraphicsSizer()
  1655                     self.RefreshGraphicsSizer()
  1297             self.RefreshView()
  1656             self.ForceRefresh()
  1298     
  1657     
  1299     def GetDebugVariables(self):
  1658     def GetDebugVariables(self):
  1300         if USE_MPL:
  1659         if USE_MPL:
  1301             return [panel.GetVariables() for panel in self.GraphicPanels]
  1660             return [panel.GetVariables() for panel in self.GraphicPanels]
  1302         else:
  1661         else:
  1321                         continue
  1680                         continue
  1322                     self.GraphicPanels.append(panel)
  1681                     self.GraphicPanels.append(panel)
  1323                     self.RefreshGraphicsSizer()
  1682                     self.RefreshGraphicsSizer()
  1324                 else:
  1683                 else:
  1325                     self.InsertValue(variable, force=True)
  1684                     self.InsertValue(variable, force=True)
  1326             self.RefreshView()
  1685             self.ForceRefresh()
  1327         else:
  1686         else:
  1328             for variable in variables:
  1687             for variable in variables:
  1329                 if isinstance(variable, (ListType, TupleType)):
  1688                 if isinstance(variable, (ListType, TupleType)):
  1330                     for iec_path in variable:
  1689                     for iec_path in variable:
  1331                         self.InsertValue(iec_path, force=True)
  1690                         self.InsertValue(iec_path, force=True)