source: sasview/guiframe/gui_manager.py @ 26bf293

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 26bf293 was 0d9dae8, checked in by Gervaise Alina <gervyh@…>, 16 years ago

add a new module containing common classes that I used often:
sans.guiframe.utils

remove event slicer and add it in sans.guicoom.events,
modified slicer for those events

  • Property mode set to 100644
File size: 27.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,Model2DPanelEvent
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        if p.window_name =="Analytical model 2D ":
423            print "guiframe 2D id",ID, p.window_name
424            event = Model2DPanelEvent(id= ID, pname=p.window_name)
425            for plug in self.plugins:
426                if hasattr(plug, fitpanel)
427                wx.PostEvent(plug, event)
428         """   
429        windowname = p.window_name
430        caption = p.window_caption
431        if count>0:
432            windowname += str(count+1)
433            caption += (' '+str(count))
434           
435        p.window_name = windowname
436        p.window_caption = caption
437           
438        self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
439                          Name(windowname).Caption(caption).
440                          Floatable().
441                          #Float().
442                          Right().
443                          Dock().
444                          TopDockable().
445                          BottomDockable().
446                          LeftDockable().
447                          RightDockable().
448                          MinimizeButton().
449                          #Hide().
450                          #Show().
451                          BestSize(wx.Size(400,400)).
452                          MinSize(wx.Size(350,350)))
453        pane = self._mgr.GetPane(windowname)
454        self._mgr.MaximizePane(pane)
455        self._mgr.RestoreMaximizedPane()
456       
457       
458        # Register for showing/hiding the panel
459       
460        wx.EVT_MENU(self, ID, self._on_view)
461       
462        self._mgr.Update()
463        return ID
464       
465    def _setup_menus(self):
466        """
467            Set up the application menus
468        """
469        # Menu
470        menubar = wx.MenuBar()
471       
472        # File menu
473        filemenu = wx.Menu()
474       
475        id = wx.NewId()
476        filemenu.Append(id, '&Open', 'Open a file')
477        wx.EVT_MENU(self, id, self._on_open)
478       
479        id = wx.NewId()
480        filemenu.Append(id,'&Quit', 'Exit') 
481        wx.EVT_MENU(self, id, self.Close)
482       
483        # Add sub menus
484        menubar.Append(filemenu,  '&File')
485       
486        # Plot menu
487        # Attach a menu item for each panel in our
488        # panel list that also appears in a plug-in.
489        # TODO: clean this up. We should just identify
490        # plug-in panels and add them all.
491       
492        # Only add the panel menu if there is more than one panel
493        n_panels = 0
494        for plug in self.plugins:
495            pers = plug.get_perspective()
496            if len(pers)>0:
497                n_panels += 1
498       
499        if n_panels>1:
500            viewmenu = wx.Menu()
501            for plug in self.plugins:
502                plugmenu = wx.Menu()
503                pers = plug.get_perspective()
504                if len(pers)>0:
505                    for item in self.panels:
506                        if item == 'default':
507                            continue
508                        panel = self.panels[item]
509                        if panel.window_name in pers:
510                            plugmenu.Append(int(item), panel.window_caption, "Show %s window" % panel.window_caption)
511                           
512                           
513                           
514                            wx.EVT_MENU(self, int(item), self._on_view)
515                   
516                    viewmenu.AppendMenu(wx.NewId(), plug.sub_menu, plugmenu, plug.sub_menu)
517               
518            menubar.Append(viewmenu, '&Panel')
519
520        # Perspective
521        # Attach a menu item for each defined perspective.
522        # Only add the perspective menu if there are more than one perspectves
523        n_perspectives = 0
524        for plug in self.plugins:
525            if len(plug.get_perspective()) > 0:
526                n_perspectives += 1
527       
528        if n_perspectives>1:
529            p_menu = wx.Menu()
530            for plug in self.plugins:
531                if len(plug.get_perspective()) > 0:
532                    id = wx.NewId()
533                    p_menu.Append(id, plug.sub_menu, "Switch to %s perspective" % plug.sub_menu)
534                    wx.EVT_MENU(self, id, plug.on_perspective)
535            menubar.Append(p_menu,   '&Perspective')
536 
537        # Help menu
538        helpmenu = wx.Menu()
539
540        # Look for help item in plug-ins
541        for item in self.plugins:
542            if hasattr(item, "help"):
543                id = wx.NewId()
544                helpmenu.Append(id,'&%s help' % item.sub_menu, '')
545                wx.EVT_MENU(self, id, item.help)
546       
547        if config._do_aboutbox:
548            id = wx.NewId()
549            helpmenu.Append(id,'&About', 'Software information')
550            wx.EVT_MENU(self, id, self._onAbout)
551        id = wx.NewId()
552        helpmenu.Append(id,'&Check for update', 'Check for the latest version of %s' % config.__appname__)
553        wx.EVT_MENU(self, id, self._check_update)
554       
555       
556       
557       
558        # Look for plug-in menus
559        # Add available plug-in sub-menus.
560        for item in self.plugins:
561            if hasattr(item, "populate_menu"):
562                for (self.next_id, menu, name) in item.populate_menu(self.next_id, self):
563                    menubar.Append(menu, name)
564                   
565
566        menubar.Append(helpmenu, '&Help')
567         
568        self.SetMenuBar(menubar)
569       
570       
571       
572    def _on_status_event(self, evt):
573        """
574            Display status message
575        """
576        self.SetStatusText(str(evt.status))
577
578       
579    def _on_view(self, evt):
580        """
581            A panel was selected to be shown. If it's not already
582            shown, display it.
583            @param evt: menu event
584        """
585        self.show_panel(evt.GetId())
586
587    def show_panel(self, uid):
588        """
589            Shows the panel with the given id
590            @param uid: unique ID number of the panel to show
591        """
592        ID = str(uid)
593        config.printEVT("show_panel: %s" % ID)
594        if ID in self.panels.keys():
595            if not self._mgr.GetPane(self.panels[ID].window_name).IsShown():
596                self._mgr.GetPane(self.panels[ID].window_name).Show()
597                # Hide default panel
598                self._mgr.GetPane(self.panels["default"].window_name).Hide()
599               
600               
601            self._mgr.Update()
602       
603    def _on_open(self, event):
604   
605        from data_loader import plot_data
606        path = self.choose_file()
607           
608        if path and os.path.isfile(path):
609            plot_data(self, path)
610           
611       
612       
613    def _onClose(self, event):
614        import sys
615        wx.Exit()
616        sys.exit()
617                   
618    def Close(self, event=None):
619        """
620            Quit the application
621        """
622        import sys
623        wx.Frame.Close(self)
624        wx.Exit()
625        sys.exit()
626
627 
628    def _check_update(self, event=None): 
629        """
630            Check with the deployment server whether a new version
631            of the application is available
632        """
633        import urllib
634        try: 
635            h = urllib.urlopen(config.__update_URL__)
636            lines = h.readlines()
637            line = ''
638            if len(lines)>0:
639                line = lines[0]
640               
641                toks = line.lstrip().rstrip().split('.')
642                toks_current = config.__version__.split('.')
643                update_available = False
644                for i in range(len(toks)):
645                    if len(toks[i].strip())>0:
646                        if int(toks[i].strip())>int(toks_current[i]):
647                            update_available = True
648                if update_available:
649                    #print "Version %s is available" % line.rstrip().lstrip()
650                    self.SetStatusText("Version %s is available! See the Help menu to download it." % line.rstrip().lstrip())
651                    if event != None:
652                        import webbrowser
653                        webbrowser.open(config.__download_page__)
654                else:
655                    if event != None:
656                        self.SetStatusText("You have the latest version of %s" % config.__appname__)
657        except:
658            if event != None:
659                self.SetStatusText("You have the latest version of %s" % config.__appname__)
660           
661           
662    def _onAbout(self, evt):
663        """
664            Pop up the about dialog
665            @param evt: menu event
666        """
667        if config._do_aboutbox:
668            import aboutbox 
669            dialog = aboutbox.DialogAbout(None, -1, "")
670            dialog.ShowModal()
671           
672    def set_manager(self, manager):
673        """
674            Sets the application manager for this frame
675            @param manager: frame manager
676        """
677        self.app_manager = manager
678       
679    def post_init(self):
680        """
681            This initialization method is called after the GUI
682            has been created and all plug-ins loaded. It calls
683            the post_init() method of each plug-in (if it exists)
684            so that final initialization can be done.
685        """
686        for item in self.plugins:
687            if hasattr(item, "post_init"):
688                item.post_init()
689       
690    def set_perspective(self, panels):
691        """
692            Sets the perspective of the GUI.
693            Opens all the panels in the list, and closes
694            all the others.
695           
696            @param panels: list of panels
697        """
698        for item in self.panels:
699            # Check whether this is a sticky panel
700            if hasattr(self.panels[item], "ALWAYS_ON"):
701                if self.panels[item].ALWAYS_ON:
702                    continue 
703           
704            if self.panels[item].window_name in panels:
705                if not self._mgr.GetPane(self.panels[item].window_name).IsShown():
706                    self._mgr.GetPane(self.panels[item].window_name).Show()
707            else:
708                if self._mgr.GetPane(self.panels[item].window_name).IsShown():
709                    self._mgr.GetPane(self.panels[item].window_name).Hide()
710                 
711        self._mgr.Update()
712       
713    def choose_file(self):
714        """
715            Functionality that belongs elsewhere
716            Should add a hook to specify the preferred file type/extension.
717        """
718        #TODO: clean this up
719        from data_loader import choose_data_file
720        path = choose_data_file(self, self._default_save_location)
721        if not path==None:
722            try:
723                self._default_save_location = os.path.dirname(path)
724            except:
725                pass
726        return path
727   
728    def load_ascii_1D(self, path):
729        from data_loader import load_ascii_1D
730        return load_ascii_1D(path)
731                 
732class DefaultPanel(wx.Panel):
733    """
734        Defines the API for a panels to work with
735        the GUI manager
736    """
737    ## Internal nickname for the window, used by the AUI manager
738    window_name = "default"
739    ## Name to appear on the window title bar
740    window_caption = "Welcome panel"
741    ## Flag to tell the AUI manager to put this panel in the center pane
742    CENTER_PANE = True
743
744 
745# Toy application to test this Frame
746class ViewApp(wx.App):
747    def OnInit(self):
748        #from gui_manager import ViewerFrame
749        self.frame = ViewerFrame(None, -1, config.__appname__)   
750        self.frame.Show(True)
751
752        if hasattr(self.frame, 'special'):
753            print "Special?", self.frame.special.__class__.__name__
754            self.frame.special.SetCurrent()
755        self.SetTopWindow(self.frame)
756        return True
757   
758    def set_manager(self, manager):
759        """
760            Sets a reference to the application manager
761            of the GUI manager (Frame)
762        """
763        self.frame.set_manager(manager)
764       
765    def build_gui(self):
766        """
767            Build the GUI
768        """
769        self.frame.build_gui()
770        self.frame.post_init()
771       
772    def add_perspective(self, perspective):
773        """
774            Manually add a perspective to the application GUI
775        """
776        self.frame.add_perspective(perspective)
777       
778
779if __name__ == "__main__": 
780    app = ViewApp(0)
781    app.MainLoop()             
Note: See TracBrowser for help on using the repository browser.