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