source: sasview/guiframe/gui_manager.py @ ddd864e

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 ddd864e was 63c7d85d, checked in by Gervaise Alina <gervyh@…>, 15 years ago

move some code from guiframe to ftting pluging

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