source: sasview/src/sas/sasgui/guiframe/gui_manager.py @ 47be5a1

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249
Last change on this file since 47be5a1 was 47be5a1, checked in by Paul Kienzle <pkienzle@…>, 5 years ago

redo handling of cust config failures

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