/[ascend]/trunk/pygtk/gtkbrowser.py
ViewVC logotype

Annotation of /trunk/pygtk/gtkbrowser.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1005 - (hide annotations) (download) (as text)
Mon Jan 1 23:35:40 2007 UTC (13 years, 7 months ago) by johnpye
File MIME type: text/x-python
File size: 34722 byte(s)
Fixed problem with default fileopenpath when ASCENDLIBRARY has multiple path components.
1 johnpye 132 #!/usr/bin/env python
2    
3 johnpye 361 import sys
4 johnpye 478 def print_loading_status(status,msg=None):
5     sys.stderr.write("\r \r")
6     if msg!=None:
7     sys.stderr.write(msg+"\n")
8 johnpye 803 sys.stderr.write(status+"...\r")
9 johnpye 478 sys.stderr.flush()
10 johnpye 361
11 johnpye 627 try:
12 johnpye 856 #print_loading_status("Loading PSYCO")
13 johnpye 627 #try:
14     # import psyco
15     # psyco.full()
16     # print "Running with PSYCO optimisation..."
17     #except ImportError:
18     # pass
19 johnpye 279
20 johnpye 463
21 johnpye 627 print_loading_status("Loading python standard libraries")
22 johnpye 478
23 johnpye 627 import re
24     import urlparse
25     import optparse
26     import platform
27     import sys
28     import os.path
29 johnpye 132
30 johnpye 627 if platform.system() != "Windows":
31     import dl
32     # This sets the flags for dlopen used by python so that the symbols in the
33     # ascend library are made available to libraries dlopened within ASCEND:
34     sys.setdlopenflags(dl.RTLD_GLOBAL|dl.RTLD_NOW)
35 johnpye 499
36 johnpye 627 print_loading_status("Loading LIBASCEND/ascpy")
37     import ascpy
38 johnpye 478
39 johnpye 627 print_loading_status("Loading PyGTK, glade, pango")
40 johnpye 478
41 johnpye 627 import pygtk
42     pygtk.require('2.0')
43     import gtk
44     import gtk.glade
45     import pango
46 johnpye 478
47 johnpye 627 print_loading_status("Loading python matplotlib")
48 johnpye 507 try:
49 johnpye 627 import matplotlib
50 johnpye 632 matplotlib.use('GTKAgg')
51 johnpye 627
52 johnpye 507 try:
53 johnpye 746 print_loading_status("Trying python numarray")
54     import numarray
55     matplotlib.rcParams['numerix'] = 'numarray'
56     print_loading_status("","Using python module numarray")
57 johnpye 507 except ImportError:
58     try:
59 johnpye 746 print_loading_status("Trying python numpy")
60     import numpy
61     matplotlib.rcParams['numerix'] = 'numpy'
62     print_loading_status("","Using python module numpy")
63 johnpye 507 except ImportError:
64 johnpye 627 try:
65     print_loading_status("Trying python Numeric")
66     import Numeric
67     matplotlib.rcParams['numerix'] = 'Numeric'
68     print_loading_status("","Using python module Numeric")
69     except ImportError:
70     print_loading_status("","FAILED TO LOAD A NUMERIC MODULE FOR PYTHON")
71 johnpye 507
72 johnpye 627 except ImportError,e:
73     print_loading_status("","FAILED TO LOAD MATPLOTLIB")
74     raise RuntimeError("Failed to load MATPLOTLIB (is it installed?). Details:"+str(e))
75 johnpye 507
76 johnpye 669 print_loading_status("Loading IPython")
77     import console;
78     if not console.have_ipython:
79     print_loading_status("","IPython couldn't be loaded")
80    
81 johnpye 627 print_loading_status("Loading ASCEND python modules")
82     from preferences import * # loading/saving of .ini options
83     from solverparameters import * # 'solver parameters' window
84     from help import * # viewing help files
85     from incidencematrix import * # incidence/sparsity matrix matplotlib window
86     from observer import * # observer tab support
87     from properties import * # solver_var properties dialog
88     from varentry import * # for inputting of variables with units
89     from diagnose import * # for diagnosing block non-convergence
90     from solverreporter import * # solver status reporting
91     from modelview import * # model browser
92 johnpye 732 from integrator import * # integrator dialog
93     from infodialog import * # general-purpose textual information dialog
94 johnpye 904 from versioncheck import * # version check (contacts ascend.cruncher2.dyndns.org)
95 johnpye 627 import config
96 johnpye 669
97 johnpye 627 except RuntimeError, e:
98     print "ASCEND had problems starting up. Please report the following"
99     print "error message at http://mantis.cruncher2.dyndns.org/."
100     print "\n\nFull error message:",str(e)
101     print "\n\nPress ENTER to close this window."
102     sys.stdout.flush()
103     sys.stdin.readline();
104     sys.exit();
105 johnpye 311
106 johnpye 627 except ImportError, e:
107     print "\n\n------------------ ERROR ---------------------"
108     print "ASCEND had problems importing required models."
109     print "\nPlease ensure you have all the runtime prerequisites installed."
110     print "Please then report a bug if you continue to have problems."
111     print "\nFull error message:",str(e)
112     if platform.system()=="Windows":
113     print "\nYou will also need to report the contents of any popup error"
114     print "messages from Windows if any were shown."
115     print "\n\nPress ENTER to close this window."
116     sys.stdout.flush()
117     sys.stdin.readline();
118     sys.exit();
119    
120 johnpye 478 print_loading_status("Starting GUI")
121    
122 johnpye 132 # This is my first ever GUI code so please be nice :)
123 johnpye 361 # But I *have* at least read
124     # http://www.joelonsoftware.com/uibook/chapters/fog0000000057.html
125     # and leafed through
126     # http://developer.gnome.org/projects/gup/hig/
127 johnpye 132
128     # The fancy tree-view gizmo is the GtkTreeView object. See the article
129     # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/300304
130     # for the original source code on which my implementation was based.
131    
132 johnpye 227 ESCAPE_KEY = 65307
133    
134 johnpye 230 HELP_ROOT = None
135    
136 johnpye 223 #======================================
137     # Browser is the main ASCEND library/model browser window
138    
139 johnpye 132 class Browser:
140    
141     # ---------------------------------
142     # SETUP
143    
144 johnpye 858 def __init__(self,librarypath=None,assetspath=None):
145    
146     if assetspath==None:
147     assetspath=config.PYGTK_ASSETS
148    
149 johnpye 132 #--------
150     # load the file referenced in the command line, if any
151    
152 johnpye 858 print_loading_status("Parsing options","CONFIG = %s"%config.VERSION)
153 johnpye 478
154 johnpye 132 parser = optparse.OptionParser(usage="%prog [[-m typename] file]", version="gtkbrowser $rev$" )
155     # add options here if we want
156    
157 johnpye 160 parser.add_option("-m", "--model"
158 johnpye 341 ,action="store", type="string", dest="model"
159 johnpye 160 ,help="specify the model to instantiate upon loading modules")
160 johnpye 436
161     parser.add_option("--pygtk-assets"
162 johnpye 455 ,action="store", type="string", dest="assets_dir"
163 johnpye 436 ,help="override the configuration value for the location of assets"\
164     +" required by PyGTK for the ASCEND GUI, optional"
165 johnpye 858 ,default=assetspath
166 johnpye 436 )
167    
168 johnpye 459 parser.add_option("--library"
169     ,action="store", type="string", dest="library_path"
170 johnpye 723 ,help="override the configuration value for the library path"
171 johnpye 858 ,default=librarypath
172 johnpye 459 )
173    
174 johnpye 723 parser.add_option("--no-auto-sim"
175     ,action="store_false", dest="auto_sim"
176     ,help="disable auto-instantiation of MODEL named as the file stem"
177     ,default=True
178     )
179    
180 johnpye 533 (self.options, args) = parser.parse_args()
181 johnpye 132
182 johnpye 533 #print "OPTIONS_______________:",self.options
183 johnpye 482
184 johnpye 533 self.assets_dir = self.options.assets_dir
185 johnpye 132
186 johnpye 246 self.observers = []
187 johnpye 251 self.clip = None
188 johnpye 246
189 johnpye 132 #--------
190     # load up the preferences ini file
191    
192 johnpye 478 print_loading_status("Loading preferences")
193    
194 johnpye 479 self.prefs = Preferences()
195 johnpye 132
196 johnpye 482 _prefpath = self.prefs.getStringPref("Directories","librarypath",None)
197 johnpye 500 _preffileopenpath = self.prefs.getStringPref("Directories","fileopenpath",None)
198 johnpye 482
199 johnpye 132 #--------
200 johnpye 482 # set up library path and the path to use for File->Open dialogs
201 johnpye 455
202 johnpye 533 if self.options.library_path != None:
203     _path = os.path.abspath(self.options.library_path)
204 johnpye 860 _pathsrc = "command line options"
205 johnpye 1005 # when a special path is specified, use the last path component as the file-open location
206     if platform.system()=="Windows":
207     self.fileopenpath = _path.split(":").pop()
208     else:
209     self.fileopenpath = _path.split(":").pop()
210 johnpye 356 else:
211 johnpye 482 if _prefpath:
212     _path = _prefpath
213     _pathsrc = "user preferences"
214     else:
215     _path = config.LIBRARY_PATH
216 johnpye 479 _pathsrc = "default (config.py)"
217 johnpye 482
218     if _preffileopenpath:
219     self.fileopenpath = _preffileopenpath
220 johnpye 479 else:
221 johnpye 482 self.fileopenpath = _path
222    
223     #--------
224     # Create the ASCXX 'Library' object
225    
226 johnpye 507 print_loading_status("Creating ASCEND 'Library' object","PATH = "+_path+" FROM "+_pathsrc)
227 johnpye 482 self.library = ascpy.Library(_path)
228 johnpye 479
229 johnpye 132 self.sim = None
230 johnpye 351
231 johnpye 132 #-------------------
232     # Set up the window and main widget actions
233    
234 johnpye 858 self.glade_file = os.path.join(self.assets_dir,config.GLADE_FILE)
235 johnpye 709
236 johnpye 895 print_loading_status("Setting up windows") #,"GLADE_FILE = %s" % self.glade_file)
237 johnpye 806
238 johnpye 436 glade = gtk.glade.XML(self.glade_file,"browserwin")
239 johnpye 132
240     self.window = glade.get_widget("browserwin")
241 johnpye 208
242 johnpye 709
243 johnpye 132 if not self.window:
244     raise RuntimeError("Couldn't load window from glade file")
245 johnpye 172
246 johnpye 294 _display = self.window.get_screen().get_display().get_name()
247 johnpye 173 _geom=self.prefs.getGeometrySizePosition(_display,"browserwin")
248 johnpye 172 if _geom:
249 johnpye 294 self.window.resize(_geom[0],_geom[1])
250     self.window.move(_geom[2],_geom[3])
251 johnpye 173
252 johnpye 132 self.window.connect("delete_event", self.delete_event)
253    
254 johnpye 294 self.browserpaned=glade.get_widget("browserpaned")
255     _geom2=self.prefs.getGeometryValue(_display,"browserpaned")
256 johnpye 173 if _geom2:
257 johnpye 294 self.browserpaned.set_position(_geom2)
258 johnpye 173
259 johnpye 132 self.openbutton=glade.get_widget("openbutton")
260     self.openbutton.connect("clicked",self.open_click)
261    
262     self.reloadbutton=glade.get_widget("reloadbutton")
263     self.reloadbutton.connect("clicked",self.reload_click)
264    
265     self.solvebutton=glade.get_widget("solvebutton")
266     self.solvebutton.connect("clicked",self.solve_click)
267    
268 johnpye 669 self.integratebutton=glade.get_widget("integratebutton")
269     self.integratebutton.connect("clicked",self.integrate_click)
270    
271 johnpye 132 self.checkbutton=glade.get_widget("checkbutton")
272     self.checkbutton.connect("clicked",self.check_click)
273    
274     self.autotoggle=glade.get_widget("autotoggle")
275 johnpye 688 self.automenu = glade.get_widget("automenu")
276 johnpye 132 self.autotoggle.connect("toggled",self.auto_toggle)
277    
278     self.methodrunbutton=glade.get_widget("methodrunbutton")
279     self.methodrunbutton.connect("clicked",self.methodrun_click)
280    
281     self.methodsel=glade.get_widget("methodsel")
282    
283     self.maintabs = glade.get_widget("maintabs")
284    
285 johnpye 164 self.statusbar = glade.get_widget("statusbar")
286    
287 johnpye 208 self.menu = glade.get_widget("browsermenu")
288    
289 johnpye 321 self.show_solving_popup=glade.get_widget("show_solving_popup")
290     self.show_solving_popup.set_active(self.prefs.getBoolPref("SolverReporter","show_popup",True))
291     self.close_on_converged=glade.get_widget("close_on_converged")
292     self.close_on_converged.set_active(self.prefs.getBoolPref("SolverReporter","close_on_converged",True))
293     self.close_on_nonconverged=glade.get_widget("close_on_nonconverged")
294     self.close_on_nonconverged.set_active(self.prefs.getBoolPref("SolverReporter","close_on_nonconverged",True))
295 johnpye 785 self.solver_engine=glade.get_widget("solver_engine")
296 johnpye 164
297 johnpye 770 self.use_relation_sharing=glade.get_widget("use_relation_sharing")
298     self.use_relation_sharing.set_active(self.prefs.getBoolPref("Compiler","use_relation_sharing",True))
299    
300 johnpye 688 glade.signal_autoconnect(self)
301    
302 johnpye 533 #-------
303     # Status icons
304 johnpye 164
305 johnpye 268 self.fixedimg = gtk.Image()
306 johnpye 858 self.fixedimg.set_from_file(os.path.join(self.options.assets_dir,'locked.png'))
307 johnpye 268
308 johnpye 258 self.iconstatusunknown = None
309 johnpye 268 self.iconfixed = self.fixedimg.get_pixbuf()
310 johnpye 255 self.iconsolved = self.window.render_icon(gtk.STOCK_YES,gtk.ICON_SIZE_MENU)
311 johnpye 258 self.iconactive = self.window.render_icon(gtk.STOCK_NO,gtk.ICON_SIZE_MENU)
312 johnpye 255 self.iconunsolved = None
313    
314     self.statusicons={
315 johnpye 463 ascpy.ASCXX_VAR_STATUS_UNKNOWN: self.iconstatusunknown
316     ,ascpy.ASCXX_VAR_FIXED: self.iconfixed
317     ,ascpy.ASCXX_VAR_SOLVED: self.iconsolved
318     ,ascpy.ASCXX_VAR_ACTIVE: self.iconactive
319     ,ascpy.ASCXX_VAR_UNSOLVED: self.iconunsolved
320 johnpye 255 }
321 johnpye 533
322    
323 johnpye 273 self.statusmessages={
324 johnpye 463 ascpy.ASCXX_VAR_STATUS_UNKNOWN: "Status unknown"
325     ,ascpy.ASCXX_VAR_FIXED: "Fixed"
326     ,ascpy.ASCXX_VAR_SOLVED: "Converged"
327     ,ascpy.ASCXX_VAR_ACTIVE: "Active (unconverged)"
328     ,ascpy.ASCXX_VAR_UNSOLVED: "Not yet visited"
329 johnpye 533 }
330 johnpye 255
331 johnpye 533 #-------------------
332     # waitwin
333 johnpye 132
334 johnpye 533 self.waitwin = gtk.gdk.Window(self.window.window,
335     gtk.gdk.screen_width(),
336     gtk.gdk.screen_height(),
337     gtk.gdk.WINDOW_CHILD,
338     0,
339     gtk.gdk.INPUT_ONLY)
340 johnpye 226
341 johnpye 533 _cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
342     self.waitwin.set_cursor(_cursor)
343 johnpye 181
344 johnpye 533 #-------------------
345     # pixbufs to be used in the error listing
346 johnpye 181
347 johnpye 533 self.iconok = self.window.render_icon(gtk.STOCK_YES,gtk.ICON_SIZE_MENU)
348     self.iconinfo = self.window.render_icon(gtk.STOCK_DIALOG_INFO,gtk.ICON_SIZE_MENU)
349     self.iconwarning = self.window.render_icon(gtk.STOCK_DIALOG_WARNING,gtk.ICON_SIZE_MENU)
350     self.iconerror = self.window.render_icon(gtk.STOCK_DIALOG_ERROR,gtk.ICON_SIZE_MENU)
351 johnpye 181
352 johnpye 533 #--------------------
353     # pixbufs for solver_var status
354 johnpye 181
355 johnpye 132 #--------------------
356     # set up the error view
357    
358 johnpye 168 self.errorview = glade.get_widget("errorview")
359 johnpye 132 errstorecolstypes = [gtk.gdk.Pixbuf,str,str,str,int]
360     self.errorstore = gtk.TreeStore(*errstorecolstypes)
361     errtitles = ["","Location","Message"];
362     self.errorview.set_model(self.errorstore)
363     self.errcols = [ gtk.TreeViewColumn() for _type in errstorecolstypes]
364    
365     i = 0
366     for tvcolumn in self.errcols[:len(errtitles)]:
367     tvcolumn.set_title(errtitles[i])
368     self.errorview.append_column(tvcolumn)
369    
370     if i>0:
371     _renderer = gtk.CellRendererText()
372     tvcolumn.pack_start(_renderer, True)
373     tvcolumn.add_attribute(_renderer, 'text', i)
374     if(i==2):
375     tvcolumn.add_attribute(_renderer, 'foreground', 3)
376     tvcolumn.add_attribute(_renderer, 'weight', 4)
377     else:
378     _renderer1 = gtk.CellRendererPixbuf()
379     tvcolumn.pack_start(_renderer1, False)
380     tvcolumn.add_attribute(_renderer1, 'pixbuf', int(0))
381    
382     i = i + 1
383    
384    
385     #--------------------
386     # set up the error reporter callback
387 johnpye 463 self.reporter = ascpy.getReporter()
388 johnpye 132 self.reporter.setPythonErrorCallback(self.error_callback)
389    
390 johnpye 785
391     #-------
392     # Solver engine list
393    
394     _slvlist = ascpy.getSolvers()
395     self.solver_engine_menu = gtk.Menu()
396     self.solver_engine_menu.show()
397     self.solver_engine.set_submenu(self.solver_engine_menu)
398     self.solver_engine_menu_dict = {}
399     _fmi = None
400     for _s in _slvlist:
401     _mi = gtk.RadioMenuItem(_fmi,_s.getName(),False)
402     if _fmi==None:
403     _fmi = _mi
404     _mi.show()
405     _mi.connect('toggled',self.on_select_solver_toggled,_s.getName())
406     self.solver_engine_menu.append(_mi)
407     self.solver_engine_menu_dict[_s.getName()]=_mi
408    
409 johnpye 803 _pref_solver = self.prefs.getStringPref("Solver","engine","QRSlv")
410     _mi = self.solver_engine_menu_dict.get(_pref_solver)
411     if _mi:
412     _mi.set_active(1)
413 johnpye 785
414 johnpye 709 #--------
415     # Assign an icon to the main window
416    
417     self.icon = None
418     if config.ICON_EXTENSION:
419     _iconpath = ""
420     try:
421     _icon = gtk.Image()
422 johnpye 858 _iconpath = os.path.join(self.assets_dir,'ascend'+config.ICON_EXTENSION)
423 johnpye 709 _icon.set_from_file(_iconpath)
424     _iconpbuf = _icon.get_pixbuf()
425     self.window.set_icon(_iconpbuf)
426     self.icon = _iconpbuf
427     except Exception, e:
428 johnpye 860 print "FAILED TO SET APPLICATION ICON PATH '%s': %s" % (_iconpath,str(e))
429 johnpye 709 self.reporter.reportError("FAILED to set application icon '%s': %s"
430     % (_iconpath,str(e))
431     )
432    
433 johnpye 132 #-------------------
434     # set up the module view
435    
436     self.modtank = {}
437     self.moduleview = glade.get_widget("moduleview")
438 johnpye 355 modulestorecoltypes = [str, str, int] # bool=can-be-instantiated
439 johnpye 132 self.modulestore = gtk.TreeStore(*modulestorecoltypes)
440     moduleviewtitles = ["Module name", "Filename"]
441     self.moduleview.set_model(self.modulestore)
442     self.modcols = [ gtk.TreeViewColumn() for _type in modulestorecoltypes]
443     i = 0
444     for modcol in self.modcols[:len(moduleviewtitles)]:
445     modcol.set_title(moduleviewtitles[i])
446     self.moduleview.append_column(modcol)
447     _renderer = gtk.CellRendererText()
448     modcol.pack_start(_renderer, True)
449     modcol.add_attribute(_renderer, 'text', i)
450 johnpye 355 modcol.add_attribute(_renderer,'weight',2)
451 johnpye 132 i = i + 1
452     self.moduleview.connect("row-activated", self.module_activated )
453    
454     #--------------------
455     # set up the methods combobox
456    
457     self.methodstore = gtk.ListStore(str)
458     self.methodsel.set_model(self.methodstore)
459     _methodrenderer = gtk.CellRendererText()
460     self.methodsel.pack_start(_methodrenderer, True)
461     self.methodsel.add_attribute(_methodrenderer, 'text',0)
462    
463     #--------
464     # set up the instance browser view
465    
466 johnpye 533 self.modelview = ModelView(self, glade)
467 johnpye 255
468 johnpye 533 #--------
469 johnpye 856 # set up the tabs
470     self.tabs = {}
471     self.activetab = None # most recent observer tab
472    
473     #--------
474 johnpye 688 # set the state of the 'auto' toggle
475    
476     self.is_auto = self.prefs.getBoolPref("Browser","auto_solve",True)
477     self.autotoggle.set_active(self.is_auto)
478     self.automenu.set_active(self.is_auto)
479    
480     #--------
481 johnpye 869 # tell libascend about this 'browser' object
482    
483     print dir(ascpy.Registry())
484     ascpy.Registry().set("browser",self)
485    
486     #--------
487 johnpye 533 # options
488 johnpye 132
489     if(len(args)==1):
490     self.do_open(args[0])
491    
492 johnpye 723 print "Options: ",self.options
493 johnpye 132
494 johnpye 723 _model = None
495 johnpye 533 if self.options.model:
496 johnpye 723 _model = self.options.model
497     print "MODEL: '%s'" % _model
498     elif self.options.auto_sim:
499 johnpye 728 _head, _tail = os.path.split(args[0])
500     if(_tail):
501     _model, _ext = os.path.splitext(_tail)
502 johnpye 723
503     if _model:
504 johnpye 132 try:
505 johnpye 723 _t=self.library.findType(_model)
506     try:
507     self.do_sim(_t)
508 johnpye 725 if not self.options.model:
509 johnpye 901 self.reporter.reportNote("Instantiated self-titled model '%s'" %_model)
510 johnpye 723 except RuntimeError, e:
511     self.reporter.reportError("Failed to create instance of '%s': %s"
512     %(_model, str(e))
513     );
514 johnpye 132 except RuntimeError, e:
515 johnpye 723 if self.options.model:
516     self.reporter.reportError("Unknown model type '%s': %s"
517     %(_model, str(e))
518     );
519 johnpye 223
520 johnpye 132 def run(self):
521     self.window.show()
522 johnpye 500 print_loading_status("ASCEND is now running")
523 johnpye 132 gtk.main()
524    
525 johnpye 785 # ------------------
526     # SOLVER LIST
527    
528     def set_solver(self,solvername):
529 johnpye 900 """ this sets the active solver in the GUI, which is the default applied to newly instantiated models """
530 johnpye 785 self.solver = ascpy.Solver(solvername)
531 johnpye 803 self.prefs.setStringPref("Solver","engine",solvername)
532 johnpye 785 self.reporter.reportNote("Set solver engine to '%s'" % solvername)
533    
534 johnpye 132 # --------------------------------------------
535     # MAJOR GUI COMMANDS
536    
537 johnpye 533 def on_fix_variable_activate(self,*args):
538     self.modelview.on_fix_variable_activate(*args)
539 johnpye 132
540 johnpye 533 def on_free_variable_activate(self,*args):
541     self.modelview.on_free_variable_activate(*args)
542    
543 johnpye 785 def on_select_solver_toggled(self,widget,solvername):
544     if widget.get_active():
545     self.set_solver(solvername)
546    
547 johnpye 132 def do_open(self,filename):
548     # TODO does the user want to lose their work?
549     # TODO do we need to chdir?
550    
551 johnpye 164 _context = self.statusbar.get_context_id("do_open")
552    
553 johnpye 132 self.errorstore.clear()
554 johnpye 533 self.modelview.clear()
555 johnpye 132
556 johnpye 175 # self.library.clear()
557 johnpye 164
558 johnpye 875 print "Filename =",filename
559 johnpye 164 self.statusbar.push(_context,"Loading '"+filename+"'")
560 johnpye 132 self.library.load(filename)
561 johnpye 875 print "Statusbar =",self.statusbar
562     try:
563     self.statusbar.pop(_context)
564     except TypeError,e:
565     print "For some reason, a type error (context=%s,filename=%s): %s" % (_context,filename,e)
566 johnpye 132
567     self.filename = filename
568    
569     # Load the current list of modules into self.modules
570     self.modtank = {}
571     self.modulestore.clear()
572     modules = self.library.getModules()
573 johnpye 895 #self.library.listModules()
574 johnpye 338 try:
575     _lll=len(modules)
576     except:
577     _msg = "UNABLE TO ACCESS MODULES LIST. This is bad.\n"+\
578 johnpye 669 "Check your SWIG configuration (check for warnings during build)."
579 johnpye 338
580     self.reporter.reportError(_msg)
581     raise RuntimeError(_msg)
582    
583 johnpye 132 for m in reversed(modules):
584     _n = str( m.getName() )
585     _f = str( m.getFilename() )
586     #print "ADDING ROW name %s, file = %s" % (_n, _f)
587 johnpye 355 _r = self.modulestore.append(None, [ _n, _f, pango.WEIGHT_NORMAL ])
588 johnpye 132 for t in self.library.getModuleTypes(m):
589     _n = t.getName()
590 johnpye 355 _hasparams = t.hasParameters()
591     if _hasparams:
592     _w = pango.WEIGHT_NORMAL
593     else:
594     _w = pango.WEIGHT_BOLD
595    
596 johnpye 132 #print "ADDING TYPE %s" % _n
597 johnpye 355 _piter = self.modulestore.append(_r , [ _n, "", _w ])
598 johnpye 132 _path = self.modulestore.get_path(_piter)
599     self.modtank[_path]=t
600    
601     #print "DONE ADDING MODULES"
602    
603     self.sim = None;
604     self.maintabs.set_current_page(0);
605 johnpye 164
606 johnpye 168 # See http://www.daa.com.au/pipermail/pygtk/2005-October/011303.html
607     # for details on how the 'wait cursor' is done.
608 johnpye 164 def start_waiting(self, message):
609     self.waitcontext = self.statusbar.get_context_id("waiting")
610     self.statusbar.push(self.waitcontext,message)
611    
612     if self.waitwin:
613     self.waitwin.show()
614    
615     while gtk.events_pending():
616     gtk.main_iteration()
617 johnpye 132
618 johnpye 164 def stop_waiting(self):
619     if self.waitwin:
620     self.statusbar.pop(self.waitcontext)
621     self.waitwin.hide()
622    
623 johnpye 132 def do_sim(self, type_object):
624     self.sim = None;
625     # TODO: clear out old simulation first!
626    
627     print "DO_SIM(%s)" % str(type_object.getName())
628 johnpye 164 self.start_waiting("Compiling...")
629    
630 johnpye 277 try:
631 johnpye 770 _v = self.prefs.getBoolPref("Compiler","use_relation_sharing",True)
632     ascpy.getCompiler().setUseRelationSharing(_v)
633    
634 johnpye 932 self.sim = type_object.getSimulation(str(type_object.getName())+"_sim",False)
635 johnpye 900
636     self.reporter.reportNote("SIMULATION ASSIGNED")
637 johnpye 277 except RuntimeError, e:
638     self.stop_waiting()
639     self.reporter.reportError(str(e))
640     return
641    
642 johnpye 132 print "...DONE 'getSimulation'"
643 johnpye 164 self.stop_waiting()
644 johnpye 132
645 johnpye 902 # get method names and load them into the GUI
646     self.methodstore.clear()
647     _methods = self.sim.getType().getMethods()
648     _activemethod = None;
649     for _m in _methods:
650     _i = self.methodstore.append([_m.getName()])
651     if _m.getName()=="on_load":
652     self.methodsel.set_active_iter(_i)
653    
654     self.modelview.setSimulation(self.sim)
655    
656     # run the 'on_load' method
657 johnpye 900 self.start_waiting("Running default method...")
658     try:
659 johnpye 915 self.reporter.reportNote("SIMULATION CREATED, RUNNING DEFAULT METHOD NOW...")
660 johnpye 900 self.sim.runDefaultMethod()
661     except RuntimeError, e:
662     self.stop_waiting()
663     self.reporter.reportError(str(e))
664     return
665     self.stop_waiting()
666 johnpye 903
667     self.modelview.refreshtree()
668 johnpye 249
669     def do_solve_if_auto(self):
670     if self.is_auto:
671 johnpye 775 self.sim.checkInstance()
672 johnpye 249 self.do_solve()
673     else:
674 johnpye 255 self.sim.processVarStatus()
675 johnpye 533 self.modelview.refreshtree()
676 johnpye 249
677     self.sync_observers()
678 johnpye 132
679     def do_solve(self):
680     if not self.sim:
681     self.reporter.reportError("No model selected yet")
682 johnpye 669 return
683 johnpye 132
684 johnpye 919 try:
685     self.sim.build()
686     except RuntimeError,e:
687     self.reporter.reportError("Couldn't build system: %s",str(e));
688     return
689    
690 johnpye 786 self.start_waiting("Solving with %s..." % self.solver.getName())
691 johnpye 164
692 johnpye 321 if self.prefs.getBoolPref("SolverReporter","show_popup",True):
693 johnpye 351 reporter = PopupSolverReporter(self,self.sim.getNumVars())
694 johnpye 321 else:
695     reporter = SimpleSolverReporter(self)
696    
697 johnpye 785 self.sim.solve(self.solver,reporter)
698 johnpye 164
699 johnpye 168 self.stop_waiting()
700 johnpye 255
701 johnpye 533 self.modelview.refreshtree()
702 johnpye 132
703 johnpye 669 def do_integrate(self):
704     if not self.sim:
705     self.reporter.reportError("No model selected yet")
706     return
707     integwin = IntegratorWindow(self,self.sim)
708     _integratorreporter = integwin.run()
709     if _integratorreporter!=None:
710     _integratorreporter.run()
711     self.sim.processVarStatus()
712     self.modelview.refreshtree()
713    
714    
715 johnpye 132 def do_check(self):
716     if not self.sim:
717 johnpye 775 self.reporter.reportError("No simulation yet")
718 johnpye 669 return
719 johnpye 132
720 johnpye 171 self.start_waiting("Checking system...")
721    
722 johnpye 278 try:
723 johnpye 775 self.sim.checkInstance()
724     self.reporter.reportWarning("System instance check run, check above for error (if any).")
725     # the above gives output but doesn't throw errors or return a status.
726     # ... this is a problem (at the C level)
727 johnpye 772
728 johnpye 775 status = self.sim.checkDoF()
729     if status==ascpy.ASCXX_DOF_UNDERSPECIFIED:
730 johnpye 776 self.on_show_fixable_variables_activate(None)
731 johnpye 918 return
732 johnpye 775 elif status==ascpy.ASCXX_DOF_OVERSPECIFIED:
733 johnpye 776 self.on_show_freeable_variables_activate(None)
734 johnpye 918 return
735 johnpye 775 elif status==ascpy.ASCXX_DOF_STRUCT_SINGULAR:
736     if not self.sim.checkStructuralSingularity():
737     sing = self.sim.getSingularityInfo()
738     title = "Structural singularity"
739     text = title
740     text += "\n\nThe singularity can be reduced by freeing the following variables:"
741     msgs = {
742     "The singularity can be reduced by freeing the following variables" : sing.freeablevars
743     ,"Relations involved in the structural singularity" : sing.rels
744     ,"Variables involved in the structural singularity" : sing.vars
745     }
746     for k,v in msgs.iteritems():
747     text+="\n\n%s:" % k
748     if len(v):
749     _l = [j.getName() for j in v]
750     _l.sort()
751     text+= "\n\t" + "\n\t".join(_l)
752     else:
753     text += "\nnone"
754 johnpye 772
755 johnpye 775 _dialog = InfoDialog(self,self.window,text,title)
756     _dialog.run()
757 johnpye 918 return
758 johnpye 775
759     self.reporter.reportNote("System DoF check OK")
760    
761 johnpye 278 except RuntimeError, e:
762     self.stop_waiting()
763     self.reporter.reportError(str(e))
764     return
765 johnpye 171
766     self.stop_waiting()
767    
768 johnpye 533 self.modelview.refreshtree()
769 johnpye 132
770     def do_method(self,method):
771     if not self.sim:
772     self.reporter.reportError("No model selected yet")
773    
774     self.sim.run(method)
775 johnpye 917 self.sim.processVarStatus()
776 johnpye 533 self.modelview.refreshtree()
777 johnpye 132
778 johnpye 230 def do_quit(self):
779 johnpye 482 print_loading_status("Saving window location")
780 johnpye 230 self.reporter.clearPythonErrorCallback()
781 johnpye 482
782 johnpye 230 _w,_h = self.window.get_size()
783     _t,_l = self.window.get_position()
784     _display = self.window.get_screen().get_display().get_name()
785     self.prefs.setGeometrySizePosition(_display,"browserwin",_w,_h,_t,_l );
786    
787     _p = self.browserpaned.get_position()
788     self.prefs.setGeometryValue(_display,"browserpaned",_p);
789    
790 johnpye 482 print_loading_status("Saving current directory")
791     self.prefs.setStringPref("Directories","fileopenpath",self.fileopenpath)
792    
793 johnpye 682 self.prefs.setBoolPref("Browser","auto_solve",self.is_auto)
794    
795 johnpye 482 print_loading_status("Saving preferences")
796 johnpye 230 # causes prefs to be saved unless they are still being used elsewher
797     del(self.prefs)
798    
799 johnpye 482 print_loading_status("Closing down GTK")
800 johnpye 230 gtk.main_quit()
801 johnpye 482
802     print_loading_status("Clearing error callback")
803     self.reporter.clearPythonErrorCallback()
804    
805     print_loading_status("Quitting")
806 johnpye 230 return False
807    
808 johnpye 231 def on_tools_sparsity_click(self,*args):
809 johnpye 237
810     self.reporter.reportNote("Preparing incidence matrix...")
811 johnpye 233 _im = self.sim.getIncidenceMatrix();
812 johnpye 231
813 johnpye 237 self.reporter.reportNote("Plotting incidence matrix...")
814 johnpye 234
815 johnpye 237 _sp = IncidenceMatrixWindow(_im);
816     _sp.run();
817    
818 johnpye 912 def on_tools_repaint_tree_activate(self,*args):
819     self.reporter.reportNote("Repainting model view...")
820     self.modelview.refreshtree()
821    
822 johnpye 280 def on_diagnose_blocks_click(self,*args):
823 johnpye 285 try:
824     _bl = self.sim.getActiveBlock()
825     except RuntimeError, e:
826     self.reporter.reportError(str(e))
827     return
828 johnpye 351 _db = DiagnoseWindow(self,_bl)
829 johnpye 280 _db.run();
830    
831 johnpye 245 def on_add_observer_click(self,*args):
832 johnpye 361 self.create_observer()
833 johnpye 245
834 johnpye 250 def on_keep_observed_click(self,*args):
835 johnpye 856 print "KEEPING..."
836 johnpye 250 if len(self.observers) <= 0:
837     self.reporter.reportError("No observer defined!")
838     return
839 johnpye 856 self.tabs[self.currentobservertab].do_add_row()
840 johnpye 250
841     def on_copy_observer_matrix_click(self,*args):
842 johnpye 251 if self.clip == None:
843     self.clip = gtk.Clipboard()
844    
845 johnpye 250 if len(self.observers) <= 0:
846     self.reporter.reportError("No observer defined!")
847     return
848 johnpye 856 self.tabs[self.currentobservertab].copy_to_clipboard(self.clip)
849 johnpye 270
850 johnpye 770 def on_use_relation_sharing_toggle(self,checkmenuitem,*args):
851     _v = checkmenuitem.get_active()
852     self.prefs.setBoolPref("Compiler","use_relation_sharing",_v)
853     self.reporter.reportNote("Relation sharing set to "+str(_v))
854    
855 johnpye 321 def on_show_solving_popup_toggle(self,checkmenuitem,*args):
856     _v = checkmenuitem.get_active()
857     self.prefs.setBoolPref("SolverReporter","show_popup",_v)
858     print "SET TO",_v
859    
860     def on_close_on_converged_toggle(self,checkmenuitem,*args):
861     _v = checkmenuitem.get_active()
862     self.prefs.setBoolPref("SolverReporter","close_on_converged",_v)
863    
864     def on_close_on_nonconverged_toggle(self,checkmenuitem,*args):
865     _v = checkmenuitem.get_active()
866     self.prefs.setBoolPref("SolverReporter","close_on_nonconverged",_v)
867    
868 johnpye 328 def on_show_variables_near_bounds_activate(self,*args):
869     _epsilon = 1e-4;
870 johnpye 895 text = "Variables Near Bounds"
871 johnpye 732 title=text;
872     text += "\n"
873 johnpye 328 _vars = self.sim.getVariablesNearBounds(_epsilon)
874 johnpye 732 if len(_vars):
875     for _v in _vars:
876     text += "\n%s"%_v.getName()
877     else:
878     text +="\nnone"
879     _dialog = InfoDialog(self,self.window,text,title)
880     _dialog.run()
881 johnpye 328
882 johnpye 132 # --------------------------------------------
883     # MODULE LIST
884    
885     def module_activated(self, treeview, path, column, *args):
886     modules = self.library.getModules()
887     print "PATH",path
888     if len(path)==1:
889     self.reporter.reportNote("Launching of external editor not yet implemented")
890     elif len(path)==2:
891     if(self.modtank.has_key(path)):
892     _type = self.modtank[path];
893     self.reporter.reportNote("Creating simulation for type %s" % str(_type.getName()) )
894     self.do_sim(_type)
895     else:
896     self.reporter.reportError("Didn't find type corresponding to row")
897    
898     # ----------------------------------
899     # ERROR PANEL
900    
901 johnpye 255 def get_error_row_data(self,sev,filename,line,msg):
902 johnpye 933 try:
903     _sevicon = {
904     0 : self.iconok
905     ,1 : self.iconinfo
906     ,2 : self.iconwarning
907     ,4 : self.iconerror
908     ,8 : self.iconinfo
909     ,16 : self.iconwarning
910     ,32 : self.iconerror
911     ,64 : self.iconerror
912     }[sev]
913     except KeyError:
914     _sevicon = self.iconerror
915 johnpye 255
916     _fontweight = pango.WEIGHT_NORMAL
917 johnpye 933 if sev==32 or sev==64:
918 johnpye 255 _fontweight = pango.WEIGHT_BOLD
919    
920     _fgcolor = "black"
921 johnpye 933 if sev==8:
922 johnpye 255 _fgcolor = "#888800"
923 johnpye 933 elif sev==16:
924 johnpye 255 _fgcolor = "#884400"
925 johnpye 933 elif sev==32 or sev==64:
926 johnpye 255 _fgcolor = "#880000"
927     elif sev==0:
928     _fgcolor = BROWSER_FIXED_COLOR
929    
930     if not filename and not line:
931     _fileline = ""
932     else:
933     if(len(filename) > 25):
934     filename = "..."+filename[-22:]
935     _fileline = filename + ":" + str(line)
936    
937 johnpye 933 _res = (_sevicon,_fileline,msg.rstrip(),_fgcolor,_fontweight)
938 johnpye 255 #print _res
939     return _res
940    
941 johnpye 132 def error_callback(self,sev,filename,line,msg):
942 johnpye 933 #print "SEV =",sev
943     #print "FILENAME =",filename
944     #print "LINE =",line
945     #print "MSG =",msg
946     pos = self.errorstore.append(None, self.get_error_row_data(sev, filename,line,msg))
947     path = self.errorstore.get_path(pos)
948     col = self.errorview.get_column(3)
949     self.errorview.scroll_to_cell(path,col)
950 johnpye 132 return 0;
951    
952     # --------------------------------
953     # BUTTON METHODS
954    
955     def open_click(self,*args):
956 johnpye 669 #print_loading_status("CURRENT FILEOPENPATH is",self.fileopenpath)
957 johnpye 482 dialog = gtk.FileChooserDialog("Open ASCEND model...",
958     self.window,
959     gtk.FILE_CHOOSER_ACTION_OPEN,
960     (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)
961     )
962     dialog.set_current_folder(self.fileopenpath)
963 johnpye 132 dialog.set_default_response(gtk.RESPONSE_OK)
964 johnpye 329 dialog.set_transient_for(self.window)
965     dialog.set_modal(True)
966 johnpye 132
967     filter = gtk.FileFilter()
968     filter.set_name("*.a4c, *.a4l")
969     filter.add_pattern("*.[Aa]4[Cc]")
970     filter.add_pattern("*.[Aa]4[Ll]")
971     dialog.add_filter(filter)
972    
973     filter = gtk.FileFilter()
974     filter.set_name("All files")
975     filter.add_pattern("*")
976     dialog.add_filter(filter)
977    
978     response = dialog.run()
979     _filename = dialog.get_filename()
980 johnpye 669 print "\nFILENAME SELECTED:",_filename
981 johnpye 482
982     _path = dialog.get_current_folder()
983     if _path:
984     self.fileopenpath = _path
985    
986 johnpye 329 dialog.hide()
987 johnpye 132
988     if response == gtk.RESPONSE_OK:
989     self.reporter.reportNote("File %s selected." % dialog.get_filename() )
990 johnpye 185 self.library.clear()
991 johnpye 329 self.do_open( _filename)
992 johnpye 132
993     def reload_click(self,*args):
994     _type = None
995     if(self.sim):
996     _type = self.sim.getType().getName().toString();
997    
998 johnpye 185 self.library.clear()
999 johnpye 132 self.do_open(self.filename)
1000    
1001     if _type:
1002     _t = self.library.findType(_type)
1003     self.do_sim(_t)
1004    
1005 johnpye 533 def props_activate(self,widget,*args):
1006     return self.modelview.props_activate(self,widget,*args)
1007    
1008     def observe_activate(self,widget,*args):
1009     return self.modelview.observe_activate(self,widget,*args)
1010    
1011 johnpye 132 def solve_click(self,*args):
1012     #self.reporter.reportError("Solving simulation '" + self.sim.getName().toString() +"'...")
1013     self.do_solve()
1014 johnpye 669
1015     def console_click(self,*args):
1016     try:
1017     console.start(self)
1018     except RuntimeError,e:
1019     self.reporter.reportError("Unable to start console: "+str(e));
1020    
1021     def integrate_click(self,*args):
1022     self.do_integrate()
1023 johnpye 132
1024     def check_click(self,*args):
1025     self.do_check()
1026     #self.reporter.reportError("CHECK clicked")
1027    
1028 johnpye 208 def preferences_click(self,*args):
1029     if not self.sim:
1030 johnpye 919 self.reporter.reportError("No simulation created yet!");
1031     self.sim.setSolver(self.solver)
1032 johnpye 351 _paramswin = SolverParametersWindow(self)
1033 johnpye 223 _paramswin.show()
1034 johnpye 221
1035 johnpye 132 def methodrun_click(self,*args):
1036     _sel = self.methodsel.get_active_text()
1037     if _sel:
1038     _method = None
1039     _methods = self.sim.getType().getMethods()
1040     for _m in _methods:
1041     if _m.getName()==_sel:
1042     _method = _m
1043     if not _method:
1044     self.reporter.reportError("Method is not valid")
1045     return
1046     self.do_method(_method)
1047     else:
1048     self.reporter.reportError("No method selected")
1049    
1050     def auto_toggle(self,button,*args):
1051 johnpye 230 self.is_auto = button.get_active()
1052 johnpye 688 if hasattr(self,'automenu'):
1053     self.automenu.set_active(self.is_auto)
1054 johnpye 132 else:
1055 johnpye 688 raise RuntimeError("no automenu")
1056 johnpye 132
1057 johnpye 688 #if self.is_auto:
1058     # self.reporter.reportSuccess("Auto mode is now ON")
1059     #else:
1060     # self.reporter.reportSuccess("Auto mode is now OFF")
1061    
1062 johnpye 230 def on_file_quit_click(self,*args):
1063     self.do_quit()
1064    
1065     def on_tools_auto_toggle(self,checkmenuitem,*args):
1066     self.is_auto = checkmenuitem.get_active()
1067     self.autotoggle.set_active(self.is_auto)
1068    
1069     def on_help_about_click(self,*args):
1070 johnpye 436 _xml = gtk.glade.XML(self.glade_file,"aboutdialog")
1071 johnpye 307 _about = _xml.get_widget("aboutdialog")
1072 johnpye 328 _about.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
1073     _about.set_transient_for(self.window);
1074 johnpye 478 _about.set_version(config.VERSION)
1075 johnpye 307 _about.run()
1076     _about.destroy()
1077 johnpye 230
1078     def on_help_contents_click(self,*args):
1079     _help = Help(HELP_ROOT)
1080     _help.run()
1081    
1082 johnpye 904 def on_help_check_for_updates_click(self,*args):
1083     v = VersionCheck()
1084     title = "Check for updates"
1085     text = "Your version is %s\n" % config.VERSION
1086 johnpye 905 try:
1087     v.check()
1088 johnpye 965 if config.VERSION==v.latest:
1089     text += "You are running the latest released version"
1090     else:
1091     text += "Latest version is %s\n" % v.latest
1092     if v.info:
1093     text += "Get more info at %s\n" % v.info
1094     if v.download:
1095     text += "Download from %s\n" % v.download
1096 johnpye 905 except Exception, e:
1097     text += "\nUnable to check version\n"
1098     text += str(e)
1099 johnpye 904
1100     _dialog = InfoDialog(self,self.window,text,title)
1101     _dialog.run()
1102    
1103 johnpye 775 def on_show_fixable_variables_activate(self,*args):
1104 johnpye 290 v = self.sim.getFixableVariables()
1105 johnpye 732 text = "Fixable Variables"
1106 johnpye 750 title = text
1107 johnpye 732 text += "\n"
1108     if len(v):
1109     for var in v:
1110     text += "\n%s"%var
1111     else:
1112     text += "\nnone"
1113     _dialog = InfoDialog(self,self.window,text,title)
1114     _dialog.run()
1115 johnpye 290
1116 johnpye 775 def on_show_freeable_variables_activate(self,*args):
1117     v = self.sim.getFreeableVariables()
1118    
1119     text = "Freeable Variables"
1120     title = text
1121     text += "\n"
1122     if len(v):
1123     for var in v:
1124     text += "\n%s" % var
1125     else:
1126     text += "\nnone"
1127     _dialog = InfoDialog(self,self.window,text,title)
1128     _dialog.run()
1129    
1130 johnpye 750 def on_show_external_functions_activate(self,*args):
1131     v = self.library.getExtMethods()
1132     text = "External Functions"
1133     title = text
1134     text +="\nHere is the list of external functions currently present in"
1135     text +=" the Library:"
1136    
1137     if len(v):
1138     for ext in v:
1139     text += "\n\n%s (%d inputs, %d outputs):" % \
1140     (ext.getName(), ext.getNumInputs(), ext.getNumOutputs())
1141     text += "\n%s" % ext.getHelp()
1142     else:
1143     text +="\n\nNone"
1144     _dialog = InfoDialog(self,self.window,text,title)
1145     _dialog.run()
1146    
1147 johnpye 856 def on_maintabs_switch_page(self,notebook,page,pagenum):
1148     print("Page switched to %d" % pagenum)
1149     if pagenum in self.tabs.keys():
1150     self.currentobservertab = pagenum
1151    
1152 johnpye 245 def create_observer(self,name=None):
1153 johnpye 436 _xml = gtk.glade.XML(self.glade_file,"observervbox");
1154 johnpye 245 _label = gtk.Label();
1155 johnpye 252 _tab = self.maintabs.append_page(_xml.get_widget("observervbox"),_label);
1156 johnpye 849 _obs = ObserverTab(xml=_xml, name=name, browser=self, tab=_tab)
1157     _label.set_text(_obs.name)
1158     self.observers.append(_obs)
1159 johnpye 856 self.tabs[_tab] = _obs
1160     self.currentobservertab = _tab
1161 johnpye 849 return _obs
1162 johnpye 249
1163     def sync_observers(self):
1164     for _o in self.observers:
1165     _o.sync()
1166 johnpye 533
1167     def delete_event(self, widget, event):
1168     self.do_quit()
1169     return False
1170 johnpye 249
1171 johnpye 533 def observe(self,instance):
1172 johnpye 246 if len(self.observers) ==0:
1173     self.create_observer()
1174 johnpye 856 _observer = self.tabs[self.currentobservertab]
1175 johnpye 533 _observer.add_instance(instance)
1176 johnpye 132
1177 johnpye 478 if __name__ == "__main__":
1178 johnpye 132 b = Browser();
1179     b.run()

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22