source: sasview/guiframe/gui_manager.py @ d22da51

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 d22da51 was 4102709, checked in by Gervaise Alina <gervyh@…>, 16 years ago

need to clean the code …working on it

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