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