source: sasview/guiframe/gui_manager.py @ 7705306

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 7705306 was 7681bac, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Added docs.

  • Property mode set to 100644
File size: 26.2 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
38
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):
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=(1000, 1000))
182       
183        # Logging info
184        logging.basicConfig(level=logging.DEBUG,
185                    format='%(asctime)s %(levelname)s %(message)s',
186                    filename='sans_app.log',
187                    filemode='w')       
188       
189        path = os.path.dirname(__file__)
190        ico_file = os.path.join(path,'images/ball.ico')
191        if os.path.isfile(ico_file):
192            self.SetIcon(wx.Icon(ico_file, wx.BITMAP_TYPE_ICO))
193        else:
194            ico_file = os.path.join(os.getcwd(),'images/ball.ico')
195            if os.path.isfile(ico_file):
196                self.SetIcon(wx.Icon(ico_file, wx.BITMAP_TYPE_ICO))
197       
198        ## Application manager
199        self.app_manager = None
200       
201        ## Find plug-ins
202        # Modify this so that we can specify the directory to look into
203        self.plugins = self._find_plugins()
204        self.plugins.append(plotting.Plugin())
205
206        ## List of panels
207        self.panels = {}
208
209        ## Next available ID for wx gui events
210        #TODO:  No longer used - remove all calls to this
211        self.next_id = 20000
212
213        # Default locations
214        self._default_save_location = os.getcwd()       
215
216        ## Default welcome panel
217        self.defaultPanel    = DefaultPanel(self, -1, style=wx.RAISED_BORDER)
218
219        # self.build_gui()
220       
221        # Register the close event so it calls our own method
222        wx.EVT_CLOSE(self, self._onClose)
223        # Register to status events
224        self.Bind(EVT_STATUS, self._on_status_event)
225             
226    def build_gui(self):
227        # Set up the layout
228        self._setup_layout()
229       
230        # Set up the menu
231        self._setup_menus()
232       
233        self.Fit()
234             
235    def _setup_layout(self):
236        """
237            Set up the layout
238        """
239        # Status bar
240        self.sb = self.CreateStatusBar()
241        self.SetStatusText("")
242       
243        # Add panel
244        self._mgr = wx.aui.AuiManager(self)
245       
246        # Load panels
247        self._load_panels()
248       
249        self._mgr.Update()
250
251    def add_perspective(self, plugin):
252        """
253            Add a perspective if it doesn't already
254            exist.
255        """
256        is_loaded = False
257        for item in self.plugins:
258             if plugin.__class__==item.__class__:
259                 print "Plugin %s already loaded" % plugin.__class__.__name__
260                 is_loaded = True
261                 
262        if not is_loaded:
263            self.plugins.append(plugin)
264     
265    def _find_plugins(self, dir="perspectives"):
266        """
267            Find available perspective plug-ins
268            @param dir: directory in which to look for plug-ins
269            @return: list of plug-ins
270        """
271        import imp
272        print "Looking for plug-ins in %s" % dir
273        # List of plug-in objects
274       
275        #path_exe = os.getcwd()
276        #path_plugs = os.path.join(path_exe, dir)
277        f = open("load.log",'w') 
278        f.write(os.getcwd()+'\n\n')
279        #f.write(str(os.listdir(dir))+'\n')
280       
281       
282        plugins = []
283        # Go through files in panels directory
284        try:
285            list = os.listdir(dir)
286            for item in list:
287                toks = os.path.splitext(os.path.basename(item))
288                name = None
289                if not toks[0] == '__init__':
290                   
291                    if toks[1]=='.py' or toks[1]=='':
292                        name = toks[0]
293               
294                    path = [os.path.abspath(dir)]
295                    file = None
296                    try:
297                        if toks[1]=='':
298                            f.write("trying to import \n")
299                            mod_path = '.'.join([dir, name])
300                            f.write("mod_path= %s\n" % mod_path)
301                            module = __import__(mod_path, globals(), locals(), [name])
302                            f.write(str(module)+'\n')
303                        else:
304                            (file, path, info) = imp.find_module(name, path)
305                            module = imp.load_module( name, file, item, info )
306                        if hasattr(module, "PLUGIN_ID"):
307                            try:
308                                plugins.append(module.Plugin())
309                                print "Found plug-in: %s" % module.PLUGIN_ID
310                            except:
311                                config.printEVT("Error accessing PluginPanel in %s\n  %s" % (name, sys.exc_value))
312                       
313                    except:
314                        print sys.exc_value
315                        f.write(str(sys.exc_value)+'\n')
316                    finally:
317                        if not file==None:
318                            file.close()
319        except:
320            # Should raise and catch at a higher level and display error on status bar
321            pass   
322        f.write(str(plugins)+'\n')
323        f.close()
324        return plugins
325   
326       
327     
328    def _load_panels(self):
329        """
330            Load all panels in the panels directory
331        """
332       
333        # Look for plug-in panels
334        panels = []       
335        for item in self.plugins:
336            if hasattr(item, "get_panels"):
337                ps = item.get_panels(self)
338                panels.extend(ps)
339
340        # Show a default panel with some help information
341        # It also sets the size of the application windows
342        self.panels["default"] = self.defaultPanel
343        self._mgr.AddPane(self.defaultPanel, wx.aui.AuiPaneInfo().
344                              Name("default").
345                              CenterPane().
346                              BestSize(wx.Size(900,800)).
347                              MinSize(wx.Size(800,700)).
348                              Show())
349
350        # Add the panels to the AUI manager
351        for panel_class in panels:
352            p = panel_class
353            id = wx.NewId()
354           
355            # Check whether we need to put this panel
356            # in the center pane
357            if hasattr(p, "CENTER_PANE"):
358                if p.CENTER_PANE:
359                    self.panels[str(id)] = p
360                    self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
361                                          Name(p.window_name).Caption(p.window_caption).
362                                          CenterPane().
363                                          BestSize(wx.Size(600,600)).
364                                          MinSize(wx.Size(200,200)).
365                                          Hide())
366               
367            else:
368                self.panels[str(id)] = p
369                self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
370                                  Name(p.window_name).Caption(p.window_caption).
371                                  #Floatable().
372                                  #Float().
373                                  Right().
374                                  Dock().
375                                  TopDockable().
376                                  BottomDockable().
377                                  LeftDockable().
378                                  RightDockable().
379                                  MinimizeButton().
380                                  Hide().
381                                  #Show().
382                                  BestSize(wx.Size(400,400)).
383                                  MinSize(wx.Size(300,200)))
384               
385       
386    def get_context_menu(self, graph=None):
387        """
388            Get the context menu items made available
389            by the different plug-ins.
390            This function is used by the plotting module
391        """
392        menu_list = []
393        for item in self.plugins:
394            if hasattr(item, "get_context_menu"):
395                menu_list.extend(item.get_context_menu(graph))
396           
397        return menu_list
398       
399    def popup_panel(self, p):
400        """
401            Add a panel object to the AUI manager
402            @param p: panel object to add to the AUI manager
403            @return: ID of the event associated with the new panel [int]
404        """
405       
406        ID = wx.NewId()
407        self.panels[str(ID)] = p
408       
409        count = 0
410        for item in self.panels:
411            if self.panels[item].window_name.startswith(p.window_name): 
412                count += 1
413               
414        windowname = p.window_name
415        caption = p.window_caption
416        if count>0:
417            windowname += str(count+1)
418            caption += (' '+str(count))
419           
420        p.window_name = windowname
421        p.window_caption = caption
422           
423        self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
424                          Name(windowname).Caption(caption).
425                          Floatable().
426                          #Float().
427                          Right().
428                          Dock().
429                          TopDockable().
430                          BottomDockable().
431                          LeftDockable().
432                          RightDockable().
433                          MinimizeButton().
434                          #Hide().
435                          #Show().
436                          BestSize(wx.Size(400,400)).
437                          MinSize(wx.Size(200,200)))
438       
439        # Register for showing/hiding the panel
440        wx.EVT_MENU(self, ID, self._on_view)
441       
442        self._mgr.Update()
443        return ID
444       
445    def _setup_menus(self):
446        """
447            Set up the application menus
448        """
449        # Menu
450        menubar = wx.MenuBar()
451       
452        # File menu
453        filemenu = wx.Menu()
454       
455        id = wx.NewId()
456        filemenu.Append(id, '&Open', 'Open a file')
457        wx.EVT_MENU(self, id, self._on_open)
458       
459        id = wx.NewId()
460        filemenu.Append(id,'&Quit', 'Exit') 
461        wx.EVT_MENU(self, id, self.Close)
462       
463        # Add sub menus
464        menubar.Append(filemenu,  '&File')
465       
466        # Plot menu
467        # Attach a menu item for each panel in our
468        # panel list that also appears in a plug-in.
469        # TODO: clean this up. We should just identify
470        # plug-in panels and add them all.
471       
472        # Only add the panel menu if there is more than one panel
473        n_panels = 0
474        for plug in self.plugins:
475            pers = plug.get_perspective()
476            if len(pers)>0:
477                n_panels += 1
478       
479        if n_panels>1:
480            viewmenu = wx.Menu()
481            for plug in self.plugins:
482                plugmenu = wx.Menu()
483                pers = plug.get_perspective()
484                if len(pers)>0:
485                    for item in self.panels:
486                        if item == 'default':
487                            continue
488                        panel = self.panels[item]
489                        if panel.window_name in pers:
490                            plugmenu.Append(int(item), panel.window_caption, "Show %s window" % panel.window_caption)
491                            wx.EVT_MENU(self, int(item), self._on_view)
492                   
493                    viewmenu.AppendMenu(wx.NewId(), plug.sub_menu, plugmenu, plug.sub_menu)
494               
495            menubar.Append(viewmenu, '&Panel')
496
497        # Perspective
498        # Attach a menu item for each defined perspective.
499        # Only add the perspective menu if there are more than one perspectves
500        n_perspectives = 0
501        for plug in self.plugins:
502            if len(plug.get_perspective()) > 0:
503                n_perspectives += 1
504       
505        if n_perspectives>1:
506            p_menu = wx.Menu()
507            for plug in self.plugins:
508                if len(plug.get_perspective()) > 0:
509                    id = wx.NewId()
510                    p_menu.Append(id, plug.sub_menu, "Switch to %s perspective" % plug.sub_menu)
511                    wx.EVT_MENU(self, id, plug.on_perspective)
512            menubar.Append(p_menu,   '&Perspective')
513 
514        # Help menu
515        helpmenu = wx.Menu()
516
517        # Look for help item in plug-ins
518        for item in self.plugins:
519            if hasattr(item, "help"):
520                id = wx.NewId()
521                helpmenu.Append(id,'&%s help' % item.sub_menu, '')
522                wx.EVT_MENU(self, id, item.help)
523       
524        if config._do_aboutbox:
525            id = wx.NewId()
526            helpmenu.Append(id,'&About', 'Software information')
527            wx.EVT_MENU(self, id, self._onAbout)
528        id = wx.NewId()
529        helpmenu.Append(id,'&Check for update', 'Check for the latest version of %s' % config.__appname__)
530        wx.EVT_MENU(self, id, self._check_update)
531       
532       
533       
534       
535        # Look for plug-in menus
536        # Add available plug-in sub-menus.
537        for item in self.plugins:
538            if hasattr(item, "populate_menu"):
539                for (self.next_id, menu, name) in item.populate_menu(self.next_id, self):
540                    menubar.Append(menu, name)
541       
542
543        menubar.Append(helpmenu, '&Help')
544         
545        self.SetMenuBar(menubar)
546       
547       
548       
549    def _on_status_event(self, evt):
550        """
551            Display status message
552        """
553        self.SetStatusText(str(evt.status))
554
555       
556    def _on_view(self, evt):
557        """
558            A panel was selected to be shown. If it's not already
559            shown, display it.
560            @param evt: menu event
561        """
562        self.show_panel(evt.GetId())
563
564    def show_panel(self, uid):
565        """
566            Shows the panel with the given id
567            @param uid: unique ID number of the panel to show
568        """
569        ID = str(uid)
570        config.printEVT("show_panel: %s" % ID)
571        if ID in self.panels.keys():
572            if not self._mgr.GetPane(self.panels[ID].window_name).IsShown():
573                self._mgr.GetPane(self.panels[ID].window_name).Show()
574                # Hide default panel
575                self._mgr.GetPane(self.panels["default"].window_name).Hide()
576               
577               
578            self._mgr.Update()
579       
580    def _on_open(self, event):
581        from data_loader import plot_data
582        path = self.choose_file()
583           
584        if path and os.path.isfile(path):
585            plot_data(self, path)
586               
587       
588       
589    def _onClose(self, event):
590        import sys
591        wx.Exit()
592        sys.exit()
593                   
594    def Close(self, event=None):
595        """
596            Quit the application
597        """
598        import sys
599        wx.Frame.Close(self)
600        wx.Exit()
601        sys.exit()
602
603 
604    def _check_update(self, event=None): 
605        """
606            Check with the deployment server whether a new version
607            of the application is available
608        """
609        import urllib
610        try: 
611            h = urllib.urlopen(config.__update_URL__)
612            lines = h.readlines()
613            line = ''
614            if len(lines)>0:
615                line = lines[0]
616               
617                toks = line.lstrip().rstrip().split('.')
618                toks_current = config.__version__.split('.')
619                update_available = False
620                for i in range(len(toks)):
621                    if int(toks[i])>int(toks_current[i]):
622                        update_available = True
623                if update_available:
624                    #print "Version %s is available" % line.rstrip().lstrip()
625                    self.SetStatusText("Version %s is available! See the Help menu to download it." % line.rstrip().lstrip())
626                    if event != None:
627                        import webbrowser
628                        webbrowser.open(config.__download_page__)
629                else:
630                    self.SetStatusText("You have the latest version of %s" % config.__appname__)
631                    #print "Server version = %s"  % line.rstrip().lstrip()
632        except:
633            self.SetStatusText("You have the latest version of %s" % config.__appname__)
634           
635           
636    def _onAbout(self, evt):
637        """
638            Pop up the about dialog
639            @param evt: menu event
640        """
641        if config._do_aboutbox:
642            import aboutbox 
643            dialog = aboutbox.DialogAbout(None, -1, "")
644            dialog.ShowModal()
645           
646    def set_manager(self, manager):
647        """
648            Sets the application manager for this frame
649            @param manager: frame manager
650        """
651        self.app_manager = manager
652       
653    def post_init(self):
654        """
655            This initialization method is called after the GUI
656            has been created and all plug-ins loaded. It calls
657            the post_init() method of each plug-in (if it exists)
658            so that final initialization can be done.
659        """
660        for item in self.plugins:
661            if hasattr(item, "post_init"):
662                item.post_init()
663       
664    def set_perspective(self, panels):
665        """
666            Sets the perspective of the GUI.
667            Opens all the panels in the list, and closes
668            all the others.
669           
670            @param panels: list of panels
671        """
672        print "gui_mng.set_perspective"
673        for item in self.panels:
674            # Check whether this is a sticky panel
675            if hasattr(self.panels[item], "ALWAYS_ON"):
676                if self.panels[item].ALWAYS_ON:
677                    continue 
678           
679            if self.panels[item].window_name in panels:
680                if not self._mgr.GetPane(self.panels[item].window_name).IsShown():
681                    self._mgr.GetPane(self.panels[item].window_name).Show()
682            else:
683                if self._mgr.GetPane(self.panels[item].window_name).IsShown():
684                    self._mgr.GetPane(self.panels[item].window_name).Hide()
685                 
686        self._mgr.Update()
687       
688    def choose_file(self):
689        """
690            Functionality that belongs elsewhere
691            Should add a hook to specify the preferred file type/extension.
692        """
693        #TODO: clean this up
694        from data_loader import choose_data_file
695        path = choose_data_file(self, self._default_save_location)
696        if not path==None:
697            try:
698                self._default_save_location = os.path.dirname(path)
699            except:
700                pass
701        return path
702   
703    def load_ascii_1D(self, path):
704        from data_loader import load_ascii_1D
705        return load_ascii_1D(path)
706                 
707class DefaultPanel(wx.Panel):
708    """
709        Defines the API for a panels to work with
710        the GUI manager
711    """
712    ## Internal nickname for the window, used by the AUI manager
713    window_name = "default"
714    ## Name to appear on the window title bar
715    window_caption = "Welcome panel"
716    ## Flag to tell the AUI manager to put this panel in the center pane
717    CENTER_PANE = True
718
719 
720# Toy application to test this Frame
721class ViewApp(wx.App):
722    def OnInit(self):
723        #from gui_manager import ViewerFrame
724        self.frame = ViewerFrame(None, -1, config.__appname__)   
725        self.frame.Show(True)
726
727        if hasattr(self.frame, 'special'):
728            print "Special?", self.frame.special.__class__.__name__
729            self.frame.special.SetCurrent()
730        self.SetTopWindow(self.frame)
731        return True
732   
733    def set_manager(self, manager):
734        """
735            Sets a reference to the application manager
736            of the GUI manager (Frame)
737        """
738        self.frame.set_manager(manager)
739       
740    def build_gui(self):
741        """
742            Build the GUI
743        """
744        self.frame.build_gui()
745        self.frame.post_init()
746       
747    def add_perspective(self, perspective):
748        """
749            Manually add a perspective to the application GUI
750        """
751        self.frame.add_perspective(perspective)
752       
753
754if __name__ == "__main__": 
755    app = ViewApp(0)
756    app.MainLoop()             
Note: See TracBrowser for help on using the repository browser.