gui/menus: move enter units dialog into separate class
[oscopy.git] / ui.py
blobb969e1574c7f5ad41ff98949441ae5fcc73c6ddc
1 import gobject
2 import gtk
3 import signal
4 import commands
6 import oscopy
8 from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
9 from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
10 import gui
12 def report_error(parent, msg):
13 dlg = gtk.MessageDialog(parent,
14 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
15 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg)
16 dlg.set_title(parent.get_title())
17 dlg.run()
18 dlg.destroy()
20 class App(object):
21 __ui = '''<ui>
22 <menubar name="MenuBar">
23 <menu action="File">
24 <menuitem action="Add file"/>
25 <menuitem action="Update files"/>
26 <menuitem action="New Math Signal..."/>
27 <menuitem action="Run netlister and simulate..."/>
28 <menuitem action="Quit"/>
29 </menu>
30 </menubar>
31 </ui>'''
33 def __init__(self):
34 self._scale_to_str = {'lin': 'Linear', 'logx': 'LogX', 'logy': 'LogY',\
35 'loglog': 'Loglog'}
36 self._figcount = 0
37 self._windows_to_figures = {}
38 self._current_graph = None
39 self._current_figure = None
40 self._TARGET_TYPE_SIGNAL = 10354
41 self._from_signal_list = [("oscopy-signals", gtk.TARGET_SAME_APP,\
42 self._TARGET_TYPE_SIGNAL)]
43 self._to_figure = [("oscopy-signals", gtk.TARGET_SAME_APP,\
44 self._TARGET_TYPE_SIGNAL)]
45 self._ctxt = oscopy.Context()
46 self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
47 self._create_widgets()
48 self._add_file('demo/irf540.dat')
49 #self._add_file('demo/ac.dat')
50 #self._add_file('demo/res.dat')
52 def _add_file(self, filename):
53 try:
54 self._ctxt.read(filename)
55 except NotImplementedError:
56 report_error(self._mainwindow,
57 'Could not find a reader for %s' % filename)
58 return
59 it = self._store.append(None, (filename, None))
60 for name, sig in self._ctxt.readers[filename].signals.iteritems():
61 self._store.append(it, (name, sig))
63 def _action_add_file(self, action):
64 dlg = gtk.FileChooserDialog('Add file', parent=self._mainwindow,
65 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
66 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
67 resp = dlg.run()
68 filename = dlg.get_filename()
69 dlg.destroy()
70 if resp == gtk.RESPONSE_ACCEPT:
71 self._add_file(filename)
73 def _action_update(self, action):
74 self._ctxt.update()
76 def _action_new_math(self, action):
77 dlg = gtk.Dialog('New math signal', parent=self._mainwindow,
78 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
79 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
81 # Label and entry
82 hbox = gtk.HBox()
83 label = gtk.Label('Expression:')
84 hbox.pack_start(label)
85 entry = gtk.Entry()
86 hbox.pack_start(entry)
87 dlg.vbox.pack_start(hbox)
89 dlg.show_all()
90 resp = dlg.run()
91 if resp == gtk.RESPONSE_ACCEPT:
92 expr = entry.get_text()
93 try:
94 self._ctxt.math(expr)
95 except ReadError, e:
96 report_error(self._mainwindow,
97 'Could not find a reader for %s' % expr)
98 return
99 it = self._store.append(None, (expr, None))
100 for name, sig in self._ctxt.readers[expr].signals.iteritems():
101 self._store.append(it, (name, sig))
103 dlg.destroy()
106 def _action_quit(self, action):
107 main_loop.quit()
109 def _create_menubar(self):
110 # tuple format:
111 # (name, stock-id, label, accelerator, tooltip, callback)
112 actions = [
113 ('File', None, '_File'),
114 ('Add file', gtk.STOCK_ADD, '_Add file', None, None,
115 self._action_add_file),
116 ('Update files', gtk.STOCK_REFRESH, '_Update', None, None,
117 self._action_update),
118 ("New Math Signal...", gtk.STOCK_NEW, '_New Math Signal', None,
119 None, self._action_new_math),
120 ("Run netlister and simulate...", gtk.STOCK_MEDIA_FORWARD,\
121 "_Run netlister and simulate...", None, None,\
122 self._action_netlist_and_simulate),
123 ('Quit', gtk.STOCK_QUIT, '_Quit', None, None,
124 self._action_quit),
126 actiongroup = gtk.ActionGroup('App')
127 actiongroup.add_actions(actions)
129 uimanager = gtk.UIManager()
130 uimanager.add_ui_from_string(self.__ui)
131 uimanager.insert_action_group(actiongroup, 0)
132 return uimanager.get_accel_group(), uimanager.get_widget('/MenuBar')
134 def _create_treeview(self):
135 col = gtk.TreeViewColumn('Signal', gtk.CellRendererText(), text=0)
136 tv = gtk.TreeView()
137 tv.append_column(col)
138 tv.set_model(self._store)
139 tv.connect('row-activated', self._row_activated)
140 tv.connect('button-press-event', self._treeview_button_press)
141 tv.connect('drag_data_get', self._drag_data_get_cb)
142 tv.drag_source_set(gtk.gdk.BUTTON1_MASK,\
143 self._from_signal_list,\
144 gtk.gdk.ACTION_COPY)
145 return tv
147 def _create_widgets(self):
148 accel_group, self._menubar = self._create_menubar()
149 self._treeview = self._create_treeview()
151 sw = gtk.ScrolledWindow()
152 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
153 sw.add(self._treeview)
155 vbox = gtk.VBox()
156 vbox.pack_start(self._menubar, False)
157 vbox.pack_start(sw)
159 w = self._mainwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
160 w.set_title('Oscopy GUI')
161 w.add(vbox)
162 w.add_accel_group(accel_group)
163 w.connect('destroy', lambda *x: self._action_quit(None))
164 w.set_default_size(400, 300)
165 w.show_all()
168 def _create_figure_popup_menu(self, figure, graph):
169 figmenu = gui.menus.FigureMenu()
170 return figmenu.create_menu(self._store, figure, graph)
172 def _create_treeview_popup_menu(self, signals):
173 menu = gtk.Menu()
174 if not signals:
175 item_none = gtk.MenuItem("No signal selected")
176 menu.append(item_none)
177 return menu
178 for name, signal in signals.iteritems():
179 item_freeze = gtk.CheckMenuItem("Freeze %s" % name)
180 item_freeze.set_active(signal.freeze)
181 item_freeze.connect('activate',\
182 self._signal_freeze_menu_item_activated,\
183 (signal))
184 menu.append(item_freeze)
185 return menu
187 def _signal_freeze_menu_item_activated(self, menuitem, signal):
188 signal.freeze = not signal.freeze
189 # Modify also the signal in the treeview
190 # (italic font? gray font color? a freeze column?)
192 def _treeview_button_press(self, widget, event):
193 if event.button == 3:
194 tv = widget
195 path, tvc, x, y = tv.get_path_at_pos(int(event.x), int(event.y))
196 if len(path) == 1:
197 return
198 tv.set_cursor(path)
199 row = self._store[path]
200 signals = {row[0]: row[1]}
201 menu = self._create_treeview_popup_menu(signals)
202 menu.show_all()
203 menu.popup(None, None, None, event.button, event.time)
205 def _button_press(self, event):
206 if event.button == 3:
207 menu = self._create_figure_popup_menu(event.canvas.figure, event.inaxes)
208 menu.show_all()
209 menu.popup(None, None, None, event.button, event.guiEvent.time)
211 #TODO: _windows_to_figures consistency...
212 # think of a better way to map events to Figure objects
213 def _row_activated(self, widget, path, col):
214 if len(path) == 1:
215 return
217 row = self._store[path]
218 self._ctxt.create({row[0]: row[1]})
219 fig = self._ctxt.figures[len(self._ctxt.figures) - 1]
221 w = gtk.Window()
222 self._figcount += 1
223 self._windows_to_figures[w] = fig
224 w.set_title('Figure %d' % self._figcount)
225 vbox = gtk.VBox()
226 w.add(vbox)
227 canvas = FigureCanvas(fig)
228 canvas.mpl_connect('button_press_event', self._button_press)
229 canvas.mpl_connect('axes_enter_event', self._axes_enter)
230 canvas.mpl_connect('axes_leave_event', self._axes_leave)
231 canvas.mpl_connect('figure_enter_event', self._figure_enter)
232 canvas.mpl_connect('figure_leave_event', self._figure_leave)
233 w.connect("drag_data_received", self._drag_data_received_cb)
234 w.drag_dest_set(gtk.DEST_DEFAULT_MOTION |\
235 gtk.DEST_DEFAULT_HIGHLIGHT |\
236 gtk.DEST_DEFAULT_DROP,
237 self._to_figure, gtk.gdk.ACTION_COPY)
238 vbox.pack_start(canvas)
239 toolbar = NavigationToolbar(canvas, w)
240 vbox.pack_start(toolbar, False, False)
241 w.resize(400, 300)
242 w.show_all()
244 def _axes_enter(self, event):
245 self._current_graph = event.inaxes
247 def _axes_leave(self, event):
248 # Unused for better user interaction
249 # self._current_graph = None
250 pass
252 def _figure_enter(self, event):
253 self._current_figure = event.canvas.figure
255 def _figure_leave(self, event):
256 # self._current_figure = None
257 pass
259 def update_from_usr1(self):
260 self._ctxt.update()
262 def _action_netlist_and_simulate(self, action):
263 dialog = gtk.Dialog("Run netlister and simulate",\
264 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,\
265 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
266 vbox_netl = gtk.VBox()
267 ckbutton_netl = gtk.CheckButton("Run netlister")
268 ckbutton_netl.set_active(True)
269 vbox_netl.pack_start(ckbutton_netl)
270 entry_netl = gtk.Entry()
271 entry_netl.set_text("gnetlist -s -o demo.cir -g spice-sdb demo.sch")
272 vbox_netl.pack_start(entry_netl)
273 dialog.vbox.pack_start(vbox_netl)
274 vbox_netl = gtk.VBox()
276 vbox_sim = gtk.VBox()
277 ckbutton_sim = gtk.CheckButton("Run simulator")
278 ckbutton_sim.set_active(True)
279 vbox_sim.pack_start(ckbutton_sim)
280 entry_sim = gtk.Entry()
281 entry_sim.set_text("gnucap -b demo.cir")
282 vbox_sim.pack_start(entry_sim)
283 dialog.vbox.pack_start(vbox_sim)
284 ckbutton_upd = gtk.CheckButton("Update readers")
285 ckbutton_upd.set_active(True)
286 dialog.vbox.pack_start(ckbutton_upd)
287 dialog.show_all()
288 resp = dialog.run()
289 if resp == gtk.RESPONSE_ACCEPT:
290 if ckbutton_netl.get_active():
291 res = commands.getstatusoutput(entry_netl.get_text())
292 if res[0]:
293 report_error(self._mainwindow, res[1])
294 print res[1]
295 if ckbutton_sim.get_active():
296 res = commands.getstatusoutput(entry_sim.get_text())
297 if res[0]:
298 report_error(self._mainwindow, res[1])
299 print res[1]
300 if ckbutton_upd.get_active():
301 self._ctxt.update()
302 dialog.destroy()
304 def _drag_data_get_cb(self, widget, drag_context, selection, target_type,\
305 time):
306 if target_type == self._TARGET_TYPE_SIGNAL:
307 tv = widget
308 (path, col) = tv.get_cursor()
309 row = self._store[path]
310 print row[0]
311 selection.set(selection.target, 8, row[0])
313 def _drag_data_received_cb(self, widget, drag_context, x, y, selection,\
314 target_type, time):
315 if target_type == self._TARGET_TYPE_SIGNAL:
316 if self._current_graph is not None:
317 signals = {selection.data: self._ctxt.signals[selection.data]}
318 self._current_graph.insert(signals)
319 if self._current_figure.canvas is not None:
320 self._current_figure.canvas.draw()
322 def usr1_handler(signum, frame):
323 app.update_from_usr1()
325 if __name__ == '__main__':
326 app = App()
327 main_loop = gobject.MainLoop()
328 signal.signal(signal.SIGUSR1, usr1_handler)
329 main_loop.run()