source: sasview/src/sas/guiframe/gui_manager.py @ f21d496

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

Cleaning up old "standalone" code

  • Property mode set to 100644
File size: 123.5 KB
Line 
1"""
2    Gui manager: manages the widgets making up an application
3"""
4################################################################################
5#This software was developed by the University of Tennessee as part of the
6#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
7#project funded by the US National Science Foundation.
8#
9#See the license text in license.txtz
10#
11#copyright 2008, University of Tennessee
12################################################################################
13
14
15import wx
16import wx.aui
17import os
18import sys
19import time
20import imp
21import warnings
22import re
23warnings.simplefilter("ignore")
24import logging
25import httplib
26import traceback
27import urllib
28import urllib2
29
30
31from sas.guiframe.events import EVT_CATEGORY
32from sas.guiframe.events import EVT_STATUS
33from sas.guiframe.events import EVT_APPEND_BOOKMARK
34from sas.guiframe.events import EVT_PANEL_ON_FOCUS
35from sas.guiframe.events import EVT_NEW_LOAD_DATA
36from sas.guiframe.events import EVT_NEW_COLOR
37from sas.guiframe.events import StatusEvent
38from sas.guiframe.events import NewPlotEvent
39from sas.guiframe.gui_style import GUIFRAME
40from sas.guiframe.gui_style import GUIFRAME_ID
41from sas.guiframe.data_panel import DataPanel
42from sas.guiframe.panel_base import PanelBase
43from sas.guiframe.gui_toolbar import GUIToolBar
44from sas.guiframe.data_processor import GridFrame
45from sas.guiframe.events import EVT_NEW_BATCH
46from sas.guiframe.CategoryManager import CategoryManager
47from sas.dataloader.loader import Loader
48from matplotlib import _pylab_helpers
49
50def get_app_dir():
51    """
52        The application directory is the one where the default custom_config.py
53        file resides.
54    """
55    # First, try the directory of the executable we are running
56    app_path = sys.path[0]
57    if os.path.isfile(app_path):
58        app_path = os.path.dirname(app_path)
59    if os.path.isfile(os.path.join(app_path, "custom_config.py")):
60        app_path = os.path.abspath(app_path)
61        logging.info("Using application path: %s", app_path)
62        return app_path
63
64    # Next, try the current working directory
65    if os.path.isfile(os.path.join(os.getcwd(), "custom_config.py")):
66        logging.info("Using application path: %s", os.getcwd())
67        return os.path.abspath(os.getcwd())
68
69    # Finally, try the directory of the sasview module
70    #TODO: gui_manager will have to know about sasview until we
71    # clean all these module variables and put them into a config class
72    # that can be passed by sasview.py.
73    logging.info(sys.executable)
74    logging.info(str(sys.argv))
75    from sas import sasview as sasview
76    app_path = os.path.dirname(sasview.__file__)
77    logging.info("Using application path: %s", app_path)
78    return app_path
79
80def get_user_directory():
81    """
82        Returns the user's home directory
83    """
84    userdir = os.path.join(os.path.expanduser("~"), ".sasview")
85    if not os.path.isdir(userdir):
86        os.makedirs(userdir)
87    return userdir
88
89def _find_local_config(file, path):
90    """
91        Find configuration file for the current application
92    """
93    config_module = None
94    fObj = None
95    try:
96        fObj, path_config, descr = imp.find_module(file, [path])
97        config_module = imp.load_module(file, fObj, path_config, descr)
98    except:
99        logging.error("Error loading %s/%s: %s" % (path, file, sys.exc_value))
100    finally:
101        if fObj is not None:
102            fObj.close()
103    logging.info("GuiManager loaded %s/%s" % (path, file))
104    return config_module
105
106# Get APP folder
107PATH_APP = get_app_dir()
108DATAPATH = PATH_APP
109
110# GUI always starts from the App folder
111#os.chdir(PATH_APP)
112# Read in the local config, which can either be with the main
113# application or in the installation directory
114config = _find_local_config('local_config', PATH_APP)
115if config is None:
116    config = _find_local_config('local_config', os.getcwd())
117    if config is None:
118        # Didn't find local config, load the default
119        import sas.guiframe.config as config
120        logging.info("using default local_config")
121    else:
122        logging.info("found local_config in %s" % os.getcwd())
123else:
124    logging.info("found local_config in %s" % PATH_APP)
125
126from sas.guiframe.customdir  import SetupCustom
127c_conf_dir = SetupCustom().setup_dir(PATH_APP)
128custom_config = _find_local_config('custom_config', c_conf_dir)
129if custom_config is None:
130    custom_config = _find_local_config('custom_config', os.getcwd())
131    if custom_config is None:
132        msgConfig = "Custom_config file was not imported"
133        logging.info(msgConfig)
134    else:
135        logging.info("using custom_config in %s" % os.getcwd())
136else:
137    logging.info("using custom_config from %s" % c_conf_dir)
138
139#read some constants from config
140APPLICATION_STATE_EXTENSION = config.APPLICATION_STATE_EXTENSION
141APPLICATION_NAME = config.__appname__
142SPLASH_SCREEN_PATH = config.SPLASH_SCREEN_PATH
143WELCOME_PANEL_ON = config.WELCOME_PANEL_ON
144SPLASH_SCREEN_WIDTH = config.SPLASH_SCREEN_WIDTH
145SPLASH_SCREEN_HEIGHT = config.SPLASH_SCREEN_HEIGHT
146SS_MAX_DISPLAY_TIME = config.SS_MAX_DISPLAY_TIME
147if not WELCOME_PANEL_ON:
148    WELCOME_PANEL_SHOW = False
149else:
150    WELCOME_PANEL_SHOW = True
151try:
152    DATALOADER_SHOW = custom_config.DATALOADER_SHOW
153    TOOLBAR_SHOW = custom_config.TOOLBAR_SHOW
154    FIXED_PANEL = custom_config.FIXED_PANEL
155    if WELCOME_PANEL_ON:
156        WELCOME_PANEL_SHOW = custom_config.WELCOME_PANEL_SHOW
157    PLOPANEL_WIDTH = custom_config.PLOPANEL_WIDTH
158    DATAPANEL_WIDTH = custom_config.DATAPANEL_WIDTH
159    GUIFRAME_WIDTH = custom_config.GUIFRAME_WIDTH
160    GUIFRAME_HEIGHT = custom_config.GUIFRAME_HEIGHT
161    CONTROL_WIDTH = custom_config.CONTROL_WIDTH
162    CONTROL_HEIGHT = custom_config.CONTROL_HEIGHT
163    DEFAULT_PERSPECTIVE = custom_config.DEFAULT_PERSPECTIVE
164    CLEANUP_PLOT = custom_config.CLEANUP_PLOT
165    # custom open_path
166    open_folder = custom_config.DEFAULT_OPEN_FOLDER
167    if open_folder != None and os.path.isdir(open_folder):
168        DEFAULT_OPEN_FOLDER = os.path.abspath(open_folder)
169    else:
170        DEFAULT_OPEN_FOLDER = PATH_APP
171except:
172    DATALOADER_SHOW = True
173    TOOLBAR_SHOW = True
174    FIXED_PANEL = True
175    WELCOME_PANEL_SHOW = False
176    PLOPANEL_WIDTH = config.PLOPANEL_WIDTH
177    DATAPANEL_WIDTH = config.DATAPANEL_WIDTH
178    GUIFRAME_WIDTH = config.GUIFRAME_WIDTH
179    GUIFRAME_HEIGHT = config.GUIFRAME_HEIGHT
180    CONTROL_WIDTH = -1
181    CONTROL_HEIGHT = -1
182    DEFAULT_PERSPECTIVE = None
183    CLEANUP_PLOT = False
184    DEFAULT_OPEN_FOLDER = PATH_APP
185
186DEFAULT_STYLE = config.DEFAULT_STYLE
187
188PLUGIN_STATE_EXTENSIONS = config.PLUGIN_STATE_EXTENSIONS
189OPEN_SAVE_MENU = config.OPEN_SAVE_PROJECT_MENU
190VIEW_MENU = config.VIEW_MENU
191EDIT_MENU = config.EDIT_MENU
192extension_list = []
193if APPLICATION_STATE_EXTENSION is not None:
194    extension_list.append(APPLICATION_STATE_EXTENSION)
195EXTENSIONS = PLUGIN_STATE_EXTENSIONS + extension_list
196try:
197    PLUGINS_WLIST = '|'.join(config.PLUGINS_WLIST)
198except:
199    PLUGINS_WLIST = ''
200APPLICATION_WLIST = config.APPLICATION_WLIST
201IS_WIN = True
202IS_LINUX = False
203CLOSE_SHOW = True
204TIME_FACTOR = 2
205MDI_STYLE = wx.DEFAULT_FRAME_STYLE
206NOT_SO_GRAPH_LIST = ["BoxSum"]
207PARENT_FRAME = wx.MDIParentFrame
208CHILD_FRAME = wx.MDIChildFrame
209if sys.platform.count("win32") < 1:
210    IS_WIN = False
211    TIME_FACTOR = 2
212    if int(str(wx.__version__).split('.')[0]) == 2:
213        if int(str(wx.__version__).split('.')[1]) < 9:
214            CLOSE_SHOW = False
215    if sys.platform.count("darwin") < 1:
216        IS_LINUX = True
217        PARENT_FRAME = wx.Frame
218        CHILD_FRAME = wx.Frame
219
220class ViewerFrame(PARENT_FRAME):
221    """
222    Main application frame
223    """
224
225    def __init__(self, parent, title,
226                 size=(GUIFRAME_WIDTH, GUIFRAME_HEIGHT),
227                 gui_style=DEFAULT_STYLE,
228                 style=wx.DEFAULT_FRAME_STYLE,
229                 pos=wx.DefaultPosition):
230        """
231        Initialize the Frame object
232        """
233
234        PARENT_FRAME.__init__(self, parent=parent, title=title, pos=pos, size=size)
235        # title
236        self.title = title
237        self.__gui_style = gui_style
238        path = os.path.dirname(__file__)
239        temp_path = os.path.join(path, 'images')
240        ico_file = os.path.join(temp_path, 'ball.ico')
241        if os.path.isfile(ico_file):
242            self.SetIcon(wx.Icon(ico_file, wx.BITMAP_TYPE_ICO))
243        else:
244            temp_path = os.path.join(os.getcwd(), 'images')
245            ico_file = os.path.join(temp_path, 'ball.ico')
246            if os.path.isfile(ico_file):
247                self.SetIcon(wx.Icon(ico_file, wx.BITMAP_TYPE_ICO))
248            else:
249                ico_file = os.path.join(os.path.dirname(os.path.sys.path[0]),
250                                        'images', 'ball.ico')
251                if os.path.isfile(ico_file):
252                    self.SetIcon(wx.Icon(ico_file, wx.BITMAP_TYPE_ICO))
253        self.path = PATH_APP
254        self.application_name = APPLICATION_NAME
255        ## Application manager
256        self._input_file = None
257        self.app_manager = None
258        self._mgr = None
259        #add current perpsective
260        self._current_perspective = None
261        self._plotting_plugin = None
262        self._data_plugin = None
263        #Menu bar and item
264        self._menubar = None
265        self._file_menu = None
266        self._data_menu = None
267        self._view_menu = None
268        self._data_panel_menu = None
269        self._help_menu = None
270        self._tool_menu = None
271        self._applications_menu_pos = -1
272        self._applications_menu_name = None
273        self._applications_menu = None
274        self._edit_menu = None
275        self._toolbar_menu = None
276        self._save_appl_menu = None
277        #tool bar
278        self._toolbar = None
279        # Status bar
280        self.sb = None
281        # number of plugins
282        self._num_perspectives = 0
283        # plot duck cleanup option
284        self.cleanup_plots = CLEANUP_PLOT
285        ## Find plug-ins
286        # Modify this so that we can specify the directory to look into
287        self.plugins = []
288        #add local plugin
289        self.plugins += self._get_local_plugins()
290        self.plugins += self._find_plugins()
291        ## List of panels
292        self.panels = {}
293        # List of plot panels
294        self.plot_panels = {}
295        # default Graph number fot the plotpanel caption
296        self.graph_num = 0
297
298        # Default locations
299        self._default_save_location = DEFAULT_OPEN_FOLDER
300        # Welcome panel
301        self.defaultPanel = None
302        self.welcome_panel_class = None
303        #panel on focus
304        self.panel_on_focus = None
305        #control_panel on focus
306        self.cpanel_on_focus = None
307
308        self.loader = Loader()
309        #data manager
310        self.batch_on = False
311        from sas.guiframe.data_manager import DataManager
312        self._data_manager = DataManager()
313        self._data_panel = None#DataPanel(parent=self)
314        if self.panel_on_focus is not None:
315            self._data_panel.set_panel_on_focus(self.panel_on_focus.window_caption)
316        # list of plot panels in schedule to full redraw
317        self.schedule = False
318        #self.callback = True
319        self._idle_count = 0
320        self.schedule_full_draw_list = []
321        self.idletimer = wx.CallLater(TIME_FACTOR, self._onDrawIdle)
322
323        self.batch_frame = GridFrame(parent=self)
324        self.batch_frame.Hide()
325        self.on_batch_selection(event=None)
326        self.add_icon()
327
328        # Register the close event so it calls our own method
329        wx.EVT_CLOSE(self, self.WindowClose)
330        # Register to status events
331        self.Bind(EVT_STATUS, self._on_status_event)
332        #Register add extra data on the same panel event on load
333        self.Bind(EVT_PANEL_ON_FOCUS, self.set_panel_on_focus)
334        self.Bind(EVT_APPEND_BOOKMARK, self.append_bookmark)
335        self.Bind(EVT_NEW_LOAD_DATA, self.on_load_data)
336        self.Bind(EVT_NEW_BATCH, self.on_batch_selection)
337        self.Bind(EVT_NEW_COLOR, self.on_color_selection)
338        self.Bind(EVT_CATEGORY, self.on_change_categories)
339        self.setup_custom_conf()
340        # Preferred window size
341        self._window_width, self._window_height = size
342
343    def add_icon(self):
344        """
345        get list of child and attempt to add the default icon
346        """
347
348        list_children = self.GetChildren()
349        for frame in list_children:
350            self.put_icon(frame)
351
352    def put_icon(self, frame):
353        """
354        Put icon on the tap of a panel
355        """
356        if hasattr(frame, "IsIconized"):
357            if not frame.IsIconized():
358                try:
359                    icon = self.GetIcon()
360                    frame.SetIcon(icon)
361                except:
362                    pass
363
364    def get_client_size(self):
365        """
366        return client size tuple
367        """
368        width, height = self.GetClientSizeTuple()
369        height -= 45
370        # Adjust toolbar height
371        toolbar = self.GetToolBar()
372        if toolbar != None:
373            _, tb_h = toolbar.GetSizeTuple()
374            height -= tb_h
375        return width, height
376
377    def on_change_categories(self, evt):
378        # ILL
379        fitpanel = None
380        for item in self.plugins:
381            if hasattr(item, "get_panels"):
382                if hasattr(item, "fit_panel"):
383                    fitpanel = item.fit_panel
384
385        if fitpanel != None:
386            for i in range(0, fitpanel.GetPageCount()):
387                fitpanel.GetPage(i)._populate_listbox()
388
389    def on_set_batch_result(self, data_outputs, data_inputs=None,
390                            plugin_name=""):
391        """
392        Display data into a grid in batch mode and show the grid
393        """
394        t = time.localtime(time.time())
395        time_str = time.strftime("%b %d %H;%M of %Y", t)
396        details = "File Generated by %s : %s" % (APPLICATION_NAME,
397                                                 str(plugin_name))
398        details += "on %s.\n" % time_str
399        ext = ".csv"
400        file_name = "Batch_" + str(plugin_name) + "_" + time_str + ext
401        file_name = self._default_save_location + str(file_name)
402
403        self.open_with_localapp(file_name=file_name,
404                                details=details,
405                                data_inputs=data_inputs,
406                                data_outputs=data_outputs)
407
408    def open_with_localapp(self, data_inputs=None, details="", file_name=None,
409                           data_outputs=None):
410        """
411        Display value of data into the application grid
412        :param data: dictionary of string and list of items
413        """
414        self.batch_frame.set_data(data_inputs=data_inputs,
415                                  data_outputs=data_outputs,
416                                  details=details,
417                                  file_name=file_name)
418        self.show_batch_frame(None)
419
420    def on_read_batch_tofile(self, base):
421        """
422        Open a file dialog , extract the file to read and display values
423        into a grid
424        """
425        path = None
426        if self._default_save_location == None:
427            self._default_save_location = os.getcwd()
428        wildcard = "(*.csv; *.txt)|*.csv; *.txt"
429        dlg = wx.FileDialog(base,
430                            "Choose a file",
431                            self._default_save_location, "",
432                            wildcard)
433        if dlg.ShowModal() == wx.ID_OK:
434            path = dlg.GetPath()
435            if path is not None:
436                self._default_save_location = os.path.dirname(path)
437        dlg.Destroy()
438        try:
439            self.read_batch_tofile(file_name=path)
440        except:
441            msg = "Error occurred when reading the file; %s\n" % path
442            msg += "%s\n" % sys.exc_value
443            wx.PostEvent(self, StatusEvent(status=msg,
444                                           info="error"))
445
446    def read_batch_tofile(self, file_name):
447        """
448        Extract value from file name and Display them into a grid
449        """
450        if file_name is None or file_name.strip() == "":
451            return
452        data = {}
453        fd = open(file_name, 'r')
454        _, ext = os.path.splitext(file_name)
455        separator = None
456        if ext.lower() == ".csv":
457            separator = ","
458        fd_buffer = fd.read()
459        lines = fd_buffer.split('\n')
460        fd.close()
461        column_names_line = ""
462        index = None
463        details = ""
464        for index in range(len(lines)):
465            line = lines[index]
466            line.strip()
467            count = 0
468            if separator == None:
469                line.replace('\t', ' ')
470                #found the first line containing the label
471                col_name_toks = line.split()
472                for item in col_name_toks:
473                    if item.strip() != "":
474                        count += 1
475                    else:
476                        line = " "
477            elif line.find(separator) != -1:
478                if line.count(separator) >= 2:
479                    #found the first line containing the label
480                    col_name_toks = line.split(separator)
481                    for item in col_name_toks:
482                        if item.strip() != "":
483                            count += 1
484            else:
485                details += line
486            if count >= 2:
487                column_names_line = line
488                break
489
490        if column_names_line.strip() == "" or index is None:
491            return
492
493        col_name_toks = column_names_line.split(separator)
494        c_index = 0
495        for col_index in range(len(col_name_toks)):
496            c_name = col_name_toks[col_index]
497            if c_name.strip() != "":
498                # distinguish between column name and value
499                try:
500                    float(c_name)
501                    col_name = "Column %s" % str(col_index + 1)
502                    index_min = index
503                except:
504                    col_name = c_name
505                    index_min = index + 1
506                data[col_name] = [lines[row].split(separator)[c_index]
507                                  for row in range(index_min, len(lines) - 1)]
508                c_index += 1
509
510        self.open_with_localapp(data_outputs=data, data_inputs=None,
511                                file_name=file_name, details=details)
512
513    def write_batch_tofile(self, data, file_name, details=""):
514        """
515        Helper to write result from batch into cvs file
516        """
517        self._default_save_location = os.path.dirname(file_name)
518        name = os.path.basename(file_name)
519        if data is None or file_name is None or file_name.strip() == "":
520            return
521        _, ext = os.path.splitext(name)
522        try:
523            fd = open(file_name, 'w')
524        except:
525            # On Permission denied: IOError
526            temp_dir = get_user_directory()
527            temp_file_name = os.path.join(temp_dir, name)
528            fd = open(temp_file_name, 'w')
529        separator = "\t"
530        if ext.lower() == ".csv":
531            separator = ","
532        fd.write(str(details))
533        for col_name  in data.keys():
534            fd.write(str(col_name))
535            fd.write(separator)
536        fd.write('\n')
537        max_list = [len(value) for value in data.values()]
538        if len(max_list) == 0:
539            return
540        max_index = max(max_list)
541        index = 0
542        while index < max_index:
543            for value_list in data.values():
544                if index < len(value_list):
545                    fd.write(str(value_list[index]))
546                    fd.write(separator)
547                else:
548                    fd.write('')
549                    fd.write(separator)
550            fd.write('\n')
551            index += 1
552        fd.close()
553
554    def open_with_externalapp(self, data, file_name, details=""):
555        """
556        Display data in the another application , by default Excel
557        """
558        if not os.path.exists(file_name):
559            self.write_batch_tofile(data=data, file_name=file_name,
560                                    details=details)
561        try:
562            from win32com.client import Dispatch
563            excel_app = Dispatch('Excel.Application')
564            excel_app.Workbooks.Open(file_name)
565            excel_app.Visible = 1
566        except:
567            msg = "Error occured when calling Excel.\n"
568            msg += "Check that Excel installed in this machine or \n"
569            msg += "check that %s really exists.\n" % str(file_name)
570            wx.PostEvent(self, StatusEvent(status=msg,
571                                           info="error"))
572
573    def on_batch_selection(self, event=None):
574        """
575        :param event: contains parameter enable. When enable is set to True
576            the application is in Batch mode otherwise the application is
577            in Single mode.
578        """
579        if event is not None:
580            self.batch_on = event.enable
581        for plug in self.plugins:
582            plug.set_batch_selection(self.batch_on)
583
584    def on_color_selection(self, event):
585        """
586        :param event: contains parameters for id and color
587        """
588        color, event_id = event.color, event.id
589        for plug in self.plugins:
590            plug.add_color(color, event_id)
591
592    def setup_custom_conf(self):
593        """
594        Set up custom configuration if exists
595        """
596        if custom_config == None:
597            return
598
599        if not FIXED_PANEL:
600            self.__gui_style &= (~GUIFRAME.FIXED_PANEL)
601            self.__gui_style |= GUIFRAME.FLOATING_PANEL
602
603        if not DATALOADER_SHOW:
604            self.__gui_style &= (~GUIFRAME.MANAGER_ON)
605
606        if not TOOLBAR_SHOW:
607            self.__gui_style &= (~GUIFRAME.TOOLBAR_ON)
608
609        if WELCOME_PANEL_SHOW:
610            self.__gui_style |= GUIFRAME.WELCOME_PANEL_ON
611
612    def set_custom_default_perspective(self):
613        """
614        Set default starting perspective
615        """
616        if custom_config == None:
617            return
618        for plugin in self.plugins:
619            try:
620                if plugin.sub_menu == DEFAULT_PERSPECTIVE:
621
622                    plugin.on_perspective(event=None)
623                    frame = plugin.get_frame()
624                    frame.Show(True)
625                    #break
626                else:
627                    frame = plugin.get_frame()
628                    frame.Show(False)
629            except:
630                pass
631        return
632
633    def on_load_data(self, event):
634        """
635        received an event to trigger load from data plugin
636        """
637        if self._data_plugin is not None:
638            self._data_plugin.load_data(event)
639
640    def get_current_perspective(self):
641        """
642        return the current perspective
643        """
644        return self._current_perspective
645
646    def get_save_location(self):
647        """
648        return the _default_save_location
649        """
650        return self._default_save_location
651
652    def set_input_file(self, input_file):
653        """
654        :param input_file: file to read
655        """
656        self._input_file = input_file
657
658    def get_data_manager(self):
659        """
660        return the data manager.
661        """
662        return self._data_manager
663
664    def get_toolbar(self):
665        """
666        return the toolbar.
667        """
668        return self._toolbar
669
670    def set_panel_on_focus(self, event):
671        """
672        Store reference to the last panel on focus
673        update the toolbar if available
674        update edit menu if available
675        """
676        if event != None:
677            self.panel_on_focus = event.panel
678        if self.panel_on_focus is not None:
679            #Disable save application if the current panel is in batch mode
680            flag = self.panel_on_focus.get_save_flag()
681            if self._save_appl_menu != None:
682                self._save_appl_menu.Enable(flag)
683
684            if self.panel_on_focus not in self.plot_panels.values():
685                for ID in self.panels.keys():
686                    if self.panel_on_focus != self.panels[ID]:
687                        self.panels[ID].on_kill_focus(None)
688
689            if self._data_panel is not None and \
690                            self.panel_on_focus is not None:
691                self.set_panel_on_focus_helper()
692                #update toolbar
693                self._update_toolbar_helper()
694                #update edit menu
695                self.enable_edit_menu()
696
697    def disable_app_menu(self, p_panel=None):
698        """
699        Disables all menus in the menubar
700        """
701        return
702
703    def send_focus_to_datapanel(self, name):
704        """
705        Send focusing on ID to data explorer
706        """
707        if self._data_panel != None:
708            self._data_panel.set_panel_on_focus(name)
709
710    def set_panel_on_focus_helper(self):
711        """
712        Helper for panel on focus with data_panel
713        """
714        caption = self.panel_on_focus.window_caption
715        self.send_focus_to_datapanel(caption)
716        #update combo
717        if self.panel_on_focus in self.plot_panels.values():
718            combo = self._data_panel.cb_plotpanel
719            combo_title = str(self.panel_on_focus.window_caption)
720            combo.SetStringSelection(combo_title)
721            combo.SetToolTip(wx.ToolTip(combo_title))
722        elif self.panel_on_focus != self._data_panel:
723            cpanel = self.panel_on_focus
724            if self.cpanel_on_focus != cpanel:
725                cpanel.on_tap_focus()
726                self.cpanel_on_focus = self.panel_on_focus
727
728    def reset_bookmark_menu(self, panel):
729        """
730        Reset Bookmark menu list
731
732        : param panel: a control panel or tap where the bookmark is
733        """
734        cpanel = panel
735        if self._toolbar != None and cpanel._bookmark_flag:
736            for item in  self._toolbar.get_bookmark_items():
737                self._toolbar.remove_bookmark_item(item)
738            self._toolbar.add_bookmark_default()
739            pos = 0
740            for bitem in cpanel.popUpMenu.GetMenuItems():
741                pos += 1
742                if pos < 3:
743                    continue
744                id = bitem.GetId()
745                label = bitem.GetLabel()
746                self._toolbar.append_bookmark_item(id, label)
747                wx.EVT_MENU(self, id, cpanel._back_to_bookmark)
748            self._toolbar.Realize()
749
750
751    def build_gui(self):
752        """
753        Build the GUI by setting up the toolbar, menu and layout.
754        """
755        # set tool bar
756        self._setup_tool_bar()
757
758        # Create the menu bar. To be filled later.
759        # WX 3.0 needs us to create the menu bar first.
760        self._menubar = wx.MenuBar()
761        if wx.VERSION_STRING >= '3.0.0.0':
762            self.SetMenuBar(self._menubar)
763        self._add_menu_file()
764        self._add_menu_edit()
765        self._add_menu_view()
766        self._add_menu_tool()
767        # Set up the layout
768        self._setup_layout()
769        self._add_menu_application()
770
771        # Set up the menu
772        self._add_current_plugin_menu()
773        self._add_help_menu()
774        # Append item from plugin under menu file if necessary
775        self._populate_file_menu()
776
777
778        if not wx.VERSION_STRING >= '3.0.0.0':
779            self.SetMenuBar(self._menubar)
780
781        try:
782            self.load_from_cmd(self._input_file)
783        except:
784            msg = "%s Cannot load file %s\n" % (str(APPLICATION_NAME),
785                                                str(self._input_file))
786            msg += str(sys.exc_value) + '\n'
787            logging.error(msg)
788        if self._data_panel is not None and len(self.plugins) > 0:
789            self._data_panel.fill_cbox_analysis(self.plugins)
790        self.post_init()
791        # Set Custom default
792        self.set_custom_default_perspective()
793        # Set up extra custom tool menu
794        self._setup_extra_custom()
795        self._check_update(None)
796
797    def _setup_extra_custom(self):
798        """
799        Set up toolbar and welcome view if needed
800        """
801        style = self.__gui_style & GUIFRAME.TOOLBAR_ON
802        if (style == GUIFRAME.TOOLBAR_ON) & (not self._toolbar.IsShown()):
803            self._on_toggle_toolbar()
804
805        # Set Custom deafult start page
806        welcome_style = self.__gui_style & GUIFRAME.WELCOME_PANEL_ON
807        if welcome_style == GUIFRAME.WELCOME_PANEL_ON:
808            self.show_welcome_panel(None)
809
810    def _setup_layout(self):
811        """
812        Set up the layout
813        """
814        # Status bar
815        from sas.guiframe.gui_statusbar import StatusBar
816        self.sb = StatusBar(self, wx.ID_ANY)
817        self.SetStatusBar(self.sb)
818        # Load panels
819        self._load_panels()
820
821    def SetStatusText(self, *args, **kwds):
822        """
823        """
824        number = self.sb.get_msg_position()
825        wx.Frame.SetStatusText(self, number=number, *args, **kwds)
826
827    def PopStatusText(self, *args, **kwds):
828        """
829        """
830        field = self.sb.get_msg_position()
831        wx.Frame.PopStatusText(self, field=field)
832
833    def PushStatusText(self, *args, **kwds):
834        """
835            FIXME: No message is passed. What is this supposed to do?
836        """
837        field = self.sb.get_msg_position()
838        wx.Frame.PushStatusText(self, field=field,
839                                string="FIXME: PushStatusText called without text")
840
841    def add_perspective(self, plugin):
842        """
843        Add a perspective if it doesn't already
844        exist.
845        """
846        self._num_perspectives += 1
847        is_loaded = False
848        for item in self.plugins:
849            item.set_batch_selection(self.batch_on)
850            if plugin.__class__ == item.__class__:
851                msg = "Plugin %s already loaded" % plugin.sub_menu
852                logging.info(msg)
853                is_loaded = True
854        if not is_loaded:
855            self.plugins.append(plugin)
856            msg = "Plugin %s appended" % plugin.sub_menu
857            logging.info(msg)
858
859    def _get_local_plugins(self):
860        """
861        get plugins local to guiframe and others
862        """
863        plugins = []
864        #import guiframe local plugins
865        #check if the style contain guiframe.dataloader
866        style1 = self.__gui_style & GUIFRAME.DATALOADER_ON
867        style2 = self.__gui_style & GUIFRAME.PLOTTING_ON
868        if style1 == GUIFRAME.DATALOADER_ON:
869            try:
870                from sas.guiframe.local_perspectives.data_loader import data_loader
871                self._data_plugin = data_loader.Plugin()
872                plugins.append(self._data_plugin)
873            except:
874                msg = "ViewerFrame._get_local_plugins:"
875                msg += "cannot import dataloader plugin.\n %s" % sys.exc_value
876                logging.error(msg)
877        if style2 == GUIFRAME.PLOTTING_ON:
878            try:
879                from sas.guiframe.local_perspectives.plotting import plotting
880                self._plotting_plugin = plotting.Plugin()
881                plugins.append(self._plotting_plugin)
882            except:
883                msg = "ViewerFrame._get_local_plugins:"
884                msg += "cannot import plotting plugin.\n %s" % sys.exc_value
885                logging.error(msg)
886
887        return plugins
888
889    def _find_plugins(self, dir="perspectives"):
890        """
891        Find available perspective plug-ins
892
893        :param dir: directory in which to look for plug-ins
894
895        :return: list of plug-ins
896
897        """
898        plugins = []
899        # Go through files in panels directory
900        try:
901            if os.path.isdir(dir):
902                file_list = os.listdir(dir)
903            else:
904                file_list = []
905            ## the default panel is the panel is the last plugin added
906            for item in file_list:
907                toks = os.path.splitext(os.path.basename(item))
908                name = ''
909                if not toks[0] == '__init__':
910                    if toks[1] == '.py' or toks[1] == '':
911                        name = toks[0]
912                    #check the validity of the module name parsed
913                    #before trying to import it
914                    if name is None or name.strip() == '':
915                        continue
916                    path = [os.path.abspath(dir)]
917                    file = None
918                    try:
919                        if toks[1] == '':
920                            mod_path = '.'.join([dir, name])
921                            module = __import__(mod_path, globals(),
922                                                locals(), [name])
923                        else:
924                            (file, path, info) = imp.find_module(name, path)
925                            module = imp.load_module(name, file, item, info)
926                        if hasattr(module, "PLUGIN_ID"):
927                            try:
928                                plugins.append(module.Plugin())
929                                msg = "Found plug-in: %s" % module.PLUGIN_ID
930                                logging.info(msg)
931                            except:
932                                msg = "Error accessing PluginPanel"
933                                msg += " in %s\n  %s" % (name, sys.exc_value)
934                                config.printEVT(msg)
935                    except:
936                        msg = "ViewerFrame._find_plugins: %s" % sys.exc_value
937                        logging.error(msg)
938                    finally:
939                        if not file == None:
940                            file.close()
941        except:
942            # Should raise and catch at a higher level and
943            # display error on status bar
944            logging.error(sys.exc_value)
945
946        return plugins
947
948    def _get_panels_size(self, p):
949        """
950        find the proper size of the current panel
951        get the proper panel width and height
952        """
953        self._window_width, self._window_height = self.get_client_size()
954        ## Default size
955        if DATAPANEL_WIDTH < 0:
956            panel_width = int(self._window_width * 0.25)
957        else:
958            panel_width = DATAPANEL_WIDTH
959        panel_height = int(self._window_height)
960        if self._data_panel is not None  and (p == self._data_panel):
961            return panel_width, panel_height
962        if hasattr(p, "CENTER_PANE") and p.CENTER_PANE:
963            panel_width = self._window_width * 0.45
964            if CONTROL_WIDTH > 0:
965                panel_width = CONTROL_WIDTH
966            if CONTROL_HEIGHT > 0:
967                panel_height = CONTROL_HEIGHT
968            return panel_width, panel_height
969        elif p == self.defaultPanel:
970            return self._window_width, panel_height
971        return panel_width, panel_height
972
973    def _load_panels(self):
974        """
975        Load all panels in the panels directory
976        """
977        # Look for plug-in panels
978        panels = []
979        if wx.VERSION_STRING >= '3.0.0.0':
980            mac_pos_y = 85
981        else:
982            mac_pos_y = 40
983        for item in self.plugins:
984            if hasattr(item, "get_panels"):
985                ps = item.get_panels(self)
986                panels.extend(ps)
987
988        # Set up welcome panel
989        #TODO: this needs serious simplification
990        if self.welcome_panel_class is not None:
991            welcome_panel = MDIFrame(self, None, 'None', (100, 200))
992            self.defaultPanel = self.welcome_panel_class(welcome_panel, -1, style=wx.RAISED_BORDER)
993            welcome_panel.set_panel(self.defaultPanel)
994            self.defaultPanel.set_frame(welcome_panel)
995            welcome_panel.Show(False)
996
997        self.panels["default"] = self.defaultPanel
998        size_t_bar = 70
999        if IS_LINUX:
1000            size_t_bar = 115
1001        if self.defaultPanel is not None:
1002            w, h = self._get_panels_size(self.defaultPanel)
1003            frame = self.defaultPanel.get_frame()
1004            frame.SetSize((self._window_width, self._window_height))
1005            if not IS_WIN:
1006                frame.SetPosition((0, mac_pos_y + size_t_bar))
1007            frame.Show(True)
1008        #add data panel
1009        win = MDIFrame(self, None, 'None', (100, 200))
1010        data_panel = DataPanel(parent=win, id=-1)
1011        win.set_panel(data_panel)
1012        self.panels["data_panel"] = data_panel
1013        self._data_panel = data_panel
1014        d_panel_width, h = self._get_panels_size(self._data_panel)
1015        win.SetSize((d_panel_width, h))
1016        is_visible = self.__gui_style & GUIFRAME.MANAGER_ON == GUIFRAME.MANAGER_ON
1017        if IS_WIN:
1018            win.SetPosition((0, 0))
1019        else:
1020            win.SetPosition((0, mac_pos_y + size_t_bar))
1021        win.Show(is_visible)
1022        # Add the panels to the AUI manager
1023        for panel_class in panels:
1024            frame = panel_class.get_frame()
1025            wx_id = wx.NewId()
1026            # Check whether we need to put this panel in the center pane
1027            if hasattr(panel_class, "CENTER_PANE") and panel_class.CENTER_PANE:
1028                w, h = self._get_panels_size(panel_class)
1029                if panel_class.CENTER_PANE:
1030                    self.panels[str(wx_id)] = panel_class
1031                    _, pos_y = frame.GetPositionTuple()
1032                    frame.SetPosition((d_panel_width + 1, pos_y))
1033                    frame.SetSize((w, h))
1034                    frame.Show(False)
1035            elif panel_class == self._data_panel:
1036                panel_class.frame.Show(is_visible)
1037                continue
1038            else:
1039                self.panels[str(wx_id)] = panel_class
1040                frame.SetSize((w, h))
1041                frame.Show(False)
1042            if IS_WIN:
1043                frame.SetPosition((d_panel_width + 1, 0))
1044            else:
1045                frame.SetPosition((d_panel_width + 1, mac_pos_y + size_t_bar))
1046
1047        if not IS_WIN:
1048            win_height = mac_pos_y
1049            if IS_LINUX:
1050                if wx.VERSION_STRING >= '3.0.0.0':
1051                    win_height = mac_pos_y + 10
1052                else:
1053                    win_height = mac_pos_y + 55
1054                self.SetMaxSize((-1, win_height))
1055            else:
1056                self.SetSize((self._window_width, win_height))
1057
1058    def update_data(self, prev_data, new_data):
1059        """
1060        Update the data.
1061        """
1062        prev_id, data_state = self._data_manager.update_data( \
1063                              prev_data=prev_data, new_data=new_data)
1064
1065        self._data_panel.remove_by_id(prev_id)
1066        self._data_panel.load_data_list(data_state)
1067
1068    def update_theory(self, data_id, theory, state=None):
1069        """
1070        Update the theory
1071        """
1072        data_state = self._data_manager.update_theory(data_id=data_id,
1073                                                      theory=theory,
1074                                                      state=state)
1075        wx.CallAfter(self._data_panel.load_data_list, data_state)
1076
1077    def onfreeze(self, theory_id):
1078        """
1079        """
1080        data_state_list = self._data_manager.freeze(theory_id)
1081        self._data_panel.load_data_list(list=data_state_list)
1082        for data_state in data_state_list.values():
1083            new_plot = data_state.get_data()
1084
1085            wx.PostEvent(self, NewPlotEvent(plot=new_plot,
1086                                            title=new_plot.title))
1087
1088    def freeze(self, data_id, theory_id):
1089        """
1090        """
1091        data_state_list = self._data_manager.freeze_theory(data_id=data_id,
1092                                                           theory_id=theory_id)
1093        self._data_panel.load_data_list(list=data_state_list)
1094        for data_state in data_state_list.values():
1095            new_plot = data_state.get_data()
1096            wx.PostEvent(self, NewPlotEvent(plot=new_plot,
1097                                            title=new_plot.title))
1098
1099    def delete_data(self, data):
1100        """
1101        Delete the data.
1102        """
1103        self._current_perspective.delete_data(data)
1104
1105
1106    def get_context_menu(self, plotpanel=None):
1107        """
1108        Get the context menu items made available
1109        by the different plug-ins.
1110        This function is used by the plotting module
1111        """
1112        if plotpanel is None:
1113            return
1114        menu_list = []
1115        for item in self.plugins:
1116            menu_list.extend(item.get_context_menu(plotpanel=plotpanel))
1117        return menu_list
1118
1119    def get_current_context_menu(self, plotpanel=None):
1120        """
1121        Get the context menu items made available
1122        by the current plug-in.
1123        This function is used by the plotting module
1124        """
1125        if plotpanel is None:
1126            return
1127        menu_list = []
1128        item = self._current_perspective
1129        if item != None:
1130            menu_list.extend(item.get_context_menu(plotpanel=plotpanel))
1131        return menu_list
1132
1133    def on_panel_close(self, event):
1134        """
1135        Gets called when the close event for a panel runs.
1136        This will check which panel has been closed and
1137        delete it.
1138        """
1139        frame = event.GetEventObject()
1140        for ID in self.plot_panels.keys():
1141            if self.plot_panels[ID].window_name == frame.name:
1142                self.disable_app_menu(self.plot_panels[ID])
1143                self.delete_panel(ID)
1144                break
1145        self.cpanel_on_focus.SetFocus()
1146
1147
1148    def popup_panel(self, p):
1149        """
1150        Add a panel object to the AUI manager
1151
1152        :param p: panel object to add to the AUI manager
1153
1154        :return: ID of the event associated with the new panel [int]
1155
1156        """
1157        ID = wx.NewId()
1158        self.panels[str(ID)] = p
1159        ## Check and set the size
1160        if PLOPANEL_WIDTH < 0:
1161            p_panel_width = int(self._window_width * 0.45)
1162        else:
1163            p_panel_width = PLOPANEL_WIDTH
1164        p_panel_height = int(p_panel_width * 0.76)
1165        p.frame.SetSize((p_panel_width, p_panel_height))
1166        self.graph_num += 1
1167        if p.window_caption.split()[0] in NOT_SO_GRAPH_LIST:
1168            windowcaption = p.window_caption
1169        else:
1170            windowcaption = 'Graph'
1171        windowname = p.window_name
1172
1173        # Append nummber
1174        captions = self._get_plotpanel_captions()
1175        #FIXME: Fix this aweful loop
1176        while (1):
1177            caption = windowcaption + '%s' % str(self.graph_num)
1178            if caption not in captions:
1179                break
1180            self.graph_num += 1
1181            # protection from forever-loop: max num = 1000
1182            if self.graph_num > 1000:
1183                break
1184        if p.window_caption.split()[0] not in NOT_SO_GRAPH_LIST:
1185            p.window_caption = caption
1186        p.window_name = windowname + str(self.graph_num)
1187
1188        p.frame.SetTitle(p.window_caption)
1189        p.frame.name = p.window_name
1190        if not IS_WIN:
1191            p.frame.Center()
1192            x_pos, _ = p.frame.GetPositionTuple()
1193            p.frame.SetPosition((x_pos, 112))
1194        p.frame.Show(True)
1195
1196        # Register for showing/hiding the panel
1197        wx.EVT_MENU(self, ID, self.on_view)
1198        if p not in self.plot_panels.values() and p.group_id != None:
1199            self.plot_panels[ID] = p
1200            if len(self.plot_panels) == 1:
1201                self.panel_on_focus = p
1202                self.set_panel_on_focus(None)
1203            if self._data_panel is not None and \
1204                self._plotting_plugin is not None:
1205                ind = self._data_panel.cb_plotpanel.FindString('None')
1206                if ind != wx.NOT_FOUND:
1207                    self._data_panel.cb_plotpanel.Delete(ind)
1208                if caption not in self._data_panel.cb_plotpanel.GetItems():
1209                    self._data_panel.cb_plotpanel.Append(str(caption), p)
1210        return ID
1211
1212    def _get_plotpanel_captions(self):
1213        """
1214        Get all the plotpanel cations
1215
1216        : return: list of captions
1217        """
1218        captions = []
1219        for Id in self.plot_panels.keys():
1220            captions.append(self.plot_panels[Id].window_caption)
1221
1222        return captions
1223
1224    def _setup_tool_bar(self):
1225        """
1226        add toolbar to the frame
1227        """
1228        self._toolbar = GUIToolBar(self)
1229        # The legacy code doesn't work well for wx 3.0
1230        # but the old code produces better results with wx 2.8
1231        if not IS_WIN and wx.VERSION_STRING >= '3.0.0.0':
1232            sizer = wx.BoxSizer(wx.VERTICAL)
1233            sizer.Add(self._toolbar, 0, wx.EXPAND)
1234            self.SetSizer(sizer)
1235        else:
1236            self.SetToolBar(self._toolbar)
1237        self._update_toolbar_helper()
1238        self._on_toggle_toolbar(event=None)
1239
1240    def _update_toolbar_helper(self):
1241        """
1242        Helping to update the toolbar
1243        """
1244        application_name = 'No Selected Analysis'
1245        panel_name = 'No Panel on Focus'
1246        c_panel = self.cpanel_on_focus
1247        if self._toolbar is  None:
1248            return
1249        if c_panel is not None:
1250            self.reset_bookmark_menu(self.cpanel_on_focus)
1251        if self._current_perspective is not None:
1252            application_name = self._current_perspective.sub_menu
1253        c_panel_state = c_panel
1254        if c_panel is not None:
1255            panel_name = c_panel.window_caption
1256            if not c_panel.IsShownOnScreen():
1257                c_panel_state = None
1258        self._toolbar.update_toolbar(c_panel_state)
1259        self._toolbar.update_button(application_name=application_name,
1260                                    panel_name=panel_name)
1261        self._toolbar.Realize()
1262
1263    def _add_menu_tool(self):
1264        """
1265        Tools menu
1266        Go through plug-ins and find tools to populate the tools menu
1267        """
1268        style = self.__gui_style & GUIFRAME.CALCULATOR_ON
1269        if style == GUIFRAME.CALCULATOR_ON:
1270            self._tool_menu = None
1271            for item in self.plugins:
1272                if hasattr(item, "get_tools"):
1273                    for tool in item.get_tools():
1274                        # Only create a menu if we have at least one tool
1275                        if self._tool_menu is None:
1276                            self._tool_menu = wx.Menu()
1277                        if tool[0].lower().count('python') > 0:
1278                            self._tool_menu.AppendSeparator()
1279                        id = wx.NewId()
1280                        self._tool_menu.Append(id, tool[0], tool[1])
1281                        wx.EVT_MENU(self, id, tool[2])
1282            if self._tool_menu is not None:
1283                self._menubar.Append(self._tool_menu, '&Tool')
1284
1285    def _add_current_plugin_menu(self):
1286        """
1287        add current plugin menu
1288        Look for plug-in menus
1289        Add available plug-in sub-menus.
1290        """
1291        if self._menubar is None or self._current_perspective is None \
1292            or self._menubar.GetMenuCount() == 0:
1293            return
1294        #replace or add a new menu for the current plugin
1295        pos = self._menubar.FindMenu(str(self._applications_menu_name))
1296        if pos == -1 and self._applications_menu_pos > 0:
1297            pos = self._applications_menu_pos
1298        if pos != -1:
1299            menu_list = self._current_perspective.populate_menu(self)
1300            if menu_list:
1301                for (menu, name) in menu_list:
1302                    self._menubar.Replace(pos, menu, name)
1303                    self._applications_menu_name = name
1304                self._applications_menu_pos = pos
1305            else:
1306                self._menubar.Remove(pos)
1307                self._applications_menu_name = None
1308                self._applications_menu_pos = -1
1309        else:
1310            menu_list = self._current_perspective.populate_menu(self)
1311            if menu_list:
1312                for (menu, name) in menu_list:
1313                    if self._applications_menu_pos == -1:
1314                        # Find the Help position and insert just before it if possible
1315                        help_pos = self._menubar.FindMenu("Help")
1316                        if help_pos == -1:
1317                            self._menubar.Append(menu, name)
1318                            self._applications_menu_pos = -1
1319                        else:
1320                            self._menubar.Insert(help_pos-1, menu, name)
1321                            self._applications_menu_pos = help_pos - 1
1322                    else:
1323                        self._menubar.Insert(self._applications_menu_pos, menu, name)
1324                    self._applications_menu_name = name
1325
1326    def _add_help_menu(self):
1327        """
1328        add help menu to menu bar.  Includes welcome page, about page,
1329        tutorial PDF and documentation pages.
1330        """
1331        # Help menu
1332        self._help_menu = wx.Menu()
1333        style = self.__gui_style & GUIFRAME.WELCOME_PANEL_ON
1334
1335        if style == GUIFRAME.WELCOME_PANEL_ON or custom_config != None:
1336            # add the welcome panel menu item
1337            if config.WELCOME_PANEL_ON and self.defaultPanel is not None:
1338                wx_id = wx.NewId()
1339                self._help_menu.Append(wx_id, '&Welcome', '')
1340                wx.EVT_MENU(self, wx_id, self.show_welcome_panel)
1341
1342        self._help_menu.AppendSeparator()
1343        wx_id = wx.NewId()
1344        self._help_menu.Append(wx_id, '&Documentation', '')
1345        wx.EVT_MENU(self, wx_id, self._onSphinxDocs)
1346
1347        if config._do_tutorial and (IS_WIN or sys.platform == 'darwin'):
1348            self._help_menu.AppendSeparator()
1349            wx_id = wx.NewId()
1350            self._help_menu.Append(wx_id, '&Tutorial', 'Software tutorial')
1351            wx.EVT_MENU(self, wx_id, self._onTutorial)
1352
1353        if config._do_acknowledge:
1354            self._help_menu.AppendSeparator()
1355            wx_id = wx.NewId()
1356            self._help_menu.Append(wx_id, '&Acknowledge', 'Acknowledging SasView')
1357            wx.EVT_MENU(self, wx_id, self._onAcknowledge)
1358
1359        if config._do_aboutbox:
1360            self._help_menu.AppendSeparator()
1361            self._help_menu.Append(wx.ID_ABOUT, '&About', 'Software information')
1362            wx.EVT_MENU(self, wx.ID_ABOUT, self._onAbout)
1363
1364        # Checking for updates
1365        wx_id = wx.NewId()
1366        self._help_menu.Append(wx_id, '&Check for update',
1367                               'Check for the latest version of %s' % config.__appname__)
1368        wx.EVT_MENU(self, wx_id, self._check_update)
1369        self._menubar.Append(self._help_menu, '&Help')
1370
1371    def _add_menu_view(self):
1372        """
1373        add menu items under view menu
1374        """
1375        if not VIEW_MENU:
1376            return
1377        self._view_menu = wx.Menu()
1378
1379        wx_id = wx.NewId()
1380        hint = "Display the Grid Window for batch results etc."
1381        self._view_menu.Append(wx_id, '&Show Grid Window', hint)
1382        wx.EVT_MENU(self, wx_id, self.show_batch_frame)
1383
1384        self._view_menu.AppendSeparator()
1385        style = self.__gui_style & GUIFRAME.MANAGER_ON
1386        wx_id = wx.NewId()
1387        self._data_panel_menu = self._view_menu.Append(wx_id,
1388                                                       '&Show Data Explorer', '')
1389        wx.EVT_MENU(self, wx_id, self.show_data_panel)
1390        if style == GUIFRAME.MANAGER_ON:
1391            self._data_panel_menu.SetText('Hide Data Explorer')
1392        else:
1393            self._data_panel_menu.SetText('Show Data Explorer')
1394
1395        self._view_menu.AppendSeparator()
1396        wx_id = wx.NewId()
1397        style1 = self.__gui_style & GUIFRAME.TOOLBAR_ON
1398        if style1 == GUIFRAME.TOOLBAR_ON:
1399            self._toolbar_menu = self._view_menu.Append(wx_id, '&Hide Toolbar', '')
1400        else:
1401            self._toolbar_menu = self._view_menu.Append(wx_id, '&Show Toolbar', '')
1402        wx.EVT_MENU(self, wx_id, self._on_toggle_toolbar)
1403
1404        if custom_config != None:
1405            self._view_menu.AppendSeparator()
1406            wx_id = wx.NewId()
1407            hint_ss = "Select the current/default configuration "
1408            hint_ss += "as a startup setting"
1409            preference_menu = self._view_menu.Append(wx_id, 'Startup Setting',
1410                                                     hint_ss)
1411            wx.EVT_MENU(self, wx_id, self._on_preference_menu)
1412
1413        wx_id = wx.NewId()
1414        self._view_menu.AppendSeparator()
1415        self._view_menu.Append(wx_id, 'Category Manager', 'Edit model categories')
1416        wx.EVT_MENU(self, wx_id, self._on_category_manager)
1417
1418        self._menubar.Append(self._view_menu, '&View')
1419
1420    def show_batch_frame(self, event=None):
1421        """
1422        show the grid of result
1423        """
1424        # Show(False) before Show(True) in order to bring it to the front
1425        self.batch_frame.Show(False)
1426        self.batch_frame.Show(True)
1427
1428    def  on_category_panel(self, event):
1429        """
1430        On cat panel
1431        """
1432        self._on_category_manager(event)
1433
1434    def _on_category_manager(self, event):
1435        """
1436        Category manager frame
1437        """
1438        frame = CategoryManager(self, -1, 'Model Category Manager')
1439        icon = self.GetIcon()
1440        frame.SetIcon(icon)
1441
1442    def _on_preference_menu(self, event):
1443        """
1444        Build a panel to allow to edit Mask
1445        """
1446        from sas.guiframe.startup_configuration \
1447        import StartupConfiguration as ConfDialog
1448
1449        dialog = ConfDialog(parent=self, gui=self.__gui_style)
1450        result = dialog.ShowModal()
1451        if result == wx.ID_OK:
1452            dialog.write_custom_config()
1453            # post event for info
1454            wx.PostEvent(self, StatusEvent(status="Wrote custom configuration", info='info'))
1455        dialog.Destroy()
1456
1457    def _add_menu_application(self):
1458        """
1459        # Attach a menu item for each defined perspective or application.
1460        # Only add the perspective menu if there are more than one perspectives
1461        add menu application
1462        """
1463        if self._num_perspectives > 1:
1464            plug_data_count = False
1465            plug_no_data_count = False
1466            self._applications_menu = wx.Menu()
1467            pos = 0
1468            separator = self._applications_menu.AppendSeparator()
1469            for plug in self.plugins:
1470                if len(plug.get_perspective()) > 0:
1471                    id = wx.NewId()
1472                    if plug.use_data():
1473                        self._applications_menu.InsertCheckItem(pos, id, plug.sub_menu, \
1474                            "Switch to analysis: %s" % plug.sub_menu)
1475                        plug_data_count = True
1476                        pos += 1
1477                    else:
1478                        plug_no_data_count = True
1479                        self._applications_menu.AppendCheckItem(id, plug.sub_menu, \
1480                            "Switch to analysis: %s" % plug.sub_menu)
1481                    wx.EVT_MENU(self, id, plug.on_perspective)
1482
1483            if not plug_data_count or not plug_no_data_count:
1484                self._applications_menu.RemoveItem(separator)
1485            self._menubar.Append(self._applications_menu, '&Analysis')
1486            self._check_applications_menu()
1487
1488    def _populate_file_menu(self):
1489        """
1490        Insert menu item under file menu
1491        """
1492        for plugin in self.plugins:
1493            if len(plugin.populate_file_menu()) > 0:
1494                for item in plugin.populate_file_menu():
1495                    m_name, m_hint, m_handler = item
1496                    id = wx.NewId()
1497                    self._file_menu.Append(id, m_name, m_hint)
1498                    wx.EVT_MENU(self, id, m_handler)
1499                self._file_menu.AppendSeparator()
1500
1501        style1 = self.__gui_style & GUIFRAME.MULTIPLE_APPLICATIONS
1502        if OPEN_SAVE_MENU:
1503            id = wx.NewId()
1504            hint_load_file = "read all analysis states saved previously"
1505            self._save_appl_menu = self._file_menu.Append(id, '&Open Project', hint_load_file)
1506            wx.EVT_MENU(self, id, self._on_open_state_project)
1507
1508        if style1 == GUIFRAME.MULTIPLE_APPLICATIONS:
1509            # some menu of plugin to be seen under file menu
1510            hint_load_file = "Read a status files and load"
1511            hint_load_file += " them into the analysis"
1512            id = wx.NewId()
1513            self._save_appl_menu = self._file_menu.Append(id,
1514                                                          '&Open Analysis', hint_load_file)
1515            wx.EVT_MENU(self, id, self._on_open_state_application)
1516        if OPEN_SAVE_MENU:
1517            self._file_menu.AppendSeparator()
1518            id = wx.NewId()
1519            self._file_menu.Append(id, '&Save Project',
1520                                   'Save the state of the whole analysis')
1521            wx.EVT_MENU(self, id, self._on_save_project)
1522        if style1 == GUIFRAME.MULTIPLE_APPLICATIONS:
1523            id = wx.NewId()
1524            self._save_appl_menu = self._file_menu.Append(id, \
1525                '&Save Analysis', 'Save state of the current active analysis panel')
1526            wx.EVT_MENU(self, id, self._on_save_application)
1527        if not sys.platform == 'darwin':
1528            self._file_menu.AppendSeparator()
1529            id = wx.NewId()
1530            self._file_menu.Append(id, '&Quit', 'Exit')
1531            wx.EVT_MENU(self, id, self.Close)
1532
1533    def _add_menu_file(self):
1534        """
1535        add menu file
1536        """
1537        # File menu
1538        self._file_menu = wx.Menu()
1539        # Add sub menus
1540        self._menubar.Append(self._file_menu, '&File')
1541
1542    def _add_menu_edit(self):
1543        """
1544        add menu edit
1545        """
1546        if not EDIT_MENU:
1547            return
1548        # Edit Menu
1549        self._edit_menu = wx.Menu()
1550        self._edit_menu.Append(GUIFRAME_ID.UNDO_ID, '&Undo',
1551                               'Undo the previous action')
1552        wx.EVT_MENU(self, GUIFRAME_ID.UNDO_ID, self.on_undo_panel)
1553        self._edit_menu.Append(GUIFRAME_ID.REDO_ID, '&Redo',
1554                               'Redo the previous action')
1555        wx.EVT_MENU(self, GUIFRAME_ID.REDO_ID, self.on_redo_panel)
1556        self._edit_menu.AppendSeparator()
1557        self._edit_menu.Append(GUIFRAME_ID.COPY_ID, '&Copy Params',
1558                               'Copy parameter values')
1559        wx.EVT_MENU(self, GUIFRAME_ID.COPY_ID, self.on_copy_panel)
1560        self._edit_menu.Append(GUIFRAME_ID.PASTE_ID, '&Paste Params',
1561                               'Paste parameter values')
1562        wx.EVT_MENU(self, GUIFRAME_ID.PASTE_ID, self.on_paste_panel)
1563
1564        self._edit_menu.AppendSeparator()
1565
1566        self._edit_menu_copyas = wx.Menu()
1567        #Sub menu for Copy As...
1568        self._edit_menu_copyas.Append(GUIFRAME_ID.COPYEX_ID, 'Copy current tab to Excel',
1569                                      'Copy parameter values in tabular format')
1570        wx.EVT_MENU(self, GUIFRAME_ID.COPYEX_ID, self.on_copy_panel)
1571
1572        self._edit_menu_copyas.Append(GUIFRAME_ID.COPYLAT_ID, 'Copy current tab to LaTeX',
1573                                      'Copy parameter values in tabular format')
1574        wx.EVT_MENU(self, GUIFRAME_ID.COPYLAT_ID, self.on_copy_panel)
1575
1576
1577        self._edit_menu.AppendMenu(GUIFRAME_ID.COPYAS_ID, 'Copy Params as...',
1578                                   self._edit_menu_copyas,
1579                                   'Copy parameter values in various formats')
1580
1581        self._edit_menu.AppendSeparator()
1582
1583        self._edit_menu.Append(GUIFRAME_ID.PREVIEW_ID, '&Report Results',
1584                               'Preview current panel')
1585        wx.EVT_MENU(self, GUIFRAME_ID.PREVIEW_ID, self.on_preview_panel)
1586
1587        self._edit_menu.Append(GUIFRAME_ID.RESET_ID, '&Reset Page',
1588                               'Reset current panel')
1589        wx.EVT_MENU(self, GUIFRAME_ID.RESET_ID, self.on_reset_panel)
1590
1591        self._menubar.Append(self._edit_menu, '&Edit')
1592        self.enable_edit_menu()
1593
1594    def get_style(self):
1595        """
1596        Return the gui style
1597        """
1598        return  self.__gui_style
1599
1600    def _add_menu_data(self):
1601        """
1602        Add menu item item data to menu bar
1603        """
1604        if self._data_plugin is not None:
1605            menu_list = self._data_plugin.populate_menu(self)
1606            if menu_list:
1607                for (menu, name) in menu_list:
1608                    self._menubar.Append(menu, name)
1609
1610    def _on_toggle_toolbar(self, event=None):
1611        """
1612        hide or show toolbar
1613        """
1614        if self._toolbar is None:
1615            return
1616        if self._toolbar.IsShown():
1617            if self._toolbar_menu is not None:
1618                self._toolbar_menu.SetItemLabel('Show Toolbar')
1619            self._toolbar.Hide()
1620        else:
1621            if self._toolbar_menu is not None:
1622                self._toolbar_menu.SetItemLabel('Hide Toolbar')
1623            self._toolbar.Show()
1624        self._toolbar.Realize()
1625
1626    def _on_status_event(self, evt):
1627        """
1628        Display status message
1629        """
1630        # This CallAfter fixes many crashes on MAC.
1631        wx.CallAfter(self.sb.set_status, evt)
1632
1633    def on_view(self, evt):
1634        """
1635        A panel was selected to be shown. If it's not already
1636        shown, display it.
1637
1638        :param evt: menu event
1639
1640        """
1641        panel_id = str(evt.GetId())
1642        self.on_set_plot_focus(self.panels[panel_id])
1643        wx.CallLater(5 * TIME_FACTOR, self.set_schedule(True))
1644        self.set_plot_unfocus()
1645
1646    def show_welcome_panel(self, event):
1647        """
1648        Display the welcome panel
1649        """
1650        if self.defaultPanel is None:
1651            return
1652        frame = self.panels['default'].get_frame()
1653        if frame == None:
1654            return
1655        # Show default panel
1656        if not frame.IsShown():
1657            frame.Show(True)
1658
1659    def on_close_welcome_panel(self):
1660        """
1661        Close the welcome panel
1662        """
1663        if self.defaultPanel is None:
1664            return
1665        default_panel = self.panels["default"].frame
1666        if default_panel.IsShown():
1667            default_panel.Show(False)
1668
1669    def delete_panel(self, uid):
1670        """
1671        delete panel given uid
1672        """
1673        ID = str(uid)
1674        config.printEVT("delete_panel: %s" % ID)
1675        if ID in self.panels.keys():
1676            self.panel_on_focus = None
1677            panel = self.panels[ID]
1678
1679            if hasattr(panel, "connect"):
1680                panel.connect.disconnect()
1681            self._plotting_plugin.delete_panel(panel.group_id)
1682
1683            if panel in self.schedule_full_draw_list:
1684                self.schedule_full_draw_list.remove(panel)
1685
1686            #delete uid number not str(uid)
1687            if ID in self.plot_panels.keys():
1688                del self.plot_panels[ID]
1689            if ID in self.panels.keys():
1690                del self.panels[ID]
1691        else:
1692            logging.error("delete_panel: No such plot id as %s" % ID)
1693
1694    def create_gui_data(self, data, path=None):
1695        """
1696        """
1697        return self._data_manager.create_gui_data(data, path)
1698
1699    def get_data(self, path):
1700        """
1701        """
1702        message = ""
1703        log_msg = ''
1704        output = []
1705        error_message = ""
1706        basename = os.path.basename(path)
1707        root, extension = os.path.splitext(basename)
1708        if extension.lower() not in EXTENSIONS:
1709            log_msg = "File Loader cannot "
1710            log_msg += "load: %s\n" % str(basename)
1711            log_msg += "Try Data opening...."
1712            logging.error(log_msg)
1713            return
1714
1715        #reading a state file
1716        for plug in self.plugins:
1717            reader, ext = plug.get_extensions()
1718            if reader is not None:
1719                #read the state of the single plugin
1720                if extension == ext:
1721                    reader.read(path)
1722                    return
1723                elif extension == APPLICATION_STATE_EXTENSION:
1724                    try:
1725                        reader.read(path)
1726                    except:
1727                        msg = "DataLoader Error: Encounted Non-ASCII character"
1728                        msg += "\n(%s)" % sys.exc_value
1729                        wx.PostEvent(self, StatusEvent(status=msg,
1730                                                       info="error", type="stop"))
1731                        return
1732
1733        style = self.__gui_style & GUIFRAME.MANAGER_ON
1734        if style == GUIFRAME.MANAGER_ON:
1735            if self._data_panel is not None:
1736                self._data_panel.frame.Show(True)
1737
1738    def load_from_cmd(self, path):
1739        """
1740        load data from cmd or application
1741        """
1742        if path is None:
1743            return
1744        else:
1745            path = os.path.abspath(path)
1746            if not os.path.isfile(path) and not os.path.isdir(path):
1747                return
1748
1749            if os.path.isdir(path):
1750                self.load_folder(path)
1751                return
1752
1753        basename = os.path.basename(path)
1754        _, extension = os.path.splitext(basename)
1755        if extension.lower() not in EXTENSIONS:
1756            self.load_data(path)
1757        else:
1758            self.load_state(path)
1759
1760        self._default_save_location = os.path.dirname(path)
1761
1762    def load_state(self, path, is_project=False):
1763        """
1764        load data from command line or application
1765        """
1766        if path and (path is not None) and os.path.isfile(path):
1767            basename = os.path.basename(path)
1768            if APPLICATION_STATE_EXTENSION is not None \
1769                and basename.endswith(APPLICATION_STATE_EXTENSION):
1770                if is_project:
1771                    for ID in self.plot_panels.keys():
1772                        panel = self.plot_panels[ID]
1773                        panel.on_close(None)
1774            self.get_data(path)
1775            wx.PostEvent(self, StatusEvent(status="Completed loading."))
1776        else:
1777            wx.PostEvent(self, StatusEvent(status=" "))
1778
1779    def load_data(self, path):
1780        """
1781        load data from command line
1782        """
1783        if not os.path.isfile(path):
1784            return
1785        basename = os.path.basename(path)
1786        _, extension = os.path.splitext(basename)
1787        if extension.lower() in EXTENSIONS:
1788            log_msg = "Data Loader cannot "
1789            log_msg += "load: %s\n" % str(path)
1790            log_msg += "Try File opening ...."
1791            logging.error(log_msg)
1792            return
1793        log_msg = ''
1794        output = {}
1795        error_message = ""
1796        try:
1797            logging.info("Loading Data...:\n" + str(path) + "\n")
1798            temp = self.loader.load(path)
1799            if temp.__class__.__name__ == "list":
1800                for item in temp:
1801                    data = self.create_gui_data(item, path)
1802                    output[data.id] = data
1803            else:
1804                data = self.create_gui_data(temp, path)
1805                output[data.id] = data
1806
1807            self.add_data(data_list=output)
1808        except:
1809            error_message = "Error while loading"
1810            error_message += " Data from cmd:\n %s\n" % str(path)
1811            error_message += str(sys.exc_value) + "\n"
1812            logging.error(error_message)
1813
1814    def load_folder(self, path):
1815        """
1816        Load entire folder
1817        """
1818        if not os.path.isdir(path):
1819            return
1820        if self._data_plugin is None:
1821            return
1822        try:
1823            if path is not None:
1824                self._default_save_location = os.path.dirname(path)
1825                file_list = self._data_plugin.get_file_path(path)
1826                self._data_plugin.get_data(file_list)
1827            else:
1828                return
1829        except:
1830            error_message = "Error while loading"
1831            error_message += " Data folder from cmd:\n %s\n" % str(path)
1832            error_message += str(sys.exc_value) + "\n"
1833            logging.error(error_message)
1834
1835    def _on_open_state_application(self, event):
1836        """
1837        """
1838        path = None
1839        if self._default_save_location == None:
1840            self._default_save_location = os.getcwd()
1841        wx.PostEvent(self, StatusEvent(status="Loading Analysis file..."))
1842        plug_wlist = self._on_open_state_app_helper()
1843        dlg = wx.FileDialog(self,
1844                            "Choose a file",
1845                            self._default_save_location, "",
1846                            plug_wlist)
1847        if dlg.ShowModal() == wx.ID_OK:
1848            path = dlg.GetPath()
1849            if path is not None:
1850                self._default_save_location = os.path.dirname(path)
1851        dlg.Destroy()
1852        self.load_state(path=path)
1853
1854    def _on_open_state_app_helper(self):
1855        """
1856        Helps '_on_open_state_application()' to find the extension of
1857        the current perspective/application
1858        """
1859        # No current perspective or no extension attr
1860        if self._current_perspective is None:
1861            return PLUGINS_WLIST
1862        try:
1863            # Find the extension of the perspective
1864            # and get that as 1st item in list
1865            ind = None
1866            app_ext = self._current_perspective._extensions
1867            plug_wlist = config.PLUGINS_WLIST
1868            for ext in set(plug_wlist):
1869                if ext.count(app_ext) > 0:
1870                    ind = ext
1871                    break
1872            # Found the extension
1873            if ind != None:
1874                plug_wlist.remove(ind)
1875                plug_wlist.insert(0, ind)
1876                try:
1877                    plug_wlist = '|'.join(plug_wlist)
1878                except:
1879                    plug_wlist = ''
1880
1881        except:
1882            plug_wlist = PLUGINS_WLIST
1883
1884        return plug_wlist
1885
1886    def _on_open_state_project(self, event):
1887        """
1888        """
1889        path = None
1890        if self._default_save_location == None:
1891            self._default_save_location = os.getcwd()
1892        wx.PostEvent(self, StatusEvent(status="Loading Project file..."))
1893        dlg = wx.FileDialog(self,
1894                            "Choose a file",
1895                            self._default_save_location, "",
1896                            APPLICATION_WLIST)
1897        if dlg.ShowModal() == wx.ID_OK:
1898            path = dlg.GetPath()
1899            if path is not None:
1900                self._default_save_location = os.path.dirname(path)
1901        dlg.Destroy()
1902
1903        self.load_state(path=path, is_project=True)
1904
1905    def _on_save_application(self, event):
1906        """
1907        save the state of the current active application
1908        """
1909        if self.cpanel_on_focus is not None:
1910            try:
1911                wx.PostEvent(self,
1912                             StatusEvent(status="Saving Analysis file..."))
1913                self.cpanel_on_focus.on_save(event)
1914                wx.PostEvent(self,
1915                             StatusEvent(status="Completed saving."))
1916            except:
1917                msg = "Error occurred while saving: "
1918                msg += "To save, the application panel should have a data set.."
1919                wx.PostEvent(self, StatusEvent(status=msg))
1920
1921    def _on_save_project(self, event):
1922        """
1923        save the state of the SasView as *.svs
1924        """
1925        if self._current_perspective is  None:
1926            return
1927        wx.PostEvent(self, StatusEvent(status="Saving Project file..."))
1928        path = None
1929        extension = '*' + APPLICATION_STATE_EXTENSION
1930        dlg = wx.FileDialog(self, "Save Project file",
1931                            self._default_save_location, "sasview_proj",
1932                            extension,
1933                            wx.SAVE)
1934        if dlg.ShowModal() == wx.ID_OK:
1935            path = dlg.GetPath()
1936            self._default_save_location = os.path.dirname(path)
1937        else:
1938            return None
1939        dlg.Destroy()
1940        try:
1941            if path is None:
1942                return
1943            # default cansas xml doc
1944            doc = None
1945            for panel in self.panels.values():
1946                temp = panel.save_project(doc)
1947                if temp is not None:
1948                    doc = temp
1949
1950            # Write the XML document
1951            extens = APPLICATION_STATE_EXTENSION
1952            fName = os.path.splitext(path)[0] + extens
1953            if doc != None:
1954                fd = open(fName, 'w')
1955                fd.write(doc.toprettyxml())
1956                fd.close()
1957                wx.PostEvent(self, StatusEvent(status="Completed Saving."))
1958            else:
1959                msg = "Error occurred while saving the project: "
1960                msg += "To save, at least one application panel "
1961                msg += "should have a data set "
1962                msg += "and model selected. "
1963                msg += "No project was saved to %s" % (str(path))
1964                logging.warning(msg)
1965                wx.PostEvent(self, StatusEvent(status=msg, info="error"))
1966        except:
1967            msg = "Error occurred while saving: "
1968            msg += "To save, at least one application panel "
1969            msg += "should have a data set.."
1970            wx.PostEvent(self, StatusEvent(status=msg, info="error"))
1971
1972    def on_save_helper(self, doc, reader, panel, path):
1973        """
1974        Save state into a file
1975        """
1976        if reader is not None:
1977            # case of a panel with multi-pages
1978            if hasattr(panel, "opened_pages"):
1979                for _, page in panel.opened_pages.iteritems():
1980                    data = page.get_data()
1981                    # state must be cloned
1982                    state = page.get_state().clone()
1983                    if data is not None:
1984                        new_doc = reader.write_toXML(data, state)
1985                        if doc != None and hasattr(doc, "firstChild"):
1986                            child = new_doc.firstChild.firstChild
1987                            doc.firstChild.appendChild(child)
1988                        else:
1989                            doc = new_doc
1990            # case of only a panel
1991            else:
1992                data = panel.get_data()
1993                state = panel.get_state()
1994                if data is not None:
1995                    new_doc = reader.write_toXML(data, state)
1996                    if doc != None and hasattr(doc, "firstChild"):
1997                        child = new_doc.firstChild.firstChild
1998                        doc.firstChild.appendChild(child)
1999                    else:
2000                        doc = new_doc
2001        return doc
2002
2003    def quit_guiframe(self):
2004        """
2005        Pop up message to make sure the user wants to quit the application
2006        """
2007        message = "\nDo you really want to exit this application?        \n\n"
2008        dial = wx.MessageDialog(self, message, 'Confirm Exit',
2009                                wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
2010        if dial.ShowModal() == wx.ID_YES:
2011            return True
2012        else:
2013            return False
2014
2015    def WindowClose(self, event=None):
2016        """
2017        Quit the application from x icon
2018        """
2019        flag = self.quit_guiframe()
2020        if flag:
2021            _pylab_helpers.Gcf.figs = {}
2022            self.Close()
2023
2024    def Close(self, event=None):
2025        """
2026        Quit the application
2027        """
2028        logging.info(" --- SasView session was closed --- \n")
2029        wx.Exit()
2030        sys.exit()
2031
2032    def _check_update(self, event=None):
2033        """
2034        Check with the deployment server whether a new version
2035        of the application is available.
2036        A thread is started for the connecting with the server. The thread calls
2037        a call-back method when the current version number has been obtained.
2038        """
2039        try:
2040            req = urllib2.Request('https://github.com/SasView/sasview/releases/latest')
2041            res = urllib2.urlopen(req)
2042            get_url= res.geturl()
2043            content = get_url.partition('/v')[2]
2044            logging.info("connected to GitHub. sasview.latestversion = %s"
2045                         % (content))
2046        except:
2047            msg = traceback.format_exc()
2048            logging.info(msg)
2049            logging.info("failed to connect to GitHub")
2050            content = "0.0.0"
2051
2052        version = content.strip()
2053        logging.info("Latest SasView version number: %s" % (version))
2054        if len(re.findall('\d+\.\d+\.\d+$', version)) < 0:
2055            content = "0.0.0"
2056        self._process_version(content, standalone=event == None)
2057
2058    def _process_version(self, version, standalone=True):
2059        """
2060        Call-back method for the process of checking for updates.
2061        This methods is called by a VersionThread object once the current
2062        version number has been obtained. If the check is being done in the
2063        background, the user will not be notified unless there's an update.
2064
2065        :param version: version string
2066        :param standalone: True of the update is being checked in
2067           the background, False otherwise.
2068
2069        """
2070        try:
2071            if version == "0.0.0":
2072                msg = "Could not connect to the application server."
2073                msg += " Please try again later."
2074                self.SetStatusText(msg)
2075            elif cmp(version, config.__version__) > 0:
2076                msg = "Version %s is available! " % str(version)
2077                if not standalone:
2078                    import webbrowser
2079                    webbrowser.open(config.__download_page__)
2080                else:
2081                    msg += "See the help menu to download it."
2082                self.SetStatusText(msg)
2083            else:
2084                if not standalone:
2085                    msg = "You have the latest version"
2086                    msg += " of %s" % str(config.__appname__)
2087                    self.SetStatusText(msg)
2088        except:
2089            msg = "guiframe: could not get latest application"
2090            msg += " version number\n  %s" % sys.exc_value
2091            logging.error(msg)
2092            if not standalone:
2093                msg = "Could not connect to the application server."
2094                msg += " Please try again later."
2095                self.SetStatusText(msg)
2096
2097    def _onAcknowledge(self, evt):
2098        """
2099        Pop up the acknowledge dialog
2100
2101        :param evt: menu event
2102
2103        """
2104        if config._do_acknowledge:
2105            import sas.guiframe.acknowledgebox as AcknowledgeBox
2106            dialog = AcknowledgeBox.DialogAcknowledge(None, -1, "")
2107            dialog.ShowModal()
2108
2109    def _onAbout(self, evt):
2110        """
2111        Pop up the about dialog
2112
2113        :param evt: menu event
2114
2115        """
2116        if config._do_aboutbox:
2117            import sas.guiframe.aboutbox as AboutBox
2118            dialog = AboutBox.DialogAbout(None, -1, "")
2119            dialog.ShowModal()
2120
2121    def _onTutorial(self, evt):
2122        """
2123        Pop up the tutorial dialog
2124
2125        :param evt: menu event
2126
2127        """
2128        if config._do_tutorial:
2129            path = config.TUTORIAL_PATH
2130            if IS_WIN:
2131                try:
2132                    from sas.guiframe.pdfview import PDFFrame
2133                    dialog = PDFFrame(None, -1, "Tutorial", path)
2134                    # put icon
2135                    self.put_icon(dialog)
2136                    dialog.Show(True)
2137                except:
2138                    logging.error("Error in _onTutorial: %s" % sys.exc_value)
2139                    try:
2140                        # Try an alternate method
2141                        logging.error("Could not open the tutorial pdf, trying xhtml2pdf")
2142                        from xhtml2pdf import pisa
2143                        pisa.startViewer(path)
2144                    except:
2145                        logging.error("Could not open the tutorial pdf with xhtml2pdf")
2146                        msg = "This feature requires 'PDF Viewer'\n"
2147                        wx.MessageBox(msg, 'Error')
2148            else:
2149                try:
2150                    command = "open '%s'" % path
2151                    os.system(command)
2152                except:
2153                    try:
2154                        # Try an alternate method
2155                        logging.error("Could not open the tutorial pdf, trying xhtml2pdf")
2156                        from xhtml2pdf import pisa
2157                        pisa.startViewer(path)
2158                    except:
2159                        logging.error("Could not open the tutorial pdf with xhtml2pdf")
2160                        msg = "This feature requires the 'Preview' application\n"
2161                        wx.MessageBox(msg, 'Error')
2162
2163    def _onSphinxDocs(self, evt):
2164        """
2165        Bring up Sphinx Documentation at top level whenever the menu item
2166        'documentation' is clicked. Calls DocumentationWindow with the top
2167        level path of "index.html"
2168
2169        :param evt: menu event
2170        """
2171        # Running SasView "in-place" using run.py means the docs will be in a
2172        # different place than they would otherwise.
2173        from documentation_window import DocumentationWindow
2174        _TreeLocation = "user/user.html"
2175        DocumentationWindow(self, -1, _TreeLocation, "", "SasView Documentation")
2176
2177    def set_manager(self, manager):
2178        """
2179        Sets the application manager for this frame
2180
2181        :param manager: frame manager
2182        """
2183        self.app_manager = manager
2184
2185    def post_init(self):
2186        """
2187        This initialization method is called after the GUI
2188        has been created and all plug-ins loaded. It calls
2189        the post_init() method of each plug-in (if it exists)
2190        so that final initialization can be done.
2191        """
2192        for item in self.plugins:
2193            if hasattr(item, "post_init"):
2194                item.post_init()
2195
2196    def set_perspective(self, panels):
2197        """
2198        Sets the perspective of the GUI.
2199        Opens all the panels in the list, and closes
2200        all the others.
2201
2202        :param panels: list of panels
2203        """
2204        for item in self.panels.keys():
2205            # Check whether this is a sticky panel
2206            if hasattr(self.panels[item], "ALWAYS_ON"):
2207                if self.panels[item].ALWAYS_ON:
2208                    continue
2209            if self.panels[item] == None:
2210                continue
2211            if self.panels[item].window_name in panels:
2212                frame = self.panels[item].get_frame()
2213                if not frame.IsShown():
2214                    frame.Show(True)
2215            else:
2216                # always show the data panel if enable
2217                style = self.__gui_style & GUIFRAME.MANAGER_ON
2218                if (style == GUIFRAME.MANAGER_ON) and self.panels[item] == self._data_panel:
2219                    if 'data_panel' in self.panels.keys():
2220                        frame = self.panels['data_panel'].get_frame()
2221                        if frame == None:
2222                            continue
2223                        flag = frame.IsShown()
2224                        frame.Show(flag)
2225                else:
2226                    frame = self.panels[item].get_frame()
2227                    if frame == None:
2228                        continue
2229
2230                    if frame.IsShown():
2231                        frame.Show(False)
2232
2233    def show_data_panel(self, event=None, action=True):
2234        """
2235        show the data panel
2236        """
2237        if self._data_panel_menu == None:
2238            return
2239        label = self._data_panel_menu.GetText()
2240        pane = self.panels["data_panel"]
2241        frame = pane.get_frame()
2242        if label == 'Show Data Explorer':
2243            if action:
2244                frame.Show(True)
2245            self.__gui_style = self.__gui_style | GUIFRAME.MANAGER_ON
2246            self._data_panel_menu.SetText('Hide Data Explorer')
2247        else:
2248            if action:
2249                frame.Show(False)
2250            self.__gui_style = self.__gui_style & (~GUIFRAME.MANAGER_ON)
2251            self._data_panel_menu.SetText('Show Data Explorer')
2252
2253    def add_data_helper(self, data_list):
2254        """
2255        """
2256        if self._data_manager is not None:
2257            self._data_manager.add_data(data_list)
2258
2259    def add_data(self, data_list):
2260        """
2261        receive a dictionary of data from loader
2262        store them its data manager if possible
2263        send to data the current active perspective if the data panel
2264        is not active.
2265        :param data_list: dictionary of data's ID and value Data
2266        """
2267        #Store data into manager
2268        self.add_data_helper(data_list)
2269        # set data in the data panel
2270        if self._data_panel is not None:
2271            data_state = self._data_manager.get_data_state(data_list.keys())
2272            self._data_panel.load_data_list(data_state)
2273        #if the data panel is shown wait for the user to press a button
2274        #to send data to the current perspective. if the panel is not
2275        #show  automatically send the data to the current perspective
2276        style = self.__gui_style & GUIFRAME.MANAGER_ON
2277        if style == GUIFRAME.MANAGER_ON:
2278            #wait for button press from the data panel to set_data
2279            if self._data_panel is not None:
2280                self._data_panel.frame.Show(True)
2281        else:
2282            #automatically send that to the current perspective
2283            self.set_data(data_id=data_list.keys())
2284
2285    def set_data(self, data_id, theory_id=None):
2286        """
2287        set data to current perspective
2288        """
2289        list_data, _ = self._data_manager.get_by_id(data_id)
2290        if self._current_perspective is not None:
2291            self._current_perspective.set_data(list_data.values())
2292
2293        else:
2294            msg = "Guiframe does not have a current perspective"
2295            logging.info(msg)
2296
2297    def set_theory(self, state_id, theory_id=None):
2298        """
2299        """
2300        _, list_theory = self._data_manager.get_by_id(theory_id)
2301        if self._current_perspective is not None:
2302            try:
2303                self._current_perspective.set_theory(list_theory.values())
2304            except:
2305                msg = "Guiframe set_theory: \n" + str(sys.exc_value)
2306                logging.info(msg)
2307                wx.PostEvent(self, StatusEvent(status=msg, info="error"))
2308        else:
2309            msg = "Guiframe does not have a current perspective"
2310            logging.info(msg)
2311
2312    def plot_data(self, state_id, data_id=None,
2313                  theory_id=None, append=False):
2314        """
2315        send a list of data to plot
2316        """
2317        data_list, _ = self._data_manager.get_by_id(data_id)
2318        _, temp_list_theory = self._data_manager.get_by_id(theory_id)
2319        total_plot_list = data_list.values()
2320        for item in temp_list_theory.values():
2321            theory_data, theory_state = item
2322            total_plot_list.append(theory_data)
2323        GROUP_ID = wx.NewId()
2324        for new_plot in total_plot_list:
2325            if append:
2326                if self.panel_on_focus is None:
2327                    message = "cannot append plot. No plot panel on focus!"
2328                    message += "please click on any available plot to set focus"
2329                    wx.PostEvent(self, StatusEvent(status=message,
2330                                                   info='warning'))
2331                    return
2332                else:
2333                    if self.enable_add_data(new_plot):
2334                        new_plot.group_id = self.panel_on_focus.group_id
2335                    else:
2336                        message = "Only 1D Data can be append to"
2337                        message += " plot panel containing 1D data.\n"
2338                        message += "%s not be appended.\n" % str(new_plot.name)
2339                        message += "try new plot option.\n"
2340                        wx.PostEvent(self, StatusEvent(status=message,
2341                                                       info='warning'))
2342            else:
2343                #if not append then new plot
2344                from sas.guiframe.dataFitting import Data2D
2345                if issubclass(Data2D, new_plot.__class__):
2346                    #for 2 D always plot in a separated new plot
2347                    new_plot.group_id = wx.NewId()
2348                else:
2349                    # plot all 1D in a new plot
2350                    new_plot.group_id = GROUP_ID
2351            title = "PLOT " + str(new_plot.title)
2352            wx.PostEvent(self, NewPlotEvent(plot=new_plot,
2353                                            title=title,
2354                                            group_id=new_plot.group_id))
2355
2356    def remove_data(self, data_id, theory_id=None):
2357        """
2358        Delete data state if data_id is provide
2359        delete theory created with data of id data_id if theory_id is provide
2360        if delete all true: delete the all state
2361        else delete theory
2362        """
2363        temp = data_id + theory_id
2364        for plug in self.plugins:
2365            plug.delete_data(temp)
2366        data_list, _ = self._data_manager.get_by_id(data_id)
2367        _, temp_list_theory = self._data_manager.get_by_id(theory_id)
2368        total_plot_list = data_list.values()
2369        for item in temp_list_theory.values():
2370            theory_data, theory_state = item
2371            total_plot_list.append(theory_data)
2372        for new_plot in total_plot_list:
2373            id = new_plot.id
2374            for group_id in new_plot.list_group_id:
2375                wx.PostEvent(self, NewPlotEvent(id=id,
2376                                                group_id=group_id,
2377                                                action='remove'))
2378                #remove res plot: Todo: improve
2379                wx.CallAfter(self._remove_res_plot, id)
2380        self._data_manager.delete_data(data_id=data_id,
2381                                       theory_id=theory_id)
2382
2383    def _remove_res_plot(self, id):
2384        """
2385        Try to remove corresponding res plot
2386
2387        : param id: id of the data
2388        """
2389        try:
2390            wx.PostEvent(self, NewPlotEvent(id=("res" + str(id)),
2391                                            group_id=("res" + str(id)),
2392                                            action='remove'))
2393        except:
2394            logging.error(sys.exc_value)
2395
2396    def save_data1d(self, data, fname):
2397        """
2398        Save data dialog
2399        """
2400        default_name = fname
2401        wildcard = "Text files (*.txt)|*.txt|"\
2402                    "CanSAS 1D files(*.xml)|*.xml"
2403        path = None
2404        dlg = wx.FileDialog(self, "Choose a file",
2405                            self._default_save_location,
2406                            default_name, wildcard, wx.SAVE)
2407
2408        if dlg.ShowModal() == wx.ID_OK:
2409            path = dlg.GetPath()
2410            # ext_num = 0 for .txt, ext_num = 1 for .xml
2411            # This is MAC Fix
2412            ext_num = dlg.GetFilterIndex()
2413            if ext_num == 0:
2414                format = '.txt'
2415            else:
2416                format = '.xml'
2417            path = os.path.splitext(path)[0] + format
2418            mypath = os.path.basename(path)
2419
2420            #Instantiate a loader
2421            loader = Loader()
2422            format = ".txt"
2423            if os.path.splitext(mypath)[1].lower() == format:
2424                # Make sure the ext included in the file name
2425                # especially on MAC
2426                fName = os.path.splitext(path)[0] + format
2427                self._onsaveTXT(data, fName)
2428            format = ".xml"
2429            if os.path.splitext(mypath)[1].lower() == format:
2430                # Make sure the ext included in the file name
2431                # especially on MAC
2432                fName = os.path.splitext(path)[0] + format
2433                loader.save(fName, data, format)
2434            try:
2435                self._default_save_location = os.path.dirname(path)
2436            except:
2437                pass
2438        dlg.Destroy()
2439
2440
2441    def _onsaveTXT(self, data, path):
2442        """
2443        Save file as txt
2444        :TODO: Refactor and remove this method. See TODO in _onSave.
2445        """
2446        if not path == None:
2447            out = open(path, 'w')
2448            has_errors = True
2449            if data.dy == None or data.dy == []:
2450                has_errors = False
2451            # Sanity check
2452            if has_errors:
2453                try:
2454                    if len(data.y) != len(data.dy):
2455                        has_errors = False
2456                except:
2457                    has_errors = False
2458            if has_errors:
2459                if data.dx != None and data.dx != []:
2460                    out.write("<X>   <Y>   <dY>   <dX>\n")
2461                else:
2462                    out.write("<X>   <Y>   <dY>\n")
2463            else:
2464                out.write("<X>   <Y>\n")
2465
2466            for i in range(len(data.x)):
2467                if has_errors:
2468                    if data.dx != None and data.dx != []:
2469                        if  data.dx[i] != None:
2470                            out.write("%g  %g  %g  %g\n" % (data.x[i],
2471                                                            data.y[i],
2472                                                            data.dy[i],
2473                                                            data.dx[i]))
2474                        else:
2475                            out.write("%g  %g  %g\n" % (data.x[i],
2476                                                        data.y[i],
2477                                                        data.dy[i]))
2478                    else:
2479                        out.write("%g  %g  %g\n" % (data.x[i],
2480                                                    data.y[i],
2481                                                    data.dy[i]))
2482                else:
2483                    out.write("%g  %g\n" % (data.x[i],
2484                                            data.y[i]))
2485            out.close()
2486
2487    def show_data1d(self, data, name):
2488        """
2489        Show data dialog
2490        """
2491        try:
2492            xmin = min(data.x)
2493            ymin = min(data.y)
2494        except:
2495            msg = "Unable to find min/max of \n data named %s" % \
2496                        data.filename
2497            wx.PostEvent(self, StatusEvent(status=msg,
2498                                           info="error"))
2499            raise ValueError, msg
2500        ## text = str(data)
2501        text = data.__str__()
2502        text += 'Data Min Max:\n'
2503        text += 'X_min = %s:  X_max = %s\n' % (xmin, max(data.x))
2504        text += 'Y_min = %s:  Y_max = %s\n' % (ymin, max(data.y))
2505        if data.dy != None:
2506            text += 'dY_min = %s:  dY_max = %s\n' % (min(data.dy), max(data.dy))
2507        text += '\nData Points:\n'
2508        x_st = "X"
2509        for index in range(len(data.x)):
2510            if data.dy != None and len(data.dy) > index:
2511                dy_val = data.dy[index]
2512            else:
2513                dy_val = 0.0
2514            if data.dx != None and len(data.dx) > index:
2515                dx_val = data.dx[index]
2516            else:
2517                dx_val = 0.0
2518            if data.dxl != None and len(data.dxl) > index:
2519                if index == 0:
2520                    x_st = "Xl"
2521                dx_val = data.dxl[index]
2522            elif data.dxw != None and len(data.dxw) > index:
2523                if index == 0:
2524                    x_st = "Xw"
2525                dx_val = data.dxw[index]
2526
2527            if index == 0:
2528                text += "<index> \t<X> \t<Y> \t<dY> \t<d%s>\n" % x_st
2529            text += "%s \t%s \t%s \t%s \t%s\n" % (index,
2530                                                  data.x[index],
2531                                                  data.y[index],
2532                                                  dy_val,
2533                                                  dx_val)
2534        from pdfview import TextFrame
2535        frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
2536        # put icon
2537        self.put_icon(frame)
2538        frame.Show(True)
2539
2540    def save_data2d(self, data, fname):
2541        """
2542        Save data2d dialog
2543        """
2544        default_name = fname
2545        wildcard = "IGOR/DAT 2D file in Q_map (*.dat)|*.DAT"
2546        dlg = wx.FileDialog(self, "Choose a file",
2547                            self._default_save_location,
2548                            default_name, wildcard, wx.SAVE)
2549
2550        if dlg.ShowModal() == wx.ID_OK:
2551            path = dlg.GetPath()
2552            # ext_num = 0 for .txt, ext_num = 1 for .xml
2553            # This is MAC Fix
2554            ext_num = dlg.GetFilterIndex()
2555            if ext_num == 0:
2556                format = '.dat'
2557            else:
2558                format = ''
2559            path = os.path.splitext(path)[0] + format
2560            mypath = os.path.basename(path)
2561
2562            #Instantiate a loader
2563            loader = Loader()
2564
2565            format = ".dat"
2566            if os.path.splitext(mypath)[1].lower() == format:
2567                # Make sure the ext included in the file name
2568                # especially on MAC
2569                fileName = os.path.splitext(path)[0] + format
2570                loader.save(fileName, data, format)
2571            try:
2572                self._default_save_location = os.path.dirname(path)
2573            except:
2574                pass
2575        dlg.Destroy()
2576
2577    def show_data2d(self, data, name):
2578        """
2579        Show data dialog
2580        """
2581        wx.PostEvent(self, StatusEvent(status="Gathering Data2D Info.",
2582                                       type='start'))
2583        text = data.__str__()
2584        text += 'Data Min Max:\n'
2585        text += 'I_min = %s\n' % min(data.data)
2586        text += 'I_max = %s\n\n' % max(data.data)
2587        text += 'Data (First 2501) Points:\n'
2588        text += 'Data columns include err(I).\n'
2589        text += 'ASCII data starts here.\n'
2590        text += "<index> \t<Qx> \t<Qy> \t<I> \t<dI> \t<dQparal> \t<dQperp>\n"
2591        di_val = 0.0
2592        dx_val = 0.0
2593        dy_val = 0.0
2594        len_data = len(data.qx_data)
2595        for index in xrange(0, len_data):
2596            x_val = data.qx_data[index]
2597            y_val = data.qy_data[index]
2598            i_val = data.data[index]
2599            if data.err_data != None:
2600                di_val = data.err_data[index]
2601            if data.dqx_data != None:
2602                dx_val = data.dqx_data[index]
2603            if data.dqy_data != None:
2604                dy_val = data.dqy_data[index]
2605
2606            text += "%s \t%s \t%s \t%s \t%s \t%s \t%s\n" % (index,
2607                                                            x_val,
2608                                                            y_val,
2609                                                            i_val,
2610                                                            di_val,
2611                                                            dx_val,
2612                                                            dy_val)
2613            # Takes too long time for typical data2d: Break here
2614            if index >= 2500:
2615                text += ".............\n"
2616                break
2617
2618        from pdfview import TextFrame
2619        frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
2620        # put icon
2621        self.put_icon(frame)
2622        frame.Show(True)
2623        wx.PostEvent(self, StatusEvent(status="Data2D Info Displayed",
2624                                       type='stop'))
2625
2626    def set_current_perspective(self, perspective):
2627        """
2628        set the current active perspective
2629        """
2630        self._current_perspective = perspective
2631        name = "No current analysis selected"
2632        if self._current_perspective is not None:
2633            self._add_current_plugin_menu()
2634            for panel in self.panels.values():
2635                if hasattr(panel, 'CENTER_PANE') and panel.CENTER_PANE:
2636                    for name in self._current_perspective.get_perspective():
2637                        frame = panel.get_frame()
2638                        if frame != None:
2639                            if name == panel.window_name:
2640                                panel.on_set_focus(event=None)
2641                                frame.Show(True)
2642                            else:
2643                                frame.Show(False)
2644            name = self._current_perspective.sub_menu
2645            if self._data_panel is not None:
2646                self._data_panel.set_active_perspective(name)
2647                self._check_applications_menu()
2648            #Set the SasView title
2649            self._set_title_name(name)
2650
2651    def _set_title_name(self, name):
2652        """
2653        Set the SasView title w/ the current application name
2654
2655        : param name: application name [string]
2656        """
2657        # Set SanView Window title w/ application anme
2658        title = self.title + "  - " + name + " -"
2659        self.SetTitle(title)
2660
2661    def _check_applications_menu(self):
2662        """
2663        check the menu of the current application
2664        """
2665        if self._applications_menu is not None:
2666            for menu in self._applications_menu.GetMenuItems():
2667                if self._current_perspective is not None:
2668                    name = self._current_perspective.sub_menu
2669                    if menu.IsCheckable():
2670                        if menu.GetLabel() == name:
2671                            menu.Check(True)
2672                        else:
2673                            menu.Check(False)
2674
2675    def enable_add_data(self, new_plot):
2676        """
2677        Enable append data on a plot panel
2678        """
2679
2680        if self.panel_on_focus not in self._plotting_plugin.plot_panels.values():
2681            return
2682        is_theory = len(self.panel_on_focus.plots) <= 1 and \
2683            self.panel_on_focus.plots.values()[0].__class__.__name__ == "Theory1D"
2684
2685        is_data2d = hasattr(new_plot, 'data')
2686
2687        is_data1d = self.panel_on_focus.__class__.__name__ == "ModelPanel1D"\
2688            and self.panel_on_focus.group_id is not None
2689        has_meta_data = hasattr(new_plot, 'meta_data')
2690
2691        #disable_add_data if the data is being recovered from  a saved state file.
2692        is_state_data = False
2693        if has_meta_data:
2694            if 'invstate' in new_plot.meta_data:
2695                is_state_data = True
2696            if  'prstate' in new_plot.meta_data:
2697                is_state_data = True
2698            if  'fitstate' in new_plot.meta_data:
2699                is_state_data = True
2700
2701        return is_data1d and not is_data2d and not is_theory and not is_state_data
2702
2703    def check_multimode(self, perspective=None):
2704        """
2705        Check the perspective have batch mode capablitity
2706        """
2707        if perspective == None or self._data_panel == None:
2708            return
2709        flag = perspective.get_batch_capable()
2710        flag_on = perspective.batch_on
2711        if flag:
2712            self._data_panel.rb_single_mode.SetValue(not flag_on)
2713            self._data_panel.rb_batch_mode.SetValue(flag_on)
2714        else:
2715            self._data_panel.rb_single_mode.SetValue(True)
2716            self._data_panel.rb_batch_mode.SetValue(False)
2717        self._data_panel.rb_single_mode.Enable(flag)
2718        self._data_panel.rb_batch_mode.Enable(flag)
2719
2720    def enable_edit_menu(self):
2721        """
2722        enable menu item under edit menu depending on the panel on focus
2723        """
2724        if self.cpanel_on_focus is not None and self._edit_menu is not None:
2725            flag = self.cpanel_on_focus.get_undo_flag()
2726            self._edit_menu.Enable(GUIFRAME_ID.UNDO_ID, flag)
2727            flag = self.cpanel_on_focus.get_redo_flag()
2728            self._edit_menu.Enable(GUIFRAME_ID.REDO_ID, flag)
2729            flag = self.cpanel_on_focus.get_copy_flag()
2730            self._edit_menu.Enable(GUIFRAME_ID.COPY_ID, flag)
2731            flag = self.cpanel_on_focus.get_paste_flag()
2732            self._edit_menu.Enable(GUIFRAME_ID.PASTE_ID, flag)
2733
2734            #Copy menu
2735            flag = self.cpanel_on_focus.get_copy_flag()
2736            self._edit_menu_copyas.Enable(GUIFRAME_ID.COPYEX_ID, flag)
2737            self._edit_menu_copyas.Enable(GUIFRAME_ID.COPYLAT_ID, flag)
2738
2739            flag = self.cpanel_on_focus.get_preview_flag()
2740            self._edit_menu.Enable(GUIFRAME_ID.PREVIEW_ID, flag)
2741            flag = self.cpanel_on_focus.get_reset_flag()
2742            self._edit_menu.Enable(GUIFRAME_ID.RESET_ID, flag)
2743        else:
2744            flag = False
2745            self._edit_menu.Enable(GUIFRAME_ID.UNDO_ID, flag)
2746            self._edit_menu.Enable(GUIFRAME_ID.REDO_ID, flag)
2747            self._edit_menu.Enable(GUIFRAME_ID.COPY_ID, flag)
2748            self._edit_menu.Enable(GUIFRAME_ID.PASTE_ID, flag)
2749            self._edit_menu.Enable(GUIFRAME_ID.PREVIEW_ID, flag)
2750            self._edit_menu.Enable(GUIFRAME_ID.RESET_ID, flag)
2751
2752    def on_undo_panel(self, event=None):
2753        """
2754        undo previous action of the last panel on focus if possible
2755        """
2756        if self.cpanel_on_focus is not None:
2757            self.cpanel_on_focus.on_undo(event)
2758
2759    def on_redo_panel(self, event=None):
2760        """
2761        redo the last cancel action done on the last panel on focus
2762        """
2763        if self.cpanel_on_focus is not None:
2764            self.cpanel_on_focus.on_redo(event)
2765
2766    def on_copy_panel(self, event=None):
2767        """
2768        copy the last panel on focus if possible
2769        """
2770        if self.cpanel_on_focus is not None:
2771            self.cpanel_on_focus.on_copy(event)
2772
2773    def on_paste_panel(self, event=None):
2774        """
2775        paste clipboard to the last panel on focus
2776        """
2777        if self.cpanel_on_focus is not None:
2778            self.cpanel_on_focus.on_paste(event)
2779
2780    def on_bookmark_panel(self, event=None):
2781        """
2782        bookmark panel
2783        """
2784        if self.cpanel_on_focus is not None:
2785            self.cpanel_on_focus.on_bookmark(event)
2786
2787    def append_bookmark(self, event=None):
2788        """
2789        Bookmark available information of the panel on focus
2790        """
2791        self._toolbar.append_bookmark(event)
2792
2793    def on_save_panel(self, event=None):
2794        """
2795        save possible information on the current panel
2796        """
2797        if self.cpanel_on_focus is not None:
2798            self.cpanel_on_focus.on_save(event)
2799
2800    def on_preview_panel(self, event=None):
2801        """
2802        preview information on the panel on focus
2803        """
2804        if self.cpanel_on_focus is not None:
2805            self.cpanel_on_focus.on_preview(event)
2806
2807    def on_print_panel(self, event=None):
2808        """
2809        print available information on the last panel on focus
2810        """
2811        if self.cpanel_on_focus is not None:
2812            self.cpanel_on_focus.on_print(event)
2813
2814    def on_zoom_panel(self, event=None):
2815        """
2816        zoom on the current panel if possible
2817        """
2818        if self.cpanel_on_focus is not None:
2819            self.cpanel_on_focus.on_zoom(event)
2820
2821    def on_zoom_in_panel(self, event=None):
2822        """
2823        zoom in of the panel on focus
2824        """
2825        if self.cpanel_on_focus is not None:
2826            self.cpanel_on_focus.on_zoom_in(event)
2827
2828    def on_zoom_out_panel(self, event=None):
2829        """
2830        zoom out on the panel on focus
2831        """
2832        if self.cpanel_on_focus is not None:
2833            self.cpanel_on_focus.on_zoom_out(event)
2834
2835    def on_drag_panel(self, event=None):
2836        """
2837        drag apply to the panel on focus
2838        """
2839        if self.cpanel_on_focus is not None:
2840            self.cpanel_on_focus.on_drag(event)
2841
2842    def on_reset_panel(self, event=None):
2843        """
2844        reset the current panel
2845        """
2846        if self.cpanel_on_focus is not None:
2847            self.cpanel_on_focus.on_reset(event)
2848
2849    def on_change_caption(self, name, old_caption, new_caption):
2850        """
2851        Change the panel caption
2852
2853        :param name: window_name of the pane
2854        :param old_caption: current caption [string]
2855        :param new_caption: new caption [string]
2856        """
2857        # wx.aui.AuiPaneInfo
2858        pane_info = self.get_paneinfo(old_caption)
2859        # update the data_panel.cb_plotpanel
2860        if 'data_panel' in self.panels.keys():
2861            # remove from data_panel combobox
2862            data_panel = self.panels["data_panel"]
2863            if data_panel.cb_plotpanel is not None:
2864                # Check if any panel has the same caption
2865                has_newstring = data_panel.cb_plotpanel.FindString\
2866                                                            (str(new_caption))
2867                caption = new_caption
2868                if has_newstring != wx.NOT_FOUND:
2869                    captions = self._get_plotpanel_captions()
2870                    # Append nummber
2871                    inc = 1
2872                    #FIXME: fix this terrible loop
2873                    while (1):
2874                        caption = new_caption + '_%s' % str(inc)
2875                        if caption not in captions:
2876                            break
2877                        inc += 1
2878                    # notify to users
2879                    msg = "Found Same Title: Added '_%s'" % str(inc)
2880                    wx.PostEvent(self, StatusEvent(status=msg))
2881                # update data_panel cb
2882                pos = data_panel.cb_plotpanel.FindString(str(old_caption))
2883                if pos != wx.NOT_FOUND:
2884                    data_panel.cb_plotpanel.SetString(pos, caption)
2885                    data_panel.cb_plotpanel.SetStringSelection(caption)
2886        # New Caption
2887        pane_info.SetTitle(caption)
2888        return caption
2889
2890    def get_paneinfo(self, name):
2891        """
2892        Get pane Caption from window_name
2893
2894        :param name: window_name in AuiPaneInfo
2895        :return: AuiPaneInfo of the name
2896        """
2897        for panel in self.plot_panels.values():
2898            if panel.frame.GetTitle() == name:
2899                return panel.frame
2900        return None
2901
2902    def enable_undo(self):
2903        """
2904        enable undo related control
2905        """
2906        if self.cpanel_on_focus is not None:
2907            self._toolbar.enable_undo(self.cpanel_on_focus)
2908
2909    def enable_redo(self):
2910        """
2911        enable redo
2912        """
2913        if self.cpanel_on_focus is not None:
2914            self._toolbar.enable_redo(self.cpanel_on_focus)
2915
2916    def enable_copy(self):
2917        """
2918        enable copy related control
2919        """
2920        if self.cpanel_on_focus is not None:
2921            self._toolbar.enable_copy(self.cpanel_on_focus)
2922
2923    def enable_paste(self):
2924        """
2925        enable paste
2926        """
2927        if self.cpanel_on_focus is not None:
2928            self._toolbar.enable_paste(self.cpanel_on_focus)
2929
2930    def enable_bookmark(self):
2931        """
2932        Bookmark
2933        """
2934        if self.cpanel_on_focus is not None:
2935            self._toolbar.enable_bookmark(self.cpanel_on_focus)
2936
2937    def enable_save(self):
2938        """
2939        save
2940        """
2941        if self.cpanel_on_focus is not None:
2942            self._toolbar.enable_save(self.cpanel_on_focus)
2943
2944    def enable_preview(self):
2945        """
2946        preview
2947        """
2948        if self.cpanel_on_focus is not None:
2949            self._toolbar.enable_preview(self.cpanel_on_focus)
2950
2951    def enable_print(self):
2952        """
2953        print
2954        """
2955        if self.cpanel_on_focus is not None:
2956            self._toolbar.enable_print(self.cpanel_on_focus)
2957
2958    def enable_zoom(self):
2959        """
2960        zoom
2961        """
2962        if self.cpanel_on_focus is not None:
2963            self._toolbar.enable_zoom(self.panel_on_focus)
2964
2965    def enable_zoom_in(self):
2966        """
2967        zoom in
2968        """
2969        if self.cpanel_on_focus is not None:
2970            self._toolbar.enable_zoom_in(self.panel_on_focus)
2971
2972    def enable_zoom_out(self):
2973        """
2974        zoom out
2975        """
2976        if self.cpanel_on_focus is not None:
2977            self._toolbar.enable_zoom_out(self.panel_on_focus)
2978
2979    def enable_drag(self, event=None):
2980        """
2981        drag
2982        """
2983        #Not implemeted
2984
2985    def enable_reset(self):
2986        """
2987        reset the current panel
2988        """
2989        if self.cpanel_on_focus is not None:
2990            self._toolbar.enable_reset(self.panel_on_focus)
2991
2992    def get_toolbar_height(self):
2993        """
2994        """
2995        size_y = 0
2996        if self.GetToolBar() != None and self.GetToolBar().IsShown():
2997            if not IS_LINUX:
2998                _, size_y = self.GetToolBar().GetSizeTuple()
2999        return size_y
3000
3001    def set_schedule_full_draw(self, panel=None, func='del'):
3002        """
3003        Add/subtract the schedule full draw list with the panel given
3004
3005        :param panel: plot panel
3006        :param func: append or del [string]
3007        """
3008
3009        # append this panel in the schedule list if not in yet
3010        if func == 'append':
3011            if not panel in self.schedule_full_draw_list:
3012                self.schedule_full_draw_list.append(panel)
3013        # remove this panel from schedule list
3014        elif func == 'del':
3015            if len(self.schedule_full_draw_list) > 0:
3016                if panel in self.schedule_full_draw_list:
3017                    self.schedule_full_draw_list.remove(panel)
3018
3019        # reset the schdule
3020        if len(self.schedule_full_draw_list) == 0:
3021            self.schedule = False
3022        else:
3023            self.schedule = True
3024
3025    def full_draw(self):
3026        """
3027        Draw the panels with axes in the schedule to full dwar list
3028        """
3029
3030        count = len(self.schedule_full_draw_list)
3031        #if not self.schedule:
3032        if count < 1:
3033            self.set_schedule(False)
3034            return
3035
3036        else:
3037            ind = 0
3038            # if any of the panel is shown do full_draw
3039            for panel in self.schedule_full_draw_list:
3040                ind += 1
3041                if panel.frame.IsShown():
3042                    break
3043                # otherwise, return
3044                if ind == count:
3045                    return
3046        #Simple redraw only for a panel shown
3047        def f_draw(panel):
3048            """
3049            Draw A panel in the full draw list
3050            """
3051            try:
3052                # This checking of GetCapture is to stop redrawing
3053                # while any panel is capture.
3054                frame = panel.frame
3055
3056                if not frame.GetCapture():
3057                    # draw if possible
3058                    panel.set_resizing(False)
3059                    #panel.Show(True)
3060                    panel.draw_plot()
3061                # Check if the panel is not shown
3062                flag = frame.IsShown()
3063                frame.Show(flag)
3064            except:
3065                pass
3066
3067        # Draw all panels
3068        if count == 1:
3069            f_draw(self.schedule_full_draw_list[0])
3070        else:
3071            map(f_draw, self.schedule_full_draw_list)
3072        # Reset the attr 
3073        if len(self.schedule_full_draw_list) == 0:
3074            self.set_schedule(False)
3075        else:
3076            self.set_schedule(True)
3077
3078    def set_schedule(self, schedule=False):
3079        """
3080        Set schedule
3081        """
3082        self.schedule = schedule
3083
3084    def get_schedule(self):
3085        """
3086        Get schedule
3087        """
3088        return self.schedule
3089
3090    def on_set_plot_focus(self, panel):
3091        """
3092        Set focus on a plot panel
3093        """
3094        if panel == None:
3095            return
3096        #self.set_plot_unfocus()
3097        panel.on_set_focus(None)
3098        # set focusing panel
3099        self.panel_on_focus = panel
3100        self.set_panel_on_focus(None)
3101
3102    def set_plot_unfocus(self):
3103        """
3104        Un focus all plot panels
3105        """
3106        for plot in self.plot_panels.values():
3107            plot.on_kill_focus(None)
3108
3109    def get_window_size(self):
3110        """
3111        Get window size
3112
3113        :return size: tuple
3114        """
3115        width, height = self.GetSizeTuple()
3116        if not IS_WIN:
3117            # Subtract toolbar height to get real window side
3118            if self._toolbar.IsShown():
3119                height -= 45
3120        return (width, height)
3121
3122    def _onDrawIdle(self, *args, **kwargs):
3123        """
3124        ReDraw with axes
3125        """
3126        try:
3127            # check if it is time to redraw
3128            if self.GetCapture() == None:
3129                # Draw plot, changes resizing too
3130                self.full_draw()
3131        except:
3132            pass
3133
3134        # restart idle       
3135        self._redraw_idle(*args, **kwargs)
3136
3137
3138    def _redraw_idle(self, *args, **kwargs):
3139        """
3140        Restart Idle
3141        """
3142        # restart idle   
3143        self.idletimer.Restart(100 * TIME_FACTOR, *args, **kwargs)
3144
3145
3146class DefaultPanel(wx.Panel, PanelBase):
3147    """
3148    Defines the API for a panels to work with
3149    the GUI manager
3150    """
3151    ## Internal nickname for the window, used by the AUI manager
3152    window_name = "default"
3153    ## Name to appear on the window title bar
3154    window_caption = "Welcome panel"
3155    ## Flag to tell the AUI manager to put this panel in the center pane
3156    CENTER_PANE = True
3157    def __init__(self, parent, *args, **kwds):
3158        wx.Panel.__init__(self, parent, *args, **kwds)
3159        PanelBase.__init__(self, parent)
3160
3161
3162
3163class ViewApp(wx.App):
3164    """
3165    Toy application to test this Frame
3166    """
3167    def OnInit(self):
3168        """
3169        When initialised
3170        """
3171        pos, size, self.is_max = self.window_placement((GUIFRAME_WIDTH,
3172                                                        GUIFRAME_HEIGHT))
3173        self.frame = ViewerFrame(parent=None,
3174                                 title=APPLICATION_NAME,
3175                                 pos=pos,
3176                                 gui_style=DEFAULT_STYLE,
3177                                 size=size)
3178        self.frame.Hide()
3179        if not IS_WIN:
3180            self.frame.EnableCloseButton(False)
3181        self.s_screen = None
3182
3183        try:
3184            self.open_file()
3185        except:
3186            msg = "%s Could not load " % str(APPLICATION_NAME)
3187            msg += "input file from command line.\n"
3188            logging.error(msg)
3189        # Display a splash screen on top of the frame.
3190        try:
3191            if os.path.isfile(SPLASH_SCREEN_PATH):
3192                self.s_screen = self.display_splash_screen(parent=self.frame,
3193                                                           path=SPLASH_SCREEN_PATH)
3194            else:
3195                self.frame.Show()
3196        except:
3197            if self.s_screen is not None:
3198                self.s_screen.Close()
3199            msg = "Cannot display splash screen\n"
3200            msg += str(sys.exc_value)
3201            logging.error(msg)
3202            self.frame.Show()
3203
3204        self.SetTopWindow(self.frame)
3205
3206        return True
3207
3208    def maximize_win(self):
3209        """
3210        Maximize the window after the frame shown
3211        """
3212        if self.is_max:
3213            if self.frame.IsShown():
3214                # Max window size
3215                self.frame.Maximize(self.is_max)
3216
3217    def open_file(self):
3218        """
3219        open a state file at the start of the application
3220        """
3221        input_file = None
3222        if len(sys.argv) >= 2:
3223            cmd = sys.argv[0].lower()
3224            basename = os.path.basename(cmd)
3225            app_base = str(APPLICATION_NAME).lower()
3226            if os.path.isfile(cmd) or basename.lower() == app_base:
3227                app_py = app_base + '.py'
3228                app_exe = app_base + '.exe'
3229                app_app = app_base + '.app'
3230                if basename.lower() in [app_py, app_exe, app_app, app_base]:
3231                    data_base = sys.argv[1]
3232                    input_file = os.path.normpath(os.path.join(DATAPATH,
3233                                                               data_base))
3234        if input_file is None:
3235            return
3236        if self.frame is not None:
3237            self.frame.set_input_file(input_file=input_file)
3238
3239    def clean_plugin_models(self, path):
3240        """
3241        Delete plugin models  in app folder
3242
3243        :param path: path of the plugin_models folder in app
3244        """
3245        # do it only the first time app loaded
3246        # delete unused model folder
3247        model_folder = os.path.join(PATH_APP, path)
3248        if os.path.exists(model_folder) and os.path.isdir(model_folder):
3249            if len(os.listdir(model_folder)) > 0:
3250                try:
3251                    for file in os.listdir(model_folder):
3252                        file_path = os.path.join(model_folder, file)
3253                        if os.path.isfile(file_path):
3254                            os.remove(file_path)
3255                except:
3256                    logging.error("gui_manager.clean_plugin_models:\n  %s" \
3257                                  % sys.exc_value)
3258
3259    def set_manager(self, manager):
3260        """
3261        Sets a reference to the application manager
3262        of the GUI manager (Frame)
3263        """
3264        self.frame.set_manager(manager)
3265
3266    def build_gui(self):
3267        """
3268        Build the GUI
3269        """
3270        #try to load file at the start
3271        try:
3272            self.open_file()
3273        except:
3274            raise
3275        self.frame.build_gui()
3276
3277    def set_welcome_panel(self, panel_class):
3278        """
3279        Set the welcome panel
3280
3281        :param panel_class: class of the welcome panel to be instantiated
3282
3283        """
3284        self.frame.welcome_panel_class = panel_class
3285
3286    def add_perspective(self, perspective):
3287        """
3288        Manually add a perspective to the application GUI
3289        """
3290        self.frame.add_perspective(perspective)
3291
3292    def window_placement(self, size):
3293        """
3294        Determines the position and size of the application frame such that it
3295        fits on the user's screen without obstructing (or being obstructed by)
3296        the Windows task bar.  The maximum initial size in pixels is bounded by
3297        WIDTH x HEIGHT.  For most monitors, the application
3298        will be centered on the screen; for very large monitors it will be
3299        placed on the left side of the screen.
3300        """
3301        is_maximized = False
3302        # Get size of screen without
3303        for screenCount in range(wx.Display().GetCount()):
3304            screen = wx.Display(screenCount)
3305            if screen.IsPrimary():
3306                displayRect = screen.GetClientArea()
3307                break
3308
3309        posX, posY, displayWidth, displayHeight = displayRect
3310        customWidth, customHeight = size
3311
3312        # If the custom size is default, set 90% of the screen size
3313        if customWidth <= 0 and customHeight <= 0:
3314            if customWidth == 0 and customHeight == 0:
3315                is_maximized = True
3316            customWidth = displayWidth * 0.9
3317            customHeight = displayHeight * 0.9
3318        else:
3319            # If the custom screen is bigger than the
3320            # window screen than make maximum size
3321            if customWidth > displayWidth:
3322                customWidth = displayWidth
3323            if customHeight > displayHeight:
3324                customHeight = displayHeight
3325
3326        # Note that when running Linux and using an Xming (X11) server on a PC
3327        # with a dual  monitor configuration, the reported display size may be
3328        # that of both monitors combined with an incorrect display count of 1.
3329        # To avoid displaying this app across both monitors, we check for
3330        # screen 'too big'.  If so, we assume a smaller width which means the
3331        # application will be placed towards the left hand side of the screen.
3332
3333        # If dual screen registered as 1 screen. Make width half.
3334        # MAC just follows the default behavior of pos
3335        if IS_WIN:
3336            if displayWidth > (displayHeight * 2):
3337                if customWidth == displayWidth:
3338                    customWidth = displayWidth / 2
3339                # and set the position to be the corner of the screen.
3340                posX = 0
3341                posY = 0
3342
3343            # Make the position the middle of the screen. (Not 0,0)
3344            else:
3345                posX = (displayWidth - customWidth) / 2
3346                posY = (displayHeight - customHeight) / 2
3347        # Return the suggested position and size for the application frame.
3348        return (posX, posY), (customWidth, customHeight), is_maximized
3349
3350
3351    def display_splash_screen(self, parent,
3352                              path=SPLASH_SCREEN_PATH):
3353        """Displays the splash screen.  It will exactly cover the main frame."""
3354
3355        # Prepare the picture.  On a 2GHz intel cpu, this takes about a second.
3356        image = wx.Image(path, wx.BITMAP_TYPE_PNG)
3357        image.Rescale(SPLASH_SCREEN_WIDTH,
3358                      SPLASH_SCREEN_HEIGHT, wx.IMAGE_QUALITY_HIGH)
3359        bm = image.ConvertToBitmap()
3360
3361        # Create and show the splash screen.  It will disappear only when the
3362        # program has entered the event loop AND either the timeout has expired
3363        # or the user has left clicked on the screen.  Thus any processing
3364        # performed in this routine (including sleeping) or processing in the
3365        # calling routine (including doing imports) will prevent the splash
3366        # screen from disappearing.
3367        #
3368        # Note that on Linux, the timeout appears to occur immediately in which
3369        # case the splash screen disappears upon entering the event loop.
3370        s_screen = wx.SplashScreen(bitmap=bm,
3371                                   splashStyle=(wx.SPLASH_TIMEOUT |
3372                                                wx.SPLASH_CENTRE_ON_SCREEN),
3373                                   style=(wx.SIMPLE_BORDER |
3374                                          wx.FRAME_NO_TASKBAR |
3375                                          wx.FRAME_FLOAT_ON_PARENT),
3376                                   milliseconds=SS_MAX_DISPLAY_TIME,
3377                                   parent=parent,
3378                                   id=wx.ID_ANY)
3379        from sas.guiframe.gui_statusbar import SPageStatusbar
3380        statusBar = SPageStatusbar(s_screen)
3381        s_screen.SetStatusBar(statusBar)
3382        s_screen.Bind(wx.EVT_CLOSE, self.on_close_splash_screen)
3383        s_screen.Show()
3384        return s_screen
3385
3386
3387    def on_close_splash_screen(self, event):
3388        """
3389        When the splash screen is closed.
3390        """
3391        self.frame.Show(True)
3392        event.Skip()
3393        self.maximize_win()
3394
3395
3396class MDIFrame(CHILD_FRAME):
3397    """
3398    Frame for panels
3399    """
3400    def __init__(self, parent, panel, title="Untitled", size=(300, 200)):
3401        """
3402        comment
3403        :param parent: parent panel/container
3404        """
3405        # Initialize the Frame object
3406        CHILD_FRAME.__init__(self, parent=parent, id=wx.ID_ANY, title=title, size=size)
3407        self.parent = parent
3408        self.name = "Untitled"
3409        self.batch_on = self.parent.batch_on
3410        self.panel = panel
3411        if panel != None:
3412            self.set_panel(panel)
3413        self.Show(False)
3414
3415    def show_data_panel(self, action):
3416        """
3417        """
3418        self.parent.show_data_panel(action)
3419
3420    def set_panel(self, panel):
3421        """
3422        """
3423        self.panel = panel
3424        self.name = panel.window_name
3425        self.SetTitle(panel.window_caption)
3426        self.SetHelpText(panel.help_string)
3427        width, height = self.parent._get_panels_size(panel)
3428        if hasattr(panel, "CENTER_PANE") and panel.CENTER_PANE:
3429            width *= 0.6
3430        self.SetSize((width, height))
3431        self.parent.put_icon(self)
3432        self.Bind(wx.EVT_SET_FOCUS, self.set_panel_focus)
3433        self.Bind(wx.EVT_CLOSE, self.OnClose)
3434        self.Show(False)
3435
3436    def set_panel_focus(self, event):
3437        """
3438        """
3439        if self.parent.panel_on_focus != self.panel:
3440            self.panel.SetFocus()
3441            self.parent.panel_on_focus = self.panel
3442
3443    def OnClose(self, event):
3444        """
3445        On Close event
3446        """
3447        self.panel.on_close(event)
3448
3449if __name__ == "__main__":
3450    app = ViewApp(0)
3451    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.