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

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 a27e8b8 was 415fb82, checked in by krzywon, 10 years ago

Fixed the perspectives loading error. Cleaned up the sasview.log by
removing any extra linefeeds/carraige returns and added a start session
and end session message.

  • Property mode set to 100644
File size: 123.3 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
574        else the application is default mode(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
1298        pos = self._menubar.FindMenu(str(self._applications_menu_name))
1299        if pos != -1:
1300            menu_list = self._current_perspective.populate_menu(self)
1301            if menu_list:
1302                for (menu, name) in menu_list:
1303                    self._menubar.Replace(pos, menu, name)
1304                    self._applications_menu_name = name
1305            else:
1306                self._menubar.Remove(pos)
1307                self._applications_menu_name = None
1308            #get the position of the menu when it first added
1309            self._applications_menu_pos = pos
1310
1311        else:
1312            menu_list = self._current_perspective.populate_menu(self)
1313            if menu_list:
1314                for (menu, name) in menu_list:
1315                    if self._applications_menu_pos == -1:
1316                        self._menubar.Append(menu, name)
1317                    else:
1318                        self._menubar.Insert(self._applications_menu_pos,
1319                                             menu, name)
1320                    self._applications_menu_name = name
1321
1322    def _add_help_menu(self):
1323        """
1324        add help menu to menu bar.  Includes welcome page, about page,
1325        tutorial PDF and documentation pages.
1326        """
1327        # Help menu
1328        self._help_menu = wx.Menu()
1329        style = self.__gui_style & GUIFRAME.WELCOME_PANEL_ON
1330
1331        if style == GUIFRAME.WELCOME_PANEL_ON or custom_config != None:
1332            # add the welcome panel menu item
1333            if config.WELCOME_PANEL_ON and self.defaultPanel is not None:
1334                wx_id = wx.NewId()
1335                self._help_menu.Append(wx_id, '&Welcome', '')
1336                wx.EVT_MENU(self, wx_id, self.show_welcome_panel)
1337
1338        self._help_menu.AppendSeparator()
1339        wx_id = wx.NewId()
1340        self._help_menu.Append(wx_id, '&Documentation', '')
1341        wx.EVT_MENU(self, wx_id, self._onSphinxDocs)
1342
1343        if config._do_tutorial and (IS_WIN or sys.platform == 'darwin'):
1344            self._help_menu.AppendSeparator()
1345            wx_id = wx.NewId()
1346            self._help_menu.Append(wx_id, '&Tutorial', 'Software tutorial')
1347            wx.EVT_MENU(self, wx_id, self._onTutorial)
1348
1349        if config._do_acknowledge:
1350            self._help_menu.AppendSeparator()
1351            wx_id = wx.NewId()
1352            self._help_menu.Append(wx_id, '&Acknowledge', 'Acknowledging SasView')
1353            wx.EVT_MENU(self, wx_id, self._onAcknowledge)
1354
1355        if config._do_aboutbox:
1356            self._help_menu.AppendSeparator()
1357            self._help_menu.Append(wx.ID_ABOUT, '&About', 'Software information')
1358            wx.EVT_MENU(self, wx.ID_ABOUT, self._onAbout)
1359
1360        # Checking for updates
1361        wx_id = wx.NewId()
1362        self._help_menu.Append(wx_id, '&Check for update',
1363                               'Check for the latest version of %s' % config.__appname__)
1364        wx.EVT_MENU(self, wx_id, self._check_update)
1365        self._menubar.Append(self._help_menu, '&Help')
1366
1367    def _add_menu_view(self):
1368        """
1369        add menu items under view menu
1370        """
1371        if not VIEW_MENU:
1372            return
1373        self._view_menu = wx.Menu()
1374
1375        wx_id = wx.NewId()
1376        hint = "Display the Grid Window for batch results etc."
1377        self._view_menu.Append(wx_id, '&Show Grid Window', hint)
1378        wx.EVT_MENU(self, wx_id, self.show_batch_frame)
1379
1380        self._view_menu.AppendSeparator()
1381        style = self.__gui_style & GUIFRAME.MANAGER_ON
1382        wx_id = wx.NewId()
1383        self._data_panel_menu = self._view_menu.Append(wx_id,
1384                                                       '&Show Data Explorer', '')
1385        wx.EVT_MENU(self, wx_id, self.show_data_panel)
1386        if style == GUIFRAME.MANAGER_ON:
1387            self._data_panel_menu.SetText('Hide Data Explorer')
1388        else:
1389            self._data_panel_menu.SetText('Show Data Explorer')
1390
1391        self._view_menu.AppendSeparator()
1392        wx_id = wx.NewId()
1393        style1 = self.__gui_style & GUIFRAME.TOOLBAR_ON
1394        if style1 == GUIFRAME.TOOLBAR_ON:
1395            self._toolbar_menu = self._view_menu.Append(wx_id, '&Hide Toolbar', '')
1396        else:
1397            self._toolbar_menu = self._view_menu.Append(wx_id, '&Show Toolbar', '')
1398        wx.EVT_MENU(self, wx_id, self._on_toggle_toolbar)
1399
1400        if custom_config != None:
1401            self._view_menu.AppendSeparator()
1402            wx_id = wx.NewId()
1403            hint_ss = "Select the current/default configuration "
1404            hint_ss += "as a startup setting"
1405            preference_menu = self._view_menu.Append(wx_id, 'Startup Setting',
1406                                                     hint_ss)
1407            wx.EVT_MENU(self, wx_id, self._on_preference_menu)
1408
1409        wx_id = wx.NewId()
1410        self._view_menu.AppendSeparator()
1411        self._view_menu.Append(wx_id, 'Category Manager', 'Edit model categories')
1412        wx.EVT_MENU(self, wx_id, self._on_category_manager)
1413
1414        self._menubar.Append(self._view_menu, '&View')
1415
1416    def show_batch_frame(self, event=None):
1417        """
1418        show the grid of result
1419        """
1420        # Show(False) before Show(True) in order to bring it to the front
1421        self.batch_frame.Show(False)
1422        self.batch_frame.Show(True)
1423
1424    def  on_category_panel(self, event):
1425        """
1426        On cat panel
1427        """
1428        self._on_category_manager(event)
1429
1430    def _on_category_manager(self, event):
1431        """
1432        Category manager frame
1433        """
1434        frame = CategoryManager(self, -1, 'Model Category Manager')
1435        icon = self.GetIcon()
1436        frame.SetIcon(icon)
1437
1438    def _on_preference_menu(self, event):
1439        """
1440        Build a panel to allow to edit Mask
1441        """
1442        from sas.guiframe.startup_configuration \
1443        import StartupConfiguration as ConfDialog
1444
1445        dialog = ConfDialog(parent=self, gui=self.__gui_style)
1446        result = dialog.ShowModal()
1447        if result == wx.ID_OK:
1448            dialog.write_custom_config()
1449            # post event for info
1450            wx.PostEvent(self, StatusEvent(status="Wrote custom configuration", info='info'))
1451        dialog.Destroy()
1452
1453    def _add_menu_application(self):
1454        """
1455        # Attach a menu item for each defined perspective or application.
1456        # Only add the perspective menu if there are more than one perspectives
1457        add menu application
1458        """
1459        if self._num_perspectives > 1:
1460            plug_data_count = False
1461            plug_no_data_count = False
1462            self._applications_menu = wx.Menu()
1463            pos = 0
1464            separator = self._applications_menu.AppendSeparator()
1465            for plug in self.plugins:
1466                if len(plug.get_perspective()) > 0:
1467                    id = wx.NewId()
1468                    if plug.use_data():
1469                        self._applications_menu.InsertCheckItem(pos, id, plug.sub_menu, \
1470                            "Switch to analysis: %s" % plug.sub_menu)
1471                        plug_data_count = True
1472                        pos += 1
1473                    else:
1474                        plug_no_data_count = True
1475                        self._applications_menu.AppendCheckItem(id, plug.sub_menu, \
1476                            "Switch to analysis: %s" % plug.sub_menu)
1477                    wx.EVT_MENU(self, id, plug.on_perspective)
1478
1479            if not plug_data_count or not plug_no_data_count:
1480                self._applications_menu.RemoveItem(separator)
1481            self._menubar.Append(self._applications_menu, '&Analysis')
1482            self._check_applications_menu()
1483
1484    def _populate_file_menu(self):
1485        """
1486        Insert menu item under file menu
1487        """
1488        for plugin in self.plugins:
1489            if len(plugin.populate_file_menu()) > 0:
1490                for item in plugin.populate_file_menu():
1491                    m_name, m_hint, m_handler = item
1492                    id = wx.NewId()
1493                    self._file_menu.Append(id, m_name, m_hint)
1494                    wx.EVT_MENU(self, id, m_handler)
1495                self._file_menu.AppendSeparator()
1496
1497        style1 = self.__gui_style & GUIFRAME.MULTIPLE_APPLICATIONS
1498        if OPEN_SAVE_MENU:
1499            id = wx.NewId()
1500            hint_load_file = "read all analysis states saved previously"
1501            self._save_appl_menu = self._file_menu.Append(id, '&Open Project', hint_load_file)
1502            wx.EVT_MENU(self, id, self._on_open_state_project)
1503
1504        if style1 == GUIFRAME.MULTIPLE_APPLICATIONS:
1505            # some menu of plugin to be seen under file menu
1506            hint_load_file = "Read a status files and load"
1507            hint_load_file += " them into the analysis"
1508            id = wx.NewId()
1509            self._save_appl_menu = self._file_menu.Append(id,
1510                                                          '&Open Analysis', hint_load_file)
1511            wx.EVT_MENU(self, id, self._on_open_state_application)
1512        if OPEN_SAVE_MENU:
1513            self._file_menu.AppendSeparator()
1514            id = wx.NewId()
1515            self._file_menu.Append(id, '&Save Project',
1516                                   'Save the state of the whole analysis')
1517            wx.EVT_MENU(self, id, self._on_save_project)
1518        if style1 == GUIFRAME.MULTIPLE_APPLICATIONS:
1519            id = wx.NewId()
1520            self._save_appl_menu = self._file_menu.Append(id, \
1521                '&Save Analysis', 'Save state of the current active analysis panel')
1522            wx.EVT_MENU(self, id, self._on_save_application)
1523        if not sys.platform == 'darwin':
1524            self._file_menu.AppendSeparator()
1525            id = wx.NewId()
1526            self._file_menu.Append(id, '&Quit', 'Exit')
1527            wx.EVT_MENU(self, id, self.Close)
1528
1529    def _add_menu_file(self):
1530        """
1531        add menu file
1532        """
1533        # File menu
1534        self._file_menu = wx.Menu()
1535        # Add sub menus
1536        self._menubar.Append(self._file_menu, '&File')
1537
1538    def _add_menu_edit(self):
1539        """
1540        add menu edit
1541        """
1542        if not EDIT_MENU:
1543            return
1544        # Edit Menu
1545        self._edit_menu = wx.Menu()
1546        self._edit_menu.Append(GUIFRAME_ID.UNDO_ID, '&Undo',
1547                               'Undo the previous action')
1548        wx.EVT_MENU(self, GUIFRAME_ID.UNDO_ID, self.on_undo_panel)
1549        self._edit_menu.Append(GUIFRAME_ID.REDO_ID, '&Redo',
1550                               'Redo the previous action')
1551        wx.EVT_MENU(self, GUIFRAME_ID.REDO_ID, self.on_redo_panel)
1552        self._edit_menu.AppendSeparator()
1553        self._edit_menu.Append(GUIFRAME_ID.COPY_ID, '&Copy Params',
1554                               'Copy parameter values')
1555        wx.EVT_MENU(self, GUIFRAME_ID.COPY_ID, self.on_copy_panel)
1556        self._edit_menu.Append(GUIFRAME_ID.PASTE_ID, '&Paste Params',
1557                               'Paste parameter values')
1558        wx.EVT_MENU(self, GUIFRAME_ID.PASTE_ID, self.on_paste_panel)
1559
1560        self._edit_menu.AppendSeparator()
1561
1562        self._edit_menu_copyas = wx.Menu()
1563        #Sub menu for Copy As...
1564        self._edit_menu_copyas.Append(GUIFRAME_ID.COPYEX_ID, 'Copy current tab to Excel',
1565                                      'Copy parameter values in tabular format')
1566        wx.EVT_MENU(self, GUIFRAME_ID.COPYEX_ID, self.on_copy_panel)
1567
1568        self._edit_menu_copyas.Append(GUIFRAME_ID.COPYLAT_ID, 'Copy current tab to LaTeX',
1569                                      'Copy parameter values in tabular format')
1570        wx.EVT_MENU(self, GUIFRAME_ID.COPYLAT_ID, self.on_copy_panel)
1571
1572
1573        self._edit_menu.AppendMenu(GUIFRAME_ID.COPYAS_ID, 'Copy Params as...',
1574                                   self._edit_menu_copyas,
1575                                   'Copy parameter values in various formats')
1576
1577        self._edit_menu.AppendSeparator()
1578
1579        self._edit_menu.Append(GUIFRAME_ID.PREVIEW_ID, '&Report Results',
1580                               'Preview current panel')
1581        wx.EVT_MENU(self, GUIFRAME_ID.PREVIEW_ID, self.on_preview_panel)
1582
1583        self._edit_menu.Append(GUIFRAME_ID.RESET_ID, '&Reset Page',
1584                               'Reset current panel')
1585        wx.EVT_MENU(self, GUIFRAME_ID.RESET_ID, self.on_reset_panel)
1586
1587        self._menubar.Append(self._edit_menu, '&Edit')
1588        self.enable_edit_menu()
1589
1590    def get_style(self):
1591        """
1592        Return the gui style
1593        """
1594        return  self.__gui_style
1595
1596    def _add_menu_data(self):
1597        """
1598        Add menu item item data to menu bar
1599        """
1600        if self._data_plugin is not None:
1601            menu_list = self._data_plugin.populate_menu(self)
1602            if menu_list:
1603                for (menu, name) in menu_list:
1604                    self._menubar.Append(menu, name)
1605
1606    def _on_toggle_toolbar(self, event=None):
1607        """
1608        hide or show toolbar
1609        """
1610        if self._toolbar is None:
1611            return
1612        if self._toolbar.IsShown():
1613            if self._toolbar_menu is not None:
1614                self._toolbar_menu.SetItemLabel('Show Toolbar')
1615            self._toolbar.Hide()
1616        else:
1617            if self._toolbar_menu is not None:
1618                self._toolbar_menu.SetItemLabel('Hide Toolbar')
1619            self._toolbar.Show()
1620        self._toolbar.Realize()
1621
1622    def _on_status_event(self, evt):
1623        """
1624        Display status message
1625        """
1626        # This CallAfter fixes many crashes on MAC.
1627        wx.CallAfter(self.sb.set_status, evt)
1628
1629    def on_view(self, evt):
1630        """
1631        A panel was selected to be shown. If it's not already
1632        shown, display it.
1633
1634        :param evt: menu event
1635
1636        """
1637        panel_id = str(evt.GetId())
1638        self.on_set_plot_focus(self.panels[panel_id])
1639        wx.CallLater(5 * TIME_FACTOR, self.set_schedule(True))
1640        self.set_plot_unfocus()
1641
1642    def show_welcome_panel(self, event):
1643        """
1644        Display the welcome panel
1645        """
1646        if self.defaultPanel is None:
1647            return
1648        frame = self.panels['default'].get_frame()
1649        if frame == None:
1650            return
1651        # Show default panel
1652        if not frame.IsShown():
1653            frame.Show(True)
1654
1655    def on_close_welcome_panel(self):
1656        """
1657        Close the welcome panel
1658        """
1659        if self.defaultPanel is None:
1660            return
1661        default_panel = self.panels["default"].frame
1662        if default_panel.IsShown():
1663            default_panel.Show(False)
1664
1665    def delete_panel(self, uid):
1666        """
1667        delete panel given uid
1668        """
1669        ID = str(uid)
1670        config.printEVT("delete_panel: %s" % ID)
1671        if ID in self.panels.keys():
1672            self.panel_on_focus = None
1673            panel = self.panels[ID]
1674
1675            if hasattr(panel, "connect"):
1676                panel.connect.disconnect()
1677            self._plotting_plugin.delete_panel(panel.group_id)
1678
1679            if panel in self.schedule_full_draw_list:
1680                self.schedule_full_draw_list.remove(panel)
1681
1682            #delete uid number not str(uid)
1683            if ID in self.plot_panels.keys():
1684                del self.plot_panels[ID]
1685            if ID in self.panels.keys():
1686                del self.panels[ID]
1687        else:
1688            logging.error("delete_panel: No such plot id as %s" % ID)
1689
1690    def create_gui_data(self, data, path=None):
1691        """
1692        """
1693        return self._data_manager.create_gui_data(data, path)
1694
1695    def get_data(self, path):
1696        """
1697        """
1698        message = ""
1699        log_msg = ''
1700        output = []
1701        error_message = ""
1702        basename = os.path.basename(path)
1703        root, extension = os.path.splitext(basename)
1704        if extension.lower() not in EXTENSIONS:
1705            log_msg = "File Loader cannot "
1706            log_msg += "load: %s\n" % str(basename)
1707            log_msg += "Try Data opening...."
1708            logging.error(log_msg)
1709            return
1710
1711        #reading a state file
1712        for plug in self.plugins:
1713            reader, ext = plug.get_extensions()
1714            if reader is not None:
1715                #read the state of the single plugin
1716                if extension == ext:
1717                    reader.read(path)
1718                    return
1719                elif extension == APPLICATION_STATE_EXTENSION:
1720                    try:
1721                        reader.read(path)
1722                    except:
1723                        msg = "DataLoader Error: Encounted Non-ASCII character"
1724                        msg += "\n(%s)" % sys.exc_value
1725                        wx.PostEvent(self, StatusEvent(status=msg,
1726                                                       info="error", type="stop"))
1727                        return
1728
1729        style = self.__gui_style & GUIFRAME.MANAGER_ON
1730        if style == GUIFRAME.MANAGER_ON:
1731            if self._data_panel is not None:
1732                self._data_panel.frame.Show(True)
1733
1734    def load_from_cmd(self, path):
1735        """
1736        load data from cmd or application
1737        """
1738        if path is None:
1739            return
1740        else:
1741            path = os.path.abspath(path)
1742            if not os.path.isfile(path) and not os.path.isdir(path):
1743                return
1744
1745            if os.path.isdir(path):
1746                self.load_folder(path)
1747                return
1748
1749        basename = os.path.basename(path)
1750        _, extension = os.path.splitext(basename)
1751        if extension.lower() not in EXTENSIONS:
1752            self.load_data(path)
1753        else:
1754            self.load_state(path)
1755
1756        self._default_save_location = os.path.dirname(path)
1757
1758    def load_state(self, path, is_project=False):
1759        """
1760        load data from command line or application
1761        """
1762        if path and (path is not None) and os.path.isfile(path):
1763            basename = os.path.basename(path)
1764            if APPLICATION_STATE_EXTENSION is not None \
1765                and basename.endswith(APPLICATION_STATE_EXTENSION):
1766                if is_project:
1767                    for ID in self.plot_panels.keys():
1768                        panel = self.plot_panels[ID]
1769                        panel.on_close(None)
1770            self.get_data(path)
1771            wx.PostEvent(self, StatusEvent(status="Completed loading."))
1772        else:
1773            wx.PostEvent(self, StatusEvent(status=" "))
1774
1775    def load_data(self, path):
1776        """
1777        load data from command line
1778        """
1779        if not os.path.isfile(path):
1780            return
1781        basename = os.path.basename(path)
1782        _, extension = os.path.splitext(basename)
1783        if extension.lower() in EXTENSIONS:
1784            log_msg = "Data Loader cannot "
1785            log_msg += "load: %s\n" % str(path)
1786            log_msg += "Try File opening ...."
1787            logging.error(log_msg)
1788            return
1789        log_msg = ''
1790        output = {}
1791        error_message = ""
1792        try:
1793            logging.info("Loading Data...:\n" + str(path) + "\n")
1794            temp = self.loader.load(path)
1795            if temp.__class__.__name__ == "list":
1796                for item in temp:
1797                    data = self.create_gui_data(item, path)
1798                    output[data.id] = data
1799            else:
1800                data = self.create_gui_data(temp, path)
1801                output[data.id] = data
1802
1803            self.add_data(data_list=output)
1804        except:
1805            error_message = "Error while loading"
1806            error_message += " Data from cmd:\n %s\n" % str(path)
1807            error_message += str(sys.exc_value) + "\n"
1808            logging.error(error_message)
1809
1810    def load_folder(self, path):
1811        """
1812        Load entire folder
1813        """
1814        if not os.path.isdir(path):
1815            return
1816        if self._data_plugin is None:
1817            return
1818        try:
1819            if path is not None:
1820                self._default_save_location = os.path.dirname(path)
1821                file_list = self._data_plugin.get_file_path(path)
1822                self._data_plugin.get_data(file_list)
1823            else:
1824                return
1825        except:
1826            error_message = "Error while loading"
1827            error_message += " Data folder from cmd:\n %s\n" % str(path)
1828            error_message += str(sys.exc_value) + "\n"
1829            logging.error(error_message)
1830
1831    def _on_open_state_application(self, event):
1832        """
1833        """
1834        path = None
1835        if self._default_save_location == None:
1836            self._default_save_location = os.getcwd()
1837        wx.PostEvent(self, StatusEvent(status="Loading Analysis file..."))
1838        plug_wlist = self._on_open_state_app_helper()
1839        dlg = wx.FileDialog(self,
1840                            "Choose a file",
1841                            self._default_save_location, "",
1842                            plug_wlist)
1843        if dlg.ShowModal() == wx.ID_OK:
1844            path = dlg.GetPath()
1845            if path is not None:
1846                self._default_save_location = os.path.dirname(path)
1847        dlg.Destroy()
1848        self.load_state(path=path)
1849
1850    def _on_open_state_app_helper(self):
1851        """
1852        Helps '_on_open_state_application()' to find the extension of
1853        the current perspective/application
1854        """
1855        # No current perspective or no extension attr
1856        if self._current_perspective is None:
1857            return PLUGINS_WLIST
1858        try:
1859            # Find the extension of the perspective
1860            # and get that as 1st item in list
1861            ind = None
1862            app_ext = self._current_perspective._extensions
1863            plug_wlist = config.PLUGINS_WLIST
1864            for ext in set(plug_wlist):
1865                if ext.count(app_ext) > 0:
1866                    ind = ext
1867                    break
1868            # Found the extension
1869            if ind != None:
1870                plug_wlist.remove(ind)
1871                plug_wlist.insert(0, ind)
1872                try:
1873                    plug_wlist = '|'.join(plug_wlist)
1874                except:
1875                    plug_wlist = ''
1876
1877        except:
1878            plug_wlist = PLUGINS_WLIST
1879
1880        return plug_wlist
1881
1882    def _on_open_state_project(self, event):
1883        """
1884        """
1885        path = None
1886        if self._default_save_location == None:
1887            self._default_save_location = os.getcwd()
1888        wx.PostEvent(self, StatusEvent(status="Loading Project file..."))
1889        dlg = wx.FileDialog(self,
1890                            "Choose a file",
1891                            self._default_save_location, "",
1892                            APPLICATION_WLIST)
1893        if dlg.ShowModal() == wx.ID_OK:
1894            path = dlg.GetPath()
1895            if path is not None:
1896                self._default_save_location = os.path.dirname(path)
1897        dlg.Destroy()
1898
1899        self.load_state(path=path, is_project=True)
1900
1901    def _on_save_application(self, event):
1902        """
1903        save the state of the current active application
1904        """
1905        if self.cpanel_on_focus is not None:
1906            try:
1907                wx.PostEvent(self,
1908                             StatusEvent(status="Saving Analysis file..."))
1909                self.cpanel_on_focus.on_save(event)
1910                wx.PostEvent(self,
1911                             StatusEvent(status="Completed saving."))
1912            except:
1913                msg = "Error occurred while saving: "
1914                msg += "To save, the application panel should have a data set.."
1915                wx.PostEvent(self, StatusEvent(status=msg))
1916
1917    def _on_save_project(self, event):
1918        """
1919        save the state of the SasView as *.svs
1920        """
1921        if self._current_perspective is  None:
1922            return
1923        wx.PostEvent(self, StatusEvent(status="Saving Project file..."))
1924        path = None
1925        extension = '*' + APPLICATION_STATE_EXTENSION
1926        dlg = wx.FileDialog(self, "Save Project file",
1927                            self._default_save_location, "sasview_proj",
1928                            extension,
1929                            wx.SAVE)
1930        if dlg.ShowModal() == wx.ID_OK:
1931            path = dlg.GetPath()
1932            self._default_save_location = os.path.dirname(path)
1933        else:
1934            return None
1935        dlg.Destroy()
1936        try:
1937            if path is None:
1938                return
1939            # default cansas xml doc
1940            doc = None
1941            for panel in self.panels.values():
1942                temp = panel.save_project(doc)
1943                if temp is not None:
1944                    doc = temp
1945
1946            # Write the XML document
1947            extens = APPLICATION_STATE_EXTENSION
1948            fName = os.path.splitext(path)[0] + extens
1949            if doc != None:
1950                fd = open(fName, 'w')
1951                fd.write(doc.toprettyxml())
1952                fd.close()
1953                wx.PostEvent(self, StatusEvent(status="Completed Saving."))
1954            else:
1955                msg = "Error occurred while saving the project: "
1956                msg += "To save, at least one application panel "
1957                msg += "should have a data set "
1958                msg += "and model selected. "
1959                msg += "No project was saved to %s" % (str(path))
1960                logging.error(msg)
1961                wx.PostEvent(self, StatusEvent(status=msg))
1962        except:
1963            msg = "Error occurred while saving: "
1964            msg += "To save, at least one application panel "
1965            msg += "should have a data set.."
1966            wx.PostEvent(self, StatusEvent(status=msg))
1967
1968    def on_save_helper(self, doc, reader, panel, path):
1969        """
1970        Save state into a file
1971        """
1972        if reader is not None:
1973            # case of a panel with multi-pages
1974            if hasattr(panel, "opened_pages"):
1975                for _, page in panel.opened_pages.iteritems():
1976                    data = page.get_data()
1977                    # state must be cloned
1978                    state = page.get_state().clone()
1979                    if data is not None:
1980                        new_doc = reader.write_toXML(data, state)
1981                        if doc != None and hasattr(doc, "firstChild"):
1982                            child = new_doc.firstChild.firstChild
1983                            doc.firstChild.appendChild(child)
1984                        else:
1985                            doc = new_doc
1986            # case of only a panel
1987            else:
1988                data = panel.get_data()
1989                state = panel.get_state()
1990                if data is not None:
1991                    new_doc = reader.write_toXML(data, state)
1992                    if doc != None and hasattr(doc, "firstChild"):
1993                        child = new_doc.firstChild.firstChild
1994                        doc.firstChild.appendChild(child)
1995                    else:
1996                        doc = new_doc
1997        return doc
1998
1999    def quit_guiframe(self):
2000        """
2001        Pop up message to make sure the user wants to quit the application
2002        """
2003        message = "\nDo you really want to exit this application?        \n\n"
2004        dial = wx.MessageDialog(self, message, 'Confirm Exit',
2005                                wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
2006        if dial.ShowModal() == wx.ID_YES:
2007            return True
2008        else:
2009            return False
2010
2011    def WindowClose(self, event=None):
2012        """
2013        Quit the application from x icon
2014        """
2015        flag = self.quit_guiframe()
2016        if flag:
2017            _pylab_helpers.Gcf.figs = {}
2018            self.Close()
2019
2020    def Close(self, event=None):
2021        """
2022        Quit the application
2023        """
2024        logging.info(" --- SasView session was closed --- \n")
2025        wx.Exit()
2026        sys.exit()
2027
2028    def _check_update(self, event=None):
2029        """
2030        Check with the deployment server whether a new version
2031        of the application is available.
2032        A thread is started for the connecting with the server. The thread calls
2033        a call-back method when the current version number has been obtained.
2034        """
2035        try:
2036            conn = httplib.HTTPConnection(config.__update_URL__[0],
2037                                          timeout=3.0)
2038            conn.request("GET", config.__update_URL__[1])
2039            res = conn.getresponse()
2040            content = res.read()
2041            conn.close()
2042        except:
2043            content = "0.0.0"
2044
2045        version = content.strip()
2046        if len(re.findall('\d+\.\d+\.\d+$', version)) < 0:
2047            content = "0.0.0"
2048        self._process_version(content, standalone=event == None)
2049
2050    def _process_version(self, version, standalone=True):
2051        """
2052        Call-back method for the process of checking for updates.
2053        This methods is called by a VersionThread object once the current
2054        version number has been obtained. If the check is being done in the
2055        background, the user will not be notified unless there's an update.
2056
2057        :param version: version string
2058        :param standalone: True of the update is being checked in
2059           the background, False otherwise.
2060
2061        """
2062        try:
2063            if version == "0.0.0":
2064                msg = "Could not connect to the application server."
2065                msg += " Please try again later."
2066                self.SetStatusText(msg)
2067            elif cmp(version, config.__version__) > 0:
2068                msg = "Version %s is available! " % str(version)
2069                if not standalone:
2070                    import webbrowser
2071                    webbrowser.open(config.__download_page__)
2072                else:
2073                    msg += "See the help menu to download it."
2074                self.SetStatusText(msg)
2075            else:
2076                if not standalone:
2077                    msg = "You have the latest version"
2078                    msg += " of %s" % str(config.__appname__)
2079                    self.SetStatusText(msg)
2080        except:
2081            msg = "guiframe: could not get latest application"
2082            msg += " version number\n  %s" % sys.exc_value
2083            logging.error(msg)
2084            if not standalone:
2085                msg = "Could not connect to the application server."
2086                msg += " Please try again later."
2087                self.SetStatusText(msg)
2088
2089    def _onAcknowledge(self, evt):
2090        """
2091        Pop up the acknowledge dialog
2092
2093        :param evt: menu event
2094
2095        """
2096        if config._do_acknowledge:
2097            import sas.guiframe.acknowledgebox as AcknowledgeBox
2098            dialog = AcknowledgeBox.DialogAcknowledge(None, -1, "")
2099            dialog.ShowModal()
2100
2101    def _onAbout(self, evt):
2102        """
2103        Pop up the about dialog
2104
2105        :param evt: menu event
2106
2107        """
2108        if config._do_aboutbox:
2109            import sas.guiframe.aboutbox as AboutBox
2110            dialog = AboutBox.DialogAbout(None, -1, "")
2111            dialog.ShowModal()
2112
2113    def _onTutorial(self, evt):
2114        """
2115        Pop up the tutorial dialog
2116
2117        :param evt: menu event
2118
2119        """
2120        if config._do_tutorial:
2121            path = config.TUTORIAL_PATH
2122            if IS_WIN:
2123                try:
2124                    from sas.guiframe.pdfview import PDFFrame
2125                    dialog = PDFFrame(None, -1, "Tutorial", path)
2126                    # put icon
2127                    self.put_icon(dialog)
2128                    dialog.Show(True)
2129                except:
2130                    logging.error("Error in _onTutorial: %s" % sys.exc_value)
2131                    try:
2132                        # Try an alternate method
2133                        logging.error("Could not open the tutorial pdf, trying xhtml2pdf")
2134                        from xhtml2pdf import pisa
2135                        pisa.startViewer(path)
2136                    except:
2137                        logging.error("Could not open the tutorial pdf with xhtml2pdf")
2138                        msg = "This feature requires 'PDF Viewer'\n"
2139                        wx.MessageBox(msg, 'Error')
2140            else:
2141                try:
2142                    command = "open '%s'" % path
2143                    os.system(command)
2144                except:
2145                    try:
2146                        # Try an alternate method
2147                        logging.error("Could not open the tutorial pdf, trying xhtml2pdf")
2148                        from xhtml2pdf import pisa
2149                        pisa.startViewer(path)
2150                    except:
2151                        logging.error("Could not open the tutorial pdf with xhtml2pdf")
2152                        msg = "This feature requires the 'Preview' application\n"
2153                        wx.MessageBox(msg, 'Error')
2154
2155    def _onSphinxDocs(self, evt):
2156        """
2157        Bring up Sphinx Documentation at top level whenever the menu item
2158        'documentation' is clicked. Calls DocumentationWindow with the top
2159        level path of "index.html"
2160
2161        :param evt: menu event
2162        """
2163        # Running SasView "in-place" using run.py means the docs will be in a
2164        # different place than they would otherwise.
2165        from documentation_window import DocumentationWindow
2166        DocumentationWindow(self, -1, "index.html", "General Help")
2167
2168    def set_manager(self, manager):
2169        """
2170        Sets the application manager for this frame
2171
2172        :param manager: frame manager
2173        """
2174        self.app_manager = manager
2175
2176    def post_init(self):
2177        """
2178        This initialization method is called after the GUI
2179        has been created and all plug-ins loaded. It calls
2180        the post_init() method of each plug-in (if it exists)
2181        so that final initialization can be done.
2182        """
2183        for item in self.plugins:
2184            if hasattr(item, "post_init"):
2185                item.post_init()
2186
2187    def set_default_perspective(self):
2188        """
2189        Choose among the plugin the first plug-in that has
2190        "set_default_perspective" method and its return value is True will be
2191        as a default perspective when the welcome page is closed
2192        """
2193        for item in self.plugins:
2194            if hasattr(item, "set_default_perspective"):
2195                if item.set_default_perspective():
2196                    item.on_perspective(event=None)
2197                    return
2198
2199    def set_perspective(self, panels):
2200        """
2201        Sets the perspective of the GUI.
2202        Opens all the panels in the list, and closes
2203        all the others.
2204
2205        :param panels: list of panels
2206        """
2207        for item in self.panels.keys():
2208            # Check whether this is a sticky panel
2209            if hasattr(self.panels[item], "ALWAYS_ON"):
2210                if self.panels[item].ALWAYS_ON:
2211                    continue
2212            if self.panels[item] == None:
2213                continue
2214            if self.panels[item].window_name in panels:
2215                frame = self.panels[item].get_frame()
2216                if not frame.IsShown():
2217                    frame.Show(True)
2218            else:
2219                # always show the data panel if enable
2220                style = self.__gui_style & GUIFRAME.MANAGER_ON
2221                if (style == GUIFRAME.MANAGER_ON) and self.panels[item] == self._data_panel:
2222                    if 'data_panel' in self.panels.keys():
2223                        frame = self.panels['data_panel'].get_frame()
2224                        if frame == None:
2225                            continue
2226                        flag = frame.IsShown()
2227                        frame.Show(flag)
2228                else:
2229                    frame = self.panels[item].get_frame()
2230                    if frame == None:
2231                        continue
2232
2233                    if frame.IsShown():
2234                        frame.Show(False)
2235
2236    def show_data_panel(self, event=None, action=True):
2237        """
2238        show the data panel
2239        """
2240        if self._data_panel_menu == None:
2241            return
2242        label = self._data_panel_menu.GetText()
2243        pane = self.panels["data_panel"]
2244        frame = pane.get_frame()
2245        if label == 'Show Data Explorer':
2246            if action:
2247                frame.Show(True)
2248            self.__gui_style = self.__gui_style | GUIFRAME.MANAGER_ON
2249            self._data_panel_menu.SetText('Hide Data Explorer')
2250        else:
2251            if action:
2252                frame.Show(False)
2253            self.__gui_style = self.__gui_style & (~GUIFRAME.MANAGER_ON)
2254            self._data_panel_menu.SetText('Show Data Explorer')
2255
2256    def add_data_helper(self, data_list):
2257        """
2258        """
2259        if self._data_manager is not None:
2260            self._data_manager.add_data(data_list)
2261
2262    def add_data(self, data_list):
2263        """
2264        receive a dictionary of data from loader
2265        store them its data manager if possible
2266        send to data the current active perspective if the data panel
2267        is not active.
2268        :param data_list: dictionary of data's ID and value Data
2269        """
2270        #Store data into manager
2271        self.add_data_helper(data_list)
2272        # set data in the data panel
2273        if self._data_panel is not None:
2274            data_state = self._data_manager.get_data_state(data_list.keys())
2275            self._data_panel.load_data_list(data_state)
2276        #if the data panel is shown wait for the user to press a button
2277        #to send data to the current perspective. if the panel is not
2278        #show  automatically send the data to the current perspective
2279        style = self.__gui_style & GUIFRAME.MANAGER_ON
2280        if style == GUIFRAME.MANAGER_ON:
2281            #wait for button press from the data panel to set_data
2282            if self._data_panel is not None:
2283                self._data_panel.frame.Show(True)
2284        else:
2285            #automatically send that to the current perspective
2286            self.set_data(data_id=data_list.keys())
2287
2288    def set_data(self, data_id, theory_id=None):
2289        """
2290        set data to current perspective
2291        """
2292        list_data, _ = self._data_manager.get_by_id(data_id)
2293        if self._current_perspective is not None:
2294            self._current_perspective.set_data(list_data.values())
2295
2296        else:
2297            msg = "Guiframe does not have a current perspective"
2298            logging.info(msg)
2299
2300    def set_theory(self, state_id, theory_id=None):
2301        """
2302        """
2303        _, list_theory = self._data_manager.get_by_id(theory_id)
2304        if self._current_perspective is not None:
2305            try:
2306                self._current_perspective.set_theory(list_theory.values())
2307            except:
2308                msg = "Guiframe set_theory: \n" + str(sys.exc_value)
2309                logging.info(msg)
2310                wx.PostEvent(self, StatusEvent(status=msg, info="error"))
2311        else:
2312            msg = "Guiframe does not have a current perspective"
2313            logging.info(msg)
2314
2315    def plot_data(self, state_id, data_id=None,
2316                  theory_id=None, append=False):
2317        """
2318        send a list of data to plot
2319        """
2320        data_list, _ = self._data_manager.get_by_id(data_id)
2321        _, temp_list_theory = self._data_manager.get_by_id(theory_id)
2322        total_plot_list = data_list.values()
2323        for item in temp_list_theory.values():
2324            theory_data, theory_state = item
2325            total_plot_list.append(theory_data)
2326        GROUP_ID = wx.NewId()
2327        for new_plot in total_plot_list:
2328            if append:
2329                if self.panel_on_focus is None:
2330                    message = "cannot append plot. No plot panel on focus!"
2331                    message += "please click on any available plot to set focus"
2332                    wx.PostEvent(self, StatusEvent(status=message,
2333                                                   info='warning'))
2334                    return
2335                else:
2336                    if self.enable_add_data(new_plot):
2337                        new_plot.group_id = self.panel_on_focus.group_id
2338                    else:
2339                        message = "Only 1D Data can be append to"
2340                        message += " plot panel containing 1D data.\n"
2341                        message += "%s not be appended.\n" % str(new_plot.name)
2342                        message += "try new plot option.\n"
2343                        wx.PostEvent(self, StatusEvent(status=message,
2344                                                       info='warning'))
2345            else:
2346                #if not append then new plot
2347                from sas.guiframe.dataFitting import Data2D
2348                if issubclass(Data2D, new_plot.__class__):
2349                    #for 2 D always plot in a separated new plot
2350                    new_plot.group_id = wx.NewId()
2351                else:
2352                    # plot all 1D in a new plot
2353                    new_plot.group_id = GROUP_ID
2354            title = "PLOT " + str(new_plot.title)
2355            wx.PostEvent(self, NewPlotEvent(plot=new_plot,
2356                                            title=title,
2357                                            group_id=new_plot.group_id))
2358
2359    def remove_data(self, data_id, theory_id=None):
2360        """
2361        Delete data state if data_id is provide
2362        delete theory created with data of id data_id if theory_id is provide
2363        if delete all true: delete the all state
2364        else delete theory
2365        """
2366        temp = data_id + theory_id
2367        for plug in self.plugins:
2368            plug.delete_data(temp)
2369        data_list, _ = self._data_manager.get_by_id(data_id)
2370        _, temp_list_theory = self._data_manager.get_by_id(theory_id)
2371        total_plot_list = data_list.values()
2372        for item in temp_list_theory.values():
2373            theory_data, theory_state = item
2374            total_plot_list.append(theory_data)
2375        for new_plot in total_plot_list:
2376            id = new_plot.id
2377            for group_id in new_plot.list_group_id:
2378                wx.PostEvent(self, NewPlotEvent(id=id,
2379                                                group_id=group_id,
2380                                                action='remove'))
2381                #remove res plot: Todo: improve
2382                wx.CallAfter(self._remove_res_plot, id)
2383        self._data_manager.delete_data(data_id=data_id,
2384                                       theory_id=theory_id)
2385
2386    def _remove_res_plot(self, id):
2387        """
2388        Try to remove corresponding res plot
2389
2390        : param id: id of the data
2391        """
2392        try:
2393            wx.PostEvent(self, NewPlotEvent(id=("res" + str(id)),
2394                                            group_id=("res" + str(id)),
2395                                            action='remove'))
2396        except:
2397            logging.error(sys.exc_value)
2398
2399    def save_data1d(self, data, fname):
2400        """
2401        Save data dialog
2402        """
2403        default_name = fname
2404        wildcard = "Text files (*.txt)|*.txt|"\
2405                    "CanSAS 1D files(*.xml)|*.xml"
2406        path = None
2407        dlg = wx.FileDialog(self, "Choose a file",
2408                            self._default_save_location,
2409                            default_name, wildcard, wx.SAVE)
2410
2411        if dlg.ShowModal() == wx.ID_OK:
2412            path = dlg.GetPath()
2413            # ext_num = 0 for .txt, ext_num = 1 for .xml
2414            # This is MAC Fix
2415            ext_num = dlg.GetFilterIndex()
2416            if ext_num == 0:
2417                format = '.txt'
2418            else:
2419                format = '.xml'
2420            path = os.path.splitext(path)[0] + format
2421            mypath = os.path.basename(path)
2422
2423            #Instantiate a loader
2424            loader = Loader()
2425            format = ".txt"
2426            if os.path.splitext(mypath)[1].lower() == format:
2427                # Make sure the ext included in the file name
2428                # especially on MAC
2429                fName = os.path.splitext(path)[0] + format
2430                self._onsaveTXT(data, fName)
2431            format = ".xml"
2432            if os.path.splitext(mypath)[1].lower() == format:
2433                # Make sure the ext included in the file name
2434                # especially on MAC
2435                fName = os.path.splitext(path)[0] + format
2436                loader.save(fName, data, format)
2437            try:
2438                self._default_save_location = os.path.dirname(path)
2439            except:
2440                pass
2441        dlg.Destroy()
2442
2443
2444    def _onsaveTXT(self, data, path):
2445        """
2446        Save file as txt
2447        :TODO: Refactor and remove this method. See TODO in _onSave.
2448        """
2449        if not path == None:
2450            out = open(path, 'w')
2451            has_errors = True
2452            if data.dy == None or data.dy == []:
2453                has_errors = False
2454            # Sanity check
2455            if has_errors:
2456                try:
2457                    if len(data.y) != len(data.dy):
2458                        has_errors = False
2459                except:
2460                    has_errors = False
2461            if has_errors:
2462                if data.dx != None and data.dx != []:
2463                    out.write("<X>   <Y>   <dY>   <dX>\n")
2464                else:
2465                    out.write("<X>   <Y>   <dY>\n")
2466            else:
2467                out.write("<X>   <Y>\n")
2468
2469            for i in range(len(data.x)):
2470                if has_errors:
2471                    if data.dx != None and data.dx != []:
2472                        if  data.dx[i] != None:
2473                            out.write("%g  %g  %g  %g\n" % (data.x[i],
2474                                                            data.y[i],
2475                                                            data.dy[i],
2476                                                            data.dx[i]))
2477                        else:
2478                            out.write("%g  %g  %g\n" % (data.x[i],
2479                                                        data.y[i],
2480                                                        data.dy[i]))
2481                    else:
2482                        out.write("%g  %g  %g\n" % (data.x[i],
2483                                                    data.y[i],
2484                                                    data.dy[i]))
2485                else:
2486                    out.write("%g  %g\n" % (data.x[i],
2487                                            data.y[i]))
2488            out.close()
2489
2490    def show_data1d(self, data, name):
2491        """
2492        Show data dialog
2493        """
2494        try:
2495            xmin = min(data.x)
2496            ymin = min(data.y)
2497        except:
2498            msg = "Unable to find min/max of \n data named %s" % \
2499                        data.filename
2500            wx.PostEvent(self, StatusEvent(status=msg,
2501                                           info="error"))
2502            raise ValueError, msg
2503        ## text = str(data)
2504        text = data.__str__()
2505        text += 'Data Min Max:\n'
2506        text += 'X_min = %s:  X_max = %s\n' % (xmin, max(data.x))
2507        text += 'Y_min = %s:  Y_max = %s\n' % (ymin, max(data.y))
2508        if data.dy != None:
2509            text += 'dY_min = %s:  dY_max = %s\n' % (min(data.dy), max(data.dy))
2510        text += '\nData Points:\n'
2511        x_st = "X"
2512        for index in range(len(data.x)):
2513            if data.dy != None and len(data.dy) > index:
2514                dy_val = data.dy[index]
2515            else:
2516                dy_val = 0.0
2517            if data.dx != None and len(data.dx) > index:
2518                dx_val = data.dx[index]
2519            else:
2520                dx_val = 0.0
2521            if data.dxl != None and len(data.dxl) > index:
2522                if index == 0:
2523                    x_st = "Xl"
2524                dx_val = data.dxl[index]
2525            elif data.dxw != None and len(data.dxw) > index:
2526                if index == 0:
2527                    x_st = "Xw"
2528                dx_val = data.dxw[index]
2529
2530            if index == 0:
2531                text += "<index> \t<X> \t<Y> \t<dY> \t<d%s>\n" % x_st
2532            text += "%s \t%s \t%s \t%s \t%s\n" % (index,
2533                                                  data.x[index],
2534                                                  data.y[index],
2535                                                  dy_val,
2536                                                  dx_val)
2537        from pdfview import TextFrame
2538        frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
2539        # put icon
2540        self.put_icon(frame)
2541        frame.Show(True)
2542
2543    def save_data2d(self, data, fname):
2544        """
2545        Save data2d dialog
2546        """
2547        default_name = fname
2548        wildcard = "IGOR/DAT 2D file in Q_map (*.dat)|*.DAT"
2549        dlg = wx.FileDialog(self, "Choose a file",
2550                            self._default_save_location,
2551                            default_name, wildcard, wx.SAVE)
2552
2553        if dlg.ShowModal() == wx.ID_OK:
2554            path = dlg.GetPath()
2555            # ext_num = 0 for .txt, ext_num = 1 for .xml
2556            # This is MAC Fix
2557            ext_num = dlg.GetFilterIndex()
2558            if ext_num == 0:
2559                format = '.dat'
2560            else:
2561                format = ''
2562            path = os.path.splitext(path)[0] + format
2563            mypath = os.path.basename(path)
2564
2565            #Instantiate a loader
2566            loader = Loader()
2567
2568            format = ".dat"
2569            if os.path.splitext(mypath)[1].lower() == format:
2570                # Make sure the ext included in the file name
2571                # especially on MAC
2572                fileName = os.path.splitext(path)[0] + format
2573                loader.save(fileName, data, format)
2574            try:
2575                self._default_save_location = os.path.dirname(path)
2576            except:
2577                pass
2578        dlg.Destroy()
2579
2580    def show_data2d(self, data, name):
2581        """
2582        Show data dialog
2583        """
2584        wx.PostEvent(self, StatusEvent(status="Gathering Data2D Info.",
2585                                       type='start'))
2586        text = data.__str__()
2587        text += 'Data Min Max:\n'
2588        text += 'I_min = %s\n' % min(data.data)
2589        text += 'I_max = %s\n\n' % max(data.data)
2590        text += 'Data (First 2501) Points:\n'
2591        text += 'Data columns include err(I).\n'
2592        text += 'ASCII data starts here.\n'
2593        text += "<index> \t<Qx> \t<Qy> \t<I> \t<dI> \t<dQparal> \t<dQperp>\n"
2594        di_val = 0.0
2595        dx_val = 0.0
2596        dy_val = 0.0
2597        len_data = len(data.qx_data)
2598        for index in xrange(0, len_data):
2599            x_val = data.qx_data[index]
2600            y_val = data.qy_data[index]
2601            i_val = data.data[index]
2602            if data.err_data != None:
2603                di_val = data.err_data[index]
2604            if data.dqx_data != None:
2605                dx_val = data.dqx_data[index]
2606            if data.dqy_data != None:
2607                dy_val = data.dqy_data[index]
2608
2609            text += "%s \t%s \t%s \t%s \t%s \t%s \t%s\n" % (index,
2610                                                            x_val,
2611                                                            y_val,
2612                                                            i_val,
2613                                                            di_val,
2614                                                            dx_val,
2615                                                            dy_val)
2616            # Takes too long time for typical data2d: Break here
2617            if index >= 2500:
2618                text += ".............\n"
2619                break
2620
2621        from pdfview import TextFrame
2622        frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
2623        # put icon
2624        self.put_icon(frame)
2625        frame.Show(True)
2626        wx.PostEvent(self, StatusEvent(status="Data2D Info Displayed",
2627                                       type='stop'))
2628
2629    def set_current_perspective(self, perspective):
2630        """
2631        set the current active perspective
2632        """
2633        self._current_perspective = perspective
2634        name = "No current analysis selected"
2635        if self._current_perspective is not None:
2636            self._add_current_plugin_menu()
2637            for panel in self.panels.values():
2638                if hasattr(panel, 'CENTER_PANE') and panel.CENTER_PANE:
2639                    for name in self._current_perspective.get_perspective():
2640                        frame = panel.get_frame()
2641                        if frame != None:
2642                            if name == panel.window_name:
2643                                panel.on_set_focus(event=None)
2644                                frame.Show(True)
2645                            else:
2646                                frame.Show(False)
2647            name = self._current_perspective.sub_menu
2648            if self._data_panel is not None:
2649                self._data_panel.set_active_perspective(name)
2650                self._check_applications_menu()
2651            #Set the SasView title
2652            self._set_title_name(name)
2653
2654    def _set_title_name(self, name):
2655        """
2656        Set the SasView title w/ the current application name
2657
2658        : param name: application name [string]
2659        """
2660        # Set SanView Window title w/ application anme
2661        title = self.title + "  - " + name + " -"
2662        self.SetTitle(title)
2663
2664    def _check_applications_menu(self):
2665        """
2666        check the menu of the current application
2667        """
2668        if self._applications_menu is not None:
2669            for menu in self._applications_menu.GetMenuItems():
2670                if self._current_perspective is not None:
2671                    name = self._current_perspective.sub_menu
2672                    if menu.IsCheckable():
2673                        if menu.GetLabel() == name:
2674                            menu.Check(True)
2675                        else:
2676                            menu.Check(False)
2677
2678    def enable_add_data(self, new_plot):
2679        """
2680        Enable append data on a plot panel
2681        """
2682
2683        if self.panel_on_focus not in self._plotting_plugin.plot_panels.values():
2684            return
2685        is_theory = len(self.panel_on_focus.plots) <= 1 and \
2686            self.panel_on_focus.plots.values()[0].__class__.__name__ == "Theory1D"
2687
2688        is_data2d = hasattr(new_plot, 'data')
2689
2690        is_data1d = self.panel_on_focus.__class__.__name__ == "ModelPanel1D"\
2691            and self.panel_on_focus.group_id is not None
2692        has_meta_data = hasattr(new_plot, 'meta_data')
2693
2694        #disable_add_data if the data is being recovered from  a saved state file.
2695        is_state_data = False
2696        if has_meta_data:
2697            if 'invstate' in new_plot.meta_data:
2698                is_state_data = True
2699            if  'prstate' in new_plot.meta_data:
2700                is_state_data = True
2701            if  'fitstate' in new_plot.meta_data:
2702                is_state_data = True
2703
2704        return is_data1d and not is_data2d and not is_theory and not is_state_data
2705
2706    def check_multimode(self, perspective=None):
2707        """
2708        Check the perspective have batch mode capablitity
2709        """
2710        if perspective == None or self._data_panel == None:
2711            return
2712        flag = perspective.get_batch_capable()
2713        flag_on = perspective.batch_on
2714        if flag:
2715            self._data_panel.rb_single_mode.SetValue(not flag_on)
2716            self._data_panel.rb_batch_mode.SetValue(flag_on)
2717        else:
2718            self._data_panel.rb_single_mode.SetValue(True)
2719            self._data_panel.rb_batch_mode.SetValue(False)
2720        self._data_panel.rb_single_mode.Enable(flag)
2721        self._data_panel.rb_batch_mode.Enable(flag)
2722
2723    def enable_edit_menu(self):
2724        """
2725        enable menu item under edit menu depending on the panel on focus
2726        """
2727        if self.cpanel_on_focus is not None and self._edit_menu is not None:
2728            flag = self.cpanel_on_focus.get_undo_flag()
2729            self._edit_menu.Enable(GUIFRAME_ID.UNDO_ID, flag)
2730            flag = self.cpanel_on_focus.get_redo_flag()
2731            self._edit_menu.Enable(GUIFRAME_ID.REDO_ID, flag)
2732            flag = self.cpanel_on_focus.get_copy_flag()
2733            self._edit_menu.Enable(GUIFRAME_ID.COPY_ID, flag)
2734            flag = self.cpanel_on_focus.get_paste_flag()
2735            self._edit_menu.Enable(GUIFRAME_ID.PASTE_ID, flag)
2736
2737            #Copy menu
2738            flag = self.cpanel_on_focus.get_copy_flag()
2739            self._edit_menu_copyas.Enable(GUIFRAME_ID.COPYEX_ID, flag)
2740            self._edit_menu_copyas.Enable(GUIFRAME_ID.COPYLAT_ID, flag)
2741
2742            flag = self.cpanel_on_focus.get_preview_flag()
2743            self._edit_menu.Enable(GUIFRAME_ID.PREVIEW_ID, flag)
2744            flag = self.cpanel_on_focus.get_reset_flag()
2745            self._edit_menu.Enable(GUIFRAME_ID.RESET_ID, flag)
2746        else:
2747            flag = False
2748            self._edit_menu.Enable(GUIFRAME_ID.UNDO_ID, flag)
2749            self._edit_menu.Enable(GUIFRAME_ID.REDO_ID, flag)
2750            self._edit_menu.Enable(GUIFRAME_ID.COPY_ID, flag)
2751            self._edit_menu.Enable(GUIFRAME_ID.PASTE_ID, flag)
2752            self._edit_menu.Enable(GUIFRAME_ID.PREVIEW_ID, flag)
2753            self._edit_menu.Enable(GUIFRAME_ID.RESET_ID, flag)
2754
2755    def on_undo_panel(self, event=None):
2756        """
2757        undo previous action of the last panel on focus if possible
2758        """
2759        if self.cpanel_on_focus is not None:
2760            self.cpanel_on_focus.on_undo(event)
2761
2762    def on_redo_panel(self, event=None):
2763        """
2764        redo the last cancel action done on the last panel on focus
2765        """
2766        if self.cpanel_on_focus is not None:
2767            self.cpanel_on_focus.on_redo(event)
2768
2769    def on_copy_panel(self, event=None):
2770        """
2771        copy the last panel on focus if possible
2772        """
2773        if self.cpanel_on_focus is not None:
2774            self.cpanel_on_focus.on_copy(event)
2775
2776    def on_paste_panel(self, event=None):
2777        """
2778        paste clipboard to the last panel on focus
2779        """
2780        if self.cpanel_on_focus is not None:
2781            self.cpanel_on_focus.on_paste(event)
2782
2783    def on_bookmark_panel(self, event=None):
2784        """
2785        bookmark panel
2786        """
2787        if self.cpanel_on_focus is not None:
2788            self.cpanel_on_focus.on_bookmark(event)
2789
2790    def append_bookmark(self, event=None):
2791        """
2792        Bookmark available information of the panel on focus
2793        """
2794        self._toolbar.append_bookmark(event)
2795
2796    def on_save_panel(self, event=None):
2797        """
2798        save possible information on the current panel
2799        """
2800        if self.cpanel_on_focus is not None:
2801            self.cpanel_on_focus.on_save(event)
2802
2803    def on_preview_panel(self, event=None):
2804        """
2805        preview information on the panel on focus
2806        """
2807        if self.cpanel_on_focus is not None:
2808            self.cpanel_on_focus.on_preview(event)
2809
2810    def on_print_panel(self, event=None):
2811        """
2812        print available information on the last panel on focus
2813        """
2814        if self.cpanel_on_focus is not None:
2815            self.cpanel_on_focus.on_print(event)
2816
2817    def on_zoom_panel(self, event=None):
2818        """
2819        zoom on the current panel if possible
2820        """
2821        if self.cpanel_on_focus is not None:
2822            self.cpanel_on_focus.on_zoom(event)
2823
2824    def on_zoom_in_panel(self, event=None):
2825        """
2826        zoom in of the panel on focus
2827        """
2828        if self.cpanel_on_focus is not None:
2829            self.cpanel_on_focus.on_zoom_in(event)
2830
2831    def on_zoom_out_panel(self, event=None):
2832        """
2833        zoom out on the panel on focus
2834        """
2835        if self.cpanel_on_focus is not None:
2836            self.cpanel_on_focus.on_zoom_out(event)
2837
2838    def on_drag_panel(self, event=None):
2839        """
2840        drag apply to the panel on focus
2841        """
2842        if self.cpanel_on_focus is not None:
2843            self.cpanel_on_focus.on_drag(event)
2844
2845    def on_reset_panel(self, event=None):
2846        """
2847        reset the current panel
2848        """
2849        if self.cpanel_on_focus is not None:
2850            self.cpanel_on_focus.on_reset(event)
2851
2852    def on_change_caption(self, name, old_caption, new_caption):
2853        """
2854        Change the panel caption
2855
2856        :param name: window_name of the pane
2857        :param old_caption: current caption [string]
2858        :param new_caption: new caption [string]
2859        """
2860        # wx.aui.AuiPaneInfo
2861        pane_info = self.get_paneinfo(old_caption)
2862        # update the data_panel.cb_plotpanel
2863        if 'data_panel' in self.panels.keys():
2864            # remove from data_panel combobox
2865            data_panel = self.panels["data_panel"]
2866            if data_panel.cb_plotpanel is not None:
2867                # Check if any panel has the same caption
2868                has_newstring = data_panel.cb_plotpanel.FindString\
2869                                                            (str(new_caption))
2870                caption = new_caption
2871                if has_newstring != wx.NOT_FOUND:
2872                    captions = self._get_plotpanel_captions()
2873                    # Append nummber
2874                    inc = 1
2875                    #FIXME: fix this terrible loop
2876                    while (1):
2877                        caption = new_caption + '_%s' % str(inc)
2878                        if caption not in captions:
2879                            break
2880                        inc += 1
2881                    # notify to users
2882                    msg = "Found Same Title: Added '_%s'" % str(inc)
2883                    wx.PostEvent(self, StatusEvent(status=msg))
2884                # update data_panel cb
2885                pos = data_panel.cb_plotpanel.FindString(str(old_caption))
2886                if pos != wx.NOT_FOUND:
2887                    data_panel.cb_plotpanel.SetString(pos, caption)
2888                    data_panel.cb_plotpanel.SetStringSelection(caption)
2889        # New Caption
2890        pane_info.SetTitle(caption)
2891        return caption
2892
2893    def get_paneinfo(self, name):
2894        """
2895        Get pane Caption from window_name
2896
2897        :param name: window_name in AuiPaneInfo
2898        : return: AuiPaneInfo of the name
2899        """
2900        for panel in self.plot_panels.values():
2901            if panel.frame.GetTitle() == name:
2902                return panel.frame
2903        return None
2904
2905    def enable_undo(self):
2906        """
2907        enable undo related control
2908        """
2909        if self.cpanel_on_focus is not None:
2910            self._toolbar.enable_undo(self.cpanel_on_focus)
2911
2912    def enable_redo(self):
2913        """
2914        enable redo
2915        """
2916        if self.cpanel_on_focus is not None:
2917            self._toolbar.enable_redo(self.cpanel_on_focus)
2918
2919    def enable_copy(self):
2920        """
2921        enable copy related control
2922        """
2923        if self.cpanel_on_focus is not None:
2924            self._toolbar.enable_copy(self.cpanel_on_focus)
2925
2926    def enable_paste(self):
2927        """
2928        enable paste
2929        """
2930        if self.cpanel_on_focus is not None:
2931            self._toolbar.enable_paste(self.cpanel_on_focus)
2932
2933    def enable_bookmark(self):
2934        """
2935        Bookmark
2936        """
2937        if self.cpanel_on_focus is not None:
2938            self._toolbar.enable_bookmark(self.cpanel_on_focus)
2939
2940    def enable_save(self):
2941        """
2942        save
2943        """
2944        if self.cpanel_on_focus is not None:
2945            self._toolbar.enable_save(self.cpanel_on_focus)
2946
2947    def enable_preview(self):
2948        """
2949        preview
2950        """
2951        if self.cpanel_on_focus is not None:
2952            self._toolbar.enable_preview(self.cpanel_on_focus)
2953
2954    def enable_print(self):
2955        """
2956        print
2957        """
2958        if self.cpanel_on_focus is not None:
2959            self._toolbar.enable_print(self.cpanel_on_focus)
2960
2961    def enable_zoom(self):
2962        """
2963        zoom
2964        """
2965        if self.cpanel_on_focus is not None:
2966            self._toolbar.enable_zoom(self.panel_on_focus)
2967
2968    def enable_zoom_in(self):
2969        """
2970        zoom in
2971        """
2972        if self.cpanel_on_focus is not None:
2973            self._toolbar.enable_zoom_in(self.panel_on_focus)
2974
2975    def enable_zoom_out(self):
2976        """
2977        zoom out
2978        """
2979        if self.cpanel_on_focus is not None:
2980            self._toolbar.enable_zoom_out(self.panel_on_focus)
2981
2982    def enable_drag(self, event=None):
2983        """
2984        drag
2985        """
2986        #Not implemeted
2987
2988    def enable_reset(self):
2989        """
2990        reset the current panel
2991        """
2992        if self.cpanel_on_focus is not None:
2993            self._toolbar.enable_reset(self.panel_on_focus)
2994
2995    def get_toolbar_height(self):
2996        """
2997        """
2998        size_y = 0
2999        if self.GetToolBar() != None and self.GetToolBar().IsShown():
3000            if not IS_LINUX:
3001                _, size_y = self.GetToolBar().GetSizeTuple()
3002        return size_y
3003
3004    def set_schedule_full_draw(self, panel=None, func='del'):
3005        """
3006        Add/subtract the schedule full draw list with the panel given
3007
3008        :param panel: plot panel
3009        :param func: append or del [string]
3010        """
3011
3012        # append this panel in the schedule list if not in yet
3013        if func == 'append':
3014            if not panel in self.schedule_full_draw_list:
3015                self.schedule_full_draw_list.append(panel)
3016        # remove this panel from schedule list
3017        elif func == 'del':
3018            if len(self.schedule_full_draw_list) > 0:
3019                if panel in self.schedule_full_draw_list:
3020                    self.schedule_full_draw_list.remove(panel)
3021
3022        # reset the schdule
3023        if len(self.schedule_full_draw_list) == 0:
3024            self.schedule = False
3025        else:
3026            self.schedule = True
3027
3028    def full_draw(self):
3029        """
3030        Draw the panels with axes in the schedule to full dwar list
3031        """
3032
3033        count = len(self.schedule_full_draw_list)
3034        #if not self.schedule:
3035        if count < 1:
3036            self.set_schedule(False)
3037            return
3038
3039        else:
3040            ind = 0
3041            # if any of the panel is shown do full_draw
3042            for panel in self.schedule_full_draw_list:
3043                ind += 1
3044                if panel.frame.IsShown():
3045                    break
3046                # otherwise, return
3047                if ind == count:
3048                    return
3049        #Simple redraw only for a panel shown
3050        def f_draw(panel):
3051            """
3052            Draw A panel in the full draw list
3053            """
3054            try:
3055                # This checking of GetCapture is to stop redrawing
3056                # while any panel is capture.
3057                frame = panel.frame
3058
3059                if not frame.GetCapture():
3060                    # draw if possible
3061                    panel.set_resizing(False)
3062                    #panel.Show(True)
3063                    panel.draw_plot()
3064                # Check if the panel is not shown
3065                flag = frame.IsShown()
3066                frame.Show(flag)
3067            except:
3068                pass
3069
3070        # Draw all panels
3071        if count == 1:
3072            f_draw(self.schedule_full_draw_list[0])
3073        else:
3074            map(f_draw, self.schedule_full_draw_list)
3075        # Reset the attr 
3076        if len(self.schedule_full_draw_list) == 0:
3077            self.set_schedule(False)
3078        else:
3079            self.set_schedule(True)
3080
3081    def set_schedule(self, schedule=False):
3082        """
3083        Set schedule
3084        """
3085        self.schedule = schedule
3086
3087    def get_schedule(self):
3088        """
3089        Get schedule
3090        """
3091        return self.schedule
3092
3093    def on_set_plot_focus(self, panel):
3094        """
3095        Set focus on a plot panel
3096        """
3097        if panel == None:
3098            return
3099        #self.set_plot_unfocus()
3100        panel.on_set_focus(None)
3101        # set focusing panel
3102        self.panel_on_focus = panel
3103        self.set_panel_on_focus(None)
3104
3105    def set_plot_unfocus(self):
3106        """
3107        Un focus all plot panels
3108        """
3109        for plot in self.plot_panels.values():
3110            plot.on_kill_focus(None)
3111
3112    def get_window_size(self):
3113        """
3114        Get window size
3115
3116        :return size: tuple
3117        """
3118        width, height = self.GetSizeTuple()
3119        if not IS_WIN:
3120            # Subtract toolbar height to get real window side
3121            if self._toolbar.IsShown():
3122                height -= 45
3123        return (width, height)
3124
3125    def _onDrawIdle(self, *args, **kwargs):
3126        """
3127        ReDraw with axes
3128        """
3129        try:
3130            # check if it is time to redraw
3131            if self.GetCapture() == None:
3132                # Draw plot, changes resizing too
3133                self.full_draw()
3134        except:
3135            pass
3136
3137        # restart idle       
3138        self._redraw_idle(*args, **kwargs)
3139
3140
3141    def _redraw_idle(self, *args, **kwargs):
3142        """
3143        Restart Idle
3144        """
3145        # restart idle   
3146        self.idletimer.Restart(100 * TIME_FACTOR, *args, **kwargs)
3147
3148
3149class DefaultPanel(wx.Panel, PanelBase):
3150    """
3151    Defines the API for a panels to work with
3152    the GUI manager
3153    """
3154    ## Internal nickname for the window, used by the AUI manager
3155    window_name = "default"
3156    ## Name to appear on the window title bar
3157    window_caption = "Welcome panel"
3158    ## Flag to tell the AUI manager to put this panel in the center pane
3159    CENTER_PANE = True
3160    def __init__(self, parent, *args, **kwds):
3161        wx.Panel.__init__(self, parent, *args, **kwds)
3162        PanelBase.__init__(self, parent)
3163
3164
3165
3166class ViewApp(wx.App):
3167    """
3168    Toy application to test this Frame
3169    """
3170    def OnInit(self):
3171        """
3172        When initialised
3173        """
3174        pos, size, self.is_max = self.window_placement((GUIFRAME_WIDTH,
3175                                                        GUIFRAME_HEIGHT))
3176        self.frame = ViewerFrame(parent=None,
3177                                 title=APPLICATION_NAME,
3178                                 pos=pos,
3179                                 gui_style=DEFAULT_STYLE,
3180                                 size=size)
3181        self.frame.Hide()
3182        if not IS_WIN:
3183            self.frame.EnableCloseButton(False)
3184        self.s_screen = None
3185
3186        try:
3187            self.open_file()
3188        except:
3189            msg = "%s Could not load " % str(APPLICATION_NAME)
3190            msg += "input file from command line.\n"
3191            logging.error(msg)
3192        # Display a splash screen on top of the frame.
3193        try:
3194            if os.path.isfile(SPLASH_SCREEN_PATH):
3195                self.s_screen = self.display_splash_screen(parent=self.frame,
3196                                                           path=SPLASH_SCREEN_PATH)
3197            else:
3198                self.frame.Show()
3199        except:
3200            if self.s_screen is not None:
3201                self.s_screen.Close()
3202            msg = "Cannot display splash screen\n"
3203            msg += str(sys.exc_value)
3204            logging.error(msg)
3205            self.frame.Show()
3206
3207        self.SetTopWindow(self.frame)
3208
3209        return True
3210
3211    def maximize_win(self):
3212        """
3213        Maximize the window after the frame shown
3214        """
3215        if self.is_max:
3216            if self.frame.IsShown():
3217                # Max window size
3218                self.frame.Maximize(self.is_max)
3219
3220    def open_file(self):
3221        """
3222        open a state file at the start of the application
3223        """
3224        input_file = None
3225        if len(sys.argv) >= 2:
3226            cmd = sys.argv[0].lower()
3227            basename = os.path.basename(cmd)
3228            app_base = str(APPLICATION_NAME).lower()
3229            if os.path.isfile(cmd) or basename.lower() == app_base:
3230                app_py = app_base + '.py'
3231                app_exe = app_base + '.exe'
3232                app_app = app_base + '.app'
3233                if basename.lower() in [app_py, app_exe, app_app, app_base]:
3234                    data_base = sys.argv[1]
3235                    input_file = os.path.normpath(os.path.join(DATAPATH,
3236                                                               data_base))
3237        if input_file is None:
3238            return
3239        if self.frame is not None:
3240            self.frame.set_input_file(input_file=input_file)
3241
3242    def clean_plugin_models(self, path):
3243        """
3244        Delete plugin models  in app folder
3245
3246        :param path: path of the plugin_models folder in app
3247        """
3248        # do it only the first time app loaded
3249        # delete unused model folder
3250        model_folder = os.path.join(PATH_APP, path)
3251        if os.path.exists(model_folder) and os.path.isdir(model_folder):
3252            if len(os.listdir(model_folder)) > 0:
3253                try:
3254                    for file in os.listdir(model_folder):
3255                        file_path = os.path.join(model_folder, file)
3256                        if os.path.isfile(file_path):
3257                            os.remove(file_path)
3258                except:
3259                    logging.error("gui_manager.clean_plugin_models:\n  %s" \
3260                                  % sys.exc_value)
3261
3262    def set_manager(self, manager):
3263        """
3264        Sets a reference to the application manager
3265        of the GUI manager (Frame)
3266        """
3267        self.frame.set_manager(manager)
3268
3269    def build_gui(self):
3270        """
3271        Build the GUI
3272        """
3273        #try to load file at the start
3274        try:
3275            self.open_file()
3276        except:
3277            raise
3278        self.frame.build_gui()
3279
3280    def set_welcome_panel(self, panel_class):
3281        """
3282        Set the welcome panel
3283
3284        :param panel_class: class of the welcome panel to be instantiated
3285
3286        """
3287        self.frame.welcome_panel_class = panel_class
3288
3289    def add_perspective(self, perspective):
3290        """
3291        Manually add a perspective to the application GUI
3292        """
3293        self.frame.add_perspective(perspective)
3294
3295    def window_placement(self, size):
3296        """
3297        Determines the position and size of the application frame such that it
3298        fits on the user's screen without obstructing (or being obstructed by)
3299        the Windows task bar.  The maximum initial size in pixels is bounded by
3300        WIDTH x HEIGHT.  For most monitors, the application
3301        will be centered on the screen; for very large monitors it will be
3302        placed on the left side of the screen.
3303        """
3304        is_maximized = False
3305        # Get size of screen without
3306        for screenCount in range(wx.Display().GetCount()):
3307            screen = wx.Display(screenCount)
3308            if screen.IsPrimary():
3309                displayRect = screen.GetClientArea()
3310                break
3311
3312        posX, posY, displayWidth, displayHeight = displayRect
3313        customWidth, customHeight = size
3314
3315        # If the custom size is default, set 90% of the screen size
3316        if customWidth <= 0 and customHeight <= 0:
3317            if customWidth == 0 and customHeight == 0:
3318                is_maximized = True
3319            customWidth = displayWidth * 0.9
3320            customHeight = displayHeight * 0.9
3321        else:
3322            # If the custom screen is bigger than the
3323            # window screen than make maximum size
3324            if customWidth > displayWidth:
3325                customWidth = displayWidth
3326            if customHeight > displayHeight:
3327                customHeight = displayHeight
3328
3329        # Note that when running Linux and using an Xming (X11) server on a PC
3330        # with a dual  monitor configuration, the reported display size may be
3331        # that of both monitors combined with an incorrect display count of 1.
3332        # To avoid displaying this app across both monitors, we check for
3333        # screen 'too big'.  If so, we assume a smaller width which means the
3334        # application will be placed towards the left hand side of the screen.
3335
3336        # If dual screen registered as 1 screen. Make width half.
3337        # MAC just follows the default behavior of pos
3338        if IS_WIN:
3339            if displayWidth > (displayHeight * 2):
3340                if customWidth == displayWidth:
3341                    customWidth = displayWidth / 2
3342                # and set the position to be the corner of the screen.
3343                posX = 0
3344                posY = 0
3345
3346            # Make the position the middle of the screen. (Not 0,0)
3347            else:
3348                posX = (displayWidth - customWidth) / 2
3349                posY = (displayHeight - customHeight) / 2
3350        # Return the suggested position and size for the application frame.
3351        return (posX, posY), (customWidth, customHeight), is_maximized
3352
3353
3354    def display_splash_screen(self, parent,
3355                              path=SPLASH_SCREEN_PATH):
3356        """Displays the splash screen.  It will exactly cover the main frame."""
3357
3358        # Prepare the picture.  On a 2GHz intel cpu, this takes about a second.
3359        image = wx.Image(path, wx.BITMAP_TYPE_PNG)
3360        image.Rescale(SPLASH_SCREEN_WIDTH,
3361                      SPLASH_SCREEN_HEIGHT, wx.IMAGE_QUALITY_HIGH)
3362        bm = image.ConvertToBitmap()
3363
3364        # Create and show the splash screen.  It will disappear only when the
3365        # program has entered the event loop AND either the timeout has expired
3366        # or the user has left clicked on the screen.  Thus any processing
3367        # performed in this routine (including sleeping) or processing in the
3368        # calling routine (including doing imports) will prevent the splash
3369        # screen from disappearing.
3370        #
3371        # Note that on Linux, the timeout appears to occur immediately in which
3372        # case the splash screen disappears upon entering the event loop.
3373        s_screen = wx.SplashScreen(bitmap=bm,
3374                                   splashStyle=(wx.SPLASH_TIMEOUT |
3375                                                wx.SPLASH_CENTRE_ON_SCREEN),
3376                                   style=(wx.SIMPLE_BORDER |
3377                                          wx.FRAME_NO_TASKBAR |
3378                                          wx.FRAME_FLOAT_ON_PARENT),
3379                                   milliseconds=SS_MAX_DISPLAY_TIME,
3380                                   parent=parent,
3381                                   id=wx.ID_ANY)
3382        from sas.guiframe.gui_statusbar import SPageStatusbar
3383        statusBar = SPageStatusbar(s_screen)
3384        s_screen.SetStatusBar(statusBar)
3385        s_screen.Bind(wx.EVT_CLOSE, self.on_close_splash_screen)
3386        s_screen.Show()
3387        return s_screen
3388
3389
3390    def on_close_splash_screen(self, event):
3391        """
3392        When the splash screen is closed.
3393        """
3394        self.frame.Show(True)
3395        event.Skip()
3396        self.maximize_win()
3397
3398
3399class MDIFrame(CHILD_FRAME):
3400    """
3401    Frame for panels
3402    """
3403    def __init__(self, parent, panel, title="Untitled", size=(300, 200)):
3404        """
3405        comment
3406        :param parent: parent panel/container
3407        """
3408        # Initialize the Frame object
3409        CHILD_FRAME.__init__(self, parent=parent, id=wx.ID_ANY, title=title, size=size)
3410        self.parent = parent
3411        self.name = "Untitled"
3412        self.batch_on = self.parent.batch_on
3413        self.panel = panel
3414        if panel != None:
3415            self.set_panel(panel)
3416        self.Show(False)
3417
3418    def show_data_panel(self, action):
3419        """
3420        """
3421        self.parent.show_data_panel(action)
3422
3423    def set_panel(self, panel):
3424        """
3425        """
3426        self.panel = panel
3427        self.name = panel.window_name
3428        self.SetTitle(panel.window_caption)
3429        self.SetHelpText(panel.help_string)
3430        width, height = self.parent._get_panels_size(panel)
3431        if hasattr(panel, "CENTER_PANE") and panel.CENTER_PANE:
3432            width *= 0.6
3433        self.SetSize((width, height))
3434        self.parent.put_icon(self)
3435        self.Bind(wx.EVT_SET_FOCUS, self.set_panel_focus)
3436        self.Bind(wx.EVT_CLOSE, self.OnClose)
3437        self.Show(False)
3438
3439    def set_panel_focus(self, event):
3440        """
3441        """
3442        if self.parent.panel_on_focus != self.panel:
3443            self.panel.SetFocus()
3444            self.parent.panel_on_focus = self.panel
3445
3446    def OnClose(self, event):
3447        """
3448        On Close event
3449        """
3450        self.panel.on_close(event)
3451
3452if __name__ == "__main__":
3453    app = ViewApp(0)
3454    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.