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) |
|