source: sasview/guiframe/gui_manager.py @ 7a28ba7

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 7a28ba7 was d468daa, checked in by Gervaise Alina <gervyh@…>, 16 years ago

print stament removed

  • Property mode set to 100644
File size: 28.4 KB
Line 
1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2008, University of Tennessee
9
10How-to build an application using guiframe:
11
12 1- Write a main application script along the lines of dummyapp.py
13 2- Write a config script along the lines of config.py, and name it local_config.py
14 3- Write your plug-ins and place them in a directory called "perspectives".
15     - Look at local_perspectives/plotting for an example of a plug-in.
16     - A plug-in should define a class called Plugin. See abstract class below.
17
18"""
19import wx
20import wx.aui
21import os, sys
22try:
23    # Try to find a local config
24    import imp
25    path = os.getcwd()
26    if(os.path.isfile("%s/%s.py" % (path, 'local_config'))) or \
27      (os.path.isfile("%s/%s.pyc" % (path, 'local_config'))):
28            fObj, path, descr = imp.find_module('local_config', [path])
29            config = imp.load_module('local_config', fObj, path, descr) 
30    else:
31        # Try simply importing local_config
32        import local_config as config
33except:
34    # Didn't find local config, load the default
35    import config
36   
37from sans.guicomm.events import EVT_STATUS,Model2DPanelEvent
38import history
39import warnings
40warnings.simplefilter("ignore")
41
42import logging
43
44class Plugin:
45    """
46        This class defines the interface for a Plugin class
47        that can be used by the gui_manager.
48       
49        Plug-ins should be placed in a sub-directory called "perspectives".
50        For example, a plug-in called Foo should be place in "perspectives/Foo".
51        That directory contains at least two files:
52            perspectives/Foo/__init.py contains two lines:
53           
54                PLUGIN_ID = "Foo plug-in 1.0"
55                from Foo import *
56               
57            perspectives/Foo/Foo.py contains the definition of the Plugin
58            class for the Foo plug-in. The interface of that Plugin class
59            should follow the interface of the class you are looking at.
60    """
61   
62    def __init__(self):
63        """
64            Abstract class for gui_manager Plugins.
65        """
66        ## Plug-in name. It will appear on the application menu.
67        self.sub_menu = "Plugin"       
68       
69        ## Reference to the parent window. Filled by get_panels() below.
70        self.parent = None
71       
72        ## List of panels that you would like to open in AUI windows
73        #  for your plug-in. This defines your plug-in "perspective"
74        self.perspective = []
75       
76        raise RuntimeError, "gui_manager.Plugin is an abstract class"
77       
78    def populate_menu(self, id, parent):
79        """
80            Create and return the list of application menu
81            items for the plug-in.
82           
83            @param id: deprecated. Un-used.
84            @param parent: parent window
85            @return: plug-in menu
86        """
87        import wx
88        # Create a menu
89        plug_menu = wx.Menu()
90
91        # Always get event IDs from wx
92        id = wx.NewId()
93       
94        # Fill your menu
95        plug_menu.Append(id, '&Do something')
96        wx.EVT_MENU(owner, id, self._on_do_something)
97   
98        # Returns the menu and a name for it.
99        return [(id, plug_menu, "name of the application menu")]
100   
101   
102    def get_panels(self, parent):
103        """
104            Create and return the list of wx.Panels for your plug-in.
105            Define the plug-in perspective.
106           
107            Panels should inherit from DefaultPanel defined below,
108            or should present the same interface. They must define
109            "window_caption" and "window_name".
110           
111            @param parent: parent window
112            @return: list of panels
113        """
114        ## Save a reference to the parent
115        self.parent = parent
116       
117        # Define a panel
118        mypanel = DefaultPanel(self.parent, -1)
119       
120        # If needed, add its name to the perspective list
121        self.perspective.append(self.control_panel.window_name)
122
123        # Return the list of panels
124        return [mypanel]
125   
126    def get_context_menu(self, graph=None):
127        """
128            This method is optional.
129       
130            When the context menu of a plot is rendered, the
131            get_context_menu method will be called to give you a
132            chance to add a menu item to the context menu.
133           
134            A ref to a Graph object is passed so that you can
135            investigate the plot content and decide whether you
136            need to add items to the context menu. 
137           
138            This method returns a list of menu items.
139            Each item is itself a list defining the text to
140            appear in the menu, a tool-tip help text, and a
141            call-back method.
142           
143            @param graph: the Graph object to which we attach the context menu
144            @return: a list of menu items with call-back function
145        """
146        return [["Menu text", 
147                 "Tool-tip help text", 
148                 self._on_context_do_something]]     
149   
150    def get_perspective(self):
151        """
152            Get the list of panel names for this perspective
153        """
154        return self.perspective
155   
156    def on_perspective(self, event):
157        """
158            Call back function for the perspective menu item.
159            We notify the parent window that the perspective
160            has changed.
161            @param event: menu event
162        """
163        self.parent.set_perspective(self.perspective)
164   
165    def post_init(self):
166        """
167            Post initialization call back to close the loose ends
168        """
169        pass
170
171
172class ViewerFrame(wx.Frame):
173    """
174        Main application frame
175    """
176    def __init__(self, parent, id, title, window_height=800, window_width=800):
177        """
178            Initialize the Frame object
179        """
180        from local_perspectives.plotting import plotting
181        wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, size=(800, 700))
182       
183        # Preferred window size
184        self._window_height = window_height
185        self._window_width  = window_width
186       
187        # Logging info
188        logging.basicConfig(level=logging.DEBUG,
189                    format='%(asctime)s %(levelname)s %(message)s',
190                    filename='sans_app.log',
191                    filemode='w')       
192       
193        path = os.path.dirname(__file__)
194        ico_file = os.path.join(path,'images/ball.ico')
195        if os.path.isfile(ico_file):
196            self.SetIcon(wx.Icon(ico_file, wx.BITMAP_TYPE_ICO))
197        else:
198            ico_file = os.path.join(os.getcwd(),'images/ball.ico')
199            if os.path.isfile(ico_file):
200                self.SetIcon(wx.Icon(ico_file, wx.BITMAP_TYPE_ICO))
201       
202        ## Application manager
203        self.app_manager = None
204       
205        ## Find plug-ins
206        # Modify this so that we can specify the directory to look into
207        self.plugins = self._find_plugins()
208        self.plugins.append(plotting.Plugin())
209
210        ## List of panels
211        self.panels = {}
212
213        ## Next available ID for wx gui events
214        #TODO:  No longer used - remove all calls to this
215        self.next_id = 20000
216
217        # Default locations
218        self._default_save_location = os.getcwd()       
219
220        ## Default welcome panel
221        self.defaultPanel    = DefaultPanel(self, -1, style=wx.RAISED_BORDER)
222       
223        # History panel
224        self.histPanel     = history.HistoryPanel(self, style=wx.RAISED_BORDER)
225        # self.build_gui()
226       
227        # Register the close event so it calls our own method
228        wx.EVT_CLOSE(self, self._onClose)
229        # Register to status events
230        self.Bind(EVT_STATUS, self._on_status_event)
231       
232    def build_gui(self):
233        # Set up the layout
234        self._setup_layout()
235       
236        # Set up the menu
237        self._setup_menus()
238       
239        self.Fit()
240       
241        #self._check_update(None)
242             
243    def _setup_layout(self):
244        """
245            Set up the layout
246        """
247        # Status bar
248        self.sb = self.CreateStatusBar()
249        self.SetStatusText("")
250
251        # Add panel
252        self._mgr = wx.aui.AuiManager(self)
253       
254        # Load panels
255        self._load_panels()
256       
257        self._mgr.Update()
258
259    def add_perspective(self, plugin):
260        """
261            Add a perspective if it doesn't already
262            exist.
263        """
264        is_loaded = False
265        for item in self.plugins:
266             if plugin.__class__==item.__class__:
267                 print "Plugin %s already loaded" % plugin.__class__.__name__
268                 is_loaded = True
269                 
270        if not is_loaded:
271            self.plugins.append(plugin)
272     
273    def _find_plugins(self, dir="perspectives"):
274        """
275            Find available perspective plug-ins
276            @param dir: directory in which to look for plug-ins
277            @return: list of plug-ins
278        """
279        import imp
280        print "Looking for plug-ins in %s" % dir
281        # List of plug-in objects
282       
283        #path_exe = os.getcwd()
284        #path_plugs = os.path.join(path_exe, dir)
285        f = open("load.log",'w') 
286        f.write(os.getcwd()+'\n\n')
287        #f.write(str(os.listdir(dir))+'\n')
288       
289       
290        plugins = []
291        # Go through files in panels directory
292        try:
293            list = os.listdir(dir)
294            for item in list:
295                toks = os.path.splitext(os.path.basename(item))
296                name = None
297                if not toks[0] == '__init__':
298                   
299                    if toks[1]=='.py' or toks[1]=='':
300                        name = toks[0]
301               
302                    path = [os.path.abspath(dir)]
303                    file = None
304                    try:
305                        if toks[1]=='':
306                            f.write("trying to import \n")
307                            mod_path = '.'.join([dir, name])
308                            f.write("mod_path= %s\n" % mod_path)
309                            module = __import__(mod_path, globals(), locals(), [name])
310                            f.write(str(module)+'\n')
311                        else:
312                            (file, path, info) = imp.find_module(name, path)
313                            module = imp.load_module( name, file, item, info )
314                        if hasattr(module, "PLUGIN_ID"):
315                            try:
316                                plugins.append(module.Plugin())
317                                print "Found plug-in: %s" % module.PLUGIN_ID
318                            except:
319                                config.printEVT("Error accessing PluginPanel in %s\n  %s" % (name, sys.exc_value))
320                       
321                    except:
322                        print sys.exc_value
323                        f.write(str(sys.exc_value)+'\n')
324                    finally:
325                        if not file==None:
326                            file.close()
327        except:
328            # Should raise and catch at a higher level and display error on status bar
329            pass   
330        f.write(str(plugins)+'\n')
331        f.close()
332        return plugins
333   
334       
335     
336    def _load_panels(self):
337        """
338            Load all panels in the panels directory
339        """
340       
341        # Look for plug-in panels
342        panels = []       
343        for item in self.plugins:
344            if hasattr(item, "get_panels"):
345                ps = item.get_panels(self)
346                panels.extend(ps)
347
348        # Show a default panel with some help information
349        # It also sets the size of the application windows
350        self.panels["default"] = self.defaultPanel
351         # History panel
352        self.panels["historyPanel"] = self.histPanel 
353       
354        self._mgr.AddPane(self.defaultPanel, wx.aui.AuiPaneInfo().
355                              Name("default").
356                              CenterPane().
357                              # This is where we set the size of the application window
358                              BestSize(wx.Size(self._window_width, self._window_height)).
359                              MinSize(wx.Size(self._window_width, self._window_height)).
360                              Show())
361        self._mgr.AddPane(self.histPanel, wx.aui.AuiPaneInfo().
362                          Name("historyPanel").Caption("History").
363                          #Float().
364                          Bottom().
365                          Dock().
366                          TopDockable().
367                          BottomDockable().
368                          LeftDockable().
369                          RightDockable().
370                          MinimizeButton().
371                          Hide().
372                          #Show().
373                          BestSize(wx.Size(500,600)).
374                          MinSize(wx.Size(200,150)))
375
376        # Add the panels to the AUI manager
377        for panel_class in panels:
378            p = panel_class
379            id = wx.NewId()
380           
381            # Check whether we need to put this panel
382            # in the center pane
383            if hasattr(p, "CENTER_PANE"):
384                if p.CENTER_PANE:
385                    self.panels[str(id)] = p
386                    self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
387                                          Name(p.window_name).Caption(p.window_caption).
388                                          CenterPane().
389                                          BestSize(wx.Size(500,500)).
390                                          MinSize(wx.Size(200,200)).
391                                          Hide())
392               
393            else:
394                self.panels[str(id)] = p
395                self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
396                                  Name(p.window_name).Caption(p.window_caption).
397                                  #Floatable().
398                                  #Float().
399                                  Right().
400                                  Dock().
401                                  TopDockable().
402                                  BottomDockable().
403                                  LeftDockable().
404                                  RightDockable().
405                                  MinimizeButton().
406                                  Hide().
407                                  #Show().
408                                  BestSize(wx.Size(400,400)).
409                                  MinSize(wx.Size(300,300)))
410
411               
412       
413    def get_context_menu(self, graph=None):
414        """
415            Get the context menu items made available
416            by the different plug-ins.
417            This function is used by the plotting module
418        """
419        menu_list = []
420        for item in self.plugins:
421            if hasattr(item, "get_context_menu"):
422                menu_list.extend(item.get_context_menu(graph))
423           
424        return menu_list
425       
426    def popup_panel(self, p):
427        """
428            Add a panel object to the AUI manager
429            @param p: panel object to add to the AUI manager
430            @return: ID of the event associated with the new panel [int]
431        """
432       
433        ID = wx.NewId()
434        self.panels[str(ID)] = p
435       
436        count = 0
437        for item in self.panels:
438            if self.panels[item].window_name.startswith(p.window_name): 
439                count += 1
440                """
441        if p.window_name =="Analytical model 2D ":
442            print "guiframe 2D id",ID, p.window_name
443            event = Model2DPanelEvent(id= ID, pname=p.window_name)
444            for plug in self.plugins:
445                if hasattr(plug, fitpanel)
446                wx.PostEvent(plug, event)
447         """   
448        windowname = p.window_name
449        caption = p.window_caption
450        if count>0:
451            windowname += str(count+1)
452            caption += (' '+str(count))
453           
454        p.window_name = windowname
455        p.window_caption = caption
456           
457        self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
458                          Name(windowname).Caption(caption).
459                          Floatable().
460                          #Float().
461                          Right().
462                          Dock().
463                          TopDockable().
464                          BottomDockable().
465                          LeftDockable().
466                          RightDockable().
467                          MinimizeButton().
468                          #Hide().
469                          #Show().
470                          BestSize(wx.Size(400,400)).
471                          MinSize(wx.Size(350,350)))
472        pane = self._mgr.GetPane(windowname)
473        self._mgr.MaximizePane(pane)
474        self._mgr.RestoreMaximizedPane()
475       
476       
477        # Register for showing/hiding the panel
478       
479        wx.EVT_MENU(self, ID, self._on_view)
480       
481        self._mgr.Update()
482        return ID
483       
484    def _setup_menus(self):
485        """
486            Set up the application menus
487        """
488        # Menu
489        menubar = wx.MenuBar()
490       
491        # File menu
492        filemenu = wx.Menu()
493       
494        id = wx.NewId()
495        filemenu.Append(id, '&Open', 'Open a file')
496        wx.EVT_MENU(self, id, self._on_open)
497       
498        id = wx.NewId()
499        filemenu.Append(id, '&History', 'Register previous States')
500        wx.EVT_MENU(self, id, self._onHistoryPanel)
501       
502        id = wx.NewId()
503        filemenu.Append(id,'&Quit', 'Exit') 
504        wx.EVT_MENU(self, id, self.Close)
505       
506        # Add sub menus
507        menubar.Append(filemenu,  '&File')
508       
509        # Plot menu
510        # Attach a menu item for each panel in our
511        # panel list that also appears in a plug-in.
512        # TODO: clean this up. We should just identify
513        # plug-in panels and add them all.
514       
515        # Only add the panel menu if there is more than one panel
516        n_panels = 0
517        for plug in self.plugins:
518            pers = plug.get_perspective()
519            if len(pers)>0:
520                n_panels += 1
521       
522        if n_panels>1:
523            viewmenu = wx.Menu()
524            for plug in self.plugins:
525                plugmenu = wx.Menu()
526                pers = plug.get_perspective()
527                if len(pers)>0:
528                    for item in self.panels:
529                        if item == 'default':
530                            continue
531                        panel = self.panels[item]
532                        if panel.window_name in pers:
533                            plugmenu.Append(int(item), panel.window_caption, "Show %s window" % panel.window_caption)
534                           
535                           
536                           
537                            wx.EVT_MENU(self, int(item), self._on_view)
538                   
539                    viewmenu.AppendMenu(wx.NewId(), plug.sub_menu, plugmenu, plug.sub_menu)
540               
541            menubar.Append(viewmenu, '&Panel')
542
543        # Perspective
544        # Attach a menu item for each defined perspective.
545        # Only add the perspective menu if there are more than one perspectves
546        n_perspectives = 0
547        for plug in self.plugins:
548            if len(plug.get_perspective()) > 0:
549                n_perspectives += 1
550       
551        if n_perspectives>1:
552            p_menu = wx.Menu()
553            for plug in self.plugins:
554                if len(plug.get_perspective()) > 0:
555                    id = wx.NewId()
556                    p_menu.Append(id, plug.sub_menu, "Switch to %s perspective" % plug.sub_menu)
557                    wx.EVT_MENU(self, id, plug.on_perspective)
558            menubar.Append(p_menu,   '&Perspective')
559 
560        # Help menu
561        helpmenu = wx.Menu()
562
563        # Look for help item in plug-ins
564        for item in self.plugins:
565            if hasattr(item, "help"):
566                id = wx.NewId()
567                helpmenu.Append(id,'&%s help' % item.sub_menu, '')
568                wx.EVT_MENU(self, id, item.help)
569       
570        if config._do_aboutbox:
571            id = wx.NewId()
572            helpmenu.Append(id,'&About', 'Software information')
573            wx.EVT_MENU(self, id, self._onAbout)
574        id = wx.NewId()
575        helpmenu.Append(id,'&Check for update', 'Check for the latest version of %s' % config.__appname__)
576        wx.EVT_MENU(self, id, self._check_update)
577       
578       
579       
580       
581        # Look for plug-in menus
582        # Add available plug-in sub-menus.
583        for item in self.plugins:
584            if hasattr(item, "populate_menu"):
585                for (self.next_id, menu, name) in item.populate_menu(self.next_id, self):
586                    menubar.Append(menu, name)
587                   
588
589        menubar.Append(helpmenu, '&Help')
590         
591        self.SetMenuBar(menubar)
592       
593       
594       
595    def _on_status_event(self, evt):
596        """
597            Display status message
598        """
599        self.SetStatusText(str(evt.status))
600
601       
602    def _on_view(self, evt):
603        """
604            A panel was selected to be shown. If it's not already
605            shown, display it.
606            @param evt: menu event
607        """
608        self.show_panel(evt.GetId())
609
610    def show_panel(self, uid):
611        """
612            Shows the panel with the given id
613            @param uid: unique ID number of the panel to show
614        """
615        ID = str(uid)
616        config.printEVT("show_panel: %s" % ID)
617        if ID in self.panels.keys():
618            if not self._mgr.GetPane(self.panels[ID].window_name).IsShown():
619                self._mgr.GetPane(self.panels[ID].window_name).Show()
620                # Hide default panel
621                self._mgr.GetPane(self.panels["default"].window_name).Hide()
622               
623               
624            self._mgr.Update()
625    def _onHistoryPanel(self, event):
626        print "on history"
627        if not self._mgr.GetPane("historyPanel").IsShown():
628            self._mgr.GetPane("historyPanel").Show()
629        self._mgr.Update()
630        return
631       
632    def _on_open(self, event):
633   
634        from data_loader import plot_data
635        path = self.choose_file()
636           
637        if path and os.path.isfile(path):
638            plot_data(self, path)
639           
640       
641       
642    def _onClose(self, event):
643        import sys
644        wx.Exit()
645        sys.exit()
646                   
647    def Close(self, event=None):
648        """
649            Quit the application
650        """
651        import sys
652        wx.Frame.Close(self)
653        wx.Exit()
654        sys.exit()
655
656 
657    def _check_update(self, event=None): 
658        """
659            Check with the deployment server whether a new version
660            of the application is available
661        """
662        import urllib
663        try: 
664            h = urllib.urlopen(config.__update_URL__)
665            lines = h.readlines()
666            line = ''
667            if len(lines)>0:
668                line = lines[0]
669               
670                toks = line.lstrip().rstrip().split('.')
671                toks_current = config.__version__.split('.')
672                update_available = False
673                for i in range(len(toks)):
674                    if len(toks[i].strip())>0:
675                        if int(toks[i].strip())>int(toks_current[i]):
676                            update_available = True
677                if update_available:
678                    #print "Version %s is available" % line.rstrip().lstrip()
679                    self.SetStatusText("Version %s is available! See the Help menu to download it." % line.rstrip().lstrip())
680                    if event != None:
681                        import webbrowser
682                        webbrowser.open(config.__download_page__)
683                else:
684                    if event != None:
685                        self.SetStatusText("You have the latest version of %s" % config.__appname__)
686        except:
687            if event != None:
688                self.SetStatusText("You have the latest version of %s" % config.__appname__)
689           
690           
691    def _onAbout(self, evt):
692        """
693            Pop up the about dialog
694            @param evt: menu event
695        """
696        if config._do_aboutbox:
697            import aboutbox 
698            dialog = aboutbox.DialogAbout(None, -1, "")
699            dialog.ShowModal()
700           
701    def set_manager(self, manager):
702        """
703            Sets the application manager for this frame
704            @param manager: frame manager
705        """
706        self.app_manager = manager
707       
708    def post_init(self):
709        """
710            This initialization method is called after the GUI
711            has been created and all plug-ins loaded. It calls
712            the post_init() method of each plug-in (if it exists)
713            so that final initialization can be done.
714        """
715        for item in self.plugins:
716            if hasattr(item, "post_init"):
717                item.post_init()
718       
719    def set_perspective(self, panels):
720        """
721            Sets the perspective of the GUI.
722            Opens all the panels in the list, and closes
723            all the others.
724           
725            @param panels: list of panels
726        """
727        for item in self.panels:
728            # Check whether this is a sticky panel
729            if hasattr(self.panels[item], "ALWAYS_ON"):
730                if self.panels[item].ALWAYS_ON:
731                    continue 
732           
733            if self.panels[item].window_name in panels:
734                if not self._mgr.GetPane(self.panels[item].window_name).IsShown():
735                    self._mgr.GetPane(self.panels[item].window_name).Show()
736            else:
737                if self._mgr.GetPane(self.panels[item].window_name).IsShown():
738                    self._mgr.GetPane(self.panels[item].window_name).Hide()
739                 
740        self._mgr.Update()
741       
742    def choose_file(self):
743        """
744            Functionality that belongs elsewhere
745            Should add a hook to specify the preferred file type/extension.
746        """
747        #TODO: clean this up
748        from data_loader import choose_data_file
749        path = choose_data_file(self, self._default_save_location)
750        if not path==None:
751            try:
752                self._default_save_location = os.path.dirname(path)
753            except:
754                pass
755        return path
756   
757    def load_ascii_1D(self, path):
758        from data_loader import load_ascii_1D
759        return load_ascii_1D(path)
760                 
761class DefaultPanel(wx.Panel):
762    """
763        Defines the API for a panels to work with
764        the GUI manager
765    """
766    ## Internal nickname for the window, used by the AUI manager
767    window_name = "default"
768    ## Name to appear on the window title bar
769    window_caption = "Welcome panel"
770    ## Flag to tell the AUI manager to put this panel in the center pane
771    CENTER_PANE = True
772
773 
774# Toy application to test this Frame
775class ViewApp(wx.App):
776    def OnInit(self):
777        #from gui_manager import ViewerFrame
778        self.frame = ViewerFrame(None, -1, config.__appname__)   
779        self.frame.Show(True)
780
781        if hasattr(self.frame, 'special'):
782            print "Special?", self.frame.special.__class__.__name__
783            self.frame.special.SetCurrent()
784        self.SetTopWindow(self.frame)
785        return True
786   
787    def set_manager(self, manager):
788        """
789            Sets a reference to the application manager
790            of the GUI manager (Frame)
791        """
792        self.frame.set_manager(manager)
793       
794    def build_gui(self):
795        """
796            Build the GUI
797        """
798        self.frame.build_gui()
799        self.frame.post_init()
800       
801    def add_perspective(self, perspective):
802        """
803            Manually add a perspective to the application GUI
804        """
805        self.frame.add_perspective(perspective)
806       
807
808if __name__ == "__main__": 
809    app = ViewApp(0)
810    app.MainLoop()             
Note: See TracBrowser for help on using the repository browser.