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

Last change on this file since f5f8553 was 82d88d5, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

Merge branch 'master' into py37-sasgui

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