Laurent@814: #!/usr/bin/env python Laurent@814: # -*- coding: utf-8 -*- Laurent@814: Laurent@814: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor Laurent@814: #based on the plcopen standard. Laurent@814: # Laurent@814: #Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD Laurent@814: # Laurent@814: #See COPYING file for copyrights details. Laurent@814: # Laurent@814: #This library is free software; you can redistribute it and/or Laurent@814: #modify it under the terms of the GNU General Public Laurent@814: #License as published by the Free Software Foundation; either Laurent@814: #version 2.1 of the License, or (at your option) any later version. Laurent@814: # Laurent@814: #This library is distributed in the hope that it will be useful, Laurent@814: #but WITHOUT ANY WARRANTY; without even the implied warranty of Laurent@814: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Laurent@814: #General Public License for more details. Laurent@814: # Laurent@814: #You should have received a copy of the GNU General Public Laurent@814: #License along with this library; if not, write to the Free Software Laurent@814: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Laurent@814: Laurent@1198: from types import TupleType Laurent@887: from time import time as gettime Laurent@887: import numpy Laurent@814: Laurent@814: import wx Laurent@888: Laurent@1198: import matplotlib Laurent@1198: matplotlib.use('WX') Laurent@1198: import matplotlib.pyplot Laurent@1198: from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas Laurent@1198: from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap Laurent@1198: from matplotlib.backends.backend_agg import FigureCanvasAgg Laurent@1198: from mpl_toolkits.mplot3d import Axes3D Laurent@1200: Laurent@1200: from editors.DebugViewer import REFRESH_PERIOD Laurent@814: Laurent@1199: from DebugVariableItem import DebugVariableItem Laurent@1200: from DebugVariableViewer import * Laurent@1199: from GraphButton import GraphButton Laurent@1199: Laurent@1198: GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2) Laurent@1198: Laurent@1198: #CANVAS_SIZE_TYPES Laurent@1198: [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200] Laurent@1198: Laurent@1198: DEFAULT_CANVAS_HEIGHT = 200. Laurent@1198: CANVAS_BORDER = (20., 10.) Laurent@1198: CANVAS_PADDING = 8.5 Laurent@1198: VALUE_LABEL_HEIGHT = 17. Laurent@1198: AXES_LABEL_HEIGHT = 12.75 Laurent@1198: Laurent@1200: COLOR_CYCLE = ['r', 'b', 'g', 'm', 'y', 'k'] Laurent@1200: CURSOR_COLOR = '#800080' Laurent@1198: Laurent@1198: def OrthogonalData(item, start_tick, end_tick): Laurent@1198: data = item.GetData(start_tick, end_tick) Laurent@1198: min_value, max_value = item.GetValueRange() Laurent@1198: if min_value is not None and max_value is not None: Laurent@1198: center = (min_value + max_value) / 2. Laurent@1198: range = max(1.0, max_value - min_value) Laurent@1198: else: Laurent@1198: center = 0.5 Laurent@1198: range = 1.0 Laurent@1198: return data, center - range * 0.55, center + range * 0.55 Laurent@1198: Laurent@1200: class DebugVariableDropTarget(wx.TextDropTarget): Laurent@1200: Laurent@1200: def __init__(self, parent, window): Laurent@1200: wx.TextDropTarget.__init__(self) Laurent@1200: self.ParentControl = parent Laurent@1198: self.ParentWindow = window Laurent@1200: Laurent@1198: def __del__(self): Laurent@1200: self.ParentControl = None Laurent@1198: self.ParentWindow = None Laurent@1200: Laurent@1200: def OnDragOver(self, x, y, d): Laurent@1200: self.ParentControl.OnMouseDragging(x, y) Laurent@1200: return wx.TextDropTarget.OnDragOver(self, x, y, d) Laurent@1200: Laurent@1200: def OnDropText(self, x, y, data): Laurent@1200: message = None Laurent@1200: try: Laurent@1200: values = eval(data) Laurent@1200: if not isinstance(values, TupleType): Laurent@1207: raise ValueError Laurent@1200: except: Laurent@1200: message = _("Invalid value \"%s\" for debug variable")%data Laurent@1200: values = None Laurent@1200: Laurent@1200: if message is not None: Laurent@1200: wx.CallAfter(self.ShowMessage, message) Laurent@1200: Laurent@1200: elif values[1] == "debug": Laurent@1200: width, height = self.ParentControl.GetSize() Laurent@1200: target_idx = self.ParentControl.GetIndex() Laurent@1200: merge_type = GRAPH_PARALLEL Laurent@1200: if self.ParentControl.Is3DCanvas(): Laurent@1200: if y > height / 2: Laurent@1200: target_idx += 1 Laurent@1200: if len(values) > 1 and values[2] == "move": Laurent@1200: self.ParentWindow.MoveValue(values[0], target_idx) Laurent@1200: else: Laurent@1200: self.ParentWindow.InsertValue(values[0], target_idx, force=True) Laurent@1200: Laurent@1198: else: Laurent@1200: rect = self.ParentControl.GetAxesBoundingBox() Laurent@1200: if rect.InsideXY(x, y): Laurent@1200: merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height) Laurent@1200: if merge_rect.InsideXY(x, y): Laurent@1200: merge_type = GRAPH_ORTHOGONAL Laurent@1200: wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True) Laurent@1200: else: Laurent@1200: if y > height / 2: Laurent@1200: target_idx += 1 Laurent@1200: if len(values) > 2 and values[2] == "move": Laurent@1200: self.ParentWindow.MoveValue(values[0], target_idx) Laurent@1200: else: Laurent@1200: self.ParentWindow.InsertValue(values[0], target_idx, force=True) Laurent@1200: Laurent@1200: def OnLeave(self): Laurent@1200: self.ParentWindow.ResetHighlight() Laurent@1200: return wx.TextDropTarget.OnLeave(self) Laurent@1200: Laurent@1200: def ShowMessage(self, message): Laurent@1200: dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) Laurent@1200: dialog.ShowModal() Laurent@1200: dialog.Destroy() Laurent@1200: Laurent@1200: Laurent@1200: class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas): Laurent@1198: Laurent@1198: def __init__(self, parent, window, items, graph_type): Laurent@1198: DebugVariableViewer.__init__(self, window, items) Laurent@1198: Laurent@1198: self.CanvasSize = SIZE_MINI Laurent@1198: self.GraphType = graph_type Laurent@1198: self.CursorTick = None Laurent@1198: self.MouseStartPos = None Laurent@1198: self.StartCursorTick = None Laurent@1198: self.CanvasStartSize = None Laurent@1198: self.ContextualButtons = [] Laurent@1198: self.ContextualButtonsItem = None Laurent@1198: Laurent@1198: self.Figure = matplotlib.figure.Figure(facecolor='w') Laurent@1198: self.Figure.subplotpars.update(top=0.95, left=0.1, bottom=0.1, right=0.95) Laurent@1198: Laurent@1198: FigureCanvas.__init__(self, parent, -1, self.Figure) Laurent@1198: self.SetWindowStyle(wx.WANTS_CHARS) Laurent@1198: self.SetBackgroundColour(wx.WHITE) Laurent@1198: self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) Laurent@1198: self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) Laurent@1198: self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) Laurent@1198: self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) Laurent@1198: self.Bind(wx.EVT_SIZE, self.OnResize) Laurent@1198: Laurent@1198: canvas_size = self.GetCanvasMinSize() Laurent@1198: self.SetMinSize(canvas_size) Laurent@1200: self.SetDropTarget(DebugVariableDropTarget(self, window)) Laurent@1198: self.mpl_connect('button_press_event', self.OnCanvasButtonPressed) Laurent@1198: self.mpl_connect('motion_notify_event', self.OnCanvasMotion) Laurent@1198: self.mpl_connect('button_release_event', self.OnCanvasButtonReleased) Laurent@1198: self.mpl_connect('scroll_event', self.OnCanvasScroll) Laurent@1198: Laurent@1198: for size, bitmap in zip([SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI], Laurent@1198: ["minimize_graph", "middle_graph", "maximize_graph"]): Laurent@1199: self.Buttons.append(GraphButton(0, 0, bitmap, self.GetOnChangeSizeButton(size))) Laurent@1199: for bitmap, callback in [("export_graph_mini", self.OnExportGraphButton), Laurent@1199: ("delete_graph", self.OnCloseButton)]: Laurent@1199: self.Buttons.append(GraphButton(0, 0, bitmap, callback)) Laurent@1198: Laurent@1198: self.ResetGraphics() Laurent@1198: self.RefreshLabelsPosition(canvas_size.height) Laurent@1198: self.ShowButtons(False) Laurent@1198: Laurent@1198: def draw(self, drawDC=None): Laurent@1198: FigureCanvasAgg.draw(self) Laurent@1198: Laurent@1198: self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) Laurent@1198: self.bitmap.UseAlpha() Laurent@1198: width, height = self.GetSize() Laurent@1198: bbox = self.GetAxesBoundingBox() Laurent@1198: Laurent@1198: destDC = wx.MemoryDC() Laurent@1198: destDC.SelectObject(self.bitmap) Laurent@1198: Laurent@1198: destGC = wx.GCDC(destDC) Laurent@1198: Laurent@1198: destGC.BeginDrawing() Laurent@1198: if self.Highlight == HIGHLIGHT_RESIZE: Laurent@1198: destGC.SetPen(HIGHLIGHT_RESIZE_PEN) Laurent@1198: destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH) Laurent@1198: destGC.DrawRectangle(0, height - 5, width, 5) Laurent@1198: else: Laurent@1198: destGC.SetPen(HIGHLIGHT_DROP_PEN) Laurent@1198: destGC.SetBrush(HIGHLIGHT_DROP_BRUSH) Laurent@1198: if self.Highlight == HIGHLIGHT_LEFT: Laurent@1198: destGC.DrawRectangle(bbox.x, bbox.y, Laurent@1198: bbox.width / 2, bbox.height) Laurent@1198: elif self.Highlight == HIGHLIGHT_RIGHT: Laurent@1198: destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y, Laurent@1198: bbox.width / 2, bbox.height) Laurent@1198: Laurent@1200: self.DrawCommonElements(destGC, self.GetButtons()) Laurent@1198: Laurent@1198: destGC.EndDrawing() Laurent@1198: Laurent@1198: self._isDrawn = True Laurent@1198: self.gui_repaint(drawDC=drawDC) Laurent@1198: Laurent@1198: def GetButtons(self): Laurent@1198: return self.Buttons + self.ContextualButtons Laurent@1198: Laurent@1198: def PopupContextualButtons(self, item, rect, style=wx.RIGHT): Laurent@1198: if self.ContextualButtonsItem is not None and item != self.ContextualButtonsItem: Laurent@1198: self.DismissContextualButtons() Laurent@1198: Laurent@1198: if self.ContextualButtonsItem is None: Laurent@1198: self.ContextualButtonsItem = item Laurent@945: Laurent@1198: if self.ContextualButtonsItem.IsForced(): Laurent@1198: self.ContextualButtons.append( Laurent@1199: GraphButton(0, 0, "release", self.OnReleaseButton)) Laurent@1199: for bitmap, callback in [("force", self.OnForceButton), Laurent@1199: ("export_graph_mini", self.OnExportItemGraphButton), Laurent@1199: ("delete_graph", self.OnRemoveItemButton)]: Laurent@1199: self.ContextualButtons.append(GraphButton(0, 0, bitmap, callback)) Laurent@916: Laurent@1198: offset = 0 Laurent@1198: buttons = self.ContextualButtons[:] Laurent@1198: if style in [wx.TOP, wx.LEFT]: Laurent@1198: buttons.reverse() Laurent@1198: for button in buttons: Laurent@1198: w, h = button.GetSize() Laurent@1198: if style in [wx.LEFT, wx.RIGHT]: Laurent@1200: x = rect.x + (- w - offset Laurent@1200: if style == wx.LEFT Laurent@1200: else rect.width + offset) Laurent@1198: y = rect.y + (rect.height - h) / 2 Laurent@1198: offset += w Laurent@1198: else: Laurent@1198: x = rect.x + (rect.width - w ) / 2 Laurent@1200: y = rect.y + (- h - offset Laurent@1200: if style == wx.TOP Laurent@1200: else rect.height + offset) Laurent@1198: offset += h Laurent@1198: button.SetPosition(x, y) Laurent@1198: self.ParentWindow.ForceRefresh() Laurent@1198: Laurent@1198: def DismissContextualButtons(self): Laurent@1198: if self.ContextualButtonsItem is not None: Laurent@1198: self.ContextualButtonsItem = None Laurent@1198: self.ContextualButtons = [] Laurent@1198: self.ParentWindow.ForceRefresh() Laurent@1198: Laurent@1198: def IsOverContextualButton(self, x, y): Laurent@1198: for button in self.ContextualButtons: Laurent@1198: if button.HitTest(x, y): Laurent@1198: return True Laurent@1198: return False Laurent@1198: Laurent@1198: def SetMinSize(self, size): Laurent@1198: wx.Window.SetMinSize(self, size) Laurent@1200: wx.CallAfter(self.RefreshButtonsPosition) Laurent@1198: Laurent@1198: def GetOnChangeSizeButton(self, size): Laurent@1198: def OnChangeSizeButton(): Laurent@1198: self.CanvasSize = size Laurent@1198: self.SetCanvasSize(200, self.CanvasSize) Laurent@1198: return OnChangeSizeButton Laurent@1198: Laurent@1198: def OnExportGraphButton(self): Laurent@1198: self.ExportGraph() Laurent@1198: Laurent@1198: def OnForceButton(self): Laurent@1200: self.ForceValue(self.ContextualButtonsItem) Laurent@1198: self.DismissContextualButtons() Laurent@1198: Laurent@1198: def OnReleaseButton(self): Laurent@1200: self.ReleaseValue(self.ContextualButtonsItem) Laurent@1198: self.DismissContextualButtons() Laurent@1198: Laurent@1198: def OnExportItemGraphButton(self): Laurent@1200: self.ExportGraph(self.ContextualButtonsItem) Laurent@1198: self.DismissContextualButtons() Laurent@1198: Laurent@1198: def OnRemoveItemButton(self): Laurent@1198: wx.CallAfter(self.ParentWindow.DeleteValue, self, Laurent@1198: self.ContextualButtonsItem) Laurent@1198: self.DismissContextualButtons() Laurent@1198: Laurent@1200: def OnLeave(self, event): Laurent@1200: if self.Highlight != HIGHLIGHT_RESIZE or self.CanvasStartSize is None: Laurent@1200: DebugVariableViewer.OnLeave(self, event) Laurent@1200: else: Laurent@1200: event.Skip() Laurent@1200: Laurent@1198: def RefreshLabelsPosition(self, height): Laurent@1198: canvas_ratio = 1. / height Laurent@1198: graph_ratio = 1. / ((1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) * height) Laurent@1198: Laurent@1198: self.Figure.subplotpars.update( Laurent@1198: top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, Laurent@1198: bottom= CANVAS_BORDER[0] * canvas_ratio) Laurent@1198: Laurent@1198: if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas(): Laurent@1198: num_item = len(self.Items) Laurent@1198: for idx in xrange(num_item): Laurent@1198: if not self.Is3DCanvas(): Laurent@1198: self.AxesLabels[idx].set_position( Laurent@1198: (0.05, Laurent@1198: 1.0 - (CANVAS_PADDING + AXES_LABEL_HEIGHT * idx) * graph_ratio)) Laurent@1198: self.Labels[idx].set_position( Laurent@1198: (0.95, Laurent@1198: CANVAS_PADDING * graph_ratio + Laurent@1198: (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio)) Laurent@1198: else: Laurent@1198: self.AxesLabels[0].set_position((0.1, CANVAS_PADDING * graph_ratio)) Laurent@1198: self.Labels[0].set_position((0.95, CANVAS_PADDING * graph_ratio)) Laurent@1198: self.AxesLabels[1].set_position((0.05, 2 * CANVAS_PADDING * graph_ratio)) Laurent@1198: self.Labels[1].set_position((0.05, 1.0 - CANVAS_PADDING * graph_ratio)) Laurent@1198: Laurent@1198: self.Figure.subplots_adjust() Laurent@1198: Laurent@1198: def GetCanvasMinSize(self): Laurent@1198: return wx.Size(200, Laurent@1198: CANVAS_BORDER[0] + CANVAS_BORDER[1] + Laurent@1198: 2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items)) Laurent@1198: Laurent@1198: def SetCanvasSize(self, width, height): Laurent@1198: height = max(height, self.GetCanvasMinSize()[1]) Laurent@1198: self.SetMinSize(wx.Size(width, height)) Laurent@1198: self.RefreshLabelsPosition(height) Laurent@1200: self.RefreshButtonsPosition() Laurent@1198: self.ParentWindow.RefreshGraphicsSizer() Laurent@1198: Laurent@1198: def GetAxesBoundingBox(self, absolute=False): Laurent@1198: width, height = self.GetSize() Laurent@1198: ax, ay, aw, ah = self.figure.gca().get_position().bounds Laurent@1198: bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1, Laurent@1198: aw * width + 2, ah * height + 1) Laurent@1198: if absolute: Laurent@1198: xw, yw = self.GetPosition() Laurent@1198: bbox.x += xw Laurent@1198: bbox.y += yw Laurent@1198: return bbox Laurent@1198: Laurent@1198: def OnCanvasButtonPressed(self, event): Laurent@1198: width, height = self.GetSize() Laurent@1198: x, y = event.x, height - event.y Laurent@1198: if not self.IsOverButton(x, y): Laurent@1198: if event.inaxes == self.Axes: Laurent@1198: item_idx = None Laurent@1198: for i, t in ([pair for pair in enumerate(self.AxesLabels)] + Laurent@1198: [pair for pair in enumerate(self.Labels)]): Laurent@1198: (x0, y0), (x1, y1) = t.get_window_extent().get_points() Laurent@1198: rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) Laurent@1198: if rect.InsideXY(x, y): Laurent@1198: item_idx = i Laurent@1198: break Laurent@1198: if item_idx is not None: Laurent@1198: self.ShowButtons(False) Laurent@1198: self.DismissContextualButtons() Laurent@1198: xw, yw = self.GetPosition() Laurent@1198: self.ParentWindow.StartDragNDrop(self, Laurent@1200: self.ItemsDict.values()[item_idx], x + xw, y + yw, x + xw, y + yw) Laurent@1198: elif not self.Is3DCanvas(): Laurent@1198: self.MouseStartPos = wx.Point(x, y) Laurent@1198: if event.button == 1 and event.inaxes == self.Axes: Laurent@1198: self.StartCursorTick = self.CursorTick Laurent@1198: self.HandleCursorMove(event) Laurent@1198: elif event.button == 2 and self.GraphType == GRAPH_PARALLEL: Laurent@1198: width, height = self.GetSize() Laurent@1198: start_tick, end_tick = self.ParentWindow.GetRange() Laurent@1198: self.StartCursorTick = start_tick Laurent@928: Laurent@1198: elif event.button == 1 and event.y <= 5: Laurent@1198: self.MouseStartPos = wx.Point(x, y) Laurent@1198: self.CanvasStartSize = self.GetSize() Laurent@1198: Laurent@1198: def OnCanvasButtonReleased(self, event): Laurent@1198: if self.ParentWindow.IsDragging(): Laurent@943: width, height = self.GetSize() Laurent@943: xw, yw = self.GetPosition() Laurent@1198: self.ParentWindow.StopDragNDrop( Laurent@1200: self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0].GetVariable(), Laurent@1198: xw + event.x, Laurent@1198: yw + height - event.y) Laurent@1198: else: Laurent@943: self.MouseStartPos = None Laurent@943: self.StartCursorTick = None Laurent@943: self.CanvasStartSize = None Laurent@928: width, height = self.GetSize() Laurent@1200: self.HandleButton(event.x, height - event.y) Laurent@1198: if event.y > 5 and self.SetHighlight(HIGHLIGHT_NONE): Laurent@943: self.SetCursor(wx.NullCursor) Laurent@937: self.ParentWindow.ForceRefresh() Laurent@1198: Laurent@1198: def OnCanvasMotion(self, event): Laurent@1198: width, height = self.GetSize() Laurent@1198: if self.ParentWindow.IsDragging(): Laurent@1198: xw, yw = self.GetPosition() Laurent@1198: self.ParentWindow.MoveDragNDrop( Laurent@1198: xw + event.x, Laurent@1198: yw + height - event.y) Laurent@1198: else: Laurent@1198: if not self.Is3DCanvas(): Laurent@1198: if event.button == 1 and self.CanvasStartSize is None: Laurent@1198: if event.inaxes == self.Axes: Laurent@1198: if self.MouseStartPos is not None: Laurent@1198: self.HandleCursorMove(event) Laurent@1198: elif self.MouseStartPos is not None and len(self.Items) == 1: Laurent@1198: xw, yw = self.GetPosition() Laurent@1198: self.ParentWindow.SetCursorTick(self.StartCursorTick) Laurent@1198: self.ParentWindow.StartDragNDrop(self, Laurent@1200: self.ItemsDict.values()[0], Laurent@1198: event.x + xw, height - event.y + yw, Laurent@1198: self.MouseStartPos.x + xw, self.MouseStartPos.y + yw) Laurent@1198: elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and self.MouseStartPos is not None: Laurent@1198: start_tick, end_tick = self.ParentWindow.GetRange() Laurent@1198: rect = self.GetAxesBoundingBox() Laurent@1198: self.ParentWindow.SetCanvasPosition( Laurent@1198: self.StartCursorTick + (self.MouseStartPos.x - event.x) * Laurent@1198: (end_tick - start_tick) / rect.width) Laurent@1198: Laurent@1198: if event.button == 1 and self.CanvasStartSize is not None: Laurent@1198: width, height = self.GetSize() Laurent@1198: self.SetCanvasSize(width, Laurent@1198: self.CanvasStartSize.height + height - event.y - self.MouseStartPos.y) Laurent@1198: Laurent@1198: elif event.button in [None, "up", "down"]: Laurent@1198: if self.GraphType == GRAPH_PARALLEL: Laurent@1198: orientation = [wx.RIGHT] * len(self.AxesLabels) + [wx.LEFT] * len(self.Labels) Laurent@1198: elif len(self.AxesLabels) > 0: Laurent@1198: orientation = [wx.RIGHT, wx.TOP, wx.LEFT, wx.BOTTOM] Laurent@1198: else: Laurent@1198: orientation = [wx.LEFT] * len(self.Labels) Laurent@1198: item_idx = None Laurent@1198: item_style = None Laurent@1198: for (i, t), style in zip([pair for pair in enumerate(self.AxesLabels)] + Laurent@1198: [pair for pair in enumerate(self.Labels)], Laurent@1198: orientation): Laurent@1198: (x0, y0), (x1, y1) = t.get_window_extent().get_points() Laurent@1198: rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) Laurent@1198: if rect.InsideXY(event.x, height - event.y): Laurent@1198: item_idx = i Laurent@1198: item_style = style Laurent@1198: break Laurent@1198: if item_idx is not None: Laurent@1200: self.PopupContextualButtons(self.ItemsDict.values()[item_idx], rect, item_style) Laurent@1198: return Laurent@1198: if not self.IsOverContextualButton(event.x, height - event.y): Laurent@1198: self.DismissContextualButtons() Laurent@1198: Laurent@1198: if event.y <= 5: Laurent@1198: if self.SetHighlight(HIGHLIGHT_RESIZE): Laurent@1198: self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS)) Laurent@1198: self.ParentWindow.ForceRefresh() Laurent@1198: else: Laurent@1198: if self.SetHighlight(HIGHLIGHT_NONE): Laurent@1198: self.SetCursor(wx.NullCursor) Laurent@1198: self.ParentWindow.ForceRefresh() Laurent@1198: Laurent@1198: def OnCanvasScroll(self, event): Laurent@1198: if event.inaxes is not None and event.guiEvent.ControlDown(): Laurent@928: if self.GraphType == GRAPH_ORTHOGONAL: Laurent@1198: start_tick, end_tick = self.ParentWindow.GetRange() Laurent@1198: tick = (start_tick + end_tick) / 2. Laurent@928: else: Laurent@1198: tick = event.xdata Laurent@1198: self.ParentWindow.ChangeRange(int(-event.step) / 3, tick) Laurent@1198: self.ParentWindow.VetoScrollEvent = True Laurent@1198: Laurent@1200: def RefreshHighlight(self, x, y): Laurent@1198: width, height = self.GetSize() Laurent@1198: bbox = self.GetAxesBoundingBox() Laurent@1198: if bbox.InsideXY(x, y) and not self.Is3DCanvas(): Laurent@1198: rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height) Laurent@1198: if rect.InsideXY(x, y): Laurent@1198: self.SetHighlight(HIGHLIGHT_LEFT) Laurent@916: else: Laurent@1198: self.SetHighlight(HIGHLIGHT_RIGHT) Laurent@1198: elif y < height / 2: Laurent@1198: if self.ParentWindow.IsViewerFirst(self): Laurent@1198: self.SetHighlight(HIGHLIGHT_BEFORE) Laurent@930: else: Laurent@1198: self.SetHighlight(HIGHLIGHT_NONE) Laurent@1198: self.ParentWindow.HighlightPreviousViewer(self) Laurent@1198: else: Laurent@1198: self.SetHighlight(HIGHLIGHT_AFTER) Laurent@1198: Laurent@1198: def OnLeave(self, event): Laurent@1198: if self.CanvasStartSize is None and self.SetHighlight(HIGHLIGHT_NONE): Laurent@1198: self.SetCursor(wx.NullCursor) Laurent@1198: self.ParentWindow.ForceRefresh() Laurent@1198: DebugVariableViewer.OnLeave(self, event) Laurent@1198: Laurent@1198: KEY_CURSOR_INCREMENT = { Laurent@1198: wx.WXK_LEFT: -1, Laurent@1198: wx.WXK_RIGHT: 1, Laurent@1198: wx.WXK_UP: -10, Laurent@1198: wx.WXK_DOWN: 10} Laurent@1198: def OnKeyDown(self, event): Laurent@1198: if self.CursorTick is not None: Laurent@1198: move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None) Laurent@1198: if move is not None: Laurent@1198: self.ParentWindow.MoveCursorTick(move) Laurent@1198: event.Skip() Laurent@1198: Laurent@1198: def HandleCursorMove(self, event): Laurent@1198: start_tick, end_tick = self.ParentWindow.GetRange() Laurent@1198: cursor_tick = None Laurent@1200: items = self.ItemsDict.values() Laurent@1198: if self.GraphType == GRAPH_ORTHOGONAL: Laurent@1200: x_data = items[0].GetData(start_tick, end_tick) Laurent@1200: y_data = items[1].GetData(start_tick, end_tick) Laurent@1198: if len(x_data) > 0 and len(y_data) > 0: Laurent@1198: length = min(len(x_data), len(y_data)) Laurent@1198: d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2) Laurent@1198: cursor_tick = x_data[numpy.argmin(d), 0] Laurent@1198: else: Laurent@1200: data = items[0].GetData(start_tick, end_tick) Laurent@1198: if len(data) > 0: Laurent@1198: cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0] Laurent@1198: if cursor_tick is not None: Laurent@1198: self.ParentWindow.SetCursorTick(cursor_tick) Laurent@1198: Laurent@1198: def OnAxesMotion(self, event): Laurent@1198: if self.Is3DCanvas(): Laurent@1198: current_time = gettime() Laurent@1198: if current_time - self.LastMotionTime > REFRESH_PERIOD: Laurent@1198: self.LastMotionTime = current_time Laurent@1198: Axes3D._on_move(self.Axes, event) Laurent@1198: Laurent@1198: def ResetGraphics(self): Laurent@1198: self.Figure.clear() Laurent@1198: if self.Is3DCanvas(): Laurent@1198: self.Axes = self.Figure.gca(projection='3d') Laurent@1198: self.Axes.set_color_cycle(['b']) Laurent@1198: self.LastMotionTime = gettime() Laurent@1198: setattr(self.Axes, "_on_move", self.OnAxesMotion) Laurent@1198: self.Axes.mouse_init() Laurent@1198: self.Axes.tick_params(axis='z', labelsize='small') Laurent@1198: else: Laurent@1198: self.Axes = self.Figure.gca() Laurent@1200: self.Axes.set_color_cycle(COLOR_CYCLE) Laurent@1198: self.Axes.tick_params(axis='x', labelsize='small') Laurent@1198: self.Axes.tick_params(axis='y', labelsize='small') Laurent@1198: self.Plots = [] Laurent@1198: self.VLine = None Laurent@1198: self.HLine = None Laurent@1198: self.Labels = [] Laurent@1198: self.AxesLabels = [] Laurent@1198: if not self.Is3DCanvas(): Laurent@1198: text_func = self.Axes.text Laurent@1198: else: Laurent@1198: text_func = self.Axes.text2D Laurent@1198: if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas(): Laurent@1198: num_item = len(self.Items) Laurent@1198: for idx in xrange(num_item): Laurent@1198: if num_item == 1: Laurent@1198: color = 'k' Laurent@1198: else: Laurent@1200: color = COLOR_CYCLE[idx % len(COLOR_CYCLE)] Laurent@1198: if not self.Is3DCanvas(): Laurent@1198: self.AxesLabels.append( Laurent@1198: text_func(0, 0, "", size='small', Laurent@1198: verticalalignment='top', Laurent@930: color=color, Laurent@929: transform=self.Axes.transAxes)) Laurent@1198: self.Labels.append( Laurent@1198: text_func(0, 0, "", size='large', Laurent@1198: horizontalalignment='right', Laurent@1198: color=color, Laurent@1198: transform=self.Axes.transAxes)) Laurent@1198: else: Laurent@1198: self.AxesLabels.append( Laurent@1198: self.Axes.text(0, 0, "", size='small', Laurent@1198: transform=self.Axes.transAxes)) Laurent@1198: self.Labels.append( Laurent@1198: self.Axes.text(0, 0, "", size='large', Laurent@1198: horizontalalignment='right', Laurent@1198: transform=self.Axes.transAxes)) Laurent@1198: self.AxesLabels.append( Laurent@1198: self.Axes.text(0, 0, "", size='small', Laurent@1198: rotation='vertical', Laurent@1198: verticalalignment='bottom', Laurent@1198: transform=self.Axes.transAxes)) Laurent@1198: self.Labels.append( Laurent@1198: self.Axes.text(0, 0, "", size='large', Laurent@1198: rotation='vertical', Laurent@1198: verticalalignment='top', Laurent@1198: transform=self.Axes.transAxes)) Laurent@1198: width, height = self.GetSize() Laurent@1198: self.RefreshLabelsPosition(height) Laurent@1198: Laurent@1198: def AddItem(self, item): Laurent@1198: DebugVariableViewer.AddItem(self, item) Laurent@1198: self.ResetGraphics() Laurent@1198: Laurent@1198: def RemoveItem(self, item): Laurent@1198: DebugVariableViewer.RemoveItem(self, item) Laurent@1200: if not self.ItemsIsEmpty(): Laurent@1198: if len(self.Items) == 1: Laurent@1198: self.GraphType = GRAPH_PARALLEL Laurent@1198: self.ResetGraphics() Laurent@1198: Laurent@1207: def SubscribeAllDataConsumers(self): Laurent@1207: DebugVariableViewer.SubscribeAllDataConsumers(self) Laurent@1200: if not self.ItemsIsEmpty(): Laurent@1198: self.ResetGraphics() Laurent@1198: Laurent@1198: def Is3DCanvas(self): Laurent@1198: return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3 Laurent@1198: Laurent@1198: def SetCursorTick(self, cursor_tick): Laurent@1198: self.CursorTick = cursor_tick Laurent@1198: Laurent@1198: def RefreshViewer(self, refresh_graphics=True): Laurent@1198: Laurent@1198: if refresh_graphics: Laurent@1198: start_tick, end_tick = self.ParentWindow.GetRange() Laurent@1198: Laurent@1198: if self.GraphType == GRAPH_PARALLEL: Laurent@1198: min_value = max_value = None Laurent@1198: Laurent@1198: for idx, item in enumerate(self.Items): Laurent@1198: data = item.GetData(start_tick, end_tick) Laurent@1198: if data is not None: Laurent@1198: item_min_value, item_max_value = item.GetValueRange() Laurent@1198: if min_value is None: Laurent@1198: min_value = item_min_value Laurent@1198: elif item_min_value is not None: Laurent@1198: min_value = min(min_value, item_min_value) Laurent@1198: if max_value is None: Laurent@1198: max_value = item_max_value Laurent@1198: elif item_max_value is not None: Laurent@1198: max_value = max(max_value, item_max_value) Laurent@1198: Laurent@1198: if len(self.Plots) <= idx: Laurent@1198: self.Plots.append( Laurent@1198: self.Axes.plot(data[:, 0], data[:, 1])[0]) Laurent@1198: else: Laurent@1198: self.Plots[idx].set_data(data[:, 0], data[:, 1]) Laurent@1198: Laurent@1198: if min_value is not None and max_value is not None: Laurent@1198: y_center = (min_value + max_value) / 2. Laurent@1198: y_range = max(1.0, max_value - min_value) Laurent@1198: else: Laurent@1198: y_center = 0.5 Laurent@1198: y_range = 1.0 Laurent@1198: x_min, x_max = start_tick, end_tick Laurent@1198: y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55 Laurent@1198: Laurent@1198: if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick: Laurent@1198: if self.VLine is None: Laurent@1200: self.VLine = self.Axes.axvline(self.CursorTick, color=CURSOR_COLOR) Laurent@1198: else: Laurent@1198: self.VLine.set_xdata((self.CursorTick, self.CursorTick)) Laurent@1198: self.VLine.set_visible(True) Laurent@1198: else: Laurent@1198: if self.VLine is not None: Laurent@1198: self.VLine.set_visible(False) Laurent@929: else: Laurent@1198: min_start_tick = reduce(max, [item.GetData()[0, 0] Laurent@1198: for item in self.Items Laurent@1198: if len(item.GetData()) > 0], 0) Laurent@1198: start_tick = max(start_tick, min_start_tick) Laurent@1198: end_tick = max(end_tick, min_start_tick) Laurent@1200: items = self.ItemsDict.values() Laurent@1200: x_data, x_min, x_max = OrthogonalData(items[0], start_tick, end_tick) Laurent@1200: y_data, y_min, y_max = OrthogonalData(items[1], start_tick, end_tick) Laurent@1198: if self.CursorTick is not None: Laurent@1200: x_cursor, x_forced = items[0].GetValue(self.CursorTick, raw=True) Laurent@1200: y_cursor, y_forced = items[1].GetValue(self.CursorTick, raw=True) Laurent@1198: length = 0 Laurent@1198: if x_data is not None and y_data is not None: Laurent@1198: length = min(len(x_data), len(y_data)) Laurent@1198: if len(self.Items) < 3: Laurent@1198: if x_data is not None and y_data is not None: Laurent@1198: if len(self.Plots) == 0: Laurent@1198: self.Plots.append( Laurent@1198: self.Axes.plot(x_data[:, 1][:length], Laurent@1198: y_data[:, 1][:length])[0]) Laurent@1198: else: Laurent@1198: self.Plots[0].set_data( Laurent@1198: x_data[:, 1][:length], Laurent@1198: y_data[:, 1][:length]) Laurent@916: Laurent@928: if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick: Laurent@924: if self.VLine is None: Laurent@1200: self.VLine = self.Axes.axvline(x_cursor, color=CURSOR_COLOR) Laurent@924: else: Laurent@1198: self.VLine.set_xdata((x_cursor, x_cursor)) Laurent@1198: if self.HLine is None: Laurent@1200: self.HLine = self.Axes.axhline(y_cursor, color=CURSOR_COLOR) Laurent@1198: else: Laurent@1198: self.HLine.set_ydata((y_cursor, y_cursor)) Laurent@924: self.VLine.set_visible(True) Laurent@1198: self.HLine.set_visible(True) Laurent@924: else: Laurent@924: if self.VLine is not None: Laurent@924: self.VLine.set_visible(False) Laurent@1198: if self.HLine is not None: Laurent@1198: self.HLine.set_visible(False) Laurent@916: else: Laurent@1198: while len(self.Axes.lines) > 0: Laurent@1198: self.Axes.lines.pop() Laurent@1200: z_data, z_min, z_max = OrthogonalData(items[2], start_tick, end_tick) Laurent@924: if self.CursorTick is not None: Laurent@1200: z_cursor, z_forced = items[2].GetValue(self.CursorTick, raw=True) Laurent@1198: if x_data is not None and y_data is not None and z_data is not None: Laurent@1198: length = min(length, len(z_data)) Laurent@1198: self.Axes.plot(x_data[:, 1][:length], Laurent@1198: y_data[:, 1][:length], Laurent@1198: zs = z_data[:, 1][:length]) Laurent@1198: self.Axes.set_zlim(z_min, z_max) Laurent@1198: if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick: Laurent@1198: for kwargs in [{"xs": numpy.array([x_min, x_max])}, Laurent@1198: {"ys": numpy.array([y_min, y_max])}, Laurent@1198: {"zs": numpy.array([z_min, z_max])}]: Laurent@1198: for param, value in [("xs", numpy.array([x_cursor, x_cursor])), Laurent@1198: ("ys", numpy.array([y_cursor, y_cursor])), Laurent@1198: ("zs", numpy.array([z_cursor, z_cursor]))]: Laurent@1198: kwargs.setdefault(param, value) Laurent@1200: kwargs["color"] = CURSOR_COLOR Laurent@1198: self.Axes.plot(**kwargs) Laurent@1198: Laurent@1198: self.Axes.set_xlim(x_min, x_max) Laurent@1198: self.Axes.set_ylim(y_min, y_max) Laurent@1198: Laurent@1198: variable_name_mask = self.ParentWindow.GetVariableNameMask() Laurent@1198: if self.CursorTick is not None: Laurent@1198: values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items]) Laurent@1198: else: Laurent@1198: values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items]) Laurent@1198: labels = [item.GetVariable(variable_name_mask) for item in self.Items] Laurent@1198: styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced) Laurent@1198: if self.Is3DCanvas(): Laurent@1198: for idx, label_func in enumerate([self.Axes.set_xlabel, Laurent@1198: self.Axes.set_ylabel, Laurent@1198: self.Axes.set_zlabel]): Laurent@1200: label_func(labels[idx], fontdict={'size': 'small','color': COLOR_CYCLE[idx]}) Laurent@1198: else: Laurent@1198: for label, text in zip(self.AxesLabels, labels): Laurent@1198: label.set_text(text) Laurent@1198: for label, value, style in zip(self.Labels, values, styles): Laurent@1198: label.set_text(value) Laurent@1198: label.set_style(style) Laurent@1198: Laurent@1198: self.draw() Laurent@1198: Laurent@1198: def ExportGraph(self, item=None): Laurent@1198: if item is not None: Laurent@1198: variables = [(item, [entry for entry in item.GetData()])] Laurent@1198: else: Laurent@1198: variables = [(item, [entry for entry in item.GetData()]) Laurent@1198: for item in self.Items] Laurent@1198: self.ParentWindow.CopyDataToClipboard(variables)