graph: rename update to update_signals
[oscopy/ivan.git] / oscopy_app
blob1344a9ce925aa6807e1dd6346b10b1724a756eb8
1 #!/usr/bin/python
2 """
3 Sample command line application
4 """
5 import gobject
6 import gtk
8 import re
9 import readline
10 import os.path
11 from optparse import OptionParser
12 import oscopy
13 from oscopy.readers.reader import ReadError
15 from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
16 from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
18 class OscopyApp(object):
19 """ Analyse command arguments and call function from Cmd
20 See Cmd for more help
21 """
22 def __init__(self):
23 self.cmds = oscopy.Context()
24 # Prompt
25 p = "oscopy> "
26 # History file
27 self.hist_file = ".oscopy_hist"
29 # Readline configuration
30 if not os.path.exists(self.hist_file):
31 f = open(self.hist_file, "w")
32 f.write("figlist")
33 f.close()
34 readline.read_history_file(self.hist_file)
36 # Parse command line arguments
37 # Current options:
38 # -b : batch mode, read commands from file
39 # -i : interactive mode, do not quit at the end of batch file
40 # -q : do not display startup message
41 parser = OptionParser()
42 parser.add_option("-b", "--batch", dest="fn",\
43 help="Execute commands in FILE then exit",\
44 metavar="FILE")
45 parser.add_option("-i", "--interactive", action="store_true",\
46 dest="inter",\
47 help="Go to interactive mode after executing batch file")
48 parser.add_option("-q", "--quiet", action="store_true",\
49 dest="quiet",\
50 help="Do not display startup message")
51 (options, args) = parser.parse_args()
52 if options.fn is None:
53 f = None
54 else:
55 try:
56 f = open(options.fn, 'r')
57 except IOError, e:
58 print "Unable to access batch file:", e
59 f = None
60 if options.inter == True:
61 batch = False
62 else:
63 batch = True
65 # Startup message
66 if options.quiet is None:
67 print "This is oscopy, a program to view electrical simulation results\n\
68 Copyright (C) 2009 Arnaud Gardelein.\n\
69 This is free software, you are invited to redistribute it \n\
70 under certain conditions.\n\
71 There is ABSOLUTELY NO WARRANTY; not even for MERCHANTIBILITY or\n\
72 FITNESS FOR A PARTICULAR PURPOSE."
74 # Current graph and current figure
75 self._current_figure = None
76 self._current_graph = None
77 self._figcount = 0
79 # Start main loop
80 self.loop(p, f, batch)
82 def create(self, args):
83 if args == "help":
84 print "Usage : create [SIG [, SIG [, SIG]...]]"
85 print " Create a new figure, set_ it as current, add the signals"
86 return
87 self.cmds.create(self.get_signames(args))
88 self._current_figure = self.cmds.figures[len(self.cmds.figures) - 1]
89 if self._current_figure.graphs:
90 self._current_graph =\
91 self._current_figure.graphs[len(\
92 self._current_figure.graphs) - 1]
93 else:
94 self._current_graph = None
96 def destroy(self, args):
97 if args == "help":
98 print "Usage : destroy FIG#"
99 print " Destroy a figure"
100 return
101 self.cmds.destroy(eval(args))
102 # Go back to the first graph of the first figure or None
103 if len(self.cmds.figures):
104 self._current_figure = self.cmds.figures[0]
105 if self._current_figure.graphs:
106 self._current_graph = self._current_figure.graphs[0]
107 else:
108 self._current_graph = None
109 else:
110 self._current_figure = None
111 self._current_graph = None
113 def select(self, args):
114 if args == "help":
115 print "Usage: select FIG#-GRAPH#"
116 print " Select the current figure and the current graph"
117 return
118 s = args.split('-')
119 num = eval(s[0])
120 if len(s) > 1:
121 gn = eval(s[1])
122 else:
123 print "Usage: select FIG#-GRAPH#"
124 return
125 # self.cmds.current = num, gn
126 self._current_figure = self.cmds.figures[num - 1]
127 self._current_graph = self._current_figure.graphs[gn - 1]
129 def layout(self, args):
130 if args == "help":
131 print "Usage : layout horiz|vert|quad"
132 print " Define the layout of the current figure"
133 return
134 if self._current_figure is not None:
135 self._current_figure.layout = args
137 def figlist(self, args):
138 if args == "help":
139 print "Usage : figlist"
140 print " Print the list of figures"
141 return
143 SEPARATOR = " "
144 for fn, f in enumerate(self.cmds.figures):
145 print "%s Figure %d: %s" %\
146 ([" ", "*"][f == self._current_figure],\
147 fn + 1, f.layout)
148 for gn, g in enumerate(f.graphs):
149 print " %s Graph %d : (%s) %s" %\
150 ([" ","*"][g == self._current_graph],\
151 gn + 1, g.type,\
152 SEPARATOR.join(g.signals.keys()))
154 def plot(self, args):
155 if args == "help":
156 print "Usage : plot"
157 print " Draw and show the figures"
158 return
159 if self._figcount == len(self.cmds.figures):
160 self._main_loop.run()
161 else:
162 # Create a window for each figure, add navigation toolbar
163 self._figcount = 0
164 for i, f in enumerate(self.cmds.figures):
165 # fig = plt.figure(i + 1)
166 w = gtk.Window()
167 self._figcount += 1
168 w.set_title('Figure %d' % self._figcount)
169 vbox = gtk.VBox()
170 w.add(vbox)
171 canvas = FigureCanvas(f)
172 canvas.connect('destroy', self._window_destroy)
173 vbox.pack_start(canvas)
174 toolbar = NavigationToolbar(canvas, w)
175 vbox.pack_start(toolbar, False, False)
176 w.resize(640, 480)
177 w.show_all()
178 self._main_loop = gobject.MainLoop()
179 self._main_loop.run()
181 def _window_destroy(self, arg):
182 self._figcount = self._figcount - 1
183 if not self._figcount:
184 self._main_loop.quit()
185 return False
187 def read(self, args):
188 if args == "help":
189 print "Usage : load DATAFILE"
190 print " Load signal file"
191 return
192 fn = args
193 try:
194 self.cmds.read(fn)
195 except ReadError, e:
196 print "Failed to read %s:" % fn, e
197 return
198 except NotImplementedError:
199 print "File format not supported"
200 return
202 print fn, ":"
203 for signal in self.cmds.signals:
204 if signal["reader"] == fn:
205 print '%s / %s %s' % (signal['name'],\
206 signal['reference'],\
207 signal['unit'])
209 def write(self, args):
210 if args == "help":
211 print "Usage: write format [(OPTIONS)] FILE SIG [, SIG [, SIG]...]"
212 print " Write signals to file"
213 return
214 # Extract format, options and signal list
215 tmp = re.search(r'(?P<fmt>\w+)\s*(?P<opts>\([^\)]*\))?\s+(?P<fn>[\w\./]+)\s+(?P<sigs>\w+(\s*,\s*\w+)*)', args)
217 if tmp is None:
218 print "What format ? Where ? Which signals ?"
219 return
220 fmt = tmp.group('fmt')
221 fn = tmp.group('fn')
222 opt = tmp.group('opts')
223 sns = self.get_signames(tmp.group('sigs'))
224 opts = {}
225 if opt is not None:
226 for on in opt.strip('()').split(','):
227 tmp = on.split(':', 1)
228 if len(tmp) == 2:
229 opts[tmp[0]] = tmp[1]
230 try:
231 self.cmds.write(fn, fmt, sns, opts)
232 except WriteError, e:
233 print "Write error:", e
234 return
235 except NotImplementedError:
236 print "File format not supported"
237 return
239 def update(self, args):
240 if args == "help":
241 print "Usage: update"
242 print " Reread data files"
243 return
244 self.cmds.update()
246 def add(self, args):
247 if args == "help":
248 print "Usage : add SIG [, SIG [, SIG]...]"
249 print " Add a graph to the current figure"
250 return
251 if self._current_figure is not None:
252 self._current_figure.add(self.cmds.names_to_signals(\
253 self.get_signames(args)))
254 self._current_graph =\
255 self._current_figure.graphs[len(\
256 self._current_figure.graphs) - 1]
257 else:
258 self.cmds.create(self.get_signames(args))
259 if self.cmds.figures:
260 self._current_figure = self.cmds.figures[0]
261 else:
262 print "Create failed"
264 def delete(self, args):
265 if args == "help":
266 print "Usage : delete GRAPH#"
267 print " Delete a graph from the current figure"
268 return
269 if self._current_figure is not None:
270 self._current_figure.delete(args)
271 if self._current_figure.graphs:
272 self._current_graph = self._current_figure.graphs[0]
273 else:
274 self._current_graph = None
276 def mode(self, args):
277 if args == "help":
278 print "Usage: mode MODE"
279 print " Set the type of the current graph of the current figure"
280 print "Available modes :\n\
281 lin Linear graph\n"
282 # fft Fast Fourier Transform (FFT) of signals\n\
283 # ifft Inverse FFT of signals"
284 return
285 if self._current_graph is None:
286 return
287 idx = self._current_figure.graphs.index(self._current_graph)
288 self._current_figure.mode = self._current_graph, mode
289 self._current_graph = self._current_figure.graphs[idx]
291 def scale(self, args):
292 if args == "help":
293 print "Usage: scale [lin|logx|logy|loglog]"
294 print " Set the axis scale"
295 return
296 if self._current_graph is None:
297 return
298 self._current_graph.scale = args
300 def range(self, args):
301 if args == "help":
302 print "Usage: range [x|y min max]|[xmin xmax ymin ymax]|[reset]"
303 print " Set the axis range of the current graph of the current figure"
304 return
305 if self._current_graph is None:
306 return
308 range = args.split()
309 if len(range) == 1:
310 if range[0] == "reset":
311 self._current_graph.range = range[0]
312 elif len(range) == 3:
313 if range[0] == 'x' or range[0] == 'y':
314 self._current_graph.range = range[0],\
315 [float(range[1]), float(range[2])]
316 elif len(range) == 4:
317 self._current_graph.range = [float(range[0]), float(range[1]),\
318 float(range[2]), float(range[3])]
320 def unit(self, args):
321 if args == "help":
322 print "Usage: unit [XUNIT,] YUNIT"
323 print " Set the unit to be displayed on graph axis"
324 return
326 units = args.split(",", 1)
327 if len(units) < 1 or self._current_graph is None:
328 return
329 elif len(units) == 1:
330 self._current_graph.unit = units[0].strip(),
331 elif len(units) == 2:
332 self._current_graph.unit = units[0].strip(), units[1].strip()
333 else:
334 return
336 def insert(self, args):
337 if args == "help":
338 print "Usage: insert SIG [, SIG [, SIG]...]"
339 print " Insert a list of signals into the current graph"
340 return
341 if self._current_graph is None:
342 return
343 self._current_graph.insert(self.cmds.names_to_signals(\
344 self.get_signames(args)))
346 def remove(self, args):
347 if args == "help":
348 print "Usage: remove SIG [, SIG [, SIG]...]"
349 print " Delete a list of signals into from current graph"
350 return
351 if self._current_graph is None:
352 return
353 self._current_graph.remove(self.cmds.names_to_signals(\
354 self.get_signames(args)))
356 def freeze(self, args):
357 if args == "help":
358 print "Usage: freeze SIG [, SIG [, SIG]...]"
359 print " Do not consider signal for subsequent updates"
360 self.cmds.freeze(self.get_signames(args))
362 def unfreeze(self, args):
363 if args == "help":
364 print "Usage: unfreeze SIG [, SIG [, SIG]...]"
365 print " Consider signal for subsequent updates"
366 self.cmds.unfreeze(self.get_signames(args))
368 def siglist(self, args):
369 if args == "help":
370 print "Usage : siglist"
371 print " List loaded signals"
372 return
373 SEPARATOR = "\t"
374 HEADER=["Name", "Unit", "Ref", "Reader"]
375 print SEPARATOR.join(HEADER)
376 for reader_name, reader in self.cmds.readers.iteritems():
377 for signal_name, signal in reader.signals.iteritems():
378 print SEPARATOR.join((signal_name, \
379 signal.unit,\
380 signal.ref.name,\
381 reader_name))
383 def math(self, inp):
384 if inp == "help":
385 print "Usage: destsig=mathexpr"
386 print " Define a new signal destsig using mathematical expression"
387 return
388 try:
389 self.cmds.math(inp)
390 except ReadError, e:
391 print "Error creating signal from math expression", e
392 return
394 def get_signames(self, args):
395 """ Return the signal names list extracted from the commandline
396 The list must be a coma separated list of signal names.
397 If no signals are loaded of no signal are found, return []
399 sns = []
400 if args == "":
401 sns = []
402 else:
403 for sn in args.split(","):
404 sns.append(sn.strip())
405 return sns
407 def help(self, args):
408 """ Display help messages
410 if args == "":
411 print "\
412 Commands related to figures:\n\
413 create create a new figure\n\
414 destroy delete a figure\n\
415 select define the current figure and the current graph\n\
416 layout set_ the layout (either horiz, vert or quad)\n\
417 figlist list the existing figures\n\
418 plot draw and show the figures\n\
419 Commands related to graphs:\n\
420 add add a graph to the current figure\n\
421 delete delete a graph from the current figure\n\
422 mode set_ the mode of the current graph of the current figure\n\
423 unit set_ the units of the current graph of the current figure\n\
424 scale set_ the scale of the current graph of the current figure\n\
425 range set_ the axis range of the current graph of the current figure\n\
426 Commands related to signals:\n\
427 read read signals from file\n\
428 write write signals to file\n\
429 update reread signals from file(s)\n\
430 insert add a signal to the current graph of the current figure\n\
431 remove delete a signal from the current graph of the current figure\n\
432 (un)freeze toggle signal update\n\
433 siglist list the signals\n\
434 Misc commands:\n\
435 echo print a message\n\
436 pause wait for the user to press enter\n\
437 quit, exit exit the program\n\
438 help display this help message\n\
440 During plot:\n\
441 1, 2 Toggle first and second vertical cursors\n\
442 3, 4 Toggle first and second horizontal cursors\n\
443 Maths:\n\
444 SIG = EXPR Compute a signal from mathematical expression\n\
445 SIG = [i]fft(SIG)\n\
446 Compute Fast Fourier Transform (FFT) or inverse FFT from SIG\n\
448 Help for individual command can be obtained with 'help COMMAND'\
450 else:
451 if args in dir(cmds):
452 eval("self." + args + "(\"help\")")
453 else:
454 print "Unknown command", args
456 def echo(self, args):
457 if args == "help":
458 print "Usage: echo [TEXT]"
459 print " Print text"
460 return
461 print args
463 def pause(self, args):
464 if args == "help":
465 print "Usage: pause"
466 print " Wait for the user to press enter"
467 return
468 inp = raw_input("Press enter")
470 def loop(self, p, f = None, batch = False):
471 # Main loop
472 while True:
473 try:
474 if f is None:
475 inp = raw_input(p)
476 else:
477 try:
478 inp = f.readline().rstrip("\n")
479 if inp == "":
480 if batch == True:
481 # Batch mode, exit at the end of script
482 break
483 else:
484 # Interactive mode, continue with command line
485 f = None
486 continue
487 except IOError, e:
488 print "Script error:", e
489 f.close()
490 f = None
491 # Check if line is a comment
492 if inp.lstrip().startswith("#"):
493 continue
494 # Check if command is assignment
495 if inp.find("=") >= 0:
496 self.math(inp)
497 continue
499 # Separate command from args
500 if inp.find(" ") >= 0:
501 st = inp.lstrip().split(' ', 1)
502 cmd = st[0]
503 args = st[1]
504 # print "cmd:", cmd, "args:", args
505 else:
506 cmd = inp
507 args = ""
509 # End of program
510 if cmd == "exit" or cmd == "quit":
511 break
513 # Evaluate the command
514 if cmd in dir(self):
515 eval("self." + cmd + "(args)")
516 else:
517 print cmd, "not supported"
518 continue
520 except EOFError:
521 break
523 # except AttributeError, e:
524 # print "Unknown command:", e.message
525 # continue
527 # except NameError, e:
528 # print "Unknown command", e.message
529 # continue
531 except SyntaxError, e:
532 print "Syntax Error", e.message
533 continue
535 except ReadError, e:
536 print "Error in read :", e
538 readline.write_history_file(self.hist_file)
540 if __name__ == "__main__":
541 o = OscopyApp()