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

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

Removed old commented text

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