source: sasview/guiframe/gui_manager.py @ 138c139

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 138c139 was b8d7491, checked in by Jae Cho <jhjcho@…>, 15 years ago

prepared for add prview to sansview: panel menu disabled

  • 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 reversed(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            else:
383                self.panels[str(id)] = p
384                self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
385                                  Name(p.window_name).Caption(p.window_caption).
386                                  Right().
387                                  Dock().
388                                  TopDockable().
389                                  BottomDockable().
390                                  LeftDockable().
391                                  RightDockable().
392                                  MinimizeButton().
393                                  Hide().
394                                  BestSize(wx.Size(550,600)).
395                                  MinSize(wx.Size(500,500)))                 
396               
397       
398    def get_context_menu(self, graph=None):
399        """
400            Get the context menu items made available
401            by the different plug-ins.
402            This function is used by the plotting module
403        """
404        menu_list = []
405        for item in self.plugins:
406            if hasattr(item, "get_context_menu"):
407                menu_list.extend(item.get_context_menu(graph))
408           
409        return menu_list
410       
411    def popup_panel(self, p):
412        """
413            Add a panel object to the AUI manager
414            @param p: panel object to add to the AUI manager
415            @return: ID of the event associated with the new panel [int]
416        """
417       
418        ID = wx.NewId()
419        self.panels[str(ID)] = p
420       
421        count = 0
422        for item in self.panels:
423            if self.panels[item].window_name.startswith(p.window_name): 
424                count += 1
425       
426        windowname = p.window_name
427        caption = p.window_caption
428       
429        if count>0:
430            windowname += str(count+1)
431            caption += (' '+str(count))
432         
433        p.window_name = windowname
434        p.window_caption = caption
435           
436        self._mgr.AddPane(p, wx.aui.AuiPaneInfo().
437                          Name(windowname).Caption(caption).
438                          Floatable().
439                          #Float().
440                          Right().
441                          Dock().
442                          TopDockable().
443                          BottomDockable().
444                          LeftDockable().
445                          RightDockable().
446                          MinimizeButton().
447                          #Hide().
448                          #Show().
449                          BestSize(wx.Size(550,600)).
450                          MinSize(wx.Size(500,500)))
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 two panels
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>2:
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            menubar.Append(viewmenu, '&Panel')
518
519        # Perspective
520        # Attach a menu item for each defined perspective.
521        # Only add the perspective menu if there are more than one perspectves
522        n_perspectives = 0
523        for plug in self.plugins:
524            if len(plug.get_perspective()) > 0:
525                n_perspectives += 1
526       
527        if n_perspectives>1:
528            p_menu = wx.Menu()
529            for plug in self.plugins:
530                if len(plug.get_perspective()) > 0:
531                    id = wx.NewId()
532                    p_menu.Append(id, plug.sub_menu, "Switch to %s perspective" % plug.sub_menu)
533                    wx.EVT_MENU(self, id, plug.on_perspective)
534            menubar.Append(p_menu,   '&Perspective')
535 
536        # Help menu
537        helpmenu = wx.Menu()
538
539        # Look for help item in plug-ins
540        for item in self.plugins:
541            if hasattr(item, "help"):
542                id = wx.NewId()
543                helpmenu.Append(id,'&%s help' % item.sub_menu, '')
544                wx.EVT_MENU(self, id, item.help)
545       
546        if config._do_aboutbox:
547            id = wx.NewId()
548            helpmenu.Append(id,'&About', 'Software information')
549            wx.EVT_MENU(self, id, self._onAbout)
550        id = wx.NewId()
551        helpmenu.Append(id,'&Check for update', 'Check for the latest version of %s' % config.__appname__)
552        wx.EVT_MENU(self, id, self._check_update)
553       
554       
555       
556       
557        # Look for plug-in menus
558        # Add available plug-in sub-menus.
559        for item in self.plugins:
560            if hasattr(item, "populate_menu"):
561                for (self.next_id, menu, name) in item.populate_menu(self.next_id, self):
562                    menubar.Append(menu, name)
563                   
564
565        menubar.Append(helpmenu, '&Help')
566         
567        self.SetMenuBar(menubar)
568       
569       
570       
571    def _on_status_event(self, evt):
572        """
573            Display status message
574        """
575        #self.sb.clear_gauge( msg="")
576        mythread=None
577        mytype= None
578        if hasattr(evt, "curr_thread"):
579            mythread= evt.curr_thread
580        if hasattr(evt, "type"):
581            mytype= evt.type
582        self.sb.set_status( type=mytype,msg=str(evt.status),thread=mythread)
583       
584
585       
586    def _on_view(self, evt):
587        """
588            A panel was selected to be shown. If it's not already
589            shown, display it.
590            @param evt: menu event
591        """
592        self.show_panel(evt.GetId())
593
594    def show_panel(self, uid):
595        """
596            Shows the panel with the given id
597            @param uid: unique ID number of the panel to show
598        """
599        ID = str(uid)
600        config.printEVT("show_panel: %s" % ID)
601        if ID in self.panels.keys():
602            if not self._mgr.GetPane(self.panels[ID].window_name).IsShown():
603                self._mgr.GetPane(self.panels[ID].window_name).Show()
604                # Hide default panel
605                self._mgr.GetPane(self.panels["default"].window_name).Hide()
606           
607               
608            self._mgr.Update()
609   
610    def _on_open(self, event):
611   
612        from data_loader import plot_data
613        path = self.choose_file()
614
615        if path ==None:
616            return
617        if path and os.path.isfile(path):
618            plot_data(self, path)
619           
620       
621       
622    def _onClose(self, event):
623        import sys
624        wx.Exit()
625        sys.exit()
626                   
627    def Close(self, event=None):
628        """
629            Quit the application
630        """
631        import sys
632        wx.Frame.Close(self)
633        wx.Exit()
634        sys.exit()
635
636 
637    def _check_update(self, event=None): 
638        """
639            Check with the deployment server whether a new version
640            of the application is available
641        """
642        import urllib
643        try: 
644            h = urllib.urlopen(config.__update_URL__)
645            lines = h.readlines()
646            line = ''
647            if len(lines)>0:
648                line = lines[0]
649               
650                toks = line.lstrip().rstrip().split('.')
651                toks_current = config.__version__.split('.')
652                update_available = False
653                for i in range(len(toks)):
654                    if len(toks[i].strip())>0:
655                        if int(toks[i].strip())>int(toks_current[i]):
656                            update_available = True
657                if update_available:
658                    #print "Version %s is available" % line.rstrip().lstrip()
659                    self.SetStatusText("Version %s is available! See the Help menu to download it." % line.rstrip().lstrip())
660                    if event != None:
661                        import webbrowser
662                        webbrowser.open(config.__download_page__)
663                else:
664                    if event != None:
665                        self.SetStatusText("You have the latest version of %s" % config.__appname__)
666        except:
667            if event != None:
668                self.SetStatusText("You have the latest version of %s" % config.__appname__)
669           
670           
671    def _onAbout(self, evt):
672        """
673            Pop up the about dialog
674            @param evt: menu event
675        """
676        if config._do_aboutbox:
677            import aboutbox 
678            dialog = aboutbox.DialogAbout(None, -1, "")
679            dialog.ShowModal()
680           
681    def set_manager(self, manager):
682        """
683            Sets the application manager for this frame
684            @param manager: frame manager
685        """
686        self.app_manager = manager
687       
688    def post_init(self):
689        """
690            This initialization method is called after the GUI
691            has been created and all plug-ins loaded. It calls
692            the post_init() method of each plug-in (if it exists)
693            so that final initialization can be done.
694        """
695        for item in self.plugins:
696            if hasattr(item, "post_init"):
697                item.post_init()
698       
699    def set_perspective(self, panels):
700        """
701            Sets the perspective of the GUI.
702            Opens all the panels in the list, and closes
703            all the others.
704           
705            @param panels: list of panels
706        """
707        for item in self.panels:
708            # Check whether this is a sticky panel
709            if hasattr(self.panels[item], "ALWAYS_ON"):
710                if self.panels[item].ALWAYS_ON:
711                    continue 
712           
713            if self.panels[item].window_name in panels:
714                if not self._mgr.GetPane(self.panels[item].window_name).IsShown():
715                    self._mgr.GetPane(self.panels[item].window_name).Show()
716            else:
717                if self._mgr.GetPane(self.panels[item].window_name).IsShown():
718                    self._mgr.GetPane(self.panels[item].window_name).Hide()
719                 
720        self._mgr.Update()
721       
722    def choose_file(self):
723        """
724            Functionality that belongs elsewhere
725            Should add a hook to specify the preferred file type/extension.
726        """
727        #TODO: clean this up
728        from data_loader import choose_data_file
729        path = choose_data_file(self, self._default_save_location)
730        if not path==None:
731            try:
732                self._default_save_location = os.path.dirname(path)
733            except:
734                pass
735        return path
736   
737    def load_ascii_1D(self, path):
738        from data_loader import load_ascii_1D
739        return load_ascii_1D(path)
740                 
741class DefaultPanel(wx.Panel):
742    """
743        Defines the API for a panels to work with
744        the GUI manager
745    """
746    ## Internal nickname for the window, used by the AUI manager
747    window_name = "default"
748    ## Name to appear on the window title bar
749    window_caption = "Welcome panel"
750    ## Flag to tell the AUI manager to put this panel in the center pane
751    CENTER_PANE = True
752
753 
754# Toy application to test this Frame
755class ViewApp(wx.App):
756    def OnInit(self):
757        #from gui_manager import ViewerFrame
758        self.frame = ViewerFrame(None, -1, config.__appname__)   
759        self.frame.Show(True)
760
761        if hasattr(self.frame, 'special'):
762            print "Special?", self.frame.special.__class__.__name__
763            self.frame.special.SetCurrent()
764        self.SetTopWindow(self.frame)
765        return True
766   
767    def set_manager(self, manager):
768        """
769            Sets a reference to the application manager
770            of the GUI manager (Frame)
771        """
772        self.frame.set_manager(manager)
773       
774    def build_gui(self):
775        """
776            Build the GUI
777        """
778        self.frame.build_gui()
779        self.frame.post_init()
780       
781    def add_perspective(self, perspective):
782        """
783            Manually add a perspective to the application GUI
784        """
785        self.frame.add_perspective(perspective)
786       
787
788if __name__ == "__main__": 
789    app = ViewApp(0)
790    app.MainLoop()             
Note: See TracBrowser for help on using the repository browser.