"""
Base Page for fitting
"""


import sys
import os
import time
import copy
import math
import json
import logging
import traceback
from queue import Queue
from threading import Thread
from collections import defaultdict

import numpy as np

import wx
from wx.lib.scrolledpanel import ScrolledPanel

from sasmodels.sasview_model import MultiplicationModel
from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
from sasmodels.weights import GaussianDispersion

from sas.sascalc.dataloader.data_info import Detector
from sas.sascalc.dataloader.data_info import Source
from sas.sascalc.fit.pagestate import PageState
from sas.sascalc.fit.models import PLUGIN_NAME_BASE

from sas.sasgui.guiframe.panel_base import PanelBase
from sas.sasgui.guiframe.utils import format_number, check_float, IdList, \
    check_int
from sas.sasgui.guiframe.events import PanelOnFocusEvent
from sas.sasgui.guiframe.events import StatusEvent
from sas.sasgui.guiframe.events import AppendBookmarkEvent
from sas.sasgui.guiframe.dataFitting import Data2D
from sas.sasgui.guiframe.dataFitting import Data1D
from sas.sasgui.guiframe.dataFitting import check_data_validity
from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
from sas.sasgui.guiframe.documentation_window import DocumentationWindow

from .report_dialog import ReportDialog
from .utils import get_weight

logger = logging.getLogger(__name__)

(PageInfoEvent, EVT_PAGE_INFO) = wx.lib.newevent.NewEvent()
(PreviousStateEvent, EVT_PREVIOUS_STATE) = wx.lib.newevent.NewEvent()
(NextStateEvent, EVT_NEXT_STATE) = wx.lib.newevent.NewEvent()

_BOX_WIDTH = 76
_QMIN_DEFAULT = 0.0005
_QMAX_DEFAULT = 0.5
_NPTS_DEFAULT = 50
# Control panel width
if sys.platform.count("win32") > 0:
    PANEL_WIDTH = 450
    FONT_VARIANT = 0
    ON_MAC = False
else:
    PANEL_WIDTH = 500
    FONT_VARIANT = 1
    ON_MAC = True

CUSTOM_MODEL = 'Plugin Models'

class BasicPage(ScrolledPanel, PanelBase):
    """
    This class provide general structure of the fitpanel page
    """
    # Internal name for the AUI manager
    window_name = "Fit Page"
    # Title to appear on top of the window
    window_caption = "Fit Page "

    # These two buttons have specific IDs since they seem to be created more
    # frequently than they need to.  In particular, set_dispers_sizer() is
    # called by _on_select_model
    ID_BOOKMARK = wx.NewId()
    ID_DISPERSER_HELP = wx.NewId()
    _id_pool = IdList()

    def __init__(self, parent, color='blue', **kwargs):
        """
        """
        ScrolledPanel.__init__(self, parent, **kwargs)
        PanelBase.__init__(self, parent)
        self.SetupScrolling()
        # Set window's font size
        self.SetWindowVariant(variant=FONT_VARIANT)
        self.SetBackgroundColour(color)

        self._ids = iter(self._id_pool)
        # parent of the page
        self.parent = parent
        # manager is the fitting plugin
        # owner of the page (fitting plugin)
        self.event_owner = None
        # current model
        self.model = None
        self.m_name = None
        self.index_model = None
        self.panel = None
        # data
        self.data = None
        # list of available data
        self.data_list = []
        self.mask = None
        self.uid = wx.NewId()
        self.graph_id = None
        # Q range for data set
        self.qmin_data_set = np.inf
        self.qmax_data_set = None
        self.npts_data_set = 0
        # Q range
        self.qmin = None
        self.qmax = None
        self.qmax_x = _QMAX_DEFAULT
        self.qmin_x = _QMIN_DEFAULT
        self.npts_x = _NPTS_DEFAULT
        # total number of point: float
        self.npts = None
        self.num_points = None
        # smear default
        self.current_smearer = None
        # 2D smear accuracy default
        self.smear2d_accuracy = 'Low'
        # slit smear:
        self.dxl = None
        self.dxw = None
        # pinhole smear
        self.dx_percent = None
        # smear attrbs
        self.enable_smearer = None
        self.disable_smearer = None
        self.pinhole_smearer = None
        self.slit_smearer = None
        # weight attrbs
        self.dI_noweight = None
        self.dI_didata = None
        self.dI_sqrdata = None
        self.dI_idata = None
        # other attrbs
        self.dq_l = None
        self.dq_r = None
        self.tcChi = None
        self.disp_box = None
        self.model_disp = None
        self.Npts_fit = None
        self.Npts_total = None
        self.theory_qmin = None
        self.theory_qmax = None
        self.theory_qmin_x = None
        self.theory_qmax_x = None
        self.btEditMask = None
        self.btFit = None
        self.sld_axes = None
        self.multi_factor = None

        self.disp_cb_dict = {}

        # self.state = PageState()
        # dictionary containing list of models
        self.model_list_box = {}

        # Data member to store the dispersion object created
        self._disp_obj_dict = {}
        # selected parameters to apply dispersion
        self.disp_cb_dict = {}
        # smearer object
        self.enable2D = False
        self._has_magnetic = False
        self.magnetic_on = False
        self.is_mac = ON_MAC
        self.formfactorbox = None
        self.structurebox = None
        self.categorybox = None
        # list of model parameters. each item must have same length
        # each item related to a given parameters
        # [cb state, name, value, "+/-", error of fit, min, max , units]
        self.parameters = []
        # non-fittable parameter whose value is astring
        self.str_parameters = []
        # list of parameters to fit , must be like self.parameters
        self.param_toFit = []
        # list of looking like parameters but with non fittable parameters info
        self.fixed_param = []
        # list of looking like parameters but with  fittable parameters info
        self.fittable_param = []
        # list of dispersion parameters
        self.disp_list = []
        self.disp_name = ""

        # list of orientation parameters
        self.orientation_params = []
        self.orientation_params_disp = []
#       Self.model should ALWAYS be None here.  It was set to none above in
#       this long init setting.  no obvious function call in between setting
#       and this - commenting out on 4/8/2014 by PDB.  Remove once clear
#       it is pointless.
#        if self.model is not None:
#            self.disp_list = self.model.getDispParamList()
        self.temp_multi_functional = False
        # enable model 2D draw
        self.enable2D = False
        # check that the fit range is correct to plot the model again
        self.fitrange = True
        # Create memento to save the current state
        self.state = PageState(model=self.model, data=self.data)
        # flag to determine if state has change
        self.state_change = False
        # save customized array
        self.values = {}   # type: Dict[str, List[float, ...]]
        self.weights = {}   # type: Dict[str, List[float, ...]]
        # retrieve saved state
        self.number_saved_state = 0
        # dictionary of saved state
        self.saved_states = {}
        # Create context menu for page
        self.popUpMenu = wx.Menu()

        wx_id = next(self._ids)
        self._keep = wx.MenuItem(self.popUpMenu, wx_id, "Add bookmark",
                                 " Keep the panel status to recall it later")
        self.popUpMenu.AppendItem(self._keep)
        self._keep.Enable(False)
        self._set_bookmark_flag(False)
        self._set_save_flag(False)
        wx.EVT_MENU(self, wx_id, self.on_bookmark)
        self.popUpMenu.AppendSeparator()

        # Default locations
        self._default_save_location = os.getcwd()
        # save initial state on context menu
        # self.onSave(event=None)
        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)

        # bind key event
        self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)

        # create the basic structure of the panel with empty sizer
        self.define_page_structure()
        # drawing Initial dispersion parameters sizer
        self.set_dispers_sizer()

        # layout
        self.set_layout()

        # Setting up a thread for the fitting
        self.threaded_draw_queue = Queue()

        self.draw_worker_thread = Thread(target = self._threaded_draw_worker,
                                         args = (self.threaded_draw_queue,))
        self.draw_worker_thread.setDaemon(True)
        self.draw_worker_thread.start()

        # And a home for the thread submission times
        self.last_time_fit_submitted = 0.00

    def set_index_model(self, index):
        """
        Index related to this page
        """
        self.index_model = index

    def create_default_data(self):
        """
        Given the user selection, creates a 1D or 2D data
        Only when the page is on theory mode.
        """
        if not hasattr(self, "model_view"):
            return
        toggle_mode_on = self.model_view.IsEnabled() or self.data is None
        if toggle_mode_on:
            if self.enable2D and not check_data_validity(self.data):
                self._create_default_2d_data()
            else:
                if self.pointsbox.GetValue():
                    self._create_log_1d_data()
                else:
                    self._create_default_1d_data()

            if self.model is not None:
                if not self.data.is_data:
                    self._manager.page_finder[self.uid].set_fit_data(
                        data=[self.data])
            self.on_smear_helper(update=True)
            self.state.enable_smearer = self.enable_smearer.GetValue()
            self.state.disable_smearer = self.disable_smearer.GetValue()
            self.state.pinhole_smearer = self.pinhole_smearer.GetValue()
            self.state.slit_smearer = self.slit_smearer.GetValue()

    def _create_default_1d_data(self):
        """
        Create default data for fitting perspective
        Only when the page is on theory mode.
        :warning: This data is never plotted.

        """
        x = np.linspace(start=self.qmin_x, stop=self.qmax_x,
                        num=self.npts_x, endpoint=True)
        self.data = Data1D(x=x)
        self.data.xaxis('\\rm{Q}', "A^{-1}")
        self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
        self.data.is_data = False
        self.data.id = str(self.uid) + " data"
        self.data.group_id = str(self.uid) + " Model1D"

    def _create_log_1d_data(self):
        """
        Create log-spaced data for fitting perspective
        Only when the page is on theory mode.
        :warning: This data is never plotted.

        """
        if self.qmin_x >= 1.e-10:
            qmin = np.log10(self.qmin_x)
        else:
            qmin = -10.

        if self.qmax_x <= 1.e10:
            qmax = np.log10(self.qmax_x)
        else:
            qmax = 10.

        x = np.logspace(start=qmin, stop=qmax,
                        num=self.npts_x, endpoint=True, base=10.0)
        self.data = Data1D(x=x)
        self.data.xaxis('\\rm{Q}', "A^{-1}")
        self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
        self.data.is_data = False
        self.data.id = str(self.uid) + " data"
        self.data.group_id = str(self.uid) + " Model1D"

    def _create_default_2d_data(self):
        """
        Create 2D data by default
        Only when the page is on theory mode.
        :warning: This data is never plotted.
        """
        self.data = Data2D()
        qmax = self.qmax_x / math.sqrt(2)
        self.data.xaxis('\\rm{Q_{x}}', 'A^{-1}')
        self.data.yaxis('\\rm{Q_{y}}', 'A^{-1}')
        self.data.is_data = False
        self.data.id = str(self.uid) + " data"
        self.data.group_id = str(self.uid) + " Model2D"
        # Default values
        self.data.detector.append(Detector())
        index = len(self.data.detector) - 1
        self.data.detector[index].distance = 8000   # mm
        self.data.source.wavelength = 6             # A
        self.data.detector[index].pixel_size.x = 5  # mm
        self.data.detector[index].pixel_size.y = 5  # mm
        self.data.detector[index].beam_center.x = qmax
        self.data.detector[index].beam_center.y = qmax
        xmax = qmax
        xmin = -qmax
        ymax = qmax
        ymin = -qmax
        qstep = self.npts_x

        x = np.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
        y = np.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True)
        # use data info instead
        new_x = np.tile(x, (len(y), 1))
        new_y = np.tile(y, (len(x), 1))
        new_y = new_y.swapaxes(0, 1)
        # all data reuire now in 1d array
        qx_data = new_x.flatten()
        qy_data = new_y.flatten()
        q_data = np.sqrt(qx_data * qx_data + qy_data * qy_data)
        # set all True (standing for unmasked) as default
        mask = np.ones(len(qx_data), dtype=bool)
        # store x and y bin centers in q space
        x_bins = x
        y_bins = y

        self.data.source = Source()
        self.data.data = np.ones(len(mask))
        self.data.err_data = np.ones(len(mask))
        self.data.qx_data = qx_data
        self.data.qy_data = qy_data
        self.data.q_data = q_data
        self.data.mask = mask
        self.data.x_bins = x_bins
        self.data.y_bins = y_bins
        # max and min taking account of the bin sizes
        self.data.xmin = xmin
        self.data.xmax = xmax
        self.data.ymin = ymin
        self.data.ymax = ymax

    def on_set_focus(self, event):
        """
        On Set Focus, update guimanger and menu
        """
        if self._manager is not None:
            wx.PostEvent(self._manager.parent, PanelOnFocusEvent(panel=self))
            self.on_tap_focus()

    def on_tap_focus(self):
        """
        Update menu1 on cliking the page tap
        """
        if self._manager.menu1 is not None:
            chain_menu = self._manager.menu1.FindItemById(
                                                   self._manager.id_reset_flag)
            chain_menu.Enable(self.batch_on)
            sim_menu = self._manager.menu1.FindItemById(self._manager.id_simfit)
            flag = self.data.is_data\
                            and (self.model is not None)
            sim_menu.Enable(not self.batch_on and flag)
            batch_menu = \
                    self._manager.menu1.FindItemById(self._manager.id_batchfit)
            batch_menu.Enable(self.batch_on and flag)

    def onContextMenu(self, event):
        """
        Retrieve the state selected state
        """
        pos = event.GetPosition()
        pos = self.ScreenToClient(pos)
        self.PopupMenu(self.popUpMenu, pos)

    def onUndo(self, event):
        """
        Cancel the previous action
        """
        event = PreviousStateEvent(page=self)
        wx.PostEvent(self.parent, event)

    def onRedo(self, event):
        """
        Restore the previous action cancelled
        """
        event = NextStateEvent(page=self)
        wx.PostEvent(self.parent, event)

    def define_page_structure(self):
        """
        Create empty sizer for a panel
        """
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.sizer0 = wx.BoxSizer(wx.VERTICAL)
        self.sizer1 = wx.BoxSizer(wx.VERTICAL)
        self.sizer2 = wx.BoxSizer(wx.VERTICAL)
        self.sizer3 = wx.BoxSizer(wx.VERTICAL)
        self.sizer4 = wx.BoxSizer(wx.VERTICAL)
        self.sizer5 = wx.BoxSizer(wx.VERTICAL)
        self.sizer6 = wx.BoxSizer(wx.VERTICAL)

        self.sizer0.SetMinSize((PANEL_WIDTH, -1))
        self.sizer1.SetMinSize((PANEL_WIDTH, -1))
        self.sizer2.SetMinSize((PANEL_WIDTH, -1))
        self.sizer3.SetMinSize((PANEL_WIDTH, -1))
        self.sizer4.SetMinSize((PANEL_WIDTH, -1))
        self.sizer5.SetMinSize((PANEL_WIDTH, -1))
        self.sizer6.SetMinSize((PANEL_WIDTH, -1))

        self.vbox.Add(self.sizer0)
        self.vbox.Add(self.sizer1)
        self.vbox.Add(self.sizer2)
        self.vbox.Add(self.sizer3)
        self.vbox.Add(self.sizer4)
        self.vbox.Add(self.sizer5)
        self.vbox.Add(self.sizer6)

    def set_layout(self):
        """
        layout
        """
        self.vbox.Layout()
        self.vbox.Fit(self)
        self.SetSizer(self.vbox)
        self.Centre()

    def set_owner(self, owner):
        """
        set owner of fitpage

        :param owner: the class responsible of plotting

        """
        self.event_owner = owner
        self.state.event_owner = owner

    def get_state(self):
        """
        return the current page state
        """
        return self.state

    def get_data(self):
        """
        return the current data
        """
        return self.data

    def get_data_list(self):
        """
        return the current data
        """
        return self.data_list

    def set_manager(self, manager):
        """
        set panel manager

        :param manager: instance of plugin fitting

        """
        self._manager = manager
        self.state.manager = manager

    def populate_box(self, model_list_box):
        """
        Store list of model

        :param model_list_box: dictionary containing categorized models
        """
        self.model_list_box = model_list_box
        self.initialize_combox()

    def set_model_dictionary(self, model_dictionary):
        """
        Store a dictionary linking model name -> model object

        :param model_dictionary: dictionary containing all models
        """
        self.model_dictionary = model_dictionary

    def initialize_combox(self):
        """
        put default value in the combo box
        """
        if self.model_list_box:
            self._populate_box(self.structurebox,
                               self.model_list_box["Structure Factors"])
            self.structurebox.Insert("None", 0, None)
            self.structurebox.SetSelection(0)
            self.structurebox.Hide()
            self.text2.Hide()
            self.structurebox.Disable()
            self.text2.Disable()

    def set_dispers_sizer(self):
        """
        fill sizer containing dispersity info
        """
        # print "==== entering set_dispers_sizer ==="
        self.sizer4.Clear(True)
        name = "Polydispersity and Orientational Distribution"
        box_description = wx.StaticBox(self, wx.ID_ANY, name)
        box_description.SetForegroundColour(wx.BLUE)
        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
        # ----------------------------------------------------
        self.disable_disp = wx.RadioButton(self, wx.ID_ANY, 'Off', (10, 10),
                                           style=wx.RB_GROUP)
        self.enable_disp = wx.RadioButton(self, wx.ID_ANY, 'On', (10, 30))
        # best size for MAC and PC
        if ON_MAC:
            size_q = (30, 20)
        else:
            size_q = (20, 15)
        self.disp_help_bt = wx.Button(self, self.ID_DISPERSER_HELP, '?',
                                      style=wx.BU_EXACTFIT,
                                      size=size_q)
        self.disp_help_bt.Bind(wx.EVT_BUTTON, self.on_pd_help_clicked,
                               id=self.disp_help_bt.GetId())
        self.disp_help_bt.SetToolTipString("Help for polydispersion.")

        self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
                  id=self.disable_disp.GetId())
        self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
                  id=self.enable_disp.GetId())
        # MAC needs SetValue
        self.disable_disp.SetValue(True)
        sizer_dispersion = wx.BoxSizer(wx.HORIZONTAL)
        sizer_dispersion.Add((20, 20))
        name = ""  # Polydispersity and \nOrientational Distribution "
        sizer_dispersion.Add(wx.StaticText(self, wx.ID_ANY, name))
        sizer_dispersion.Add(self.enable_disp)
        sizer_dispersion.Add((20, 20))
        sizer_dispersion.Add(self.disable_disp)
        sizer_dispersion.Add((25, 20))
        sizer_dispersion.Add(self.disp_help_bt)

        # fill a sizer for dispersion
        boxsizer1.Add(sizer_dispersion, 0,
                      wx.TOP|wx.BOTTOM|wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE,
                      border=5)
        self.sizer4_4 = wx.GridBagSizer(6, 5)

        boxsizer1.Add(self.sizer4_4)
        # -----------------------------------------------------
        self.sizer4.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
        self.sizer4_4.Layout()
        self.sizer4.Layout()
        self.Layout()

        self.Refresh()
        # saving the state of enable dispersity button
        self.state.enable_disp = self.enable_disp.GetValue()
        self.state.disable_disp = self.disable_disp.GetValue()
        self.SetupScrolling()

    def onResetModel(self, event):
        """
        Reset model state
        """
        menu = event.GetEventObject()
        # post help message for the selected model
        msg = menu.GetHelpString(event.GetId())
        msg += " reloaded"
        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
        self.Show(False)
        name = menu.GetLabel(event.GetId())
        self._on_select_model_helper()
        if self.model is not None:
            self.m_name = self.model.name
        if name in list(self.saved_states.keys()):
            previous_state = self.saved_states[name]
            # reset state of checkbox,textcrtl  and  regular parameters value

            self.reset_page(previous_state)
        self.state.m_name = self.m_name
        self.Show(True)

    def on_preview(self, event):
        """
        Report the current fit results
        """
        # Get plot image from plotpanel
        images, canvases = self.get_images()
        imgRAM, images, refs = self._build_plots_for_report(images, canvases)

        # get the strings for report
        report_str, text_str = self.state.report(fig_urls=refs)

        # Show the dialog
        report_list = [report_str, text_str, images]
        dialog = ReportDialog(report_list, None, wx.ID_ANY, "")
        dialog.Show()

    def _build_plots_for_report(self, figs, canvases):
        """
        Build image state that wx.html understand
        by plotting, putting it into wx.FileSystem image object
        """
        images = []
        refs = []

        # For no figures in the list, prepare empty plot
        if figs is None or len(figs) == 0:
            figs = [None]

        # Loop over the list of figures
        # use wx.MemoryFSHandler
        imgRAM = wx.MemoryFSHandler()
        for fig in figs:
            if fig is not None:
                ind = figs.index(fig)
                canvas = canvases[ind]

            # store the image in wx.FileSystem Object
            wx.FileSystem.AddHandler(wx.MemoryFSHandler())

            # index of the fig
            ind = figs.index(fig)

            # AddFile, image can be retrieved with 'memory:filename'
            name = 'img_fit%s.png' % ind
            refs.append('memory:' + name)
            imgRAM.AddFile(name, canvas.bitmap, wx.BITMAP_TYPE_PNG)

            # append figs
            images.append(fig)

        return imgRAM, images, refs


    def on_save(self, event):
        """
        Save the current state into file
        """
        self.save_current_state()
        new_state = self.state.clone()
        # Ask the user the location of the file to write to.
        path = None
        if self.parent is not None:
            self._default_save_location = \
                        self._manager.parent._default_save_location
        dlg = wx.FileDialog(self, "Choose a file", self._default_save_location,
                            self.window_caption, "*.fitv", wx.SAVE)

        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self._default_save_location = os.path.dirname(path)
            self._manager.parent._default_save_location = \
                self._default_save_location
        else:
            return None
        # MAC always needs the extension for saving
        extens = ".fitv"
        # Make sure the ext included in the file name
        fName = os.path.splitext(path)[0] + extens
        # the manager write the state into file
        self._manager.save_fit_state(filepath=fName, fitstate=new_state)
        return new_state

    def on_copy(self, event):
        """
        Copy Parameter values to the clipboad
        """
        if event is not None:
            event.Skip()
        # It seems MAC needs wxCallAfter
        if event.GetId() == GUIFRAME_ID.COPYEX_ID:
            print("copy excel")
            wx.CallAfter(self.get_copy_excel)
        elif event.GetId() == GUIFRAME_ID.COPYLAT_ID:
            print("copy latex")
            wx.CallAfter(self.get_copy_latex)
        else:
            wx.CallAfter(self.get_copy)

    def on_paste(self, event):
        """
        Paste Parameter values to the panel if possible
        """
        # if event is not None:
        #    event.Skip()
        # It seems MAC needs wxCallAfter for the setvalues
        # for multiple textctrl items, otherwise it tends to crash once a while
        wx.CallAfter(self.get_paste)
        # messages depending on the flag
        # self._copy_info(True)

    def _copy_info(self, flag):
        """
        Send event depending on flag

        : Param flag: flag that distinguishes the event
        """
        # messages depending on the flag
        if flag is None:
            msg = " Parameter values are copied to the clipboard..."
            infor = 'warning'
        elif flag:
            msg = " Parameter values are pasted from the clipboard..."
            infor = "warning"
        else:
            msg = "Error occurred: "
            msg += "No valid parameter values to paste from the clipboard..."
            infor = "warning"
        # inform msg to wx
        wx.PostEvent(self._manager.parent,
                     StatusEvent(status=msg, info=infor))

    def _get_time_stamp(self):
        """
        return time and date stings
        """
        # date and time
        year, month, day, hour, minute, second, _, _, _ = time.localtime()
        current_time = str(hour) + ":" + str(minute) + ":" + str(second)
        current_date = str(month) + "/" + str(day) + "/" + str(year)
        return current_time, current_date

    def on_bookmark(self, event):
        """
        save history of the data and model
        """
        if self.model is None:
            msg = "Can not bookmark; Please select Data and Model first..."
            wx.MessageBox(msg, 'Info')
            return
        self.save_current_state()
        new_state = self.state.clone()
        # Add model state on context menu
        self.number_saved_state += 1
        current_time, current_date = self._get_time_stamp()
        # name= self.model.name+"[%g]"%self.number_saved_state
        name = "Fitting: %g]" % self.number_saved_state
        name += self.model.__class__.__name__
        name += "bookmarked at %s on %s" % (current_time, current_date)
        self.saved_states[name] = new_state

        # Add item in the context menu
        msg = "Model saved at %s on %s" % (current_time, current_date)
        # post help message for the selected model
        msg += " Saved! right click on this page to retrieve this model"
        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))

        self.popUpMenu.Append(self.ID_BOOKMARK, name, str(msg))
        wx.EVT_MENU(self, self.ID_BOOKMARK, self.onResetModel)
        wx.PostEvent(self._manager.parent,
                     AppendBookmarkEvent(title=name,
                                         hint=str(msg),
                                         handler=self._back_to_bookmark))

    def _back_to_bookmark(self, event):
        """
        Back to bookmark
        """
        self._manager.on_perspective(event)
        self.onResetModel(event)
        self._draw_model()

    def onSetFocus(self, evt):
        """
        highlight the current textcrtl and hide the error text control shown
        after fitting
        """
        return

    def read_file(self, path):
        """
        Read two columns file

        :param path: the path to the file to read

        """
        try:
            if path is None:
                status = " Selected Distribution was not loaded: %s" % path
                wx.PostEvent(self._manager.parent,
                             StatusEvent(status=status))
                return None, None
            input_f = open(path, 'r')
            buff = input_f.read()
            lines = buff.split('\n')
            input_f.close()
            angles = []
            weights = []
            for line in lines:
                toks = line.split()
                try:
                    angle = float(toks[0])
                    weight = float(toks[1])
                    angles.append(angle)
                    weights.append(weight)
                except Exception:
                    # Skip non-data lines
                    logger.error(traceback.format_exc())
            return np.array(angles), np.array(weights)
        except:
            raise

    def createMemento(self):
        """
        return the current state of the page
        """
        return self.state.clone()

    def save_current_state(self):
        """
        Store current state
        """
        # save model option
        if self.model is not None:
            self.disp_list = self.model.getDispParamList()
            self.state.disp_list = copy.deepcopy(self.disp_list)
            self.state.model = self.model.clone()

            # model combobox: complex code because of mac's silent error
            if self.structurebox is not None:
                if self.structurebox.IsShown():
                    self.state.structurecombobox = 'None'
                    s_select = self.structurebox.GetSelection()
                    if s_select > 0:
                        self.state.structurecombobox = \
                            self.structurebox.GetString(s_select)
            if self.formfactorbox is not None:
                f_select = self.formfactorbox.GetSelection()
                if f_select > 0:
                    self.state.formfactorcombobox = \
                        self.formfactorbox.GetString(f_select)
        if self.categorybox is not None:
            cb_select = self.categorybox.GetSelection()
            if cb_select > 0:
                self.state.categorycombobox = \
                    self.categorybox.GetString(cb_select)

        self.state.enable2D = copy.deepcopy(self.enable2D)
        self.state.values = copy.deepcopy(self.values)
        self.state.weights = copy.deepcopy(self.weights)
        # save data
        self.state.data = copy.deepcopy(self.data)
        self.state.qmax_x = self.qmax_x
        self.state.qmin_x = self.qmin_x
        self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
        self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
        self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
        self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
        self.state.dq_l = self.dq_l
        self.state.dq_r = self.dq_r
        if hasattr(self, "enable_disp"):
            self.state.enable_disp = self.enable_disp.GetValue()
            self.state.disable_disp = self.disable_disp.GetValue()

        if hasattr(self, "enable_smearer"):
            self.state.enable_smearer = \
                                copy.deepcopy(self.enable_smearer.GetValue())
            self.state.disable_smearer = \
                                copy.deepcopy(self.disable_smearer.GetValue())

        self.state.pinhole_smearer = \
                                copy.deepcopy(self.pinhole_smearer.GetValue())
        self.state.dx_percent = copy.deepcopy(self.dx_percent)
        self.state.dxl = copy.deepcopy(self.dxl)
        self.state.dxw = copy.deepcopy(self.dxw)
        self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())

        if len(self._disp_obj_dict) > 0:
            for k, v in self._disp_obj_dict.items():
                self.state.disp_obj_dict[k] = v.type

            self.state.values = copy.deepcopy(self.values)
            self.state.weights = copy.deepcopy(self.weights)
        # save plotting range
        self._save_plotting_range()

        self.state.orientation_params = []
        self.state.orientation_params_disp = []
        self.state.parameters = []
        self.state.fittable_param = []
        self.state.fixed_param = []
        self.state.str_parameters = []

        # save checkbutton state and txtcrtl values
        self._copy_parameters_state(self.str_parameters,
                                    self.state.str_parameters)
        self._copy_parameters_state(self.orientation_params,
                                    self.state.orientation_params)
        self._copy_parameters_state(self.orientation_params_disp,
                                    self.state.orientation_params_disp)

        self._copy_parameters_state(self.parameters, self.state.parameters)
        self._copy_parameters_state(self.fittable_param,
                                    self.state.fittable_param)
        self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
        # save chisqr
        self.state.tcChi = self.tcChi.GetValue()

    def save_current_state_fit(self):
        """
        Store current state for fit_page
        """
        # save model option
        if self.model is not None:
            self.disp_list = self.model.getDispParamList()
            self.state.disp_list = copy.deepcopy(self.disp_list)
            self.state.model = self.model.clone()

        self.state.enable2D = copy.deepcopy(self.enable2D)
        self.state.values = copy.deepcopy(self.values)
        self.state.weights = copy.deepcopy(self.weights)
        # save data
        self.state.data = copy.deepcopy(self.data)

        if hasattr(self, "enable_disp"):
            self.state.enable_disp = self.enable_disp.GetValue()
            self.state.disable_disp = self.disable_disp.GetValue()

        if hasattr(self, "enable_smearer"):
            self.state.enable_smearer = \
                                copy.deepcopy(self.enable_smearer.GetValue())
            self.state.disable_smearer = \
                                copy.deepcopy(self.disable_smearer.GetValue())

        self.state.pinhole_smearer = \
                                copy.deepcopy(self.pinhole_smearer.GetValue())
        self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())
        self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
        self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
        self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
        self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
        if hasattr(self, "disp_box") and self.disp_box is not None:
            self.state.disp_box = self.disp_box.GetCurrentSelection()

            if len(self.disp_cb_dict) > 0:
                for k, v in self.disp_cb_dict.items():
                    if v is None:
                        self.state.disp_cb_dict[k] = v
                    else:
                        try:
                            self.state.disp_cb_dict[k] = v.GetValue()
                        except Exception:
                            self.state.disp_cb_dict[k] = None
            if len(self._disp_obj_dict) > 0:
                for k, v in self._disp_obj_dict.items():
                    self.state.disp_obj_dict[k] = v.type

            self.state.values = copy.deepcopy(self.values)
            self.state.weights = copy.deepcopy(self.weights)

        # save plotting range
        self._save_plotting_range()

        # save checkbutton state and txtcrtl values
        self._copy_parameters_state(self.orientation_params,
                                    self.state.orientation_params)
        self._copy_parameters_state(self.orientation_params_disp,
                                    self.state.orientation_params_disp)
        self._copy_parameters_state(self.parameters, self.state.parameters)
        self._copy_parameters_state(self.fittable_param,
                                    self.state.fittable_param)
        self._copy_parameters_state(self.fixed_param, self.state.fixed_param)

    def check_invalid_panel(self):
        """
        check if the user can already perform some action with this panel
        """
        if self.data is None:
            self.disable_smearer.SetValue(True)
            self.disable_disp.SetValue(True)
            msg = "Please load Data and select Model to start..."
            wx.MessageBox(msg, 'Info')
            return True

    def set_model_state(self, state):
        """
        reset page given a model state
        """
        self.disp_cb_dict = state.disp_cb_dict
        self.disp_list = state.disp_list

        # fill model combobox
        self._show_combox_helper()
        # select the current model
        try:
            # to support older version
            category_pos = int(state.categorycombobox)
        except Exception:
            category_pos = 0
            for ind_cat in range(self.categorybox.GetCount()):
                if self.categorycombobox.GetString(ind_cat) == \
                                        state.categorycombobox:
                    category_pos = int(ind_cat)
                    break

        self.categorybox.Select(category_pos)
        try:
            # to support older version
            formfactor_pos = int(state.formfactorcombobox)
        except Exception:
            formfactor_pos = 0
            for ind_form in range(self.formfactorbox.GetCount()):
                if self.formfactorbox.GetString(ind_form) == \
                                        state.formfactorcombobox:
                    formfactor_pos = int(ind_form)
                    break

        self.formfactorbox.Select(formfactor_pos)

        try:
            # to support older version
            structfactor_pos = int(state.structurecombobox)
        except Exception:
            structfactor_pos = 0
            for ind_struct in range(self.structurebox.GetCount()):
                if self.structurebox.GetString(ind_struct) == \
                                        state.structurecombobox:
                    structfactor_pos = int(ind_struct)
                    break

        self.structurebox.SetSelection(structfactor_pos)

        if state.multi_factor is not None:
            self.multifactorbox.SetSelection(state.multi_factor)

        # reset state of checkbox,textcrtl  and  regular parameters value
        self._reset_parameters_state(self.orientation_params_disp,
                                     state.orientation_params_disp)
        self._reset_parameters_state(self.orientation_params,
                                     state.orientation_params)
        self._reset_parameters_state(self.str_parameters,
                                     state.str_parameters)
        self._reset_parameters_state(self.parameters, state.parameters)
        # display dispersion info layer
        self.enable_disp.SetValue(state.enable_disp)
        self.disable_disp.SetValue(state.disable_disp)

        if hasattr(self, "disp_box") and self.disp_box is not None:
            self.disp_box.SetSelection(state.disp_box)
            n = self.disp_box.GetCurrentSelection()
            dispersity = self.disp_box.GetClientData(n)
            name = dispersity.__name__
            self._set_dipers_Param(event=None)

            if name == "ArrayDispersion":

                for item in list(self.disp_cb_dict.keys()):

                    if hasattr(self.disp_cb_dict[item], "SetValue"):
                        self.disp_cb_dict[item].SetValue(
                                                    state.disp_cb_dict[item])
                        # Create the dispersion objects
                        disp_model = POLYDISPERSITY_MODELS['array']()
                        if hasattr(state, "values") and \
                                 self.disp_cb_dict[item].GetValue():
                            if len(state.values) > 0:
                                self.values = state.values
                                self.weights = state.weights
                                disp_model.set_weights(self.values,
                                                       state.weights)
                            else:
                                self._reset_dispersity()

                        self._disp_obj_dict[item] = disp_model
                        # Set the new model as the dispersion object
                        # for the selected parameter
                        self.model.set_dispersion(item, disp_model)

                        self.model._persistency_dict[item] = \
                                                [state.values, state.weights]

            else:
                keys = self.model.getParamList()
                for item in keys:
                    if item in self.disp_list and \
                            item not in self.model.details:
                        self.model.details[item] = ["", None, None]
                self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
                self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
        # smearing info  restore
        if hasattr(self, "enable_smearer"):
            # set smearing value whether or not the data
            # contain the smearing info
            self.enable_smearer.SetValue(state.enable_smearer)
            self.disable_smearer.SetValue(state.disable_smearer)
            self.onSmear(event=None)
        self.pinhole_smearer.SetValue(state.pinhole_smearer)
        self.slit_smearer.SetValue(state.slit_smearer)

        self.dI_noweight.SetValue(state.dI_noweight)
        self.dI_didata.SetValue(state.dI_didata)
        self.dI_sqrdata.SetValue(state.dI_sqrdata)
        self.dI_idata.SetValue(state.dI_idata)

        # we have two more options for smearing
        if self.pinhole_smearer.GetValue():
            self.onPinholeSmear(event=None)
        elif self.slit_smearer.GetValue():
            self.onSlitSmear(event=None)

        # reset state of checkbox,textcrtl  and dispersity parameters value
        self._reset_parameters_state(self.fittable_param, state.fittable_param)
        self._reset_parameters_state(self.fixed_param, state.fixed_param)

        # draw the model with previous parameters value
        self._onparamEnter_helper()
        self.select_param(event=None)
        # Save state_fit
        self.save_current_state_fit()
        self._lay_out()
        self.Refresh()

    def get_cat_combo_box_pos(self, state):
        """
        Iterate through the categories to find the structurefactor
        :return: combo_box_position
        """
        for key, value in self.master_category_dict.items():
            formfactor = state.formfactorcombobox.split(":")
            if isinstance(formfactor, list):
                formfactor = formfactor[0]
            for list_item in value:
                if formfactor in list_item:
                    return self.categorybox.Items.index(key)
        return 0

    def reset_page_helper(self, state):
        """
        Use page_state and change the state of existing page

        :precondition: the page is already drawn or created

        :postcondition: the state of the underlying data changes as well as the
            state of the graphic interface
        """
        if state is None:
            return
        # set data, etc. from the state
        # reset page between theory and fitting from bookmarking
        data = state.data

        if data is None:
            data_min = state.qmin
            data_max = state.qmax
            self.qmin_x = data_min
            self.qmax_x = data_max
            self.qmin.SetValue(str(data_min))
            self.qmax.SetValue(str(data_max))

            self.state.data = data
            self.state.qmin = self.qmin_x
            self.state.qmax = self.qmax_x
        else:
            self.set_data(data)

        self.enable2D = state.enable2D
        try:
            self.magnetic_on = state.magnetic_on
        except:
            # Backward compatibility (for older state files)
            self.magnetic_on = False

        self.disp_cb_dict = state.disp_cb_dict
        self.disp_list = state.disp_list

        # fill model combobox
        self._show_combox_helper()
        # select the current model
        state._convert_to_sasmodels()
        state.categorycombobox = str(state.categorycombobox)
        if state.categorycombobox in self.categorybox.Items:
            category_pos = self.categorybox.Items.index(
                state.categorycombobox)
        else:
            # Look in master list for model name (model.lower)
            category_pos = self.get_cat_combo_box_pos(state)

        self.categorybox.Select(category_pos)
        self._show_combox(None)
        if (self.categorybox.GetValue() == CUSTOM_MODEL
                and PLUGIN_NAME_BASE not in state.formfactorcombobox):
            state.formfactorcombobox = \
                PLUGIN_NAME_BASE + state.formfactorcombobox
        formfactor_pos = 0
        for ind_form in range(self.formfactorbox.GetCount()):
            if (self.formfactorbox.GetString(ind_form)
                    == state.formfactorcombobox):
                formfactor_pos = int(ind_form)
                break

        self.formfactorbox.Select(formfactor_pos)

        structfactor_pos = 0
        if state.structurecombobox is not None:
            state.structurecombobox = str(state.structurecombobox)
            for ind_struct in range(self.structurebox.GetCount()):
                if (self.structurebox.GetString(ind_struct)
                        == state.structurecombobox):
                    structfactor_pos = int(ind_struct)
                    break

        self.structurebox.SetSelection(structfactor_pos)

        if state.multi_factor is not None:
            self.multifactorbox.SetSelection(state.multi_factor)

        # draw the panel according to the new model parameter
        self._on_select_model(event=None)

        # take care of 2D button
        if data is None and self.model_view.IsEnabled():
            if self.enable2D:
                self.model_view.SetLabel("2D Mode")
            else:
                self.model_view.SetLabel("1D Mode")

        # reset state of checkbox,textcrtl  and  regular parameters value
        self._reset_parameters_state(self.orientation_params_disp,
                                     state.orientation_params_disp)
        self._reset_parameters_state(self.orientation_params,
                                     state.orientation_params)
        self._reset_parameters_state(self.str_parameters,
                                     state.str_parameters)
        self._reset_parameters_state(self.parameters, state.parameters)
        # display dispersion info layer
        self.enable_disp.SetValue(state.enable_disp)
        self.disable_disp.SetValue(state.disable_disp)
        # If the polydispersion is ON
        if state.enable_disp:
            # reset dispersion according the state
            self._set_dipers_Param(event=None)
            self._reset_page_disp_helper(state)
        # plotting range restore
        self._reset_plotting_range(state)
        # smearing info  restore
        if hasattr(self, "enable_smearer"):
            # set smearing value whether or not the data
            # contain the smearing info
            self.enable_smearer.SetValue(state.enable_smearer)
            self.disable_smearer.SetValue(state.disable_smearer)
            self.onSmear(event=None)
        self.pinhole_smearer.SetValue(state.pinhole_smearer)
        self.slit_smearer.SetValue(state.slit_smearer)
        try:
            self.dI_noweight.SetValue(state.dI_noweight)
            self.dI_didata.SetValue(state.dI_didata)
            self.dI_sqrdata.SetValue(state.dI_sqrdata)
            self.dI_idata.SetValue(state.dI_idata)
        except Exception:
            # to support older state file formats
            self.dI_noweight.SetValue(False)
            self.dI_didata.SetValue(True)
            self.dI_sqrdata.SetValue(False)
            self.dI_idata.SetValue(False)

        # we have two more options for smearing
        if self.pinhole_smearer.GetValue():
            self.dx_percent = state.dx_percent
            if self.dx_percent is not None:
                if state.dx_old:
                    self.dx_percent = 100 * (self.dx_percent / self.data.x[0])
                self.smear_pinhole_percent.SetValue("%.2f" % self.dx_percent)
            self.onPinholeSmear(event=None)
        elif self.slit_smearer.GetValue():
            self.dxl = state.dxl
            self.dxw = state.dxw
            if self.dxl is not None:
                self.smear_slit_height.SetValue(str(self.dxl))
            if self.dxw is not None:
                self.smear_slit_width.SetValue(str(self.dxw))
            else:
                self.smear_slit_width.SetValue('')
            self.onSlitSmear(event=None)

        # reset state of checkbox,textcrtl  and dispersity parameters value
        self._reset_parameters_state(self.fittable_param, state.fittable_param)
        self._reset_parameters_state(self.fixed_param, state.fixed_param)

        # draw the model with previous parameters value
        self._onparamEnter_helper()
        # reset the value of chisqr when not consistent with the value computed
        self.tcChi.SetValue(str(self.state.tcChi))
        # reset context menu items
        self._reset_context_menu()

        # set the value of the current state to the state given as parameter
        self.state = state.clone()
        self.state.m_name = self.m_name

    def _reset_page_disp_helper(self, state):
        """
        Help to rest page for dispersions
        """
        keys = self.model.getParamList()
        for item in keys:
            if item in self.disp_list and \
                            item not in self.model.details:
                self.model.details[item] = ["", None, None]
        # for k,v in self.state.disp_cb_dict.iteritems():
        self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
        self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
        self.values = copy.deepcopy(state.values)
        self.weights = copy.deepcopy(state.weights)

        for key, disp_type in state.disp_obj_dict.items():
            # disp_model = disp
            disp_model = POLYDISPERSITY_MODELS[disp_type]()
            self._disp_obj_dict[key] = disp_model
            param_name = key.split('.')[0]
            # Try to set dispersion only when available
            # for eg., pass the orient. angles for 1D Cal
            try:
                self.model.set_dispersion(param_name, disp_model)
                self.model._persistency_dict[key] = \
                    [state.values, state.weights]
            except Exception:
                logger.error(traceback.format_exc())
            selection = self._find_polyfunc_selection(disp_model)
            for list in self.fittable_param:
                if list[1] == key and list[7] is not None:
                    list[7].SetSelection(selection)
                    # For the array disp_model, set the values and weights
                    if selection == 1:
                        disp_model.set_weights(self.values[key],
                                               self.weights[key])
                        try:
                            # Diables all fittable params for array
                            list[0].SetValue(False)
                            list[0].Disable()
                            list[2].Disable()
                            list[5].Disable()
                            list[6].Disable()
                        except Exception:
                            logger.error(traceback.format_exc())
            # For array, disable all fixed params
            if selection == 1:
                for item in self.fixed_param:
                    if item[1].split(".")[0] == key.split(".")[0]:
                        # try it and pass it for the orientation for 1D
                        try:
                            item[2].Disable()
                        except Exception:
                            logger.error(traceback.format_exc())

    def _selectDlg(self):
        """
        open a dialog file to select the customized polydispersity function
        """
        if self.parent is not None:
            self._default_save_location = \
                        self._manager.parent.get_save_location()
        dlg = wx.FileDialog(self, "Choose a weight file",
                            self._default_save_location, "",
                            "*.*", wx.OPEN)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
        dlg.Destroy()
        return path

    def _reset_context_menu(self):
        """
        reset the context menu
        """
        ids = iter(self._id_pool)  # Reusing ids for context menu
        for name, _ in self.state.saved_states.items():
            self.number_saved_state += 1
            # Add item in the context menu
            wx_id = next(ids)
            msg = 'Save model and state %g' % self.number_saved_state
            self.popUpMenu.Append(wx_id, name, msg)
            wx.EVT_MENU(self, wx_id, self.onResetModel)

    def _reset_plotting_range(self, state):
        """
        Reset the plotting range to a given state
        """
        self.qmin.SetValue(str(state.qmin))
        self.qmax.SetValue(str(state.qmax))

    def _save_typeOfmodel(self):
        """
        save radiobutton containing the type model that can be selected
        """
        # self.state.shape_rbutton = self.shape_rbutton.GetValue()
        # self.state.shape_indep_rbutton = self.shape_indep_rbutton.GetValue()
        # self.state.struct_rbutton = self.struct_rbutton.GetValue()
        # self.state.plugin_rbutton = self.plugin_rbutton.GetValue()
        self.state.structurecombobox = self.structurebox.GetValue()
        self.state.formfactorcombobox = self.formfactorbox.GetValue()
        self.state.categorycombobox = self.categorybox.GetValue()

        # post state to fit panel
        event = PageInfoEvent(page=self)
        wx.PostEvent(self.parent, event)

    def _save_plotting_range(self):
        """
        save the state of plotting range
        """
        self.state.qmin = self.qmin_x
        self.state.qmax = self.qmax_x
        self.state.npts = self.npts_x

    def _onparamEnter_helper(self, is_modified=False):
        """
        check if values entered by the user are changed and valid to replot
        model
        """
        # Flag to register when a parameter has changed.
        # is_modified = False
        self.fitrange = True
        is_2Ddata = False
        # self._undo.Enable(True)
        # check if 2d data
        if self.data.__class__.__name__ == "Data2D":
            is_2Ddata = True
        if self.model is not None:
            # Either we get a is_modified = True passed in because
            # _update_paramv_on_fit() has been called already or
            # we need to check here ourselves.
            if not is_modified:
                is_modified = (self._check_value_enter(self.fittable_param)
                               or self._check_value_enter(self.fixed_param)
                               or self._check_value_enter(self.parameters))

            # Here we should check whether the boundaries have been modified.
            # If qmin and qmax have been modified, update qmin and qmax and
            # set the is_modified flag to True
            if self._validate_qrange(self.qmin, self.qmax):
                tempmin = float(self.qmin.GetValue())
                if tempmin != self.qmin_x:
                    self.qmin_x = tempmin
                    is_modified = True
                tempmax = float(self.qmax.GetValue())
                if tempmax != self.qmax_x:
                    self.qmax_x = tempmax
                    is_modified = True
                if is_2Ddata:
                    is_modified = self._validate_Npts()
                else:
                    is_modified = self._validate_Npts_1D()
            else:
                self.fitrange = False

            # if any value is modify draw model with new value
            if not self.fitrange:
                # self.btFit.Disable()
                if is_2Ddata:
                    self.btEditMask.Disable()
            else:
                if is_2Ddata and self.data.is_data and not self.batch_on:
                    self.btEditMask.Enable(True)
            if is_modified and self.fitrange:
                # Theory case: need to get npts value to draw
                self.npts_x = float(self.Npts_total.GetValue())
                self.Npts_fit.SetValue(str(self.Npts_total.GetValue()))
                self._save_plotting_range()
                self.create_default_data()
                self.state_change = True
                self._draw_model()
                self.Refresh()

        # logger.info("is_modified flag set to %g",is_modified)
        return is_modified

    def _update_paramv_on_fit(self):
        """
        make sure that update param values just before the fitting
        """
        # flag for qmin qmax check values
        flag = True
        self.fitrange = True

        # wx.PostEvent(self._manager.parent, StatusEvent(status=" \
        # updating ... ",type="update"))

        # So make sure that update param values on_Fit.
        # self._undo.Enable(True)
        if self.model is not None:
            if self.Npts_total.GetValue() != self.Npts_fit.GetValue():
                if not self.data.is_data:
                    self._manager.page_finder[self.uid].set_fit_data(
                        data=[self.data])
            # Check the values
            is_modified = (self._check_value_enter(self.fittable_param)
                           or self._check_value_enter(self.fixed_param)
                           or self._check_value_enter(self.parameters))

            # If qmin and qmax have been modified, update qmin and qmax and
            # Here we should check whether the boundaries have been modified.
            # If qmin and qmax have been modified, update qmin and qmax and
            # set the is_modified flag to True
            self.fitrange = self._validate_qrange(self.qmin, self.qmax)
            if self.fitrange:
                tempmin = float(self.qmin.GetValue())
                if tempmin != self.qmin_x:
                    self.qmin_x = tempmin
                tempmax = float(self.qmax.GetValue())
                if tempmax != self.qmax_x:
                    self.qmax_x = tempmax
                if tempmax == tempmin:
                    flag = False
                temp_smearer = None
                if not self.disable_smearer.GetValue():
                    temp_smearer = self.current_smearer
                    if self.slit_smearer.GetValue():
                        flag = self.update_slit_smear()
                    elif self.pinhole_smearer.GetValue():
                        flag = self.update_pinhole_smear()
                    else:
                        enable_smearer = not self.disable_smearer.GetValue()
                        self._manager.set_smearer(smearer=temp_smearer,
                                                  uid=self.uid,
                                                  fid=self.data.id,
                                                  qmin=float(self.qmin_x),
                                                  qmax=float(self.qmax_x),
                                                  enable_smearer=enable_smearer,
                                                  draw=False)
                elif not self._is_2D():
                    enable_smearer = not self.disable_smearer.GetValue()
                    self._manager.set_smearer(smearer=temp_smearer,
                                              qmin=float(self.qmin_x),
                                              uid=self.uid,
                                              fid=self.data.id,
                                              qmax=float(self.qmax_x),
                                              enable_smearer=enable_smearer,
                                              draw=False)
                    if self.data is not None:
                        index_data = ((self.qmin_x <= self.data.x) &
                                      (self.data.x <= self.qmax_x))
                        val = str(len(self.data.x[index_data]))
                        self.Npts_fit.SetValue(val)
                    else:
                        # No data in the panel
                        try:
                            self.npts_x = float(self.Npts_total.GetValue())
                        except Exception:
                            flag = False
                            return flag
                    flag = True
                if self._is_2D():
                    # only 2D case set mask
                    flag = self._validate_Npts()
                    if not flag:
                        return flag
            else:
                flag = False
        else:
            flag = False

        # For invalid q range, disable the mask editor and fit button, vs.
        if not self.fitrange:
            if self._is_2D():
                self.btEditMask.Disable()
        else:
            if self._is_2D() and self.data.is_data and not self.batch_on:
                self.btEditMask.Enable(True)

        if not flag:
            msg = "Cannot Plot or Fit :Must select a "
            msg += " model or Fitting range is not valid!!!  "
            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))

        try:
            self.save_current_state()
        except Exception:
            logger.error(traceback.format_exc())

        return flag, is_modified

    def _reset_parameters_state(self, listtorestore, statelist):
        """
        Reset the parameters at the given state
        """
        if len(statelist) == 0 or len(listtorestore) == 0:
            return

        for item_page in listtorestore:
            for param in statelist:
                if param[1] == item_page[1]:
                    item_page_info = param
                    if (item_page_info[1] == "theta" or item_page_info[1] ==
                            "phi") and not self._is_2D():
                        break
                    # change the state of the check box for simple parameters
                    if item_page[0] is not None:
                        item_page[0].SetValue(item_page_info[0])
                    if item_page[2] is not None:
                        item_page[2].SetValue(item_page_info[2])
                        if item_page[2].__class__.__name__ == "ComboBox":
                            if item_page_info[2] in self.model.fun_list:
                                fun_val = self.model.fun_list[item_page_info[2]]
                                self.model.setParam(item_page_info[1], fun_val)
                    if item_page[3] is not None:
                        # show or hide text +/-
                        if item_page_info[2]:
                            item_page[3].Show(True)
                        else:
                            item_page[3].Hide()
                    if item_page[4] is not None:
                        # show of hide the text crtl for fitting error
                        if item_page_info[4][0]:
                            item_page[4].Show(True)
                            item_page[4].SetValue(str(item_page_info[4][1]))
                        else:
                            item_page[3].Hide()
                    if item_page[5] is not None:
                        # show of hide the text crtl for fitting error
                        item_page[5].Show(True)
                        item_page[5].SetValue(str(item_page_info[5][1]))
                    if item_page[6] is not None:
                        # show of hide the text crtl for fitting error
                        item_page[6].Show(True)
                        item_page[6].SetValue(str(item_page_info[6][1]))
                    break

    def _reset_strparam_state(self, listtorestore, statelist):
        """
        Reset the string parameters at the given state
        """
        if len(statelist) == 0:
            return

        listtorestore = copy.deepcopy(statelist)

        for item_page, item_page_info in zip(listtorestore, statelist):
            # change the state of the check box for simple parameters
            if item_page[0] is not None:
                item_page[0].SetValue(format_number(item_page_info[0], True))

            if item_page[2] is not None:
                param_name = item_page_info[1]
                value = item_page_info[2]
                selection = value
                if value in self.model.fun_list:
                    selection = self.model.fun_list[value]
                item_page[2].SetValue(selection)
                self.model.setParam(param_name, selection)

    def _copy_parameters_state(self, listtocopy, statelist):
        """
        copy the state of button

        :param listtocopy: the list of check button to copy
        :param statelist: list of state object to store the current state

        """
        if len(listtocopy) == 0:
            return

        for item in listtocopy:

            checkbox_state = None
            if item[0] is not None:
                checkbox_state = item[0].GetValue()
            parameter_name = item[1]
            parameter_value = None
            if item[2] is not None:
                parameter_value = item[2].GetValue()
            static_text = None
            if item[3] is not None:
                static_text = item[3].IsShown()
            error_value = None
            error_state = None
            if item[4] is not None:
                error_value = item[4].GetValue()
                error_state = item[4].IsShown()

            min_value = None
            min_state = None
            if item[5] is not None:
                min_value = item[5].GetValue()
                min_state = item[5].IsShown()

            max_value = None
            max_state = None
            if item[6] is not None:
                max_value = item[6].GetValue()
                max_state = item[6].IsShown()
            unit = None
            if item[7] is not None:
                unit = item[7].GetLabel()

            statelist.append([checkbox_state, parameter_name, parameter_value,
                              static_text, [error_state, error_value],
                              [min_state, min_value],
                              [max_state, max_value], unit])

    def _draw_model(self, update_chisqr=True, source='model'):
        """
        Method to draw or refresh a plotted model.
        The method will use the data member from the model page
        to build a call to the fitting perspective manager.

        :param chisqr: update chisqr value [bool]
        """
        self.threaded_draw_queue.put([copy.copy(update_chisqr), copy.copy(source)])

    def _threaded_draw_worker(self, threaded_draw_queue):
        while True:
            # sit and wait for the next task
            next_task = threaded_draw_queue.get()

            # sleep for 1/10th second in case some other tasks accumulate
            time.sleep(0.1)

            # skip all intermediate tasks
            while self.threaded_draw_queue.qsize() > 0:
                self.threaded_draw_queue.task_done()
                next_task = self.threaded_draw_queue.get()

            # and finally, do the task
            self._draw_model_after(*next_task)
            threaded_draw_queue.task_done()

    def _draw_model_after(self, update_chisqr=True, source='model'):
        """
        Method to draw or refresh a plotted model.
        The method will use the data member from the model page
        to build a call to the fitting perspective manager.

        :param chisqr: update chisqr value [bool]
        """
        # if self.check_invalid_panel():
        #    return
        if self.model is not None:
            temp_smear = None
            if hasattr(self, "enable_smearer"):
                if not self.disable_smearer.GetValue():
                    temp_smear = self.current_smearer
            # compute weight for the current data
            flag = self.get_weight_flag()
            weight = get_weight(data=self.data, is2d=self._is_2D(), flag=flag)
            toggle_mode_on = self.model_view.IsEnabled()
            is_2d = self._is_2D()

            self._manager.draw_model(self.model,
                                     data=self.data,
                                     smearer=temp_smear,
                                     qmin=float(self.qmin_x),
                                     qmax=float(self.qmax_x),
                                     page_id=self.uid,
                                     toggle_mode_on=toggle_mode_on,
                                     state=self.state,
                                     enable2D=is_2d,
                                     update_chisqr=update_chisqr,
                                     source='model',
                                     weight=weight)

    def _on_show_sld(self, event=None):
        """
        Plot SLD profile
        """
        # get profile data
        x, y = self.model.getProfile()

        from sas.sasgui.plottools import Data1D as pf_data1d
        from sas.sasgui.guiframe.local_perspectives.plotting.profile_dialog \
            import SLDPanel
        sld_data = pf_data1d(x, y)
        sld_data.name = 'SLD'
        sld_data.axes = self.sld_axes
        self.panel = SLDPanel(self, data=sld_data, axes=self.sld_axes,
                              id=wx.ID_ANY)
        self.panel.ShowModal()

    def _set_multfactor_combobox(self, multiplicity=10):
        """
        Set comboBox for multitfactor of CoreMultiShellModel
        :param multiplicit: no. of multi-functionality
        """
        # build content of the combobox
        for idx in range(0, multiplicity):
            self.multifactorbox.Append(str(idx), int(idx))
        self._hide_multfactor_combobox()

    def _show_multfactor_combobox(self):
        """
        Show the comboBox of muitfactor of CoreMultiShellModel
        """
        if not self.mutifactor_text.IsShown():
            self.mutifactor_text.Show(True)
            self.mutifactor_text1.Show(True)
        if not self.multifactorbox.IsShown():
            self.multifactorbox.Show(True)

    def _hide_multfactor_combobox(self):
        """
        Hide the comboBox of muitfactor of CoreMultiShellModel
        """
        if self.mutifactor_text.IsShown():
            self.mutifactor_text.Hide()
            self.mutifactor_text1.Hide()
        if self.multifactorbox.IsShown():
            self.multifactorbox.Hide()

    def formfactor_combo_init(self):
        """
        First time calls _show_combox_helper
        """
        self._show_combox(None)

    def _show_combox_helper(self):
        """
        Fill panel's combo box according to the type of model selected
        """

        mod_cat = self.categorybox.GetStringSelection()
        self.structurebox.SetSelection(0)
        self.structurebox.Disable()
        self.formfactorbox.Clear()
        if mod_cat is None:
            return
        m_list = []
        try:
            if mod_cat == CUSTOM_MODEL:
                for model in self.model_list_box[mod_cat]:
                    m_list.append(self.model_dictionary[model.name])
            else:
                cat_dic = self.master_category_dict[mod_cat]
                for model, enabled in cat_dic:
                    if enabled:
                        m_list.append(self.model_dictionary[model])
        except Exception:
            msg = traceback.format_exc()
            wx.PostEvent(self._manager.parent,
                         StatusEvent(status=msg, info="error"))
        self._populate_box(self.formfactorbox, m_list)

    def _on_modify_cat(self, event=None):
        """
        Called when category manager is opened
        """
        self._manager.parent.on_category_panel(event)

    def _show_combox(self, event=None):
        """
        Show combox box associate with type of model selected
        """
        self.Show(False)
        self._show_combox_helper()
        self._on_select_model(event=None)
        self.Show(True)
        self._save_typeOfmodel()
        self.sizer4_4.Layout()
        self.sizer4.Layout()
        self.Layout()
        self.Refresh()

    def _populate_box(self, combobox, list):
        """
        fill combox box with dict item

        :param list: contains item to fill the combox
            item must model class
        """
        mlist = []
        for models in list:
            if models.name != "NoStructure":
                mlist.append((models.name, models))
        # Sort the models
        mlist_sorted = sorted(mlist)
        for item in mlist_sorted:
            combobox.Append(item[0], item[1])
        return 0

    def _onQrangeEnter(self, event):
        """
        Check validity of value enter in the Q range field

        """
        tcrtl = event.GetEventObject()
        # Clear msg if previously shown.
        msg = ""
        wx.PostEvent(self.parent, StatusEvent(status=msg))
        # Flag to register when a parameter has changed.
        if tcrtl.GetValue().lstrip().rstrip() != "":
            try:
                float(tcrtl.GetValue())
                tcrtl.SetBackgroundColour(wx.WHITE)
                # If qmin and qmax have been modified, update qmin and qmax
                if self._validate_qrange(self.qmin, self.qmax):
                    tempmin = float(self.qmin.GetValue())
                    if tempmin != self.qmin_x:
                        self.qmin_x = tempmin
                    tempmax = float(self.qmax.GetValue())
                    if tempmax != self.qmax_x:
                        self.qmax_x = tempmax
                else:
                    tcrtl.SetBackgroundColour("pink")
                    msg = "Model Error: wrong value entered: %s" % \
                          sys.exc_info()[1]
                    wx.PostEvent(self.parent, StatusEvent(status=msg))
                    return
            except Exception:
                tcrtl.SetBackgroundColour("pink")
                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
                wx.PostEvent(self.parent, StatusEvent(status=msg))
                return
            # Check if # of points for theory model are valid(>0).
            if self.npts is not None:
                if check_float(self.npts):
                    temp_npts = float(self.npts.GetValue())
                    if temp_npts != self.num_points:
                        self.num_points = temp_npts
                else:
                    msg = "Cannot plot: No points in Q range!!!  "
                    wx.PostEvent(self.parent, StatusEvent(status=msg))
        else:
            tcrtl.SetBackgroundColour("pink")
            msg = "Model Error: wrong value entered!!!"
            wx.PostEvent(self.parent, StatusEvent(status=msg))
        self.save_current_state()
        event = PageInfoEvent(page=self)
        wx.PostEvent(self.parent, event)
        self.state_change = False
        # Draw the model for a different range
        if not self.data.is_data:
            self.create_default_data()
        self._draw_model()

    def _theory_qrange_enter(self, event):
        """
        Check validity of value enter in the Q range field
        """

        tcrtl = event.GetEventObject()
        # Clear msg if previously shown.
        msg = ""
        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
        # Flag to register when a parameter has changed.
        is_modified = False
        if tcrtl.GetValue().lstrip().rstrip() != "":
            try:
                value = float(tcrtl.GetValue())
                tcrtl.SetBackgroundColour(wx.WHITE)

                # If qmin and qmax have been modified, update qmin and qmax
                if self._validate_qrange(self.theory_qmin, self.theory_qmax):
                    tempmin = float(self.theory_qmin.GetValue())
                    if tempmin != self.theory_qmin_x:
                        self.theory_qmin_x = tempmin
                    tempmax = float(self.theory_qmax.GetValue())
                    if tempmax != self.qmax_x:
                        self.theory_qmax_x = tempmax
                else:
                    tcrtl.SetBackgroundColour("pink")
                    msg = "Model Error: wrong value entered: %s" % \
                          sys.exc_info()[1]
                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                    return
            except Exception:
                tcrtl.SetBackgroundColour("pink")
                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                return
            # Check if # of points for theory model are valid(>0).
            if self.Npts_total.IsEditable():
                if check_float(self.Npts_total):
                    temp_npts = float(self.Npts_total.GetValue())
                    if temp_npts != self.num_points:
                        self.num_points = temp_npts
                        is_modified = True
                else:
                    msg = "Cannot Plot: No points in Q range!!!  "
                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
        else:
            tcrtl.SetBackgroundColour("pink")
            msg = "Model Error: wrong value entered!!!"
            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
        self.save_current_state()
        event = PageInfoEvent(page=self)
        wx.PostEvent(self.parent, event)
        self.state_change = False
        # Draw the model for a different range
        self.create_default_data()
        self._draw_model()

    def _on_select_model_helper(self):
        """
        call back for model selection
        """
        # reset dictionary containing reference to dispersion
        self._disp_obj_dict = {}
        self.disp_cb_dict = {}
        self.temp_multi_functional = False
        f_id = self.formfactorbox.GetCurrentSelection()
        # For MAC
        form_factor = None
        if f_id >= 0:
            form_factor = self.formfactorbox.GetClientData(f_id)

        if form_factor is None or \
            not hasattr(form_factor, 'is_form_factor') or \
                not form_factor.is_form_factor:
            self.structurebox.Hide()
            self.text2.Hide()
            self.structurebox.Disable()
            self.structurebox.SetSelection(0)
            self.text2.Disable()
        else:
            self.structurebox.Show()
            self.text2.Show()
            self.structurebox.Enable()
            self.text2.Enable()

        if form_factor is not None:
            # set multifactor for Mutifunctional models
            if form_factor.is_multiplicity_model:
                m_id = self.multifactorbox.GetCurrentSelection()
                multiplicity = form_factor.multiplicity_info[0]
                self.multifactorbox.Clear()
                self._set_multfactor_combobox(multiplicity)
                self._show_multfactor_combobox()
                # ToDo: this info should be called directly from the model
                text = form_factor.multiplicity_info[1]  # 'No. of Shells: '

                self.mutifactor_text.SetLabel(text)
                if m_id > multiplicity - 1:
                    # default value
                    m_id = 1

                self.multi_factor = self.multifactorbox.GetClientData(m_id)
                if self.multi_factor is None:
                    self.multi_factor = 0
                self.multifactorbox.SetSelection(m_id)
                # Check len of the text1 and max_multiplicity
                text = ''
                if form_factor.multiplicity_info[0] == \
                        len(form_factor.multiplicity_info[2]):
                    text = form_factor.multiplicity_info[2][self.multi_factor]
                self.mutifactor_text1.SetLabel(text)
                # Check if model has  get sld profile.
                if len(form_factor.multiplicity_info[3]) > 0:
                    self.sld_axes = form_factor.multiplicity_info[3]
                    self.show_sld_button.Show(True)
                else:
                    self.sld_axes = ""
            else:
                self._hide_multfactor_combobox()
                self.show_sld_button.Hide()
                self.multi_factor = None
        else:
            self._hide_multfactor_combobox()
            self.show_sld_button.Hide()
            self.multi_factor = None

        s_id = self.structurebox.GetCurrentSelection()
        struct_factor = self.structurebox.GetClientData(s_id)

        if struct_factor is not None:
            self.model = MultiplicationModel(form_factor(self.multi_factor),
                                             struct_factor())
            # multifunctional form factor
            if len(form_factor.non_fittable) > 0:
                self.temp_multi_functional = True
        elif form_factor is not None:
            if self.multi_factor is not None:
                self.model = form_factor(self.multi_factor)
            else:
                # old style plugin models do not accept a multiplicity argument
                self.model = form_factor()
        else:
            self.model = None
            return

        # check if model has magnetic parameters
        if len(self.model.magnetic_params) > 0:
            self._has_magnetic = True
        else:
            self._has_magnetic = False
        # post state to fit panel
        self.state.parameters = []
        self.state.model = self.model
        self.state.qmin = self.qmin_x
        self.state.multi_factor = self.multi_factor
        self.disp_list = self.model.getDispParamList()
        self.state.disp_list = self.disp_list
        self.on_set_focus(None)
        self.Layout()


    def _validate_qrange(self, qmin_ctrl, qmax_ctrl):
        """
        Verify that the Q range controls have valid values
        and that Qmin < Qmax.

        :param qmin_ctrl: text control for Qmin
        :param qmax_ctrl: text control for Qmax

        :return: True is the Q range is value, False otherwise

        """
        qmin_validity = check_float(qmin_ctrl)
        qmax_validity = check_float(qmax_ctrl)
        if not (qmin_validity and qmax_validity):
            return False
        else:
            qmin = float(qmin_ctrl.GetValue())
            qmax = float(qmax_ctrl.GetValue())
            if qmin < qmax:
                # Make sure to set both colours white.
                qmin_ctrl.SetBackgroundColour(wx.WHITE)
                qmin_ctrl.Refresh()
                qmax_ctrl.SetBackgroundColour(wx.WHITE)
                qmax_ctrl.Refresh()
            else:
                qmin_ctrl.SetBackgroundColour("pink")
                qmin_ctrl.Refresh()
                qmax_ctrl.SetBackgroundColour("pink")
                qmax_ctrl.Refresh()
                msg = "Invalid Q range: Q min must be smaller than Q max"
                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                return False
        return True

    def _validate_Npts(self):
        """
        Validate the number of points for fitting is more than 10 points.
        If valid, setvalues Npts_fit otherwise post msg.
        """
        # default flag
        flag = True
        # Theory
        if self.data is None and self.enable2D:
            return flag
        for data in self.data_list:
            # q value from qx and qy
            radius = np.sqrt(data.qx_data * data.qx_data +
                             data.qy_data * data.qy_data)
            # get unmasked index
            index_data = (float(self.qmin.GetValue()) <= radius) & \
                         (radius <= float(self.qmax.GetValue()))
            index_data = (index_data) & (data.mask)
            index_data = (index_data) & (np.isfinite(data.data))

            if len(index_data[index_data]) < 10:
                # change the color pink.
                self.qmin.SetBackgroundColour("pink")
                self.qmin.Refresh()
                self.qmax.SetBackgroundColour("pink")
                self.qmax.Refresh()
                msg = "Data Error: "
                msg += "Too few points in %s." % data.name
                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                self.fitrange = False
                flag = False
            else:
                self.Npts_fit.SetValue(str(len(index_data[index_data])))
                self.fitrange = True

        return flag

    def _validate_Npts_1D(self):
        """
        Validate the number of points for fitting is more than 5 points.
        If valid, setvalues Npts_fit otherwise post msg.
        """
        # default flag
        flag = True
        # Theory
        if self.data is None:
            return flag
        for data in self.data_list:
            # q value from qx and qy
            radius = data.x
            # get unmasked index
            index_data = (float(self.qmin.GetValue()) <= radius) & \
                         (radius <= float(self.qmax.GetValue()))
            index_data = (index_data) & (np.isfinite(data.y))

            if len(index_data[index_data]) < 5:
                # change the color pink.
                self.qmin.SetBackgroundColour("pink")
                self.qmin.Refresh()
                self.qmax.SetBackgroundColour("pink")
                self.qmax.Refresh()
                msg = "Data Error: "
                msg += "Too few points in %s." % data.name
                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                self.fitrange = False
                flag = False
            else:
                self.Npts_fit.SetValue(str(len(index_data[index_data])))
                self.fitrange = True

        return flag

    def _check_value_enter(self, list):
        """
        :param list: model parameter and panel info
        :Note: each item of the list should be as follow:
            item=[check button state, parameter's name,
                paramater's value, string="+/-",
                parameter's error of fit,
                parameter's minimum value,
                parameter's maximum value ,
                parameter's units]

        Returns True if the model parameters have changed.
        """
        is_modified = False
        for item in list:
            # skip angle parameters for 1D
            if not self.enable2D and item in self.orientation_params:
                continue

            value_ctrl = item[2]
            if not value_ctrl.IsEnabled():
                # ArrayDispersion disables PD, Min, Max, Npts, Nsigs
                continue

            name = item[1]
            value_str = value_ctrl.GetValue().strip()
            if name.endswith(".npts"):
                validity = check_int(value_ctrl)
                if not validity:
                    continue
                value = int(value_str)

            elif name.endswith(".nsigmas"):
                validity = check_float(value_ctrl)
                if not validity:
                    continue
                value = float(value_str)

            else:  # value or polydispersity

                # Check that min, max and value are floats
                min_ctrl, max_ctrl = item[5], item[6]
                min_str = min_ctrl.GetValue().strip()
                max_str = max_ctrl.GetValue().strip()
                validity = check_float(value_ctrl)
                if min_str != "":
                    validity = validity and check_float(min_ctrl)
                if max_str != "":
                    validity = validity and check_float(max_ctrl)
                if not validity:
                    continue

                # Check that min is less than max
                low = -np.inf if min_str == "" else float(min_str)
                high = np.inf if max_str == "" else float(max_str)
                if high < low:
                    min_ctrl.SetBackgroundColour("pink")
                    min_ctrl.Refresh()
                    max_ctrl.SetBackgroundColour("pink")
                    max_ctrl.Refresh()
                    # msg = "Invalid fit range for %s: min must be smaller
                    # than max"%name
                    # wx.PostEvent(self._manager.parent,
                    # StatusEvent(status=msg))
                    continue

                # Force value between min and max
                value = float(value_str)
                if value < low:
                    value = low
                    value_ctrl.SetValue(format_number(value))
                elif value > high:
                    value = high
                    value_ctrl.SetValue(format_number(value))

                if name not in list(self.model.details.keys()):
                    self.model.details[name] = ["", None, None]
                old_low, old_high = self.model.details[name][1:3]
                if old_low != low or old_high != high:
                    # The configuration has changed but it won't change the
                    # computed curve so no need to set is_modified to True
                    # is_modified = True
                    self.model.details[name][1:3] = low, high

            # Update value in model if it has changed
            if value != self.model.getParam(name):
                self.model.setParam(name, value)
                is_modified = True

        return is_modified

    def _set_dipers_Param(self, event):
        """
        respond to self.enable_disp and self.disable_disp radio box.
        The dispersity object is reset inside the model into Gaussian.
        When the user select yes , this method display a combo box for
        more selection when the user selects No,the combo box disappears.
        Redraw the model with the default dispersity (Gaussian)
        """
        # On selction if no model exists.
        if self.model is None:
            self.disable_disp.SetValue(True)
            msg = "Please select a Model first..."
            wx.MessageBox(msg, 'Info')
            wx.PostEvent(self._manager.parent,
                         StatusEvent(status="Polydispersion: %s" % msg))
            return

        self._reset_dispersity()

        if self.model is None:
            self.model_disp.Hide()
            self.sizer4_4.Clear(True)
            return

        if self.enable_disp.GetValue():
            # layout for model containing no dispersity parameters

            self.disp_list = self.model.getDispParamList()

            if len(self.disp_list) == 0 and len(self.disp_cb_dict) == 0:
                self._layout_sizer_noDipers()
            else:
                # set gaussian sizer
                self._on_select_Disp(event=None)
        else:
            self.sizer4_4.Clear(True)

        # post state to fit panel
        self.save_current_state()
        if event is not None:
            event = PageInfoEvent(page=self)
            wx.PostEvent(self.parent, event)
        # draw the model with the current dispersity

        # Wojtek P, Oct 8, 2016: Calling draw_model seems to be unessecary.
        # By comenting it we save an extra Iq calculation
        # self._draw_model()

        # Need to use FitInside again here to replace the next four lines.
        # Otherwised polydispersity off does not resize the scrollwindow.
        # PDB Nov 28, 2015
        self.FitInside()
#        self.sizer4_4.Layout()
#        self.sizer5.Layout()
#        self.Layout()
#        self.Refresh()

    def _layout_sizer_noDipers(self):
        """
        Draw a sizer with no dispersity info
        """
        ix = 0
        iy = 1
        self.fittable_param = []
        self.fixed_param = []
        self.orientation_params_disp = []

        self.sizer4_4.Clear(True)
        text = "No polydispersity available for this model"
        model_disp = wx.StaticText(self, wx.ID_ANY, text)
        self.sizer4_4.Add(model_disp, (iy, ix), (1, 1),
                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
        self.sizer4_4.Layout()
        self.sizer4.Layout()

    def _reset_dispersity(self):
        """
        put gaussian dispersity into current model
        """
        if self.param_toFit:
            for item in self.fittable_param:
                if item in self.param_toFit:
                    self.param_toFit.remove(item)

            for item in self.orientation_params_disp:
                if item in self.param_toFit:
                    self.param_toFit.remove(item)

        self.fittable_param = []
        self.fixed_param = []
        self.orientation_params_disp = []
        self.values = {}
        self.weights = {}

        if not self.disp_cb_dict:
            self.sizer4_4.Clear(True)
        else:
            for p in self.disp_cb_dict:
                # The parameter was un-selected.
                # Go back to Gaussian model (with 0 pts)
                disp_model = GaussianDispersion()

                self._disp_obj_dict[p] = disp_model
                # Set the new model as the dispersion object
                # for the selected parameter
                try:
                    self.model.set_dispersion(p, disp_model)
                except Exception:
                    logger.error(traceback.format_exc())

        # save state into
        self.save_current_state()
        self.Layout()
        self.Refresh()

    def _on_select_Disp(self, event):
        """
        allow selecting different dispersion
        self.disp_list should change type later .now only gaussian
        """
        self._set_sizer_dispersion()

        # Redraw the model
        #  Wojtek P. Nov 7, 2016: Redrawing seems to be unnecessary here
        # self._draw_model()
        # self._undo.Enable(True)
        event = PageInfoEvent(page=self)
        wx.PostEvent(self.parent, event)

        self.sizer4_4.Layout()
        self.sizer4.Layout()
        self.SetupScrolling()

    def _on_disp_func(self, event=None):
        """
        Select a distribution function for the polydispersion

        :Param event: ComboBox event
        """
        # get ready for new event
        if event is not None:
            event.Skip()
        # Get event object
        disp_box = event.GetEventObject()

        # Try to select a Distr. function
        try:
            disp_box.SetBackgroundColour("white")
            selection = disp_box.GetCurrentSelection()
            param_name = disp_box.Name.split('.')[0]
            disp_name = disp_box.GetValue()
            dispersity = disp_box.GetClientData(selection)

            # disp_model =  GaussianDispersion()
            disp_model = dispersity()
            # Get param names to reset the values of the param
            name1 = param_name + ".width"
            name2 = param_name + ".npts"
            name3 = param_name + ".nsigmas"
            # Check Disp. function whether or not it is 'array'
            if disp_name.lower() == "array":
                value2 = ""
                value3 = ""
                value1 = self._set_array_disp(name=name1, disp=disp_model)
            else:
                self._del_array_values(name1)
                # self._reset_array_disp(param_name)
                self._disp_obj_dict[name1] = disp_model
                self.model.set_dispersion(param_name, disp_model)
                self.state.disp_obj_dict[name1] = disp_model.type

                value1 = str(format_number(self.model.getParam(name1), True))
                value2 = str(format_number(self.model.getParam(name2)))
                value3 = str(format_number(self.model.getParam(name3)))
            # Reset fittable polydispersin parameter value
            for item in self.fittable_param:
                if item[1] == name1:
                    item[2].SetValue(value1)
                    item[5].SetValue("")
                    item[6].SetValue("")
                    # Disable for array
                    if disp_name.lower() == "array":
                        item[0].SetValue(False)
                        item[0].Disable()
                        item[2].Disable()
                        item[3].Show(False)
                        item[4].Show(False)
                        item[5].Disable()
                        item[6].Disable()
                    else:
                        item[0].Enable()
                        item[2].Enable()
                        item[3].Show(True)
                        item[4].Show(True)
                        item[5].Enable()
                        item[6].Enable()
                    break
            # Reset fixed polydispersion params
            for item in self.fixed_param:
                if item[1] == name2:
                    item[2].SetValue(value2)
                    # Disable Npts for array
                    if disp_name.lower() == "array":
                        item[2].Disable()
                    else:
                        item[2].Enable()
                if item[1] == name3:
                    item[2].SetValue(value3)
                    # Disable Nsigs for array
                    if disp_name.lower() == "array":
                        item[2].Disable()
                    else:
                        item[2].Enable()

            # Make sure the check box updated
            self.get_all_checked_params()

            # update params
            self._update_paramv_on_fit()
            # draw
            self._draw_model()
            self.Refresh()
        except Exception:
            logger.error(traceback.format_exc())
            # Error msg
            msg = "Error occurred:"
            msg += " Could not select the distribution function..."
            msg += " Please select another distribution function."
            disp_box.SetBackgroundColour("pink")
            # Focus on Fit button so that users can see the pinky box
            self.btFit.SetFocus()
            wx.PostEvent(self._manager.parent,
                         StatusEvent(status=msg, info="error"))

    def _set_array_disp(self, name=None, disp=None):
        """
        Set array dispersion

        :param name: name of the parameter for the dispersion to be set
        :param disp: the polydisperion object
        """
        # The user wants this parameter to be averaged.
        # Pop up the file selection dialog.
        path = self._selectDlg()
        # Array data
        values = []
        weights = []
        # If nothing was selected, just return
        if path is None:
            self.disp_cb_dict[name].SetValue(False)
            # self.noDisper_rbox.SetValue(True)
            return
        self._default_save_location = os.path.dirname(path)
        if self._manager is not None:
            self._manager.parent._default_save_location = \
                             self._default_save_location

        basename = os.path.basename(path)
        values, weights = self.read_file(path)

        # If any of the two arrays is empty, notify the user that we won't
        # proceed
        if len(self.param_toFit) > 0:
            if name in self.param_toFit:
                self.param_toFit.remove(name)

        # Tell the user that we are about to apply the distribution
        msg = "Applying loaded %s distribution: %s" % (name, path)
        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
        self._set_array_disp_model(name=name, disp=disp,
                                   values=values, weights=weights)
        return basename

    def _set_array_disp_model(self, name=None, disp=None,
                              values=[], weights=[]):
        """
        Set array dispersion model

        :param name: name of the parameter for the dispersion to be set
        :param disp: the polydisperion object
        """
        disp.set_weights(values, weights)
        self._disp_obj_dict[name] = disp
        self.model.set_dispersion(name.split('.')[0], disp)
        self.state.disp_obj_dict[name] = disp.type
        self.values[name] = values
        self.weights[name] = weights
        # Store the object to make it persist outside the
        # scope of this method
        # TODO: refactor model to clean this up?
        self.state.values = {}
        self.state.weights = {}
        self.state.values = copy.deepcopy(self.values)
        self.state.weights = copy.deepcopy(self.weights)

        # Set the new model as the dispersion object for the
        # selected parameter
        # self.model.set_dispersion(p, disp_model)
        # Store a reference to the weights in the model object
        # so that
        # it's not lost when we use the model within another thread.
        self.state.model = self.model.clone()
        self.model._persistency_dict[name.split('.')[0]] = \
            [values, weights]
        self.state.model._persistency_dict[name.split('.')[0]] = \
            [values, weights]

    def _del_array_values(self, name=None):
        """
        Reset array dispersion

        :param name: name of the parameter for the dispersion to be set
        """
        # Try to delete values and weight of the names array dic if exists
        try:
            if name in self.values:
                del self.values[name]
                del self.weights[name]
                # delete all other dic
                del self.state.values[name]
                del self.state.weights[name]
                del self.model._persistency_dict[name.split('.')[0]]
                del self.state.model._persistency_dict[name.split('.')[0]]
        except Exception:
            logger.error(traceback.format_exc())

    def _lay_out(self):
        """
        returns self.Layout

        :Note: Mac seems to like this better when self.
            Layout is called after fitting.
        """
        self.Layout()
        return

    def _find_polyfunc_selection(self, disp_func=None):
        """
        FInd Comboox selection from disp_func

        :param disp_function: dispersion distr. function
        """
        # Find the selection
        if disp_func is not None:
            try:
                return list(POLYDISPERSITY_MODELS.values()).index(disp_func.__class__)
            except ValueError:
                pass  # Fall through to default class
        return list(POLYDISPERSITY_MODELS.keys()).index('gaussian')

    def on_reset_clicked(self, event):
        """
        On 'Reset' button  for Q range clicked
        """
        flag = True
        # For 3 different cases: Data2D, Data1D, and theory
        if self.model is None:
            msg = "Please select a model first..."
            wx.MessageBox(msg, 'Info')
            flag = False
            return

        elif self.data.__class__.__name__ == "Data2D":
            data_min = 0
            x = max(math.fabs(self.data.xmin), math.fabs(self.data.xmax))
            y = max(math.fabs(self.data.ymin), math.fabs(self.data.ymax))
            self.qmin_x = data_min
            self.qmax_x = math.sqrt(x * x + y * y)
            # self.data.mask = np.ones(len(self.data.data),dtype=bool)
            # check smearing
            if not self.disable_smearer.GetValue():
                # set smearing value whether or
                # not the data contain the smearing info
                if self.pinhole_smearer.GetValue():
                    flag = self.update_pinhole_smear()
                else:
                    flag = True

        elif self.data is None:
            self.qmin_x = _QMIN_DEFAULT
            self.qmax_x = _QMAX_DEFAULT
            self.num_points = _NPTS_DEFAULT
            self.state.npts = self.num_points

        elif self.data.__class__.__name__ != "Data2D":
            self.qmin_x = min(self.data.x)
            self.qmax_x = max(self.data.x)
            # check smearing
            if not self.disable_smearer.GetValue():
                # set smearing value whether or
                # not the data contain the smearing info
                if self.slit_smearer.GetValue():
                    flag = self.update_slit_smear()
                elif self.pinhole_smearer.GetValue():
                    flag = self.update_pinhole_smear()
                else:
                    flag = True
        else:
            flag = False

        if flag is False:
            msg = "Cannot Plot :Must enter a number!!!  "
            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
        else:
            # set relative text ctrs.
            self.qmin.SetValue(str(self.qmin_x))
            self.qmax.SetValue(str(self.qmax_x))
            self.show_npts2fit()
            # At this point, some button and variables satatus (disabled?)
            # should be checked such as color that should be reset to
            # white in case that it was pink.
            self._onparamEnter_helper()

        self.save_current_state()
        self.state.qmin = self.qmin_x
        self.state.qmax = self.qmax_x

        # reset the q range values
        self._reset_plotting_range(self.state)
        self._draw_model()

    def select_log(self, event):
        """
        Log checked to generate log spaced points for theory model
        """

    def get_images(self):
        """
        Get the images of the plots corresponding this panel for report

        : return graphs: list of figures
        : Need Move to guiframe
        """
        # set list of graphs
        graphs = []
        canvases = []
        res_item = None
        # call gui_manager
        gui_manager = self._manager.parent
        # loops through the panels [dic]
        for _, item2 in gui_manager.plot_panels.items():
            data_title = self.data.group_id
            # try to get all plots belonging to this control panel
            try:
                g_id = item2.group_id
                if g_id == data_title or \
                        str(g_id).count("res" + str(self.graph_id)) or \
                        str(g_id).count(str(self.uid)) > 0:
                    if str(g_id).count("res" + str(self.graph_id)) > 0:
                        res_item = [item2.figure, item2.canvas]
                    else:
                        # append to the list
                        graphs.append(item2.figure)
                        canvases.append(item2.canvas)
            except Exception:
                # Not for control panels
                logger.error(traceback.format_exc())
        # Make sure the resduals plot goes to the last
        if res_item is not None:
            graphs.append(res_item[0])
            canvases.append(res_item[1])
        # return the list of graphs
        return graphs, canvases

    def on_function_help_clicked(self, event):
        """
        Function called when 'Help' button is pressed next to model
        of interest.  This calls DocumentationWindow from
        documentation_window.py. It will load the top level of the model
        help documenation sphinx generated html if no model is presented.
        If a model IS present then if documention for that model exists
        it will load to that  point otherwise again it will go to the top.
        For Wx2.8 and below is used (i.e. non-released through installer)
        a browser is loaded and the top of the model documentation only is
        accessible because webbrowser module does not pass anything after
        the # to the browser.

        :param event: on Help Button pressed event
        """

        if self.model is not None:
            name = self.formfactorbox.GetValue()
            _TreeLocation = 'user/models/' + name.lower()+'.html'
            _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
                                              "", name + " Help")
        else:
            _TreeLocation = 'user/sasgui/perspectives/fitting/models/index.html'
            _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
                                              "", "General Model Help")

    def on_model_help_clicked(self, event):
        """
        Function called when 'Description' button is pressed next to model
        of interest.  This calls the Description embedded in the model. This
        should work with either Wx2.8 and lower or higher. If no model is
        selected it will give the message that a model must be chosen first
        in the box that would normally contain the description.  If a badly
        behaved model is encountered which has no description then it will
        give the message that none is available.

        :param event: on Description Button pressed event
        """

        if self.model is None:
            name = 'index.html'
        else:
            name = self.formfactorbox.GetValue()

        msg = 'Model description:\n'
        info = "Info"
        if self.model is not None:
            # frame.Destroy()
            if str(self.model.description).rstrip().lstrip() == '':
                msg += "Sorry, no information is available for this model."
            else:
                msg += self.model.description + '\n'
            wx.MessageBox(msg, info)
        else:
            msg += "You must select a model to get information on this"
            wx.MessageBox(msg, info)

    def _on_mag_angle_help(self, event):
        """
        Bring up Magnetic Angle definition.png image whenever the ? button
        is clicked. Calls DocumentationWindow with the path of the location
        within the documentation tree (after /doc/ ....". When using old
        versions of Wx (i.e. before 2.9 and therefore not part of release
        versions distributed via installer) it brings up an image viewer
        box which allows the user to click through the rest of the images in
        the directory.  Not ideal but probably better than alternative which
        would bring up the entire discussion of how magnetic models work?
        Specially since it is not likely to be accessed.  The normal release
        versions bring up the normal image box.

        :param evt: Triggers on clicking ? in Magnetic Angles? box
        """

        _TreeLocation = "_images/M_angles_pic.png"
        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
                                          "Magnetic Angle Defintions")

    def _on_mag_help(self, event):
        """
        Bring up Magnetic Angle definition.png image whenever the ? button
        is clicked. Calls DocumentationWindow with the path of the location
        within the documentation tree (after /doc/ ....". When using old
        versions of Wx (i.e. before 2.9 and therefore not part of release
        versions distributed via installer) it brings up an image viewer
        box which allows the user to click through the rest of the images in
        the directory.  Not ideal but probably better than alternative which
        would bring up the entire discussion of how magnetic models work?
        Specially since it is not likely to be accessed.  The normal release
        versions bring up the normal image box.

        :param evt: Triggers on clicking ? in Magnetic Angles? box
        """

        _TreeLocation = "user/sasgui/perspectives/fitting/magnetism/magnetism.html"
        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
                                          "Polarized Beam/Magnetc Help")

    def _on_mag_on(self, event):
        """
        Magnetic Parameters ON/OFF
        """
        button = event.GetEventObject()

        if button.GetLabel().count('ON') > 0:
            self.magnetic_on = True
            button.SetLabel("Magnetic OFF")
            m_value = 1.0e-06
            for key in self.model.magnetic_params:
                if key.count('M0') > 0:
                    self.model.setParam(key, m_value)
                    m_value += 0.5e-06
        else:
            self.magnetic_on = False
            button.SetLabel("Magnetic ON")
            for key in self.model.magnetic_params:
                if key.count('M0') > 0:
                    # reset mag value to zero fo safety
                    self.model.setParam(key, 0.0)

        self.Show(False)
        self.set_model_param_sizer(self.model)
        # self._set_sizer_dispersion()
        self.state.magnetic_on = self.magnetic_on
        self.SetupScrolling()
        self.Show(True)

    def on_pd_help_clicked(self, event):
        """
        Bring up Polydispersity Documentation whenever the ? button is clicked.
        Calls DocumentationWindow with the path of the location within the
        documentation tree (after /doc/ ....".  Note that when using old
        versions of Wx (before 2.9) and thus not the release version of
        istallers, the help comes up at the top level of the file as
        webbrowser does not pass anything past the # to the browser when it is
        running "file:///...."

        :param event: Triggers on clicking ? in polydispersity box
        """

        _TreeLocation = "user/sasgui/perspectives/fitting/pd/polydispersity.html"
        _PageAnchor = ""
        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
                                          _PageAnchor, "Polydispersity Help")

    def on_left_down(self, event):
        """
        Get key stroke event
        """
        # Figuring out key combo: Cmd for copy, Alt for paste
        if event.CmdDown() and event.ShiftDown():
            self.get_paste()
        elif event.CmdDown():
            self.get_copy()
        else:
            event.Skip()
            return
        # make event free
        event.Skip()

    def get_copy(self):
        """
        Get copy params to clipboard
        """
        content = self.get_copy_params()
        flag = self.set_clipboard(content)
        self._copy_info(flag)
        return flag

    def get_copy_params(self):
        """
        Get the string copies of the param names and values in the tap
        """
        content = 'sasview_parameter_values:'
        # Do it if params exist
        if self.parameters:

            # go through the parameters
            strings = self._get_copy_helper(self.parameters,
                                            self.orientation_params)
            content += strings

            # go through the fittables
            strings = self._get_copy_helper(self.fittable_param,
                                            self.orientation_params_disp)
            content += strings

            # go through the fixed params
            strings = self._get_copy_helper(self.fixed_param,
                                            self.orientation_params_disp)
            content += strings

            # go through the str params
            strings = self._get_copy_helper(self.str_parameters,
                                            self.orientation_params)
            content += strings
            return content
        else:
            return False


    def _get_copy_params_details(self):
        """
        Combines polydisperse parameters with self.parameters so that they can
        be written to the clipboard (for Excel or LaTeX). Also returns a list of
        the names of parameters that have been fitted

        :returns: all_params - A list of all parameters, in the format of
        self.parameters
        :returns: fitted_par_names - A list of the names of parameters that have
        been fitted
        """
        # Names of params that are being fitted
        fitted_par_names = [param[1] for param in self.param_toFit]
        # Names of params with associated polydispersity
        disp_params = [param[1].split('.')[0] for param in self.fittable_param]

        # Create array of all parameters
        all_params = copy.copy(self.parameters)
        for param in self.parameters:
            if param[1] in disp_params:
                # Polydisperse params aren't in self.parameters, so need adding
                # to all_params
                name = param[1] + ".width"
                index = all_params.index(param) + 1
                to_insert = []
                if name in fitted_par_names:
                    # Param is fitted, so already has a param list in self.param_toFit
                    to_insert = self.param_toFit[fitted_par_names.index(name)]
                else:
                    # Param isn't fitted, so mockup a param list
                    to_insert = [None, name, self.model.getParam(name), None, None]
                all_params.insert(index, to_insert)
        return all_params, fitted_par_names

    def get_copy_excel(self):
        """
        Get copy params to clipboard
        """
        content = self.get_copy_params_excel()
        flag = self.set_clipboard(content)
        self._copy_info(flag)
        return flag

    def get_copy_params_excel(self):
        """
        Get the string copies of the param names and values in the tap
        """
        if not self.parameters:
            # Do nothing if parameters doesn't exist
            return False

        content = ''
        crlf = chr(13) + chr(10)
        tab = chr(9)

        all_params, fitted_param_names = self._get_copy_params_details()

        # Construct row of parameter names
        for param in all_params:
            name = param[1] # Parameter name
            content += name
            content += tab
            if name in fitted_param_names:
                # Only print errors for fitted parameters
                content += name + "_err"
                content += tab

        content += crlf

        # Construct row of parameter values and errors
        for param in all_params:
            value = param[2]
            if hasattr(value, 'GetValue'):
                # param[2] is a text box
                value = value.GetValue()
            else:
                # param[2] is a float (from our self._get_copy_params_details)
                value = str(value)
            content += value
            content += tab
            if param[1] in fitted_param_names:
                # Only print errors for fitted parameters
                content += param[4].GetValue()
                content += tab

        return content

    def get_copy_latex(self):
        """
        Get copy params to clipboard
        """
        content = self.get_copy_params_latex()
        flag = self.set_clipboard(content)
        self._copy_info(flag)
        return flag

    def get_copy_params_latex(self):
        """
        Get the string copies of the param names and values in the tap
        """
        if not self.parameters:
            # Do nothing if self.parameters doesn't exist
            return False

        content = r'\begin{table}'
        content += r'\begin{tabular}[h]'

        crlf = chr(13) + chr(10)
        tab = chr(9)

        all_params, fitted_param_names = self._get_copy_params_details()

        content += '{|'
        for param in all_params:
            content += 'l|l|'
        content += r'}\hline'
        content += crlf

        # Construct row of parameter names
        for index, param in enumerate(all_params):
            name = param[1] # Parameter name
            content += name.replace('_', r'\_')  # Escape underscores
            if name in fitted_param_names:
                # Only print errors for fitted parameters
                content += ' & '
                content += name.replace('_', r'\_') + r"\_err"
            if index < len(all_params) - 1:
                content += ' & '

        content += r'\\ \hline'
        content += crlf

        # Construct row of values and errors
        for index, param in enumerate(all_params):
            value = param[2]
            if hasattr(value, "GetValue"):
                # value is a text box
                value = value.GetValue()
            else:
                # value is a float (from self._get_copy_params_details)
                value = str(value)
            content += value
            if param[1] in fitted_param_names:
                # Only print errors for fitted params
                content += ' & '
                content += param[4].GetValue()
            if index < len(all_params) - 1:
                content += ' & '

        content += r'\\ \hline'
        content += crlf
        content += r'\end{tabular}'
        content += r'\end{table}'

        return content

    def set_clipboard(self, content=None):
        """
        Put the string to the clipboard
        """
        if not content:
            return False
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(wx.TextDataObject(str(content)))
            wx.TheClipboard.Close()
            return True
        return None

    def _get_copy_helper(self, param, orient_param):
        """
        Helping get value and name of the params

        : param param:  parameters
        : param orient_param = oritational params
        : return content: strings [list] [name,value:....]
        """
        content = ''
        bound_hi = ''
        bound_lo = ''
        # go through the str params
        for item in param:
            # copy only the params shown
            if not item[2].IsShown():
                continue
            disfunc = ''
            try:
                if item[7].__class__.__name__ == 'ComboBox':
                    disfunc = str(item[7].GetValue())
            except Exception:
                logger.error(traceback.format_exc())

            # 2D
            if self.data.__class__.__name__ == "Data2D":
                try:
                    check = item[0].GetValue()
                except Exception:
                    check = None
                name = item[1]
                value = item[2].GetValue()
            # 1D
            else:
                # for 1D all parameters except orientation
                if not item[1] in orient_param:
                    try:
                        check = item[0].GetValue()
                    except:
                        check = None
                    name = item[1]
                    value = item[2].GetValue()

            # Bounds
            try:
                bound_lo = item[5].GetValue()
                bound_hi = item[6].GetValue()
            except Exception:
                # harmless - need to just pass
                pass

            # add to the content
            if disfunc != '':

                disfunc = ',' + disfunc
            # Need to support array func for copy/paste
            try:
                if disfunc.count('array') > 0:
                    disfunc += ','
                    for val in self.values[name]:
                        disfunc += ' ' + str(val)
                    disfunc += ','
                    for weight in self.weights[name]:
                        disfunc += ' ' + str(weight)
            except Exception:
                logger.error(traceback.format_exc())
            content += name + ',' + str(check) + ',' + value + disfunc + ',' + \
                       bound_lo + ',' + bound_hi + ':'

        return content

    def get_clipboard(self):
        """
        Get strings in the clipboard
        """
        text = ""
        # Get text from the clip board
        if wx.TheClipboard.Open():
            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
                data = wx.TextDataObject()
                # get wx dataobject
                success = wx.TheClipboard.GetData(data)
                # get text
                if success:
                    text = data.GetText()
                else:
                    text = ''
            # close clipboard
            wx.TheClipboard.Close()
        return text

    def get_paste(self):
        """
        Paste params from the clipboard
        """
        text = self.get_clipboard()
        flag = self.get_paste_params(text)
        self._copy_info(flag)
        return flag

    def get_paste_params(self, text=''):
        """
        Get the string copies of the param names and values in the tap
        """
        context = {}
        # put the text into dictionary
        lines = text.split(':')
        if lines[0] != 'sasview_parameter_values':
            self._copy_info(False)
            return False
        for line in lines[1:-1]:
            if len(line) != 0:
                item = line.split(',')
                check = item[1]
                name = item[0]
                value = item[2]
                # Transfer the text to content[dictionary]
                context[name] = [check, value]

                # limits
                limit_lo = item[3]
                context[name].append(limit_lo)
                limit_hi = item[4]
                context[name].append(limit_hi)

            # ToDo: PlugIn this poly disp function for pasting
            try:
                poly_func = item[5]
                context[name].append(poly_func)
                try:
                    # take the vals and weights for  array
                    array_values = item[6].split(' ')
                    array_weights = item[7].split(' ')
                    val = [float(a_val) for a_val in array_values[1:]]
                    weit = [float(a_weit) for a_weit in array_weights[1:]]

                    context[name].append(val)
                    context[name].append(weit)
                except:
                    raise
            except:
                poly_func = ''
                context[name].append(poly_func)

        # Do it if params exist
        if self.parameters:
            # go through the parameters
            self._get_paste_helper(self.parameters,
                                   self.orientation_params, context)

            # go through the fittables
            self._get_paste_helper(self.fittable_param,
                                   self.orientation_params_disp,
                                   context)

            # go through the fixed params
            self._get_paste_helper(self.fixed_param,
                                   self.orientation_params_disp, context)

            # go through the str params
            self._get_paste_helper(self.str_parameters,
                                   self.orientation_params, context)

            return True
        return None

    def _get_paste_helper(self, param, orient_param, content):
        """
        Helping set values of the params

        : param param:  parameters
        : param orient_param: oritational params
        : param content: dictionary [ name, value: name1.value1,...]
        """
        # go through the str params
        for item in param:
            # 2D
            if self.data.__class__.__name__ == "Data2D":
                name = item[1]
                if name in list(content.keys()):
                    values = content[name]
                    check = values[0]
                    pd = values[1]

                    if name.count('.') > 0:
                        # If this is parameter.width, then pd may be a floating
                        # point value or it may be an array distribution.
                        # Nothing to do for parameter.npts or parameter.nsigmas.
                        try:
                            float(pd)
                            if name.endswith('.npts'):
                                pd = int(pd)
                        except Exception:
                            # continue
                            if not pd and pd != '':
                                continue
                    item[2].SetValue(str(pd))
                    if item in self.fixed_param and pd == '':
                        # Only array func has pd == '' case.
                        item[2].Enable(False)
                    else:
                        item[2].Enable(True)
                    if item[2].__class__.__name__ == "ComboBox":
                        if content[name][1] in self.model.fun_list:
                            fun_val = self.model.fun_list[content[name][1]]
                            self.model.setParam(name, fun_val)
                    try:
                        item[5].SetValue(str(values[-3]))
                        item[6].SetValue(str(values[-2]))
                    except Exception:
                        # passing as harmless non-update
                        pass

                    value = content[name][1:]
                    self._paste_poly_help(item, value)
                    if check == 'True':
                        is_true = True
                    elif check == 'False':
                        is_true = False
                    else:
                        is_true = None
                    if is_true is not None:
                        item[0].SetValue(is_true)
            # 1D
            else:
                # for 1D all parameters except orientation
                if not item[1] in orient_param:
                    name = item[1]
                    if name in list(content.keys()):
                        check = content[name][0]
                        # Avoid changing combox content
                        value = content[name][1:]
                        pd = value[0]
                        if name.count('.') > 0:
                            # If this is parameter.width, then pd may be a
                            # floating point value or it may be an array
                            # distribution. Nothing to do for parameter.npts or
                            # parameter.nsigmas.
                            try:
                                pd = float(pd)
                                if name.endswith('.npts'):
                                    pd = int(pd)
                            except Exception:
                                # continue
                                if not pd and pd != '':
                                    continue
                        item[2].SetValue(str(pd))
                        if item in self.fixed_param and pd == '':
                            # Only array func has pd == '' case.
                            item[2].Enable(False)
                        else:
                            item[2].Enable(True)
                        if item[2].__class__.__name__ == "ComboBox":
                            if value[0] in self.model.fun_list:
                                fun_val = self.model.fun_list[value[0]]
                                self.model.setParam(name, fun_val)
                                # save state
                        try:
                            item[5].SetValue(str(value[-3]))
                            item[6].SetValue(str(value[-2]))
                        except Exception:
                            # passing as harmless non-update
                            pass

                        self._paste_poly_help(item, value)
                        if check == 'True':
                            is_true = True
                        elif check == 'False':
                            is_true = False
                        else:
                            is_true = None
                        if is_true is not None:
                            item[0].SetValue(is_true)

        self.select_param(event=None)
        self.Refresh()

    def _paste_poly_help(self, item, value):
        """
        Helps get paste for poly function

        *item* is the parameter name

        *value* depends on which parameter is being processed, and whether it
        has array polydispersity.

        For parameters without array polydispersity:

            parameter => ['FLOAT', '']
            parameter.width => ['FLOAT', 'DISTRIBUTION', '']
            parameter.npts => ['FLOAT', '']
            parameter.nsigmas => ['FLOAT', '']

        For parameters with array polydispersity:

            parameter => ['FLOAT', '']
            parameter.width => ['FILENAME', 'array', [x1, ...], [w1, ...]]
            parameter.npts => ['FLOAT', '']
            parameter.nsigmas => ['FLOAT', '']
        """
        # Do nothing if not setting polydispersity
        if len(value[3]) == 0:
            return

        try:
            name = item[7].Name
            param_name = name.split('.')[0]
            item[7].SetValue(value[1])
            selection = item[7].GetCurrentSelection()
            dispersity = item[7].GetClientData(selection)
            disp_model = dispersity()

            if value[1] == 'array':
                pd_vals = np.array(value[2])
                pd_weights = np.array(value[3])
                if len(pd_vals) == 0 or len(pd_vals) != len(pd_weights):
                    msg = ("bad array distribution parameters for %s"
                           % param_name)
                    raise ValueError(msg)
                self._set_disp_cb(True, item=item)
                self._set_array_disp_model(name=name,
                                           disp=disp_model,
                                           values=pd_vals,
                                           weights=pd_weights)
            else:
                self._set_disp_cb(False, item=item)
                self._disp_obj_dict[name] = disp_model
                self.model.set_dispersion(param_name, disp_model)
                self.state.disp_obj_dict[name] = disp_model.type
                # TODO: It's not an array, why update values and weights?
                self.model._persistency_dict[param_name] = \
                    [self.values, self.weights]
                self.state.values = self.values
                self.state.weights = self.weights

        except Exception:
            logger.error(traceback.format_exc())
            print("Error in BasePage._paste_poly_help: %s" % \
                  sys.exc_info()[1])

    def _set_disp_cb(self, isarray, item):
        """
        Set cb for array disp
        """
        if isarray:
            item[0].SetValue(False)
            item[0].Enable(False)
            item[2].Enable(False)
            item[3].Show(False)
            item[4].Show(False)
            item[5].SetValue('')
            item[5].Enable(False)
            item[6].SetValue('')
            item[6].Enable(False)
        else:
            item[0].Enable()
            item[2].Enable()
            item[3].Show(True)
            item[4].Show(True)
            item[5].Enable()
            item[6].Enable()

    def update_pinhole_smear(self):
        """
            Method to be called by sub-classes
            Moveit; This method doesn't belong here
        """
        print("BasicPage.update_pinhole_smear was called: skipping")
        return

    def _read_category_info(self):
        """
        Reads the categories in from file
        """
        # # ILL mod starts here - July 2012 kieranrcampbell@gmail.com
        self.master_category_dict = defaultdict(list)
        self.by_model_dict = defaultdict(list)
        self.model_enabled_dict = defaultdict(bool)
        categorization_file = CategoryInstaller.get_user_file()
        with open(categorization_file, 'rb') as f:
            self.master_category_dict = json.load(f)
        self._regenerate_model_dict()

    def _regenerate_model_dict(self):
        """
        regenerates self.by_model_dict which has each model name as the
        key and the list of categories belonging to that model
        along with the enabled mapping
        """
        self.by_model_dict = defaultdict(list)
        for category in self.master_category_dict:
            for (model, enabled) in self.master_category_dict[category]:
                self.by_model_dict[model].append(category)
                self.model_enabled_dict[model] = enabled

    def _populate_listbox(self):
        """
        fills out the category list box
        """
        uncat_str = 'Plugin Models'
        self._read_category_info()

        self.categorybox.Clear()
        cat_list = sorted(self.master_category_dict.keys())
        if uncat_str not in cat_list:
            cat_list.append(uncat_str)

        for category in cat_list:
            if category != '':
                self.categorybox.Append(category)

        if self.categorybox.GetSelection() == wx.NOT_FOUND:
            self.categorybox.SetSelection(0)
        else:
            self.categorybox.SetSelection(
                self.categorybox.GetSelection())
        # self._on_change_cat(None)

    def _on_change_cat(self, event):
        """
        Callback for category change action
        """
        self.model_name = None
        category = self.categorybox.GetStringSelection()
        if category is None:
            return
        self.model_box.Clear()

        if category == CUSTOM_MODEL:
            for model in self.model_list_box[category]:
                str_m = str(model).split(".")[0]
                self.model_box.Append(str_m)

        else:
            for model, enabled in sorted(self.master_category_dict[category],
                                         key=lambda name: name[0]):
                if enabled:
                    self.model_box.Append(model)

    def _fill_model_sizer(self, sizer):
        """
        fill sizer containing model info
        """
        # This should only be called once per fit tab
        # print "==== Entering _fill_model_sizer"
        # Add model function Details button in fitpanel.
        # The following 3 lines are for Mac. Let JHC know before modifying...
        title = "Model"
        self.formfactorbox = None
        self.multifactorbox = None
        self.mbox_description = wx.StaticBox(self, wx.ID_ANY, str(title))
        boxsizer1 = wx.StaticBoxSizer(self.mbox_description, wx.VERTICAL)
        sizer_cat = wx.BoxSizer(wx.HORIZONTAL)
        self.mbox_description.SetForegroundColour(wx.RED)
        wx_id = next(self._ids)
        self.model_func = wx.Button(self, wx_id, 'Help', size=(80, 23))
        self.model_func.Bind(wx.EVT_BUTTON, self.on_function_help_clicked,
                             id=wx_id)
        self.model_func.SetToolTipString("Full Model Function Help")
        wx_id = next(self._ids)
        self.model_help = wx.Button(self, wx_id, 'Description', size=(80, 23))
        self.model_help.Bind(wx.EVT_BUTTON, self.on_model_help_clicked,
                             id=wx_id)
        self.model_help.SetToolTipString("Short Model Function Description")
        wx_id = next(self._ids)
        self.model_view = wx.Button(self, wx_id, "Show 2D", size=(80, 23))
        self.model_view.Bind(wx.EVT_BUTTON, self._onModel2D, id=wx_id)
        hint = "toggle view of model from 1D to 2D  or 2D to 1D"
        self.model_view.SetToolTipString(hint)

        cat_set_box = wx.StaticBox(self, wx.ID_ANY, 'Category')
        sizer_cat_box = wx.StaticBoxSizer(cat_set_box, wx.HORIZONTAL)
        sizer_cat_box.SetMinSize((200, 50))
        self.categorybox = wx.ComboBox(self, wx.ID_ANY,
                                       style=wx.CB_READONLY)
        self.categorybox.SetToolTip(wx.ToolTip("Select a Category/Type"))
        self._populate_listbox()
        wx.EVT_COMBOBOX(self.categorybox, wx.ID_ANY, self._show_combox)
        # self.shape_rbutton = wx.RadioButton(self, wx.ID_ANY, 'Shapes',
        #                                     style=wx.RB_GROUP)
        # self.shape_indep_rbutton = wx.RadioButton(self, wx.ID_ANY,
        #                                          "Shape-Independent")
        # self.struct_rbutton = wx.RadioButton(self, wx.ID_ANY,
        #                                     "Structure Factor ")
        # self.plugin_rbutton = wx.RadioButton(self, wx.ID_ANY,
        #                                     "Uncategorized")

        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
        #                   id=self.shape_rbutton.GetId())
        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
        #                    id=self.shape_indep_rbutton.GetId())
        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
        #                    id=self.struct_rbutton.GetId())
        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
        #                    id=self.plugin_rbutton.GetId())
        # MAC needs SetValue

        show_cat_button = wx.Button(self, wx.ID_ANY, "Modify")
        cat_tip = "Modify model categories \n"
        cat_tip += "(also accessible from the menu bar)."
        show_cat_button.SetToolTip(wx.ToolTip(cat_tip))
        show_cat_button.Bind(wx.EVT_BUTTON, self._on_modify_cat)
        sizer_cat_box.Add(self.categorybox, 1, wx.RIGHT, 3)
        sizer_cat_box.Add((10, 10))
        sizer_cat_box.Add(show_cat_button)
        # self.shape_rbutton.SetValue(True)

        sizer_radiobutton = wx.GridSizer(2, 2, 5, 5)
        # sizer_radiobutton.Add(self.shape_rbutton)
        # sizer_radiobutton.Add(self.shape_indep_rbutton)
        sizer_radiobutton.Add((5, 5))
        sizer_radiobutton.Add(self.model_view, 1, wx.RIGHT, 5)
        # sizer_radiobutton.Add(self.plugin_rbutton)
        # sizer_radiobutton.Add(self.struct_rbutton)
        # sizer_radiobutton.Add((5,5))
        sizer_radiobutton.Add(self.model_help, 1, wx.RIGHT | wx.LEFT, 5)
        # sizer_radiobutton.Add((5,5))
        sizer_radiobutton.Add(self.model_func, 1, wx.RIGHT, 5)
        sizer_cat.Add(sizer_cat_box, 1, wx.LEFT, 2.5)
        sizer_cat.Add(sizer_radiobutton)
        sizer_selection = wx.BoxSizer(wx.HORIZONTAL)
        mutifactor_selection = wx.BoxSizer(wx.HORIZONTAL)

        self.text1 = wx.StaticText(self, wx.ID_ANY, "")
        self.text2 = wx.StaticText(self, wx.ID_ANY, "P(Q)*S(Q)")
        self.mutifactor_text = wx.StaticText(self, wx.ID_ANY, "No. of Shells: ")
        self.mutifactor_text1 = wx.StaticText(self, wx.ID_ANY, "")
        self.show_sld_button = wx.Button(self, wx.ID_ANY, "Show SLD Profile")
        self.show_sld_button.Bind(wx.EVT_BUTTON, self._on_show_sld)

        self.formfactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
        self.formfactorbox.SetToolTip(wx.ToolTip("Select a Model"))
        if self.model is not None:
            self.formfactorbox.SetValue(self.model.name)
        self.structurebox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
        self.multifactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
        self.initialize_combox()
        wx.EVT_COMBOBOX(self.formfactorbox, wx.ID_ANY, self._on_select_model)

        wx.EVT_COMBOBOX(self.structurebox, wx.ID_ANY, self._on_select_model)
        wx.EVT_COMBOBOX(self.multifactorbox, wx.ID_ANY, self._on_select_model)
        # check model type to show sizer
        if self.model is not None:
            print("_set_model_sizer_selection: disabled.")
            # self._set_model_sizer_selection(self.model)

        sizer_selection.Add(self.text1)
        sizer_selection.Add((10, 5))
        sizer_selection.Add(self.formfactorbox)
        sizer_selection.Add((5, 5))
        sizer_selection.Add(self.text2)
        sizer_selection.Add((5, 5))
        sizer_selection.Add(self.structurebox)

        mutifactor_selection.Add((13, 5))
        mutifactor_selection.Add(self.mutifactor_text)
        mutifactor_selection.Add(self.multifactorbox)
        mutifactor_selection.Add((5, 5))
        mutifactor_selection.Add(self.mutifactor_text1)
        mutifactor_selection.Add((10, 5))
        mutifactor_selection.Add(self.show_sld_button)

        boxsizer1.Add(sizer_cat)
        boxsizer1.Add((10, 10))
        boxsizer1.Add(sizer_selection)
        boxsizer1.Add((10, 10))
        boxsizer1.Add(mutifactor_selection)

        self._set_multfactor_combobox()
        self.multifactorbox.SetSelection(1)
        self.show_sld_button.Hide()
        sizer.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
        sizer.Layout()

    def on_smear_helper(self, update=False):
        """
        Help for onSmear if implemented

        :param update: force or not to update
        """
    def reset_page(self, state, first=False):
        """
        reset the state  if implemented
        """
    def onSmear(self, event):
        """
        Create a smear object if implemented
        """
    def onPinholeSmear(self, event):
        """
        Create a custom pinhole smear object if implemented
        """
    def onSlitSmear(self, event):
        """
        Create a custom slit smear object if implemented
        """
    def update_slit_smear(self):
        """
        called by kill_focus on pinhole TextCntrl
        to update the changes if implemented
        """
    def select_param(self, event):
        """
        Select TextCtrl  checked if implemented
        """
    def set_data(self, data=None):
        """
        Sets data if implemented
        """
    def _is_2D(self):
        """
        Check if data_name is Data2D if implemented
        """
    def _on_select_model(self, event=None):
        """
        call back for model selection if implemented
        """
    def get_weight_flag(self):
        """
        Get flag corresponding to a given weighting dI data if implemented
        """
    def _set_sizer_dispersion(self):
        """
        draw sizer for dispersity if implemented
        """
    def get_all_checked_params(self):
        """
        Found all parameters current check and add them to list of parameters
        to fit if implemented
        """
    def show_npts2fit(self):
        """
        setValue Npts for fitting if implemented
        """
    def _onModel2D(self, event):
        """
        toggle view of model from 1D to 2D  or 2D from 1D if implemented
        """


class ModelTextCtrl(wx.TextCtrl):
    """
    Text control for model and fit parameters.
    Binds the appropriate events for user interactions.
    Default callback methods can be overwritten on initialization

    :param kill_focus_callback: callback method for EVT_KILL_FOCUS event
    :param set_focus_callback:  callback method for EVT_SET_FOCUS event
    :param mouse_up_callback:   callback method for EVT_LEFT_UP event
    :param text_enter_callback: callback method for EVT_TEXT_ENTER event

    """
    # Set to True when the mouse is clicked while whole string is selected
    full_selection = False
    # Call back for EVT_SET_FOCUS events
    _on_set_focus_callback = None

    def __init__(self, parent, id=-1,
                 value=wx.EmptyString,
                 pos=wx.DefaultPosition,
                 size=wx.DefaultSize,
                 style=0,
                 validator=wx.DefaultValidator,
                 name=wx.TextCtrlNameStr,
                 kill_focus_callback=None,
                 set_focus_callback=None,
                 mouse_up_callback=None,
                 text_enter_callback=None):

        wx.TextCtrl.__init__(self, parent, id, value, pos,
                             size, style, validator, name)

        # Bind appropriate events
        self._on_set_focus_callback = parent.onSetFocus \
            if set_focus_callback is None else set_focus_callback
        self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
        self.Bind(wx.EVT_KILL_FOCUS, self._silent_kill_focus
                  if kill_focus_callback is None else kill_focus_callback)
        self.Bind(wx.EVT_TEXT_ENTER, parent._onparamEnter
                  if text_enter_callback is None else text_enter_callback)
        if not ON_MAC:
            self.Bind(wx.EVT_LEFT_UP, self._highlight_text
                      if mouse_up_callback is None else mouse_up_callback)

    def _on_set_focus(self, event):
        """
        Catch when the text control is set in focus to highlight the whole
        text if necessary

        :param event: mouse event

        """
        event.Skip()
        self.full_selection = True
        return self._on_set_focus_callback(event)

    def _highlight_text(self, event):
        """
        Highlight text of a TextCtrl only of no text has be selected

        :param event: mouse event

        """
        # Make sure the mouse event is available to other listeners
        event.Skip()
        control = event.GetEventObject()
        if self.full_selection:
            self.full_selection = False
            # Check that we have a TextCtrl
            if issubclass(control.__class__, wx.TextCtrl):
                # Check whether text has been selected,
                # if not, select the whole string
                (start, end) = control.GetSelection()
                if start == end:
                    control.SetSelection(-1, -1)

    def _silent_kill_focus(self, event):
        """
        Save the state of the page
        """

        event.Skip()
        # pass