source: sasview/src/sas/sasgui/guiframe/gui_manager.py @ 845144e

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249unittest-saveload
Last change on this file since 845144e was 845144e, checked in by krzywon, 5 years ago

Merge branch master into ticket-1111

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