2 from __future__
import with_statement
11 from xdg
import BaseDirectory
15 from matplotlib
.backends
.backend_gtkagg
import FigureCanvasGTKAgg
as FigureCanvas
16 from matplotlib
.backends
.backend_gtkagg
import NavigationToolbar2GTKAgg
as NavigationToolbar
19 def report_error(parent
, msg
):
20 dlg
= gtk
.MessageDialog(parent
,
21 gtk
.DIALOG_MODAL | gtk
.DIALOG_DESTROY_WITH_PARENT
,
22 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
, msg
)
23 dlg
.set_title(parent
.get_title())
27 class OscopyAppUI(oscopy
.OscopyApp
):
28 def __init__(self
, context
):
29 oscopy
.OscopyApp
.__init
__(self
, context
)
31 self
._autorefresh
= True
33 def connect(self
, event
, func
, data
):
34 if not isinstance(event
, str):
36 if hasattr(self
, 'do_'+event
):
37 self
._callbacks
[event
] = {func
: data
}
39 def postcmd(self
, stop
, line
):
40 oscopy
.OscopyApp
.postcmd(self
, stop
, line
)
43 event
= line
.split()[0].strip()
44 if len(line
.split()) > 1:
45 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="Quit"/>
98 <menu action="Windows">
99 <menuitem action="Show terminal"/>
105 self
._scale
_to
_str
= {'lin': 'Linear', 'logx': 'LogX', 'logy': 'LogY',\
108 self
._windows
_to
_figures
= {}
109 self
._current
_graph
= None
110 self
._current
_figure
= None
111 self
._term
_win
= None
112 self
._prompt
= "oscopy-ui>"
116 self
._TARGET
_TYPE
_SIGNAL
= 10354
117 self
._from
_signal
_list
= [("oscopy-signals", gtk
.TARGET_SAME_APP
,\
118 self
._TARGET
_TYPE
_SIGNAL
)]
119 self
._to
_figure
= [("oscopy-signals", gtk
.TARGET_SAME_APP
,\
120 self
._TARGET
_TYPE
_SIGNAL
)]
122 self
._ctxt
= oscopy
.Context()
123 self
._app
= OscopyAppUI(self
._ctxt
)
124 self
._app
.connect('read', self
._add
_file
, None)
125 self
._app
.connect('math', self
._add
_file
, None)
126 self
._app
.connect('freeze', self
._freeze
, None)
127 self
._app
.connect('unfreeze', self
._freeze
, None)
128 self
._app
.connect('create', self
._create
, None)
129 self
._store
= gtk
.TreeStore(gobject
.TYPE_STRING
, gobject
.TYPE_PYOBJECT
,
130 gobject
.TYPE_BOOLEAN
)
131 self
._create
_widgets
()
132 #self._app_exec('read demo/irf540.dat')
133 #self._app_exec('read demo/ac.dat')
134 #self._add_file('demo/res.dat')
136 SECTION
= 'oscopy_ui'
137 OPT_NETLISTER_COMMANDS
= 'netlister_commands'
138 OPT_SIMULATOR_COMMANDS
= 'simulator_commands'
139 OPT_RUN_DIRECTORY
= 'run_directory'
141 def _init_config(self
):
142 # initialize configuration stuff
143 path
= BaseDirectory
.load_first_config('oscopy')
144 self
.config_file
= os
.path
.join(path
, 'gui')
145 self
.hist_file
= os
.path
.join(path
, 'history')
146 section
= App
.SECTION
147 self
.config
= ConfigParser
.RawConfigParser()
148 self
.config
.add_section(section
)
150 self
.config
.set(section
, App
.OPT_NETLISTER_COMMANDS
, '')
151 self
.config
.set(section
, App
.OPT_SIMULATOR_COMMANDS
, '')
152 self
.config
.set(section
, App
.OPT_RUN_DIRECTORY
, '.')
154 def _add_file(self
, event
, filename
, data
=None):
155 it
= self
._store
.append(None, (filename
.strip(), None, False))
156 for name
, sig
in self
._ctxt
.readers
[filename
.strip()]\
157 .signals
.iteritems():
158 self
._store
.append(it
, (name
, sig
, sig
.freeze
))
160 def _action_add_file(self
, action
):
161 dlg
= gtk
.FileChooserDialog('Add file', parent
=self
._mainwindow
,
162 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_REJECT
,
163 gtk
.STOCK_OK
, gtk
.RESPONSE_ACCEPT
))
165 filename
= dlg
.get_filename()
167 if resp
== gtk
.RESPONSE_ACCEPT
:
168 self
._app
_exec
("read " + filename
)
170 def _app_exec(self
, line
):
171 line
= self
._app
.precmd(line
)
172 stop
= self
._app
.onecmd(line
)
173 self
._app
.postcmd(stop
, line
)
175 def _action_update(self
, action
):
178 def _action_new_math(self
, action
):
179 dlg
= gtk
.Dialog('New math signal', parent
=self
._mainwindow
,
180 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_REJECT
,
181 gtk
.STOCK_OK
, gtk
.RESPONSE_ACCEPT
))
185 label
= gtk
.Label('Expression:')
186 hbox
.pack_start(label
)
188 hbox
.pack_start(entry
)
189 dlg
.vbox
.pack_start(hbox
)
193 if resp
== gtk
.RESPONSE_ACCEPT
:
194 expr
= entry
.get_text()
195 self
._app
_exec
('%s' % expr
)
199 def _action_show_terminal(self
, action
):
200 self
._create
_terminal
_window
()
202 def _action_execute_script(self
, action
):
203 dlg
= gtk
.FileChooserDialog('Execute script', parent
=self
._mainwindow
,
204 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_REJECT
,
205 gtk
.STOCK_OK
, gtk
.RESPONSE_ACCEPT
))
207 filename
= dlg
.get_filename()
209 if resp
== gtk
.RESPONSE_ACCEPT
:
210 self
._app
_exec
("exec " + filename
)
212 def _action_quit(self
, action
):
214 readline
.write_history_file(self
.hist_file
)
217 def _create_menubar(self
):
219 # (name, stock-id, label, accelerator, tooltip, callback)
221 ('File', None, '_File'),
222 ('Add file', gtk
.STOCK_ADD
, '_Add file', None, None,
223 self
._action
_add
_file
),
224 ('Update files', gtk
.STOCK_REFRESH
, '_Update', None, None,
225 self
._action
_update
),
226 ('Execute script...', gtk
.STOCK_MEDIA_PLAY
, '_Execute script...',
227 None, None, self
._action
_execute
_script
),
228 ("New Math Signal...", gtk
.STOCK_NEW
, '_New Math Signal', None,
229 None, self
._action
_new
_math
),
230 ("Run netlister and simulate...", gtk
.STOCK_MEDIA_FORWARD
,\
231 "_Run netlister and simulate...", None, None,\
232 self
._action
_netlist
_and
_simulate
),
233 ('Windows', None, '_Windows'),
234 ("Show terminal", None, "_Show terminal", None, None,
235 self
._action
_show
_terminal
),
236 ('Quit', gtk
.STOCK_QUIT
, '_Quit', None, None,
239 actiongroup
= gtk
.ActionGroup('App')
240 actiongroup
.add_actions(actions
)
242 uimanager
= gtk
.UIManager()
243 uimanager
.add_ui_from_string(self
.__ui
)
244 uimanager
.insert_action_group(actiongroup
, 0)
245 return uimanager
.get_accel_group(), uimanager
.get_widget('/MenuBar')
247 def _create_treeview(self
):
248 celltext
= gtk
.CellRendererText()
249 col
= gtk
.TreeViewColumn('Signal', celltext
, text
=0)
251 col
.set_cell_data_func(celltext
, self
._reader
_name
_in
_bold
)
253 tv
.append_column(col
)
254 tv
.set_model(self
._store
)
255 tv
.connect('row-activated', self
._row
_activated
)
256 tv
.connect('drag_data_get', self
._drag
_data
_get
_cb
)
257 tv
.drag_source_set(gtk
.gdk
.BUTTON1_MASK
,\
258 self
._from
_signal
_list
,\
260 self
._togglecell
= gtk
.CellRendererToggle()
261 self
._togglecell
.set_property('activatable', True)
262 self
._togglecell
.connect('toggled', self
._cell
_toggled
, None)
263 colfreeze
= gtk
.TreeViewColumn('Freeze', self
._togglecell
)
264 colfreeze
.add_attribute(self
._togglecell
, 'active', 2)
265 tv
.append_column(colfreeze
)
266 tv
.get_selection().set_mode(gtk
.SELECTION_MULTIPLE
)
269 def _reader_name_in_bold(self
, column
, cell
, model
, iter, data
=None):
270 if len(model
.get_path(iter)) == 1:
271 cell
.set_property('markup', "<b>" + model
.get_value(iter, 0) +\
274 cell
.set_property('text', model
.get_value(iter, 0))
276 # Search algorithm from pygtk tutorial
277 def _match_func(self
, row
, data
):
279 return row
[column
] == key
281 def _search(self
, rows
, func
, data
):
282 if not rows
: return None
286 result
= self
._search
(row
.iterchildren(), func
, data
)
287 if result
: return result
290 def _freeze(self
, event
, signals
, data
=None):
291 for signal
in signals
.split(','):
292 match_row
= self
._search
(self
._store
, self
._match
_func
,\
294 if match_row
is not None:
295 match_row
[2] = match_row
[1].freeze
296 parent
= self
._store
.iter_parent(match_row
.iter)
297 iter = self
._store
.iter_children(parent
)
298 freeze
= match_row
[2]
300 if not self
._store
.get_value(iter, 2) == freeze
:
302 iter = self
._store
.iter_next(iter)
304 # All row at the same freeze value,
305 # set freeze for the reader
306 self
._store
.set_value(parent
, 2, freeze
)
308 # Set reader freeze to false
309 self
._store
.set_value(parent
, 2, False)
311 def _cell_toggled(self
, cellrenderer
, path
, data
):
314 if self
._store
[path
][1].freeze
:
318 self
._app
_exec
('%s %s' % (cmd
, self
._store
[path
][0]))
321 parent
= self
._store
.get_iter(path
)
322 freeze
= not self
._store
.get_value(parent
, 2)
323 if self
._store
[path
][2]:
327 self
._store
.set_value(parent
, 2, freeze
)
328 iter = self
._store
.iter_children(parent
)
330 self
._app
_exec
('%s %s' % (cmd
, self
._store
.get_value(iter, 0)))
331 iter = self
._store
.iter_next(iter)
333 def _create_widgets(self
):
334 accel_group
, self
._menubar
= self
._create
_menubar
()
335 self
._treeview
= self
._create
_treeview
()
336 self
._create
_terminal
_window
()
338 sw
= gtk
.ScrolledWindow()
339 sw
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
340 sw
.add(self
._treeview
)
343 vbox
.pack_start(self
._menubar
, False)
346 w
= self
._mainwindow
= gtk
.Window(gtk
.WINDOW_TOPLEVEL
)
347 w
.set_title('Oscopy GUI')
349 w
.add_accel_group(accel_group
)
350 w
.connect('destroy', lambda *x
: self
._action
_quit
(None))
351 w
.set_default_size(400, 300)
354 def _create_terminal_window(self
):
355 if self
._term
_win
is None:
356 self
._term
_win
= gui
.dialogs
.TerminalWindow(self
._prompt
,
360 self
._term
_win
.create()
361 self
._term
_win
.connect('delete-event', lambda w
, e
: w
.hide() or True)
362 if not (self
._term
_win
.flags() & gtk
.VISIBLE
):
363 self
._term
_win
.show_all()
365 def _create_figure_popup_menu(self
, figure
, graph
):
366 figmenu
= gui
.menus
.FigureMenu()
367 return figmenu
.create_menu(self
._store
, figure
, graph
, self
._app
_exec
)
369 def _treeview_button_press(self
, widget
, event
):
370 if event
.button
== 3:
372 path
, tvc
, x
, y
= tv
.get_path_at_pos(int(event
.x
), int(event
.y
))
376 row
= self
._store
[path
]
377 signals
= {row
[0]: row
[1]}
378 menu
= self
._create
_treeview
_popup
_menu
(signals
, path
)
380 menu
.popup(None, None, None, event
.button
, event
.time
)
382 def _button_press(self
, event
):
383 if event
.button
== 3:
384 menu
= self
._create
_figure
_popup
_menu
(event
.canvas
.figure
, event
.inaxes
)
386 menu
.popup(None, None, None, event
.button
, event
.guiEvent
.time
)
388 #TODO: _windows_to_figures consistency...
389 # think of a better way to map events to Figure objects
390 def _row_activated(self
, widget
, path
, col
):
394 row
= self
._store
[path
]
395 self
._app
_exec
('create %s' % row
[0])
397 def _create(self
, event
, signals
, data
=None):
398 fig
= self
._ctxt
.figures
[len(self
._ctxt
.figures
) - 1]
402 self
._windows
_to
_figures
[w
] = fig
403 w
.set_title('Figure %d' % self
._figcount
)
406 canvas
= FigureCanvas(fig
)
407 canvas
.mpl_connect('button_press_event', self
._button
_press
)
408 canvas
.mpl_connect('axes_enter_event', self
._axes
_enter
)
409 canvas
.mpl_connect('axes_leave_event', self
._axes
_leave
)
410 canvas
.mpl_connect('figure_enter_event', self
._figure
_enter
)
411 canvas
.mpl_connect('figure_leave_event', self
._figure
_leave
)
412 w
.connect("drag_data_received", self
._drag
_data
_received
_cb
)
413 w
.connect('delete-event', lambda w
, e
: w
.hide() or True)
414 w
.drag_dest_set(gtk
.DEST_DEFAULT_MOTION |\
415 gtk
.DEST_DEFAULT_HIGHLIGHT |\
416 gtk
.DEST_DEFAULT_DROP
,
417 self
._to
_figure
, gtk
.gdk
.ACTION_COPY
)
418 vbox
.pack_start(canvas
)
419 toolbar
= NavigationToolbar(canvas
, w
)
420 vbox
.pack_start(toolbar
, False, False)
423 self
._app
_exec
('select %d-1' % len(self
._ctxt
.figures
))
425 def _axes_enter(self
, event
):
426 self
._current
_graph
= event
.inaxes
427 axes_num
= event
.canvas
.figure
.axes
.index(event
.inaxes
) + 1
428 fig_num
= self
._ctxt
.figures
.index(self
._current
_figure
) + 1
429 self
._app
_exec
('select %d-%d' % (fig_num
, axes_num
))
431 def _axes_leave(self
, event
):
432 # Unused for better user interaction
433 # self._current_graph = None
436 def _figure_enter(self
, event
):
437 self
._current
_figure
= event
.canvas
.figure
438 if hasattr(event
, 'inaxes') and event
.inaxes
is not None:
439 axes_num
= event
.canvas
.figure
.axes
.index(event
.inaxes
) + 1
442 fig_num
= self
._ctxt
.figures
.index(self
._current
_figure
) + 1
443 self
._app
_exec
('select %d-%d' % (fig_num
, axes_num
))
445 def _figure_leave(self
, event
):
446 # self._current_figure = None
449 def update_from_usr1(self
):
452 def _run_ext_command(self
, cmd
, run_dir
):
453 old_dir
= os
.getcwd()
456 status
, output
= commands
.getstatusoutput(cmd
)
458 msg
= "Executing command '%s' failed." % cmd
459 report_error(self
._mainwindow
, msg
)
464 def _action_netlist_and_simulate(self
, action
):
465 dlg
= gui
.dialogs
.Run_Netlister_and_Simulate_Dialog()
466 dlg
.display(self
._actions
)
470 self
._actions
= actions
471 run_dir
= actions
['run_from']
472 if actions
['run_netlister'][0]:
473 if not self
._run
_ext
_command
(actions
['run_netlister'][1][0], run_dir
):
475 if actions
['run_simulator'][0]:
476 if not self
._run
_ext
_command
(actions
['run_simulator'][1][0], run_dir
):
478 if actions
['update']:
481 def _drag_data_get_cb(self
, widget
, drag_context
, selection
, target_type
,\
483 if target_type
== self
._TARGET
_TYPE
_SIGNAL
:
485 sel
= tv
.get_selection()
486 (model
, pathlist
) = sel
.get_selected_rows()
487 iter = self
._store
.get_iter(pathlist
[0])
488 data
= " ".join(map(lambda x
:self
._store
[x
][1].name
, pathlist
))
489 selection
.set(selection
.target
, 8, data
)
490 # The multiple selection do work, but how to select signals
491 # that are not neighbours in the list? Ctrl+left do not do
492 # anything, neither alt+left or shift+left!
494 def _drag_data_received_cb(self
, widget
, drag_context
, x
, y
, selection
,\
496 if target_type
== self
._TARGET
_TYPE
_SIGNAL
:
497 if self
._current
_graph
is not None:
499 for name
in selection
.data
.split():
500 signals
[name
] = self
._ctxt
.signals
[name
]
501 self
._current
_graph
.insert(signals
)
502 if self
._current
_figure
.canvas
is not None:
503 self
._current
_figure
.canvas
.draw()
505 def _sanitize_list(self
, lst
):
506 return filter(lambda x
: len(x
) > 0, map(lambda x
: x
.strip(), lst
))
508 def _actions_from_config(self
, config
):
509 section
= App
.SECTION
510 netlister_commands
= config
.get(section
, App
.OPT_NETLISTER_COMMANDS
)
511 netlister_commands
= self
._sanitize
_list
(netlister_commands
.split(';'))
512 simulator_commands
= config
.get(section
, App
.OPT_SIMULATOR_COMMANDS
)
513 simulator_commands
= self
._sanitize
_list
(simulator_commands
.split(';'))
515 'run_netlister': (True, netlister_commands
),
516 'run_simulator': (True, simulator_commands
),
518 'run_from': config
.get(section
, App
.OPT_RUN_DIRECTORY
)}
521 def _actions_to_config(self
, actions
, config
):
522 section
= App
.SECTION
523 netlister_commands
= ';'.join(actions
['run_netlister'][1])
524 simulator_commands
= ';'.join(actions
['run_simulator'][1])
525 config
.set(section
, App
.OPT_NETLISTER_COMMANDS
, netlister_commands
)
526 config
.set(section
, App
.OPT_SIMULATOR_COMMANDS
, simulator_commands
)
527 config
.set(section
, App
.OPT_RUN_DIRECTORY
, actions
['run_from'])
529 def _read_config(self
):
530 self
.config
.read(self
.config_file
)
531 self
._actions
= self
._actions
_from
_config
(self
.config
)
533 def _write_config(self
):
534 self
._actions
_to
_config
(self
._actions
, self
.config
)
535 with
open(self
.config_file
, 'w') as f
:
539 def usr1_handler(signum
, frame
):
540 app
.update_from_usr1()
542 if __name__
== '__main__':
544 main_loop
= gobject
.MainLoop()
545 signal
.signal(signal
.SIGUSR1
, usr1_handler
)