1 """
2 TopoConsole class file.
3
4
5 $Id: topoconsole.py 11310 2010-07-27 16:56:14Z ceball $
6 """
7 __version__='$Revision: 11310 $'
8
9
10
11
12
13
14 import os
15 import copy
16 import sys
17 import __main__
18 import webbrowser
19 import string
20
21 from Tkinter import Frame, Button, \
22 LEFT, YES, Label, DISABLED, \
23 NORMAL, DoubleVar
24 from tkFileDialog import asksaveasfilename,askopenfilename
25
26 import param
27 from param import tk,normalize_path,resolve_path
28
29
30 import topo
31 from topo.plotting.plotgroup import plotgroups, FeatureCurvePlotGroup
32 from topo.misc.keyedlist import KeyedList
33 from topo.misc.commandline import sim_name_from_filename
34 import topo.misc.genexamples
35 import topo.command.basic
36
37 import topo.tkgui
38
39 from templateplotgrouppanel import TemplatePlotGroupPanel
40 from featurecurvepanel import FeatureCurvePanel
41 from projectionpanel import CFProjectionPanel,ProjectionActivityPanel,ConnectionFieldsPanel,RFProjectionPanel
42 from testpattern import TestPattern
43 from editor import ModelEditor
44
45
46 tk.AppWindow.window_icon_path = resolve_path('tkgui/icons/topo.xbm')
47
48 SCRIPT_FILETYPES = [('Topographica scripts','*.ty'),
49 ('Python scripts','*.py'),
50 ('All files','*')]
51 SAVED_FILE_EXTENSION = '.typ'
52 SAVED_FILETYPES = [('Topographica saved networks',
53 '*'+SAVED_FILE_EXTENSION),
54 ('All files','*')]
55
56 turl = "http://topographica.org/"
57 userman = "User_Manual/index.html"
58 tuts = "Tutorials/index.html"
59 refman = "Reference_Manual/index.html"
60 plotman = "User_Manual/plotting.html"
61
62
63 pkgdoc = "/usr/share/doc/topographica/doc/"
64
65
66 user_manual_locations = ('doc/'+userman,
67 pkgdoc+userman,
68 turl+userman)
69 tutorials_locations = ('doc/'+tuts,
70 pkgdoc+tuts,
71 turl+tuts)
72 reference_manual_locations = ('doc/'+refman,
73 pkgdoc+refman,
74 turl+refman)
75 python_doc_locations = ('http://www.python.org/doc/',)
76 topo_www_locations = (turl,)
77 plotting_help_locations = ('doc/'+plotman,
78 pkgdoc+plotman,
79 turl+plotman)
80
81
82
83
84
85
86 plotpanel_classes = {}
87
88
89
90
91
115
116
117
118
119
121 """
122 Stores information about a Plots menu command
123 (including the command itself, and the plotgroup template).
124 """
126 """
127 Store the template, and set the class that will be created by this menu entry
128
129 If users want to extend the Plot Panel classes, then they
130 should add entries to the plotpanel_classes dictionary.
131 If no entry is defined there, then the default class is used.
132
133 The class_ is overridden for any special cases listed in this method.
134 """
135 super(PlotsMenuEntry,self).__init__(**params)
136
137 self.plotgroup = plotgroup
138
139
140
141
142 if isinstance(self.plotgroup,FeatureCurvePlotGroup):
143 class_ = plotpanel_classes.get(self.plotgroup.name,FeatureCurvePanel)
144
145 self.class_ = plotpanel_classes.get(self.plotgroup.name,class_)
146
147
149 """
150 Instantiate the class_ (used as menu commands' 'command' attribute).
151
152 Keyword args are passed to the class_.
153 """
154 new_plotgroup = copy.deepcopy(self.plotgroup)
155
156
157
158 new_plotgroup.plot_templates = topo.plotting.plotgroup.plotgroups[self.plotgroup.name].plot_templates
159
160 return open_plotgroup_panel(self.class_,new_plotgroup,**kw)
161
162
163
164 try:
165 from param.external import Notebook
167 """Manages windows that can be tabs in a notebook, or toplevels."""
168 - def __init__(self, master=None, cnf={}, **kw):
171
173 self.tab(self._tab_ids[win],text=title)
174
180
181 - def add(self, child, cnf={}, **kw):
182 self._tab_ids[child]=len(self.tabs())
183 Notebook.add(self,child,cnf=cnf,**kw)
184
185
186
187
188
193
195 if win not in self._tab_ids:
196 self.tk.call('wm','forget',win._w)
197 win.title = lambda x: self._set_tab_title(win,x)
198 self.add(win)
199
201 if win in self._tab_ids:
202 self.forget(self._tab_ids[win])
203
204
205 del self._tab_ids[win]
206 for w in self._tab_ids:
207 self._tab_ids[w]-=1
208 self._tab_ids[w]=max(self._tab_ids[w],0)
209
210 self.tk.call('wm','manage',win._w)
211 win.renew()
212 win.title = lambda x: self._set_toplevel_title(win,x)
213 return win
214
215 except ImportError:
224 - def add(self,*args):
235
236 DockManager = FakeDockManager
237
238
239
240
241
242
244 exc, val, tb = sys.exc_type, sys.exc_value, sys.exc_traceback
245 msg = "(%s) %s"%(exc.__name__,val)
246
247
248
249
250 if not widget.master:
251 widget = tk._last_one_set
252
253 stat = None
254
255 while (widget is not None and widget.master):
256
257
258 if hasattr(widget,'status'):
259 stat = widget.status
260 break
261 elif hasattr(widget,'messageBar'):
262 stat = widget.messageBar
263 break
264 widget = widget.master
265
266 if stat is not None:
267 stat.error('%s'%msg)
268 else:
269 topo.guimain.messageBar.error('%s'%msg)
270
271 param.Parameterized().warning(msg)
272
273
274
275 import Tkinter
276
278 """
279 Main window for the Tk-based GUI.
280 """
281
284
285 menubar = property(_getmenubar)
286
288 """Allow dictionary-style access to the menu bar."""
289 return self.menubar[menu_name]
290
292 tk.AppWindow.__init__(self,root,status=True)
293 tk.TkParameterized.__init__(self,root,**params)
294
295
296
297
298 Tkinter.Misc._report_exception=_tkinter_report_exception
299
300 self.auto_refresh_panels = []
301 self._init_widgets()
302 self.title(topo.sim.name)
303
304
305
306
307
308 self.protocol("WM_DELETE_WINDOW",self.quit_topographica)
309
310
311
312
313
314
315
316 if topo.tkgui.system_platform is 'linux':
317 activate_cascade = """\
318 if {[%W cget -type] != {menubar} && [%W type active] == {cascade}} {
319 %W postcascade active
320 }
321 """
322 self.bind_class("Menu", "<<MenuSelect>>", activate_cascade)
323
324
325
326 from param.parameterized import Parameterized
327 self.__orig_P_warning = Parameterized.warning
328 self.__orig_P_message = Parameterized.message
329 type.__setattr__(Parameterized,'warning',self.gui_warning)
330 type.__setattr__(Parameterized,'message',self.gui_message)
331
337
343
344
346 newtitle = "Topographica"
347 if t: newtitle+=": %s" % t
348 tk.AppWindow.title(self,newtitle)
349
350
415
416
432
433
434
437
438
439
440
441
442
444 """
445 Add the plot menu to the menubar, with Basic plots on the menu itself and
446 others in cascades by category (the plots come from plotgroup_templates).
447 """
448 plots_menu = ControllableMenu(self.menubar,tearoff=0)
449 self.menubar.add_cascade(label='Plots',menu=plots_menu)
450
451
452
454 plots_menu = self['Plots']
455 plots_menu.delete(0,'end')
456
457
458 entries=KeyedList()
459 categories = []
460 for label,plotgroup in plotgroups.items():
461 entries[label] = PlotsMenuEntry(plotgroup)
462 categories.append(plotgroup.category)
463 categories = sorted(set(categories))
464
465
466
467 assert 'Basic' in categories, "'Basic' is the category for the standard Plots menu entries."
468 for label,entry in entries:
469 if entry.plotgroup.category=='Basic':
470 plots_menu.add_command(label=label,command=entry.__call__)
471
472 categories.remove('Basic')
473
474 plots_menu.add_separator()
475
476
477
478 for category in categories:
479 category_menu = ControllableMenu(plots_menu,tearoff=0)
480 plots_menu.add_cascade(label=category,menu=category_menu)
481
482
483 for label,entry in sorted(entries):
484 if entry.plotgroup.category==category:
485 category_menu.add_command(label=label,command=entry.__call__)
486
487
488 plots_menu.add_separator()
489
490 plots_menu.add_command(label="Help",command=(lambda x=plotting_help_locations: self.open_location(x)))
491
492
494 """Add the help menu options."""
495
496 help_menu = ControllableMenu(self.menubar,tearoff=0,name='help')
497 self.menubar.add_cascade(label='Help',menu=help_menu)
498
499 help_menu.add_command(label='About',command=self.new_about_window)
500 help_menu.add_command(label="User Manual",
501 command=(lambda x=user_manual_locations: self.open_location(x)))
502
503 help_menu.add_command(label="Tutorials",
504 command=(lambda x=tutorials_locations: self.open_location(x)))
505
506 help_menu.add_command(label="Examples",
507 command=self.run_example_script)
508
509 help_menu.add_command(label="Reference Manual",
510 command=(lambda x=reference_manual_locations: self.open_location(x)))
511
512 help_menu.add_command(label="Topographica.org",
513 command=(lambda x=topo_www_locations: self.open_location(x)))
514
515 help_menu.add_command(label="Python documentation",
516 command=(lambda x=python_doc_locations: self.open_location(x)))
517
518
519
520
522 """Quit topographica."""
523 if not check or (check and tk.askyesno("Quit Topographica","Really quit?")):
524 self.destroy()
525
526
527
528
529 try:
530 import matplotlib._pylab_helpers
531 for figman in matplotlib._pylab_helpers.Gcf.get_all_fig_managers():
532 figman.destroy()
533 except:
534 pass
535
536 print "Quit selected; exiting"
537
538
539
540
541
542
543
544
545
546
547
548 if topo.tkgui.system_platform=="linux" and os.getenv('EMACS')!='t':
549 try: os.system("stty sane")
550 except: pass
551
552
553 sys.exit(exit_status)
554
555
557 """
558 Dialog to run a user-selected script
559
560 The script is exec'd in __main__.__dict__ (i.e. as if it were specified on the commandline.)
561 """
562 script = askopenfilename(initialdir=normalize_path(),filetypes=SCRIPT_FILETYPES)
563 if script in ('',(),None):
564 self.messageBar.response('Run canceled')
565 else:
566 execfile(script,__main__.__dict__)
567 self.messageBar.response('Ran ' + script)
568 sim_name_from_filename(script)
569 self.title(topo.sim.name)
570
571
585
586
587
596
597
599 """
600 Dialog to load a user-selected snapshot (see topo.command.basic.load_snapshot() ).
601 """
602 snapshot_name = askopenfilename(initialdir=normalize_path(),filetypes=SAVED_FILETYPES)
603
604 if snapshot_name in ('',(),None):
605 self.messageBar.response('No snapshot loaded.')
606 else:
607 self.messageBar.dynamicinfo('Loading snapshot (may take some time)...')
608 self.update_idletasks()
609 topo.command.basic.load_snapshot(snapshot_name)
610 self.messageBar.response('Loaded snapshot ' + snapshot_name)
611 self.title(topo.sim.name)
612
613 self.auto_refresh()
614
615
617 """
618 Dialog to save a snapshot (see topo.command.basic.save_snapshot() ).
619
620 Adds the file extension .typ if not already present.
621 """
622 snapshot_name = asksaveasfilename(filetypes=SAVED_FILETYPES,
623 initialdir=normalize_path(),
624 initialfile=topo.sim.basename()+".typ")
625
626 if snapshot_name in ('',(),None):
627 self.messageBar.response('No snapshot saved.')
628 else:
629 if not snapshot_name.endswith('.typ'):
630 snapshot_name = snapshot_name + SAVED_FILE_EXTENSION
631
632 self.messageBar.dynamicinfo('Saving snapshot (may take some time)...')
633 self.update_idletasks()
634 topo.command.basic.save_snapshot(snapshot_name)
635 self.messageBar.response('Snapshot saved to ' + snapshot_name)
636
637
639 """
640 Refresh all windows in auto_refresh_panels.
641
642 Panels can add and remove themselves to the list; those in the list
643 will have their refresh() method called whenever this console's
644 autorefresh() is called.
645 """
646 for win in self.auto_refresh_panels:
647 win.refresh()
648
649 self.set_step_button_state()
650 self.update_idletasks()
651
652
653
654
655
656
657
658
660 """
661 Update any windows with a plotgroup_key of 'Activity'.
662
663 Used primarily for debugging long scripts that present a lot of activity patterns.
664 """
665 for win in self.auto_refresh_panels:
666 if win.plotgroup.name=='Activity' or win.plotgroup.name=='ProjectionActivity' :
667 win.refresh()
668 self.update_idletasks()
669
670
671
672
674 """Start the Model editor."""
675 return ModelEditor(self)
676
677
678
679
681 win = tk.AppWindow(self)
682 win.withdraw()
683 win.title("About Topographica")
684 text = Label(win,text=topo.about(display=False),justify=LEFT)
685 text.pack(side=LEFT)
686 win.deiconify()
687
688
690 """
691 Try to open one of the specified locations in a new window of the default
692 browser. See webbrowser module for more information.
693
694 locations should be a tuple.
695 """
696
697
698 assert isinstance(locations,tuple),"locations must be a tuple."
699
700 for location in locations:
701 try:
702 existing_location = resolve_path(location)
703 webbrowser.open(existing_location,new=2,autoraise=True)
704 self.messageBar.response('Opened local file '+existing_location+' in browser.')
705 return
706 except:
707 pass
708
709 for location in locations:
710 if location.startswith('http'):
711 try:
712 webbrowser.open(location,new=2,autoraise=True)
713 self.messageBar.response('Opened remote location '+location+' in browser.')
714 return
715 except:
716 pass
717
718 self.messageBar.response("Could not open any of %s in a browser."%locations)
719
720
721
722
725
726
728 """
729 Run the simulation for the duration specified in the
730 'run for' taggedslider.
731 """
732 fduration = self.run_for_var.get()
733 self.open_progress_window(timer=topo.sim.timer)
734 topo.sim.run_and_time(fduration)
735 self.auto_refresh()
736
737
738
739
740
752
758
759
761
762
763 try:
764 while True:
765 f = sys._getframe(i)
766 if hasattr(f,'f_locals'):
767 if 'self' in f.f_locals:
768 o = f.f_locals['self']
769
770 if o.__class__.__name__!='ScrolledFrame':
771 if hasattr(o,'messageBar'):
772 return o.messageBar
773 elif hasattr(o,'status'):
774 return o.status
775 i+=1
776 except:
777 pass
778
779
780 return self.messageBar
781
782
789
790
791
792
793
794
795
796
797
799 """
800 A Menu, but where entries are accessible by name (using
801 dictionary-style access).
802
803 ** Not truly compatible with Tkinter; work in progress **
804 """
806 return self.named_commands[name]
807
808
809
810 if __name__ != '__main__':
811 plotpanel_classes['Connection Fields'] = ConnectionFieldsPanel
812 plotpanel_classes['RF Projection'] = RFProjectionPanel
813 plotpanel_classes['Projection'] = CFProjectionPanel
814 plotpanel_classes['Projection Activity'] = ProjectionActivityPanel
815