source: sasview/guiframe/gui_manager.py @ 6ed82c7

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 6ed82c7 was 1b70098, checked in by Jae Cho <jhjcho@…>, 15 years ago

generalize os.join for ball.ico

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