ui: support for figure enter/leave events
[oscopy/ivan.git] / ui.py
blobc6b6be15cc279beee9b9a21a7cacec8945e72ab8
1 import gobject
2 import gtk
4 from oscopy.readers.detect_reader import DetectReader
5 from oscopy import Figure
6 import oscopy
8 from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
9 from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
11 def report_error(parent, msg):
12 dlg = gtk.MessageDialog(parent,
13 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
14 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg)
15 dlg.set_title(parent.get_title())
16 dlg.run()
17 dlg.destroy()
19 class App(object):
20 __ui = '''<ui>
21 <menubar name="MenuBar">
22 <menu action="File">
23 <menuitem action="Add file"/>
24 <menuitem action="Quit"/>
25 </menu>
26 </menubar>
27 </ui>'''
29 def __init__(self):
30 self._scale_to_str = {'lin': 'Linear', 'logx': 'LogX', 'logy': 'LogY',\
31 'loglog': 'Loglog'}
32 self._layout_to_str = {'horiz': 'Horizontal', 'vert':'Vertical',\
33 'quad':'Quad'}
34 self._ctxt = oscopy.Context()
35 self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
36 self._create_widgets()
37 #self._add_file('demo/irf540.dat')
38 #self._add_file('demo/ac.dat')
39 #self._add_file('demo/res.dat')
40 self._figcount = 0
41 self._windows_to_figures = {}
42 self._current_graph = None
43 self._current_figure = None
45 def _add_file(self, filename):
46 try:
47 self._ctxt.read(filename)
48 except NotImplementedError:
49 report_error(self._mainwindow,
50 'Could not find a reader for %s' % filename)
51 return
52 it = self._store.append(None, (filename, None))
53 for name, sig in self._ctxt.readers[filename].signals.iteritems():
54 self._store.append(it, (name, sig))
56 def _action_add_file(self, action):
57 dlg = gtk.FileChooserDialog('Add file', parent=self._mainwindow,
58 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
59 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
60 resp = dlg.run()
61 filename = dlg.get_filename()
62 dlg.destroy()
63 if resp == gtk.RESPONSE_ACCEPT:
64 self._add_file(filename)
66 def _action_quit(self, action):
67 main_loop.quit()
69 def _create_menubar(self):
70 # tuple format:
71 # (name, stock-id, label, accelerator, tooltip, callback)
72 actions = [
73 ('File', None, '_File'),
74 ('Add file', gtk.STOCK_ADD, '_Add file', None, None,
75 self._action_add_file),
76 ('Quit', gtk.STOCK_QUIT, '_Quit', None, None,
77 self._action_quit),
79 actiongroup = gtk.ActionGroup('App')
80 actiongroup.add_actions(actions)
82 uimanager = gtk.UIManager()
83 uimanager.add_ui_from_string(self.__ui)
84 uimanager.insert_action_group(actiongroup, 0)
85 return uimanager.get_accel_group(), uimanager.get_widget('/MenuBar')
87 def _create_treeview(self):
88 col = gtk.TreeViewColumn('Signal', gtk.CellRendererText(), text=0)
89 tv = gtk.TreeView()
90 tv.append_column(col)
91 tv.set_model(self._store)
92 tv.connect('row-activated', self._row_activated)
93 return tv
95 def _create_widgets(self):
96 accel_group, self._menubar = self._create_menubar()
97 self._treeview = self._create_treeview()
99 sw = gtk.ScrolledWindow()
100 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
101 sw.add(self._treeview)
103 vbox = gtk.VBox()
104 vbox.pack_start(self._menubar, False)
105 vbox.pack_start(sw)
107 w = self._mainwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
108 w.set_title('Oscopy GUI')
109 w.add(vbox)
110 w.add_accel_group(accel_group)
111 w.connect('destroy', lambda *x: self._action_quit(None))
112 w.set_default_size(400, 300)
113 w.show_all()
115 def _signals_menu_item_activated(self, menuitem, user_data):
116 fig, parent_it, it = user_data
117 name, sig = self._store.get(it, 0, 1)
118 print 'Adding signal %s to %s' % (name, fig)
119 if not fig.graphs:
120 fig.add({name:sig})
121 else:
122 if self._current_graph is not None:
123 self._current_graph.insert({name: sig})
124 if fig.canvas is not None:
125 fig.canvas.draw()
127 def _graph_menu_item_activated(self, menuitem, user_data):
128 fig = user_data
129 fig.add()
130 fig.canvas.draw()
132 def _scale_menu_item_activated(self, menuitem, user_data):
133 fig, scale = user_data
134 self._current_graph.set_scale(scale)
135 if fig.canvas is not None:
136 fig.canvas.draw()
138 def _layout_menu_item_activated(self, menuitem, user_data):
139 fig, layout = user_data
140 fig.layout = layout
141 if fig.canvas is not None:
142 fig.canvas.draw()
144 def _create_scale_menu(self, fig):
145 menu = gtk.Menu()
146 for scale in self._scale_to_str.keys():
147 item = gtk.CheckMenuItem(self._scale_to_str[scale])
148 item.set_active(self._current_graph.scale == scale)
149 item.connect('activate', self._scale_menu_item_activated,
150 (fig, scale))
151 menu.append(item)
152 return menu
154 def _create_layout_menu(self, fig):
155 menu = gtk.Menu()
156 for layout in self._layout_to_str.keys():
157 item = gtk.CheckMenuItem(self._layout_to_str[layout])
158 item.set_active(fig.layout == layout)
159 item.connect('activate', self._layout_menu_item_activated,
160 (fig, layout))
161 menu.append(item)
162 return menu
164 def _create_graph_menu(self, fig):
165 menu = gtk.Menu()
166 item_add = gtk.MenuItem('Add graph')
167 item_add.connect('activate', self._graph_menu_item_activated,
168 (fig))
169 menu.append(item_add)
170 return menu
172 def _create_signals_menu(self, fig, parent_it):
173 menu = gtk.Menu()
174 it = self._store.iter_children(parent_it)
175 while it is not None:
176 name = self._store.get_value(it, 0)
177 item = gtk.MenuItem(name)
178 item.connect('activate', self._signals_menu_item_activated,
179 (fig, parent_it, it))
180 menu.append(item)
181 it = self._store.iter_next(it)
182 return menu
184 def _create_filename_menu(self, fig):
185 it = self._store.get_iter_root()
186 if it is None:
187 return gtk.Menu()
189 menu = gtk.Menu()
190 while it is not None:
191 filename = self._store.get_value(it, 0)
192 item = gtk.MenuItem(filename)
193 item.set_submenu(self._create_signals_menu(fig, it))
194 menu.append(item)
195 it = self._store.iter_next(it)
196 return menu
198 def _create_figure_popup_menu(self, fig):
199 menu = gtk.Menu()
200 if self._current_graph is None:
201 item_nograph = gtk.MenuItem('No graph selected')
202 menu.append(item_nograph)
203 return menu
204 item_add = gtk.MenuItem('Add signal')
205 item_add.set_submenu(self._create_filename_menu(fig))
206 menu.append(item_add)
207 item_scale = gtk.MenuItem('Scale')
208 item_scale.set_submenu(self._create_scale_menu(fig))
209 menu.append(item_scale)
210 item_layout = gtk.MenuItem('Layout')
211 item_layout.set_submenu(self._create_layout_menu(fig))
212 menu.append(item_layout)
213 item_graph = gtk.MenuItem('Graph')
214 item_graph.set_submenu(self._create_graph_menu(fig))
215 menu.append(item_graph)
216 return menu
218 def _button_press(self, widget, event):
219 if event.button == 3:
220 window = widget.parent.parent
221 fig = self._windows_to_figures[window]
222 menu = self._create_figure_popup_menu(fig)
223 menu.show_all()
224 menu.popup(None, None, None, event.button, event.time)
226 #TODO: _windows_to_figures consistency...
227 # think of a better way to map events to Figure objects
228 def _row_activated(self, widget, path, col):
229 if len(path) == 1:
230 return
232 row = self._store[path]
233 self._ctxt.create({row[0]: row[1]})
234 fig = self._ctxt.figures[len(self._ctxt.figures) - 1]
236 w = gtk.Window()
237 self._figcount += 1
238 self._windows_to_figures[w] = fig
239 w.set_title('Figure %d' % self._figcount)
240 vbox = gtk.VBox()
241 w.add(vbox)
242 canvas = FigureCanvas(fig)
243 canvas.connect('button-press-event', self._button_press)
244 canvas.mpl_connect('axes_enter_event', self._axes_enter)
245 canvas.mpl_connect('axes_leave_event', self._axes_leave)
246 canvas.mpl_connect('figure_enter_event', self._figure_enter)
247 canvas.mpl_connect('figure_leave_event', self._figure_leave)
248 vbox.pack_start(canvas)
249 toolbar = NavigationToolbar(canvas, w)
250 vbox.pack_start(toolbar, False, False)
251 w.resize(400, 300)
252 w.show_all()
254 def _axes_enter(self, event):
255 self._current_graph = event.inaxes
257 def _axes_leave(self, event):
258 # Unused for better user interaction
259 # self._current_graph = None
260 pass
262 def _figure_enter(self, event):
263 self._current_figure = event.canvas.figure
265 def _figure_leave(self, event):
266 # self._current_figure = None
267 pass
269 if __name__ == '__main__':
270 app = App()
271 main_loop = gobject.MainLoop()
272 main_loop.run()