source: sasview/guiframe/gui_manager.py @ dabb633

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

allow multiple loaded data plots

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