Remove matplotlib toolbar from Figure
[oscopy.git] / src / oscopy_ipython / gtk_figure.py
blob9ebd55b0c1ff3624f37e2951d57416114b68bbac
2 import oscopy
3 import gtk
4 import gobject
5 import gui
6 from math import log10, sqrt
7 from matplotlib.backend_bases import LocationEvent
8 from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
9 from matplotlib.backends.backend_gtk import FileChooserDialog
10 from matplotlib.widgets import SpanSelector, RectangleSelector
11 from matplotlib.transforms import Bbox
13 IOSCOPY_COL_TEXT = 0 # Combo box text
14 IOSCOPY_COL_X10 = 1 # x10 mode status
15 IOSCOPY_COL_VIS = 2 # Combobox items sensitive
16 IOSCOPY_COL_SPAN = 3 # Span mode status
17 IOSCOPY_COL_HADJ = 4 # Horizontal scrollbar adjustment
18 IOSCOPY_COL_VADJ = 5 # Vertical scrollbar adjustment
20 DEFAULT_ZOOM_FACTOR = 0.8
21 DEFAULT_PAN_FACTOR = 10
23 class MyRectangleSelector(RectangleSelector):
24 """ FIXME: To be removed once upstream has merged PR #658
25 https://github.com/matplotlib/matplotlib/pull/658
26 """
28 def ignore(self, event):
29 'return ``True`` if *event* should be ignored'
30 # If RectangleSelector is not active :
31 if not self.active:
32 return True
34 # If canvas was locked
35 if not self.canvas.widgetlock.available(self):
36 return True
38 # Only do rectangle selection if event was triggered
39 # with a desired button
40 if self.validButtons is not None:
41 if not event.button in self.validButtons:
42 return True
44 # If no button was pressed yet ignore the event if it was out
45 # of the axes
46 if self.eventpress == None:
47 return event.inaxes!= self.ax
49 # If a button was pressed, check if the release-button is the
50 # same. If event is out of axis, limit the data coordinates to axes
51 # boundaries.
52 if event.button == self.eventpress.button and event.inaxes != self.ax:
53 (xdata, ydata) = self.ax.transData.inverted().transform_point((event.x, event.y))
54 x0, x1 = self.ax.get_xbound()
55 y0, y1 = self.ax.get_ybound()
56 xdata = max(x0, xdata)
57 xdata = min(x1, xdata)
58 ydata = max(y0, ydata)
59 ydata = min(y1, ydata)
60 event.xdata = xdata
61 event.ydata = ydata
62 return False
64 # If a button was pressed, check if the release-button is the
65 # same.
66 return (event.inaxes!=self.ax or
67 event.button != self.eventpress.button)
69 class IOscopy_GTK_Figure(oscopy.Figure):
70 def __init__(self, sigs={}, fig=None, title=''):
71 oscopy.Figure.__init__(self, None, fig)
72 self._TARGET_TYPE_SIGNAL = 10354
73 self._to_figure = [("oscopy-signals", gtk.TARGET_SAME_APP,\
74 self._TARGET_TYPE_SIGNAL)]
75 self._hadjpreval = None
76 self._vadjpreval = None
78 w = gtk.Window()
79 w.set_title(title)
81 hbox1 = gtk.HBox() # The window
82 vbox1 = gtk.VBox() # The Graphs
83 hbox1.pack_start(vbox1)
84 w.add(hbox1)
85 canvas = FigureCanvas(self)
86 canvas.mpl_connect('button_press_event', self._button_press)
87 canvas.mpl_connect('scroll_event', self._mouse_scroll)
88 canvas.mpl_connect('axes_enter_event', self._axes_enter)
89 canvas.mpl_connect('axes_leave_event', self._axes_leave)
90 canvas.mpl_connect('figure_enter_event', self._figure_enter)
91 canvas.mpl_connect('figure_leave_event', self._figure_leave)
92 canvas.mpl_connect('key_press_event', self._key_press)
93 canvas.mpl_connect('motion_notify_event', self._show_coords)
94 self._draw_hid = canvas.mpl_connect('draw_event', self._update_scrollbars)
95 w.connect('delete-event', lambda w, e: w.hide() or True)
96 w.drag_dest_set(gtk.DEST_DEFAULT_MOTION |\
97 gtk.DEST_DEFAULT_HIGHLIGHT |\
98 gtk.DEST_DEFAULT_DROP,
99 self._to_figure, gtk.gdk.ACTION_COPY)
101 hbar = gtk.HScrollbar()
102 hbar.set_sensitive(False)
103 self.hbar = hbar
104 vbox1.pack_start(hbar, False, False)
105 vbar = gtk.VScrollbar()
106 vbar.set_sensitive(False)
107 self.vbar = vbar
108 hbox1.pack_start(vbar, False, False)
110 vbox1.pack_start(canvas)
112 vbox2 = gtk.VBox() # The right-side menu
113 store = gtk.ListStore(gobject.TYPE_STRING, # String displayed
114 gobject.TYPE_BOOLEAN, # x10 mode status
115 gobject.TYPE_BOOLEAN, # Combobox item sensitive
116 gobject.TYPE_BOOLEAN, # Span mode status
117 gobject.TYPE_PYOBJECT, # Horizontal Adjustment
118 gobject.TYPE_PYOBJECT, # Vertical Adjustment
120 iter = store.append([_('All Graphs'), False, True, False, gtk.Adjustment(), gtk.Adjustment()])
121 for i in xrange(4):
122 iter = store.append([_('Graph %d') % (i + 1), False, True if i < len(self.graphs) else False, False, gtk.Adjustment(), gtk.Adjustment()])
123 self._cbx_store = store
124 hbar.set_adjustment(store[0][IOSCOPY_COL_HADJ])
125 vbar.set_adjustment(store[0][IOSCOPY_COL_VADJ])
127 graphs_cbx = gtk.ComboBox(store)
128 cell = gtk.CellRendererText()
129 graphs_cbx.pack_start(cell, True)
130 graphs_cbx.add_attribute(cell, 'text', IOSCOPY_COL_TEXT)
131 graphs_cbx.add_attribute(cell, 'sensitive', IOSCOPY_COL_VIS)
132 graphs_cbx.set_active(0)
133 vbox2.pack_start(graphs_cbx, False, False)
135 # master pan radiobuttons
136 label = gtk.Label('master pan')
137 vbox2.pack_start(label, False, False)
139 rbtns = [gtk.RadioButton(None, '%d' % (i + 1)) for i in xrange(4)]
140 rbtnbox = gtk.HBox()
141 for rb in rbtns[1:4]: rb.set_group(rbtns[0])
142 for rb in rbtns:
143 rb.set_sensitive(False)
144 rbtnbox.pack_start(rb)
145 # Cache the methods
146 self._mpsel_get_act = [b.get_active for b in rbtns]
147 self._mpsel_set_act = [b.set_active for b in rbtns]
148 self._mpsel_set_sens = [b.set_sensitive for b in rbtns]
149 for b in rbtns:
150 b.connect('toggled', self._update_scrollbars)
151 vbox2.pack_start(rbtnbox, False, False)
153 vbox2.pack_start(gtk.HSeparator(), False, False)
155 x10_toggle_btn = gtk.ToggleButton('x10 mode')
156 x10_toggle_btn.set_mode(True)
157 x10_toggle_btn.connect('toggled', self.x10_toggle_btn_toggled,
158 graphs_cbx, store)
160 span_toggle_btn = gtk.ToggleButton(_('Span'))
161 span_toggle_btn.set_mode(True)
162 span_toggle_btn.connect('toggled', self.span_toggle_btn_toggled,
163 graphs_cbx, store)
165 save_fig_btn = gtk.Button(_('Export'))
166 save_fig_btn.connect('clicked', self.save_fig_btn_clicked)
168 coords_lbl1 = gtk.Label('')
169 coords_lbl1.set_alignment(0.1, 0.5)
170 coords_lbl2 = gtk.Label('')
171 coords_lbl2.set_alignment(0.1, 0.5)
173 self._cbx = graphs_cbx
174 self._btn = x10_toggle_btn
175 self._coords_lbl1 = coords_lbl1
176 self._coords_lbl2 = coords_lbl2
178 graphs_cbx.connect('changed', self.graphs_cbx_changed, x10_toggle_btn,
179 span_toggle_btn, store)
180 hbar.connect('change-value', self.hscroll_change_value, graphs_cbx,
181 store)
182 hbar.connect('enter-notify-event', self.disable_adj_update_on_draw)
183 hbar.connect('leave-notify-event', self.enable_adj_update_on_draw)
184 hbar.connect('button-press-event', self.hadj_pressed)
185 hbar.connect('button-release-event', self.hadj_released)
186 vbar.connect('change-value', self.vscroll_change_value, graphs_cbx,
187 store)
188 vbar.connect('enter-notify-event', self.disable_adj_update_on_draw)
189 vbar.connect('leave-notify-event', self.enable_adj_update_on_draw)
190 vbar.connect('button-press-event', self.vadj_pressed)
191 vbar.connect('button-release-event', self.vadj_released)
192 vbox2.pack_start(x10_toggle_btn, False, False)
193 vbox2.pack_start(span_toggle_btn, False, False)
194 vbox2.pack_start(save_fig_btn, False, False)
195 vbox2.pack_end(coords_lbl1, False, False)
196 vbox2.pack_end(coords_lbl2, False, False)
198 hbox1.pack_start(vbox2, False, False)
200 w.resize(640, 480)
201 w.show_all()
202 self.window = w
203 if sigs:
204 self.add(sigs)
205 # # Update canvas for SpanSelector of Graphs
206 for gr in self.graphs:
207 if hasattr(gr, 'span'):
208 gr.span.new_axes(gr)
210 def set_layout(self, layout='quad'):
211 oscopy.Figure.set_layout(self, layout)
212 iter = self._cbx_store.get_iter_first()
213 for g in self.graphs:
214 iter = self._cbx_store.iter_next(iter)
215 if self._layout == 'horiz':
216 g.span = SpanSelector(g, g.onselect, 'horizontal',
217 useblit=True)
218 g.span.visible = self._cbx_store.get_value(iter, IOSCOPY_COL_SPAN)
219 self.hbar.set_sensitive(True)
220 self.hbar.show()
221 self.vbar.set_sensitive(False)
222 self.vbar.hide()
223 elif self._layout == 'vert':
224 g.span = SpanSelector(g, g.onselect, 'vertical',
225 useblit=True)
226 g.span.visible = self._cbx_store.get_value(iter, IOSCOPY_COL_SPAN)
227 self.hbar.set_sensitive(False)
228 self.hbar.hide()
229 self.vbar.set_sensitive(True)
230 self.vbar.show()
231 elif self._layout == 'quad':
232 g.span = MyRectangleSelector(g, g.onselect, rectprops=dict(facecolor='red', edgecolor = 'black', alpha=0.5, fill=True),
233 useblit=True)
234 g.span.active = self._cbx_store.get_value(iter, IOSCOPY_COL_SPAN)
235 self.hbar.set_sensitive(True)
236 self.hbar.show()
237 self.vbar.set_sensitive(True)
238 self.vbar.show()
240 def graphs_cbx_changed(self, graphs_cbx, x10_toggle_btn, span_toggle_btn, store):
241 iter = graphs_cbx.get_active_iter()
242 if store.get_string_from_iter(iter) == '0':
243 # Do all the graphs have same state?
244 store.iter_next(iter)
245 val = store.get_value(iter, IOSCOPY_COL_X10)
246 while iter is not None:
247 if val != store.get_value(iter, IOSCOPY_COL_X10):
248 # Yes, set the button into inconsistent state
249 x10_toggle_btn.set_inconsistent(True)
250 break
251 iter = store.iter_next(iter)
252 iter = store.get_iter_first()
253 store.iter_next(iter)
254 val = store.get_value(iter, IOSCOPY_COL_SPAN)
255 self.hbar.set_adjustment(store.get_value(iter, IOSCOPY_COL_HADJ))
256 self.vbar.set_adjustment(store.get_value(iter, IOSCOPY_COL_VADJ))
257 while iter is not None:
258 if val != store.get_value(iter, IOSCOPY_COL_SPAN):
259 # Yes, set the button into inconsistent state
260 span_toggle_btn.set_inconsistent(True)
261 break
262 iter = store.iter_next(iter)
263 else:
264 x10_toggle_btn.set_inconsistent(False)
265 x10_toggle_btn.set_active(store.get_value(iter, IOSCOPY_COL_X10))
266 span_toggle_btn.set_inconsistent(False)
267 span_toggle_btn.set_active(store.get_value(iter, IOSCOPY_COL_SPAN))
268 self.hbar.set_adjustment(store.get_value(iter, IOSCOPY_COL_HADJ))
269 self.vbar.set_adjustment(store.get_value(iter, IOSCOPY_COL_VADJ))
271 def x10_toggle_btn_toggled(self, x10_toggle_btn, graphs_cbx, store):
272 center = None
273 iter = graphs_cbx.get_active_iter()
274 a = x10_toggle_btn.get_active()
275 val = -.1 if a else -1 # x10 zoom
276 x10_toggle_btn.set_inconsistent(False)
277 layout = self.layout
278 if iter is not None:
279 store.set_value(iter, IOSCOPY_COL_X10, a)
280 grnum = int(store.get_string_from_iter(iter))
281 if store.get_string_from_iter(iter) == '0':
282 # Set the value for all graphs
283 iter = store.iter_next(iter)
284 while iter is not None:
285 store.set_value(iter, IOSCOPY_COL_X10, a)
286 grnum = int(store.get_string_from_iter(iter))
287 if grnum > len(self.graphs):
288 break
289 g = self.graphs[grnum - 1]
290 self._zoom_x10(g, a)
291 iter = store.iter_next(iter)
292 else:
293 g = self.graphs[grnum - 1]
294 self._zoom_x10(g, a)
295 self.canvas.draw_idle()
297 def _zoom_x10(self, g, a):
298 layout = self._layout
299 result = g.dataLim.frozen()
300 if layout == 'horiz' or layout == 'quad':
301 g.set_xlim(*result.intervalx)
302 if layout == 'vert' or layout == 'quad':
303 g.set_ylim(*result.intervaly)
304 if a:
305 result = g.bbox.expanded(0.1, 0.1).transformed(g.transData.inverted())
306 if layout == 'horiz' or layout == 'quad':
307 g.set_xlim(*result.intervalx)
308 if layout == 'vert' or layout == 'quad':
309 g.set_ylim(*result.intervaly)
311 def span_toggle_btn_toggled(self, span_toggle_btn, graphs_cbx, store):
312 iter = graphs_cbx.get_active_iter()
313 a = span_toggle_btn.get_active()
314 span_toggle_btn.set_inconsistent(False)
315 if iter is not None:
316 store.set_value(iter, IOSCOPY_COL_SPAN, a)
317 grnum = int(store.get_string_from_iter(iter))
318 if store.get_string_from_iter(iter) == '0':
319 # Set the value for all graphs
320 iter = store.iter_next(iter)
321 while iter is not None:
322 store.set_value(iter, IOSCOPY_COL_SPAN, a)
323 grnum = int(store.get_string_from_iter(iter))
324 if grnum > len(self.graphs):
325 break
326 if hasattr(self.graphs[grnum - 1].span, 'active'):
327 self.graphs[grnum - 1].span.active = a
328 elif hasattr(self.graphs[grnum - 1].span, 'visible'):
329 self.graphs[grnum - 1].span.visible = a
330 iter = store.iter_next(iter)
331 else:
332 if hasattr(self.graphs[grnum - 1].span, 'active'):
333 self.graphs[grnum - 1].span.active = a
334 elif hasattr(self.graphs[grnum - 1].span, 'visible'):
335 self.graphs[grnum - 1].span.visible = a
336 self.canvas.draw()
338 def save_fig_btn_clicked(self, save_fig_btn):
339 fname, format = self.get_filechooser().get_filename_from_user()
340 if fname:
341 try:
342 self.canvas.print_figure(fname, format=format)
343 except Exception, e:
344 error_msg_gtk(str(e), parent=self)
346 def get_filechooser(self):
347 # From matplotlib/backends/backend_gtk.py
348 return FileChooserDialog(
349 title=_('Save the figure'),
350 parent=self.window,
351 filetypes=self.canvas.get_supported_filetypes(),
352 default_filetype=self.canvas.get_default_filetype())
354 def _key_press(self, event):
355 if event.inaxes is not None:
356 g = event.inaxes
357 if event.key == 'z':
358 self._zoom_on_event(event, DEFAULT_ZOOM_FACTOR)
359 self.canvas.draw_idle()
360 elif event.key == 'Z':
361 self._zoom_on_event(event, 1. / DEFAULT_ZOOM_FACTOR)
362 self.canvas.draw_idle()
363 elif event.key == 'l':
364 result = g.bbox.translated(-DEFAULT_PAN_FACTOR, 0).transformed(g.transData.inverted())
365 g.set_xlim(*result.intervalx)
366 g.set_ylim(*result.intervaly)
367 self.canvas.draw_idle()
368 elif event.key == 'r':
369 result = g.bbox.translated(DEFAULT_PAN_FACTOR, 0).transformed(g.transData.inverted())
370 g.set_xlim(*result.intervalx)
371 g.set_ylim(*result.intervaly)
372 self.canvas.draw_idle()
373 return True
375 def _zoom_on_event(self, event, factor):
376 g = event.inaxes
377 if g is None or factor == 1:
378 return
379 layout = self.layout
380 result = g.bbox.expanded(factor, factor).transformed(g.transData.inverted())
381 # Localisation of event.xdata in the new transform
382 if layout == 'horiz' or layout == 'quad':
383 g.set_xlim(*result.intervalx)
384 if layout == 'vert' or layout == 'quad':
385 g.set_ylim(*result.intervaly)
386 # Then place it under cursor
387 b = g.transData.transform_point([event.xdata, event.ydata])
388 result = g.bbox.translated(-(event.x - b[0]), -(event.y - b[1])).transformed(g.transData.inverted())
389 if layout == 'horiz' or layout == 'quad':
390 g.set_xlim(*result.intervalx)
391 if layout == 'vert' or layout == 'quad':
392 g.set_ylim(*result.intervaly)
393 # Limit to data boundaries
394 (dxmin, dxmax) = (g.dataLim.xmin, g.dataLim.xmax)
395 (xmin, xmax) = g.get_xbound()
396 g.set_xbound(max(dxmin, xmin), min(dxmax, xmax))
397 (dymin, dymax) = (g.dataLim.ymin, g.dataLim.ymax)
398 (ymin, ymax) = g.get_ybound()
399 g.set_ybound(max(dymin, ymin), min(dymax, ymax))
401 def drag_data_received_cb(self, widget, drag_context, x, y, selection,\
402 target_type, time, ctxtsignals):
403 # Event handling issue: this drag and drop callback is
404 # processed before matplotlib callback _axes_enter. Therefore
405 # when dropping, self._current_graph is not valid: it contains
406 # the last graph.
407 # The workaround is to retrieve the Graph by creating a Matplotlib
408 # LocationEvent considering inverse 'y' coordinates
409 if target_type == self._TARGET_TYPE_SIGNAL:
410 canvas = self.canvas
411 my_y = canvas.allocation.height - y
412 event = LocationEvent('axes_enter_event', canvas, x, my_y)
413 signals = {}
414 for name in selection.data.split():
415 signals[name] = ctxtsignals[name]
416 if event.inaxes is not None:
417 # Graph not found
418 event.inaxes.insert(signals)
419 self.canvas.draw()
421 def add(self, args):
422 oscopy.Figure.add(self, args)
423 store = self._cbx_store
424 iter = store.get_iter_first()
425 iter = store.iter_next(iter) # First item always sensitive
426 while iter is not None:
427 grnum = int(store.get_string_from_iter(iter))
428 if grnum > len(self.graphs):
429 store.set_value(iter, IOSCOPY_COL_VIS, False)
430 self._mpsel_set_sens[grnum - 1](False) # master pan
431 else:
432 store.set_value(iter, IOSCOPY_COL_VIS, True)
433 self._mpsel_set_sens[grnum - 1](True) # master pan
434 iter = store.iter_next(iter)
436 def delete(self, args):
437 oscopy.Figure.delete(self, args)
438 store = self._cbx_store
439 iter = store.get_iter_first()
440 iter = store.iter_next(iter) # First item always sensitive
441 while iter is not None:
442 grnum = int(store.get_string_from_iter(iter))
443 if grnum > len(self.graphs):
444 store.set_value(iter, IOSCOPY_COL_VIS, False)
445 self._mpsel_set_sens[grnum - 1](False) # master pan
446 if grnum > 1 and self._mpsel_get_act[grnum - 1]():
447 self._mpsel_set_act[grnum - 2](True)
448 else:
449 store.set_value(iter, IOSCOPY_COL_VIS, True)
450 self._mpsel_set_sens[grnum - 1](True) # master pan
451 iter = store.iter_next(iter)
453 def _button_press(self, event):
454 if event.button == 3:
455 menu = self._create_figure_popup_menu(event.canvas.figure, event.inaxes)
456 menu.show_all()
457 menu.popup(None, None, None, event.button, event.guiEvent.time)
459 def _mouse_scroll(self, event):
460 if event.button == 'up':
461 if event.inaxes is None:
462 return False
463 self._zoom_on_event(event, DEFAULT_ZOOM_FACTOR)
464 self.canvas.draw_idle()
465 elif event.button == 'down':
466 if event.inaxes is None:
467 return False
468 self._zoom_on_event(event, 1. / DEFAULT_ZOOM_FACTOR)
469 self.canvas.draw_idle()
470 return True
472 def _axes_enter(self, event):
473 # self._figure_enter(event)
474 # self._current_graph = event.inaxes
476 # axes_num = event.canvas.figure.axes.index(event.inaxes) + 1
477 # fig_num = self._ctxt.figures.index(self._current_figure) + 1
478 # self._app_exec('%%oselect %d-%d' % (fig_num, axes_num))
479 pass
481 def _axes_leave(self, event):
482 # Unused for better user interaction
483 # self._current_graph = None
484 pass
486 def _figure_enter(self, event):
487 # self._current_figure = event.canvas.figure
488 # if hasattr(event, 'inaxes') and event.inaxes is not None:
489 # axes_num = event.canvas.figure.axes.index(event.inaxes) + 1
490 # else:
491 # axes_num = 1
492 # fig_num = self._ctxt.figures.index(self._current_figure) + 1
493 # self._app_exec('%%oselect %d-%d' % (fig_num, axes_num))
494 self.canvas.grab_focus()
495 pass
497 def _figure_leave(self, event):
498 # self._current_figure = None
499 pass
501 def _show_coords(self, event):
502 a = event.inaxes
503 if a is not None:
504 x = '%.3f %s%s' % (event.xdata,
505 oscopy.factors_to_names[a.scale_factors[0]][0],
506 a.unit[0])
507 y = '%.3f %s%s' % (event.ydata,
508 oscopy.factors_to_names[a.scale_factors[1]][0],
509 a.unit[1])
510 self._coords_lbl1.set_text(x)
511 self._coords_lbl2.set_text(y)
512 else:
513 self._coords_lbl1.set_text('')
514 self._coords_lbl2.set_text('')
517 def _create_figure_popup_menu(self, figure, graph):
518 figmenu = gui.menus.FigureMenu()
519 return figmenu.create_menu(figure, graph)
521 def _update_scrollbars(self, unused):
522 # Unused is not used but can be either a MPL event or a togglebutton
523 (lower, upper) = (0, 1)
524 (xpgs_min, ypgs_min) = (1, 1)
525 (xvs, yvs) = ([], [])
526 for grnum, gr in enumerate(self.graphs):
527 (xv, xpgs, yv, ypgs) = self._update_graph_adj(grnum, gr)
528 xpgs_min = min(xpgs_min, xpgs)
529 ypgs_min = min(ypgs_min, ypgs)
530 xvs.append(xv)
531 yvs.append(yv)
532 # Then for all graphs...
533 for i, get_act in enumerate(self._mpsel_get_act):
534 if get_act():
535 break
536 if xvs:
537 hadj = self._cbx_store[0][IOSCOPY_COL_HADJ]
538 hadj.configure(xvs[i], lower, upper,
539 xpgs_min / 10.0, xpgs_min, xpgs_min)
540 if yvs:
541 vadj = self._cbx_store[0][IOSCOPY_COL_VADJ]
542 vadj.configure(-yvs[i], -upper, lower,
543 ypgs_min / 10.0, ypgs_min, ypgs_min)
545 def _update_graph_adj(self, grnum, g):
546 (lower, upper) = (0, 1)
548 hadj = self._cbx_store[grnum + 1][IOSCOPY_COL_HADJ]
549 vadj = self._cbx_store[grnum + 1][IOSCOPY_COL_VADJ]
551 # Get data Bbox and view Bbox in pixels
552 (x0data, y0data, wdata, hdata) = g.dataLim.transformed(g.transData).bounds
553 (xvmin, yvmin, wv, hv) = g.bbox.bounds
555 # Compute page_size and value relatively to data bounds
556 xpage_size = wv / wdata
557 ypage_size = hv / hdata
558 xvalue = (xvmin - x0data) / wdata
559 yvalue = (yvmin - y0data + hv) / hdata
561 xstep_increment = xpage_size / 10
562 xpage_increment = xpage_size
563 hadj.configure(xvalue, lower, upper,
564 xstep_increment, xpage_increment, xpage_size)
565 ystep_increment = ypage_size / 10
566 ypage_increment = ypage_size
567 # Need to revert scroll bar to have minimum on bottom
568 vadj.configure(-yvalue, -upper, lower,
569 ystep_increment, ypage_increment, ypage_size)
570 return (xvalue, xpage_size, yvalue, ypage_size)
572 def hscroll_change_value(self, widget, scroll, value, cbx, store):
573 if self.layout == 'vert':
574 return False
575 iter = cbx.get_active_iter()
576 layout = self.layout
577 for mp, get_act in enumerate(self._mpsel_get_act):
578 if get_act():
579 break # Here is the master pan graph
580 master_pan_gr = self.graphs[mp]
581 if iter is not None:
582 grnum = int(store.get_string_from_iter(iter))
583 if store.get_string_from_iter(iter) == '0':
584 # move only graphs having the same unit as selected master
585 adj = widget.get_adjustment()
586 val = adj.get_value()
587 delta = (val - self._hadjpreval) if self._hadjpreval is not None else 0
588 iter = store.iter_next(iter)
589 while iter is not None:
590 grnum = int(store.get_string_from_iter(iter))
591 if grnum > len(self.graphs):
592 break
593 g = self.graphs[grnum - 1]
594 # Pan
595 if self._hadjpreval is None or g.get_unit()[0] != master_pan_gr.get_unit()[0]:
596 iter = store.iter_next(iter)
597 continue
598 hadj = self._cbx_store[grnum][IOSCOPY_COL_HADJ]
599 curval = hadj.get_value()
600 if ((val < curval and delta < 0) or (val > curval and delta > 0)) and val < 1 - hadj.get_page_size():
601 self._translate_to(g, val, None)
602 hadj.set_value(val) # Because the callback is disabled
603 iter = store.iter_next(iter)
604 self._hadjpreval = widget.get_adjustment().get_value()
605 else:
606 g = self.graphs[grnum - 1]
607 # Pan
608 self._translate_to(g, widget.get_value(), None)
609 self.canvas.draw_idle()
610 return False
612 def vscroll_change_value(self, widget, scroll, value, cbx, store):
613 if self.layout == 'horiz':
614 return False
615 iter = cbx.get_active_iter()
616 layout = self.layout
617 for mp, get_act in enumerate(self._mpsel_get_act):
618 if get_act():
619 break # Here is the master pan graph
620 master_pan_gr = self.graphs[mp]
621 if iter is not None:
622 grnum = int(store.get_string_from_iter(iter))
623 if store.get_string_from_iter(iter) == '0':
624 # Move only graphs having the same unit as selected master
625 adj = widget.get_adjustment()
626 val = adj.get_value()
627 delta = (val - self._vadjpreval) if self._vadjpreval is not None else 0
628 iter = store.iter_next(iter)
629 while iter is not None:
630 grnum = int(store.get_string_from_iter(iter))
631 if grnum > len(self.graphs):
632 break
633 g = self.graphs[grnum - 1]
634 # Pan
635 if self._vadjpreval is None or g.get_unit()[1] != master_pan_gr.get_unit()[1]:
636 iter = store.iter_next(iter)
637 continue
638 vadj = self._cbx_store[grnum][IOSCOPY_COL_VADJ]
639 curval = vadj.get_value()
640 if ((val < curval and delta < 0) or (val > curval and delta > 0)) and val < -vadj.get_page_size():
641 self._translate_to(g, None, val)
642 vadj.set_value(val) # Because the callback is disabled
643 iter = store.iter_next(iter)
644 self._vadjpreval = widget.get_adjustment().get_value()
645 else:
646 g = self.graphs[grnum - 1]
647 # Pan
648 self._translate_to(g, None, widget.get_value())
649 self.canvas.draw_idle()
650 return False
652 def _translate_to(self, g, xvalue, yvalue):
653 layout = self.layout
655 (x0data, y0data, wdata, hdata) = g.dataLim.transformed(g.transData).bounds
656 x0, y0, w, h = g.bbox.bounds
657 x0_new = (xvalue * wdata + x0data) if xvalue is not None else x0
658 # Need to revert scroll bar to have minimum on bottom
659 y0_new = (-yvalue * hdata + y0data - h) if yvalue is not None else y0
660 result = Bbox.from_bounds(x0_new, y0_new, w, h).transformed(g.transData.inverted())
661 g.set_xlim(*result.intervalx)
662 g.set_ylim(*result.intervaly)
664 def disable_adj_update_on_draw(self, widget, event):
665 layout = self.layout
666 if widget == self.hbar and layout not in ['horiz', 'quad']:
667 return
668 if widget == self.vbar and layout not in ['vert', 'quad']:
669 return
670 if self._draw_hid is not None:
671 self.canvas.mpl_disconnect(self._draw_hid)
672 self._draw_hid = None
674 def enable_adj_update_on_draw(self, widget, event):
675 layout = self.layout
676 if widget == self.hbar and layout not in ['horiz', 'quad']:
677 return
678 if widget == self.vbar and layout not in ['vert', 'quad']:
679 return
680 if self._draw_hid is None and not event.state & gtk.gdk.BUTTON1_MASK:
681 self._draw_hid = self.canvas.mpl_connect('draw_event', self._update_scrollbars)
683 def hadj_pressed(self, widget, event):
684 self._hadjpreval = widget.get_value()
686 def hadj_released(self, widget, event):
687 self._hadjpreval = None
689 def vadj_pressed(self, widget, event):
690 self._vadjpreval = widget.get_value()
692 def vadj_released(self, widget, event):
693 self._vadjpreval = None
695 layout = property(oscopy.Figure.get_layout, set_layout)
697 def error_msg_gtk(msg, parent=None):
698 # From matplotlib/backends/backend_gtk.py
699 if parent is not None: # find the toplevel gtk.Window
700 parent = parent.get_toplevel()
701 if parent.flags() & gtk.TOPLEVEL == 0:
702 parent = None
704 if not is_string_like(msg):
705 msg = ','.join(map(str,msg))
707 dialog = gtk.MessageDialog(
708 parent = parent,
709 type = gtk.MESSAGE_ERROR,
710 buttons = gtk.BUTTONS_OK,
711 message_format = msg)
712 dialog.run()
713 dialog.destroy()