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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249
Last change on this file since dcd6efd was dcd6efd, checked in by GitHub <noreply@…>, 5 years ago

Merge pull request #180 from SasView?/ticket-1111

Multiple data loader and writer updates - NXcanSAS compliance, data conversion, saving as NXcanSAS, plus more

Fixes #976
Fixes #1129
Fixes #1111
Fixes #1221

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