lbessard@301: #!/usr/bin/env python lbessard@301: # -*- coding: utf-8 -*- lbessard@301: lbessard@301: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor lbessard@301: #based on the plcopen standard. lbessard@301: # lbessard@301: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD lbessard@301: # lbessard@301: #See COPYING file for copyrights details. lbessard@301: # lbessard@301: #This library is free software; you can redistribute it and/or lbessard@301: #modify it under the terms of the GNU General Public lbessard@301: #License as published by the Free Software Foundation; either lbessard@301: #version 2.1 of the License, or (at your option) any later version. lbessard@301: # lbessard@301: #This library is distributed in the hope that it will be useful, lbessard@301: #but WITHOUT ANY WARRANTY; without even the implied warranty of lbessard@301: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU lbessard@301: #General Public License for more details. lbessard@301: # lbessard@301: #You should have received a copy of the GNU General Public lbessard@301: #License along with this library; if not, write to the Free Software lbessard@301: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA lbessard@301: Laurent@711: import numpy Laurent@711: import math Laurent@711: lbessard@301: import wx lbessard@301: import wx.lib.plot as plot Laurent@711: import wx.lib.buttons Laurent@711: laurent@660: from graphics.GraphicCommons import DebugViewer, MODE_SELECTION, MODE_MOTION laurent@586: from controls import EditorPanel Laurent@714: from utils.BitmapLibrary import GetBitmap lbessard@301: lbessard@301: colours = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'brown', 'cyan', lbessard@301: 'pink', 'grey'] lbessard@301: markers = ['circle', 'dot', 'square', 'triangle', 'triangle_down', 'cross', 'plus', 'circle'] lbessard@301: lbessard@301: lbessard@301: #------------------------------------------------------------------------------- lbessard@301: # Debug Variable Graphic Viewer class lbessard@301: #------------------------------------------------------------------------------- lbessard@301: laurent@632: SECOND = 1000000000 laurent@632: MINUTE = 60 * SECOND laurent@632: HOUR = 60 * MINUTE laurent@632: laurent@660: ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)]) laurent@660: RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)]) laurent@640: TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \ laurent@632: [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \ laurent@632: [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)] lbessard@301: laurent@586: class GraphicViewer(EditorPanel, DebugViewer): lbessard@301: laurent@586: def _init_Editor(self, prnt): Laurent@711: self.Editor = wx.Panel(prnt) Laurent@711: Laurent@711: main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) Laurent@711: main_sizer.AddGrowableCol(0) Laurent@711: main_sizer.AddGrowableRow(0) Laurent@711: Laurent@711: self.Canvas = plot.PlotCanvas(self.Editor, name='Canvas') lbessard@301: def _axisInterval(spec, lower, upper): lbessard@301: if spec == 'border': lbessard@301: if lower == upper: lbessard@301: return lower - 0.5, upper + 0.5 lbessard@301: else: lbessard@301: border = (upper - lower) * 0.05 lbessard@301: return lower - border, upper + border lbessard@301: else: lbessard@301: return plot.PlotCanvas._axisInterval(self.Canvas, spec, lower, upper) lbessard@301: self.Canvas._axisInterval = _axisInterval lbessard@301: self.Canvas.SetYSpec('border') laurent@660: self.Canvas.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasLeftDown) laurent@660: self.Canvas.canvas.Bind(wx.EVT_LEFT_UP, self.OnCanvasLeftUp) Laurent@724: self.Canvas.canvas.Bind(wx.EVT_MIDDLE_DOWN, self.OnCanvasMiddleDown) Laurent@724: self.Canvas.canvas.Bind(wx.EVT_MIDDLE_UP, self.OnCanvasMiddleUp) laurent@660: self.Canvas.canvas.Bind(wx.EVT_MOTION, self.OnCanvasMotion) laurent@668: self.Canvas.canvas.Bind(wx.EVT_SIZE, self.OnCanvasResize) Laurent@711: main_sizer.AddWindow(self.Canvas, 0, border=0, flag=wx.GROW) Laurent@711: Laurent@724: range_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=0) Laurent@711: range_sizer.AddGrowableCol(5) Laurent@711: range_sizer.AddGrowableRow(0) Laurent@711: main_sizer.AddSizer(range_sizer, 0, border=5, flag=wx.GROW|wx.ALL) Laurent@711: Laurent@711: range_label = wx.StaticText(self.Editor, label=_('Range:')) Laurent@711: range_sizer.AddWindow(range_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) Laurent@711: Laurent@711: self.CanvasRange = wx.ComboBox(self.Editor, laurent@640: size=wx.Size(100, 28), style=wx.CB_READONLY) Laurent@711: self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange) Laurent@714: range_sizer.AddWindow(self.CanvasRange, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) Laurent@711: Laurent@711: zoom_label = wx.StaticText(self.Editor, label=_('Zoom:')) Laurent@711: range_sizer.AddWindow(zoom_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) Laurent@711: Laurent@711: self.CanvasZoom = wx.ComboBox(self.Editor, laurent@660: size=wx.Size(70, 28), style=wx.CB_READONLY) Laurent@711: self.Bind(wx.EVT_COMBOBOX, self.OnZoomChanged, self.CanvasZoom) Laurent@714: range_sizer.AddWindow(self.CanvasZoom, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) Laurent@711: Laurent@711: position_label = wx.StaticText(self.Editor, label=_('Position:')) Laurent@711: range_sizer.AddWindow(position_label, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL) Laurent@711: Laurent@711: self.CanvasPosition = wx.ScrollBar(self.Editor, lbessard@301: size=wx.Size(0, 16), style=wx.SB_HORIZONTAL) lbessard@301: self.CanvasPosition.SetScrollbar(0, 10, 100, 10) Laurent@711: self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK, Laurent@711: self.OnPositionChanging, self.CanvasPosition) Laurent@711: self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP, Laurent@711: self.OnPositionChanging, self.CanvasPosition) Laurent@711: self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN, Laurent@711: self.OnPositionChanging, self.CanvasPosition) Laurent@711: self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP, Laurent@711: self.OnPositionChanging, self.CanvasPosition) Laurent@711: self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN, Laurent@711: self.OnPositionChanging, self.CanvasPosition) Laurent@711: range_sizer.AddWindow(self.CanvasPosition, 0, border=5, flag=wx.GROW|wx.ALL) Laurent@711: Laurent@714: self.ResetButton = wx.lib.buttons.GenBitmapButton(self.Editor, Laurent@714: bitmap=GetBitmap("reset"), size=wx.Size(28, 28), style=wx.NO_BORDER) Laurent@714: self.ResetButton.SetToolTipString(_("Clear the graph values")) Laurent@711: self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton) Laurent@711: range_sizer.AddWindow(self.ResetButton, 0, border=0, flag=0) Laurent@711: Laurent@714: self.CurrentButton = wx.lib.buttons.GenBitmapButton(self.Editor, Laurent@714: bitmap=GetBitmap("current"), size=wx.Size(28, 28), style=wx.NO_BORDER) Laurent@714: self.CurrentButton.SetToolTipString(_("Go to current value")) Laurent@711: self.Bind(wx.EVT_BUTTON, self.OnCurrentButton, self.CurrentButton) Laurent@711: range_sizer.AddWindow(self.CurrentButton, 0, border=0, flag=0) Laurent@711: Laurent@724: self.ResetZoomOffsetButton = wx.lib.buttons.GenBitmapButton(self.Editor, Laurent@724: bitmap=GetBitmap("fit"), size=wx.Size(28, 28), style=wx.NO_BORDER) Laurent@752: self.ResetZoomOffsetButton.SetToolTipString(_("Reset zoom and offset")) Laurent@724: self.Bind(wx.EVT_BUTTON, self.OnResetZoomOffsetButton, Laurent@724: self.ResetZoomOffsetButton) Laurent@724: range_sizer.AddWindow(self.ResetZoomOffsetButton, 0, border=0, flag=0) Laurent@724: Laurent@711: self.ExportGraphButton = wx.lib.buttons.GenBitmapButton(self.Editor, Laurent@714: bitmap=GetBitmap("export_graph"), size=wx.Size(28, 28), style=wx.NO_BORDER) Laurent@714: self.ExportGraphButton.SetToolTipString(_("Export graph values to clipboard")) Laurent@711: self.Bind(wx.EVT_BUTTON, self.OnExportGraphButtonClick, Laurent@711: self.ExportGraphButton) Laurent@711: range_sizer.AddWindow(self.ExportGraphButton, 0, border=0, flag=0) Laurent@711: Laurent@711: self.Editor.SetSizer(main_sizer) lbessard@301: Laurent@726: self.Editor.Bind(wx.EVT_MOUSEWHEEL, self.OnCanvasMouseWheel) Laurent@726: b@415: def __init__(self, parent, window, producer, instancepath = ""): laurent@586: EditorPanel.__init__(self, parent, "", window, None) b@415: DebugViewer.__init__(self, producer, True, False) greg@361: lbessard@301: self.InstancePath = instancepath laurent@632: self.RangeValues = None laurent@660: self.CursorIdx = None laurent@660: self.LastCursor = None laurent@660: self.CurrentMousePos = None laurent@660: self.CurrentMotionValue = None laurent@660: self.Dragging = False laurent@660: laurent@660: # Initialize Viewer mode to Selection mode laurent@660: self.Mode = MODE_SELECTION laurent@632: lbessard@301: self.Datas = [] laurent@660: self.StartTick = 0 laurent@660: self.StartIdx = 0 laurent@660: self.EndIdx = 0 laurent@660: self.MinValue = None laurent@660: self.MaxValue = None laurent@660: self.YCenter = 0 Laurent@724: self.CurrentZoom = 1.0 laurent@642: self.Fixed = False laurent@668: self.Ticktime = self.DataProducer.GetTicktime() laurent@632: self.RefreshCanvasRange() lbessard@301: laurent@660: for zoom_txt, zoom in ZOOM_VALUES: laurent@660: self.CanvasZoom.Append(zoom_txt) laurent@660: self.CanvasZoom.SetSelection(0) laurent@660: greg@361: self.AddDataConsumer(self.InstancePath.upper(), self) lbessard@301: lbessard@338: def __del__(self): greg@361: DebugViewer.__del__(self) greg@361: self.RemoveDataConsumer(self) lbessard@338: laurent@586: def GetTitle(self): laurent@586: if len(self.InstancePath) > 15: laurent@586: return "..." + self.InstancePath[-12:] laurent@586: return self.InstancePath laurent@586: laurent@660: # Changes Viewer mode laurent@660: def SetMode(self, mode): laurent@660: if self.Mode != mode or mode == MODE_SELECTION: laurent@660: if self.Mode == MODE_MOTION: laurent@660: wx.CallAfter(self.Canvas.canvas.SetCursor, wx.NullCursor) laurent@660: self.Mode = mode laurent@660: if self.Mode == MODE_MOTION: laurent@660: wx.CallAfter(self.Canvas.canvas.SetCursor, wx.StockCursor(wx.CURSOR_HAND)) laurent@660: laurent@660: def ResetView(self, register=False): lbessard@338: self.Datas = [] laurent@660: self.StartTick = 0 laurent@660: self.StartIdx = 0 laurent@660: self.EndIdx = 0 Laurent@706: self.MinValue = None Laurent@706: self.MaxValue = None laurent@665: self.CursorIdx = None laurent@642: self.Fixed = False laurent@632: self.Ticktime = self.DataProducer.GetTicktime() laurent@660: if register: laurent@660: self.AddDataConsumer(self.InstancePath.upper(), self) laurent@665: self.ResetLastCursor() laurent@632: self.RefreshCanvasRange() lbessard@338: self.RefreshView() lbessard@338: laurent@642: def RefreshNewData(self, *args, **kwargs): laurent@642: self.RefreshView(*args, **kwargs) greg@374: DebugViewer.RefreshNewData(self) greg@361: laurent@642: def GetNearestData(self, tick, adjust): laurent@642: ticks = numpy.array(zip(*self.Datas)[0]) laurent@642: new_cursor = numpy.argmin(abs(ticks - tick)) laurent@642: if adjust == -1 and ticks[new_cursor] > tick and new_cursor > 0: laurent@642: new_cursor -= 1 laurent@642: elif adjust == 1 and ticks[new_cursor] < tick and new_cursor < len(self.Datas): laurent@642: new_cursor += 1 laurent@642: return new_cursor laurent@642: laurent@660: def GetBounds(self): laurent@660: if self.StartIdx is None or self.EndIdx is None: laurent@660: self.StartIdx = self.GetNearestData(self.StartTick, -1) laurent@665: self.EndIdx = self.GetNearestData(self.StartTick + self.CurrentRange, 1) laurent@660: laurent@660: def ResetBounds(self): laurent@660: self.StartIdx = None laurent@660: self.EndIdx = None laurent@660: laurent@660: def RefreshCanvasRange(self): laurent@660: if self.Ticktime == 0 and self.RangeValues != RANGE_VALUES: laurent@660: self.RangeValues = RANGE_VALUES laurent@660: self.CanvasRange.Clear() laurent@660: for text, value in RANGE_VALUES: laurent@660: self.CanvasRange.Append(text) laurent@660: self.CanvasRange.SetStringSelection(RANGE_VALUES[0][0]) laurent@660: self.CurrentRange = RANGE_VALUES[0][1] laurent@660: elif self.RangeValues != TIME_RANGE_VALUES: laurent@660: self.RangeValues = TIME_RANGE_VALUES laurent@660: self.CanvasRange.Clear() laurent@660: for text, value in TIME_RANGE_VALUES: laurent@660: self.CanvasRange.Append(text) laurent@660: self.CanvasRange.SetStringSelection(TIME_RANGE_VALUES[0][0]) laurent@660: self.CurrentRange = TIME_RANGE_VALUES[0][1] / self.Ticktime laurent@660: Laurent@722: def RefreshView(self, force=False): laurent@660: self.Freeze() laurent@665: if force or not self.Fixed or (len(self.Datas) > 0 and self.StartTick + self.CurrentRange > self.Datas[-1][0]): laurent@660: if (self.MinValue is not None and laurent@660: self.MaxValue is not None and laurent@660: self.MinValue != self.MaxValue): laurent@660: Yrange = float(self.MaxValue - self.MinValue) / self.CurrentZoom laurent@660: else: laurent@660: Yrange = 2. / self.CurrentZoom laurent@660: Laurent@722: if not force and not self.Fixed and len(self.Datas) > 0: laurent@660: self.YCenter = max(self.Datas[-1][1] - Yrange / 2, laurent@660: min(self.YCenter, laurent@660: self.Datas[-1][1] + Yrange / 2)) laurent@660: laurent@660: var_name = self.InstancePath.split(".")[-1] laurent@660: laurent@660: self.GetBounds() laurent@660: self.VariableGraphic = plot.PolyLine(self.Datas[self.StartIdx:self.EndIdx + 1], laurent@660: legend=var_name, colour=colours[0]) laurent@660: self.GraphicsObject = plot.PlotGraphics([self.VariableGraphic], _("%s Graphics") % var_name, _("Tick"), _("Values")) laurent@660: self.Canvas.Draw(self.GraphicsObject, laurent@665: xAxis=(self.StartTick, self.StartTick + self.CurrentRange), Laurent@722: yAxis=(self.YCenter - Yrange * 1.1 / 2., self.YCenter + Yrange * 1.1 / 2.)) laurent@665: laurent@665: # Reset and draw cursor laurent@665: self.ResetLastCursor() laurent@665: self.RefreshCursor() laurent@665: laurent@660: self.RefreshScrollBar() laurent@660: laurent@660: self.Thaw() laurent@660: laurent@660: def GetInstancePath(self): laurent@660: return self.InstancePath laurent@660: laurent@660: def IsViewing(self, tagname): laurent@660: return self.InstancePath == tagname laurent@660: edouard@504: def NewValue(self, tick, value, forced=False): Laurent@724: value = {True:1., False:0.}.get(value, float(value)) Laurent@724: self.Datas.append((float(tick), value)) laurent@660: if self.MinValue is None: laurent@660: self.MinValue = value laurent@660: else: laurent@660: self.MinValue = min(self.MinValue, value) laurent@660: if self.MaxValue is None: laurent@660: self.MaxValue = value laurent@660: else: laurent@660: self.MaxValue = max(self.MaxValue, value) laurent@665: if not self.Fixed or tick < self.StartTick + self.CurrentRange: laurent@660: self.GetBounds() laurent@660: while int(self.Datas[self.StartIdx][0]) < tick - self.CurrentRange: laurent@660: self.StartIdx += 1 laurent@660: self.EndIdx += 1 laurent@660: self.StartTick = self.Datas[self.StartIdx][0] greg@361: self.NewDataAvailable() greg@361: lbessard@301: def RefreshScrollBar(self): laurent@642: if len(self.Datas) > 0: laurent@660: self.GetBounds() laurent@660: pos = int(self.Datas[self.StartIdx][0] - self.Datas[0][0]) laurent@642: range = int(self.Datas[-1][0] - self.Datas[0][0]) laurent@642: else: laurent@642: pos = 0 laurent@642: range = 0 laurent@642: self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange) lbessard@301: laurent@660: def RefreshRange(self): laurent@648: if len(self.Datas) > 0: laurent@648: if self.Fixed and self.Datas[-1][0] - self.Datas[0][0] < self.CurrentRange: laurent@648: self.Fixed = False laurent@660: self.ResetBounds() laurent@648: if self.Fixed: laurent@660: self.StartTick = min(self.StartTick, self.Datas[-1][0] - self.CurrentRange) laurent@648: else: laurent@665: self.StartTick = max(self.Datas[0][0], self.Datas[-1][0] - self.CurrentRange) Laurent@722: self.RefreshView(True) laurent@660: laurent@660: def OnRangeChanged(self, event): laurent@660: try: laurent@660: if self.Ticktime == 0: laurent@660: self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1] laurent@660: else: laurent@660: self.CurrentRange = self.RangeValues[self.CanvasRange.GetSelection()][1] / self.Ticktime laurent@660: except ValueError, e: laurent@660: self.CanvasRange.SetValue(str(self.CurrentRange)) laurent@660: wx.CallAfter(self.RefreshRange) laurent@660: event.Skip() laurent@660: laurent@660: def OnZoomChanged(self, event): laurent@660: self.CurrentZoom = ZOOM_VALUES[self.CanvasZoom.GetSelection()][1] Laurent@722: wx.CallAfter(self.RefreshView, True) lbessard@301: event.Skip() lbessard@301: lbessard@301: def OnPositionChanging(self, event): Laurent@724: if len(self.Datas) > 0: Laurent@724: self.ResetBounds() Laurent@724: self.StartTick = self.Datas[0][0] + event.GetPosition() Laurent@724: self.Fixed = True Laurent@724: self.NewDataAvailable(True) lbessard@301: event.Skip() lbessard@301: lbessard@301: def OnResetButton(self, event): laurent@642: self.Fixed = False laurent@660: self.ResetView() lbessard@301: event.Skip() lbessard@301: lbessard@301: def OnCurrentButton(self, event): Laurent@724: if len(self.Datas) > 0: Laurent@724: self.ResetBounds() Laurent@724: self.StartTick = max(self.Datas[0][0], self.Datas[-1][0] - self.CurrentRange) Laurent@724: self.Fixed = False Laurent@724: self.NewDataAvailable(True) Laurent@724: event.Skip() Laurent@724: Laurent@724: def OnResetZoomOffsetButton(self, event): Laurent@724: if len(self.Datas) > 0: Laurent@724: self.YCenter = (self.MaxValue + self.MinValue) / 2 Laurent@724: else: Laurent@724: self.YCenter = 0.0 Laurent@724: self.CurrentZoom = 1.0 Laurent@724: self.CanvasZoom.SetSelection(0) Laurent@724: wx.CallAfter(self.RefreshView, True) laurent@642: event.Skip() Laurent@711: Laurent@711: def OnExportGraphButtonClick(self, event): Laurent@711: data_copy = self.Datas[:] Laurent@711: text = "tick;%s;\n" % self.InstancePath Laurent@711: for tick, value in data_copy: Laurent@711: text += "%d;%.3f;\n" % (tick, value) Laurent@711: self.ParentWindow.SetCopyBuffer(text) Laurent@711: event.Skip() laurent@642: laurent@660: def OnCanvasLeftDown(self, event): laurent@660: self.Fixed = True laurent@660: self.Canvas.canvas.CaptureMouse() laurent@665: if len(self.Datas) > 0: laurent@665: if self.Mode == MODE_SELECTION: laurent@665: self.Dragging = True laurent@665: pos = self.Canvas.PositionScreenToUser(event.GetPosition()) laurent@665: self.CursorIdx = self.GetNearestData(pos[0], -1) laurent@665: self.RefreshCursor() laurent@665: elif self.Mode == MODE_MOTION: laurent@665: self.GetBounds() laurent@665: self.CurrentMousePos = event.GetPosition() laurent@665: self.CurrentMotionValue = self.Datas[self.StartIdx][0] laurent@660: event.Skip() Laurent@724: laurent@660: def OnCanvasLeftUp(self, event): laurent@660: self.Dragging = False laurent@660: if self.Mode == MODE_MOTION: laurent@660: self.CurrentMousePos = None laurent@660: self.CurrentMotionValue = None laurent@660: if self.Canvas.canvas.HasCapture(): laurent@660: self.Canvas.canvas.ReleaseMouse() laurent@660: event.Skip() Laurent@724: Laurent@724: def OnCanvasMiddleDown(self, event): Laurent@724: self.Fixed = True Laurent@724: self.Canvas.canvas.CaptureMouse() Laurent@724: if len(self.Datas) > 0: Laurent@724: self.GetBounds() Laurent@724: self.CurrentMousePos = event.GetPosition() Laurent@724: self.CurrentMotionValue = self.Datas[self.StartIdx][0] Laurent@724: event.Skip() Laurent@724: Laurent@724: def OnCanvasMiddleUp(self, event): Laurent@724: self.CurrentMousePos = None Laurent@724: self.CurrentMotionValue = None Laurent@724: if self.Canvas.canvas.HasCapture(): Laurent@724: self.Canvas.canvas.ReleaseMouse() Laurent@724: event.Skip() laurent@660: laurent@660: def OnCanvasMotion(self, event): laurent@660: if self.Mode == MODE_SELECTION and self.Dragging: laurent@660: pos = self.Canvas.PositionScreenToUser(event.GetPosition()) laurent@660: graphics, xAxis, yAxis = self.Canvas.last_draw laurent@660: self.CursorIdx = self.GetNearestData(max(xAxis[0], min(pos[0], xAxis[1])), -1) laurent@660: self.RefreshCursor() laurent@665: elif self.CurrentMousePos is not None and len(self.Datas) > 0: laurent@660: oldpos = self.Canvas.PositionScreenToUser(self.CurrentMousePos) laurent@660: newpos = self.Canvas.PositionScreenToUser(event.GetPosition()) laurent@660: self.CurrentMotionValue += oldpos[0] - newpos[0] laurent@660: self.YCenter += oldpos[1] - newpos[1] laurent@660: self.ResetBounds() laurent@665: self.StartTick = max(self.Datas[0][0], min(self.CurrentMotionValue, self.Datas[-1][0] - self.CurrentRange)) laurent@660: self.CurrentMousePos = event.GetPosition() laurent@660: self.NewDataAvailable(True) laurent@660: event.Skip() laurent@660: laurent@660: def OnCanvasMouseWheel(self, event): laurent@660: if self.CurrentMousePos is None: laurent@660: rotation = event.GetWheelRotation() / event.GetWheelDelta() laurent@660: if event.ShiftDown(): laurent@660: current = self.CanvasRange.GetSelection() laurent@660: new = max(0, min(current - rotation, len(self.RangeValues) - 1)) laurent@660: if new != current: laurent@660: if self.Ticktime == 0: laurent@660: self.CurrentRange = self.RangeValues[new][1] laurent@660: else: laurent@660: self.CurrentRange = self.RangeValues[new][1] / self.Ticktime laurent@660: self.CanvasRange.SetStringSelection(self.RangeValues[new][0]) laurent@660: wx.CallAfter(self.RefreshRange) laurent@660: else: laurent@660: current = self.CanvasZoom.GetSelection() Laurent@722: new = max(0, min(current + rotation, len(ZOOM_VALUES) - 1)) laurent@660: if new != current: laurent@660: self.CurrentZoom = ZOOM_VALUES[new][1] laurent@660: self.CanvasZoom.SetStringSelection(ZOOM_VALUES[new][0]) Laurent@722: wx.CallAfter(self.RefreshView, True) laurent@660: event.Skip() laurent@660: laurent@668: def OnCanvasResize(self, event): laurent@668: self.ResetLastCursor() laurent@668: wx.CallAfter(self.RefreshCursor) laurent@668: event.Skip() laurent@668: laurent@660: ## Reset the last cursor laurent@660: def ResetLastCursor(self): laurent@660: self.LastCursor = None laurent@660: laurent@660: ## Draw the cursor on graphic laurent@660: # @param dc The draw canvas laurent@660: # @param cursor The cursor parameters laurent@660: def DrawCursor(self, dc, cursor, value): laurent@665: if self.StartTick <= cursor <= self.StartTick + self.CurrentRange: laurent@660: # Prepare temporary dc for drawing laurent@660: width = self.Canvas._Buffer.GetWidth() laurent@660: height = self.Canvas._Buffer.GetHeight() laurent@660: tmp_Buffer = wx.EmptyBitmap(width, height) laurent@660: dcs = wx.MemoryDC() laurent@660: dcs.SelectObject(tmp_Buffer) laurent@660: dcs.Clear() laurent@660: dcs.BeginDrawing() laurent@660: laurent@660: dcs.SetPen(wx.Pen(wx.RED)) laurent@660: dcs.SetBrush(wx.Brush(wx.RED, wx.SOLID)) laurent@660: dcs.SetFont(self.Canvas._getFont(self.Canvas._fontSizeAxis)) laurent@660: laurent@660: # Calculate clipping region laurent@660: graphics, xAxis, yAxis = self.Canvas.last_draw laurent@660: p1 = numpy.array([xAxis[0], yAxis[0]]) laurent@660: p2 = numpy.array([xAxis[1], yAxis[1]]) laurent@660: cx, cy, cwidth, cheight = self.Canvas._point2ClientCoord(p1, p2) laurent@660: laurent@660: px, py = self.Canvas.PositionUserToScreen((float(cursor), 0.)) laurent@660: laurent@660: # Draw line cross drawing for diaplaying time cursor laurent@660: dcs.DrawLine(px, cy + 1, px, cy + cheight - 1) laurent@660: laurent@665: lines = ("X:%d\nY:%f" % (cursor, value)).splitlines() laurent@665: laurent@665: wtext = 0 laurent@665: for line in lines: laurent@665: w, h = dcs.GetTextExtent(line) laurent@665: wtext = max(wtext, w) laurent@665: laurent@665: offset = 0 laurent@665: for line in lines: laurent@665: # Draw time cursor date laurent@665: dcs.DrawText(line, min(px + 3, cx + cwidth - wtext), cy + 3 + offset) laurent@665: w, h = dcs.GetTextExtent(line) laurent@665: offset += h laurent@660: laurent@660: dcs.EndDrawing() laurent@660: laurent@660: #this will erase if called twice laurent@660: dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst laurent@660: laurent@660: ## Refresh the variable cursor. laurent@660: # @param dc The draw canvas laurent@660: def RefreshCursor(self, dc=None): laurent@687: if self: laurent@687: if dc is None: laurent@687: dc = wx.BufferedDC(wx.ClientDC(self.Canvas.canvas), self.Canvas._Buffer) laurent@687: laurent@687: # Erase previous time cursor if drawn laurent@687: if self.LastCursor is not None: laurent@687: self.DrawCursor(dc, *self.LastCursor) laurent@687: laurent@687: # Draw new time cursor laurent@687: if self.CursorIdx is not None: laurent@687: self.LastCursor = self.Datas[self.CursorIdx] laurent@687: self.DrawCursor(dc, *self.LastCursor)