68 if self.Parent and self.Variable != variable: |
74 if self.Parent and self.Variable != variable: |
69 self.Variable = variable |
75 self.Variable = variable |
70 self.RefreshVariableType() |
76 self.RefreshVariableType() |
71 self.Parent.RefreshView() |
77 self.Parent.RefreshView() |
72 |
78 |
73 def GetVariable(self, max_size=None): |
79 def GetVariable(self, mask=None): |
74 variable = self.Variable |
80 variable = self.Variable |
75 if max_size is not None: |
81 if mask is not None: |
76 max_size = max(max_size, 10) |
82 parts = variable.split('.') |
77 if len(variable) > max_size: |
83 mask = mask + ['*'] * max(0, len(parts) - len(mask)) |
78 variable = "..." + variable[-(max_size - 3):] |
84 last = None |
|
85 variable = "" |
|
86 for m, v in zip(mask, parts): |
|
87 if m == '*': |
|
88 if last == '*': |
|
89 variable += '.' |
|
90 variable += v |
|
91 elif last is None or last == '*': |
|
92 variable += '..' |
|
93 last = m |
79 return variable |
94 return variable |
80 |
95 |
81 def RefreshVariableType(self): |
96 def RefreshVariableType(self): |
82 self.VariableType = self.Parent.GetDataType(self.Variable) |
97 self.VariableType = self.Parent.GetDataType(self.Variable) |
83 if USE_MPL: |
98 if USE_MPL: |
103 else: |
118 else: |
104 return self.Data[start_idx:] |
119 return self.Data[start_idx:] |
105 |
120 |
106 return None |
121 return None |
107 |
122 |
|
123 def GetRawValue(self, idx): |
|
124 if self.VariableType in ["STRING", "WSTRING"] and idx < len(self.RawData): |
|
125 return self.RawData[idx][0] |
|
126 return "" |
|
127 |
108 def GetRange(self): |
128 def GetRange(self): |
109 return self.MinValue, self.MaxValue |
129 return self.MinValue, self.MaxValue |
110 |
130 |
111 def ResetData(self): |
131 def ResetData(self): |
112 if self.IsNumVariable(): |
132 if self.IsNumVariable(): |
113 self.Data = numpy.array([]).reshape(0, 2) |
133 self.Data = numpy.array([]).reshape(0, 3) |
|
134 if self.VariableType in ["STRING", "WSTRING"]: |
|
135 self.RawData = [] |
114 self.MinValue = None |
136 self.MinValue = None |
115 self.MaxValue = None |
137 self.MaxValue = None |
116 else: |
138 else: |
117 self.Data = None |
139 self.Data = None |
118 |
140 |
119 def IsNumVariable(self): |
141 def IsNumVariable(self): |
120 return self.Parent.IsNumType(self.VariableType) |
142 return (self.Parent.IsNumType(self.VariableType) or |
|
143 self.VariableType in ["STRING", "WSTRING"]) |
121 |
144 |
122 def NewValue(self, tick, value, forced=False): |
145 def NewValue(self, tick, value, forced=False): |
123 if USE_MPL and self.IsNumVariable(): |
146 if USE_MPL and self.IsNumVariable(): |
124 num_value = {True:1., False:0.}.get(value, float(value)) |
147 if self.VariableType in ["STRING", "WSTRING"]: |
|
148 num_value = binascii.crc32(value) & CRC_MASK |
|
149 else: |
|
150 num_value = float(value) |
125 if self.MinValue is None: |
151 if self.MinValue is None: |
126 self.MinValue = num_value |
152 self.MinValue = num_value |
127 else: |
153 else: |
128 self.MinValue = min(self.MinValue, num_value) |
154 self.MinValue = min(self.MinValue, num_value) |
129 if self.MaxValue is None: |
155 if self.MaxValue is None: |
130 self.MaxValue = num_value |
156 self.MaxValue = num_value |
131 else: |
157 else: |
132 self.MaxValue = max(self.MaxValue, num_value) |
158 self.MaxValue = max(self.MaxValue, num_value) |
133 self.Data = numpy.append(self.Data, [[float(tick), num_value]], axis=0) |
159 forced_value = float(forced) |
|
160 if self.VariableType in ["STRING", "WSTRING"]: |
|
161 raw_data = (value, forced_value) |
|
162 if len(self.RawData) == 0 or self.RawData[-1] != raw_data: |
|
163 extra_value = len(self.RawData) |
|
164 self.RawData.append(raw_data) |
|
165 else: |
|
166 extra_value = len(self.RawData) - 1 |
|
167 else: |
|
168 extra_value = forced_value |
|
169 self.Data = numpy.append(self.Data, [[float(tick), num_value, extra_value]], axis=0) |
134 self.Parent.HasNewData = True |
170 self.Parent.HasNewData = True |
135 DebugDataConsumer.NewValue(self, tick, value, forced) |
171 DebugDataConsumer.NewValue(self, tick, value, forced) |
136 |
172 |
137 def SetForced(self, forced): |
173 def SetForced(self, forced): |
138 if self.Forced != forced: |
174 if self.Forced != forced: |
145 value = value[1:-1] |
181 value = value[1:-1] |
146 if self.Value != value: |
182 if self.Value != value: |
147 self.Value = value |
183 self.Value = value |
148 self.Parent.HasNewData = True |
184 self.Parent.HasNewData = True |
149 |
185 |
150 def GetValue(self): |
186 def GetValue(self, tick=None, raw=False): |
151 if self.VariableType == "STRING": |
187 if tick is not None and self.IsNumVariable() and len(self.Data) > 0: |
152 return "'%s'" % self.Value |
188 idx = numpy.argmin(abs(self.Data[:, 0] - tick)) |
153 elif self.VariableType == "WSTRING": |
189 if self.VariableType in ["STRING", "WSTRING"]: |
154 return "\"%s\"" % self.Value |
190 value, forced = self.RawData[int(self.Data[idx, 2])] |
155 elif isinstance(self.Value, FloatType): |
191 if not raw: |
156 return "%.6g" % self.Value |
192 if self.VariableType == "STRING": |
|
193 value = "'%s'" % value |
|
194 else: |
|
195 value = '"%s"' % value |
|
196 return value, forced |
|
197 else: |
|
198 value = self.Data[idx, 1] |
|
199 if not raw and isinstance(value, FloatType): |
|
200 value = "%.6g" % value |
|
201 return value, self.IsForced() |
|
202 elif not raw: |
|
203 if self.VariableType == "STRING": |
|
204 return "'%s'" % self.Value |
|
205 elif self.VariableType == "WSTRING": |
|
206 return '"%s"' % self.Value |
|
207 elif isinstance(self.Value, FloatType): |
|
208 return "%.6g" % self.Value |
157 return self.Value |
209 return self.Value |
158 |
210 |
159 def GetNearestData(self, tick, adjust): |
211 def GetNearestData(self, tick, adjust): |
160 if USE_MPL and self.IsNumVariable(): |
212 if USE_MPL and self.IsNumVariable(): |
161 ticks = self.Data[:, 0] |
213 ticks = self.Data[:, 0] |
278 if len(values) > 2 and values[2] == "move": |
330 if len(values) > 2 and values[2] == "move": |
279 self.ParentWindow.MoveGraph(values[0], target_idx) |
331 self.ParentWindow.MoveGraph(values[0], target_idx) |
280 else: |
332 else: |
281 self.ParentWindow.InsertValue(values[0], target_idx, force=True) |
333 self.ParentWindow.InsertValue(values[0], target_idx, force=True) |
282 else: |
334 else: |
283 ax, ay, aw, ah = self.ParentControl.Axes.get_position().bounds |
335 rect = self.ParentControl.GetAxesBoundingBox() |
284 rect = wx.Rect(ax * width, height - (ay + ah) * height, |
|
285 aw * width, ah * height) |
|
286 if rect.InsideXY(x, y): |
336 if rect.InsideXY(x, y): |
287 merge_rect = wx.Rect(ax * width, height - (ay + ah) * height, |
337 merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height) |
288 aw * width / 2., ah * height) |
|
289 if merge_rect.InsideXY(x, y): |
338 if merge_rect.InsideXY(x, y): |
290 merge_type = GRAPH_ORTHOGONAL |
339 merge_type = GRAPH_ORTHOGONAL |
291 wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True) |
340 wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True) |
292 else: |
341 else: |
293 if y > height / 2: |
342 if y > height / 2: |
305 dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) |
354 dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) |
306 dialog.ShowModal() |
355 dialog.ShowModal() |
307 dialog.Destroy() |
356 dialog.Destroy() |
308 |
357 |
309 if USE_MPL: |
358 if USE_MPL: |
310 SECOND = 1000000000 |
359 MILLISECOND = 1000000 |
|
360 SECOND = 1000 * MILLISECOND |
311 MINUTE = 60 * SECOND |
361 MINUTE = 60 * SECOND |
312 HOUR = 60 * MINUTE |
362 HOUR = 60 * MINUTE |
|
363 DAY = 24 * HOUR |
313 |
364 |
314 ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)]) |
365 ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)]) |
315 RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)]) |
366 RANGE_VALUES = map(lambda x: (str(x), x), [25 * 2 ** i for i in xrange(6)]) |
316 TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \ |
367 TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \ |
317 [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \ |
368 [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \ |
386 if self.GraphType == GRAPH_ORTHOGONAL: |
438 if self.GraphType == GRAPH_ORTHOGONAL: |
387 return tuple(variables) |
439 return tuple(variables) |
388 return variables |
440 return variables |
389 return self.Items[0].GetVariable() |
441 return self.Items[0].GetVariable() |
390 |
442 |
|
443 def ResetVariableNameMask(self): |
|
444 if len(self.Items) > 1: |
|
445 self.VariableNameMask = reduce(compute_mask, |
|
446 [item.GetVariable().split('.') for item in self.Items]) |
|
447 elif len(self.Items) > 0: |
|
448 self.VariableNameMask = self.Items[0].GetVariable().split('.')[:-1] + ['*'] |
|
449 else: |
|
450 self.VariableNameMask = [] |
|
451 |
391 def AddItem(self, item): |
452 def AddItem(self, item): |
392 self.Items.append(item) |
453 self.Items.append(item) |
|
454 self.ResetVariableNameMask() |
393 |
455 |
394 def RemoveItem(self, item): |
456 def RemoveItem(self, item): |
395 if item in self.Items: |
457 if item in self.Items: |
396 self.Items.remove(item) |
458 self.Items.remove(item) |
|
459 self.ResetVariableNameMask() |
397 |
460 |
398 def Clear(self): |
461 def Clear(self): |
399 for item in self.Items: |
462 for item in self.Items: |
400 self.ParentWindow.RemoveDataConsumer(item) |
463 self.ParentWindow.RemoveDataConsumer(item) |
401 self.Items = [] |
464 self.Items = [] |
|
465 self.ResetVariableNameMask() |
402 |
466 |
403 def IsEmpty(self): |
467 def IsEmpty(self): |
404 return len(self.Items) == 0 |
468 return len(self.Items) == 0 |
405 |
469 |
406 def UnregisterObsoleteData(self): |
470 def UnregisterObsoleteData(self): |
543 self.ValueLabel.SetForegroundColour(wx.BLUE) |
608 self.ValueLabel.SetForegroundColour(wx.BLUE) |
544 else: |
609 else: |
545 self.ValueLabel.SetForegroundColour(wx.BLACK) |
610 self.ValueLabel.SetForegroundColour(wx.BLACK) |
546 self.ValueLabel.SetSelection(self.ValueLabel.GetLastPosition(), -1) |
611 self.ValueLabel.SetSelection(self.ValueLabel.GetLastPosition(), -1) |
547 |
612 |
|
613 def compute_mask(x, y): |
|
614 mask = [] |
|
615 for xp, yp in zip(x, y): |
|
616 if xp == yp: |
|
617 mask.append(xp) |
|
618 else: |
|
619 mask.append("*") |
|
620 return mask |
|
621 |
|
622 class DraggingFigureCanvas(FigureCanvas): |
|
623 |
|
624 def __init__(self, parent, window, *args, **kwargs): |
|
625 FigureCanvas.__init__(self, parent, *args, **kwargs) |
|
626 |
|
627 self.ParentWindow = window |
|
628 |
|
629 def draw(self, drawDC=None): |
|
630 FigureCanvasAgg.draw(self) |
|
631 |
|
632 self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) |
|
633 if self.ParentWindow.IsDragging(): |
|
634 destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self.Parent) |
|
635 if destBBox.width > 0 and destBBox.height > 0: |
|
636 srcPanel = self.ParentWindow.DraggingAxesPanel |
|
637 srcBBox = srcPanel.GetAxesBoundingBox() |
|
638 |
|
639 if destBBox.x == 0: |
|
640 srcX = srcBBox.x + srcBBox.width - destBBox.width |
|
641 else: |
|
642 srcX = srcBBox.x |
|
643 if destBBox.y == 0: |
|
644 srcY = srcBBox.y + srcBBox.height - destBBox.height |
|
645 else: |
|
646 srcY = srcBBox.y |
|
647 |
|
648 srcBmp = _convert_agg_to_wx_bitmap(srcPanel.Canvas.get_renderer(), None) |
|
649 srcDC = wx.MemoryDC() |
|
650 srcDC.SelectObject(srcBmp) |
|
651 |
|
652 destDC = wx.MemoryDC() |
|
653 destDC.SelectObject(self.bitmap) |
|
654 |
|
655 destDC.BeginDrawing() |
|
656 destDC.Blit(destBBox.x, destBBox.y, |
|
657 int(destBBox.width), int(destBBox.height), |
|
658 srcDC, srcX, srcY) |
|
659 destDC.EndDrawing() |
|
660 |
|
661 self._isDrawn = True |
|
662 self.gui_repaint(drawDC=drawDC) |
|
663 |
548 class DebugVariableGraphic(DebugVariableViewer): |
664 class DebugVariableGraphic(DebugVariableViewer): |
549 |
665 |
550 def __init__(self, parent, window, items, graph_type): |
666 def __init__(self, parent, window, items, graph_type): |
551 DebugVariableViewer.__init__(self, parent, window, items) |
667 DebugVariableViewer.__init__(self, parent, window, items) |
552 |
668 |
553 self.GraphType = graph_type |
669 self.GraphType = graph_type |
|
670 self.CursorTick = None |
554 |
671 |
555 self.ResetGraphics() |
672 self.ResetGraphics() |
556 |
673 |
557 def AddViewer(self): |
674 def AddViewer(self): |
558 self.Figure = matplotlib.figure.Figure(facecolor='w') |
675 self.Figure = matplotlib.figure.Figure(facecolor='w') |
559 |
676 |
560 self.Canvas = FigureCanvas(self, -1, self.Figure) |
677 self.Canvas = DraggingFigureCanvas(self, self.ParentWindow, -1, self.Figure) |
561 self.Canvas.SetMinSize(wx.Size(200, 200)) |
678 self.Canvas.SetMinSize(wx.Size(200, 200)) |
562 self.Canvas.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self)) |
679 self.Canvas.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self)) |
563 self.Canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasClick) |
680 self.Canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasLeftDown) |
|
681 self.Canvas.mpl_connect('motion_notify_event', self.OnCanvasMotion) |
|
682 self.Canvas.mpl_connect('button_release_event', self.OnCanvasLeftUp) |
564 |
683 |
565 self.MainSizer.AddWindow(self.Canvas, flag=wx.GROW) |
684 self.MainSizer.AddWindow(self.Canvas, flag=wx.GROW) |
566 |
685 |
567 def AddButtons(self): |
686 def AddButtons(self): |
568 button_sizer = wx.BoxSizer(wx.VERTICAL) |
687 button_sizer = wx.BoxSizer(wx.VERTICAL) |
580 size=wx.Size(28, 28), style=wx.NO_BORDER) |
699 size=wx.Size(28, 28), style=wx.NO_BORDER) |
581 button.SetToolTipString(help) |
700 button.SetToolTipString(help) |
582 setattr(self, name, button) |
701 setattr(self, name, button) |
583 self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button) |
702 self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button) |
584 button_sizer.AddWindow(button, border=5, flag=wx.LEFT) |
703 button_sizer.AddWindow(button, border=5, flag=wx.LEFT) |
585 |
704 |
586 def OnCanvasClick(self, event): |
705 def GetAxesBoundingBox(self, absolute=False): |
|
706 width, height = self.Canvas.GetSize() |
|
707 ax, ay, aw, ah = self.Axes.get_position().bounds |
|
708 bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1, |
|
709 aw * width + 2, ah * height + 1) |
|
710 if absolute: |
|
711 xw, yw = self.GetPosition() |
|
712 bbox.x += xw |
|
713 bbox.y += yw |
|
714 return bbox |
|
715 |
|
716 def OnCanvasLeftDown(self, event): |
587 x, y = event.GetPosition() |
717 x, y = event.GetPosition() |
588 width, height = self.Canvas.GetSize() |
718 width, height = self.Canvas.GetSize() |
589 if len(self.Items) == 1: |
719 if len(self.Items) == 1: |
590 ax, ay, aw, ah = self.Axes.get_position().bounds |
720 rect = self.GetAxesBoundingBox() |
591 rect = wx.Rect(ax * width, height - (ay + ah) * height, |
|
592 aw * width, ah * height) |
|
593 if rect.InsideXY(x, y): |
721 if rect.InsideXY(x, y): |
594 self.DoDragDrop(0) |
722 xw, yw = self.GetPosition() |
|
723 self.ParentWindow.StartDragNDrop(self, x + xw, y + yw) |
595 return |
724 return |
596 elif self.Legend is not None: |
725 elif self.Legend is not None: |
597 item_idx = None |
726 item_idx = None |
598 for i, t in enumerate(self.Legend.get_texts()): |
727 for i, t in enumerate(self.Legend.get_texts()): |
599 (x0, y0), (x1, y1) = t.get_window_extent().get_points() |
728 (x0, y0), (x1, y1) = t.get_window_extent().get_points() |
604 if item_idx is not None: |
733 if item_idx is not None: |
605 self.DoDragDrop(item_idx) |
734 self.DoDragDrop(item_idx) |
606 return |
735 return |
607 event.Skip() |
736 event.Skip() |
608 |
737 |
|
738 def OnCanvasLeftUp(self, event): |
|
739 if self.ParentWindow.IsDragging(): |
|
740 width, height = self.Canvas.GetSize() |
|
741 xw, yw = self.GetPosition() |
|
742 self.ParentWindow.StopDragNDrop( |
|
743 self.Items[0].GetVariable(), |
|
744 xw + event.x, |
|
745 yw + height - event.y) |
|
746 |
609 def DoDragDrop(self, item_idx): |
747 def DoDragDrop(self, item_idx): |
|
748 self.ParentWindow.ResetCursorTickRatio() |
610 data = wx.TextDataObject(str((self.Items[item_idx].GetVariable(), "debug", "move"))) |
749 data = wx.TextDataObject(str((self.Items[item_idx].GetVariable(), "debug", "move"))) |
611 dragSource = wx.DropSource(self.Canvas) |
750 dragSource = wx.DropSource(self.Canvas) |
612 dragSource.SetData(data) |
751 dragSource.SetData(data) |
613 dragSource.DoDragDrop() |
752 dragSource.DoDragDrop() |
614 |
753 |
615 def OnMotion(self, event): |
754 def OnAxesMotion(self, event): |
616 if self.Is3DCanvas(): |
755 if self.Is3DCanvas(): |
617 current_time = gettime() |
756 current_time = gettime() |
618 if current_time - self.LastMotionTime > REFRESH_PERIOD: |
757 if current_time - self.LastMotionTime > REFRESH_PERIOD: |
619 self.LastMotionTime = current_time |
758 self.LastMotionTime = current_time |
620 Axes3D._on_move(self.Axes, event) |
759 Axes3D._on_move(self.Axes, event) |
621 |
760 |
|
761 def OnCanvasMotion(self, event): |
|
762 if self.ParentWindow.IsDragging(): |
|
763 width, height = self.Canvas.GetSize() |
|
764 xw, yw = self.GetPosition() |
|
765 self.ParentWindow.MoveDragNDrop( |
|
766 xw + event.x, |
|
767 yw + height - event.y) |
|
768 elif not self.Is3DCanvas(): |
|
769 if event.inaxes == self.Axes: |
|
770 start_tick, end_tick = self.ParentWindow.GetRange() |
|
771 cursor_tick_ratio = None |
|
772 if self.GraphType == GRAPH_ORTHOGONAL: |
|
773 x_data = self.Items[0].GetData(start_tick, end_tick) |
|
774 y_data = self.Items[1].GetData(start_tick, end_tick) |
|
775 if len(x_data) > 0 and len(y_data) > 0: |
|
776 length = min(len(x_data), len(y_data)) |
|
777 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2) |
|
778 cursor_tick_ratio = float(x_data[numpy.argmin(d), 0] - start_tick) / (end_tick - start_tick) |
|
779 else: |
|
780 data = self.Items[0].GetData(start_tick, end_tick) |
|
781 if len(data) > 0: |
|
782 x_min, x_max = self.Axes.get_xlim() |
|
783 cursor_tick_ratio = float(event.xdata - x_min) / (x_max - x_min) |
|
784 if cursor_tick_ratio is not None: |
|
785 self.ParentWindow.SetCursorTickRatio(cursor_tick_ratio) |
|
786 else: |
|
787 self.ParentWindow.ResetCursorTickRatio() |
|
788 |
622 def OnSplitButton(self, event): |
789 def OnSplitButton(self, event): |
623 if len(self.Items) == 2 or self.GraphType == GRAPH_ORTHOGONAL: |
790 if len(self.Items) == 2 or self.GraphType == GRAPH_ORTHOGONAL: |
624 wx.CallAfter(self.ParentWindow.SplitGraphs, self) |
791 wx.CallAfter(self.ParentWindow.SplitGraphs, self) |
625 else: |
792 else: |
626 menu = wx.Menu(title='') |
793 menu = wx.Menu(title='') |
627 for item in self.Items: |
794 for item in self.Items: |
628 new_id = wx.NewId() |
795 new_id = wx.NewId() |
629 AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, |
796 AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, |
630 text=item.GetVariable(20)) |
797 text=item.GetVariable(self.VariableNameMask)) |
631 self.Bind(wx.EVT_MENU, |
798 self.Bind(wx.EVT_MENU, |
632 self.GetSplitGraphMenuFunction(item), |
799 self.GetSplitGraphMenuFunction(item), |
633 id=new_id) |
800 id=new_id) |
634 |
801 |
635 new_id = wx.NewId() |
802 new_id = wx.NewId() |
643 self.Figure.clear() |
810 self.Figure.clear() |
644 if self.Is3DCanvas(): |
811 if self.Is3DCanvas(): |
645 self.Axes = self.Figure.gca(projection='3d') |
812 self.Axes = self.Figure.gca(projection='3d') |
646 self.Axes.set_color_cycle(['b']) |
813 self.Axes.set_color_cycle(['b']) |
647 self.LastMotionTime = gettime() |
814 self.LastMotionTime = gettime() |
648 setattr(self.Axes, "_on_move", self.OnMotion) |
815 setattr(self.Axes, "_on_move", self.OnAxesMotion) |
649 self.Axes.mouse_init() |
816 self.Axes.mouse_init() |
650 else: |
817 else: |
651 self.Axes = self.Figure.gca() |
818 self.Axes = self.Figure.gca() |
652 if self.GraphType == GRAPH_ORTHOGONAL: |
819 if self.GraphType == GRAPH_ORTHOGONAL: |
653 self.Figure.subplotpars.update(bottom=0.15) |
820 self.Figure.subplotpars.update(bottom=0.15) |
|
821 self.Axes.set_title('.'.join(self.VariableNameMask)) |
654 self.Plots = [] |
822 self.Plots = [] |
|
823 self.VLine = None |
|
824 self.HLine = None |
|
825 self.TickLabel = None |
655 self.SplitButton.Enable(len(self.Items) > 1) |
826 self.SplitButton.Enable(len(self.Items) > 1) |
656 |
827 |
657 def AddItem(self, item): |
828 def AddItem(self, item): |
658 DebugVariableViewer.AddItem(self, item) |
829 DebugVariableViewer.AddItem(self, item) |
659 self.ResetGraphics() |
830 self.ResetGraphics() |
660 |
831 |
661 def RemoveItem(self, item): |
832 def RemoveItem(self, item): |
662 DebugVariableViewer.RemoveItem(self, item) |
833 DebugVariableViewer.RemoveItem(self, item) |
663 if not self.IsEmpty(): |
834 if not self.IsEmpty(): |
|
835 self.ResetVariableNameMask() |
664 self.ResetGraphics() |
836 self.ResetGraphics() |
665 |
837 |
666 def UnregisterObsoleteData(self): |
838 def UnregisterObsoleteData(self): |
667 DebugVariableViewer.UnregisterObsoleteData(self) |
839 DebugVariableViewer.UnregisterObsoleteData(self) |
668 if not self.IsEmpty(): |
840 if not self.IsEmpty(): |
|
841 self.ResetVariableNameMask() |
669 self.ResetGraphics() |
842 self.ResetGraphics() |
670 |
843 |
671 def Is3DCanvas(self): |
844 def Is3DCanvas(self): |
672 return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3 |
845 return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3 |
673 |
846 |
|
847 def SetCursorTick(self, cursor_tick): |
|
848 self.CursorTick = cursor_tick |
|
849 |
674 def Refresh(self, refresh_graphics=True): |
850 def Refresh(self, refresh_graphics=True): |
675 |
851 |
676 if refresh_graphics: |
852 if refresh_graphics: |
677 start_tick, end_tick = self.ParentWindow.GetRange() |
853 start_tick, end_tick = self.ParentWindow.GetRange() |
678 |
854 |
705 y_center = 0.5 |
881 y_center = 0.5 |
706 y_range = 1.0 |
882 y_range = 1.0 |
707 x_min, x_max = start_tick, end_tick |
883 x_min, x_max = start_tick, end_tick |
708 y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55 |
884 y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55 |
709 |
885 |
|
886 if self.CursorTick is not None: |
|
887 if self.VLine is None: |
|
888 self.VLine = self.Axes.axvline(self.CursorTick, color='r') |
|
889 else: |
|
890 self.VLine.set_xdata((self.CursorTick, self.CursorTick)) |
|
891 self.VLine.set_visible(True) |
|
892 tick_label = self.ParentWindow.GetTickLabel(self.CursorTick) |
|
893 if self.TickLabel is None: |
|
894 self.TickLabel = self.Axes.text(0.5, 0.05, tick_label, |
|
895 size = 'small', transform = self.Axes.transAxes) |
|
896 else: |
|
897 self.TickLabel.set_text(tick_label) |
|
898 else: |
|
899 if self.VLine is not None: |
|
900 self.VLine.set_visible(False) |
|
901 if self.TickLabel is not None: |
|
902 self.TickLabel.set_text("") |
710 else: |
903 else: |
711 min_start_tick = reduce(max, [item.GetData()[0, 0] |
904 min_start_tick = reduce(max, [item.GetData()[0, 0] |
712 for item in self.Items |
905 for item in self.Items |
713 if len(item.GetData()) > 0], 0) |
906 if len(item.GetData()) > 0], 0) |
714 start_tick = max(start_tick, min_start_tick) |
907 start_tick = max(start_tick, min_start_tick) |
715 end_tick = max(end_tick, min_start_tick) |
908 end_tick = max(end_tick, min_start_tick) |
716 x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick) |
909 x_data, x_min, x_max = OrthogonalData(self.Items[0], start_tick, end_tick) |
717 y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick) |
910 y_data, y_min, y_max = OrthogonalData(self.Items[1], start_tick, end_tick) |
|
911 if self.CursorTick is not None: |
|
912 x_cursor, x_forced = self.Items[0].GetValue(self.CursorTick, raw=True) |
|
913 y_cursor, y_forced = self.Items[1].GetValue(self.CursorTick, raw=True) |
718 length = 0 |
914 length = 0 |
719 if x_data is not None and y_data is not None: |
915 if x_data is not None and y_data is not None: |
720 length = min(len(x_data), len(y_data)) |
916 length = min(len(x_data), len(y_data)) |
721 if len(self.Items) < 3: |
917 if len(self.Items) < 3: |
722 if x_data is not None and y_data is not None: |
918 if x_data is not None and y_data is not None: |
726 y_data[:, 1][:length])[0]) |
922 y_data[:, 1][:length])[0]) |
727 else: |
923 else: |
728 self.Plots[0].set_data( |
924 self.Plots[0].set_data( |
729 x_data[:, 1][:length], |
925 x_data[:, 1][:length], |
730 y_data[:, 1][:length]) |
926 y_data[:, 1][:length]) |
|
927 |
|
928 if self.CursorTick is not None: |
|
929 if self.VLine is None: |
|
930 self.VLine = self.Axes.axvline(x_cursor, color='r') |
|
931 else: |
|
932 self.VLine.set_xdata((x_cursor, x_cursor)) |
|
933 if self.HLine is None: |
|
934 self.HLine = self.Axes.axhline(y_cursor, color='r') |
|
935 else: |
|
936 self.HLine.set_ydata((y_cursor, y_cursor)) |
|
937 self.VLine.set_visible(True) |
|
938 self.HLine.set_visible(True) |
|
939 tick_label = self.ParentWindow.GetTickLabel(self.CursorTick) |
|
940 if self.TickLabel is None: |
|
941 self.TickLabel = self.Axes.text(0.05, 0.90, tick_label, |
|
942 size = 'small', transform = self.Axes.transAxes) |
|
943 else: |
|
944 self.TickLabel.set_text(tick_label) |
|
945 else: |
|
946 if self.VLine is not None: |
|
947 self.VLine.set_visible(False) |
|
948 if self.HLine is not None: |
|
949 self.HLine.set_visible(False) |
|
950 if self.TickLabel is not None: |
|
951 self.TickLabel.set_text("") |
731 else: |
952 else: |
732 while len(self.Axes.lines) > 0: |
953 while len(self.Axes.lines) > 0: |
733 self.Axes.lines.pop() |
954 self.Axes.lines.pop() |
734 z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick) |
955 z_data, z_min, z_max = OrthogonalData(self.Items[2], start_tick, end_tick) |
|
956 if self.CursorTick is not None: |
|
957 z_cursor, z_forced = self.Items[2].GetValue(self.CursorTick, raw=True) |
735 if x_data is not None and y_data is not None and z_data is not None: |
958 if x_data is not None and y_data is not None and z_data is not None: |
736 length = min(length, len(z_data)) |
959 length = min(length, len(z_data)) |
737 self.Axes.plot(x_data[:, 1][:length], |
960 self.Axes.plot(x_data[:, 1][:length], |
738 y_data[:, 1][:length], |
961 y_data[:, 1][:length], |
739 zs = z_data[:, 1][:length]) |
962 zs = z_data[:, 1][:length]) |
740 self.Axes.set_zlim(z_min, z_max) |
963 self.Axes.set_zlim(z_min, z_max) |
741 |
964 if self.CursorTick is not None: |
|
965 for kwargs in [{"xs": numpy.array([x_min, x_max])}, |
|
966 {"ys": numpy.array([y_min, y_max])}, |
|
967 {"zs": numpy.array([z_min, z_max])}]: |
|
968 for param, value in [("xs", numpy.array([x_cursor, x_cursor])), |
|
969 ("ys", numpy.array([y_cursor, y_cursor])), |
|
970 ("zs", numpy.array([z_cursor, z_cursor]))]: |
|
971 kwargs.setdefault(param, value) |
|
972 kwargs["color"] = 'r' |
|
973 self.Axes.plot(**kwargs) |
|
974 |
742 self.Axes.set_xlim(x_min, x_max) |
975 self.Axes.set_xlim(x_min, x_max) |
743 self.Axes.set_ylim(y_min, y_max) |
976 self.Axes.set_ylim(y_min, y_max) |
744 |
977 |
745 labels = ["%s: %s" % (item.GetVariable(40), item.GetValue()) |
978 if self.CursorTick is not None: |
746 for item in self.Items] |
979 values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items]) |
747 colors = [{True: 'b', False: 'k'}[item.IsForced()] for item in self.Items] |
980 else: |
|
981 values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items]) |
|
982 names = [item.GetVariable(self.VariableNameMask) for item in self.Items] |
|
983 labels = map(lambda x: "%s: %s" % x, zip(names, values)) |
|
984 colors = map(lambda x: {True: 'b', False: 'k'}[x], forced) |
748 if self.GraphType == GRAPH_PARALLEL: |
985 if self.GraphType == GRAPH_PARALLEL: |
749 self.Legend = self.Axes.legend(self.Plots, labels, |
986 self.Legend = self.Axes.legend(self.Plots, labels, |
750 loc="upper left", frameon=False, |
987 loc="upper left", frameon=False, |
751 prop={'size':'small'}) |
988 prop={'size':'small'}) |
752 for t, color in zip(self.Legend.get_texts(), colors): |
989 for t, color in zip(self.Legend.get_texts(), colors): |
921 def NewDataAvailable(self, tick, *args, **kwargs): |
1163 def NewDataAvailable(self, tick, *args, **kwargs): |
922 if USE_MPL and tick is not None: |
1164 if USE_MPL and tick is not None: |
923 self.Ticks = numpy.append(self.Ticks, [tick]) |
1165 self.Ticks = numpy.append(self.Ticks, [tick]) |
924 if not self.Fixed or tick < self.StartTick + self.CurrentRange: |
1166 if not self.Fixed or tick < self.StartTick + self.CurrentRange: |
925 self.StartTick = max(self.StartTick, tick - self.CurrentRange) |
1167 self.StartTick = max(self.StartTick, tick - self.CurrentRange) |
|
1168 self.ResetCursorTick(False) |
926 DebugViewer.NewDataAvailable(self, tick, *args, **kwargs) |
1169 DebugViewer.NewDataAvailable(self, tick, *args, **kwargs) |
|
1170 |
|
1171 def ForceRefresh(self): |
|
1172 self.Force = True |
|
1173 wx.CallAfter(self.NewDataAvailable, None, True) |
927 |
1174 |
928 def RefreshGraphicsSizer(self): |
1175 def RefreshGraphicsSizer(self): |
929 self.GraphicsSizer.Clear() |
1176 self.GraphicsSizer.Clear() |
930 |
1177 |
931 for panel in self.GraphicPanels: |
1178 for panel in self.GraphicPanels: |
932 self.GraphicsSizer.AddWindow(panel, flag=wx.GROW) |
1179 self.GraphicsSizer.AddWindow(panel, flag=wx.GROW) |
933 |
1180 |
934 self.GraphicsSizer.Layout() |
1181 self.GraphicsSizer.Layout() |
935 self.RefreshGraphicsWindowScrollbars() |
1182 self.RefreshGraphicsWindowScrollbars() |
|
1183 |
|
1184 def SetCursorTickRatio(self, cursor_tick_ratio): |
|
1185 self.CursorTickRatio = cursor_tick_ratio |
|
1186 self.ResetCursorTick() |
|
1187 |
|
1188 def ResetCursorTickRatio(self): |
|
1189 self.CursorTickRatio = None |
|
1190 self.ResetCursorTick() |
|
1191 |
|
1192 def ResetCursorTick(self, force_refresh=True): |
|
1193 if self.CursorTickRatio is not None and len(self.Ticks) > 0: |
|
1194 raw_tick = self.StartTick + self.CursorTickRatio * self.CurrentRange |
|
1195 cursor_tick = self.Ticks[numpy.argmin(abs(self.Ticks - raw_tick))] |
|
1196 else: |
|
1197 cursor_tick = None |
|
1198 for panel in self.GraphicPanels: |
|
1199 if isinstance(panel, DebugVariableGraphic): |
|
1200 panel.SetCursorTick(cursor_tick) |
|
1201 if force_refresh: |
|
1202 self.ForceRefresh() |
|
1203 |
|
1204 def GetTickLabel(self, tick): |
|
1205 label = "Tick: %d" % tick |
|
1206 if self.Ticktime > 0: |
|
1207 tick_duration = int(tick * self.Ticktime) |
|
1208 not_null = False |
|
1209 duration = "" |
|
1210 for value, format in [(tick_duration / DAY, "%dd"), |
|
1211 ((tick_duration % DAY) / HOUR, "%dh"), |
|
1212 ((tick_duration % HOUR) / MINUTE, "%dm"), |
|
1213 ((tick_duration % MINUTE) / SECOND, "%ds")]: |
|
1214 |
|
1215 if value > 0 or not_null: |
|
1216 duration += format % value |
|
1217 not_null = True |
|
1218 |
|
1219 duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND) |
|
1220 label += "(%s)" % duration |
|
1221 return label |
|
1222 |
|
1223 def StartDragNDrop(self, panel, x_mouse, y_mouse): |
|
1224 self.DraggingAxesPanel = panel |
|
1225 self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(absolute=True) |
|
1226 self.DraggingAxesMousePos = wx.Point( |
|
1227 x_mouse - self.DraggingAxesBoundingBox.x, |
|
1228 y_mouse - self.DraggingAxesBoundingBox.y) |
|
1229 self.ResetCursorTickRatio() |
|
1230 |
|
1231 def MoveDragNDrop(self, x_mouse, y_mouse): |
|
1232 self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x |
|
1233 self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y |
|
1234 self.ForceRefresh() |
|
1235 |
|
1236 def IsDragging(self): |
|
1237 return self.DraggingAxesPanel is not None |
|
1238 |
|
1239 def GetDraggingAxesClippingRegion(self, panel): |
|
1240 x, y = panel.GetPosition() |
|
1241 width, height = panel.Canvas.GetSize() |
|
1242 bbox = wx.Rect(x, y, width, height) |
|
1243 bbox = bbox.Intersect(self.DraggingAxesBoundingBox) |
|
1244 bbox.x -= x |
|
1245 bbox.y -= y |
|
1246 return bbox |
|
1247 |
|
1248 def StopDragNDrop(self, variable, x_mouse, y_mouse): |
|
1249 self.DraggingAxesPanel = None |
|
1250 self.DraggingAxesBoundingBox = None |
|
1251 self.DraggingAxesMousePos = None |
|
1252 for idx, panel in enumerate(self.GraphicPanels): |
|
1253 xw, yw = panel.GetPosition() |
|
1254 width, height = panel.Canvas.GetSize() |
|
1255 bbox = wx.Rect(xw, yw, width, height) |
|
1256 if bbox.InsideXY(x_mouse, y_mouse): |
|
1257 merge_type = GRAPH_PARALLEL |
|
1258 if panel.Is3DCanvas(): |
|
1259 if y_mouse > yw + height / 2: |
|
1260 idx += 1 |
|
1261 wx.CallAfter(self.MoveGraph, variable, idx) |
|
1262 return |
|
1263 else: |
|
1264 rect = panel.GetAxesBoundingBox(True) |
|
1265 if rect.InsideXY(x_mouse, y_mouse): |
|
1266 merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height) |
|
1267 if merge_rect.InsideXY(x_mouse, y_mouse): |
|
1268 merge_type = GRAPH_ORTHOGONAL |
|
1269 wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True) |
|
1270 else: |
|
1271 if y_mouse > yw + height / 2: |
|
1272 idx += 1 |
|
1273 wx.CallAfter(self.MoveGraph, variable, idx) |
|
1274 break |
|
1275 self.ForceRefresh() |
936 |
1276 |
937 def RefreshView(self, only_values=False): |
1277 def RefreshView(self, only_values=False): |
938 if USE_MPL: |
1278 if USE_MPL: |
939 self.RefreshCanvasPosition() |
1279 self.RefreshCanvasPosition() |
940 |
1280 |
1103 def OnResetButton(self, event): |
1450 def OnResetButton(self, event): |
1104 self.StartTick = 0 |
1451 self.StartTick = 0 |
1105 self.Fixed = False |
1452 self.Fixed = False |
1106 for panel in self.GraphicPanels: |
1453 for panel in self.GraphicPanels: |
1107 panel.ResetData() |
1454 panel.ResetData() |
1108 self.RefreshView(True) |
1455 self.ForceRefresh() |
1109 event.Skip() |
1456 event.Skip() |
1110 |
1457 |
1111 def OnCurrentButton(self, event): |
1458 def OnCurrentButton(self, event): |
1112 if len(self.Ticks) > 0: |
1459 if len(self.Ticks) > 0: |
1113 self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange) |
1460 self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange) |
1114 self.Fixed = False |
1461 self.Fixed = False |
1115 self.Force = True |
1462 self.ForceRefresh() |
1116 self.RefreshView(True) |
|
1117 event.Skip() |
1463 event.Skip() |
1118 |
1464 |
1119 def CopyDataToClipboard(self, variables): |
1465 def CopyDataToClipboard(self, variables): |
1120 text = "tick;%s;\n" % ";".join([var_name for var_name, data in variables]) |
1466 text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables]) |
1121 next_tick = NextTick(variables) |
1467 next_tick = NextTick(variables) |
1122 while next_tick is not None: |
1468 while next_tick is not None: |
1123 values = [] |
1469 values = [] |
1124 for var_name, data in variables: |
1470 for item, data in variables: |
1125 if len(data) > 0: |
1471 if len(data) > 0: |
1126 if next_tick == data[0][0]: |
1472 if next_tick == data[0][0]: |
1127 values.append("%.3f" % data.pop(0)[1]) |
1473 var_type = item.GetVariableType() |
|
1474 if var_type in ["STRING", "WSTRING"]: |
|
1475 value = item.GetRawValue(int(data.pop(0)[2])) |
|
1476 if var_type == "STRING": |
|
1477 values.append("'%s'" % value) |
|
1478 else: |
|
1479 values.append('"%s"' % value) |
|
1480 else: |
|
1481 values.append("%.3f" % data.pop(0)[1]) |
1128 else: |
1482 else: |
1129 values.append("") |
1483 values.append("") |
1130 else: |
1484 else: |
1131 values.append("") |
1485 values.append("") |
1132 text += "%d;%s;\n" % (next_tick, ";".join(values)) |
1486 text += "%d;%s;\n" % (next_tick, ";".join(values)) |
1133 next_tick = NextTick(variables) |
1487 next_tick = NextTick(variables) |
1134 self.ParentWindow.SetCopyBuffer(text) |
1488 self.ParentWindow.SetCopyBuffer(text) |
1135 |
1489 |
1136 def OnExportGraphButton(self, event): |
1490 def OnExportGraphButton(self, event): |
1137 variables = [] |
1491 variables = [] |
1138 for item in self.Table.GetData(): |
1492 if USE_MPL: |
|
1493 items = [] |
|
1494 for panel in self.GraphicPanels: |
|
1495 items.extend(panel.GetItems()) |
|
1496 else: |
|
1497 items = self.Table.GetData() |
|
1498 for item in items: |
1139 if item.IsNumVariable(): |
1499 if item.IsNumVariable(): |
1140 variables.append((item.GetVariable(), [entry for entry in item.GetData()])) |
1500 variables.append((item, [entry for entry in item.GetData()])) |
1141 wx.CallAfter(self.CopyDataToClipboard, variables) |
1501 wx.CallAfter(self.CopyDataToClipboard, variables) |
1142 event.Skip() |
1502 event.Skip() |
1143 |
1503 |
1144 def OnPositionChanging(self, event): |
1504 def OnPositionChanging(self, event): |
1145 if len(self.Ticks) > 0: |
1505 if len(self.Ticks) > 0: |
1146 self.StartTick = self.Ticks[0] + event.GetPosition() |
1506 self.StartTick = self.Ticks[0] + event.GetPosition() |
1147 self.Fixed = True |
1507 self.Fixed = True |
1148 self.Force = True |
1508 self.ForceRefresh() |
1149 wx.CallAfter(self.NewDataAvailable, None, True) |
|
1150 event.Skip() |
1509 event.Skip() |
1151 |
1510 |
1152 def GetRange(self): |
1511 def GetRange(self): |
1153 return self.StartTick, self.StartTick + self.CurrentRange |
1512 return self.StartTick, self.StartTick + self.CurrentRange |
1154 |
1513 |