10 from xdg
import BaseDirectory
14 from matplotlib
.backends
.backend_gtkagg
import FigureCanvasGTKAgg
as FigureCanvas
15 from matplotlib
.backends
.backend_gtkagg
import NavigationToolbar2GTKAgg
as NavigationToolbar
18 def report_error(parent
, msg
):
19 dlg
= gtk
.MessageDialog(parent
,
20 gtk
.DIALOG_MODAL | gtk
.DIALOG_DESTROY_WITH_PARENT
,
21 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
, msg
)
22 dlg
.set_title(parent
.get_title())
26 class OscopyAppUI(oscopy
.OscopyApp
):
27 def __init__(self
, context
):
28 oscopy
.OscopyApp
.__init
__(self
, context
)
30 self
._autorefresh
= True
32 def connect(self
, event
, func
, data
):
33 if not isinstance(event
, str):
35 if hasattr(self
, 'do_'+event
):
36 self
._callbacks
[event
] = {func
: data
}
38 def postcmd(self
, stop
, line
):
39 oscopy
.OscopyApp
.postcmd(self
, stop
, line
)
42 if len(line
.split()) > 1:
43 event
= line
.split()[0].strip()
44 args
= line
.split(' ', 1)[1].strip()
48 if self
._callbacks
.has_key(event
):
49 for func
, data
in self
._callbacks
[event
].iteritems():
50 func(event
, args
, data
)
51 if self
._autorefresh
and self
._current
_figure
is not None and\
52 self
._current
_figure
.canvas
is not None:
53 self
._current
_figure
.canvas
.draw()
55 def help_refresh(self
):
56 print 'refresh FIG#|on|off|current|all'
57 print ' on|off toggle auto refresh of current figure'
58 print ' current|all refresh either current figure or all'
59 print ' FIG# figure to refresh'
60 print 'without arguments refresh current figure'
61 def do_refresh(self
, args
):
63 self
._autorefresh
= True
65 self
._autorefresh
= False
66 elif args
== 'current' or args
== '':
67 if self
._current
_figure
is not None and\
68 self
._current
_figure
.canvas
is not None:
69 self
._current
_figure
.canvas
.draw()
71 for fig
in self
._ctxt
.figures
:
72 if fig
.canvas
is not None:
75 fignum
= int(args
) - 1
76 if fignum
>= 0 and fignum
< len(self
._ctxt
.figures
):
77 if self
._ctxt
.figures
[fignum
].canvas
is not None:
79 self
._ctxt
.figures
[fignum
].canvas
.draw()
81 def do_pause(self
, args
):
82 print "Pause command disabled in UI"
84 def do_plot(self
, line
):
85 print "Plot command disabled in UI"
89 <menubar name="MenuBar">
91 <menuitem action="Add file"/>
92 <menuitem action="Update files"/>
93 <menuitem action="Execute script..."/>
94 <menuitem action="New Math Signal..."/>
95 <menuitem action="Run netlister and simulate..."/>
96 <menuitem action="Show terminal"/>
97 <menuitem action="Quit"/>
103 self
._scale
_to
_str
= {'lin': 'Linear', 'logx': 'LogX', 'logy': 'LogY',\
105 self
._configfile
= "gui"
107 self
._windows
_to
_figures
= {}
108 self
._current
_graph
= None
109 self
._current
_figure
= None
110 self
._term
_win
= None
111 self
._prompt
= "oscopy-ui>"
113 self
.hist_file
= None
115 self
._TARGET
_TYPE
_SIGNAL
= 10354
116 self
._from
_signal
_list
= [("oscopy-signals", gtk
.TARGET_SAME_APP
,\
117 self
._TARGET
_TYPE
_SIGNAL
)]
118 self
._to
_figure
= [("oscopy-signals", gtk
.TARGET_SAME_APP
,\
119 self
._TARGET
_TYPE
_SIGNAL
)]
120 self
._resource
= "oscopy"
123 self
._ctxt
= oscopy
.Context()
124 self
._app
= OscopyAppUI(self
._ctxt
)
125 self
._app
.connect('read', self
._add
_file
, None)
126 self
._app
.connect('math', self
._add
_file
, None)
127 self
._app
.connect('freeze', self
._freeze
, None)
128 self
._app
.connect('unfreeze', self
._freeze
, None)
129 self
._app
.connect('create', self
._create
, None)
130 self
._store
= gtk
.TreeStore(gobject
.TYPE_STRING
, gobject
.TYPE_PYOBJECT
,
131 gobject
.TYPE_BOOLEAN
)
132 self
._create
_widgets
()
133 #self._app_exec('read demo/irf540.dat')
134 #self._app_exec('read demo/ac.dat')
135 #self._add_file('demo/res.dat')
137 def _add_file(self
, event
, filename
, data
=None):
138 it
= self
._store
.append(None, (filename
.strip(), None, False))
139 for name
, sig
in self
._ctxt
.readers
[filename
.strip()]\
140 .signals
.iteritems():
141 self
._store
.append(it
, (name
, sig
, sig
.freeze
))
143 def _action_add_file(self
, action
):
144 dlg
= gtk
.FileChooserDialog('Add file', parent
=self
._mainwindow
,
145 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_REJECT
,
146 gtk
.STOCK_OK
, gtk
.RESPONSE_ACCEPT
))
148 filename
= dlg
.get_filename()
150 if resp
== gtk
.RESPONSE_ACCEPT
:
151 self
._app
_exec
("read " + filename
)
153 def _app_exec(self
, line
):
154 line
= self
._app
.precmd(line
)
155 stop
= self
._app
.onecmd(line
)
156 self
._app
.postcmd(stop
, line
)
158 def _action_update(self
, action
):
161 def _action_new_math(self
, action
):
162 dlg
= gtk
.Dialog('New math signal', parent
=self
._mainwindow
,
163 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_REJECT
,
164 gtk
.STOCK_OK
, gtk
.RESPONSE_ACCEPT
))
168 label
= gtk
.Label('Expression:')
169 hbox
.pack_start(label
)
171 hbox
.pack_start(entry
)
172 dlg
.vbox
.pack_start(hbox
)
176 if resp
== gtk
.RESPONSE_ACCEPT
:
177 expr
= entry
.get_text()
178 self
._app
_exec
('%s' % expr
)
182 def _action_show_terminal(self
, action
):
183 self
._create
_terminal
_window
()
185 def _action_execute_script(self
, action
):
186 dlg
= gtk
.FileChooserDialog('Execute script', parent
=self
._mainwindow
,
187 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_REJECT
,
188 gtk
.STOCK_OK
, gtk
.RESPONSE_ACCEPT
))
190 filename
= dlg
.get_filename()
192 if resp
== gtk
.RESPONSE_ACCEPT
:
193 self
._app
_exec
("exec " + filename
)
195 def _action_quit(self
, action
):
197 readline
.write_history_file(self
.hist_file
)
200 def _create_menubar(self
):
202 # (name, stock-id, label, accelerator, tooltip, callback)
204 ('File', None, '_File'),
205 ('Add file', gtk
.STOCK_ADD
, '_Add file', None, None,
206 self
._action
_add
_file
),
207 ('Update files', gtk
.STOCK_REFRESH
, '_Update', None, None,
208 self
._action
_update
),
209 ('Execute script...', gtk
.STOCK_MEDIA_PLAY
, '_Execute script...',
210 None, None, self
._action
_execute
_script
),
211 ("New Math Signal...", gtk
.STOCK_NEW
, '_New Math Signal', None,
212 None, self
._action
_new
_math
),
213 ("Run netlister and simulate...", gtk
.STOCK_MEDIA_FORWARD
,\
214 "_Run netlister and simulate...", None, None,\
215 self
._action
_netlist
_and
_simulate
),
216 ("Show terminal", None, "_Show terminal", None, None,
217 self
._action
_show
_terminal
),
218 ('Quit', gtk
.STOCK_QUIT
, '_Quit', None, None,
221 actiongroup
= gtk
.ActionGroup('App')
222 actiongroup
.add_actions(actions
)
224 uimanager
= gtk
.UIManager()
225 uimanager
.add_ui_from_string(self
.__ui
)
226 uimanager
.insert_action_group(actiongroup
, 0)
227 return uimanager
.get_accel_group(), uimanager
.get_widget('/MenuBar')
229 def _create_treeview(self
):
230 celltext
= gtk
.CellRendererText()
231 col
= gtk
.TreeViewColumn('Signal', celltext
, text
=0)
233 col
.set_cell_data_func(celltext
, self
._reader
_name
_in
_bold
)
234 tv
.append_column(col
)
235 tv
.set_model(self
._store
)
236 tv
.connect('row-activated', self
._row
_activated
)
237 tv
.connect('drag_data_get', self
._drag
_data
_get
_cb
)
238 tv
.drag_source_set(gtk
.gdk
.BUTTON1_MASK
,\
239 self
._from
_signal
_list
,\
241 self
._togglecell
= gtk
.CellRendererToggle()
242 self
._togglecell
.set_property('activatable', True)
243 self
._togglecell
.connect('toggled', self
._cell
_toggled
, None)
244 colfreeze
= gtk
.TreeViewColumn('Freeze', self
._togglecell
)
245 colfreeze
.add_attribute(self
._togglecell
, 'active', 2)
246 tv
.append_column(colfreeze
)
247 tv
.get_selection().set_mode(gtk
.SELECTION_MULTIPLE
)
250 def _reader_name_in_bold(self
, column
, cell
, model
, iter, data
=None):
251 if len(model
.get_path(iter)) == 1:
252 cell
.set_property('markup', "<b>" + model
.get_value(iter, 0) +\
255 cell
.set_property('text', model
.get_value(iter, 0))
257 # Search algorithm from pygtk tutorial
258 def _match_func(self
, row
, data
):
260 return row
[column
] == key
262 def _search(self
, rows
, func
, data
):
263 if not rows
: return None
267 result
= self
._search
(row
.iterchildren(), func
, data
)
268 if result
: return result
271 def _freeze(self
, event
, signals
, data
=None):
272 for signal
in signals
.split(','):
273 match_row
= self
._search
(self
._store
, self
._match
_func
,\
275 if match_row
is not None:
276 match_row
[2] = match_row
[1].freeze
277 parent
= self
._store
.iter_parent(match_row
.iter)
278 iter = self
._store
.iter_children(parent
)
279 freeze
= match_row
[2]
281 if not self
._store
.get_value(iter, 2) == freeze
:
283 iter = self
._store
.iter_next(iter)
285 # All row at the same freeze value,
286 # set freeze for the reader
287 self
._store
.set_value(parent
, 2, freeze
)
289 # Set reader freeze to false
290 self
._store
.set_value(parent
, 2, False)
292 def _cell_toggled(self
, cellrenderer
, path
, data
):
295 if self
._store
[path
][1].freeze
:
299 self
._app
_exec
('%s %s' % (cmd
, self
._store
[path
][0]))
302 parent
= self
._store
.get_iter(path
)
303 freeze
= not self
._store
.get_value(parent
, 2)
304 if self
._store
[path
][2]:
308 self
._store
.set_value(parent
, 2, freeze
)
309 iter = self
._store
.iter_children(parent
)
311 self
._app
_exec
('%s %s' % (cmd
, self
._store
.get_value(iter, 0)))
312 iter = self
._store
.iter_next(iter)
314 def _create_widgets(self
):
315 accel_group
, self
._menubar
= self
._create
_menubar
()
316 self
._treeview
= self
._create
_treeview
()
317 self
._create
_terminal
_window
()
319 sw
= gtk
.ScrolledWindow()
320 sw
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
321 sw
.add(self
._treeview
)
324 vbox
.pack_start(self
._menubar
, False)
327 w
= self
._mainwindow
= gtk
.Window(gtk
.WINDOW_TOPLEVEL
)
328 w
.set_title('Oscopy GUI')
330 w
.add_accel_group(accel_group
)
331 w
.connect('destroy', lambda *x
: self
._action
_quit
(None))
332 w
.set_default_size(400, 300)
335 def _create_terminal_window(self
):
336 if self
._term
_win
is None:
337 self
._term
_win
= gui
.dialogs
.TerminalWindow(self
._prompt
,
341 if not self
._term
_win
.is_there
:
342 self
._term
_win
.create()
345 def _create_figure_popup_menu(self
, figure
, graph
):
346 figmenu
= gui
.menus
.FigureMenu()
347 return figmenu
.create_menu(self
._store
, figure
, graph
, self
._app
_exec
)
349 def _treeview_button_press(self
, widget
, event
):
350 if event
.button
== 3:
352 path
, tvc
, x
, y
= tv
.get_path_at_pos(int(event
.x
), int(event
.y
))
356 row
= self
._store
[path
]
357 signals
= {row
[0]: row
[1]}
358 menu
= self
._create
_treeview
_popup
_menu
(signals
, path
)
360 menu
.popup(None, None, None, event
.button
, event
.time
)
362 def _button_press(self
, event
):
363 if event
.button
== 3:
364 menu
= self
._create
_figure
_popup
_menu
(event
.canvas
.figure
, event
.inaxes
)
366 menu
.popup(None, None, None, event
.button
, event
.guiEvent
.time
)
368 #TODO: _windows_to_figures consistency...
369 # think of a better way to map events to Figure objects
370 def _row_activated(self
, widget
, path
, col
):
374 row
= self
._store
[path
]
375 self
._app
_exec
('create %s' % row
[0])
377 def _create(self
, event
, signals
, data
=None):
378 fig
= self
._ctxt
.figures
[len(self
._ctxt
.figures
) - 1]
382 self
._windows
_to
_figures
[w
] = fig
383 w
.set_title('Figure %d' % self
._figcount
)
386 canvas
= FigureCanvas(fig
)
387 canvas
.mpl_connect('button_press_event', self
._button
_press
)
388 canvas
.mpl_connect('axes_enter_event', self
._axes
_enter
)
389 canvas
.mpl_connect('axes_leave_event', self
._axes
_leave
)
390 canvas
.mpl_connect('figure_enter_event', self
._figure
_enter
)
391 canvas
.mpl_connect('figure_leave_event', self
._figure
_leave
)
392 w
.connect("drag_data_received", self
._drag
_data
_received
_cb
)
393 w
.drag_dest_set(gtk
.DEST_DEFAULT_MOTION |\
394 gtk
.DEST_DEFAULT_HIGHLIGHT |\
395 gtk
.DEST_DEFAULT_DROP
,
396 self
._to
_figure
, gtk
.gdk
.ACTION_COPY
)
397 vbox
.pack_start(canvas
)
398 toolbar
= NavigationToolbar(canvas
, w
)
399 vbox
.pack_start(toolbar
, False, False)
402 self
._app
_exec
('select %d-1' % len(self
._ctxt
.figures
))
404 def _axes_enter(self
, event
):
405 self
._current
_graph
= event
.inaxes
406 axes_num
= event
.canvas
.figure
.axes
.index(event
.inaxes
) + 1
407 fig_num
= self
._ctxt
.figures
.index(self
._current
_figure
) + 1
408 self
._app
_exec
('select %d-%d' % (fig_num
, axes_num
))
410 def _axes_leave(self
, event
):
411 # Unused for better user interaction
412 # self._current_graph = None
415 def _figure_enter(self
, event
):
416 self
._current
_figure
= event
.canvas
.figure
417 if hasattr(event
, 'inaxes') and event
.inaxes
is not None:
418 axes_num
= event
.canvas
.figure
.axes
.index(event
.inaxes
) + 1
421 fig_num
= self
._ctxt
.figures
.index(self
._current
_figure
) + 1
422 self
._app
_exec
('select %d-%d' % (fig_num
, axes_num
))
424 def _figure_leave(self
, event
):
425 # self._current_figure = None
428 def update_from_usr1(self
):
431 def _action_netlist_and_simulate(self
, action
):
432 netnsimdlg
= gui
.dialogs
.Run_Netlister_and_Simulate_Dialog()
433 netnsimdlg
.display(self
._actions
)
434 actions
= netnsimdlg
.run()
435 if actions
is not None:
436 self
._actions
= actions
437 old_dir
= os
.getcwd()
438 os
.chdir(self
._actions
['run_from'])
439 if self
._actions
['run_netlister'][0]:
440 res
= commands
.getstatusoutput(self
._actions
['run_netlister'][1])
442 report_error(self
._mainwindow
, res
[1])
444 if self
._actions
['run_simulator'][0]:
445 res
= commands
.getstatusoutput(self
._actions
['run_simulator'][1])
447 report_error(self
._mainwindow
, res
[1])
450 if self
._actions
['update']:
453 def _drag_data_get_cb(self
, widget
, drag_context
, selection
, target_type
,\
455 if target_type
== self
._TARGET
_TYPE
_SIGNAL
:
457 sel
= tv
.get_selection()
458 (model
, pathlist
) = sel
.get_selected_rows()
459 iter = self
._store
.get_iter(pathlist
[0])
460 data
= " ".join(map(lambda x
:self
._store
[x
][1].name
, pathlist
))
461 selection
.set(selection
.target
, 8, data
)
462 # The multiple selection do work, but how to select signals
463 # that are not neighbours in the list? Ctrl+left do not do
464 # anything, neither alt+left or shift+left!
466 def _drag_data_received_cb(self
, widget
, drag_context
, x
, y
, selection
,\
468 if target_type
== self
._TARGET
_TYPE
_SIGNAL
:
469 if self
._current
_graph
is not None:
471 for name
in selection
.data
.split():
472 signals
[name
] = self
._ctxt
.signals
[name
]
473 self
._current
_graph
.insert(signals
)
474 if self
._current
_figure
.canvas
is not None:
475 self
._current
_figure
.canvas
.draw()
477 def _read_config(self
):
479 netlopt
= 'Netlister'
483 self
._confparse
= ConfigParser
.SafeConfigParser()
484 self
._confparse
.add_section(cmdsec
)
485 self
._confparse
.set(cmdsec
, netlopt
, '')
486 self
._confparse
.set(cmdsec
, simopt
, '')
487 self
._confparse
.set(cmdsec
, runfrom
, '.')
488 path
= BaseDirectory
.load_first_config(self
._resource
)
490 res
= self
._confparse
.read('/'.join((path
, self
._configfile
)))
492 path
= BaseDirectory
.save_config_path(self
._resource
)
493 self
.hist_file
= '/'.join((path
, history
))
494 self
._actions
= {'run_netlister': (True,
495 self
._confparse
.get(cmdsec
, netlopt
)),
496 'run_simulator': (True,
497 self
._confparse
.get(cmdsec
, simopt
)),
499 'run_from': self
._confparse
.get(cmdsec
, runfrom
)}
501 def _write_config(self
):
503 netlopt
= 'Netlister'
506 self
._confparse
.set(cmdsec
, netlopt
, self
._actions
['run_netlister'][1])
507 self
._confparse
.set(cmdsec
, simopt
, self
._actions
['run_simulator'][1])
508 self
._confparse
.set(cmdsec
, runfrom
, self
._actions
['run_from'])
509 path
= BaseDirectory
.save_config_path(self
._resource
)
510 f
= open('/'.join((path
, self
._configfile
)), 'w')
511 res
= self
._confparse
.write(f
)
515 def usr1_handler(signum
, frame
):
516 app
.update_from_usr1()
518 if __name__
== '__main__':
520 main_loop
= gobject
.MainLoop()
521 signal
.signal(signal
.SIGUSR1
, usr1_handler
)