# HG changeset patch # User Laurent Bessard # Date 1360707597 -3600 # Node ID a94e7fea7051635c0bb6a0472c98a9117110b4aa # Parent bd3e5b65e8be9feccddc97f2995ff771b2523b3e Improved matplotlib graphic debug panel implementation diff -r bd3e5b65e8be -r a94e7fea7051 controls/DebugVariablePanel.py --- a/controls/DebugVariablePanel.py Thu Feb 07 00:49:52 2013 +0100 +++ b/controls/DebugVariablePanel.py Tue Feb 12 23:19:57 2013 +0100 @@ -39,6 +39,7 @@ from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap from matplotlib.backends.backend_agg import FigureCanvasAgg from mpl_toolkits.mplot3d import Axes3D + color_cycle = matplotlib.rcParams['axes.color_cycle'] USE_MPL = True except: USE_MPL = False @@ -184,21 +185,23 @@ self.Parent.HasNewData = True def GetValue(self, tick=None, raw=False): - if tick is not None and self.IsNumVariable() and len(self.Data) > 0: - idx = numpy.argmin(abs(self.Data[:, 0] - tick)) - if self.VariableType in ["STRING", "WSTRING"]: - value, forced = self.RawData[int(self.Data[idx, 2])] - if not raw: - if self.VariableType == "STRING": - value = "'%s'" % value - else: - value = '"%s"' % value - return value, forced - else: - value = self.Data[idx, 1] - if not raw and isinstance(value, FloatType): - value = "%.6g" % value - return value, self.IsForced() + if tick is not None and self.IsNumVariable(): + if len(self.Data) > 0: + idx = numpy.argmin(abs(self.Data[:, 0] - tick)) + if self.VariableType in ["STRING", "WSTRING"]: + value, forced = self.RawData[int(self.Data[idx, 2])] + if not raw: + if self.VariableType == "STRING": + value = "'%s'" % value + else: + value = '"%s"' % value + return value, forced + else: + value = self.Data[idx, 1] + if not raw and isinstance(value, FloatType): + value = "%.6g" % value + return value, self.Data[idx, 2] + return self.Value, self.IsForced() elif not raw: if self.VariableType == "STRING": return "'%s'" % self.Value @@ -300,6 +303,11 @@ self.ParentWindow = None self.ParentControl = None + def OnDragOver(self, x, y, d): + if self.ParentControl is not None: + self.ParentControl.OnCanvasDragging(x, y) + return wx.TextDropTarget.OnDragOver(self, x, y, d) + def OnDropText(self, x, y, data): message = None try: @@ -349,7 +357,12 @@ self.ParentWindow.MoveGraph(values[0]) else: self.ParentWindow.InsertValue(values[0], force=True) - + + def OnLeave(self): + if self.ParentControl is not None: + self.ParentControl.OnCanvasLeave() + return wx.TextDropTarget.OnLeave(self) + def ShowMessage(self, message): dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) dialog.ShowModal() @@ -408,7 +421,6 @@ self.ParentWindow = window self.Items = items - self.ResetVariableNameMask() self.MainSizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0) self.AddViewer() @@ -440,30 +452,18 @@ return variables return self.Items[0].GetVariable() - def ResetVariableNameMask(self): - if len(self.Items) > 1: - self.VariableNameMask = reduce(compute_mask, - [item.GetVariable().split('.') for item in self.Items]) - elif len(self.Items) > 0: - self.VariableNameMask = self.Items[0].GetVariable().split('.')[:-1] + ['*'] - else: - self.VariableNameMask = [] - def AddItem(self, item): self.Items.append(item) - self.ResetVariableNameMask() - + def RemoveItem(self, item): if item in self.Items: self.Items.remove(item) - self.ResetVariableNameMask() def Clear(self): for item in self.Items: self.ParentWindow.RemoveDataConsumer(item) self.Items = [] - self.ResetVariableNameMask() - + def IsEmpty(self): return len(self.Items) == 0 @@ -476,8 +476,7 @@ else: self.ParentWindow.AddDataConsumer(iec_path, item) item.RefreshVariableType() - self.ResetVariableNameMask() - + def ResetData(self): for item in self.Items: item.ResetData() @@ -493,7 +492,7 @@ for item in self.Items: new_id = wx.NewId() AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, - text=item.GetVariable(self.VariableNameMask)) + text=item.GetVariable(self.ParentWindow.GetVariableNameMask())) self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(item), id=new_id) @@ -508,7 +507,7 @@ for item in self.Items: new_id = wx.NewId() AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, - text=item.GetVariable(self.VariableNameMask)) + text=item.GetVariable(self.ParentWindow.GetVariableNameMask())) self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(item), id=new_id) @@ -524,7 +523,7 @@ for item in self.Items: new_id = wx.NewId() AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, - text=item.GetVariable(self.VariableNameMask)) + text=item.GetVariable(self.ParentWindow.GetVariableNameMask())) self.Bind(wx.EVT_MENU, self.GetDeleteValueMenuFunction(item), id=new_id) @@ -619,17 +618,63 @@ mask.append("*") return mask + #CANVAS_HIGHLIGHT_TYPES + [HIGHLIGHT_NONE, + HIGHLIGHT_BEFORE, + HIGHLIGHT_AFTER, + HIGHLIGHT_LEFT, + HIGHLIGHT_RIGHT] = range(5) + + HIGHLIGHT_PEN = wx.Pen(wx.Colour(0, 128, 255)) + HIGHLIGHT_BRUSH = wx.Brush(wx.Colour(0, 128, 255, 128)) + class DraggingFigureCanvas(FigureCanvas): def __init__(self, parent, window, *args, **kwargs): FigureCanvas.__init__(self, parent, *args, **kwargs) self.ParentWindow = window - + self.Highlight = HIGHLIGHT_NONE + + def SetHighlight(self, highlight): + if self.Highlight != highlight: + self.Highlight = highlight + return True + return False + + def GetAxesBoundingBox(self): + width, height = self.GetSize() + ax, ay, aw, ah = self.figure.gca().get_position().bounds + return wx.Rect(ax * width, height - (ay + ah) * height - 1, + aw * width + 2, ah * height + 1) + def draw(self, drawDC=None): FigureCanvasAgg.draw(self) self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) + self.bitmap.UseAlpha() + width, height = self.GetSize() + bbox = self.GetAxesBoundingBox() + + destDC = wx.MemoryDC() + destDC.SelectObject(self.bitmap) + + destGC = wx.GCDC(destDC) + + destGC.BeginDrawing() + destGC.SetPen(HIGHLIGHT_PEN) + destGC.SetBrush(HIGHLIGHT_BRUSH) + if self.Highlight == HIGHLIGHT_BEFORE: + destGC.DrawLine(0, 0, width - 1, 0) + elif self.Highlight == HIGHLIGHT_AFTER: + destGC.DrawLine(0, height - 1, width - 1, height - 1) + elif self.Highlight == HIGHLIGHT_LEFT: + destGC.DrawRectangle(bbox.x, bbox.y, + bbox.width / 2, bbox.height) + elif self.Highlight == HIGHLIGHT_RIGHT: + destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y, + bbox.width / 2, bbox.height) + if self.ParentWindow.IsDragging(): destBBox = self.ParentWindow.GetDraggingAxesClippingRegion(self.Parent) if destBBox.width > 0 and destBBox.height > 0: @@ -649,15 +694,12 @@ srcDC = wx.MemoryDC() srcDC.SelectObject(srcBmp) - destDC = wx.MemoryDC() - destDC.SelectObject(self.bitmap) - - destDC.BeginDrawing() - destDC.Blit(destBBox.x, destBBox.y, + destGC.Blit(destBBox.x, destBBox.y, int(destBBox.width), int(destBBox.height), srcDC, srcX, srcY) - destDC.EndDrawing() + destGC.EndDrawing() + self._isDrawn = True self.gui_repaint(drawDC=drawDC) @@ -668,6 +710,8 @@ self.GraphType = graph_type self.CursorTick = None + self.MouseStartPos = None + self.StartCursorTick = None self.ResetGraphics() @@ -678,8 +722,10 @@ self.Canvas.SetMinSize(wx.Size(200, 200)) self.Canvas.SetDropTarget(DebugVariableDropTarget(self.ParentWindow, self)) self.Canvas.Bind(wx.EVT_LEFT_DOWN, self.OnCanvasLeftDown) + self.Canvas.mpl_connect('button_press_event', self.OnCanvasButtonPressed) self.Canvas.mpl_connect('motion_notify_event', self.OnCanvasMotion) - self.Canvas.mpl_connect('button_release_event', self.OnCanvasLeftUp) + self.Canvas.mpl_connect('button_release_event', self.OnCanvasButtonReleased) + self.Canvas.mpl_connect('scroll_event', self.OnCanvasScroll) self.MainSizer.AddWindow(self.Canvas, flag=wx.GROW) @@ -703,10 +749,7 @@ button_sizer.AddWindow(button, border=5, flag=wx.LEFT) def GetAxesBoundingBox(self, absolute=False): - width, height = self.Canvas.GetSize() - ax, ay, aw, ah = self.Axes.get_position().bounds - bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1, - aw * width + 2, ah * height + 1) + bbox = self.Canvas.GetAxesBoundingBox() if absolute: xw, yw = self.GetPosition() bbox.x += xw @@ -714,28 +757,36 @@ return bbox def OnCanvasLeftDown(self, event): - x, y = event.GetPosition() - width, height = self.Canvas.GetSize() - if len(self.Items) == 1: + if not self.Is3DCanvas(): + x, y = event.GetPosition() + width, height = self.Canvas.GetSize() rect = self.GetAxesBoundingBox() if rect.InsideXY(x, y): - xw, yw = self.GetPosition() - self.ParentWindow.StartDragNDrop(self, x + xw, y + yw) - return - elif self.Legend is not None: - item_idx = None - for i, t in enumerate(self.Legend.get_texts()): - (x0, y0), (x1, y1) = t.get_window_extent().get_points() - rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) - if rect.InsideXY(x, y): - item_idx = i - break - if item_idx is not None: - self.DoDragDrop(item_idx) - return + self.MouseStartPos = wx.Point(x, y) + if self.Legend is not None: + item_idx = None + for i, t in enumerate(self.Legend.get_texts()): + (x0, y0), (x1, y1) = t.get_window_extent().get_points() + rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) + if rect.InsideXY(x, y): + item_idx = i + break + if item_idx is not None: + self.DoDragDrop(item_idx) + return event.Skip() - def OnCanvasLeftUp(self, event): + def OnCanvasButtonPressed(self, event): + if not self.Is3DCanvas(): + if event.button == 1: + self.HandleCursorMove(event) + elif event.button == 2 and self.GraphType == GRAPH_PARALLEL: + width, height = self.Canvas.GetSize() + start_tick, end_tick = self.ParentWindow.GetRange() + self.MouseStartPos = wx.Point(event.x, height - event.y) + self.StartCursorTick = start_tick + + def OnCanvasButtonReleased(self, event): if self.ParentWindow.IsDragging(): width, height = self.Canvas.GetSize() xw, yw = self.GetPosition() @@ -743,20 +794,9 @@ self.Items[0].GetVariable(), xw + event.x, yw + height - event.y) - - def DoDragDrop(self, item_idx): - self.ParentWindow.ResetCursorTickRatio() - data = wx.TextDataObject(str((self.Items[item_idx].GetVariable(), "debug", "move"))) - dragSource = wx.DropSource(self.Canvas) - dragSource.SetData(data) - dragSource.DoDragDrop() - - def OnAxesMotion(self, event): - if self.Is3DCanvas(): - current_time = gettime() - if current_time - self.LastMotionTime > REFRESH_PERIOD: - self.LastMotionTime = current_time - Axes3D._on_move(self.Axes, event) + else: + self.MouseStartPos = None + self.StartCursorTick = None def OnCanvasMotion(self, event): if self.ParentWindow.IsDragging(): @@ -766,25 +806,82 @@ xw + event.x, yw + height - event.y) elif not self.Is3DCanvas(): - if event.inaxes == self.Axes: + if event.button == 1: + if event.inaxes == self.Axes: + if self.MouseStartPos is not None: + self.HandleCursorMove(event) + elif self.MouseStartPos is not None: + xw, yw = self.GetPosition() + width, height = self.Canvas.GetSize() + self.ParentWindow.StartDragNDrop(self, + event.x + xw, height - event.y + yw, + self.MouseStartPos.x + xw, self.MouseStartPos.y + yw) + elif event.button == 2 and self.GraphType == GRAPH_PARALLEL: start_tick, end_tick = self.ParentWindow.GetRange() - cursor_tick_ratio = None - if self.GraphType == GRAPH_ORTHOGONAL: - x_data = self.Items[0].GetData(start_tick, end_tick) - y_data = self.Items[1].GetData(start_tick, end_tick) - if len(x_data) > 0 and len(y_data) > 0: - length = min(len(x_data), len(y_data)) - d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2) - cursor_tick_ratio = float(x_data[numpy.argmin(d), 0] - start_tick) / (end_tick - start_tick) - else: - data = self.Items[0].GetData(start_tick, end_tick) - if len(data) > 0: - x_min, x_max = self.Axes.get_xlim() - cursor_tick_ratio = float(event.xdata - x_min) / (x_max - x_min) - if cursor_tick_ratio is not None: - self.ParentWindow.SetCursorTickRatio(cursor_tick_ratio) + rect = self.GetAxesBoundingBox() + self.ParentWindow.SetCanvasPosition( + self.StartCursorTick + (self.MouseStartPos.x - event.x) * + (end_tick - start_tick) / rect.width) + + def OnCanvasDragging(self, x, y, refresh=True): + width, height = self.Canvas.GetSize() + bbox = self.Canvas.GetAxesBoundingBox() + if bbox.InsideXY(x, y): + rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height) + if rect.InsideXY(x, y): + self.Canvas.SetHighlight(HIGHLIGHT_LEFT) else: - self.ParentWindow.ResetCursorTickRatio() + self.Canvas.SetHighlight(HIGHLIGHT_RIGHT) + elif y < height / 2: + self.Canvas.SetHighlight(HIGHLIGHT_BEFORE) + else: + self.Canvas.SetHighlight(HIGHLIGHT_AFTER) + if refresh: + self.ParentWindow.ForceRefresh() + + def OnCanvasLeave(self, refresh=True): + self.Canvas.SetHighlight(HIGHLIGHT_NONE) + if refresh: + self.ParentWindow.ForceRefresh() + + def OnCanvasScroll(self, event): + if event.inaxes is not None: + if self.GraphType == GRAPH_ORTHOGONAL: + start_tick, end_tick = self.ParentWindow.GetRange() + tick = (start_tick + end_tick) / 2. + else: + tick = event.xdata + self.ParentWindow.ChangeRange(int(-event.step) / 3, tick) + + def HandleCursorMove(self, event): + start_tick, end_tick = self.ParentWindow.GetRange() + cursor_tick = None + if self.GraphType == GRAPH_ORTHOGONAL: + x_data = self.Items[0].GetData(start_tick, end_tick) + y_data = self.Items[1].GetData(start_tick, end_tick) + if len(x_data) > 0 and len(y_data) > 0: + length = min(len(x_data), len(y_data)) + d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2) + cursor_tick = x_data[numpy.argmin(d), 0] + else: + data = self.Items[0].GetData(start_tick, end_tick) + if len(data) > 0: + cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0] + if cursor_tick is not None: + self.ParentWindow.SetCursorTick(cursor_tick) + + def DoDragDrop(self, item_idx): + data = wx.TextDataObject(str((self.Items[item_idx].GetVariable(), "debug", "move"))) + dragSource = wx.DropSource(self.Canvas) + dragSource.SetData(data) + dragSource.DoDragDrop() + + def OnAxesMotion(self, event): + if self.Is3DCanvas(): + current_time = gettime() + if current_time - self.LastMotionTime > REFRESH_PERIOD: + self.LastMotionTime = current_time + Axes3D._on_move(self.Axes, event) def OnSplitButton(self, event): if len(self.Items) == 2 or self.GraphType == GRAPH_ORTHOGONAL: @@ -794,7 +891,7 @@ for item in self.Items: new_id = wx.NewId() AppendMenu(menu, help='', id=new_id, kind=wx.ITEM_NORMAL, - text=item.GetVariable(self.VariableNameMask)) + text=item.GetVariable(self.ParentWindow.GetVariableNameMask())) self.Bind(wx.EVT_MENU, self.GetSplitGraphMenuFunction(item), id=new_id) @@ -816,13 +913,23 @@ self.Axes.mouse_init() else: self.Axes = self.Figure.gca() + self.Figure.subplotpars.update(top=0.95) if self.GraphType == GRAPH_ORTHOGONAL: self.Figure.subplotpars.update(bottom=0.15) - self.Axes.set_title('.'.join(self.VariableNameMask)) self.Plots = [] self.VLine = None self.HLine = None - self.TickLabel = None + self.Legend = None + self.Labels = [] + if self.GraphType == GRAPH_PARALLEL: + num_item = len(self.Items) + for idx in xrange(num_item): + self.Labels.append( + self.Axes.text(0.95, 0.05 + (num_item - idx - 1) * 0.1, + "", size = 'large', + horizontalalignment='right', + color = color_cycle[idx % len(color_cycle)], + transform = self.Axes.transAxes)) self.SplitButton.Enable(len(self.Items) > 1) def AddItem(self, item): @@ -832,13 +939,11 @@ def RemoveItem(self, item): DebugVariableViewer.RemoveItem(self, item) if not self.IsEmpty(): - self.ResetVariableNameMask() self.ResetGraphics() def UnregisterObsoleteData(self): DebugVariableViewer.UnregisterObsoleteData(self) if not self.IsEmpty(): - self.ResetVariableNameMask() self.ResetGraphics() def Is3DCanvas(self): @@ -883,23 +988,15 @@ x_min, x_max = start_tick, end_tick y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55 - if self.CursorTick is not None: + if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick: if self.VLine is None: self.VLine = self.Axes.axvline(self.CursorTick, color='r') else: self.VLine.set_xdata((self.CursorTick, self.CursorTick)) self.VLine.set_visible(True) - tick_label = self.ParentWindow.GetTickLabel(self.CursorTick) - if self.TickLabel is None: - self.TickLabel = self.Axes.text(0.5, 0.05, tick_label, - size = 'small', transform = self.Axes.transAxes) - else: - self.TickLabel.set_text(tick_label) else: if self.VLine is not None: self.VLine.set_visible(False) - if self.TickLabel is not None: - self.TickLabel.set_text("") else: min_start_tick = reduce(max, [item.GetData()[0, 0] for item in self.Items @@ -925,7 +1022,7 @@ x_data[:, 1][:length], y_data[:, 1][:length]) - if self.CursorTick is not None: + if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick: if self.VLine is None: self.VLine = self.Axes.axvline(x_cursor, color='r') else: @@ -936,19 +1033,11 @@ self.HLine.set_ydata((y_cursor, y_cursor)) self.VLine.set_visible(True) self.HLine.set_visible(True) - tick_label = self.ParentWindow.GetTickLabel(self.CursorTick) - if self.TickLabel is None: - self.TickLabel = self.Axes.text(0.05, 0.90, tick_label, - size = 'small', transform = self.Axes.transAxes) - else: - self.TickLabel.set_text(tick_label) else: if self.VLine is not None: self.VLine.set_visible(False) if self.HLine is not None: self.HLine.set_visible(False) - if self.TickLabel is not None: - self.TickLabel.set_text("") else: while len(self.Axes.lines) > 0: self.Axes.lines.pop() @@ -961,7 +1050,7 @@ y_data[:, 1][:length], zs = z_data[:, 1][:length]) self.Axes.set_zlim(z_min, z_max) - if self.CursorTick is not None: + if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick: for kwargs in [{"xs": numpy.array([x_min, x_max])}, {"ys": numpy.array([y_min, y_max])}, {"zs": numpy.array([z_min, z_max])}]: @@ -975,19 +1064,24 @@ self.Axes.set_xlim(x_min, x_max) self.Axes.set_ylim(y_min, y_max) + variable_name_mask = self.ParentWindow.GetVariableNameMask() if self.CursorTick is not None: values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items]) else: values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items]) - names = [item.GetVariable(self.VariableNameMask) for item in self.Items] + names = [item.GetVariable(variable_name_mask) for item in self.Items] labels = map(lambda x: "%s: %s" % x, zip(names, values)) colors = map(lambda x: {True: 'b', False: 'k'}[x], forced) if self.GraphType == GRAPH_PARALLEL: - self.Legend = self.Axes.legend(self.Plots, labels, - loc="upper left", frameon=False, - prop={'size':'small'}) + if self.Legend is None: + self.Legend = self.Axes.legend(self.Plots, labels, + loc="upper left", frameon=False, prop={'size':'small'}, + title = '.'.join(variable_name_mask)) + self.Legend.get_title().set_fontsize('small') for t, color in zip(self.Legend.get_texts(), colors): t.set_color(color) + for label, value in zip(self.Labels, values): + label.set_text(value) else: self.Legend = None self.Axes.set_xlabel(labels[0], fontdict={'size':'small','color':colors[0]}) @@ -1008,6 +1102,7 @@ def __init__(self, parent, producer, window): wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL) + self.SetBackgroundColour(wx.WHITE) self.ParentWindow = window @@ -1023,10 +1118,11 @@ self.StartTick = 0 self.Fixed = False self.Force = False - self.CursorTickRatio = None + self.CursorTick = None self.DraggingAxesPanel = None self.DraggingAxesBoundingBox = None self.DraggingAxesMousePos = None + self.VariableNameMask = None self.GraphicPanels = [] @@ -1067,7 +1163,17 @@ self.OnPositionChanging, self.CanvasPosition) main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM) + self.TickSizer = wx.BoxSizer(wx.HORIZONTAL) + main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL|wx.GROW) + + self.TickLabel = wx.StaticText(self) + self.TickSizer.AddWindow(self.TickLabel, 1, border=5, flag=wx.RIGHT|wx.GROW) + + self.TickTimeLabel = wx.StaticText(self) + self.TickSizer.AddWindow(self.TickTimeLabel) + self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL|wx.VSCROLL) + self.GraphicsWindow.SetBackgroundColour(wx.WHITE) self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self)) self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize) main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW) @@ -1167,7 +1273,8 @@ self.Ticks = numpy.append(self.Ticks, [tick]) if not self.Fixed or tick < self.StartTick + self.CurrentRange: self.StartTick = max(self.StartTick, tick - self.CurrentRange) - self.ResetCursorTick(False) + if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange: + self.Force = True DebugViewer.NewDataAvailable(self, tick, *args, **kwargs) def ForceRefresh(self): @@ -1183,56 +1290,47 @@ self.GraphicsSizer.Layout() self.RefreshGraphicsWindowScrollbars() - def SetCursorTickRatio(self, cursor_tick_ratio): - self.CursorTickRatio = cursor_tick_ratio + def SetCanvasPosition(self, tick): + tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange)) + self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))] + self.Fixed = True + self.RefreshCanvasPosition() + self.ForceRefresh() + + def SetCursorTick(self, cursor_tick): + self.CursorTick = cursor_tick + self.Fixed = True self.ResetCursorTick() - def ResetCursorTickRatio(self): - self.CursorTickRatio = None + def ResetCursorTick(self): + self.CursorTick = None self.ResetCursorTick() - def ResetCursorTick(self, force_refresh=True): - if self.CursorTickRatio is not None and len(self.Ticks) > 0: - raw_tick = self.StartTick + self.CursorTickRatio * self.CurrentRange - cursor_tick = self.Ticks[numpy.argmin(abs(self.Ticks - raw_tick))] - else: - cursor_tick = None + def ResetCursorTick(self): for panel in self.GraphicPanels: if isinstance(panel, DebugVariableGraphic): - panel.SetCursorTick(cursor_tick) - if force_refresh: - self.ForceRefresh() - - def GetTickLabel(self, tick): - label = "Tick: %d" % tick - if self.Ticktime > 0: - tick_duration = int(tick * self.Ticktime) - not_null = False - duration = "" - for value, format in [(tick_duration / DAY, "%dd"), - ((tick_duration % DAY) / HOUR, "%dh"), - ((tick_duration % HOUR) / MINUTE, "%dm"), - ((tick_duration % MINUTE) / SECOND, "%ds")]: - - if value > 0 or not_null: - duration += format % value - not_null = True - - duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND) - label += "(%s)" % duration - return label - - def StartDragNDrop(self, panel, x_mouse, y_mouse): + panel.SetCursorTick(self.CursorTick) + self.ForceRefresh() + + def StartDragNDrop(self, panel, x_mouse, y_mouse, x_mouse_start, y_mouse_start): self.DraggingAxesPanel = panel self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(absolute=True) self.DraggingAxesMousePos = wx.Point( - x_mouse - self.DraggingAxesBoundingBox.x, - y_mouse - self.DraggingAxesBoundingBox.y) - self.ResetCursorTickRatio() + x_mouse_start - self.DraggingAxesBoundingBox.x, + y_mouse_start - self.DraggingAxesBoundingBox.y) + self.MoveDragNDrop(x_mouse, y_mouse) def MoveDragNDrop(self, x_mouse, y_mouse): self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y + for panel in self.GraphicPanels: + x, y = panel.GetPosition() + width, height = panel.Canvas.GetSize() + rect = wx.Rect(x, y, width, height) + if rect.InsideXY(x_mouse, y_mouse): + panel.OnCanvasDragging(x_mouse - x, y_mouse - y, False) + else: + panel.OnCanvasLeave(False) self.ForceRefresh() def IsDragging(self): @@ -1252,6 +1350,7 @@ self.DraggingAxesBoundingBox = None self.DraggingAxesMousePos = None for idx, panel in enumerate(self.GraphicPanels): + panel.OnCanvasLeave(False) xw, yw = panel.GetPosition() width, height = panel.Canvas.GetSize() bbox = wx.Rect(xw, yw, width, height) @@ -1291,7 +1390,36 @@ panel.Refresh(refresh_graphics) else: panel.Refresh() - + + if self.CursorTick is not None: + tick = self.CursorTick + elif len(self.Ticks) > 0: + tick = self.Ticks[-1] + else: + tick = None + if tick is not None: + self.TickLabel.SetLabel("Tick: %d" % tick) + if self.Ticktime > 0: + tick_duration = int(tick * self.Ticktime) + not_null = False + duration = "" + for value, format in [(tick_duration / DAY, "%dd"), + ((tick_duration % DAY) / HOUR, "%dh"), + ((tick_duration % HOUR) / MINUTE, "%dm"), + ((tick_duration % MINUTE) / SECOND, "%ds")]: + + if value > 0 or not_null: + duration += format % value + not_null = True + + duration += "%gms" % (float(tick_duration % SECOND) / MILLISECOND) + self.TickTimeLabel.SetLabel("t: %s" % duration) + else: + self.TickTimeLabel.SetLabel("") + else: + self.TickLabel.SetLabel("") + self.TickTimeLabel.SetLabel("") + self.TickSizer.Layout() else: self.Freeze() @@ -1320,6 +1448,7 @@ self.GraphicPanels.remove(panel) panel.Destroy() + self.ResetVariableNameMask() self.RefreshGraphicsSizer() self.ForceRefresh() @@ -1346,6 +1475,7 @@ for panel in self.GraphicPanels: panel.Destroy() self.GraphicPanels = [] + self.ResetVariableNameMask() self.RefreshGraphicsSizer() else: self.Table.Empty() @@ -1429,6 +1559,22 @@ menu.Destroy() event.Skip() + def ChangeRange(self, dir, tick): + current_range = self.CurrentRange + current_range_idx = self.CanvasRange.GetSelection() + new_range_idx = max(0, min(current_range_idx + dir, len(self.RangeValues) - 1)) + if new_range_idx != current_range_idx: + self.CanvasRange.SetSelection(new_range_idx) + if self.Ticktime == 0: + self.CurrentRange = self.RangeValues[new_range_idx][1] + else: + self.CurrentRange = self.RangeValues[new_range_idx][1] / self.Ticktime + if len(self.Ticks) > 0: + new_start_tick = tick - (tick - self.StartTick) * self.CurrentRange / current_range + self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))] + self.Fixed = self.StartTick < self.Ticks[-1] - self.CurrentRange + self.ForceRefresh() + def RefreshRange(self): if len(self.Ticks) > 0: if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange: @@ -1458,7 +1604,8 @@ if len(self.Ticks) > 0: self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange) self.Fixed = False - self.ForceRefresh() + self.CursorTick = None + self.ResetCursorTick() event.Skip() def CopyDataToClipboard(self, variables): @@ -1515,6 +1662,21 @@ return self.GraphicPanels.index(viewer) return None + def ResetVariableNameMask(self): + items = [] + for panel in self.GraphicPanels: + items.extend(panel.GetItems()) + if len(items) > 1: + self.VariableNameMask = reduce(compute_mask, + [item.GetVariable().split('.') for item in items]) + elif len(items) > 0: + self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*'] + else: + self.VariableNameMask = [] + + def GetVariableNameMask(self): + return self.VariableNameMask + def InsertValue(self, iec_path, idx = None, force=False): if USE_MPL: for panel in self.GraphicPanels: @@ -1535,12 +1697,15 @@ if USE_MPL: if item.IsNumVariable(): panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL) + if self.CursorTick is not None: + panel.SetCursorTick(self.CursorTick) else: panel = DebugVariableText(self.GraphicsWindow, self, [item]) if idx is not None: self.GraphicPanels.insert(idx, panel) else: self.GraphicPanels.append(panel) + self.ResetVariableNameMask() self.RefreshGraphicsSizer() else: self.Table.InsertItem(idx, item) @@ -1566,7 +1731,10 @@ source_panel.Destroy() panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL) + if self.CursorTick is not None: + panel.SetCursorTick(self.CursorTick) self.GraphicPanels.insert(idx, panel) + self.ResetVariableNameMask() self.RefreshGraphicsSizer() self.ForceRefresh() @@ -1580,6 +1748,8 @@ item = source_items.pop(-1) if item.IsNumVariable(): panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL) + if self.CursorTick is not None: + panel.SetCursorTick(self.CursorTick) else: panel = DebugVariableText(self.GraphicsWindow, self, [item]) self.GraphicPanels.insert(source_idx + 1, panel) @@ -1591,10 +1761,13 @@ source_panel.RemoveItem(item) if item.IsNumVariable(): panel = DebugVariableGraphic(self.GraphicsWindow, self, [item], GRAPH_PARALLEL) + if self.CursorTick is not None: + panel.SetCursorTick(self.CursorTick) else: panel = DebugVariableText(self.GraphicsWindow, self, [item]) self.GraphicPanels.insert(source_idx + 1, panel) + self.ResetVariableNameMask() self.RefreshGraphicsSizer() self.ForceRefresh() @@ -1634,6 +1807,7 @@ target_panel.GraphType = merge_type target_panel.ResetGraphics() + self.ResetVariableNameMask() self.RefreshGraphicsSizer() self.ForceRefresh() @@ -1645,12 +1819,14 @@ source_panel.Clear() source_panel.Destroy() self.GraphicPanels.remove(source_panel) + self.ResetVariableNameMask() self.RefreshGraphicsSizer() else: source_panel.RemoveItem(item) if source_panel.IsEmpty(): source_panel.Destroy() self.GraphicPanels.remove(source_panel) + self.ResetVariableNameMask() self.RefreshGraphicsSizer() self.ForceRefresh() @@ -1678,6 +1854,7 @@ else: continue self.GraphicPanels.append(panel) + self.ResetVariableNameMask() self.RefreshGraphicsSizer() else: self.InsertValue(variable, force=True)