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

ticket-1094-headless
Last change on this file since 3388337 was b963b20, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

pull config out of sas.sasgui so it can be used without reference to wx

  • Property mode set to 100644
File size: 126.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()
1283            self._help_menu.Append(wx_id, '&Tutorial', 'Software tutorial')
1284            wx.EVT_MENU(self, wx_id, self._onTutorial)
1285
[f53cd30]1286        if config._do_acknowledge:
[091e71a2]1287            wx_id = wx.NewId()
[c8e1996]1288            self._help_menu.Append(wx_id, '&Acknowledge',
1289                                   'Acknowledging SasView')
[091e71a2]1290            wx.EVT_MENU(self, wx_id, self._onAcknowledge)
1291
[f53cd30]1292        if config._do_aboutbox:
[c155a16]1293            logger.info("Doing help menu")
[3fac0df]1294            wx_id = wx.NewId()
1295            self._help_menu.Append(wx_id, '&About', 'Software information')
1296            wx.EVT_MENU(self, wx_id, self._onAbout)
1297
[adb0851]1298        if config.marketplace_url:
1299            wx_id = wx.NewId()
1300            self._help_menu.Append(wx_id, '&Model marketplace', '')
1301            wx.EVT_MENU(self, wx_id, self._on_marketplace_click)
[091e71a2]1302
[f53cd30]1303        # Checking for updates
[091e71a2]1304        wx_id = wx.NewId()
1305        self._help_menu.Append(wx_id, '&Check for update',
[c8e1996]1306                               'Check for the latest version of %s' %
1307                               config.__appname__)
[091e71a2]1308        wx.EVT_MENU(self, wx_id, self._check_update)
[f53cd30]1309        self._menubar.Append(self._help_menu, '&Help')
[091e71a2]1310
[f53cd30]1311    def _add_menu_view(self):
1312        """
1313        add menu items under view menu
1314        """
1315        if not VIEW_MENU:
1316            return
1317        self._view_menu = wx.Menu()
[091e71a2]1318
1319        wx_id = wx.NewId()
[f53cd30]1320        hint = "Display the Grid Window for batch results etc."
[091e71a2]1321        self._view_menu.Append(wx_id, '&Show Grid Window', hint)
1322        wx.EVT_MENU(self, wx_id, self.show_batch_frame)
1323
[f53cd30]1324        self._view_menu.AppendSeparator()
1325        style = self.__gui_style & GUIFRAME.MANAGER_ON
[091e71a2]1326        wx_id = wx.NewId()
1327        self._data_panel_menu = self._view_menu.Append(wx_id,
[c8e1996]1328                                                       '&Show Data Explorer',
1329                                                       '')
[091e71a2]1330        wx.EVT_MENU(self, wx_id, self.show_data_panel)
[f53cd30]1331        if style == GUIFRAME.MANAGER_ON:
1332            self._data_panel_menu.SetText('Hide Data Explorer')
1333        else:
1334            self._data_panel_menu.SetText('Show Data Explorer')
[091e71a2]1335
[f53cd30]1336        self._view_menu.AppendSeparator()
[091e71a2]1337        wx_id = wx.NewId()
[f53cd30]1338        style1 = self.__gui_style & GUIFRAME.TOOLBAR_ON
1339        if style1 == GUIFRAME.TOOLBAR_ON:
[c8e1996]1340            self._toolbar_menu = self._view_menu.Append(wx_id, '&Hide Toolbar',
1341                                                        '')
[f53cd30]1342        else:
[c8e1996]1343            self._toolbar_menu = self._view_menu.Append(wx_id, '&Show Toolbar',
1344                                                        '')
[091e71a2]1345        wx.EVT_MENU(self, wx_id, self._on_toggle_toolbar)
[f53cd30]1346
[c8e1996]1347        if custom_config is not None:
[f53cd30]1348            self._view_menu.AppendSeparator()
[091e71a2]1349            wx_id = wx.NewId()
[f53cd30]1350            hint_ss = "Select the current/default configuration "
1351            hint_ss += "as a startup setting"
[091e71a2]1352            preference_menu = self._view_menu.Append(wx_id, 'Startup Setting',
[f53cd30]1353                                                     hint_ss)
[091e71a2]1354            wx.EVT_MENU(self, wx_id, self._on_preference_menu)
1355
1356        wx_id = wx.NewId()
[f53cd30]1357        self._view_menu.AppendSeparator()
[c8e1996]1358        self._view_menu.Append(wx_id, 'Category Manager',
1359                               'Edit model categories')
[091e71a2]1360        wx.EVT_MENU(self, wx_id, self._on_category_manager)
1361
1362        self._menubar.Append(self._view_menu, '&View')
[f53cd30]1363
1364    def show_batch_frame(self, event=None):
1365        """
1366        show the grid of result
1367        """
1368        # Show(False) before Show(True) in order to bring it to the front
1369        self.batch_frame.Show(False)
1370        self.batch_frame.Show(True)
[091e71a2]1371
[c8e1996]1372    def on_category_panel(self, event):
[f53cd30]1373        """
1374        On cat panel
1375        """
1376        self._on_category_manager(event)
[091e71a2]1377
[f53cd30]1378    def _on_category_manager(self, event):
1379        """
1380        Category manager frame
1381        """
1382        frame = CategoryManager(self, -1, 'Model Category Manager')
1383        icon = self.GetIcon()
1384        frame.SetIcon(icon)
1385
[091e71a2]1386    def _on_preference_menu(self, event):
[f53cd30]1387        """
1388        Build a panel to allow to edit Mask
1389        """
[d85c194]1390        from sas.sasgui.guiframe.startup_configuration \
[c8e1996]1391            import StartupConfiguration as ConfDialog
[091e71a2]1392
[f53cd30]1393        dialog = ConfDialog(parent=self, gui=self.__gui_style)
1394        result = dialog.ShowModal()
1395        if result == wx.ID_OK:
1396            dialog.write_custom_config()
1397            # post event for info
[c8e1996]1398            wx.PostEvent(self, StatusEvent(status="Wrote custom configuration",
1399                                           info='info'))
[f53cd30]1400        dialog.Destroy()
[091e71a2]1401
[f53cd30]1402    def _add_menu_application(self):
1403        """
1404        # Attach a menu item for each defined perspective or application.
1405        # Only add the perspective menu if there are more than one perspectives
1406        add menu application
1407        """
[091e71a2]1408        if self._num_perspectives > 1:
[f53cd30]1409            plug_data_count = False
1410            plug_no_data_count = False
1411            self._applications_menu = wx.Menu()
1412            pos = 0
1413            separator = self._applications_menu.AppendSeparator()
1414            for plug in self.plugins:
1415                if len(plug.get_perspective()) > 0:
1416                    id = wx.NewId()
1417                    if plug.use_data():
[c8e1996]1418                        self._applications_menu.InsertCheckItem(pos, id,
1419                                                                plug.sub_menu,
1420                                    "Switch to analysis: %s" % plug.sub_menu)
[f53cd30]1421                        plug_data_count = True
1422                        pos += 1
1423                    else:
1424                        plug_no_data_count = True
[c8e1996]1425                        self._applications_menu.AppendCheckItem(id,
1426                                                                plug.sub_menu,
[091e71a2]1427                            "Switch to analysis: %s" % plug.sub_menu)
[f53cd30]1428                    wx.EVT_MENU(self, id, plug.on_perspective)
1429
[091e71a2]1430            if not plug_data_count or not plug_no_data_count:
[f53cd30]1431                self._applications_menu.RemoveItem(separator)
[c8e1996]1432            # Windows introduces a "Window" menu item during the layout process
1433            # somehow.  We want it to be next to the last item with Help as
1434            # last. However Analysis gets stuck after Window in normal ordering
1435            # so force it to be next after the Tools menu item.  Should we add
1436            # another menu item will need to check if this is still where we
1437            # want Analysis.  This is NOT an issue on the Mac which does not
1438            # have the extra Window menu item.
[914ba0a]1439            #      March 2016 Code Camp  -- PDB
[0d534de]1440            Tools_pos = self._menubar.FindMenu("Tools")
[c8e1996]1441            self._menubar.Insert(Tools_pos+1, self._applications_menu,
[3835dbd]1442                                 '&Analysis')
[f53cd30]1443            self._check_applications_menu()
[091e71a2]1444
[f53cd30]1445    def _populate_file_menu(self):
1446        """
1447        Insert menu item under file menu
1448        """
1449        for plugin in self.plugins:
1450            if len(plugin.populate_file_menu()) > 0:
1451                for item in plugin.populate_file_menu():
1452                    m_name, m_hint, m_handler = item
[78f75d02]1453                    wx_id = wx.NewId()
1454                    self._file_menu.Append(wx_id, m_name, m_hint)
1455                    wx.EVT_MENU(self, wx_id, m_handler)
[f53cd30]1456                self._file_menu.AppendSeparator()
[091e71a2]1457
[f53cd30]1458        style1 = self.__gui_style & GUIFRAME.MULTIPLE_APPLICATIONS
1459        if OPEN_SAVE_MENU:
[78f75d02]1460            wx_id = wx.NewId()
[f53cd30]1461            hint_load_file = "read all analysis states saved previously"
[c8e1996]1462            self._save_appl_menu = self._file_menu.Append(wx_id,
1463                                                          '&Open Project',
1464                                                          hint_load_file)
[78f75d02]1465            wx.EVT_MENU(self, wx_id, self._on_open_state_project)
[091e71a2]1466
[f53cd30]1467        if style1 == GUIFRAME.MULTIPLE_APPLICATIONS:
1468            # some menu of plugin to be seen under file menu
1469            hint_load_file = "Read a status files and load"
1470            hint_load_file += " them into the analysis"
[78f75d02]1471            wx_id = wx.NewId()
1472            self._save_appl_menu = self._file_menu.Append(wx_id,
[c8e1996]1473                                                          '&Open Analysis',
1474                                                          hint_load_file)
[78f75d02]1475            wx.EVT_MENU(self, wx_id, self._on_open_state_application)
[091e71a2]1476        if OPEN_SAVE_MENU:
[f53cd30]1477            self._file_menu.AppendSeparator()
[78f75d02]1478            wx_id = wx.NewId()
1479            self._file_menu.Append(wx_id, '&Save Project',
[091e71a2]1480                                   'Save the state of the whole analysis')
[78f75d02]1481            wx.EVT_MENU(self, wx_id, self._on_save_project)
[f53cd30]1482        if style1 == GUIFRAME.MULTIPLE_APPLICATIONS:
[78f75d02]1483            wx_id = wx.NewId()
[c8e1996]1484            txt = '&Save Analysis'
1485            txt2 = 'Save state of the current active analysis panel'
1486            self._save_appl_menu = self._file_menu.Append(wx_id, txt, txt2)
[78f75d02]1487            wx.EVT_MENU(self, wx_id, self._on_save_application)
[091e71a2]1488        if not sys.platform == 'darwin':
[f53cd30]1489            self._file_menu.AppendSeparator()
[78f75d02]1490            wx_id = wx.NewId()
1491            self._file_menu.Append(wx_id, '&Quit', 'Exit')
1492            wx.EVT_MENU(self, wx_id, self.Close)
[091e71a2]1493
[f53cd30]1494    def _add_menu_file(self):
1495        """
1496        add menu file
1497        """
1498        # File menu
1499        self._file_menu = wx.Menu()
1500        # Add sub menus
1501        self._menubar.Append(self._file_menu, '&File')
[091e71a2]1502
[f53cd30]1503    def _add_menu_edit(self):
1504        """
1505        add menu edit
1506        """
1507        if not EDIT_MENU:
1508            return
1509        # Edit Menu
1510        self._edit_menu = wx.Menu()
[091e71a2]1511        self._edit_menu.Append(GUIFRAME_ID.UNDO_ID, '&Undo',
[f53cd30]1512                               'Undo the previous action')
1513        wx.EVT_MENU(self, GUIFRAME_ID.UNDO_ID, self.on_undo_panel)
[091e71a2]1514        self._edit_menu.Append(GUIFRAME_ID.REDO_ID, '&Redo',
[f53cd30]1515                               'Redo the previous action')
1516        wx.EVT_MENU(self, GUIFRAME_ID.REDO_ID, self.on_redo_panel)
1517        self._edit_menu.AppendSeparator()
[091e71a2]1518        self._edit_menu.Append(GUIFRAME_ID.COPY_ID, '&Copy Params',
[f53cd30]1519                               'Copy parameter values')
1520        wx.EVT_MENU(self, GUIFRAME_ID.COPY_ID, self.on_copy_panel)
[091e71a2]1521        self._edit_menu.Append(GUIFRAME_ID.PASTE_ID, '&Paste Params',
[f53cd30]1522                               'Paste parameter values')
1523        wx.EVT_MENU(self, GUIFRAME_ID.PASTE_ID, self.on_paste_panel)
1524
1525        self._edit_menu.AppendSeparator()
1526
1527        self._edit_menu_copyas = wx.Menu()
[c8e1996]1528        # Sub menu for Copy As...
1529        self._edit_menu_copyas.Append(GUIFRAME_ID.COPYEX_ID,
1530                                      'Copy current tab to Excel',
[091e71a2]1531                                      'Copy parameter values in tabular format')
[f53cd30]1532        wx.EVT_MENU(self, GUIFRAME_ID.COPYEX_ID, self.on_copy_panel)
1533
[c8e1996]1534        self._edit_menu_copyas.Append(GUIFRAME_ID.COPYLAT_ID,
1535                                      'Copy current tab to LaTeX',
[091e71a2]1536                                      'Copy parameter values in tabular format')
[f53cd30]1537        wx.EVT_MENU(self, GUIFRAME_ID.COPYLAT_ID, self.on_copy_panel)
1538
[091e71a2]1539        self._edit_menu.AppendMenu(GUIFRAME_ID.COPYAS_ID, 'Copy Params as...',
1540                                   self._edit_menu_copyas,
1541                                   'Copy parameter values in various formats')
[f53cd30]1542
1543        self._edit_menu.AppendSeparator()
[091e71a2]1544
[f53cd30]1545        self._edit_menu.Append(GUIFRAME_ID.PREVIEW_ID, '&Report Results',
1546                               'Preview current panel')
1547        wx.EVT_MENU(self, GUIFRAME_ID.PREVIEW_ID, self.on_preview_panel)
1548
[091e71a2]1549        self._edit_menu.Append(GUIFRAME_ID.RESET_ID, '&Reset Page',
[f53cd30]1550                               'Reset current panel')
1551        wx.EVT_MENU(self, GUIFRAME_ID.RESET_ID, self.on_reset_panel)
[091e71a2]1552
1553        self._menubar.Append(self._edit_menu, '&Edit')
[f53cd30]1554        self.enable_edit_menu()
[091e71a2]1555
[f53cd30]1556    def get_style(self):
1557        """
1558        Return the gui style
1559        """
[c8e1996]1560        return self.__gui_style
[091e71a2]1561
[f53cd30]1562    def _add_menu_data(self):
1563        """
1564        Add menu item item data to menu bar
1565        """
1566        if self._data_plugin is not None:
1567            menu_list = self._data_plugin.populate_menu(self)
1568            if menu_list:
1569                for (menu, name) in menu_list:
1570                    self._menubar.Append(menu, name)
[091e71a2]1571
[f53cd30]1572    def _on_toggle_toolbar(self, event=None):
1573        """
1574        hide or show toolbar
1575        """
1576        if self._toolbar is None:
1577            return
1578        if self._toolbar.IsShown():
1579            if self._toolbar_menu is not None:
1580                self._toolbar_menu.SetItemLabel('Show Toolbar')
1581            self._toolbar.Hide()
1582        else:
1583            if self._toolbar_menu is not None:
1584                self._toolbar_menu.SetItemLabel('Hide Toolbar')
1585            self._toolbar.Show()
1586        self._toolbar.Realize()
[091e71a2]1587
[f53cd30]1588    def _on_status_event(self, evt):
1589        """
1590        Display status message
1591        """
1592        # This CallAfter fixes many crashes on MAC.
1593        wx.CallAfter(self.sb.set_status, evt)
[091e71a2]1594
[f53cd30]1595    def on_view(self, evt):
1596        """
1597        A panel was selected to be shown. If it's not already
1598        shown, display it.
[091e71a2]1599
[f53cd30]1600        :param evt: menu event
[091e71a2]1601
[f53cd30]1602        """
1603        panel_id = str(evt.GetId())
1604        self.on_set_plot_focus(self.panels[panel_id])
[091e71a2]1605        wx.CallLater(5 * TIME_FACTOR, self.set_schedule(True))
[f53cd30]1606        self.set_plot_unfocus()
[091e71a2]1607
[f53cd30]1608    def show_welcome_panel(self, event):
[091e71a2]1609        """
[f53cd30]1610        Display the welcome panel
1611        """
1612        if self.defaultPanel is None:
[091e71a2]1613            return
[f53cd30]1614        frame = self.panels['default'].get_frame()
[c8e1996]1615        if frame is None:
[f53cd30]1616            return
1617        # Show default panel
1618        if not frame.IsShown():
1619            frame.Show(True)
[091e71a2]1620
[f53cd30]1621    def on_close_welcome_panel(self):
1622        """
1623        Close the welcome panel
1624        """
1625        if self.defaultPanel is None:
[091e71a2]1626            return
[f53cd30]1627        default_panel = self.panels["default"].frame
1628        if default_panel.IsShown():
[091e71a2]1629            default_panel.Show(False)
1630
[f53cd30]1631    def delete_panel(self, uid):
1632        """
1633        delete panel given uid
1634        """
1635        ID = str(uid)
1636        config.printEVT("delete_panel: %s" % ID)
1637        if ID in self.panels.keys():
1638            self.panel_on_focus = None
1639            panel = self.panels[ID]
1640
1641            if hasattr(panel, "connect"):
1642                panel.connect.disconnect()
1643            self._plotting_plugin.delete_panel(panel.group_id)
1644
1645            if panel in self.schedule_full_draw_list:
[091e71a2]1646                self.schedule_full_draw_list.remove(panel)
1647
[c8e1996]1648            # delete uid number not str(uid)
[f53cd30]1649            if ID in self.plot_panels.keys():
1650                del self.plot_panels[ID]
1651            if ID in self.panels.keys():
1652                del self.panels[ID]
[091e71a2]1653        else:
[c155a16]1654            logger.error("delete_panel: No such plot id as %s" % ID)
[091e71a2]1655
[f53cd30]1656    def create_gui_data(self, data, path=None):
1657        """
1658        """
1659        return self._data_manager.create_gui_data(data, path)
[091e71a2]1660
[f53cd30]1661    def get_data(self, path):
1662        """
1663        """
1664        log_msg = ''
[091e71a2]1665        basename = os.path.basename(path)
[78f75d02]1666        _, extension = os.path.splitext(basename)
[f53cd30]1667        if extension.lower() not in EXTENSIONS:
1668            log_msg = "File Loader cannot "
1669            log_msg += "load: %s\n" % str(basename)
1670            log_msg += "Try Data opening...."
[c155a16]1671            logger.error(log_msg)
[f53cd30]1672            return
[091e71a2]1673
[c8e1996]1674        # reading a state file
[f53cd30]1675        for plug in self.plugins:
1676            reader, ext = plug.get_extensions()
1677            if reader is not None:
[c8e1996]1678                # read the state of the single plugin
[f53cd30]1679                if extension == ext:
1680                    reader.read(path)
1681                    return
1682                elif extension == APPLICATION_STATE_EXTENSION:
1683                    try:
1684                        reader.read(path)
1685                    except:
1686                        msg = "DataLoader Error: Encounted Non-ASCII character"
[091e71a2]1687                        msg += "\n(%s)" % sys.exc_value
1688                        wx.PostEvent(self, StatusEvent(status=msg,
[c8e1996]1689                                                       info="error",
1690                                                       type="stop"))
[f53cd30]1691                        return
[091e71a2]1692
[f53cd30]1693        style = self.__gui_style & GUIFRAME.MANAGER_ON
1694        if style == GUIFRAME.MANAGER_ON:
1695            if self._data_panel is not None:
1696                self._data_panel.frame.Show(True)
[091e71a2]1697
1698    def load_from_cmd(self, path):
[f53cd30]1699        """
1700        load data from cmd or application
[091e71a2]1701        """
[f53cd30]1702        if path is None:
1703            return
1704        else:
1705            path = os.path.abspath(path)
1706            if not os.path.isfile(path) and not os.path.isdir(path):
1707                return
[091e71a2]1708
[f53cd30]1709            if os.path.isdir(path):
1710                self.load_folder(path)
1711                return
1712
[091e71a2]1713        basename = os.path.basename(path)
1714        _, extension = os.path.splitext(basename)
[f53cd30]1715        if extension.lower() not in EXTENSIONS:
1716            self.load_data(path)
1717        else:
1718            self.load_state(path)
1719
1720        self._default_save_location = os.path.dirname(path)
[091e71a2]1721
1722    def load_state(self, path, is_project=False):
[f53cd30]1723        """
1724        load data from command line or application
1725        """
1726        if path and (path is not None) and os.path.isfile(path):
[091e71a2]1727            basename = os.path.basename(path)
[f53cd30]1728            if APPLICATION_STATE_EXTENSION is not None \
[c8e1996]1729                    and basename.endswith(APPLICATION_STATE_EXTENSION):
[f53cd30]1730                if is_project:
1731                    for ID in self.plot_panels.keys():
1732                        panel = self.plot_panels[ID]
1733                        panel.on_close(None)
1734            self.get_data(path)
1735            wx.PostEvent(self, StatusEvent(status="Completed loading."))
1736        else:
1737            wx.PostEvent(self, StatusEvent(status=" "))
[091e71a2]1738
[f53cd30]1739    def load_data(self, path):
1740        """
1741        load data from command line
1742        """
1743        if not os.path.isfile(path):
1744            return
[091e71a2]1745        basename = os.path.basename(path)
1746        _, extension = os.path.splitext(basename)
[f53cd30]1747        if extension.lower() in EXTENSIONS:
1748            log_msg = "Data Loader cannot "
1749            log_msg += "load: %s\n" % str(path)
1750            log_msg += "Try File opening ...."
[c155a16]1751            logger.error(log_msg)
[f53cd30]1752            return
1753        log_msg = ''
1754        output = {}
1755        error_message = ""
1756        try:
[c155a16]1757            logger.info("Loading Data...:\n" + str(path) + "\n")
[091e71a2]1758            temp = self.loader.load(path)
[f53cd30]1759            if temp.__class__.__name__ == "list":
1760                for item in temp:
1761                    data = self.create_gui_data(item, path)
1762                    output[data.id] = data
1763            else:
1764                data = self.create_gui_data(temp, path)
1765                output[data.id] = data
[091e71a2]1766
[f53cd30]1767            self.add_data(data_list=output)
1768        except:
1769            error_message = "Error while loading"
1770            error_message += " Data from cmd:\n %s\n" % str(path)
1771            error_message += str(sys.exc_value) + "\n"
[c155a16]1772            logger.error(error_message)
[091e71a2]1773
[f53cd30]1774    def load_folder(self, path):
1775        """
1776        Load entire folder
[091e71a2]1777        """
[f53cd30]1778        if not os.path.isdir(path):
1779            return
1780        if self._data_plugin is None:
1781            return
1782        try:
1783            if path is not None:
1784                self._default_save_location = os.path.dirname(path)
1785                file_list = self._data_plugin.get_file_path(path)
1786                self._data_plugin.get_data(file_list)
1787            else:
[091e71a2]1788                return
[f53cd30]1789        except:
1790            error_message = "Error while loading"
1791            error_message += " Data folder from cmd:\n %s\n" % str(path)
1792            error_message += str(sys.exc_value) + "\n"
[c155a16]1793            logger.error(error_message)
[091e71a2]1794
[f53cd30]1795    def _on_open_state_application(self, event):
1796        """
1797        """
1798        path = None
[c8e1996]1799        if self._default_save_location is None:
[f53cd30]1800            self._default_save_location = os.getcwd()
1801        wx.PostEvent(self, StatusEvent(status="Loading Analysis file..."))
1802        plug_wlist = self._on_open_state_app_helper()
[091e71a2]1803        dlg = wx.FileDialog(self,
1804                            "Choose a file",
[f53cd30]1805                            self._default_save_location, "",
1806                            plug_wlist)
1807        if dlg.ShowModal() == wx.ID_OK:
1808            path = dlg.GetPath()
1809            if path is not None:
1810                self._default_save_location = os.path.dirname(path)
1811        dlg.Destroy()
[091e71a2]1812        self.load_state(path=path)
1813
[f53cd30]1814    def _on_open_state_app_helper(self):
1815        """
[091e71a2]1816        Helps '_on_open_state_application()' to find the extension of
[f53cd30]1817        the current perspective/application
1818        """
1819        # No current perspective or no extension attr
1820        if self._current_perspective is None:
[091e71a2]1821            return PLUGINS_WLIST
[f53cd30]1822        try:
[091e71a2]1823            # Find the extension of the perspective
[f53cd30]1824            # and get that as 1st item in list
1825            ind = None
1826            app_ext = self._current_perspective._extensions
1827            plug_wlist = config.PLUGINS_WLIST
1828            for ext in set(plug_wlist):
1829                if ext.count(app_ext) > 0:
1830                    ind = ext
1831                    break
1832            # Found the extension
[c8e1996]1833            if ind is not None:
[f53cd30]1834                plug_wlist.remove(ind)
1835                plug_wlist.insert(0, ind)
1836                try:
1837                    plug_wlist = '|'.join(plug_wlist)
1838                except:
1839                    plug_wlist = ''
1840
1841        except:
[091e71a2]1842            plug_wlist = PLUGINS_WLIST
1843
[f53cd30]1844        return plug_wlist
[091e71a2]1845
[f53cd30]1846    def _on_open_state_project(self, event):
1847        """
[a4c2445]1848        Load in a .svs project file after removing all data from SasView
[f53cd30]1849        """
1850        path = None
[c8e1996]1851        if self._default_save_location is None:
[f53cd30]1852            self._default_save_location = os.getcwd()
[a4c2445]1853        msg = "This operation will set remove all data, plots and analyses from"
1854        msg += " SasView before loading the project. Do you wish to continue?"
[c8e1996]1855        msg_box = wx.MessageDialog(None, msg, 'Warning', wx.OK | wx.CANCEL)
1856        if msg_box.ShowModal() == wx.ID_OK:
1857            self._data_panel.selection_cbox.SetValue('Select all Data')
1858            self._data_panel._on_selection_type(None)
1859            for _, theory_dict in self._data_panel.list_cb_theory.iteritems():
1860                for key, value in theory_dict.iteritems():
1861                    item, _, _ = value
1862                    item.Check(True)
1863
[998ca90]1864            wx.PostEvent(self, StatusEvent(status="Loading Project file..."))
[c8e1996]1865            dlg = wx.FileDialog(self, "Choose a file",
1866                                self._default_save_location, "",
1867                                APPLICATION_WLIST)
[998ca90]1868            if dlg.ShowModal() == wx.ID_OK:
1869                path = dlg.GetPath()
[f53cd30]1870            if path is not None:
1871                self._default_save_location = os.path.dirname(path)
[998ca90]1872                dlg.Destroy()
[e6de6b8]1873                # Reset to a base state
1874                self._on_reset_state()
[67b0a99]1875                self._data_panel.on_remove(None, False)
[e6de6b8]1876                # Load the project file
1877                self.load_state(path=path, is_project=True)
[091e71a2]1878
[998ca90]1879    def _on_reset_state(self):
1880        """
1881        Resets SasView to its freshly opened state.
1882        :return: None
1883        """
1884        # Reset all plugins to their base state
[e6de6b8]1885        self._data_panel.set_panel_on_focus()
1886        # Remove all loaded data
[998ca90]1887        for plugin in self.plugins:
1888            plugin.clear_panel()
1889        # Reset plot number to 0
1890        self.graph_num = 0
[091e71a2]1891
[f53cd30]1892    def _on_save_application(self, event):
1893        """
1894        save the state of the current active application
1895        """
1896        if self.cpanel_on_focus is not None:
1897            try:
[091e71a2]1898                wx.PostEvent(self,
[f53cd30]1899                             StatusEvent(status="Saving Analysis file..."))
1900                self.cpanel_on_focus.on_save(event)
[091e71a2]1901                wx.PostEvent(self,
[f53cd30]1902                             StatusEvent(status="Completed saving."))
[d3911e3]1903            except Exception:
[f53cd30]1904                msg = "Error occurred while saving: "
[d3911e3]1905                msg += traceback.format_exc()
[f53cd30]1906                msg += "To save, the application panel should have a data set.."
[091e71a2]1907                wx.PostEvent(self, StatusEvent(status=msg))
1908
[f53cd30]1909    def _on_save_project(self, event):
1910        """
1911        save the state of the SasView as *.svs
1912        """
[c8e1996]1913        if self._current_perspective is None:
[f53cd30]1914            return
1915        wx.PostEvent(self, StatusEvent(status="Saving Project file..."))
1916        path = None
1917        extension = '*' + APPLICATION_STATE_EXTENSION
1918        dlg = wx.FileDialog(self, "Save Project file",
1919                            self._default_save_location, "sasview_proj",
[091e71a2]1920                            extension,
1921                            wx.SAVE)
[f53cd30]1922        if dlg.ShowModal() == wx.ID_OK:
1923            path = dlg.GetPath()
1924            self._default_save_location = os.path.dirname(path)
1925        else:
1926            return None
1927        dlg.Destroy()
1928        try:
1929            if path is None:
1930                return
1931            # default cansas xml doc
1932            doc = None
1933            for panel in self.panels.values():
1934                temp = panel.save_project(doc)
1935                if temp is not None:
1936                    doc = temp
[091e71a2]1937
[f53cd30]1938            # Write the XML document
1939            extens = APPLICATION_STATE_EXTENSION
1940            fName = os.path.splitext(path)[0] + extens
[c8e1996]1941            if doc is not None:
[f53cd30]1942                fd = open(fName, 'w')
1943                fd.write(doc.toprettyxml())
1944                fd.close()
1945                wx.PostEvent(self, StatusEvent(status="Completed Saving."))
1946            else:
1947                msg = "Error occurred while saving the project: "
1948                msg += "To save, at least one application panel "
1949                msg += "should have a data set "
1950                msg += "and model selected. "
1951                msg += "No project was saved to %s" % (str(path))
[c155a16]1952                logger.warning(msg)
[5276eeb]1953                wx.PostEvent(self, StatusEvent(status=msg, info="error"))
[d3911e3]1954        except Exception:
[f53cd30]1955            msg = "Error occurred while saving: "
[d3911e3]1956            msg += traceback.format_exc()
[f53cd30]1957            msg += "To save, at least one application panel "
1958            msg += "should have a data set.."
[5276eeb]1959            wx.PostEvent(self, StatusEvent(status=msg, info="error"))
[091e71a2]1960
[f53cd30]1961    def on_save_helper(self, doc, reader, panel, path):
1962        """
1963        Save state into a file
1964        """
[091e71a2]1965        if reader is not None:
1966            # case of a panel with multi-pages
1967            if hasattr(panel, "opened_pages"):
1968                for _, page in panel.opened_pages.iteritems():
1969                    data = page.get_data()
1970                    # state must be cloned
1971                    state = page.get_state().clone()
[f53cd30]1972                    if data is not None:
1973                        new_doc = reader.write_toXML(data, state)
[c8e1996]1974                        if doc is not None and hasattr(doc, "firstChild"):
[f53cd30]1975                            child = new_doc.firstChild.firstChild
[091e71a2]1976                            doc.firstChild.appendChild(child)
[f53cd30]1977                        else:
[091e71a2]1978                            doc = new_doc
1979            # case of only a panel
1980            else:
1981                data = panel.get_data()
1982                state = panel.get_state()
1983                if data is not None:
1984                    new_doc = reader.write_toXML(data, state)
[c8e1996]1985                    if doc is not None and hasattr(doc, "firstChild"):
[091e71a2]1986                        child = new_doc.firstChild.firstChild
1987                        doc.firstChild.appendChild(child)
1988                    else:
1989                        doc = new_doc
[f53cd30]1990        return doc
1991
1992    def quit_guiframe(self):
1993        """
1994        Pop up message to make sure the user wants to quit the application
1995        """
1996        message = "\nDo you really want to exit this application?        \n\n"
1997        dial = wx.MessageDialog(self, message, 'Confirm Exit',
[091e71a2]1998                                wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
[f53cd30]1999        if dial.ShowModal() == wx.ID_YES:
2000            return True
2001        else:
[091e71a2]2002            return False
2003
[f53cd30]2004    def WindowClose(self, event=None):
2005        """
2006        Quit the application from x icon
2007        """
2008        flag = self.quit_guiframe()
2009        if flag:
2010            _pylab_helpers.Gcf.figs = {}
2011            self.Close()
[091e71a2]2012
[f53cd30]2013    def Close(self, event=None):
2014        """
2015        Quit the application
2016        """
[73cbeec]2017        #IF SAS_OPENCL is set, settings are stored in the custom config file
2018        self._write_opencl_config_file()
[c155a16]2019        logger.info(" --- SasView session was closed --- \n")
[f53cd30]2020        wx.Exit()
2021        sys.exit()
[091e71a2]2022
[73cbeec]2023    def _write_opencl_config_file(self):
2024        """
2025        Writes OpenCL settings to custom config file, so they can be remmbered
2026        from session to session
2027        """
2028        if custom_config is not None:
[49165488]2029            sas_opencl = os.environ.get("SAS_OPENCL")
[73cbeec]2030            new_config_lines = []
2031            config_file = open(custom_config.__file__)
2032            config_lines = config_file.readlines()
2033            for line in config_lines:
2034                if "SAS_OPENCL" in line:
2035                    if sas_opencl:
[2f22db9]2036                        new_config_lines.append("SAS_OPENCL = \"" + sas_opencl
2037                                                + "\"\n")
[73cbeec]2038                    else:
[2f22db9]2039                        new_config_lines.append("SAS_OPENCL = \"None\"\n")
[73cbeec]2040                else:
2041                    new_config_lines.append(line)
2042            config_file.close()
2043
2044            #If custom_config is None, settings will not be remmbered
2045            new_config_file = open(custom_config.__file__,"w")
2046            new_config_file.writelines(new_config_lines)
2047            new_config_file.close()
2048        else:
[c155a16]2049            logger.info("Failed to save OPENCL settings in custom config file")
[73cbeec]2050
2051
[091e71a2]2052    def _check_update(self, event=None):
[f53cd30]2053        """
2054        Check with the deployment server whether a new version
2055        of the application is available.
2056        A thread is started for the connecting with the server. The thread calls
2057        a call-back method when the current version number has been obtained.
2058        """
[957af0d]2059        version_info = {"version": "0.0.0"}
2060        c = Connection(config.__update_URL__, config.UPDATE_TIMEOUT)
2061        response = c.connect()
2062        if response is not None:
2063            try:
2064                content = response.read().strip()
[38beeab]2065                logger.info("Connected to www.sasview.org. Latest version: %s", content)
[957af0d]2066                version_info = json.loads(content)
2067            except:
[c155a16]2068                logger.info("Failed to connect to www.sasview.org")
[c8e1996]2069        self._process_version(version_info, standalone=event is None)
[091e71a2]2070
2071
[9989a6a]2072    def _process_version(self, version_info, standalone=True):
[f53cd30]2073        """
2074        Call-back method for the process of checking for updates.
2075        This methods is called by a VersionThread object once the current
2076        version number has been obtained. If the check is being done in the
2077        background, the user will not be notified unless there's an update.
[091e71a2]2078
[f53cd30]2079        :param version: version string
[091e71a2]2080        :param standalone: True of the update is being checked in
[f53cd30]2081           the background, False otherwise.
[091e71a2]2082
[f53cd30]2083        """
2084        try:
[24386b9]2085            version = version_info["version"]
[f53cd30]2086            if version == "0.0.0":
2087                msg = "Could not connect to the application server."
2088                msg += " Please try again later."
2089                self.SetStatusText(msg)
2090            elif cmp(version, config.__version__) > 0:
2091                msg = "Version %s is available! " % str(version)
2092                if not standalone:
2093                    import webbrowser
[9989a6a]2094                    if "download_url" in version_info:
2095                        webbrowser.open(version_info["download_url"])
2096                    else:
2097                        webbrowser.open(config.__download_page__)
[f53cd30]2098                else:
[091e71a2]2099                    msg += "See the help menu to download it."
[f53cd30]2100                self.SetStatusText(msg)
2101            else:
2102                if not standalone:
2103                    msg = "You have the latest version"
2104                    msg += " of %s" % str(config.__appname__)
2105                    self.SetStatusText(msg)
2106        except:
2107            msg = "guiframe: could not get latest application"
2108            msg += " version number\n  %s" % sys.exc_value
[c155a16]2109            logger.error(msg)
[f53cd30]2110            if not standalone:
2111                msg = "Could not connect to the application server."
2112                msg += " Please try again later."
2113                self.SetStatusText(msg)
[091e71a2]2114
[f53cd30]2115    def _onAcknowledge(self, evt):
2116        """
2117        Pop up the acknowledge dialog
[091e71a2]2118
[f53cd30]2119        :param evt: menu event
[091e71a2]2120
[f53cd30]2121        """
2122        if config._do_acknowledge:
[d85c194]2123            import sas.sasgui.guiframe.acknowledgebox as AcknowledgeBox
[f53cd30]2124            dialog = AcknowledgeBox.DialogAcknowledge(None, -1, "")
2125            dialog.ShowModal()
[091e71a2]2126
[f53cd30]2127    def _onAbout(self, evt):
2128        """
2129        Pop up the about dialog
[091e71a2]2130
[f53cd30]2131        :param evt: menu event
[091e71a2]2132
[f53cd30]2133        """
2134        if config._do_aboutbox:
[d85c194]2135            import sas.sasgui.guiframe.aboutbox as AboutBox
[f53cd30]2136            dialog = AboutBox.DialogAbout(None, -1, "")
[091e71a2]2137            dialog.ShowModal()
2138
[f53cd30]2139    def _onTutorial(self, evt):
2140        """
2141        Pop up the tutorial dialog
[091e71a2]2142
[f53cd30]2143        :param evt: menu event
[091e71a2]2144
[f53cd30]2145        """
[091e71a2]2146        if config._do_tutorial:
[f53cd30]2147            path = config.TUTORIAL_PATH
2148            if IS_WIN:
2149                try:
[d85c194]2150                    from sas.sasgui.guiframe.pdfview import PDFFrame
[f53cd30]2151                    dialog = PDFFrame(None, -1, "Tutorial", path)
2152                    # put icon
[091e71a2]2153                    self.put_icon(dialog)
2154                    dialog.Show(True)
[f53cd30]2155                except:
[c155a16]2156                    logger.error("Error in _onTutorial: %s" % sys.exc_value)
[f53cd30]2157                    try:
2158                        # Try an alternate method
[c155a16]2159                        logger.error(
[c8e1996]2160                            "Could not open the tutorial pdf, trying xhtml2pdf")
[f53cd30]2161                        from xhtml2pdf import pisa
2162                        pisa.startViewer(path)
2163                    except:
[c155a16]2164                        logger.error(
[c8e1996]2165                            "Could not open the tutorial pdf with xhtml2pdf")
[f53cd30]2166                        msg = "This feature requires 'PDF Viewer'\n"
2167                        wx.MessageBox(msg, 'Error')
2168            else:
2169                try:
2170                    command = "open '%s'" % path
2171                    os.system(command)
2172                except:
2173                    try:
2174                        # Try an alternate method
[c155a16]2175                        logger.error(
[c8e1996]2176                            "Could not open the tutorial pdf, trying xhtml2pdf")
[f53cd30]2177                        from xhtml2pdf import pisa
2178                        pisa.startViewer(path)
2179                    except:
[c155a16]2180                        logger.error(
[c8e1996]2181                            "Could not open the tutorial pdf with xhtml2pdf")
2182                        msg = "This feature requires the Preview application\n"
[f53cd30]2183                        wx.MessageBox(msg, 'Error')
2184
2185    def _onSphinxDocs(self, evt):
2186        """
2187        Bring up Sphinx Documentation at top level whenever the menu item
2188        'documentation' is clicked. Calls DocumentationWindow with the top
2189        level path of "index.html"
[091e71a2]2190
[f53cd30]2191        :param evt: menu event
2192        """
2193        # Running SasView "in-place" using run.py means the docs will be in a
2194        # different place than they would otherwise.
[7801df8]2195        from documentation_window import DocumentationWindow
[22d92da]2196        _TreeLocation = "user/user.html"
[c8e1996]2197        DocumentationWindow(self, -1, _TreeLocation, "",
2198                            "SasView Documentation")
[f53cd30]2199
2200    def set_manager(self, manager):
2201        """
2202        Sets the application manager for this frame
[091e71a2]2203
[f53cd30]2204        :param manager: frame manager
2205        """
2206        self.app_manager = manager
[091e71a2]2207
[f53cd30]2208    def post_init(self):
2209        """
[091e71a2]2210        This initialization method is called after the GUI
[f53cd30]2211        has been created and all plug-ins loaded. It calls
2212        the post_init() method of each plug-in (if it exists)
2213        so that final initialization can be done.
2214        """
2215        for item in self.plugins:
2216            if hasattr(item, "post_init"):
2217                item.post_init()
[091e71a2]2218
[f53cd30]2219    def set_perspective(self, panels):
2220        """
2221        Sets the perspective of the GUI.
2222        Opens all the panels in the list, and closes
2223        all the others.
[091e71a2]2224
[f53cd30]2225        :param panels: list of panels
2226        """
2227        for item in self.panels.keys():
2228            # Check whether this is a sticky panel
2229            if hasattr(self.panels[item], "ALWAYS_ON"):
2230                if self.panels[item].ALWAYS_ON:
[091e71a2]2231                    continue
[c8e1996]2232            if self.panels[item] is None:
[f53cd30]2233                continue
2234            if self.panels[item].window_name in panels:
2235                frame = self.panels[item].get_frame()
2236                if not frame.IsShown():
2237                    frame.Show(True)
2238            else:
2239                # always show the data panel if enable
2240                style = self.__gui_style & GUIFRAME.MANAGER_ON
[c8e1996]2241                if (style == GUIFRAME.MANAGER_ON) \
2242                        and self.panels[item] == self._data_panel:
[f53cd30]2243                    if 'data_panel' in self.panels.keys():
2244                        frame = self.panels['data_panel'].get_frame()
[c8e1996]2245                        if frame is None:
[f53cd30]2246                            continue
2247                        flag = frame.IsShown()
2248                        frame.Show(flag)
2249                else:
2250                    frame = self.panels[item].get_frame()
[c8e1996]2251                    if frame is None:
[f53cd30]2252                        continue
2253
2254                    if frame.IsShown():
2255                        frame.Show(False)
[091e71a2]2256
[f53cd30]2257    def show_data_panel(self, event=None, action=True):
2258        """
2259        show the data panel
2260        """
[c8e1996]2261        if self._data_panel_menu is None:
[f53cd30]2262            return
2263        label = self._data_panel_menu.GetText()
2264        pane = self.panels["data_panel"]
2265        frame = pane.get_frame()
2266        if label == 'Show Data Explorer':
[091e71a2]2267            if action:
[f53cd30]2268                frame.Show(True)
2269            self.__gui_style = self.__gui_style | GUIFRAME.MANAGER_ON
2270            self._data_panel_menu.SetText('Hide Data Explorer')
2271        else:
2272            if action:
2273                frame.Show(False)
2274            self.__gui_style = self.__gui_style & (~GUIFRAME.MANAGER_ON)
2275            self._data_panel_menu.SetText('Show Data Explorer')
2276
2277    def add_data_helper(self, data_list):
2278        """
2279        """
2280        if self._data_manager is not None:
2281            self._data_manager.add_data(data_list)
[091e71a2]2282
[f53cd30]2283    def add_data(self, data_list):
2284        """
2285        receive a dictionary of data from loader
2286        store them its data manager if possible
[091e71a2]2287        send to data the current active perspective if the data panel
2288        is not active.
[f53cd30]2289        :param data_list: dictionary of data's ID and value Data
2290        """
[c8e1996]2291        # Store data into manager
[f53cd30]2292        self.add_data_helper(data_list)
2293        # set data in the data panel
2294        if self._data_panel is not None:
2295            data_state = self._data_manager.get_data_state(data_list.keys())
2296            self._data_panel.load_data_list(data_state)
[c8e1996]2297        # if the data panel is shown wait for the user to press a button
2298        # to send data to the current perspective. if the panel is not
2299        # show  automatically send the data to the current perspective
[f53cd30]2300        style = self.__gui_style & GUIFRAME.MANAGER_ON
2301        if style == GUIFRAME.MANAGER_ON:
[c8e1996]2302            # wait for button press from the data panel to set_data
[f53cd30]2303            if self._data_panel is not None:
2304                self._data_panel.frame.Show(True)
2305        else:
[c8e1996]2306            # automatically send that to the current perspective
[f53cd30]2307            self.set_data(data_id=data_list.keys())
[091e71a2]2308
2309    def set_data(self, data_id, theory_id=None):
[f53cd30]2310        """
2311        set data to current perspective
2312        """
2313        list_data, _ = self._data_manager.get_by_id(data_id)
2314        if self._current_perspective is not None:
2315            self._current_perspective.set_data(list_data.values())
2316
2317        else:
2318            msg = "Guiframe does not have a current perspective"
[c155a16]2319            logger.info(msg)
[091e71a2]2320
[f53cd30]2321    def set_theory(self, state_id, theory_id=None):
2322        """
2323        """
2324        _, list_theory = self._data_manager.get_by_id(theory_id)
2325        if self._current_perspective is not None:
2326            try:
2327                self._current_perspective.set_theory(list_theory.values())
2328            except:
2329                msg = "Guiframe set_theory: \n" + str(sys.exc_value)
[c155a16]2330                logger.info(msg)
[f53cd30]2331                wx.PostEvent(self, StatusEvent(status=msg, info="error"))
2332        else:
2333            msg = "Guiframe does not have a current perspective"
[c155a16]2334            logger.info(msg)
[091e71a2]2335
2336    def plot_data(self, state_id, data_id=None,
[f53cd30]2337                  theory_id=None, append=False):
2338        """
2339        send a list of data to plot
2340        """
2341        data_list, _ = self._data_manager.get_by_id(data_id)
2342        _, temp_list_theory = self._data_manager.get_by_id(theory_id)
2343        total_plot_list = data_list.values()
2344        for item in temp_list_theory.values():
2345            theory_data, theory_state = item
2346            total_plot_list.append(theory_data)
2347        GROUP_ID = wx.NewId()
2348        for new_plot in total_plot_list:
2349            if append:
2350                if self.panel_on_focus is None:
2351                    message = "cannot append plot. No plot panel on focus!"
2352                    message += "please click on any available plot to set focus"
[091e71a2]2353                    wx.PostEvent(self, StatusEvent(status=message,
[f53cd30]2354                                                   info='warning'))
[091e71a2]2355                    return
[f53cd30]2356                else:
2357                    if self.enable_add_data(new_plot):
2358                        new_plot.group_id = self.panel_on_focus.group_id
2359                    else:
2360                        message = "Only 1D Data can be append to"
2361                        message += " plot panel containing 1D data.\n"
[091e71a2]2362                        message += "%s not be appended.\n" % str(new_plot.name)
[f53cd30]2363                        message += "try new plot option.\n"
[091e71a2]2364                        wx.PostEvent(self, StatusEvent(status=message,
2365                                                       info='warning'))
[f53cd30]2366            else:
[c8e1996]2367                # if not append then new plot
[d85c194]2368                from sas.sasgui.guiframe.dataFitting import Data2D
[f53cd30]2369                if issubclass(Data2D, new_plot.__class__):
[c8e1996]2370                    # for 2 D always plot in a separated new plot
[f53cd30]2371                    new_plot.group_id = wx.NewId()
2372                else:
2373                    # plot all 1D in a new plot
2374                    new_plot.group_id = GROUP_ID
2375            title = "PLOT " + str(new_plot.title)
2376            wx.PostEvent(self, NewPlotEvent(plot=new_plot,
[091e71a2]2377                                            title=title,
2378                                            group_id=new_plot.group_id))
2379
[f53cd30]2380    def remove_data(self, data_id, theory_id=None):
2381        """
2382        Delete data state if data_id is provide
2383        delete theory created with data of id data_id if theory_id is provide
2384        if delete all true: delete the all state
2385        else delete theory
2386        """
2387        temp = data_id + theory_id
2388        for plug in self.plugins:
2389            plug.delete_data(temp)
2390        data_list, _ = self._data_manager.get_by_id(data_id)
2391        _, temp_list_theory = self._data_manager.get_by_id(theory_id)
2392        total_plot_list = data_list.values()
2393        for item in temp_list_theory.values():
2394            theory_data, theory_state = item
2395            total_plot_list.append(theory_data)
2396        for new_plot in total_plot_list:
2397            for group_id in new_plot.list_group_id:
[78f75d02]2398                wx.PostEvent(self, NewPlotEvent(id=new_plot.id,
[091e71a2]2399                                                group_id=group_id,
2400                                                action='remove'))
[78f75d02]2401                wx.CallAfter(self._remove_res_plot, new_plot.id)
[091e71a2]2402        self._data_manager.delete_data(data_id=data_id,
[f53cd30]2403                                       theory_id=theory_id)
[091e71a2]2404
[f53cd30]2405    def _remove_res_plot(self, id):
2406        """
2407        Try to remove corresponding res plot
[091e71a2]2408
[f53cd30]2409        : param id: id of the data
2410        """
2411        try:
[091e71a2]2412            wx.PostEvent(self, NewPlotEvent(id=("res" + str(id)),
2413                                            group_id=("res" + str(id)),
2414                                            action='remove'))
[f53cd30]2415        except:
[c155a16]2416            logger.error(sys.exc_value)
[091e71a2]2417
[f53cd30]2418    def save_data1d(self, data, fname):
2419        """
2420        Save data dialog
2421        """
2422        default_name = fname
2423        wildcard = "Text files (*.txt)|*.txt|"\
[091e71a2]2424                    "CanSAS 1D files(*.xml)|*.xml"
[f53cd30]2425        path = None
2426        dlg = wx.FileDialog(self, "Choose a file",
2427                            self._default_save_location,
[091e71a2]2428                            default_name, wildcard, wx.SAVE)
2429
[f53cd30]2430        if dlg.ShowModal() == wx.ID_OK:
2431            path = dlg.GetPath()
2432            # ext_num = 0 for .txt, ext_num = 1 for .xml
2433            # This is MAC Fix
2434            ext_num = dlg.GetFilterIndex()
2435            if ext_num == 0:
[78f75d02]2436                ext_format = '.txt'
[f53cd30]2437            else:
[78f75d02]2438                ext_format = '.xml'
2439            path = os.path.splitext(path)[0] + ext_format
[f53cd30]2440            mypath = os.path.basename(path)
[091e71a2]2441
[c8e1996]2442            # Instantiate a loader
[091e71a2]2443            loader = Loader()
[78f75d02]2444            ext_format = ".txt"
2445            if os.path.splitext(mypath)[1].lower() == ext_format:
[f53cd30]2446                # Make sure the ext included in the file name
2447                # especially on MAC
[78f75d02]2448                fName = os.path.splitext(path)[0] + ext_format
[f53cd30]2449                self._onsaveTXT(data, fName)
[78f75d02]2450            ext_format = ".xml"
2451            if os.path.splitext(mypath)[1].lower() == ext_format:
[f53cd30]2452                # Make sure the ext included in the file name
2453                # especially on MAC
[78f75d02]2454                fName = os.path.splitext(path)[0] + ext_format
2455                loader.save(fName, data, ext_format)
[f53cd30]2456            try:
2457                self._default_save_location = os.path.dirname(path)
2458            except:
[091e71a2]2459                pass
[f53cd30]2460        dlg.Destroy()
[091e71a2]2461
[f53cd30]2462    def _onsaveTXT(self, data, path):
2463        """
[091e71a2]2464        Save file as txt
[069aae2]2465
2466        .. todo:: Refactor and remove this method. See 'TODO' in _onSave.
[f53cd30]2467        """
[c8e1996]2468        if path is not None:
[f53cd30]2469            out = open(path, 'w')
2470            has_errors = True
[c8e1996]2471            if data.dy is None or data.dy == []:
[f53cd30]2472                has_errors = False
2473            # Sanity check
2474            if has_errors:
2475                try:
2476                    if len(data.y) != len(data.dy):
2477                        has_errors = False
2478                except:
2479                    has_errors = False
2480            if has_errors:
[c8e1996]2481                if data.dx is not None and data.dx != []:
[f53cd30]2482                    out.write("<X>   <Y>   <dY>   <dX>\n")
2483                else:
2484                    out.write("<X>   <Y>   <dY>\n")
2485            else:
2486                out.write("<X>   <Y>\n")
[091e71a2]2487
[f53cd30]2488            for i in range(len(data.x)):
2489                if has_errors:
[c8e1996]2490                    if data.dx is not None and data.dx != []:
2491                        if data.dx[i] is not None:
[091e71a2]2492                            out.write("%g  %g  %g  %g\n" % (data.x[i],
2493                                                            data.y[i],
2494                                                            data.dy[i],
2495                                                            data.dx[i]))
[f53cd30]2496                        else:
[091e71a2]2497                            out.write("%g  %g  %g\n" % (data.x[i],
[f53cd30]2498                                                        data.y[i],
2499                                                        data.dy[i]))
2500                    else:
[091e71a2]2501                        out.write("%g  %g  %g\n" % (data.x[i],
[f53cd30]2502                                                    data.y[i],
2503                                                    data.dy[i]))
2504                else:
[091e71a2]2505                    out.write("%g  %g\n" % (data.x[i],
[f53cd30]2506                                            data.y[i]))
[091e71a2]2507            out.close()
2508
[f53cd30]2509    def show_data1d(self, data, name):
2510        """
2511        Show data dialog
[091e71a2]2512        """
[f53cd30]2513        try:
2514            xmin = min(data.x)
2515            ymin = min(data.y)
2516        except:
[091e71a2]2517            msg = "Unable to find min/max of \n data named %s" % \
2518                        data.filename
[f53cd30]2519            wx.PostEvent(self, StatusEvent(status=msg,
[091e71a2]2520                                           info="error"))
[f53cd30]2521            raise ValueError, msg
[c8e1996]2522        # text = str(data)
[f53cd30]2523        text = data.__str__()
2524        text += 'Data Min Max:\n'
[091e71a2]2525        text += 'X_min = %s:  X_max = %s\n' % (xmin, max(data.x))
2526        text += 'Y_min = %s:  Y_max = %s\n' % (ymin, max(data.y))
[c8e1996]2527        if data.dy is not None:
[091e71a2]2528            text += 'dY_min = %s:  dY_max = %s\n' % (min(data.dy), max(data.dy))
[f53cd30]2529        text += '\nData Points:\n'
2530        x_st = "X"
2531        for index in range(len(data.x)):
[c8e1996]2532            if data.dy is not None and len(data.dy) > index:
[f53cd30]2533                dy_val = data.dy[index]
2534            else:
2535                dy_val = 0.0
[c8e1996]2536            if data.dx is not None and len(data.dx) > index:
[f53cd30]2537                dx_val = data.dx[index]
2538            else:
2539                dx_val = 0.0
[c8e1996]2540            if data.dxl is not None and len(data.dxl) > index:
[091e71a2]2541                if index == 0:
[f53cd30]2542                    x_st = "Xl"
2543                dx_val = data.dxl[index]
[c8e1996]2544            elif data.dxw is not None and len(data.dxw) > index:
[091e71a2]2545                if index == 0:
[f53cd30]2546                    x_st = "Xw"
2547                dx_val = data.dxw[index]
[091e71a2]2548
[f53cd30]2549            if index == 0:
[091e71a2]2550                text += "<index> \t<X> \t<Y> \t<dY> \t<d%s>\n" % x_st
[f53cd30]2551            text += "%s \t%s \t%s \t%s \t%s\n" % (index,
[091e71a2]2552                                                  data.x[index],
2553                                                  data.y[index],
2554                                                  dy_val,
2555                                                  dx_val)
[f53cd30]2556        from pdfview import TextFrame
[091e71a2]2557        frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
[f53cd30]2558        # put icon
[091e71a2]2559        self.put_icon(frame)
2560        frame.Show(True)
2561
2562    def save_data2d(self, data, fname):
[f53cd30]2563        """
2564        Save data2d dialog
2565        """
2566        default_name = fname
2567        wildcard = "IGOR/DAT 2D file in Q_map (*.dat)|*.DAT"
2568        dlg = wx.FileDialog(self, "Choose a file",
2569                            self._default_save_location,
[091e71a2]2570                            default_name, wildcard, wx.SAVE)
2571
[f53cd30]2572        if dlg.ShowModal() == wx.ID_OK:
2573            path = dlg.GetPath()
2574            # ext_num = 0 for .txt, ext_num = 1 for .xml
2575            # This is MAC Fix
2576            ext_num = dlg.GetFilterIndex()
2577            if ext_num == 0:
[78f75d02]2578                ext_format = '.dat'
[f53cd30]2579            else:
[78f75d02]2580                ext_format = ''
2581            path = os.path.splitext(path)[0] + ext_format
[f53cd30]2582            mypath = os.path.basename(path)
[091e71a2]2583
[c8e1996]2584            # Instantiate a loader
[091e71a2]2585            loader = Loader()
[f53cd30]2586
[78f75d02]2587            ext_format = ".dat"
2588            if os.path.splitext(mypath)[1].lower() == ext_format:
[f53cd30]2589                # Make sure the ext included in the file name
2590                # especially on MAC
[78f75d02]2591                fileName = os.path.splitext(path)[0] + ext_format
2592                loader.save(fileName, data, ext_format)
[f53cd30]2593            try:
2594                self._default_save_location = os.path.dirname(path)
2595            except:
[091e71a2]2596                pass
2597        dlg.Destroy()
2598
[f53cd30]2599    def show_data2d(self, data, name):
2600        """
2601        Show data dialog
[091e71a2]2602        """
2603        wx.PostEvent(self, StatusEvent(status="Gathering Data2D Info.",
2604                                       type='start'))
2605        text = data.__str__()
[f53cd30]2606        text += 'Data Min Max:\n'
[091e71a2]2607        text += 'I_min = %s\n' % min(data.data)
2608        text += 'I_max = %s\n\n' % max(data.data)
[f53cd30]2609        text += 'Data (First 2501) Points:\n'
2610        text += 'Data columns include err(I).\n'
2611        text += 'ASCII data starts here.\n'
2612        text += "<index> \t<Qx> \t<Qy> \t<I> \t<dI> \t<dQparal> \t<dQperp>\n"
2613        di_val = 0.0
2614        dx_val = 0.0
2615        dy_val = 0.0
2616        len_data = len(data.qx_data)
2617        for index in xrange(0, len_data):
2618            x_val = data.qx_data[index]
2619            y_val = data.qy_data[index]
2620            i_val = data.data[index]
[c8e1996]2621            if data.err_data is not None:
[f53cd30]2622                di_val = data.err_data[index]
[c8e1996]2623            if data.dqx_data is not None:
[f53cd30]2624                dx_val = data.dqx_data[index]
[c8e1996]2625            if data.dqy_data is not None:
[f53cd30]2626                dy_val = data.dqy_data[index]
[091e71a2]2627
[f53cd30]2628            text += "%s \t%s \t%s \t%s \t%s \t%s \t%s\n" % (index,
[091e71a2]2629                                                            x_val,
2630                                                            y_val,
2631                                                            i_val,
2632                                                            di_val,
2633                                                            dx_val,
2634                                                            dy_val)
[f53cd30]2635            # Takes too long time for typical data2d: Break here
2636            if index >= 2500:
2637                text += ".............\n"
2638                break
2639
2640        from pdfview import TextFrame
[091e71a2]2641        frame = TextFrame(None, -1, "Data Info: %s" % data.name, text)
[f53cd30]2642        # put icon
2643        self.put_icon(frame)
[091e71a2]2644        frame.Show(True)
2645        wx.PostEvent(self, StatusEvent(status="Data2D Info Displayed",
2646                                       type='stop'))
2647
[f53cd30]2648    def set_current_perspective(self, perspective):
2649        """
[091e71a2]2650        set the current active perspective
[f53cd30]2651        """
2652        self._current_perspective = perspective
2653        name = "No current analysis selected"
2654        if self._current_perspective is not None:
2655            self._add_current_plugin_menu()
2656            for panel in self.panels.values():
2657                if hasattr(panel, 'CENTER_PANE') and panel.CENTER_PANE:
2658                    for name in self._current_perspective.get_perspective():
2659                        frame = panel.get_frame()
[c8e1996]2660                        if frame is not None:
[f53cd30]2661                            if name == panel.window_name:
2662                                panel.on_set_focus(event=None)
2663                                frame.Show(True)
2664                            else:
2665                                frame.Show(False)
2666            name = self._current_perspective.sub_menu
2667            if self._data_panel is not None:
2668                self._data_panel.set_active_perspective(name)
2669                self._check_applications_menu()
[c8e1996]2670            # Set the SasView title
[f53cd30]2671            self._set_title_name(name)
[091e71a2]2672
[f53cd30]2673    def _set_title_name(self, name):
2674        """
2675        Set the SasView title w/ the current application name
[091e71a2]2676
[f53cd30]2677        : param name: application name [string]
2678        """
2679        # Set SanView Window title w/ application anme
2680        title = self.title + "  - " + name + " -"
2681        self.SetTitle(title)
[091e71a2]2682
[f53cd30]2683    def _check_applications_menu(self):
2684        """
2685        check the menu of the current application
2686        """
2687        if self._applications_menu is not None:
2688            for menu in self._applications_menu.GetMenuItems():
2689                if self._current_perspective is not None:
2690                    name = self._current_perspective.sub_menu
2691                    if menu.IsCheckable():
2692                        if menu.GetLabel() == name:
2693                            menu.Check(True)
2694                        else:
[091e71a2]2695                            menu.Check(False)
2696
[f53cd30]2697    def enable_add_data(self, new_plot):
2698        """
2699        Enable append data on a plot panel
2700        """
2701
[c8e1996]2702        if self.panel_on_focus \
2703                not in self._plotting_plugin.plot_panels.values():
[f53cd30]2704            return
[c8e1996]2705        check = "Theory1D"
[f53cd30]2706        is_theory = len(self.panel_on_focus.plots) <= 1 and \
[c8e1996]2707            self.panel_on_focus.plots.values()[0].__class__.__name__ == check
[091e71a2]2708
[f53cd30]2709        is_data2d = hasattr(new_plot, 'data')
[091e71a2]2710
[f53cd30]2711        is_data1d = self.panel_on_focus.__class__.__name__ == "ModelPanel1D"\
2712            and self.panel_on_focus.group_id is not None
2713        has_meta_data = hasattr(new_plot, 'meta_data')
[091e71a2]2714
[c8e1996]2715        # disable_add_data if the data is being recovered from  a saved state
[f53cd30]2716        is_state_data = False
2717        if has_meta_data:
[091e71a2]2718            if 'invstate' in new_plot.meta_data:
[f53cd30]2719                is_state_data = True
[c8e1996]2720            if 'prstate' in new_plot.meta_data:
[f53cd30]2721                is_state_data = True
[c8e1996]2722            if 'fitstate' in new_plot.meta_data:
[f53cd30]2723                is_state_data = True
[091e71a2]2724
[c8e1996]2725        return is_data1d and not is_data2d and not is_theory \
2726               and not is_state_data
[091e71a2]2727
[f53cd30]2728    def check_multimode(self, perspective=None):
2729        """
2730        Check the perspective have batch mode capablitity
2731        """
[c8e1996]2732        if perspective is None or self._data_panel is None:
[f53cd30]2733            return
2734        flag = perspective.get_batch_capable()
2735        flag_on = perspective.batch_on
2736        if flag:
2737            self._data_panel.rb_single_mode.SetValue(not flag_on)
2738            self._data_panel.rb_batch_mode.SetValue(flag_on)
2739        else:
2740            self._data_panel.rb_single_mode.SetValue(True)
2741            self._data_panel.rb_batch_mode.SetValue(False)
2742        self._data_panel.rb_single_mode.Enable(flag)
2743        self._data_panel.rb_batch_mode.Enable(flag)
2744
2745    def enable_edit_menu(self):
2746        """
2747        enable menu item under edit menu depending on the panel on focus
2748        """
2749        if self.cpanel_on_focus is not None and self._edit_menu is not None:
2750            flag = self.cpanel_on_focus.get_undo_flag()
2751            self._edit_menu.Enable(GUIFRAME_ID.UNDO_ID, flag)
2752            flag = self.cpanel_on_focus.get_redo_flag()
2753            self._edit_menu.Enable(GUIFRAME_ID.REDO_ID, flag)
2754            flag = self.cpanel_on_focus.get_copy_flag()
2755            self._edit_menu.Enable(GUIFRAME_ID.COPY_ID, flag)
2756            flag = self.cpanel_on_focus.get_paste_flag()
2757            self._edit_menu.Enable(GUIFRAME_ID.PASTE_ID, flag)
2758
[c8e1996]2759            # Copy menu
[f53cd30]2760            flag = self.cpanel_on_focus.get_copy_flag()
2761            self._edit_menu_copyas.Enable(GUIFRAME_ID.COPYEX_ID, flag)
2762            self._edit_menu_copyas.Enable(GUIFRAME_ID.COPYLAT_ID, flag)
2763
2764            flag = self.cpanel_on_focus.get_preview_flag()
2765            self._edit_menu.Enable(GUIFRAME_ID.PREVIEW_ID, flag)
2766            flag = self.cpanel_on_focus.get_reset_flag()
2767            self._edit_menu.Enable(GUIFRAME_ID.RESET_ID, flag)
2768        else:
2769            flag = False
2770            self._edit_menu.Enable(GUIFRAME_ID.UNDO_ID, flag)
2771            self._edit_menu.Enable(GUIFRAME_ID.REDO_ID, flag)
2772            self._edit_menu.Enable(GUIFRAME_ID.COPY_ID, flag)
2773            self._edit_menu.Enable(GUIFRAME_ID.PASTE_ID, flag)
2774            self._edit_menu.Enable(GUIFRAME_ID.PREVIEW_ID, flag)
2775            self._edit_menu.Enable(GUIFRAME_ID.RESET_ID, flag)
[091e71a2]2776
[f53cd30]2777    def on_undo_panel(self, event=None):
2778        """
2779        undo previous action of the last panel on focus if possible
2780        """
2781        if self.cpanel_on_focus is not None:
2782            self.cpanel_on_focus.on_undo(event)
[091e71a2]2783
[f53cd30]2784    def on_redo_panel(self, event=None):
2785        """
2786        redo the last cancel action done on the last panel on focus
2787        """
2788        if self.cpanel_on_focus is not None:
2789            self.cpanel_on_focus.on_redo(event)
[091e71a2]2790
[f53cd30]2791    def on_copy_panel(self, event=None):
2792        """
2793        copy the last panel on focus if possible
2794        """
2795        if self.cpanel_on_focus is not None:
2796            self.cpanel_on_focus.on_copy(event)
[091e71a2]2797
[f53cd30]2798    def on_paste_panel(self, event=None):
2799        """
2800        paste clipboard to the last panel on focus
2801        """
2802        if self.cpanel_on_focus is not None:
2803            self.cpanel_on_focus.on_paste(event)
[091e71a2]2804
[f53cd30]2805    def on_bookmark_panel(self, event=None):
2806        """
2807        bookmark panel
2808        """
2809        if self.cpanel_on_focus is not None:
2810            self.cpanel_on_focus.on_bookmark(event)
[091e71a2]2811
[f53cd30]2812    def append_bookmark(self, event=None):
2813        """
2814        Bookmark available information of the panel on focus
2815        """
2816        self._toolbar.append_bookmark(event)
[091e71a2]2817
[f53cd30]2818    def on_save_panel(self, event=None):
2819        """
2820        save possible information on the current panel
2821        """
2822        if self.cpanel_on_focus is not None:
2823            self.cpanel_on_focus.on_save(event)
[091e71a2]2824
[f53cd30]2825    def on_preview_panel(self, event=None):
2826        """
2827        preview information on the panel on focus
2828        """
2829        if self.cpanel_on_focus is not None:
2830            self.cpanel_on_focus.on_preview(event)
[091e71a2]2831
[f53cd30]2832    def on_print_panel(self, event=None):
2833        """
2834        print available information on the last panel on focus
2835        """
2836        if self.cpanel_on_focus is not None:
2837            self.cpanel_on_focus.on_print(event)
[091e71a2]2838
[f53cd30]2839    def on_zoom_panel(self, event=None):
2840        """
2841        zoom on the current panel if possible
2842        """
2843        if self.cpanel_on_focus is not None:
2844            self.cpanel_on_focus.on_zoom(event)
[091e71a2]2845
[f53cd30]2846    def on_zoom_in_panel(self, event=None):
2847        """
2848        zoom in of the panel on focus
2849        """
2850        if self.cpanel_on_focus is not None:
2851            self.cpanel_on_focus.on_zoom_in(event)
[091e71a2]2852
[f53cd30]2853    def on_zoom_out_panel(self, event=None):
2854        """
2855        zoom out on the panel on focus
2856        """
2857        if self.cpanel_on_focus is not None:
2858            self.cpanel_on_focus.on_zoom_out(event)
[091e71a2]2859
[f53cd30]2860    def on_drag_panel(self, event=None):
2861        """
2862        drag apply to the panel on focus
2863        """
2864        if self.cpanel_on_focus is not None:
2865            self.cpanel_on_focus.on_drag(event)
[091e71a2]2866
[f53cd30]2867    def on_reset_panel(self, event=None):
2868        """
2869        reset the current panel
2870        """
2871        if self.cpanel_on_focus is not None:
2872            self.cpanel_on_focus.on_reset(event)
[091e71a2]2873
2874    def on_change_caption(self, name, old_caption, new_caption):
[f53cd30]2875        """
2876        Change the panel caption
[091e71a2]2877
[f53cd30]2878        :param name: window_name of the pane
2879        :param old_caption: current caption [string]
2880        :param new_caption: new caption [string]
2881        """
2882        # wx.aui.AuiPaneInfo
[091e71a2]2883        pane_info = self.get_paneinfo(old_caption)
[f53cd30]2884        # update the data_panel.cb_plotpanel
2885        if 'data_panel' in self.panels.keys():
2886            # remove from data_panel combobox
2887            data_panel = self.panels["data_panel"]
2888            if data_panel.cb_plotpanel is not None:
2889                # Check if any panel has the same caption
[c8e1996]2890                has_newstring = data_panel.cb_plotpanel.FindString(
2891                    str(new_caption))
[f53cd30]2892                caption = new_caption
2893                if has_newstring != wx.NOT_FOUND:
2894                    captions = self._get_plotpanel_captions()
2895                    # Append nummber
2896                    inc = 1
[069aae2]2897                    # FIXME: fix this terrible loop
[f53cd30]2898                    while (1):
[091e71a2]2899                        caption = new_caption + '_%s' % str(inc)
[f53cd30]2900                        if caption not in captions:
2901                            break
2902                        inc += 1
2903                    # notify to users
[091e71a2]2904                    msg = "Found Same Title: Added '_%s'" % str(inc)
[f53cd30]2905                    wx.PostEvent(self, StatusEvent(status=msg))
2906                # update data_panel cb
[091e71a2]2907                pos = data_panel.cb_plotpanel.FindString(str(old_caption))
[f53cd30]2908                if pos != wx.NOT_FOUND:
2909                    data_panel.cb_plotpanel.SetString(pos, caption)
2910                    data_panel.cb_plotpanel.SetStringSelection(caption)
2911        # New Caption
2912        pane_info.SetTitle(caption)
2913        return caption
[091e71a2]2914
[f53cd30]2915    def get_paneinfo(self, name):
2916        """
2917        Get pane Caption from window_name
[091e71a2]2918
[f53cd30]2919        :param name: window_name in AuiPaneInfo
[069aae2]2920        :returns: AuiPaneInfo of the name
[f53cd30]2921        """
2922        for panel in self.plot_panels.values():
2923            if panel.frame.GetTitle() == name:
2924                return panel.frame
2925        return None
[091e71a2]2926
[f53cd30]2927    def enable_undo(self):
2928        """
2929        enable undo related control
2930        """
2931        if self.cpanel_on_focus is not None:
2932            self._toolbar.enable_undo(self.cpanel_on_focus)
[091e71a2]2933
[f53cd30]2934    def enable_redo(self):
2935        """
[091e71a2]2936        enable redo
[f53cd30]2937        """
2938        if self.cpanel_on_focus is not None:
2939            self._toolbar.enable_redo(self.cpanel_on_focus)
[091e71a2]2940
[f53cd30]2941    def enable_copy(self):
2942        """
2943        enable copy related control
2944        """
2945        if self.cpanel_on_focus is not None:
2946            self._toolbar.enable_copy(self.cpanel_on_focus)
[091e71a2]2947
[f53cd30]2948    def enable_paste(self):
2949        """
[091e71a2]2950        enable paste
[f53cd30]2951        """
2952        if self.cpanel_on_focus is not None:
2953            self._toolbar.enable_paste(self.cpanel_on_focus)
[091e71a2]2954
[f53cd30]2955    def enable_bookmark(self):
2956        """
[091e71a2]2957        Bookmark
[f53cd30]2958        """
2959        if self.cpanel_on_focus is not None:
2960            self._toolbar.enable_bookmark(self.cpanel_on_focus)
[091e71a2]2961
[f53cd30]2962    def enable_save(self):
2963        """
[091e71a2]2964        save
[f53cd30]2965        """
2966        if self.cpanel_on_focus is not None:
2967            self._toolbar.enable_save(self.cpanel_on_focus)
[091e71a2]2968
[f53cd30]2969    def enable_preview(self):
2970        """
[091e71a2]2971        preview
[f53cd30]2972        """
2973        if self.cpanel_on_focus is not None:
2974            self._toolbar.enable_preview(self.cpanel_on_focus)
[091e71a2]2975
[f53cd30]2976    def enable_print(self):
2977        """
[091e71a2]2978        print
[f53cd30]2979        """
2980        if self.cpanel_on_focus is not None:
2981            self._toolbar.enable_print(self.cpanel_on_focus)
[091e71a2]2982
[f53cd30]2983    def enable_zoom(self):
2984        """
[091e71a2]2985        zoom
[f53cd30]2986        """
2987        if self.cpanel_on_focus is not None:
2988            self._toolbar.enable_zoom(self.panel_on_focus)
[091e71a2]2989
[f53cd30]2990    def enable_zoom_in(self):
2991        """
[091e71a2]2992        zoom in
[f53cd30]2993        """
2994        if self.cpanel_on_focus is not None:
2995            self._toolbar.enable_zoom_in(self.panel_on_focus)
[091e71a2]2996
[f53cd30]2997    def enable_zoom_out(self):
2998        """
[091e71a2]2999        zoom out
[f53cd30]3000        """
3001        if self.cpanel_on_focus is not None:
3002            self._toolbar.enable_zoom_out(self.panel_on_focus)
[091e71a2]3003
[f53cd30]3004    def enable_drag(self, event=None):
3005        """
[091e71a2]3006        drag
[f53cd30]3007        """
[c8e1996]3008        # Not implemeted
[091e71a2]3009
[f53cd30]3010    def enable_reset(self):
3011        """
3012        reset the current panel
3013        """
3014        if self.cpanel_on_focus is not None:
3015            self._toolbar.enable_reset(self.panel_on_focus)
[091e71a2]3016
[f53cd30]3017    def get_toolbar_height(self):
3018        """
3019        """
3020        size_y = 0
[c8e1996]3021        if self.GetToolBar() is not None and self.GetToolBar().IsShown():
[f53cd30]3022            if not IS_LINUX:
3023                _, size_y = self.GetToolBar().GetSizeTuple()
3024        return size_y
[091e71a2]3025
[f53cd30]3026    def set_schedule_full_draw(self, panel=None, func='del'):
3027        """
3028        Add/subtract the schedule full draw list with the panel given
[091e71a2]3029
[f53cd30]3030        :param panel: plot panel
3031        :param func: append or del [string]
3032        """
3033
3034        # append this panel in the schedule list if not in yet
3035        if func == 'append':
[c8e1996]3036            if panel not in self.schedule_full_draw_list:
[091e71a2]3037                self.schedule_full_draw_list.append(panel)
[f53cd30]3038        # remove this panel from schedule list
3039        elif func == 'del':
3040            if len(self.schedule_full_draw_list) > 0:
3041                if panel in self.schedule_full_draw_list:
3042                    self.schedule_full_draw_list.remove(panel)
3043
3044        # reset the schdule
3045        if len(self.schedule_full_draw_list) == 0:
3046            self.schedule = False
3047        else:
[091e71a2]3048            self.schedule = True
3049
[f53cd30]3050    def full_draw(self):
3051        """
3052        Draw the panels with axes in the schedule to full dwar list
3053        """
[091e71a2]3054
[f53cd30]3055        count = len(self.schedule_full_draw_list)
[c8e1996]3056        # if not self.schedule:
[f53cd30]3057        if count < 1:
3058            self.set_schedule(False)
3059            return
3060
3061        else:
3062            ind = 0
3063            # if any of the panel is shown do full_draw
3064            for panel in self.schedule_full_draw_list:
3065                ind += 1
3066                if panel.frame.IsShown():
3067                    break
3068                # otherwise, return
3069                if ind == count:
3070                    return
[c8e1996]3071        # Simple redraw only for a panel shown
3072
[f53cd30]3073        def f_draw(panel):
3074            """
3075            Draw A panel in the full draw list
3076            """
3077            try:
3078                # This checking of GetCapture is to stop redrawing
3079                # while any panel is capture.
3080                frame = panel.frame
[091e71a2]3081
[f53cd30]3082                if not frame.GetCapture():
3083                    # draw if possible
3084                    panel.set_resizing(False)
[c8e1996]3085                    # panel.Show(True)
[f53cd30]3086                    panel.draw_plot()
3087                # Check if the panel is not shown
3088                flag = frame.IsShown()
[091e71a2]3089                frame.Show(flag)
[f53cd30]3090            except:
3091                pass
[091e71a2]3092
[069aae2]3093        # Draw all panels
[f53cd30]3094        if count == 1:
[091e71a2]3095            f_draw(self.schedule_full_draw_list[0])
[f53cd30]3096        else:
3097            map(f_draw, self.schedule_full_draw_list)
[069aae2]3098        # Reset the attr
[f53cd30]3099        if len(self.schedule_full_draw_list) == 0:
3100            self.set_schedule(False)
3101        else:
3102            self.set_schedule(True)
[091e71a2]3103
3104    def set_schedule(self, schedule=False):
[f53cd30]3105        """
3106        Set schedule
3107        """
3108        self.schedule = schedule
[091e71a2]3109
3110    def get_schedule(self):
[f53cd30]3111        """
3112        Get schedule
3113        """
3114        return self.schedule
[091e71a2]3115
[f53cd30]3116    def on_set_plot_focus(self, panel):
3117        """
3118        Set focus on a plot panel
3119        """
[c8e1996]3120        if panel is None:
[f53cd30]3121            return
[c8e1996]3122        # self.set_plot_unfocus()
[091e71a2]3123        panel.on_set_focus(None)
[f53cd30]3124        # set focusing panel
[091e71a2]3125        self.panel_on_focus = panel
[f53cd30]3126        self.set_panel_on_focus(None)
3127
[091e71a2]3128    def set_plot_unfocus(self):
[f53cd30]3129        """
3130        Un focus all plot panels
3131        """
3132        for plot in self.plot_panels.values():
3133            plot.on_kill_focus(None)
[091e71a2]3134
[f53cd30]3135    def get_window_size(self):
3136        """
[091e71a2]3137        Get window size
3138
[069aae2]3139        :returns: size
3140        :rtype: tuple
[f53cd30]3141        """
3142        width, height = self.GetSizeTuple()
3143        if not IS_WIN:
3144            # Subtract toolbar height to get real window side
3145            if self._toolbar.IsShown():
3146                height -= 45
3147        return (width, height)
[091e71a2]3148
[f53cd30]3149    def _onDrawIdle(self, *args, **kwargs):
3150        """
3151        ReDraw with axes
3152        """
3153        try:
3154            # check if it is time to redraw
[c8e1996]3155            if self.GetCapture() is None:
[f53cd30]3156                # Draw plot, changes resizing too
3157                self.full_draw()
3158        except:
3159            pass
[091e71a2]3160
[069aae2]3161        # restart idle
[f53cd30]3162        self._redraw_idle(*args, **kwargs)
3163
3164    def _redraw_idle(self, *args, **kwargs):
3165        """
3166        Restart Idle
3167        """
[069aae2]3168        # restart idle
[091e71a2]3169        self.idletimer.Restart(100 * TIME_FACTOR, *args, **kwargs)
3170
[f53cd30]3171
3172class DefaultPanel(wx.Panel, PanelBase):
3173    """
3174    Defines the API for a panels to work with
3175    the GUI manager
3176    """
[c8e1996]3177    # Internal nickname for the window, used by the AUI manager
[f53cd30]3178    window_name = "default"
[c8e1996]3179    # Name to appear on the window title bar
[f53cd30]3180    window_caption = "Welcome panel"
[c8e1996]3181    # Flag to tell the AUI manager to put this panel in the center pane
[f53cd30]3182    CENTER_PANE = True
[c8e1996]3183
[f53cd30]3184    def __init__(self, parent, *args, **kwds):
3185        wx.Panel.__init__(self, parent, *args, **kwds)
3186        PanelBase.__init__(self, parent)
[091e71a2]3187
[f53cd30]3188
[78f75d02]3189class SasViewApp(wx.App):
[f53cd30]3190    """
[78f75d02]3191    SasView application
[f53cd30]3192    """
3193    def OnInit(self):
3194        """
3195        When initialised
3196        """
[091e71a2]3197        pos, size, self.is_max = self.window_placement((GUIFRAME_WIDTH,
3198                                                        GUIFRAME_HEIGHT))
3199        self.frame = ViewerFrame(parent=None,
3200                                 title=APPLICATION_NAME,
3201                                 pos=pos,
3202                                 gui_style=DEFAULT_STYLE,
3203                                 size=size)
[f53cd30]3204        self.frame.Hide()
3205        if not IS_WIN:
3206            self.frame.EnableCloseButton(False)
3207        self.s_screen = None
3208
3209        try:
3210            self.open_file()
3211        except:
3212            msg = "%s Could not load " % str(APPLICATION_NAME)
3213            msg += "input file from command line.\n"
[c155a16]3214            logger.error(msg)
[f53cd30]3215        # Display a splash screen on top of the frame.
3216        try:
3217            if os.path.isfile(SPLASH_SCREEN_PATH):
[c8e1996]3218                self.s_screen = \
3219                    self.display_splash_screen(parent=self.frame,
3220                                               path=SPLASH_SCREEN_PATH)
[f53cd30]3221            else:
[091e71a2]3222                self.frame.Show()
[f53cd30]3223        except:
3224            if self.s_screen is not None:
3225                self.s_screen.Close()
3226            msg = "Cannot display splash screen\n"
[091e71a2]3227            msg += str(sys.exc_value)
[c155a16]3228            logger.error(msg)
[f53cd30]3229            self.frame.Show()
3230
3231        self.SetTopWindow(self.frame)
[091e71a2]3232
[f53cd30]3233        return True
[091e71a2]3234
[f53cd30]3235    def maximize_win(self):
3236        """
3237        Maximize the window after the frame shown
3238        """
3239        if self.is_max:
3240            if self.frame.IsShown():
3241                # Max window size
3242                self.frame.Maximize(self.is_max)
3243
3244    def open_file(self):
3245        """
3246        open a state file at the start of the application
3247        """
3248        input_file = None
3249        if len(sys.argv) >= 2:
3250            cmd = sys.argv[0].lower()
[091e71a2]3251            basename = os.path.basename(cmd)
[f53cd30]3252            app_base = str(APPLICATION_NAME).lower()
3253            if os.path.isfile(cmd) or basename.lower() == app_base:
3254                app_py = app_base + '.py'
3255                app_exe = app_base + '.exe'
3256                app_app = app_base + '.app'
3257                if basename.lower() in [app_py, app_exe, app_app, app_base]:
3258                    data_base = sys.argv[1]
[efe730d]3259                    input_file = os.path.normpath(os.path.join(get_app_dir(),
[f53cd30]3260                                                               data_base))
3261        if input_file is None:
3262            return
3263        if self.frame is not None:
3264            self.frame.set_input_file(input_file=input_file)
[091e71a2]3265
3266    def clean_plugin_models(self, path):
[f53cd30]3267        """
3268        Delete plugin models  in app folder
[091e71a2]3269
[f53cd30]3270        :param path: path of the plugin_models folder in app
3271        """
3272        # do it only the first time app loaded
[091e71a2]3273        # delete unused model folder
[efe730d]3274        model_folder = os.path.join(get_app_dir(), path)
[f53cd30]3275        if os.path.exists(model_folder) and os.path.isdir(model_folder):
3276            if len(os.listdir(model_folder)) > 0:
3277                try:
[78f75d02]3278                    for filename in os.listdir(model_folder):
3279                        file_path = os.path.join(model_folder, filename)
[f53cd30]3280                        if os.path.isfile(file_path):
3281                            os.remove(file_path)
3282                except:
[c155a16]3283                    logger.error("gui_manager.clean_plugin_models:\n  %s"
[f53cd30]3284                                  % sys.exc_value)
[091e71a2]3285
[f53cd30]3286    def set_manager(self, manager):
3287        """
3288        Sets a reference to the application manager
[091e71a2]3289        of the GUI manager (Frame)
[f53cd30]3290        """
3291        self.frame.set_manager(manager)
[091e71a2]3292
[f53cd30]3293    def build_gui(self):
3294        """
3295        Build the GUI
3296        """
[c8e1996]3297        # try to load file at the start
[78f75d02]3298        self.open_file()
[f53cd30]3299        self.frame.build_gui()
[091e71a2]3300
[f53cd30]3301    def set_welcome_panel(self, panel_class):
3302        """
3303        Set the welcome panel
[091e71a2]3304
[f53cd30]3305        :param panel_class: class of the welcome panel to be instantiated
[091e71a2]3306
[f53cd30]3307        """
3308        self.frame.welcome_panel_class = panel_class
[091e71a2]3309
[f53cd30]3310    def add_perspective(self, perspective):
3311        """
3312        Manually add a perspective to the application GUI
3313        """
3314        self.frame.add_perspective(perspective)
[091e71a2]3315
[f53cd30]3316    def window_placement(self, size):
3317        """
3318        Determines the position and size of the application frame such that it
3319        fits on the user's screen without obstructing (or being obstructed by)
3320        the Windows task bar.  The maximum initial size in pixels is bounded by
3321        WIDTH x HEIGHT.  For most monitors, the application
3322        will be centered on the screen; for very large monitors it will be
3323        placed on the left side of the screen.
3324        """
3325        is_maximized = False
[069aae2]3326        # Get size of screen without
[f53cd30]3327        for screenCount in range(wx.Display().GetCount()):
3328            screen = wx.Display(screenCount)
3329            if screen.IsPrimary():
3330                displayRect = screen.GetClientArea()
3331                break
[091e71a2]3332
3333        posX, posY, displayWidth, displayHeight = displayRect
[f53cd30]3334        customWidth, customHeight = size
[091e71a2]3335
[f53cd30]3336        # If the custom size is default, set 90% of the screen size
3337        if customWidth <= 0 and customHeight <= 0:
3338            if customWidth == 0 and customHeight == 0:
3339                is_maximized = True
3340            customWidth = displayWidth * 0.9
3341            customHeight = displayHeight * 0.9
3342        else:
[069aae2]3343            # If the custom screen is bigger than the
[f53cd30]3344            # window screen than make maximum size
3345            if customWidth > displayWidth:
3346                customWidth = displayWidth
3347            if customHeight > displayHeight:
3348                customHeight = displayHeight
[091e71a2]3349
[f53cd30]3350        # Note that when running Linux and using an Xming (X11) server on a PC
3351        # with a dual  monitor configuration, the reported display size may be
3352        # that of both monitors combined with an incorrect display count of 1.
3353        # To avoid displaying this app across both monitors, we check for
3354        # screen 'too big'.  If so, we assume a smaller width which means the
3355        # application will be placed towards the left hand side of the screen.
[091e71a2]3356
[f53cd30]3357        # If dual screen registered as 1 screen. Make width half.
3358        # MAC just follows the default behavior of pos
3359        if IS_WIN:
[091e71a2]3360            if displayWidth > (displayHeight * 2):
3361                if customWidth == displayWidth:
3362                    customWidth = displayWidth / 2
[f53cd30]3363                # and set the position to be the corner of the screen.
3364                posX = 0
3365                posY = 0
[091e71a2]3366
[f53cd30]3367            # Make the position the middle of the screen. (Not 0,0)
3368            else:
[091e71a2]3369                posX = (displayWidth - customWidth) / 2
3370                posY = (displayHeight - customHeight) / 2
3371        # Return the suggested position and size for the application frame.
[f53cd30]3372        return (posX, posY), (customWidth, customHeight), is_maximized
3373
[091e71a2]3374    def display_splash_screen(self, parent,
[f53cd30]3375                              path=SPLASH_SCREEN_PATH):
3376        """Displays the splash screen.  It will exactly cover the main frame."""
[091e71a2]3377
[f53cd30]3378        # Prepare the picture.  On a 2GHz intel cpu, this takes about a second.
3379        image = wx.Image(path, wx.BITMAP_TYPE_PNG)
[091e71a2]3380        image.Rescale(SPLASH_SCREEN_WIDTH,
[f53cd30]3381                      SPLASH_SCREEN_HEIGHT, wx.IMAGE_QUALITY_HIGH)
3382        bm = image.ConvertToBitmap()
3383
3384        # Create and show the splash screen.  It will disappear only when the
3385        # program has entered the event loop AND either the timeout has expired
3386        # or the user has left clicked on the screen.  Thus any processing
3387        # performed in this routine (including sleeping) or processing in the
3388        # calling routine (including doing imports) will prevent the splash
3389        # screen from disappearing.
3390        #
3391        # Note that on Linux, the timeout appears to occur immediately in which
3392        # case the splash screen disappears upon entering the event loop.
3393        s_screen = wx.SplashScreen(bitmap=bm,
[091e71a2]3394                                   splashStyle=(wx.SPLASH_TIMEOUT |
[f53cd30]3395                                                wx.SPLASH_CENTRE_ON_SCREEN),
[091e71a2]3396                                   style=(wx.SIMPLE_BORDER |
3397                                          wx.FRAME_NO_TASKBAR |
[f53cd30]3398                                          wx.FRAME_FLOAT_ON_PARENT),
3399                                   milliseconds=SS_MAX_DISPLAY_TIME,
3400                                   parent=parent,
3401                                   id=wx.ID_ANY)
[d85c194]3402        from sas.sasgui.guiframe.gui_statusbar import SPageStatusbar
[f53cd30]3403        statusBar = SPageStatusbar(s_screen)
3404        s_screen.SetStatusBar(statusBar)
3405        s_screen.Bind(wx.EVT_CLOSE, self.on_close_splash_screen)
3406        s_screen.Show()
3407        return s_screen
[091e71a2]3408
[f53cd30]3409    def on_close_splash_screen(self, event):
3410        """
3411        When the splash screen is closed.
3412        """
3413        self.frame.Show(True)
3414        event.Skip()
3415        self.maximize_win()
3416
3417
3418class MDIFrame(CHILD_FRAME):
3419    """
3420    Frame for panels
3421    """
[091e71a2]3422    def __init__(self, parent, panel, title="Untitled", size=(300, 200)):
[f53cd30]3423        """
3424        comment
3425        :param parent: parent panel/container
3426        """
3427        # Initialize the Frame object
[c8e1996]3428        CHILD_FRAME.__init__(self, parent=parent, id=wx.ID_ANY,
3429                             title=title, size=size)
[f53cd30]3430        self.parent = parent
3431        self.name = "Untitled"
3432        self.batch_on = self.parent.batch_on
3433        self.panel = panel
[c8e1996]3434        if panel is not None:
[f53cd30]3435            self.set_panel(panel)
3436        self.Show(False)
[091e71a2]3437
[f53cd30]3438    def show_data_panel(self, action):
3439        """
[069aae2]3440        Turns on the data panel
3441
3442        The the data panel is optional.  Most of its functions can be
3443        performed from the menu bar and from the plots.
[f53cd30]3444        """
3445        self.parent.show_data_panel(action)
[091e71a2]3446
[f53cd30]3447    def set_panel(self, panel):
3448        """
3449        """
3450        self.panel = panel
3451        self.name = panel.window_name
3452        self.SetTitle(panel.window_caption)
3453        self.SetHelpText(panel.help_string)
3454        width, height = self.parent._get_panels_size(panel)
3455        if hasattr(panel, "CENTER_PANE") and panel.CENTER_PANE:
[091e71a2]3456            width *= 0.6
[f53cd30]3457        self.SetSize((width, height))
3458        self.parent.put_icon(self)
3459        self.Bind(wx.EVT_SET_FOCUS, self.set_panel_focus)
3460        self.Bind(wx.EVT_CLOSE, self.OnClose)
3461        self.Show(False)
[091e71a2]3462
[f53cd30]3463    def set_panel_focus(self, event):
3464        """
3465        """
3466        if self.parent.panel_on_focus != self.panel:
3467            self.panel.SetFocus()
3468            self.parent.panel_on_focus = self.panel
[091e71a2]3469
[f53cd30]3470    def OnClose(self, event):
3471        """
3472        On Close event
3473        """
3474        self.panel.on_close(event)
[091e71a2]3475
3476if __name__ == "__main__":
[b080ba8]3477    app = SasViewApp(0)
[f53cd30]3478    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.