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