editors/GraphicViewer.py
changeset 1364 e9e17d3b2849
parent 1363 e87e0166d0a7
child 1365 debc97102b23
equal deleted inserted replaced
1363:e87e0166d0a7 1364:e9e17d3b2849
     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) 2007: 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 import numpy
       
    26 import math
       
    27 
       
    28 import wx
       
    29 import wx.lib.plot as plot
       
    30 import wx.lib.buttons
       
    31 
       
    32 from graphics.GraphicCommons import MODE_SELECTION, MODE_MOTION
       
    33 from editors.DebugViewer import DebugViewer
       
    34 from editors.EditorPanel import EditorPanel
       
    35 from util.BitmapLibrary import GetBitmap
       
    36 
       
    37 colours = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'brown', 'cyan',
       
    38            'pink', 'grey']
       
    39 markers = ['circle', 'dot', 'square', 'triangle', 'triangle_down', 'cross', 'plus', 'circle']
       
    40 
       
    41 
       
    42 #-------------------------------------------------------------------------------
       
    43 #                       Debug Variable Graphic Viewer class
       
    44 #-------------------------------------------------------------------------------
       
    45 
       
    46 SECOND = 1000000000
       
    47 MINUTE = 60 * SECOND
       
    48 HOUR = 60 * MINUTE
       
    49 
       
    50 ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)])
       
    51 RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)])
       
    52 TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \
       
    53                     [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \
       
    54                     [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
       
    55 
       
    56 class GraphicViewer(EditorPanel, DebugViewer):
       
    57 
       
    58     def _init_Editor(self, prnt):
       
    59         self.Editor = wx.Panel(prnt)
       
    60         
       
    61         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
       
    62         main_sizer.AddGrowableCol(0)
       
    63         main_sizer.AddGrowableRow(0)
       
    64         
       
    65         self.Canvas = plot.PlotCanvas(self.Editor, name='Canvas')
       
    66         def _axisInterval(spec, lower, upper):
       
    67             if spec == 'border':
       
    68                 if lower == upper:
       
    69                     return lower - 0.5, upper + 0.5
       
    70                 else:
       
    71                     border = (upper - lower) * 0.05
       
    72                     return lower - border, upper + border
       
    73             else:
       
    74                 return plot.PlotCanvas._axisInterval(self.Canvas, spec, lower, upper)
       
    75         self.Canvas._axisInterval = _axisInterval
       
    76         self.Canvas.SetYSpec('border')
       
    77         self.Canvas.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasLeftDown)
       
    78         self.Canvas.canvas.Bind(wx.EVT_LEFT_UP, self.OnCanvasLeftUp)
       
    79         self.Canvas.canvas.Bind(wx.EVT_MIDDLE_DOWN, self.OnCanvasMiddleDown)
       
    80         self.Canvas.canvas.Bind(wx.EVT_MIDDLE_UP, self.OnCanvasMiddleUp)
       
    81         self.Canvas.canvas.Bind(wx.EVT_MOTION, self.OnCanvasMotion)
       
    82         self.Canvas.canvas.Bind(wx.EVT_SIZE, self.OnCanvasResize)
       
    83         main_sizer.AddWindow(self.Canvas, 0, border=0, flag=wx.GROW)
       
    84         
       
    85         range_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=0)
       
    86         range_sizer.AddGrowableCol(5)
       
    87         range_sizer.AddGrowableRow(0)
       
    88         main_sizer.AddSizer(range_sizer, 0, border=5, flag=wx.GROW|wx.ALL)
       
    89         
       
    90         range_label = wx.StaticText(self.Editor, label=_('Range:'))
       
    91         range_sizer.AddWindow(range_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
       
    92         
       
    93         self.CanvasRange = wx.ComboBox(self.Editor, 
       
    94               size=wx.Size(100, 28), style=wx.CB_READONLY)
       
    95         self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
       
    96         range_sizer.AddWindow(self.CanvasRange, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
       
    97         
       
    98         zoom_label = wx.StaticText(self.Editor, label=_('Zoom:'))
       
    99         range_sizer.AddWindow(zoom_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
       
   100         
       
   101         self.CanvasZoom = wx.ComboBox(self.Editor, 
       
   102               size=wx.Size(70, 28), style=wx.CB_READONLY)
       
   103         self.Bind(wx.EVT_COMBOBOX, self.OnZoomChanged, self.CanvasZoom)
       
   104         range_sizer.AddWindow(self.CanvasZoom, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
       
   105         
       
   106         position_label = wx.StaticText(self.Editor, label=_('Position:'))
       
   107         range_sizer.AddWindow(position_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
       
   108         
       
   109         self.CanvasPosition = wx.ScrollBar(self.Editor, 
       
   110               size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
       
   111         self.CanvasPosition.SetScrollbar(0, 10, 100, 10)
       
   112         self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK, 
       
   113               self.OnPositionChanging, self.CanvasPosition)
       
   114         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP, 
       
   115               self.OnPositionChanging, self.CanvasPosition)
       
   116         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN, 
       
   117               self.OnPositionChanging, self.CanvasPosition)
       
   118         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP, 
       
   119               self.OnPositionChanging, self.CanvasPosition)
       
   120         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN, 
       
   121               self.OnPositionChanging, self.CanvasPosition)
       
   122         range_sizer.AddWindow(self.CanvasPosition, 0, border=5, flag=wx.GROW|wx.ALL)
       
   123         
       
   124         self.ResetButton = wx.lib.buttons.GenBitmapButton(self.Editor, 
       
   125               bitmap=GetBitmap("reset"), size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   126         self.ResetButton.SetToolTipString(_("Clear the graph values"))
       
   127         self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton)
       
   128         range_sizer.AddWindow(self.ResetButton, 0, border=0, flag=0)
       
   129         
       
   130         self.CurrentButton = wx.lib.buttons.GenBitmapButton(self.Editor, 
       
   131               bitmap=GetBitmap("current"), size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   132         self.CurrentButton.SetToolTipString(_("Go to current value"))
       
   133         self.Bind(wx.EVT_BUTTON, self.OnCurrentButton, self.CurrentButton)
       
   134         range_sizer.AddWindow(self.CurrentButton, 0, border=0, flag=0)
       
   135         
       
   136         self.ResetZoomOffsetButton = wx.lib.buttons.GenBitmapButton(self.Editor, 
       
   137               bitmap=GetBitmap("fit"), size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   138         self.ResetZoomOffsetButton.SetToolTipString(_("Reset zoom and offset"))
       
   139         self.Bind(wx.EVT_BUTTON, self.OnResetZoomOffsetButton, 
       
   140               self.ResetZoomOffsetButton)
       
   141         range_sizer.AddWindow(self.ResetZoomOffsetButton, 0, border=0, flag=0)
       
   142         
       
   143         self.ExportGraphButton = wx.lib.buttons.GenBitmapButton(self.Editor, 
       
   144               bitmap=GetBitmap("export_graph"), size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   145         self.ExportGraphButton.SetToolTipString(_("Export graph values to clipboard"))
       
   146         self.Bind(wx.EVT_BUTTON, self.OnExportGraphButtonClick, 
       
   147                 self.ExportGraphButton)
       
   148         range_sizer.AddWindow(self.ExportGraphButton, 0, border=0, flag=0)
       
   149         
       
   150         self.Editor.SetSizer(main_sizer)
       
   151 
       
   152         self.Editor.Bind(wx.EVT_MOUSEWHEEL, self.OnCanvasMouseWheel)
       
   153 
       
   154     def __init__(self, parent, window, producer, instancepath = ""):
       
   155         EditorPanel.__init__(self, parent, "", window, None)
       
   156         DebugViewer.__init__(self, producer, True, False)
       
   157         
       
   158         self.InstancePath = instancepath
       
   159         self.RangeValues = None
       
   160         self.CursorIdx = None
       
   161         self.LastCursor = None
       
   162         self.CurrentMousePos = None
       
   163         self.CurrentMotionValue = None
       
   164         self.Dragging = False
       
   165         
       
   166         # Initialize Viewer mode to Selection mode
       
   167         self.Mode = MODE_SELECTION
       
   168         
       
   169         self.Data = numpy.array([]).reshape(0, 2)
       
   170         self.StartTick = 0
       
   171         self.StartIdx = 0
       
   172         self.EndIdx = 0
       
   173         self.MinValue = None
       
   174         self.MaxValue = None
       
   175         self.YCenter = 0
       
   176         self.CurrentZoom = 1.0
       
   177         self.Fixed = False
       
   178         self.Ticktime = self.DataProducer.GetTicktime()
       
   179         self.RefreshCanvasRange()
       
   180         
       
   181         for zoom_txt, zoom in ZOOM_VALUES:
       
   182             self.CanvasZoom.Append(zoom_txt)
       
   183         self.CanvasZoom.SetSelection(0)
       
   184         
       
   185         self.AddDataConsumer(self.InstancePath.upper(), self)
       
   186     
       
   187     def __del__(self):
       
   188         DebugViewer.__del__(self)
       
   189         self.RemoveDataConsumer(self)
       
   190     
       
   191     def GetTitle(self):
       
   192         if len(self.InstancePath) > 15:
       
   193             return "..." + self.InstancePath[-12:]
       
   194         return self.InstancePath
       
   195     
       
   196     # Changes Viewer mode
       
   197     def SetMode(self, mode):
       
   198         if self.Mode != mode or mode == MODE_SELECTION:    
       
   199             if self.Mode == MODE_MOTION:
       
   200                 wx.CallAfter(self.Canvas.canvas.SetCursor, wx.NullCursor)
       
   201             self.Mode = mode
       
   202         if self.Mode == MODE_MOTION:
       
   203             wx.CallAfter(self.Canvas.canvas.SetCursor, wx.StockCursor(wx.CURSOR_HAND))
       
   204         
       
   205     def ResetView(self, register=False):
       
   206         self.Data = numpy.array([]).reshape(0, 2)
       
   207         self.StartTick = 0
       
   208         self.StartIdx = 0
       
   209         self.EndIdx = 0
       
   210         self.MinValue = None
       
   211         self.MaxValue = None
       
   212         self.CursorIdx = None
       
   213         self.Fixed = False
       
   214         self.Ticktime = self.DataProducer.GetTicktime()
       
   215         if register:
       
   216             self.AddDataConsumer(self.InstancePath.upper(), self)
       
   217         self.ResetLastCursor()
       
   218         self.RefreshCanvasRange()
       
   219         self.RefreshView()
       
   220     
       
   221     def RefreshNewData(self, *args, **kwargs):
       
   222         self.RefreshView(*args, **kwargs)
       
   223         DebugViewer.RefreshNewData(self)
       
   224     
       
   225     def GetNearestData(self, tick, adjust):
       
   226         ticks = self.Data[:, 0]
       
   227         new_cursor = numpy.argmin(abs(ticks - tick))
       
   228         if adjust == -1 and ticks[new_cursor] > tick and new_cursor > 0:
       
   229             new_cursor -= 1
       
   230         elif adjust == 1 and ticks[new_cursor] < tick and new_cursor < len(ticks):
       
   231             new_cursor += 1
       
   232         return new_cursor
       
   233     
       
   234     def GetBounds(self):
       
   235         if self.StartIdx is None or self.EndIdx is None:
       
   236             self.StartIdx = self.GetNearestData(self.StartTick, -1)
       
   237             self.EndIdx = self.GetNearestData(self.StartTick + self.CurrentRange, 1)
       
   238     
       
   239     def ResetBounds(self):
       
   240         self.StartIdx = None
       
   241         self.EndIdx = None
       
   242     
       
   243     def RefreshCanvasRange(self):
       
   244         if self.Ticktime == 0 and self.RangeValues != RANGE_VALUES:
       
   245             self.RangeValues = RANGE_VALUES
       
   246             self.CanvasRange.Clear()
       
   247             for text, value in RANGE_VALUES:
       
   248                 self.CanvasRange.Append(text)
       
   249             self.CanvasRange.SetStringSelection(RANGE_VALUES[0][0])
       
   250             self.CurrentRange = RANGE_VALUES[0][1]
       
   251         elif self.RangeValues != TIME_RANGE_VALUES:
       
   252             self.RangeValues = TIME_RANGE_VALUES
       
   253             self.CanvasRange.Clear()
       
   254             for text, value in TIME_RANGE_VALUES:
       
   255                 self.CanvasRange.Append(text)
       
   256             self.CanvasRange.SetStringSelection(TIME_RANGE_VALUES[0][0])
       
   257             self.CurrentRange = TIME_RANGE_VALUES[0][1] / self.Ticktime
       
   258         
       
   259     def RefreshView(self, force=False):
       
   260         self.Freeze()
       
   261         if force or not self.Fixed or (len(self.Data) > 0 and self.StartTick + self.CurrentRange > self.Data[-1, 0]):
       
   262             if (self.MinValue is not None and 
       
   263                 self.MaxValue is not None and 
       
   264                 self.MinValue != self.MaxValue):
       
   265                 Yrange = float(self.MaxValue - self.MinValue) / self.CurrentZoom
       
   266             else:
       
   267                 Yrange = 2. / self.CurrentZoom
       
   268             
       
   269             if not force and not self.Fixed and len(self.Data) > 0:
       
   270                 self.YCenter = max(self.Data[-1, 1] - Yrange / 2, 
       
   271                                min(self.YCenter, 
       
   272                                    self.Data[-1, 1] + Yrange / 2))
       
   273             
       
   274             var_name = self.InstancePath.split(".")[-1]
       
   275             
       
   276             self.GetBounds()
       
   277             self.VariableGraphic = plot.PolyLine(self.Data[self.StartIdx:self.EndIdx + 1], 
       
   278                                                  legend=var_name, colour=colours[0])
       
   279             self.GraphicsObject = plot.PlotGraphics([self.VariableGraphic], _("%s Graphics") % var_name, _("Tick"), _("Values"))
       
   280             self.Canvas.Draw(self.GraphicsObject, 
       
   281                              xAxis=(self.StartTick, self.StartTick + self.CurrentRange),
       
   282                              yAxis=(self.YCenter - Yrange * 1.1 / 2., self.YCenter + Yrange * 1.1 / 2.))
       
   283         
       
   284             # Reset and draw cursor 
       
   285             self.ResetLastCursor()
       
   286             self.RefreshCursor()
       
   287         
       
   288         self.RefreshScrollBar()
       
   289         
       
   290         self.Thaw()
       
   291     
       
   292     def GetInstancePath(self):
       
   293         return self.InstancePath
       
   294     
       
   295     def IsViewing(self, tagname):
       
   296         return self.InstancePath == tagname
       
   297     
       
   298     def NewValue(self, tick, value, forced=False):
       
   299         value = {True:1., False:0.}.get(value, float(value))
       
   300         self.Data = numpy.append(self.Data, [[float(tick), value]], axis=0)
       
   301         if self.MinValue is None:
       
   302             self.MinValue = value
       
   303         else:
       
   304             self.MinValue = min(self.MinValue, value)
       
   305         if self.MaxValue is None:
       
   306             self.MaxValue = value
       
   307         else:
       
   308             self.MaxValue = max(self.MaxValue, value)
       
   309         if not self.Fixed or tick < self.StartTick + self.CurrentRange:
       
   310             self.GetBounds()
       
   311             while int(self.Data[self.StartIdx, 0]) < tick - self.CurrentRange:
       
   312                 self.StartIdx += 1
       
   313             self.EndIdx += 1
       
   314             self.StartTick = self.Data[self.StartIdx, 0]
       
   315         self.NewDataAvailable(None)
       
   316     
       
   317     def RefreshScrollBar(self):
       
   318         if len(self.Data) > 0:
       
   319             self.GetBounds()
       
   320             pos = int(self.Data[self.StartIdx, 0] - self.Data[0, 0])
       
   321             range = int(self.Data[-1, 0] - self.Data[0, 0])
       
   322         else:
       
   323             pos = 0
       
   324             range = 0
       
   325         self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
       
   326 
       
   327     def RefreshRange(self):
       
   328         if len(self.Data) > 0:
       
   329             if self.Fixed and self.Data[-1, 0] - self.Data[0, 0] < self.CurrentRange:
       
   330                 self.Fixed = False
       
   331             self.ResetBounds()
       
   332             if self.Fixed:
       
   333                 self.StartTick = min(self.StartTick, self.Data[-1, 0] - self.CurrentRange)
       
   334             else:
       
   335                 self.StartTick = max(self.Data[0, 0], self.Data[-1, 0] - self.CurrentRange)
       
   336         self.RefreshView(True)
       
   337 
       
   338     def OnRangeChanged(self, event):
       
   339         try:
       
   340             if self.Ticktime == 0:
       
   341                 self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1]
       
   342             else:
       
   343                 self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1] / self.Ticktime
       
   344         except ValueError, e:
       
   345             self.CanvasRange.SetValue(str(self.CurrentRange))
       
   346         wx.CallAfter(self.RefreshRange)
       
   347         event.Skip()
       
   348     
       
   349     def OnZoomChanged(self, event):
       
   350         self.CurrentZoom = ZOOM_VALUES[self.CanvasZoom.GetSelection()][1]
       
   351         wx.CallAfter(self.RefreshView, True)
       
   352         event.Skip()
       
   353     
       
   354     def OnPositionChanging(self, event):
       
   355         if len(self.Data) > 0:
       
   356             self.ResetBounds()
       
   357             self.StartTick = self.Data[0, 0] + event.GetPosition()
       
   358             self.Fixed = True
       
   359             self.NewDataAvailable(None, True)
       
   360         event.Skip()
       
   361 
       
   362     def OnResetButton(self, event):
       
   363         self.Fixed = False
       
   364         self.ResetView()
       
   365         event.Skip()
       
   366 
       
   367     def OnCurrentButton(self, event):
       
   368         if len(self.Data) > 0:
       
   369             self.ResetBounds()
       
   370             self.StartTick = max(self.Data[0, 0], self.Data[-1, 0] - self.CurrentRange)
       
   371             self.Fixed = False
       
   372             self.NewDataAvailable(None, True)
       
   373         event.Skip()
       
   374     
       
   375     def OnResetZoomOffsetButton(self, event):
       
   376         if len(self.Data) > 0:
       
   377             self.YCenter = (self.MaxValue + self.MinValue) / 2
       
   378         else:
       
   379             self.YCenter = 0.0
       
   380         self.CurrentZoom = 1.0
       
   381         self.CanvasZoom.SetSelection(0)
       
   382         wx.CallAfter(self.RefreshView, True)
       
   383         event.Skip()
       
   384     
       
   385     def OnExportGraphButtonClick(self, event):
       
   386         data_copy = self.Data[:]
       
   387         text = "tick;%s;\n" % self.InstancePath
       
   388         for tick, value in data_copy:
       
   389             text += "%d;%.3f;\n" % (tick, value)
       
   390         self.ParentWindow.SetCopyBuffer(text)
       
   391         event.Skip()
       
   392 
       
   393     def OnCanvasLeftDown(self, event):
       
   394         self.Fixed = True
       
   395         self.Canvas.canvas.CaptureMouse()
       
   396         if len(self.Data) > 0:
       
   397             if self.Mode == MODE_SELECTION:
       
   398                 self.Dragging = True
       
   399                 pos = self.Canvas.PositionScreenToUser(event.GetPosition())
       
   400                 self.CursorIdx = self.GetNearestData(pos[0], -1)
       
   401                 self.RefreshCursor()
       
   402             elif self.Mode == MODE_MOTION:
       
   403                 self.GetBounds()
       
   404                 self.CurrentMousePos = event.GetPosition()
       
   405                 self.CurrentMotionValue = self.Data[self.StartIdx, 0]
       
   406         event.Skip()
       
   407     
       
   408     def OnCanvasLeftUp(self, event):
       
   409         self.Dragging = False
       
   410         if self.Mode == MODE_MOTION:
       
   411             self.CurrentMousePos = None
       
   412             self.CurrentMotionValue = None
       
   413         if self.Canvas.canvas.HasCapture():
       
   414             self.Canvas.canvas.ReleaseMouse()
       
   415         event.Skip()
       
   416     
       
   417     def OnCanvasMiddleDown(self, event):
       
   418         self.Fixed = True
       
   419         self.Canvas.canvas.CaptureMouse()
       
   420         if len(self.Data) > 0:
       
   421             self.GetBounds()
       
   422             self.CurrentMousePos = event.GetPosition()
       
   423             self.CurrentMotionValue = self.Data[self.StartIdx, 0]
       
   424         event.Skip()
       
   425         
       
   426     def OnCanvasMiddleUp(self, event):
       
   427         self.CurrentMousePos = None
       
   428         self.CurrentMotionValue = None
       
   429         if self.Canvas.canvas.HasCapture():
       
   430             self.Canvas.canvas.ReleaseMouse()
       
   431         event.Skip()
       
   432         
       
   433     def OnCanvasMotion(self, event):
       
   434         if self.Mode == MODE_SELECTION and self.Dragging:
       
   435             pos = self.Canvas.PositionScreenToUser(event.GetPosition())
       
   436             graphics, xAxis, yAxis = self.Canvas.last_draw
       
   437             self.CursorIdx = self.GetNearestData(max(xAxis[0], min(pos[0], xAxis[1])), -1)
       
   438             self.RefreshCursor()
       
   439         elif self.CurrentMousePos is not None and len(self.Data) > 0:
       
   440             oldpos = self.Canvas.PositionScreenToUser(self.CurrentMousePos)
       
   441             newpos = self.Canvas.PositionScreenToUser(event.GetPosition())
       
   442             self.CurrentMotionValue += oldpos[0] - newpos[0]
       
   443             self.YCenter += oldpos[1] - newpos[1]
       
   444             self.ResetBounds()
       
   445             self.StartTick = max(self.Data[0, 0], min(self.CurrentMotionValue, self.Data[-1, 0] - self.CurrentRange))
       
   446             self.CurrentMousePos = event.GetPosition()
       
   447             self.NewDataAvailable(None, True)
       
   448         event.Skip()
       
   449 
       
   450     def OnCanvasMouseWheel(self, event):
       
   451         if self.CurrentMousePos is None:
       
   452             rotation = event.GetWheelRotation() / event.GetWheelDelta()
       
   453             if event.ShiftDown():
       
   454                 current = self.CanvasRange.GetSelection()
       
   455                 new = max(0, min(current - rotation, len(self.RangeValues) - 1))
       
   456                 if new != current:
       
   457                     if self.Ticktime == 0:
       
   458                         self.CurrentRange = self.RangeValues[new][1]
       
   459                     else:
       
   460                         self.CurrentRange = self.RangeValues[new][1] / self.Ticktime
       
   461                     self.CanvasRange.SetStringSelection(self.RangeValues[new][0])
       
   462                     wx.CallAfter(self.RefreshRange)
       
   463             else:
       
   464                 current = self.CanvasZoom.GetSelection()
       
   465                 new = max(0, min(current + rotation, len(ZOOM_VALUES) - 1))
       
   466                 if new != current:
       
   467                     self.CurrentZoom = ZOOM_VALUES[new][1]
       
   468                     self.CanvasZoom.SetStringSelection(ZOOM_VALUES[new][0])
       
   469                     wx.CallAfter(self.RefreshView, True)
       
   470         event.Skip()
       
   471 
       
   472     def OnCanvasResize(self, event):
       
   473         self.ResetLastCursor()
       
   474         wx.CallAfter(self.RefreshCursor)
       
   475         event.Skip()
       
   476 
       
   477     ## Reset the last cursor
       
   478     def ResetLastCursor(self):
       
   479         self.LastCursor = None
       
   480 
       
   481     ## Draw the cursor on graphic
       
   482     #  @param dc The draw canvas
       
   483     #  @param cursor The cursor parameters
       
   484     def DrawCursor(self, dc, cursor, value):
       
   485         if self.StartTick <= cursor <= self.StartTick + self.CurrentRange:
       
   486             # Prepare temporary dc for drawing
       
   487             width = self.Canvas._Buffer.GetWidth()
       
   488             height = self.Canvas._Buffer.GetHeight()
       
   489             tmp_Buffer = wx.EmptyBitmap(width, height)
       
   490             dcs = wx.MemoryDC()
       
   491             dcs.SelectObject(tmp_Buffer)
       
   492             dcs.Clear()
       
   493             dcs.BeginDrawing()
       
   494             
       
   495             dcs.SetPen(wx.Pen(wx.RED))
       
   496             dcs.SetBrush(wx.Brush(wx.RED, wx.SOLID))
       
   497             dcs.SetFont(self.Canvas._getFont(self.Canvas._fontSizeAxis))
       
   498             
       
   499             # Calculate clipping region
       
   500             graphics, xAxis, yAxis = self.Canvas.last_draw
       
   501             p1 = numpy.array([xAxis[0], yAxis[0]])
       
   502             p2 = numpy.array([xAxis[1], yAxis[1]])
       
   503             cx, cy, cwidth, cheight = self.Canvas._point2ClientCoord(p1, p2)
       
   504             
       
   505             px, py = self.Canvas.PositionUserToScreen((float(cursor), 0.))
       
   506             
       
   507             # Draw line cross drawing for diaplaying time cursor
       
   508             dcs.DrawLine(px, cy + 1, px, cy + cheight - 1)
       
   509             
       
   510             lines = ("X:%d\nY:%f" % (cursor, value)).splitlines()
       
   511             
       
   512             wtext = 0
       
   513             for line in lines:
       
   514                 w, h = dcs.GetTextExtent(line)
       
   515                 wtext = max(wtext, w)
       
   516             
       
   517             offset = 0
       
   518             for line in lines:
       
   519                 # Draw time cursor date
       
   520                 dcs.DrawText(line, min(px + 3, cx + cwidth - wtext), cy + 3 + offset)
       
   521                 w, h = dcs.GetTextExtent(line)
       
   522                 offset += h
       
   523             
       
   524             dcs.EndDrawing()
       
   525     
       
   526             #this will erase if called twice
       
   527             dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV)  #(NOT src) XOR dst
       
   528     
       
   529     ## Refresh the variable cursor.
       
   530     #  @param dc The draw canvas
       
   531     def RefreshCursor(self, dc=None):
       
   532         if self:
       
   533             if dc is None:
       
   534                 dc = wx.BufferedDC(wx.ClientDC(self.Canvas.canvas), self.Canvas._Buffer)
       
   535             
       
   536             # Erase previous time cursor if drawn
       
   537             if self.LastCursor is not None:
       
   538                 self.DrawCursor(dc, *self.LastCursor)
       
   539             
       
   540             # Draw new time cursor
       
   541             if self.CursorIdx is not None:
       
   542                 self.LastCursor = self.Data[self.CursorIdx]
       
   543                 self.DrawCursor(dc, *self.LastCursor)