controls/DebugVariablePanel/DebugVariableGraphicViewer.py
changeset 1209 953a8f14040a
parent 1207 fb9799a0c0f7
child 1212 b351d3a7917c
equal deleted inserted replaced
1208:d22fcdfae8d0 1209:953a8f14040a
    40 
    40 
    41 from DebugVariableItem import DebugVariableItem
    41 from DebugVariableItem import DebugVariableItem
    42 from DebugVariableViewer import *
    42 from DebugVariableViewer import *
    43 from GraphButton import GraphButton
    43 from GraphButton import GraphButton
    44 
    44 
       
    45 # Graph variable display type
    45 GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
    46 GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
    46 
    47 
    47 #CANVAS_SIZE_TYPES
    48 # Canvas height
    48 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
    49 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
    49 
    50 
    50 DEFAULT_CANVAS_HEIGHT = 200.
    51 CANVAS_BORDER = (20., 10.) # Border height on at bottom and top of graph
    51 CANVAS_BORDER = (20., 10.)
    52 CANVAS_PADDING = 8.5       # Border inside graph where no label is drawn
    52 CANVAS_PADDING = 8.5
    53 VALUE_LABEL_HEIGHT = 17.   # Height of variable label in graph
    53 VALUE_LABEL_HEIGHT = 17.
    54 AXES_LABEL_HEIGHT = 12.75  # Height of variable value in graph
    54 AXES_LABEL_HEIGHT = 12.75
    55 
    55 
    56 # Colors used cyclically for graph curves
    56 COLOR_CYCLE = ['r', 'b', 'g', 'm', 'y', 'k']
    57 COLOR_CYCLE = ['r', 'b', 'g', 'm', 'y', 'k']
       
    58 # Color for graph cursor
    57 CURSOR_COLOR = '#800080'
    59 CURSOR_COLOR = '#800080'
    58 
    60 
    59 def OrthogonalData(item, start_tick, end_tick):
    61 def OrthogonalDataAndRange(item, start_tick, end_tick):
    60     data = item.GetData(start_tick, end_tick)
    62     data = item.GetData(start_tick, end_tick)
    61     min_value, max_value = item.GetValueRange()
    63     min_value, max_value = item.GetValueRange()
    62     if min_value is not None and max_value is not None:
    64     if min_value is not None and max_value is not None:
    63         center = (min_value + max_value) / 2.
    65         center = (min_value + max_value) / 2.
    64         range = max(1.0, max_value - min_value)
    66         range = max(1.0, max_value - min_value)
    65     else:
    67     else:
    66         center = 0.5
    68         center = 0.5
    67         range = 1.0
    69         range = 1.0
    68     return data, center - range * 0.55, center + range * 0.55
    70     return data, center - range * 0.55, center + range * 0.55
    69 
    71 
    70 class DebugVariableDropTarget(wx.TextDropTarget):
    72 #-------------------------------------------------------------------------------
       
    73 #                   Debug Variable Graphic Viewer Drop Target
       
    74 #-------------------------------------------------------------------------------
       
    75 
       
    76 """
       
    77 Class that implements a custom drop target class for Debug Variable Graphic Viewer
       
    78 """
       
    79 
       
    80 class DebugVariableGraphicDropTarget(wx.TextDropTarget):
    71     
    81     
    72     def __init__(self, parent, window):
    82     def __init__(self, parent, window):
       
    83         """
       
    84         Constructor
       
    85         @param parent: Reference to Debug Variable Graphic Viewer
       
    86         @param window: Reference to the Debug Variable Panel
       
    87         """
    73         wx.TextDropTarget.__init__(self)
    88         wx.TextDropTarget.__init__(self)
    74         self.ParentControl = parent
    89         self.ParentControl = parent
    75         self.ParentWindow = window
    90         self.ParentWindow = window
    76         
    91         
    77     def __del__(self):
    92     def __del__(self):
       
    93         """
       
    94         Destructor
       
    95         """
       
    96         # Remove reference to Debug Variable Graphic Viewer and Debug Variable
       
    97         # Panel
    78         self.ParentControl = None
    98         self.ParentControl = None
    79         self.ParentWindow = None
    99         self.ParentWindow = None
    80         
   100         
    81     def OnDragOver(self, x, y, d):
   101     def OnDragOver(self, x, y, d):
       
   102         """
       
   103         Function called when mouse is dragged over Drop Target
       
   104         @param x: X coordinate of mouse pointer
       
   105         @param y: Y coordinate of mouse pointer
       
   106         @param d: Suggested default for return value
       
   107         """
       
   108         # Signal parent that mouse is dragged over
    82         self.ParentControl.OnMouseDragging(x, y)
   109         self.ParentControl.OnMouseDragging(x, y)
       
   110         
    83         return wx.TextDropTarget.OnDragOver(self, x, y, d)
   111         return wx.TextDropTarget.OnDragOver(self, x, y, d)
    84         
   112         
    85     def OnDropText(self, x, y, data):
   113     def OnDropText(self, x, y, data):
       
   114         """
       
   115         Function called when mouse is dragged over Drop Target
       
   116         @param x: X coordinate of mouse pointer
       
   117         @param y: Y coordinate of mouse pointer
       
   118         @param data: Text associated to drag'n drop
       
   119         """
    86         message = None
   120         message = None
       
   121         
       
   122         # Check that data is valid regarding DebugVariablePanel
    87         try:
   123         try:
    88             values = eval(data)
   124             values = eval(data)
    89             if not isinstance(values, TupleType):
   125             if not isinstance(values, TupleType):
    90                 raise ValueError
   126                 raise ValueError
    91         except:
   127         except:
    92             message = _("Invalid value \"%s\" for debug variable")%data
   128             message = _("Invalid value \"%s\" for debug variable")%data
    93             values = None
   129             values = None
    94         
   130         
       
   131         # Display message if data is invalid
    95         if message is not None:
   132         if message is not None:
    96             wx.CallAfter(self.ShowMessage, message)
   133             wx.CallAfter(self.ShowMessage, message)
    97         
   134         
       
   135         # Data contain a reference to a variable to debug
    98         elif values[1] == "debug":
   136         elif values[1] == "debug":
    99             width, height = self.ParentControl.GetSize()
       
   100             target_idx = self.ParentControl.GetIndex()
   137             target_idx = self.ParentControl.GetIndex()
   101             merge_type = GRAPH_PARALLEL
   138             
   102             if self.ParentControl.Is3DCanvas():
   139             # If mouse is dropped in graph canvas bounding box and graph is
       
   140             # not 3D canvas, graphs will be merged
       
   141             rect = self.ParentControl.GetAxesBoundingBox()
       
   142             if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
       
   143                 # Default merge type is parallel
       
   144                 merge_type = GRAPH_PARALLEL
       
   145                 
       
   146                 # If mouse is dropped in left part of graph canvas, graph
       
   147                 # wall be merged orthogonally
       
   148                 merge_rect = wx.Rect(rect.x, rect.y, 
       
   149                                      rect.width / 2., rect.height)
       
   150                 if merge_rect.InsideXY(x, y):
       
   151                     merge_type = GRAPH_ORTHOGONAL
       
   152                 
       
   153                 # Merge graphs
       
   154                 wx.CallAfter(self.ParentWindow.MergeGraphs, 
       
   155                              values[0], target_idx, 
       
   156                              merge_type, force=True)
       
   157                 
       
   158             else:
       
   159                 width, height = self.ParentControl.GetSize()
       
   160                 
       
   161                 # Get Before which Viewer the variable has to be moved or added
       
   162                 # according to the position of mouse in Viewer.
   103                 if y > height / 2:
   163                 if y > height / 2:
   104                     target_idx += 1
   164                     target_idx += 1
   105                 if len(values) > 1 and values[2] == "move":
   165                 
   106                     self.ParentWindow.MoveValue(values[0], target_idx)
   166                 # Drag'n Drop is an internal is an internal move inside Debug
       
   167                 # Variable Panel 
       
   168                 if len(values) > 2 and values[2] == "move":
       
   169                     self.ParentWindow.MoveValue(values[0], 
       
   170                                                 target_idx)
       
   171                 
       
   172                 # Drag'n Drop was initiated by another control of Beremiz
   107                 else:
   173                 else:
   108                     self.ParentWindow.InsertValue(values[0], target_idx, force=True)
   174                     self.ParentWindow.InsertValue(values[0], 
   109                 
   175                                                   target_idx, 
   110             else:
   176                                                   force=True)
   111                 rect = self.ParentControl.GetAxesBoundingBox()
       
   112                 if rect.InsideXY(x, y):
       
   113                     merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
       
   114                     if merge_rect.InsideXY(x, y):
       
   115                         merge_type = GRAPH_ORTHOGONAL
       
   116                     wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
       
   117                 else:
       
   118                     if y > height / 2:
       
   119                         target_idx += 1
       
   120                     if len(values) > 2 and values[2] == "move":
       
   121                         self.ParentWindow.MoveValue(values[0], target_idx)
       
   122                     else:
       
   123                         self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
   124     
   177     
   125     def OnLeave(self):
   178     def OnLeave(self):
       
   179         """
       
   180         Function called when mouse is leave Drop Target
       
   181         """
       
   182         # Signal Debug Variable Panel to reset highlight
   126         self.ParentWindow.ResetHighlight()
   183         self.ParentWindow.ResetHighlight()
   127         return wx.TextDropTarget.OnLeave(self)
   184         return wx.TextDropTarget.OnLeave(self)
   128     
   185     
   129     def ShowMessage(self, message):
   186     def ShowMessage(self, message):
       
   187         """
       
   188         Show error message in Error Dialog
       
   189         @param message: Error message to display
       
   190         """
   130         dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
   191         dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
   131         dialog.ShowModal()
   192         dialog.ShowModal()
   132         dialog.Destroy()
   193         dialog.Destroy()
   133 
   194 
   134 
   195 
       
   196 #-------------------------------------------------------------------------------
       
   197 #                      Debug Variable Graphic Viewer Class
       
   198 #-------------------------------------------------------------------------------
       
   199 
       
   200 """
       
   201 Class that implements a Viewer that display variable values as a graphs
       
   202 """
       
   203 
   135 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
   204 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
   136     
   205     
   137     def __init__(self, parent, window, items, graph_type):
   206     def __init__(self, parent, window, items, graph_type):
       
   207         """
       
   208         Constructor
       
   209         @param parent: Parent wx.Window of DebugVariableText
       
   210         @param window: Reference to the Debug Variable Panel
       
   211         @param items: List of DebugVariableItem displayed by Viewer
       
   212         @param graph_type: Graph display type (Parallel or orthogonal)
       
   213         """
   138         DebugVariableViewer.__init__(self, window, items)
   214         DebugVariableViewer.__init__(self, window, items)
   139         
   215         
   140         self.CanvasSize = SIZE_MINI
   216         self.GraphType = graph_type        # Graph type display
   141         self.GraphType = graph_type
   217         self.CursorTick = None             # Tick of the graph cursor
   142         self.CursorTick = None
   218         
       
   219         # Mouse position when start dragging
   143         self.MouseStartPos = None
   220         self.MouseStartPos = None
       
   221         # Tick when moving tick start
   144         self.StartCursorTick = None
   222         self.StartCursorTick = None
   145         self.CanvasStartSize = None
   223         # Canvas size when starting to resize canvas
       
   224         self.CanvasStartSize = None        
       
   225         
       
   226         # List of current displayed contextual buttons
   146         self.ContextualButtons = []
   227         self.ContextualButtons = []
       
   228         # Reference to item for which contextual buttons was displayed
   147         self.ContextualButtonsItem = None
   229         self.ContextualButtonsItem = None
   148         
   230         
       
   231         # Create figure for drawing graphs
   149         self.Figure = matplotlib.figure.Figure(facecolor='w')
   232         self.Figure = matplotlib.figure.Figure(facecolor='w')
   150         self.Figure.subplotpars.update(top=0.95, left=0.1, bottom=0.1, right=0.95)
   233         # Defined border around figure in canvas
       
   234         self.Figure.subplotpars.update(top=0.95, left=0.1, 
       
   235                                        bottom=0.1, right=0.95)
   151         
   236         
   152         FigureCanvas.__init__(self, parent, -1, self.Figure)
   237         FigureCanvas.__init__(self, parent, -1, self.Figure)
   153         self.SetWindowStyle(wx.WANTS_CHARS)
   238         self.SetWindowStyle(wx.WANTS_CHARS)
   154         self.SetBackgroundColour(wx.WHITE)
   239         self.SetBackgroundColour(wx.WHITE)
       
   240         
       
   241         # Bind wx events
   155         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
   242         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
   156         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
   243         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
   157         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
   244         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
   158         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
   245         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
   159         self.Bind(wx.EVT_SIZE, self.OnResize)
   246         self.Bind(wx.EVT_SIZE, self.OnResize)
   160         
   247         
       
   248         # Set canvas min size
   161         canvas_size = self.GetCanvasMinSize()
   249         canvas_size = self.GetCanvasMinSize()
   162         self.SetMinSize(canvas_size)
   250         self.SetMinSize(canvas_size)
   163         self.SetDropTarget(DebugVariableDropTarget(self, window))
   251         
       
   252         # Define Viewer drop target
       
   253         self.SetDropTarget(DebugVariableGraphicDropTarget(self, window))
       
   254         
       
   255         # Connect matplotlib events
   164         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
   256         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
   165         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
   257         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
   166         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
   258         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
   167         self.mpl_connect('scroll_event', self.OnCanvasScroll)
   259         self.mpl_connect('scroll_event', self.OnCanvasScroll)
   168         
   260         
   169         for size, bitmap in zip([SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
   261         # Add buttons for changing canvas size with predefined height
   170                                 ["minimize_graph", "middle_graph", "maximize_graph"]):
   262         for size, bitmap in zip(
   171             self.Buttons.append(GraphButton(0, 0, bitmap, self.GetOnChangeSizeButton(size)))
   263                 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
   172         for bitmap, callback in [("export_graph_mini", self.OnExportGraphButton),
   264                 ["minimize_graph", "middle_graph", "maximize_graph"]):
   173                                  ("delete_graph", self.OnCloseButton)]:
   265             self.Buttons.append(
       
   266                     GraphButton(0, 0, bitmap, 
       
   267                                 self.GetOnChangeSizeButton(size)))
       
   268         
       
   269         # Add buttons for exporting graph values to clipboard and close graph
       
   270         for bitmap, callback in [
       
   271                 ("export_graph_mini", self.OnExportGraphButton),
       
   272                 ("delete_graph", self.OnCloseButton)]:
   174             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
   273             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
   175         
   274         
       
   275         # Update graphs elements
   176         self.ResetGraphics()
   276         self.ResetGraphics()
   177         self.RefreshLabelsPosition(canvas_size.height)
   277         self.RefreshLabelsPosition(canvas_size.height)
   178         self.ShowButtons(False)
   278     
   179     
   279     def AddItem(self, item):
   180     def draw(self, drawDC=None):
   280         """
   181         FigureCanvasAgg.draw(self)
   281         Add an item to the list of items displayed by Viewer
   182 
   282         @param item: Item to add to the list
   183         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
   283         """
   184         self.bitmap.UseAlpha() 
   284         DebugVariableViewer.AddItem(self, item)
   185         width, height = self.GetSize()
   285         self.ResetGraphics()
   186         bbox = self.GetAxesBoundingBox()
   286         
   187         
   287     def RemoveItem(self, item):
   188         destDC = wx.MemoryDC()
   288         """
   189         destDC.SelectObject(self.bitmap)
   289         Remove an item from the list of items displayed by Viewer
   190         
   290         @param item: Item to remove from the list
   191         destGC = wx.GCDC(destDC)
   291         """
   192         
   292         DebugVariableViewer.RemoveItem(self, item)
   193         destGC.BeginDrawing()
   293         
   194         if self.Highlight == HIGHLIGHT_RESIZE:
   294         # If list of items is not empty
   195             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
   295         if not self.ItemsIsEmpty():
   196             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
   296             # Return to parallel graph if there is only one item
   197             destGC.DrawRectangle(0, height - 5, width, 5)
   297             # especially if it's actually orthogonal
   198         else:
   298             if len(self.Items) == 1:
   199             destGC.SetPen(HIGHLIGHT_DROP_PEN)
   299                 self.GraphType = GRAPH_PARALLEL
   200             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
   300             self.ResetGraphics()
   201             if self.Highlight == HIGHLIGHT_LEFT:
   301     
   202                 destGC.DrawRectangle(bbox.x, bbox.y, 
   302     def SubscribeAllDataConsumers(self):
   203                                      bbox.width / 2, bbox.height)
   303         """
   204             elif self.Highlight == HIGHLIGHT_RIGHT:
   304         Function that unsubscribe and remove every item that store values of
   205                 destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y, 
   305         a variable that doesn't exist in PLC anymore
   206                                      bbox.width / 2, bbox.height)
   306         """
   207         
   307         DebugVariableViewer.SubscribeAllDataConsumers(self)
   208         self.DrawCommonElements(destGC, self.GetButtons())
   308         if not self.ItemsIsEmpty():
   209         
   309             self.ResetGraphics()
   210         destGC.EndDrawing()
   310     
   211         
   311     def Is3DCanvas(self):
   212         self._isDrawn = True
   312         """
   213         self.gui_repaint(drawDC=drawDC)
   313         Return if Viewer is a 3D canvas
       
   314         @return: True if Viewer is a 3D canvas
       
   315         """
       
   316         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
   214     
   317     
   215     def GetButtons(self):
   318     def GetButtons(self):
       
   319         """
       
   320         Return list of buttons defined in Viewer
       
   321         @return: List of buttons
       
   322         """
       
   323         # Add contextual buttons to default buttons
   216         return self.Buttons + self.ContextualButtons
   324         return self.Buttons + self.ContextualButtons
   217     
   325     
   218     def PopupContextualButtons(self, item, rect, style=wx.RIGHT):
   326     def PopupContextualButtons(self, item, rect, direction=wx.RIGHT):
   219         if self.ContextualButtonsItem is not None and item != self.ContextualButtonsItem:
   327         """
   220             self.DismissContextualButtons()
   328         Show contextual menu for item aside a label of this item defined
   221         
   329         by the bounding box of label in figure
       
   330         @param item: Item for which contextual is shown
       
   331         @param rect: Bounding box of label aside which drawing menu
       
   332         @param direction: Direction in which buttons must be drawn
       
   333         """
       
   334         # Return immediately if contextual menu for item is already shown
       
   335         if self.ContextualButtonsItem == item:
       
   336             return
       
   337         
       
   338         # Close already shown contextual menu
       
   339         self.DismissContextualButtons()
       
   340         
       
   341         # Save item for which contextual menu is shown
       
   342         self.ContextualButtonsItem = item
       
   343         
       
   344         # If item variable is forced, add button for release variable to
       
   345         # contextual menu
       
   346         if self.ContextualButtonsItem.IsForced():
       
   347             self.ContextualButtons.append(
       
   348                 GraphButton(0, 0, "release", self.OnReleaseItemButton))
       
   349         
       
   350         # Add other buttons to contextual menu
       
   351         for bitmap, callback in [
       
   352                 ("force", self.OnForceItemButton),
       
   353                 ("export_graph_mini", self.OnExportItemGraphButton),
       
   354                 ("delete_graph", self.OnRemoveItemButton)]:
       
   355             self.ContextualButtons.append(
       
   356                     GraphButton(0, 0, bitmap, callback))
       
   357         
       
   358         # If buttons are shown at left side or upper side of rect, positions
       
   359         # will be set in reverse order
       
   360         buttons = self.ContextualButtons[:]
       
   361         if direction in [wx.TOP, wx.LEFT]:
       
   362              buttons.reverse()
       
   363              
       
   364         # Set contextual menu buttons position aside rect depending on
       
   365         # direction given
       
   366         offset = 0
       
   367         for button in buttons:
       
   368             w, h = button.GetSize()
       
   369             if direction in [wx.LEFT, wx.RIGHT]:
       
   370                 x = rect.x + (- w - offset
       
   371                             if direction == wx.LEFT
       
   372                             else rect.width + offset)
       
   373                 y = rect.y + (rect.height - h) / 2
       
   374                 offset += w
       
   375             else:
       
   376                 x = rect.x + (rect.width - w ) / 2
       
   377                 y = rect.y + (- h - offset
       
   378                               if direction == wx.TOP
       
   379                               else rect.height + offset)
       
   380                 offset += h
       
   381             button.SetPosition(x, y)
       
   382             button.Show()
       
   383         
       
   384         # Refresh canvas
       
   385         self.ParentWindow.ForceRefresh()
       
   386     
       
   387     def DismissContextualButtons(self):
       
   388         """
       
   389         Close current shown contextual menu
       
   390         """
       
   391         # Return immediately if no contextual menu is shown
   222         if self.ContextualButtonsItem is None:
   392         if self.ContextualButtonsItem is None:
   223             self.ContextualButtonsItem = item
   393             return
   224             
   394         
   225             if self.ContextualButtonsItem.IsForced():
   395         # Reset variables corresponding to contextual menu
   226                 self.ContextualButtons.append(
   396         self.ContextualButtonsItem = None
   227                     GraphButton(0, 0, "release", self.OnReleaseButton))
   397         self.ContextualButtons = []
   228             for bitmap, callback in [("force", self.OnForceButton),
   398         
   229                                      ("export_graph_mini", self.OnExportItemGraphButton),
   399         # Refresh canvas
   230                                      ("delete_graph", self.OnRemoveItemButton)]:
   400         self.ParentWindow.ForceRefresh()
   231                 self.ContextualButtons.append(GraphButton(0, 0, bitmap, callback))
       
   232             
       
   233             offset = 0
       
   234             buttons = self.ContextualButtons[:]
       
   235             if style in [wx.TOP, wx.LEFT]:
       
   236                  buttons.reverse()
       
   237             for button in buttons:
       
   238                 w, h = button.GetSize()
       
   239                 if style in [wx.LEFT, wx.RIGHT]:
       
   240                     x = rect.x + (- w - offset
       
   241                                 if style == wx.LEFT
       
   242                                 else rect.width + offset)
       
   243                     y = rect.y + (rect.height - h) / 2
       
   244                     offset += w
       
   245                 else:
       
   246                     x = rect.x + (rect.width - w ) / 2
       
   247                     y = rect.y + (- h - offset
       
   248                                   if style == wx.TOP
       
   249                                   else rect.height + offset)
       
   250                     offset += h
       
   251                 button.SetPosition(x, y)
       
   252             self.ParentWindow.ForceRefresh()
       
   253     
       
   254     def DismissContextualButtons(self):
       
   255         if self.ContextualButtonsItem is not None:
       
   256             self.ContextualButtonsItem = None
       
   257             self.ContextualButtons = []
       
   258             self.ParentWindow.ForceRefresh()
       
   259     
   401     
   260     def IsOverContextualButton(self, x, y):
   402     def IsOverContextualButton(self, x, y):
       
   403         """
       
   404         Return if point is over one contextual button of Viewer
       
   405         @param x: X coordinate of point
       
   406         @param y: Y coordinate of point
       
   407         @return: contextual button where point is over
       
   408         """
   261         for button in self.ContextualButtons:
   409         for button in self.ContextualButtons:
   262             if button.HitTest(x, y):
   410             if button.HitTest(x, y):
   263                 return True
   411                 return button
   264         return False
   412         return None
   265     
   413     
   266     def SetMinSize(self, size):
   414     def ExportGraph(self, item=None):
   267         wx.Window.SetMinSize(self, size)
   415         """
   268         wx.CallAfter(self.RefreshButtonsPosition)
   416         Export item(s) data to clipboard in CSV format
   269     
   417         @param item: Item from which data to export, all items if None
   270     def GetOnChangeSizeButton(self, size):
   418         (default None)
       
   419         """
       
   420         self.ParentWindow.CopyDataToClipboard(
       
   421             [(item, [entry for entry in item.GetData()])
       
   422              for item in (self.Items 
       
   423                           if item is None 
       
   424                           else [item])])
       
   425     
       
   426     def GetOnChangeSizeButton(self, height):
       
   427         """
       
   428         Function that generate callback function for change Viewer height to
       
   429         pre-defined height button
       
   430         @param height: Height that change Viewer to
       
   431         @return: callback function
       
   432         """
   271         def OnChangeSizeButton():
   433         def OnChangeSizeButton():
   272             self.CanvasSize = size
   434             self.SetCanvasSize(200, height)
   273             self.SetCanvasSize(200, self.CanvasSize)
       
   274         return OnChangeSizeButton
   435         return OnChangeSizeButton
   275     
   436     
   276     def OnExportGraphButton(self):
   437     def OnExportGraphButton(self):
       
   438         """
       
   439         Function called when Viewer Export button is pressed
       
   440         """
       
   441         # Export data of every item in Viewer
   277         self.ExportGraph()
   442         self.ExportGraph()
   278     
   443     
   279     def OnForceButton(self):
   444     def OnForceItemButton(self):
       
   445         """
       
   446         Function called when contextual menu Force button is pressed
       
   447         """
       
   448         # Open dialog for forcing item variable value 
   280         self.ForceValue(self.ContextualButtonsItem)
   449         self.ForceValue(self.ContextualButtonsItem)
       
   450         # Close contextual menu
   281         self.DismissContextualButtons()
   451         self.DismissContextualButtons()
   282         
   452         
   283     def OnReleaseButton(self):
   453     def OnReleaseItemButton(self):
       
   454         """
       
   455         Function called when contextual menu Release button is pressed
       
   456         """
       
   457         # Release item variable value 
   284         self.ReleaseValue(self.ContextualButtonsItem)
   458         self.ReleaseValue(self.ContextualButtonsItem)
       
   459         # Close contextual menu
   285         self.DismissContextualButtons()
   460         self.DismissContextualButtons()
   286     
   461     
   287     def OnExportItemGraphButton(self):
   462     def OnExportItemGraphButton(self):
       
   463         """
       
   464         Function called when contextual menu Export button is pressed
       
   465         """
       
   466         # Export data of item variable
   288         self.ExportGraph(self.ContextualButtonsItem)
   467         self.ExportGraph(self.ContextualButtonsItem)
       
   468         # Close contextual menu
   289         self.DismissContextualButtons()
   469         self.DismissContextualButtons()
   290         
   470         
   291     def OnRemoveItemButton(self):            
   471     def OnRemoveItemButton(self):            
       
   472         """
       
   473         Function called when contextual menu Remove button is pressed
       
   474         """
       
   475         # Remove item from Viewer
   292         wx.CallAfter(self.ParentWindow.DeleteValue, self, 
   476         wx.CallAfter(self.ParentWindow.DeleteValue, self, 
   293                      self.ContextualButtonsItem)
   477                      self.ContextualButtonsItem)
       
   478         # Close contextual menu
   294         self.DismissContextualButtons()
   479         self.DismissContextualButtons()
   295     
   480     
       
   481     def HandleCursorMove(self, event):
       
   482         start_tick, end_tick = self.ParentWindow.GetRange()
       
   483         cursor_tick = None
       
   484         items = self.ItemsDict.values()
       
   485         if self.GraphType == GRAPH_ORTHOGONAL:
       
   486             x_data = items[0].GetData(start_tick, end_tick)
       
   487             y_data = items[1].GetData(start_tick, end_tick)
       
   488             if len(x_data) > 0 and len(y_data) > 0:
       
   489                 length = min(len(x_data), len(y_data))
       
   490                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
       
   491                 cursor_tick = x_data[numpy.argmin(d), 0]
       
   492         else:
       
   493             data = items[0].GetData(start_tick, end_tick)
       
   494             if len(data) > 0:
       
   495                 cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0]
       
   496         if cursor_tick is not None:
       
   497             self.ParentWindow.SetCursorTick(cursor_tick)
       
   498     
       
   499     def OnCanvasButtonPressed(self, event):
       
   500         """
       
   501         Function called when a button of mouse is pressed
       
   502         @param event: Mouse event
       
   503         """
       
   504         # Get mouse position, graph coordinates are inverted comparing to wx
       
   505         # coordinates
       
   506         width, height = self.GetSize()
       
   507         x, y = event.x, height - event.y
       
   508         
       
   509         # Return immediately if mouse is over a button
       
   510         if self.IsOverButton(x, y):
       
   511             return 
       
   512         
       
   513         # Mouse was clicked inside graph figure
       
   514         if event.inaxes == self.Axes:
       
   515             
       
   516             # Find if it was on an item label
       
   517             item_idx = None
       
   518             # Check every label paired with corresponding item
       
   519             for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
       
   520                          [pair for pair in enumerate(self.Labels)]):
       
   521                 # Get label bounding box
       
   522                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   523                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   524                 # Check if mouse was over label
       
   525                 if rect.InsideXY(x, y):
       
   526                     item_idx = i
       
   527                     break
       
   528             
       
   529             # If an item label have been clicked
       
   530             if item_idx is not None:
       
   531                 # Hide buttons and contextual buttons
       
   532                 self.ShowButtons(False)
       
   533                 self.DismissContextualButtons()
       
   534                 
       
   535                 # Start a drag'n drop from mouse position in wx coordinate of
       
   536                 # parent
       
   537                 xw, yw = self.GetPosition()
       
   538                 self.ParentWindow.StartDragNDrop(self, 
       
   539                     self.ItemsDict.values()[item_idx], 
       
   540                     x + xw, y + yw, # Current mouse position
       
   541                     x + xw, y + yw) # Mouse position when button was clicked
       
   542             
       
   543             # Don't handle mouse button if canvas is 3D and let matplotlib do
       
   544             # the default behavior (rotate 3D axes)
       
   545             elif not self.Is3DCanvas():
       
   546                 # Save mouse position when clicked
       
   547                 self.MouseStartPos = wx.Point(x, y)
       
   548                 
       
   549                 # Mouse button was left button, start moving cursor
       
   550                 if event.button == 1:
       
   551                     # Save current tick in case a drag'n drop is initiate to
       
   552                     # restore it
       
   553                     self.StartCursorTick = self.CursorTick
       
   554                     
       
   555                     self.HandleCursorMove(event)
       
   556                     
       
   557                 # Mouse button is middle button and graph is parallel, start
       
   558                 # moving graph along X coordinate (tick)
       
   559                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
       
   560                     self.StartCursorTick = self.ParentWindow.GetRange()[0]
       
   561         
       
   562         # Mouse was clicked outside graph figure and over resize highlight with
       
   563         # left button, start resizing Viewer
       
   564         elif event.button == 1 and event.y <= 5:
       
   565             self.MouseStartPos = wx.Point(x, y)
       
   566             self.CanvasStartSize = height
       
   567     
       
   568     def OnCanvasButtonReleased(self, event):
       
   569         """
       
   570         Function called when a button of mouse is released
       
   571         @param event: Mouse event
       
   572         """
       
   573         # If a drag'n drop is in progress, stop it
       
   574         if self.ParentWindow.IsDragging():
       
   575             width, height = self.GetSize()
       
   576             xw, yw = self.GetPosition()
       
   577             item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0]
       
   578             # Give mouse position in wx coordinate of parent
       
   579             self.ParentWindow.StopDragNDrop(item.GetVariable(),
       
   580                 xw + event.x, yw + height - event.y)
       
   581         
       
   582         else:
       
   583             # Reset any move in progress
       
   584             self.MouseStartPos = None
       
   585             self.StartCursorTick = None
       
   586             self.CanvasStartSize = None
       
   587             
       
   588             # Handle button under mouse if it exist
       
   589             width, height = self.GetSize()
       
   590             self.HandleButton(event.x, height - event.y)
       
   591     
       
   592     def OnCanvasMotion(self, event):
       
   593         """
       
   594         Function called when a button of mouse is moved over Viewer
       
   595         @param event: Mouse event
       
   596         """
       
   597         width, height = self.GetSize()
       
   598         
       
   599         # If a drag'n drop is in progress, move canvas dragged
       
   600         if self.ParentWindow.IsDragging():
       
   601             xw, yw = self.GetPosition()
       
   602             # Give mouse position in wx coordinate of parent
       
   603             self.ParentWindow.MoveDragNDrop(
       
   604                 xw + event.x, 
       
   605                 yw + height - event.y)
       
   606         
       
   607         # If a Viewer resize is in progress, change Viewer size 
       
   608         elif event.button == 1 and self.CanvasStartSize is not None:
       
   609             width, height = self.GetSize()
       
   610             self.SetCanvasSize(width, 
       
   611                 self.CanvasStartSize + height - event.y - self.MouseStartPos.y)
       
   612         
       
   613         # If no button is pressed, show or hide contextual buttons or resize
       
   614         # highlight
       
   615         elif event.button is None:
       
   616             # Compute direction for items label according graph type
       
   617             if self.GraphType == GRAPH_PARALLEL: # Graph is parallel
       
   618                 directions = [wx.RIGHT] * len(self.AxesLabels) + \
       
   619                              [wx.LEFT] * len(self.Labels)
       
   620             elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D
       
   621                 directions = [wx.RIGHT, wx.TOP,  # Directions for AxesLabels
       
   622                              wx.LEFT, wx.BOTTOM] # Directions for Labels
       
   623             else: # Graph is orthogonal in 3D
       
   624                 directions = [wx.LEFT] * len(self.Labels)
       
   625             
       
   626             # Find if mouse is over an item label
       
   627             item_idx = None
       
   628             menu_direction = None
       
   629             for (i, t), dir in zip(
       
   630                     [pair for pair in enumerate(self.AxesLabels)] + 
       
   631                     [pair for pair in enumerate(self.Labels)], 
       
   632                     directions):
       
   633                 # Check every label paired with corresponding item
       
   634                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   635                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   636                 # Check if mouse was over label
       
   637                 if rect.InsideXY(event.x, height - event.y):
       
   638                     item_idx = i
       
   639                     menu_direction = dir
       
   640                     break
       
   641             
       
   642             # If mouse is over an item label, 
       
   643             if item_idx is not None:
       
   644                 self.PopupContextualButtons(
       
   645                     self.ItemsDict.values()[item_idx], 
       
   646                     rect, menu_direction)
       
   647                 return
       
   648             
       
   649             # If mouse isn't over a contextual menu, hide the current shown one
       
   650             # if it exists 
       
   651             if self.IsOverContextualButton(event.x, height - event.y) is None:
       
   652                 self.DismissContextualButtons()
       
   653             
       
   654             # Update resize highlight
       
   655             if event.y <= 5:
       
   656                 if self.SetHighlight(HIGHLIGHT_RESIZE):
       
   657                     self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
       
   658                     self.ParentWindow.ForceRefresh()
       
   659             else:
       
   660                 if self.SetHighlight(HIGHLIGHT_NONE):
       
   661                     self.SetCursor(wx.NullCursor)
       
   662                     self.ParentWindow.ForceRefresh()
       
   663         
       
   664         # Handle buttons if canvas is not 3D 
       
   665         elif not self.Is3DCanvas():
       
   666             
       
   667             # If left button is pressed
       
   668             if event.button == 1:
       
   669                 
       
   670                 # Mouse is inside graph figure
       
   671                 if event.inaxes == self.Axes:
       
   672                     
       
   673                     # If a cursor move is in progress, update cursor position
       
   674                     if self.MouseStartPos is not None:
       
   675                         self.HandleCursorMove(event)
       
   676                 
       
   677                 # Mouse is outside graph figure, cursor move is in progress and
       
   678                 # there is only one item in Viewer, start a drag'n drop
       
   679                 elif self.MouseStartPos is not None and len(self.Items) == 1:
       
   680                     xw, yw = self.GetPosition()
       
   681                     self.ParentWindow.SetCursorTick(self.StartCursorTick)
       
   682                     self.ParentWindow.StartDragNDrop(self, 
       
   683                         self.ItemsDict.values()[0],
       
   684                         # Current mouse position
       
   685                         event.x + xw, height - event.y + yw,
       
   686                         # Mouse position when button was clicked
       
   687                         self.MouseStartPos.x + xw,
       
   688                         self.MouseStartPos.y + yw)
       
   689             
       
   690             # If middle button is pressed and moving graph along X coordinate
       
   691             # is in progress
       
   692             elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \
       
   693                  self.MouseStartPos is not None:
       
   694                 start_tick, end_tick = self.ParentWindow.GetRange()
       
   695                 rect = self.GetAxesBoundingBox()
       
   696                 
       
   697                 # Move graph along X coordinate
       
   698                 self.ParentWindow.SetCanvasPosition(
       
   699                     self.StartCursorTick + 
       
   700                     (self.MouseStartPos.x - event.x) *
       
   701                     (end_tick - start_tick) / rect.width)
       
   702     
       
   703     def OnCanvasScroll(self, event):
       
   704         """
       
   705         Function called when a wheel mouse is use in Viewer
       
   706         @param event: Mouse event
       
   707         """
       
   708         # Change X range of graphs if mouse is in canvas figure and ctrl is
       
   709         # pressed
       
   710         if event.inaxes is not None and event.guiEvent.ControlDown():
       
   711             
       
   712             # Calculate position of fixed tick point according to graph type
       
   713             # and mouse position
       
   714             if self.GraphType == GRAPH_ORTHOGONAL:
       
   715                 start_tick, end_tick = self.ParentWindow.GetRange()
       
   716                 tick = (start_tick + end_tick) / 2.
       
   717             else:
       
   718                 tick = event.xdata
       
   719             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
       
   720             
       
   721             # Vetoing event to prevent parent panel to be scrolled
       
   722             self.ParentWindow.VetoScrollEvent = True
       
   723     
       
   724     def OnAxesMotion(self, event):
       
   725         """
       
   726         Function overriding default function called when mouse is dragged for
       
   727         rotating graph preventing refresh to be called too quickly
       
   728         @param event: Mouse event
       
   729         """
       
   730         if self.Is3DCanvas():
       
   731             # Call default function at most 10 times per second
       
   732             current_time = gettime()
       
   733             if current_time - self.LastMotionTime > REFRESH_PERIOD:
       
   734                 self.LastMotionTime = current_time
       
   735                 Axes3D._on_move(self.Axes, event)
       
   736     
       
   737     # Cursor tick move for each arrow key
       
   738     KEY_CURSOR_INCREMENT = {
       
   739         wx.WXK_LEFT: -1,
       
   740         wx.WXK_RIGHT: 1,
       
   741         wx.WXK_UP: -10,
       
   742         wx.WXK_DOWN: 10}
       
   743     
       
   744     def OnKeyDown(self, event):
       
   745         """
       
   746         Function called when key is pressed
       
   747         @param event: wx.KeyEvent
       
   748         """
       
   749         # If cursor is shown and arrow key is pressed, move cursor tick
       
   750         if self.CursorTick is not None:
       
   751             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
       
   752             if move is not None:
       
   753                 self.ParentWindow.MoveCursorTick(move)
       
   754         event.Skip()
       
   755     
   296     def OnLeave(self, event):
   756     def OnLeave(self, event):
   297         if self.Highlight != HIGHLIGHT_RESIZE or self.CanvasStartSize is None:
   757         """
       
   758         Function called when mouse leave Viewer
       
   759         @param event: wx.MouseEvent
       
   760         """
       
   761         # If Viewer is not resizing, reset resize highlight
       
   762         if self.CanvasStartSize is None:
       
   763             self.SetHighlight(HIGHLIGHT_NONE)
       
   764             self.SetCursor(wx.NullCursor)
   298             DebugVariableViewer.OnLeave(self, event)
   765             DebugVariableViewer.OnLeave(self, event)
   299         else:
   766         else:
   300             event.Skip()
   767             event.Skip()
   301     
   768     
   302     def RefreshLabelsPosition(self, height):
   769     def RefreshLabelsPosition(self, height):
   333     
   800     
   334     def SetCanvasSize(self, width, height):
   801     def SetCanvasSize(self, width, height):
   335         height = max(height, self.GetCanvasMinSize()[1])
   802         height = max(height, self.GetCanvasMinSize()[1])
   336         self.SetMinSize(wx.Size(width, height))
   803         self.SetMinSize(wx.Size(width, height))
   337         self.RefreshLabelsPosition(height)
   804         self.RefreshLabelsPosition(height)
   338         self.RefreshButtonsPosition()
       
   339         self.ParentWindow.RefreshGraphicsSizer()
   805         self.ParentWindow.RefreshGraphicsSizer()
   340         
   806         
   341     def GetAxesBoundingBox(self, absolute=False):
   807     def GetAxesBoundingBox(self, absolute=False):
   342         width, height = self.GetSize()
   808         width, height = self.GetSize()
   343         ax, ay, aw, ah = self.figure.gca().get_position().bounds
   809         ax, ay, aw, ah = self.figure.gca().get_position().bounds
   346         if absolute:
   812         if absolute:
   347             xw, yw = self.GetPosition()
   813             xw, yw = self.GetPosition()
   348             bbox.x += xw
   814             bbox.x += xw
   349             bbox.y += yw
   815             bbox.y += yw
   350         return bbox
   816         return bbox
   351     
       
   352     def OnCanvasButtonPressed(self, event):
       
   353         width, height = self.GetSize()
       
   354         x, y = event.x, height - event.y
       
   355         if not self.IsOverButton(x, y):
       
   356             if event.inaxes == self.Axes:
       
   357                 item_idx = None
       
   358                 for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
       
   359                              [pair for pair in enumerate(self.Labels)]):
       
   360                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   361                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   362                     if rect.InsideXY(x, y):
       
   363                         item_idx = i
       
   364                         break
       
   365                 if item_idx is not None:
       
   366                     self.ShowButtons(False)
       
   367                     self.DismissContextualButtons()
       
   368                     xw, yw = self.GetPosition()
       
   369                     self.ParentWindow.StartDragNDrop(self, 
       
   370                         self.ItemsDict.values()[item_idx], x + xw, y + yw, x + xw, y + yw)
       
   371                 elif not self.Is3DCanvas():
       
   372                     self.MouseStartPos = wx.Point(x, y)
       
   373                     if event.button == 1 and event.inaxes == self.Axes:
       
   374                         self.StartCursorTick = self.CursorTick
       
   375                         self.HandleCursorMove(event)
       
   376                     elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
       
   377                         width, height = self.GetSize()
       
   378                         start_tick, end_tick = self.ParentWindow.GetRange()
       
   379                         self.StartCursorTick = start_tick
       
   380             
       
   381             elif event.button == 1 and event.y <= 5:
       
   382                 self.MouseStartPos = wx.Point(x, y)
       
   383                 self.CanvasStartSize = self.GetSize()
       
   384     
       
   385     def OnCanvasButtonReleased(self, event):
       
   386         if self.ParentWindow.IsDragging():
       
   387             width, height = self.GetSize()
       
   388             xw, yw = self.GetPosition()
       
   389             self.ParentWindow.StopDragNDrop(
       
   390                 self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0].GetVariable(),
       
   391                 xw + event.x, 
       
   392                 yw + height - event.y)
       
   393         else:
       
   394             self.MouseStartPos = None
       
   395             self.StartCursorTick = None
       
   396             self.CanvasStartSize = None
       
   397             width, height = self.GetSize()
       
   398             self.HandleButton(event.x, height - event.y)
       
   399             if event.y > 5 and self.SetHighlight(HIGHLIGHT_NONE):
       
   400                 self.SetCursor(wx.NullCursor)
       
   401                 self.ParentWindow.ForceRefresh()
       
   402     
       
   403     def OnCanvasMotion(self, event):
       
   404         width, height = self.GetSize()
       
   405         if self.ParentWindow.IsDragging():
       
   406             xw, yw = self.GetPosition()
       
   407             self.ParentWindow.MoveDragNDrop(
       
   408                 xw + event.x, 
       
   409                 yw + height - event.y)
       
   410         else:
       
   411             if not self.Is3DCanvas():
       
   412                 if event.button == 1 and self.CanvasStartSize is None:
       
   413                     if event.inaxes == self.Axes:
       
   414                         if self.MouseStartPos is not None:
       
   415                             self.HandleCursorMove(event)
       
   416                     elif self.MouseStartPos is not None and len(self.Items) == 1:
       
   417                         xw, yw = self.GetPosition()
       
   418                         self.ParentWindow.SetCursorTick(self.StartCursorTick)
       
   419                         self.ParentWindow.StartDragNDrop(self, 
       
   420                             self.ItemsDict.values()[0],
       
   421                             event.x + xw, height - event.y + yw, 
       
   422                             self.MouseStartPos.x + xw, self.MouseStartPos.y + yw)
       
   423                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and self.MouseStartPos is not None:
       
   424                     start_tick, end_tick = self.ParentWindow.GetRange()
       
   425                     rect = self.GetAxesBoundingBox()
       
   426                     self.ParentWindow.SetCanvasPosition(
       
   427                         self.StartCursorTick + (self.MouseStartPos.x - event.x) *
       
   428                         (end_tick - start_tick) / rect.width)    
       
   429             
       
   430             if event.button == 1 and self.CanvasStartSize is not None:
       
   431                 width, height = self.GetSize()
       
   432                 self.SetCanvasSize(width, 
       
   433                     self.CanvasStartSize.height + height - event.y - self.MouseStartPos.y)
       
   434                 
       
   435             elif event.button in [None, "up", "down"]:
       
   436                 if self.GraphType == GRAPH_PARALLEL:
       
   437                     orientation = [wx.RIGHT] * len(self.AxesLabels) + [wx.LEFT] * len(self.Labels)
       
   438                 elif len(self.AxesLabels) > 0:
       
   439                     orientation = [wx.RIGHT, wx.TOP, wx.LEFT, wx.BOTTOM]
       
   440                 else:
       
   441                     orientation = [wx.LEFT] * len(self.Labels)
       
   442                 item_idx = None
       
   443                 item_style = None
       
   444                 for (i, t), style in zip([pair for pair in enumerate(self.AxesLabels)] + 
       
   445                                          [pair for pair in enumerate(self.Labels)], 
       
   446                                          orientation):
       
   447                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   448                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   449                     if rect.InsideXY(event.x, height - event.y):
       
   450                         item_idx = i
       
   451                         item_style = style
       
   452                         break
       
   453                 if item_idx is not None:
       
   454                     self.PopupContextualButtons(self.ItemsDict.values()[item_idx], rect, item_style)
       
   455                     return 
       
   456                 if not self.IsOverContextualButton(event.x, height - event.y):
       
   457                     self.DismissContextualButtons()
       
   458                 
       
   459                 if event.y <= 5:
       
   460                     if self.SetHighlight(HIGHLIGHT_RESIZE):
       
   461                         self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
       
   462                         self.ParentWindow.ForceRefresh()
       
   463                 else:
       
   464                     if self.SetHighlight(HIGHLIGHT_NONE):
       
   465                         self.SetCursor(wx.NullCursor)
       
   466                         self.ParentWindow.ForceRefresh()
       
   467     
       
   468     def OnCanvasScroll(self, event):
       
   469         if event.inaxes is not None and event.guiEvent.ControlDown():
       
   470             if self.GraphType == GRAPH_ORTHOGONAL:
       
   471                 start_tick, end_tick = self.ParentWindow.GetRange()
       
   472                 tick = (start_tick + end_tick) / 2.
       
   473             else:
       
   474                 tick = event.xdata
       
   475             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
       
   476             self.ParentWindow.VetoScrollEvent = True
       
   477     
   817     
   478     def RefreshHighlight(self, x, y):
   818     def RefreshHighlight(self, x, y):
   479         width, height = self.GetSize()
   819         width, height = self.GetSize()
   480         bbox = self.GetAxesBoundingBox()
   820         bbox = self.GetAxesBoundingBox()
   481         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
   821         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
   490             else:
   830             else:
   491                 self.SetHighlight(HIGHLIGHT_NONE)
   831                 self.SetHighlight(HIGHLIGHT_NONE)
   492                 self.ParentWindow.HighlightPreviousViewer(self)
   832                 self.ParentWindow.HighlightPreviousViewer(self)
   493         else:
   833         else:
   494             self.SetHighlight(HIGHLIGHT_AFTER)
   834             self.SetHighlight(HIGHLIGHT_AFTER)
   495     
       
   496     def OnLeave(self, event):
       
   497         if self.CanvasStartSize is None and self.SetHighlight(HIGHLIGHT_NONE):
       
   498             self.SetCursor(wx.NullCursor)
       
   499             self.ParentWindow.ForceRefresh()
       
   500         DebugVariableViewer.OnLeave(self, event)
       
   501     
       
   502     KEY_CURSOR_INCREMENT = {
       
   503         wx.WXK_LEFT: -1,
       
   504         wx.WXK_RIGHT: 1,
       
   505         wx.WXK_UP: -10,
       
   506         wx.WXK_DOWN: 10}
       
   507     def OnKeyDown(self, event):
       
   508         if self.CursorTick is not None:
       
   509             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
       
   510             if move is not None:
       
   511                 self.ParentWindow.MoveCursorTick(move)
       
   512         event.Skip()
       
   513     
       
   514     def HandleCursorMove(self, event):
       
   515         start_tick, end_tick = self.ParentWindow.GetRange()
       
   516         cursor_tick = None
       
   517         items = self.ItemsDict.values()
       
   518         if self.GraphType == GRAPH_ORTHOGONAL:
       
   519             x_data = items[0].GetData(start_tick, end_tick)
       
   520             y_data = items[1].GetData(start_tick, end_tick)
       
   521             if len(x_data) > 0 and len(y_data) > 0:
       
   522                 length = min(len(x_data), len(y_data))
       
   523                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
       
   524                 cursor_tick = x_data[numpy.argmin(d), 0]
       
   525         else:
       
   526             data = items[0].GetData(start_tick, end_tick)
       
   527             if len(data) > 0:
       
   528                 cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0]
       
   529         if cursor_tick is not None:
       
   530             self.ParentWindow.SetCursorTick(cursor_tick)
       
   531     
       
   532     def OnAxesMotion(self, event):
       
   533         if self.Is3DCanvas():
       
   534             current_time = gettime()
       
   535             if current_time - self.LastMotionTime > REFRESH_PERIOD:
       
   536                 self.LastMotionTime = current_time
       
   537                 Axes3D._on_move(self.Axes, event)
       
   538     
   835     
   539     def ResetGraphics(self):
   836     def ResetGraphics(self):
   540         self.Figure.clear()
   837         self.Figure.clear()
   541         if self.Is3DCanvas():
   838         if self.Is3DCanvas():
   542             self.Axes = self.Figure.gca(projection='3d')
   839             self.Axes = self.Figure.gca(projection='3d')
   596                                verticalalignment='top',
   893                                verticalalignment='top',
   597                                transform=self.Axes.transAxes))
   894                                transform=self.Axes.transAxes))
   598         width, height = self.GetSize()
   895         width, height = self.GetSize()
   599         self.RefreshLabelsPosition(height)
   896         self.RefreshLabelsPosition(height)
   600         
   897         
   601     def AddItem(self, item):
       
   602         DebugVariableViewer.AddItem(self, item)
       
   603         self.ResetGraphics()
       
   604         
       
   605     def RemoveItem(self, item):
       
   606         DebugVariableViewer.RemoveItem(self, item)
       
   607         if not self.ItemsIsEmpty():
       
   608             if len(self.Items) == 1:
       
   609                 self.GraphType = GRAPH_PARALLEL
       
   610             self.ResetGraphics()
       
   611     
       
   612     def SubscribeAllDataConsumers(self):
       
   613         DebugVariableViewer.SubscribeAllDataConsumers(self)
       
   614         if not self.ItemsIsEmpty():
       
   615             self.ResetGraphics()
       
   616     
       
   617     def Is3DCanvas(self):
       
   618         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
       
   619     
       
   620     def SetCursorTick(self, cursor_tick):
   898     def SetCursorTick(self, cursor_tick):
   621         self.CursorTick = cursor_tick
   899         self.CursorTick = cursor_tick
   622         
   900         
   623     def RefreshViewer(self, refresh_graphics=True):
   901     def RefreshViewer(self, refresh_graphics=True):
   624         
   902         
   670                                               for item in self.Items
   948                                               for item in self.Items
   671                                               if len(item.GetData()) > 0], 0)
   949                                               if len(item.GetData()) > 0], 0)
   672                 start_tick = max(start_tick, min_start_tick)
   950                 start_tick = max(start_tick, min_start_tick)
   673                 end_tick = max(end_tick, min_start_tick)
   951                 end_tick = max(end_tick, min_start_tick)
   674                 items = self.ItemsDict.values()
   952                 items = self.ItemsDict.values()
   675                 x_data, x_min, x_max = OrthogonalData(items[0], start_tick, end_tick)
   953                 x_data, x_min, x_max = OrthogonalDataAndRange(items[0], start_tick, end_tick)
   676                 y_data, y_min, y_max = OrthogonalData(items[1], start_tick, end_tick)
   954                 y_data, y_min, y_max = OrthogonalDataAndRange(items[1], start_tick, end_tick)
   677                 if self.CursorTick is not None:
   955                 if self.CursorTick is not None:
   678                     x_cursor, x_forced = items[0].GetValue(self.CursorTick, raw=True)
   956                     x_cursor, x_forced = items[0].GetValue(self.CursorTick, raw=True)
   679                     y_cursor, y_forced = items[1].GetValue(self.CursorTick, raw=True)
   957                     y_cursor, y_forced = items[1].GetValue(self.CursorTick, raw=True)
   680                 length = 0
   958                 length = 0
   681                 if x_data is not None and y_data is not None:  
   959                 if x_data is not None and y_data is not None:  
   708                         if self.HLine is not None:
   986                         if self.HLine is not None:
   709                             self.HLine.set_visible(False)
   987                             self.HLine.set_visible(False)
   710                 else:
   988                 else:
   711                     while len(self.Axes.lines) > 0:
   989                     while len(self.Axes.lines) > 0:
   712                         self.Axes.lines.pop()
   990                         self.Axes.lines.pop()
   713                     z_data, z_min, z_max = OrthogonalData(items[2], start_tick, end_tick)
   991                     z_data, z_min, z_max = OrthogonalDataAndRange(items[2], start_tick, end_tick)
   714                     if self.CursorTick is not None:
   992                     if self.CursorTick is not None:
   715                         z_cursor, z_forced = items[2].GetValue(self.CursorTick, raw=True)
   993                         z_cursor, z_forced = items[2].GetValue(self.CursorTick, raw=True)
   716                     if x_data is not None and y_data is not None and z_data is not None:
   994                     if x_data is not None and y_data is not None and z_data is not None:
   717                         length = min(length, len(z_data))
   995                         length = min(length, len(z_data))
   718                         self.Axes.plot(x_data[:, 1][:length],
   996                         self.Axes.plot(x_data[:, 1][:length],
   752             label.set_text(value)
  1030             label.set_text(value)
   753             label.set_style(style)
  1031             label.set_style(style)
   754         
  1032         
   755         self.draw()
  1033         self.draw()
   756 
  1034 
   757     def ExportGraph(self, item=None):
  1035     def draw(self, drawDC=None):
   758         if item is not None:
  1036         """
   759             variables = [(item, [entry for entry in item.GetData()])]
  1037         Render the figure.
   760         else:
  1038         """
   761             variables = [(item, [entry for entry in item.GetData()])
  1039         # Render figure using agg
   762                          for item in self.Items]
  1040         FigureCanvasAgg.draw(self)
   763         self.ParentWindow.CopyDataToClipboard(variables)
  1041         
       
  1042         # Get bitmap of figure rendered
       
  1043         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
       
  1044         self.bitmap.UseAlpha()
       
  1045         
       
  1046         # Create DC for rendering graphics in bitmap
       
  1047         destDC = wx.MemoryDC()
       
  1048         destDC.SelectObject(self.bitmap)
       
  1049         
       
  1050         # Get Graphics Context for DC, for anti-aliased and transparent
       
  1051         # rendering
       
  1052         destGC = wx.GCDC(destDC)
       
  1053         
       
  1054         destGC.BeginDrawing()
       
  1055         
       
  1056         # Get canvas size and figure bounding box in canvas
       
  1057         width, height = self.GetSize()
       
  1058         bbox = self.GetAxesBoundingBox()
       
  1059         
       
  1060         # If highlight to display is resize, draw thick grey line at bottom
       
  1061         # side of canvas 
       
  1062         if self.Highlight == HIGHLIGHT_RESIZE:
       
  1063             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
       
  1064             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
       
  1065             destGC.DrawRectangle(0, height - 5, width, 5)
       
  1066         
       
  1067         # If highlight to display is merging graph, draw 50% transparent blue
       
  1068         # rectangle on left or right part of figure depending on highlight type
       
  1069         elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]:
       
  1070             destGC.SetPen(HIGHLIGHT_DROP_PEN)
       
  1071             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
       
  1072             
       
  1073             x_offset = (bbox.width / 2 
       
  1074                         if self.Highlight == HIGHLIGHT_RIGHT
       
  1075                         else 0)
       
  1076             destGC.DrawRectangle(bbox.x + x_offset, bbox.y, 
       
  1077                                  bbox.width / 2, bbox.height)
       
  1078         
       
  1079         # Draw other Viewer common elements
       
  1080         self.DrawCommonElements(destGC, self.GetButtons())
       
  1081         
       
  1082         destGC.EndDrawing()
       
  1083         
       
  1084         self._isDrawn = True
       
  1085         self.gui_repaint(drawDC=drawDC)
       
  1086     
       
  1087