source: sasview/src/sas/guiframe/gui_manager.py @ 422e7e8

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 422e7e8 was 5276eeb, checked in by krzywon, 9 years ago

Made a window pop up when saving a project fails.

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