source: sasview/src/sas/sasgui/perspectives/fitting/basepage.py @ c08756f

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since c08756f was b301db9, checked in by krzywon, 8 years ago

Correct the calculation for dx_percent.

  • Property mode set to 100644
File size: 143.0 KB
RevLine 
[f0d720b]1"""
2Base Page for fitting
3"""
4import sys
5import os
6import wx
7import numpy
8import time
9import copy
10import math
11import json
[5ce7f17]12import logging
[7673ecd]13import traceback
14
[f0d720b]15from collections import defaultdict
16from wx.lib.scrolledpanel import ScrolledPanel
[7673ecd]17
[a0373d5]18from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
19
[d85c194]20from sas.sasgui.guiframe.panel_base import PanelBase
[c8e1996]21from sas.sasgui.guiframe.utils import format_number, check_float, IdList, \
22    check_int
[d85c194]23from sas.sasgui.guiframe.events import PanelOnFocusEvent
24from sas.sasgui.guiframe.events import StatusEvent
25from sas.sasgui.guiframe.events import AppendBookmarkEvent
26from sas.sasgui.guiframe.dataFitting import Data2D
27from sas.sasgui.guiframe.dataFitting import Data1D
28from sas.sasgui.guiframe.dataFitting import check_data_validity
29from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
[b699768]30from sas.sascalc.dataloader.data_info import Detector
31from sas.sascalc.dataloader.data_info import Source
[d85c194]32from sas.sasgui.perspectives.fitting.pagestate import PageState
33from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
34from sas.sasgui.guiframe.documentation_window import DocumentationWindow
[5ce7f17]35
[f0d720b]36
37(PageInfoEvent, EVT_PAGE_INFO) = wx.lib.newevent.NewEvent()
38(PreviousStateEvent, EVT_PREVIOUS_STATE) = wx.lib.newevent.NewEvent()
39(NextStateEvent, EVT_NEXT_STATE) = wx.lib.newevent.NewEvent()
40
41_BOX_WIDTH = 76
42_QMIN_DEFAULT = 0.0005
43_QMAX_DEFAULT = 0.5
44_NPTS_DEFAULT = 50
[c8e1996]45# Control panel width
[f0d720b]46if sys.platform.count("win32") > 0:
47    PANEL_WIDTH = 450
48    FONT_VARIANT = 0
49    ON_MAC = False
50else:
51    PANEL_WIDTH = 500
52    FONT_VARIANT = 1
53    ON_MAC = True
54
[4387385]55CUSTOM_MODEL = 'Plugin Models'
56
[f0d720b]57class BasicPage(ScrolledPanel, PanelBase):
58    """
[e92a352]59    This class provide general structure of the fitpanel page
[f0d720b]60    """
[c8e1996]61    # Internal name for the AUI manager
[f0d720b]62    window_name = "Fit Page"
[c8e1996]63    # Title to appear on top of the window
[f0d720b]64    window_caption = "Fit Page "
[02098e3]65
[6f16e25]66    # These two buttons have specific IDs since they seem to be created more
67    # frequently than they need to.  In particular, set_dispers_sizer() is
68    # called by _on_select_model
69    ID_BOOKMARK = wx.NewId()
70    ID_DISPERSER_HELP = wx.NewId()
71    _id_pool = IdList()
[5ce7f17]72
[f0d720b]73    def __init__(self, parent, color='blue', **kwargs):
74        """
75        """
76        ScrolledPanel.__init__(self, parent, **kwargs)
77        PanelBase.__init__(self, parent)
78        self.SetupScrolling()
[c8e1996]79        # Set window's font size
[f0d720b]80        self.SetWindowVariant(variant=FONT_VARIANT)
81        self.SetBackgroundColour(color)
[6f16e25]82
83        self._ids = iter(self._id_pool)
[c8e1996]84        # parent of the page
[f0d720b]85        self.parent = parent
[c8e1996]86        # manager is the fitting plugin
87        # owner of the page (fitting plugin)
[f0d720b]88        self.event_owner = None
[c8e1996]89        # current model
[f0d720b]90        self.model = None
91        self.m_name = None
92        self.index_model = None
93        self.panel = None
[c8e1996]94        # data
[f0d720b]95        self.data = None
[c8e1996]96        # list of available data
[f0d720b]97        self.data_list = []
98        self.mask = None
99        self.uid = wx.NewId()
100        self.graph_id = None
[c8e1996]101        # Q range for data set
[f0d720b]102        self.qmin_data_set = numpy.inf
103        self.qmax_data_set = None
104        self.npts_data_set = 0
[c8e1996]105        # Q range
[f0d720b]106        self.qmin = None
107        self.qmax = None
108        self.qmax_x = _QMAX_DEFAULT
109        self.qmin_x = _QMIN_DEFAULT
110        self.npts_x = _NPTS_DEFAULT
[c8e1996]111        # total number of point: float
[f0d720b]112        self.npts = None
113        self.num_points = None
[c8e1996]114        # smear default
[f0d720b]115        self.current_smearer = None
[c8e1996]116        # 2D smear accuracy default
[f0d720b]117        self.smear2d_accuracy = 'Low'
[c8e1996]118        # slit smear:
[f0d720b]119        self.dxl = None
120        self.dxw = None
[c8e1996]121        # pinhole smear
[7e98655]122        self.dx_percent = None
[c8e1996]123        # smear attrbs
[f0d720b]124        self.enable_smearer = None
125        self.disable_smearer = None
126        self.pinhole_smearer = None
127        self.slit_smearer = None
[c8e1996]128        # weight attrbs
[f0d720b]129        self.dI_noweight = None
130        self.dI_didata = None
131        self.dI_sqrdata = None
132        self.dI_idata = None
[c8e1996]133        # other attrbs
[f0d720b]134        self.dq_l = None
135        self.dq_r = None
136        self.tcChi = None
137        self.disp_box = None
138        self.model_disp = None
139        self.Npts_fit = None
140        self.Npts_total = None
[5ce7f17]141        self.theory_qmin = None
[f0d720b]142        self.theory_qmax = None
143        self.theory_qmin_x = None
144        self.theory_qmax_x = None
145        self.btEditMask = None
146        self.btFit = None
147        self.sld_axes = None
148        self.multi_factor = None
[5ce7f17]149
[f0d720b]150        self.disp_cb_dict = {}
[5ce7f17]151
[c8e1996]152        # self.state = PageState(parent=parent)
153        # dictionary containing list of models
[f0d720b]154        self.model_list_box = {}
[5ce7f17]155
[c8e1996]156        # Data member to store the dispersion object created
[f0d720b]157        self._disp_obj_dict = {}
[c8e1996]158        # selected parameters to apply dispersion
[f0d720b]159        self.disp_cb_dict = {}
[c8e1996]160        # smearer object
[f0d720b]161        self.enable2D = False
162        self._has_magnetic = False
163        self.magnetic_on = False
164        self.is_mac = ON_MAC
165        self.formfactorbox = None
166        self.structurebox = None
167        self.categorybox = None
[c8e1996]168        # list of model parameters. each item must have same length
169        # each item related to a given parameters
170        # [cb state, name, value, "+/-", error of fit, min, max , units]
[f0d720b]171        self.parameters = []
172        # non-fittable parameter whose value is astring
173        self.str_parameters = []
[c8e1996]174        # list of parameters to fit , must be like self.parameters
[f0d720b]175        self.param_toFit = []
[c8e1996]176        # list of looking like parameters but with non fittable parameters info
[f0d720b]177        self.fixed_param = []
[c8e1996]178        # list of looking like parameters but with  fittable parameters info
[f0d720b]179        self.fittable_param = []
[c8e1996]180        # list of dispersion parameters
[f0d720b]181        self.disp_list = []
182        self.disp_name = ""
[5ce7f17]183
[c8e1996]184        # list of orientation parameters
[f0d720b]185        self.orientation_params = []
186        self.orientation_params_disp = []
[5ce7f17]187#       Self.model should ALWAYS be None here.  It was set to none above in
[f0d720b]188#       this long init setting.  no obvious function call in between setting
[5ce7f17]189#       and this - commenting out on 4/8/2014 by PDB.  Remove once clear
[f0d720b]190#       it is pointless.
[c8e1996]191#        if self.model is not None:
[f0d720b]192#            self.disp_list = self.model.getDispParamList()
193        self.temp_multi_functional = False
[c8e1996]194        # enable model 2D draw
[f0d720b]195        self.enable2D = False
[c8e1996]196        # check that the fit range is correct to plot the model again
[f0d720b]197        self.fitrange = True
[c8e1996]198        # Create memento to save the current state
[f0d720b]199        self.state = PageState(parent=self.parent,
200                               model=self.model, data=self.data)
[c8e1996]201        # flag to determine if state has change
[f0d720b]202        self.state_change = False
[c8e1996]203        # save customized array
[6ed67db]204        self.values = {}   # type: Dict[str, List[float, ...]]
205        self.weights = {}   # type: Dict[str, List[float, ...]]
[c8e1996]206        # retrieve saved state
[f0d720b]207        self.number_saved_state = 0
[c8e1996]208        # dictionary of saved state
[f0d720b]209        self.saved_states = {}
[c8e1996]210        # Create context menu for page
[f0d720b]211        self.popUpMenu = wx.Menu()
[5ce7f17]212
[6f16e25]213        wx_id = self._ids.next()
214        self._keep = wx.MenuItem(self.popUpMenu, wx_id, "Add bookmark",
[f0d720b]215                                 " Keep the panel status to recall it later")
216        self.popUpMenu.AppendItem(self._keep)
217        self._keep.Enable(False)
218        self._set_bookmark_flag(False)
219        self._set_save_flag(False)
[6f16e25]220        wx.EVT_MENU(self, wx_id, self.on_bookmark)
[f0d720b]221        self.popUpMenu.AppendSeparator()
[5ce7f17]222
[c8e1996]223        # Default locations
[f0d720b]224        self._default_save_location = os.getcwd()
[c8e1996]225        # save initial state on context menu
226        # self.onSave(event=None)
[f0d720b]227        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
[5ce7f17]228
[f0d720b]229        # bind key event
230        self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
[5ce7f17]231
[c8e1996]232        # create the basic structure of the panel with empty sizer
[f0d720b]233        self.define_page_structure()
[c8e1996]234        # drawing Initial dispersion parameters sizer
[f0d720b]235        self.set_dispers_sizer()
[5ce7f17]236
[c8e1996]237        # layout
[f0d720b]238        self.set_layout()
[5ce7f17]239
[f0d720b]240    def set_index_model(self, index):
241        """
242        Index related to this page
243        """
244        self.index_model = index
[5ce7f17]245
[f0d720b]246    def create_default_data(self):
247        """
248        Given the user selection, creates a 1D or 2D data
249        Only when the page is on theory mode.
250        """
251        if not hasattr(self, "model_view"):
252            return
253        toggle_mode_on = self.model_view.IsEnabled()
254        if toggle_mode_on:
255            if self.enable2D and not check_data_validity(self.data):
256                self._create_default_2d_data()
257            else:
258                if self.pointsbox.GetValue():
259                    self._create_log_1d_data()
260                else:
261                    self._create_default_1d_data()
[5ce7f17]262
[c8e1996]263            if self.model is not None:
[f0d720b]264                if not self.data.is_data:
[c8e1996]265                    self._manager.page_finder[self.uid].set_fit_data(
266                        data=[self.data])
[f0d720b]267            self.on_smear_helper(update=True)
268            self.state.enable_smearer = self.enable_smearer.GetValue()
269            self.state.disable_smearer = self.disable_smearer.GetValue()
270            self.state.pinhole_smearer = self.pinhole_smearer.GetValue()
271            self.state.slit_smearer = self.slit_smearer.GetValue()
[5ce7f17]272
[f0d720b]273    def _create_default_1d_data(self):
274        """
275        Create default data for fitting perspective
276        Only when the page is on theory mode.
277        :warning: This data is never plotted.
[5ce7f17]278
[f0d720b]279        """
280        x = numpy.linspace(start=self.qmin_x, stop=self.qmax_x,
281                           num=self.npts_x, endpoint=True)
282        self.data = Data1D(x=x)
283        self.data.xaxis('\\rm{Q}', "A^{-1}")
284        self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
285        self.data.is_data = False
286        self.data.id = str(self.uid) + " data"
287        self.data.group_id = str(self.uid) + " Model1D"
[5ce7f17]288
[f0d720b]289    def _create_log_1d_data(self):
290        """
291        Create log-spaced data for fitting perspective
292        Only when the page is on theory mode.
293        :warning: This data is never plotted.
[5ce7f17]294
[f0d720b]295        """
296        if self.qmin_x >= 1.e-10:
297            qmin = numpy.log10(self.qmin_x)
298        else:
[5ce7f17]299            qmin = -10.
300
[f0d720b]301        if self.qmax_x <= 1.e10:
302            qmax = numpy.log10(self.qmax_x)
303        else:
[5ce7f17]304            qmax = 10.
305
[f0d720b]306        x = numpy.logspace(start=qmin, stop=qmax,
307                           num=self.npts_x, endpoint=True, base=10.0)
308        self.data = Data1D(x=x)
309        self.data.xaxis('\\rm{Q}', "A^{-1}")
310        self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
311        self.data.is_data = False
312        self.data.id = str(self.uid) + " data"
313        self.data.group_id = str(self.uid) + " Model1D"
[5ce7f17]314
[f0d720b]315    def _create_default_2d_data(self):
316        """
317        Create 2D data by default
318        Only when the page is on theory mode.
319        :warning: This data is never plotted.
320        """
321        self.data = Data2D()
322        qmax = self.qmax_x / math.sqrt(2)
323        self.data.xaxis('\\rm{Q_{x}}', 'A^{-1}')
324        self.data.yaxis('\\rm{Q_{y}}', 'A^{-1}')
325        self.data.is_data = False
326        self.data.id = str(self.uid) + " data"
327        self.data.group_id = str(self.uid) + " Model2D"
[c8e1996]328        # Default values
[f0d720b]329        self.data.detector.append(Detector())
330        index = len(self.data.detector) - 1
331        self.data.detector[index].distance = 8000   # mm
332        self.data.source.wavelength = 6             # A
333        self.data.detector[index].pixel_size.x = 5  # mm
334        self.data.detector[index].pixel_size.y = 5  # mm
335        self.data.detector[index].beam_center.x = qmax
336        self.data.detector[index].beam_center.y = qmax
337        xmax = qmax
338        xmin = -qmax
339        ymax = qmax
340        ymin = -qmax
341        qstep = self.npts_x
342
343        x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
344        y = numpy.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True)
[c8e1996]345        # use data info instead
[f0d720b]346        new_x = numpy.tile(x, (len(y), 1))
347        new_y = numpy.tile(y, (len(x), 1))
348        new_y = new_y.swapaxes(0, 1)
349        # all data reuire now in 1d array
350        qx_data = new_x.flatten()
351        qy_data = new_y.flatten()
352        q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
353        # set all True (standing for unmasked) as default
354        mask = numpy.ones(len(qx_data), dtype=bool)
355        # store x and y bin centers in q space
356        x_bins = x
357        y_bins = y
[5ce7f17]358
[f0d720b]359        self.data.source = Source()
360        self.data.data = numpy.ones(len(mask))
361        self.data.err_data = numpy.ones(len(mask))
362        self.data.qx_data = qx_data
363        self.data.qy_data = qy_data
364        self.data.q_data = q_data
365        self.data.mask = mask
366        self.data.x_bins = x_bins
367        self.data.y_bins = y_bins
368        # max and min taking account of the bin sizes
369        self.data.xmin = xmin
370        self.data.xmax = xmax
371        self.data.ymin = ymin
372        self.data.ymax = ymax
373
374    def on_set_focus(self, event):
375        """
376        On Set Focus, update guimanger and menu
377        """
378        if self._manager is not None:
379            wx.PostEvent(self._manager.parent, PanelOnFocusEvent(panel=self))
380            self.on_tap_focus()
[5ce7f17]381
[f0d720b]382    def on_tap_focus(self):
383        """
384        Update menu1 on cliking the page tap
385        """
[c8e1996]386        if self._manager.menu1 is not None:
387            chain_menu = self._manager.menu1.FindItemById(
[f0d720b]388                                                   self._manager.id_reset_flag)
389            chain_menu.Enable(self.batch_on)
390            sim_menu = self._manager.menu1.FindItemById(self._manager.id_simfit)
391            flag = self.data.is_data\
[c8e1996]392                            and (self.model is not None)
[f0d720b]393            sim_menu.Enable(not self.batch_on and flag)
394            batch_menu = \
395                    self._manager.menu1.FindItemById(self._manager.id_batchfit)
396            batch_menu.Enable(self.batch_on and flag)
[5ce7f17]397
[f0d720b]398    def onContextMenu(self, event):
399        """
400        Retrieve the state selected state
401        """
402        pos = event.GetPosition()
403        pos = self.ScreenToClient(pos)
404        self.PopupMenu(self.popUpMenu, pos)
[5ce7f17]405
[f0d720b]406    def onUndo(self, event):
407        """
408        Cancel the previous action
409        """
410        event = PreviousStateEvent(page=self)
411        wx.PostEvent(self.parent, event)
[5ce7f17]412
[f0d720b]413    def onRedo(self, event):
414        """
415        Restore the previous action cancelled
416        """
417        event = NextStateEvent(page=self)
418        wx.PostEvent(self.parent, event)
[5ce7f17]419
[f0d720b]420    def define_page_structure(self):
421        """
422        Create empty sizer for a panel
423        """
424        self.vbox = wx.BoxSizer(wx.VERTICAL)
425        self.sizer0 = wx.BoxSizer(wx.VERTICAL)
426        self.sizer1 = wx.BoxSizer(wx.VERTICAL)
427        self.sizer2 = wx.BoxSizer(wx.VERTICAL)
428        self.sizer3 = wx.BoxSizer(wx.VERTICAL)
429        self.sizer4 = wx.BoxSizer(wx.VERTICAL)
430        self.sizer5 = wx.BoxSizer(wx.VERTICAL)
431        self.sizer6 = wx.BoxSizer(wx.VERTICAL)
[5ce7f17]432
[f0d720b]433        self.sizer0.SetMinSize((PANEL_WIDTH, -1))
434        self.sizer1.SetMinSize((PANEL_WIDTH, -1))
435        self.sizer2.SetMinSize((PANEL_WIDTH, -1))
436        self.sizer3.SetMinSize((PANEL_WIDTH, -1))
437        self.sizer4.SetMinSize((PANEL_WIDTH, -1))
438        self.sizer5.SetMinSize((PANEL_WIDTH, -1))
439        self.sizer6.SetMinSize((PANEL_WIDTH, -1))
[5ce7f17]440
[f0d720b]441        self.vbox.Add(self.sizer0)
442        self.vbox.Add(self.sizer1)
443        self.vbox.Add(self.sizer2)
444        self.vbox.Add(self.sizer3)
445        self.vbox.Add(self.sizer4)
446        self.vbox.Add(self.sizer5)
447        self.vbox.Add(self.sizer6)
[5ce7f17]448
[f0d720b]449    def set_layout(self):
450        """
451        layout
452        """
453        self.vbox.Layout()
454        self.vbox.Fit(self)
455        self.SetSizer(self.vbox)
456        self.Centre()
[5ce7f17]457
[f0d720b]458    def set_owner(self, owner):
459        """
460        set owner of fitpage
[5ce7f17]461
[f0d720b]462        :param owner: the class responsible of plotting
[5ce7f17]463
[f0d720b]464        """
465        self.event_owner = owner
466        self.state.event_owner = owner
[5ce7f17]467
[f0d720b]468    def get_state(self):
469        """
[5ce7f17]470        return the current page state
[f0d720b]471        """
472        return self.state
[5ce7f17]473
[f0d720b]474    def get_data(self):
475        """
476        return the current data
477        """
478        return self.data
[5ce7f17]479
[f0d720b]480    def get_data_list(self):
481        """
482        return the current data
483        """
484        return self.data_list
[5ce7f17]485
[f0d720b]486    def set_manager(self, manager):
487        """
488        set panel manager
[5ce7f17]489
[f0d720b]490        :param manager: instance of plugin fitting
[5ce7f17]491
[f0d720b]492        """
493        self._manager = manager
494        self.state.manager = manager
[5ce7f17]495
[f0d720b]496    def populate_box(self, model_dict):
497        """
498        Store list of model
[5ce7f17]499
[f0d720b]500        :param model_dict: dictionary containing list of models
[5ce7f17]501
[f0d720b]502        """
503        self.model_list_box = model_dict
504        self.state.model_list_box = self.model_list_box
505        self.initialize_combox()
[5ce7f17]506
[f0d720b]507    def set_model_dictionary(self, model_dict):
508        """
509        Store a dictionary linking model name -> model object
510
511        :param model_dict: dictionary containing list of models
512        """
513        self.model_dict = model_dict
514
515    def initialize_combox(self):
516        """
[e28f34d]517        put default value in the combo box
[5ce7f17]518        """
[e28f34d]519        if self.model_list_box is not None and len(self.model_list_box) > 0:
[f0d720b]520            self._populate_box(self.structurebox,
[5ce7f17]521                               self.model_list_box["Structure Factors"])
[f0d720b]522            self.structurebox.Insert("None", 0, None)
523            self.structurebox.SetSelection(0)
524            self.structurebox.Hide()
525            self.text2.Hide()
526            self.structurebox.Disable()
527            self.text2.Disable()
[5ce7f17]528
[f0d720b]529    def set_dispers_sizer(self):
530        """
531        fill sizer containing dispersity info
532        """
[c8e1996]533        # print "==== entering set_dispers_sizer ==="
[f0d720b]534        self.sizer4.Clear(True)
535        name = "Polydispersity and Orientational Distribution"
[6f16e25]536        box_description = wx.StaticBox(self, wx.ID_ANY, name)
[f0d720b]537        box_description.SetForegroundColour(wx.BLUE)
538        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
[c8e1996]539        # ----------------------------------------------------
[6f16e25]540        self.disable_disp = wx.RadioButton(self, wx.ID_ANY, 'Off', (10, 10),
[5ce7f17]541                                           style=wx.RB_GROUP)
[6f16e25]542        self.enable_disp = wx.RadioButton(self, wx.ID_ANY, 'On', (10, 30))
[f0d720b]543        # best size for MAC and PC
544        if ON_MAC:
545            size_q = (30, 20)
546        else:
547            size_q = (20, 15)
[6f16e25]548        self.disp_help_bt = wx.Button(self, self.ID_DISPERSER_HELP, '?',
[f0d720b]549                                      style=wx.BU_EXACTFIT,
550                                      size=size_q)
[5ce7f17]551        self.disp_help_bt.Bind(wx.EVT_BUTTON, self.on_pd_help_clicked,
552                               id=self.disp_help_bt.GetId())
[a0373d5]553        self.disp_help_bt.SetToolTipString("Help for polydispersion.")
[5ce7f17]554
[f0d720b]555        self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
[5ce7f17]556                  id=self.disable_disp.GetId())
[f0d720b]557        self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
[5ce7f17]558                  id=self.enable_disp.GetId())
[c8e1996]559        # MAC needs SetValue
[f0d720b]560        self.disable_disp.SetValue(True)
561        sizer_dispersion = wx.BoxSizer(wx.HORIZONTAL)
562        sizer_dispersion.Add((20, 20))
563        name = ""  # Polydispersity and \nOrientational Distribution "
[6f16e25]564        sizer_dispersion.Add(wx.StaticText(self, wx.ID_ANY, name))
[f0d720b]565        sizer_dispersion.Add(self.enable_disp)
566        sizer_dispersion.Add((20, 20))
567        sizer_dispersion.Add(self.disable_disp)
568        sizer_dispersion.Add((25, 20))
569        sizer_dispersion.Add(self.disp_help_bt)
[5ce7f17]570
[c8e1996]571        # fill a sizer for dispersion
[f0d720b]572        boxsizer1.Add(sizer_dispersion, 0,
[5ce7f17]573                      wx.TOP|wx.BOTTOM|wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE,
574                      border=5)
[f0d720b]575        self.sizer4_4 = wx.GridBagSizer(6, 5)
576
577        boxsizer1.Add(self.sizer4_4)
[c8e1996]578        # -----------------------------------------------------
[f0d720b]579        self.sizer4.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
580        self.sizer4_4.Layout()
581        self.sizer4.Layout()
582        self.Layout()
[5ce7f17]583
[f0d720b]584        self.Refresh()
[c8e1996]585        # saving the state of enable dispersity button
[f0d720b]586        self.state.enable_disp = self.enable_disp.GetValue()
587        self.state.disable_disp = self.disable_disp.GetValue()
588        self.SetupScrolling()
[5ce7f17]589
[f0d720b]590    def onResetModel(self, event):
591        """
592        Reset model state
593        """
594        menu = event.GetEventObject()
[c8e1996]595        # post help message for the selected model
[f0d720b]596        msg = menu.GetHelpString(event.GetId())
597        msg += " reloaded"
598        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
599        self.Show(False)
600        name = menu.GetLabel(event.GetId())
601        self._on_select_model_helper()
[c8e1996]602        if self.model is not None:
[f0d720b]603            self.m_name = self.model.name
604        if name in self.saved_states.keys():
605            previous_state = self.saved_states[name]
[c8e1996]606            # reset state of checkbox,textcrtl  and  regular parameters value
[5ce7f17]607
[f0d720b]608            self.reset_page(previous_state)
609        self.state.m_name = self.m_name
610        self.Show(True)
[5ce7f17]611
[f0d720b]612    def on_preview(self, event):
613        """
614        Report the current fit results
615        """
616        # Get plot image from plotpanel
617        images, canvases = self.get_images()
618        # get the report dialog
619        self.state.report(images, canvases)
[5ce7f17]620
[f0d720b]621    def on_save(self, event):
622        """
623        Save the current state into file
624        """
625        self.save_current_state()
626        new_state = self.state.clone()
627        # Ask the user the location of the file to write to.
628        path = None
[c8e1996]629        if self.parent is not None:
[f0d720b]630            self._default_save_location = \
631                        self._manager.parent._default_save_location
632        dlg = wx.FileDialog(self, "Choose a file", self._default_save_location,
[5ce7f17]633                            self.window_caption, "*.fitv", wx.SAVE)
[f0d720b]634
635        if dlg.ShowModal() == wx.ID_OK:
636            path = dlg.GetPath()
637            self._default_save_location = os.path.dirname(path)
[5ce7f17]638            self._manager.parent._default_save_location = \
[c8e1996]639                self._default_save_location
[f0d720b]640        else:
641            return None
642        # MAC always needs the extension for saving
643        extens = ".fitv"
644        # Make sure the ext included in the file name
645        fName = os.path.splitext(path)[0] + extens
[c8e1996]646        # the manager write the state into file
[f0d720b]647        self._manager.save_fit_state(filepath=fName, fitstate=new_state)
648        return new_state
[5ce7f17]649
[f0d720b]650    def on_copy(self, event):
651        """
652        Copy Parameter values to the clipboad
653        """
[c8e1996]654        if event is not None:
[f0d720b]655            event.Skip()
656        # It seems MAC needs wxCallAfter
657        if event.GetId() == GUIFRAME_ID.COPYEX_ID:
658            print "copy excel"
659            wx.CallAfter(self.get_copy_excel)
660        elif event.GetId() == GUIFRAME_ID.COPYLAT_ID:
661            print "copy latex"
662            wx.CallAfter(self.get_copy_latex)
663        else:
664            wx.CallAfter(self.get_copy)
665
666    def on_paste(self, event):
667        """
668        Paste Parameter values to the panel if possible
669        """
[c8e1996]670        # if event is not None:
[f0d720b]671        #    event.Skip()
672        # It seems MAC needs wxCallAfter for the setvalues
673        # for multiple textctrl items, otherwise it tends to crash once a while
674        wx.CallAfter(self.get_paste)
675        # messages depending on the flag
[c8e1996]676        # self._copy_info(True)
[5ce7f17]677
[f0d720b]678    def _copy_info(self, flag):
679        """
[e92a352]680        Send event depending on flag
[5ce7f17]681
[e92a352]682        : Param flag: flag that distinguishes the event
[f0d720b]683        """
684        # messages depending on the flag
[c8e1996]685        if flag is None:
[f0d720b]686            msg = " Parameter values are copied to the clipboard..."
687            infor = 'warning'
688        elif flag:
689            msg = " Parameter values are pasted from the clipboard..."
690            infor = "warning"
691        else:
692            msg = "Error occurred: "
693            msg += "No valid parameter values to paste from the clipboard..."
694            infor = "warning"
695        # inform msg to wx
696        wx.PostEvent(self._manager.parent,
[5ce7f17]697                     StatusEvent(status=msg, info=infor))
698
[f0d720b]699    def _get_time_stamp(self):
700        """
701        return time and date stings
702        """
703        # date and time
704        year, month, day, hour, minute, second, _, _, _ = time.localtime()
705        current_time = str(hour) + ":" + str(minute) + ":" + str(second)
706        current_date = str(month) + "/" + str(day) + "/" + str(year)
707        return current_time, current_date
[5ce7f17]708
[f0d720b]709    def on_bookmark(self, event):
710        """
711        save history of the data and model
712        """
[c8e1996]713        if self.model is None:
[f0d720b]714            msg = "Can not bookmark; Please select Data and Model first..."
715            wx.MessageBox(msg, 'Info')
716            return
717        self.save_current_state()
718        new_state = self.state.clone()
[c8e1996]719        # Add model state on context menu
[f0d720b]720        self.number_saved_state += 1
721        current_time, current_date = self._get_time_stamp()
[c8e1996]722        # name= self.model.name+"[%g]"%self.number_saved_state
[f0d720b]723        name = "Fitting: %g]" % self.number_saved_state
724        name += self.model.__class__.__name__
725        name += "bookmarked at %s on %s" % (current_time, current_date)
726        self.saved_states[name] = new_state
[5ce7f17]727
[c8e1996]728        # Add item in the context menu
[f0d720b]729        msg = "Model saved at %s on %s" % (current_time, current_date)
[c8e1996]730        # post help message for the selected model
[f0d720b]731        msg += " Saved! right click on this page to retrieve this model"
732        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
[5ce7f17]733
[6f16e25]734        self.popUpMenu.Append(self.ID_BOOKMARK, name, str(msg))
735        wx.EVT_MENU(self, self.ID_BOOKMARK, self.onResetModel)
[f0d720b]736        wx.PostEvent(self._manager.parent,
737                     AppendBookmarkEvent(title=name,
738                                         hint=str(msg),
739                                         handler=self._back_to_bookmark))
[5ce7f17]740
[f0d720b]741    def _back_to_bookmark(self, event):
742        """
743        Back to bookmark
744        """
745        self._manager.on_perspective(event)
746        self.onResetModel(event)
747        self._draw_model()
[5ce7f17]748
[f0d720b]749    def onSetFocus(self, evt):
750        """
751        highlight the current textcrtl and hide the error text control shown
752        after fitting
753        """
754        return
[5ce7f17]755
[f0d720b]756    def read_file(self, path):
757        """
758        Read two columns file
[5ce7f17]759
[f0d720b]760        :param path: the path to the file to read
[5ce7f17]761
[f0d720b]762        """
763        try:
[c8e1996]764            if path is None:
[5ce7f17]765                status = " Selected Distribution was not loaded: %s" % path
[f0d720b]766                wx.PostEvent(self._manager.parent,
[5ce7f17]767                             StatusEvent(status=status))
[f0d720b]768                return None, None
769            input_f = open(path, 'r')
770            buff = input_f.read()
771            lines = buff.split('\n')
772            input_f.close()
773            angles = []
774            weights = []
775            for line in lines:
776                toks = line.split()
777                try:
778                    angle = float(toks[0])
779                    weight = float(toks[1])
780                    angles.append(angle)
781                    weights.append(weight)
[7673ecd]782                except Exception:
[f0d720b]783                    # Skip non-data lines
[7673ecd]784                    logging.error(traceback.format_exc())
[f0d720b]785            return numpy.array(angles), numpy.array(weights)
786        except:
787            raise
788
789    def createMemento(self):
790        """
791        return the current state of the page
792        """
793        return self.state.clone()
[5ce7f17]794
[f0d720b]795    def save_current_state(self):
796        """
797        Store current state
798        """
[c8e1996]799        # save model option
800        if self.model is not None:
[f0d720b]801            self.disp_list = self.model.getDispParamList()
802            self.state.disp_list = copy.deepcopy(self.disp_list)
803            self.state.model = self.model.clone()
[5ce7f17]804
[c8e1996]805            # model combobox: complex code because of mac's silent error
806            if self.structurebox is not None:
[f0d720b]807                if self.structurebox.IsShown():
808                    self.state.structurecombobox = 'None'
809                    s_select = self.structurebox.GetSelection()
810                    if s_select > 0:
[c8e1996]811                        self.state.structurecombobox = \
812                            self.structurebox.GetString(s_select)
813            if self.formfactorbox is not None:
[f0d720b]814                f_select = self.formfactorbox.GetSelection()
815                if f_select > 0:
[c8e1996]816                    self.state.formfactorcombobox = \
817                        self.formfactorbox.GetString(f_select)
818        if self.categorybox is not None:
[f0d720b]819            cb_select = self.categorybox.GetSelection()
820            if cb_select > 0:
[c8e1996]821                self.state.categorycombobox = \
822                    self.categorybox.GetString(cb_select)
[5ce7f17]823
[f0d720b]824        self.state.enable2D = copy.deepcopy(self.enable2D)
825        self.state.values = copy.deepcopy(self.values)
826        self.state.weights = copy.deepcopy(self.weights)
[c8e1996]827        # save data
[f0d720b]828        self.state.data = copy.deepcopy(self.data)
829        self.state.qmax_x = self.qmax_x
830        self.state.qmin_x = self.qmin_x
831        self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
832        self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
833        self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
834        self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
835        self.state.dq_l = self.dq_l
836        self.state.dq_r = self.dq_r
837        if hasattr(self, "enable_disp"):
838            self.state.enable_disp = self.enable_disp.GetValue()
839            self.state.disable_disp = self.disable_disp.GetValue()
[5ce7f17]840
[f0d720b]841        self.state.smearer = copy.deepcopy(self.current_smearer)
842        if hasattr(self, "enable_smearer"):
843            self.state.enable_smearer = \
844                                copy.deepcopy(self.enable_smearer.GetValue())
845            self.state.disable_smearer = \
846                                copy.deepcopy(self.disable_smearer.GetValue())
847
848        self.state.pinhole_smearer = \
849                                copy.deepcopy(self.pinhole_smearer.GetValue())
[7e98655]850        self.state.dx_percent = copy.deepcopy(self.dx_percent)
[f0d720b]851        self.state.dxl = copy.deepcopy(self.dxl)
852        self.state.dxw = copy.deepcopy(self.dxw)
853        self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())
[5ce7f17]854
[f0d720b]855        if len(self._disp_obj_dict) > 0:
856            for k, v in self._disp_obj_dict.iteritems():
[6c382da]857                self.state._disp_obj_dict[k] = v.type
[f0d720b]858
859            self.state.values = copy.deepcopy(self.values)
860            self.state.weights = copy.deepcopy(self.weights)
[c8e1996]861        # save plotting range
[f0d720b]862        self._save_plotting_range()
[5ce7f17]863
[f0d720b]864        self.state.orientation_params = []
865        self.state.orientation_params_disp = []
866        self.state.parameters = []
867        self.state.fittable_param = []
868        self.state.fixed_param = []
869        self.state.str_parameters = []
870
[c8e1996]871        # save checkbutton state and txtcrtl values
[f0d720b]872        self._copy_parameters_state(self.str_parameters,
873                                    self.state.str_parameters)
874        self._copy_parameters_state(self.orientation_params,
875                                     self.state.orientation_params)
876        self._copy_parameters_state(self.orientation_params_disp,
[2abe6bf]877                                    self.state.orientation_params_disp)
[5ce7f17]878
[f0d720b]879        self._copy_parameters_state(self.parameters, self.state.parameters)
880        self._copy_parameters_state(self.fittable_param,
[2abe6bf]881                                    self.state.fittable_param)
[f0d720b]882        self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
[c8e1996]883        # save chisqr
[f0d720b]884        self.state.tcChi = self.tcChi.GetValue()
[5ce7f17]885
[f0d720b]886    def save_current_state_fit(self):
887        """
888        Store current state for fit_page
889        """
[c8e1996]890        # save model option
891        if self.model is not None:
[f0d720b]892            self.disp_list = self.model.getDispParamList()
893            self.state.disp_list = copy.deepcopy(self.disp_list)
894            self.state.model = self.model.clone()
[5ce7f17]895
[f0d720b]896        self.state.enable2D = copy.deepcopy(self.enable2D)
897        self.state.values = copy.deepcopy(self.values)
898        self.state.weights = copy.deepcopy(self.weights)
[c8e1996]899        # save data
[f0d720b]900        self.state.data = copy.deepcopy(self.data)
[5ce7f17]901
[f0d720b]902        if hasattr(self, "enable_disp"):
903            self.state.enable_disp = self.enable_disp.GetValue()
904            self.state.disable_disp = self.disable_disp.GetValue()
[5ce7f17]905
[f0d720b]906        self.state.smearer = copy.deepcopy(self.current_smearer)
907        if hasattr(self, "enable_smearer"):
908            self.state.enable_smearer = \
909                                copy.deepcopy(self.enable_smearer.GetValue())
910            self.state.disable_smearer = \
911                                copy.deepcopy(self.disable_smearer.GetValue())
[5ce7f17]912
[f0d720b]913        self.state.pinhole_smearer = \
914                                copy.deepcopy(self.pinhole_smearer.GetValue())
915        self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())
916        self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
917        self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
918        self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
919        self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
[c8e1996]920        if hasattr(self, "disp_box") and self.disp_box is not None:
[f0d720b]921            self.state.disp_box = self.disp_box.GetCurrentSelection()
922
923            if len(self.disp_cb_dict) > 0:
924                for k, v in self.disp_cb_dict.iteritems():
[c8e1996]925                    if v is None:
[f0d720b]926                        self.state.disp_cb_dict[k] = v
927                    else:
928                        try:
929                            self.state.disp_cb_dict[k] = v.GetValue()
930                        except:
931                            self.state.disp_cb_dict[k] = None
932            if len(self._disp_obj_dict) > 0:
933                for k, v in self._disp_obj_dict.iteritems():
[6c382da]934                    self.state._disp_obj_dict[k] = v.type
[5ce7f17]935
[f0d720b]936            self.state.values = copy.deepcopy(self.values)
937            self.state.weights = copy.deepcopy(self.weights)
[5ce7f17]938
[c8e1996]939        # save plotting range
[f0d720b]940        self._save_plotting_range()
[5ce7f17]941
[c8e1996]942        # save checkbutton state and txtcrtl values
[f0d720b]943        self._copy_parameters_state(self.orientation_params,
[5ce7f17]944                                    self.state.orientation_params)
[f0d720b]945        self._copy_parameters_state(self.orientation_params_disp,
[5ce7f17]946                                    self.state.orientation_params_disp)
[f0d720b]947        self._copy_parameters_state(self.parameters, self.state.parameters)
948        self._copy_parameters_state(self.fittable_param,
[5ce7f17]949                                    self.state.fittable_param)
[f0d720b]950        self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
[5ce7f17]951
[f0d720b]952    def check_invalid_panel(self):
953        """
954        check if the user can already perform some action with this panel
955        """
956        if self.data is None:
957            self.disable_smearer.SetValue(True)
958            self.disable_disp.SetValue(True)
959            msg = "Please load Data and select Model to start..."
960            wx.MessageBox(msg, 'Info')
[c8e1996]961            return True
[5ce7f17]962
[f0d720b]963    def set_model_state(self, state):
964        """
965        reset page given a model state
966        """
967        self.disp_cb_dict = state.disp_cb_dict
968        self.disp_list = state.disp_list
[5ce7f17]969
[c8e1996]970        # fill model combobox
[f0d720b]971        self._show_combox_helper()
[c8e1996]972        # select the current model
[f0d720b]973        try:
974            # to support older version
975            category_pos = int(state.categorycombobox)
976        except:
977            category_pos = 0
978            for ind_cat in range(self.categorybox.GetCount()):
[5ce7f17]979                if self.categorycombobox.GetString(ind_cat) == \
[f0d720b]980                                        state.categorycombobox:
981                    category_pos = int(ind_cat)
982                    break
[5ce7f17]983
[f0d720b]984        self.categorybox.Select(category_pos)
985        try:
986            # to support older version
987            formfactor_pos = int(state.formfactorcombobox)
988        except:
989            formfactor_pos = 0
990            for ind_form in range(self.formfactorbox.GetCount()):
991                if self.formfactorbox.GetString(ind_form) == \
992                                        state.formfactorcombobox:
993                    formfactor_pos = int(ind_form)
994                    break
[5ce7f17]995
[f0d720b]996        self.formfactorbox.Select(formfactor_pos)
[5ce7f17]997
[f0d720b]998        try:
999            # to support older version
1000            structfactor_pos = int(state.structurecombobox)
1001        except:
1002            structfactor_pos = 0
1003            for ind_struct in range(self.structurebox.GetCount()):
1004                if self.structurebox.GetString(ind_struct) == \
1005                                        state.structurecombobox:
1006                    structfactor_pos = int(ind_struct)
1007                    break
[5ce7f17]1008
[f0d720b]1009        self.structurebox.SetSelection(structfactor_pos)
[5ce7f17]1010
[c8e1996]1011        if state.multi_factor is not None:
[f0d720b]1012            self.multifactorbox.SetSelection(state.multi_factor)
[5ce7f17]1013
[c8e1996]1014        # reset state of checkbox,textcrtl  and  regular parameters value
[f0d720b]1015        self._reset_parameters_state(self.orientation_params_disp,
1016                                     state.orientation_params_disp)
1017        self._reset_parameters_state(self.orientation_params,
1018                                     state.orientation_params)
1019        self._reset_parameters_state(self.str_parameters,
1020                                     state.str_parameters)
1021        self._reset_parameters_state(self.parameters, state.parameters)
[c8e1996]1022        # display dispersion info layer
[f0d720b]1023        self.enable_disp.SetValue(state.enable_disp)
1024        self.disable_disp.SetValue(state.disable_disp)
[5ce7f17]1025
[c8e1996]1026        if hasattr(self, "disp_box") and self.disp_box is not None:
[f0d720b]1027            self.disp_box.SetSelection(state.disp_box)
1028            n = self.disp_box.GetCurrentSelection()
1029            dispersity = self.disp_box.GetClientData(n)
1030            name = dispersity.__name__
1031            self._set_dipers_Param(event=None)
[5ce7f17]1032
[f0d720b]1033            if name == "ArrayDispersion":
[5ce7f17]1034
[f0d720b]1035                for item in self.disp_cb_dict.keys():
[5ce7f17]1036
[f0d720b]1037                    if hasattr(self.disp_cb_dict[item], "SetValue"):
[c8e1996]1038                        self.disp_cb_dict[item].SetValue(
[f0d720b]1039                                                    state.disp_cb_dict[item])
1040                        # Create the dispersion objects
[a0373d5]1041                        disp_model = POLYDISPERSITY_MODELS['array']()
[f0d720b]1042                        if hasattr(state, "values") and \
[505706a]1043                                 self.disp_cb_dict[item].GetValue():
[f0d720b]1044                            if len(state.values) > 0:
1045                                self.values = state.values
1046                                self.weights = state.weights
1047                                disp_model.set_weights(self.values,
1048                                                       state.weights)
1049                            else:
1050                                self._reset_dispersity()
[5ce7f17]1051
[f0d720b]1052                        self._disp_obj_dict[item] = disp_model
1053                        # Set the new model as the dispersion object
[c8e1996]1054                        # for the selected parameter
[f0d720b]1055                        self.model.set_dispersion(item, disp_model)
[5ce7f17]1056
[f0d720b]1057                        self.model._persistency_dict[item] = \
1058                                                [state.values, state.weights]
[5ce7f17]1059
[f0d720b]1060            else:
1061                keys = self.model.getParamList()
1062                for item in keys:
1063                    if item in self.disp_list and \
[c8e1996]1064                            item not in self.model.details:
[f0d720b]1065                        self.model.details[item] = ["", None, None]
1066                self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
1067                self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
[c8e1996]1068        # smearing info  restore
[f0d720b]1069        if hasattr(self, "enable_smearer"):
[c8e1996]1070            # set smearing value whether or not the data
1071            # contain the smearing info
[f0d720b]1072            self.enable_smearer.SetValue(state.enable_smearer)
1073            self.disable_smearer.SetValue(state.disable_smearer)
1074            self.onSmear(event=None)
1075        self.pinhole_smearer.SetValue(state.pinhole_smearer)
1076        self.slit_smearer.SetValue(state.slit_smearer)
[5ce7f17]1077
[f0d720b]1078        self.dI_noweight.SetValue(state.dI_noweight)
1079        self.dI_didata.SetValue(state.dI_didata)
1080        self.dI_sqrdata.SetValue(state.dI_sqrdata)
1081        self.dI_idata.SetValue(state.dI_idata)
[5ce7f17]1082
[c8e1996]1083        # we have two more options for smearing
[f0d720b]1084        if self.pinhole_smearer.GetValue():
1085            self.onPinholeSmear(event=None)
1086        elif self.slit_smearer.GetValue():
1087            self.onSlitSmear(event=None)
[5ce7f17]1088
[c8e1996]1089        # reset state of checkbox,textcrtl  and dispersity parameters value
[f0d720b]1090        self._reset_parameters_state(self.fittable_param, state.fittable_param)
1091        self._reset_parameters_state(self.fixed_param, state.fixed_param)
[5ce7f17]1092
[c8e1996]1093        # draw the model with previous parameters value
[f0d720b]1094        self._onparamEnter_helper()
1095        self.select_param(event=None)
[c8e1996]1096        # Save state_fit
[f0d720b]1097        self.save_current_state_fit()
1098        self._lay_out()
1099        self.Refresh()
[5ce7f17]1100
[f22b43c]1101    def get_cat_combo_box_pos(self, state):
1102        """
1103        Iterate through the categories to find the structurefactor
1104        :return: combo_box_position
1105        """
1106        for key, value in self.master_category_dict.iteritems():
[db5294e]1107            formfactor = state.formfactorcombobox.split(":")
1108            if isinstance(formfactor, list):
1109                formfactor = formfactor[0]
[f22b43c]1110            for list_item in value:
[db5294e]1111                if formfactor in list_item:
[f22b43c]1112                    return self.categorybox.Items.index(key)
[c8e1996]1113        return 0
[f22b43c]1114
[f0d720b]1115    def reset_page_helper(self, state):
1116        """
1117        Use page_state and change the state of existing page
[5ce7f17]1118
[f0d720b]1119        :precondition: the page is already drawn or created
[5ce7f17]1120
[e92a352]1121        :postcondition: the state of the underlying data changes as well as the
[f0d720b]1122            state of the graphic interface
1123        """
[c8e1996]1124        if state is None:
[f0d720b]1125            return
1126        # set data, etc. from the state
1127        # reset page between theory and fitting from bookmarking
1128        data = state.data
1129
[c8e1996]1130        if data is None:
[f0d720b]1131            data_min = state.qmin
1132            data_max = state.qmax
1133            self.qmin_x = data_min
1134            self.qmax_x = data_max
1135            self.qmin.SetValue(str(data_min))
1136            self.qmax.SetValue(str(data_max))
1137
1138            self.state.data = data
1139            self.state.qmin = self.qmin_x
1140            self.state.qmax = self.qmax_x
1141        else:
1142            self.set_data(data)
[5ce7f17]1143
[f0d720b]1144        self.enable2D = state.enable2D
1145        try:
1146            self.magnetic_on = state.magnetic_on
1147        except:
1148            # Backward compatibility (for older state files)
1149            self.magnetic_on = False
1150
1151        self.disp_cb_dict = state.disp_cb_dict
1152        self.disp_list = state.disp_list
[5ce7f17]1153
[c8e1996]1154        # fill model combobox
[f0d720b]1155        self._show_combox_helper()
[c8e1996]1156        # select the current model
[0633048]1157        state._convert_to_sasmodels()
1158        state.categorycombobox = unicode(state.categorycombobox)
1159        if state.categorycombobox in self.categorybox.Items:
1160            category_pos = self.categorybox.Items.index(
1161                state.categorycombobox)
1162        else:
1163            # Look in master list for model name (model.lower)
1164            category_pos = self.get_cat_combo_box_pos(state)
[5ce7f17]1165
[f0d720b]1166        self.categorybox.Select(category_pos)
1167        self._show_combox(None)
[0de74af]1168        from models import PLUGIN_NAME_BASE
[4387385]1169        if self.categorybox.GetValue() == CUSTOM_MODEL \
[0de74af]1170                and PLUGIN_NAME_BASE not in state.formfactorcombobox:
[0633048]1171            state.formfactorcombobox = \
[0de74af]1172                PLUGIN_NAME_BASE + state.formfactorcombobox
[0633048]1173        formfactor_pos = 0
1174        for ind_form in range(self.formfactorbox.GetCount()):
1175            if self.formfactorbox.GetString(ind_form) == \
1176                                                (state.formfactorcombobox):
1177                formfactor_pos = int(ind_form)
1178                break
[5ce7f17]1179
[f0d720b]1180        self.formfactorbox.Select(formfactor_pos)
[5ce7f17]1181
[c8e1996]1182        structfactor_pos = 0
[0633048]1183        if state.structurecombobox is not None:
1184            state.structurecombobox = unicode(state.structurecombobox)
1185            for ind_struct in range(self.structurebox.GetCount()):
1186                if self.structurebox.GetString(ind_struct) == \
1187                                                (state.structurecombobox):
1188                    structfactor_pos = int(ind_struct)
1189                    break
[5ce7f17]1190
[f0d720b]1191        self.structurebox.SetSelection(structfactor_pos)
1192
[c8e1996]1193        if state.multi_factor is not None:
[f0d720b]1194            self.multifactorbox.SetSelection(state.multi_factor)
1195
[c8e1996]1196        # draw the panel according to the new model parameter
[f0d720b]1197        self._on_select_model(event=None)
[5ce7f17]1198
[f0d720b]1199        # take care of 2D button
[c8e1996]1200        if data is None and self.model_view.IsEnabled():
[f0d720b]1201            if self.enable2D:
1202                self.model_view.SetLabel("2D Mode")
1203            else:
1204                self.model_view.SetLabel("1D Mode")
[bac3988]1205
[c8e1996]1206        # reset state of checkbox,textcrtl  and  regular parameters value
[f0d720b]1207        self._reset_parameters_state(self.orientation_params_disp,
1208                                     state.orientation_params_disp)
1209        self._reset_parameters_state(self.orientation_params,
1210                                     state.orientation_params)
1211        self._reset_parameters_state(self.str_parameters,
1212                                     state.str_parameters)
1213        self._reset_parameters_state(self.parameters, state.parameters)
[c8e1996]1214        # display dispersion info layer
[f0d720b]1215        self.enable_disp.SetValue(state.enable_disp)
1216        self.disable_disp.SetValue(state.disable_disp)
1217        # If the polydispersion is ON
1218        if state.enable_disp:
1219            # reset dispersion according the state
1220            self._set_dipers_Param(event=None)
1221            self._reset_page_disp_helper(state)
[c8e1996]1222        # plotting range restore
[f0d720b]1223        self._reset_plotting_range(state)
[c8e1996]1224        # smearing info  restore
[f0d720b]1225        if hasattr(self, "enable_smearer"):
[c8e1996]1226            # set smearing value whether or not the data
1227            # contain the smearing info
[f0d720b]1228            self.enable_smearer.SetValue(state.enable_smearer)
1229            self.disable_smearer.SetValue(state.disable_smearer)
1230            self.onSmear(event=None)
1231        self.pinhole_smearer.SetValue(state.pinhole_smearer)
1232        self.slit_smearer.SetValue(state.slit_smearer)
1233        try:
1234            self.dI_noweight.SetValue(state.dI_noweight)
1235            self.dI_didata.SetValue(state.dI_didata)
1236            self.dI_sqrdata.SetValue(state.dI_sqrdata)
1237            self.dI_idata.SetValue(state.dI_idata)
1238        except:
1239            # to support older state file formats
1240            self.dI_noweight.SetValue(False)
1241            self.dI_didata.SetValue(True)
1242            self.dI_sqrdata.SetValue(False)
1243            self.dI_idata.SetValue(False)
[5ce7f17]1244
[c8e1996]1245        # we have two more options for smearing
[f0d720b]1246        if self.pinhole_smearer.GetValue():
[d85f1d8a]1247            self.dx_percent = state.dx_percent
[7e98655]1248            if self.dx_percent is not None:
[096181d]1249                if state.dx_old:
[b301db9]1250                    self.dx_percent = 100 * (self.dx_percent / self.data.x[0])
[096181d]1251                self.smear_pinhole_percent.SetValue("%.2f" % self.dx_percent)
[f0d720b]1252            self.onPinholeSmear(event=None)
1253        elif self.slit_smearer.GetValue():
1254            self.dxl = state.dxl
1255            self.dxw = state.dxw
[c8e1996]1256            if self.dxl is not None:
[f0d720b]1257                self.smear_slit_height.SetValue(str(self.dxl))
[c8e1996]1258            if self.dxw is not None:
[5ce7f17]1259                self.smear_slit_width.SetValue(str(self.dxw))
[f0d720b]1260            else:
[5ce7f17]1261                self.smear_slit_width.SetValue('')
[f0d720b]1262            self.onSlitSmear(event=None)
[5ce7f17]1263
[c8e1996]1264        # reset state of checkbox,textcrtl  and dispersity parameters value
[f0d720b]1265        self._reset_parameters_state(self.fittable_param, state.fittable_param)
1266        self._reset_parameters_state(self.fixed_param, state.fixed_param)
[5ce7f17]1267
[c8e1996]1268        # draw the model with previous parameters value
[f0d720b]1269        self._onparamEnter_helper()
[c8e1996]1270        # reset the value of chisqr when not consistent with the value computed
[f0d720b]1271        self.tcChi.SetValue(str(self.state.tcChi))
[c8e1996]1272        # reset context menu items
[f0d720b]1273        self._reset_context_menu()
[5ce7f17]1274
[c8e1996]1275        # set the value of the current state to the state given as parameter
[f0d720b]1276        self.state = state.clone()
1277        self.state.m_name = self.m_name
[5ce7f17]1278
[f0d720b]1279    def _reset_page_disp_helper(self, state):
1280        """
1281        Help to rest page for dispersions
1282        """
1283        keys = self.model.getParamList()
1284        for item in keys:
1285            if item in self.disp_list and \
[c8e1996]1286                            item not in self.model.details:
[f0d720b]1287                self.model.details[item] = ["", None, None]
[c8e1996]1288        # for k,v in self.state.disp_cb_dict.iteritems():
[f0d720b]1289        self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
1290        self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
1291        self.values = copy.deepcopy(state.values)
1292        self.weights = copy.deepcopy(state.weights)
[5ce7f17]1293
[6c382da]1294        for key, disp_type in state._disp_obj_dict.iteritems():
[c8e1996]1295            # disp_model = disp
[6c382da]1296            disp_model = POLYDISPERSITY_MODELS[disp_type]()
[f0d720b]1297            self._disp_obj_dict[key] = disp_model
1298            param_name = key.split('.')[0]
1299            # Try to set dispersion only when available
1300            # for eg., pass the orient. angles for 1D Cal
1301            try:
1302                self.model.set_dispersion(param_name, disp_model)
1303                self.model._persistency_dict[key] = \
[c8e1996]1304                    [state.values, state.weights]
[7673ecd]1305            except Exception:
1306                logging.error(traceback.format_exc())
[f0d720b]1307            selection = self._find_polyfunc_selection(disp_model)
1308            for list in self.fittable_param:
[c8e1996]1309                if list[1] == key and list[7] is not None:
[f0d720b]1310                    list[7].SetSelection(selection)
1311                    # For the array disp_model, set the values and weights
1312                    if selection == 1:
1313                        disp_model.set_weights(self.values[key],
1314                                               self.weights[key])
1315                        try:
1316                            # Diables all fittable params for array
1317                            list[0].SetValue(False)
1318                            list[0].Disable()
1319                            list[2].Disable()
1320                            list[5].Disable()
1321                            list[6].Disable()
[7673ecd]1322                        except Exception:
1323                            logging.error(traceback.format_exc())
[f0d720b]1324            # For array, disable all fixed params
1325            if selection == 1:
1326                for item in self.fixed_param:
1327                    if item[1].split(".")[0] == key.split(".")[0]:
1328                        # try it and pass it for the orientation for 1D
1329                        try:
1330                            item[2].Disable()
[7673ecd]1331                        except Exception:
1332                            logging.error(traceback.format_exc())
[5ce7f17]1333
[f0d720b]1334    def _selectDlg(self):
1335        """
[e92a352]1336        open a dialog file to select the customized polydispersity function
[f0d720b]1337        """
[c8e1996]1338        if self.parent is not None:
[f0d720b]1339            self._default_save_location = \
1340                        self._manager.parent.get_save_location()
1341        dlg = wx.FileDialog(self, "Choose a weight file",
[5ce7f17]1342                            self._default_save_location, "",
1343                            "*.*", wx.OPEN)
[f0d720b]1344        path = None
1345        if dlg.ShowModal() == wx.ID_OK:
1346            path = dlg.GetPath()
1347        dlg.Destroy()
1348        return path
1349
1350    def _reset_context_menu(self):
1351        """
1352        reset the context menu
1353        """
[6f16e25]1354        ids = iter(self._id_pool)  # Reusing ids for context menu
[f0d720b]1355        for name, _ in self.state.saved_states.iteritems():
1356            self.number_saved_state += 1
[c8e1996]1357            # Add item in the context menu
[6f16e25]1358            wx_id = ids.next()
[f0d720b]1359            msg = 'Save model and state %g' % self.number_saved_state
[6f16e25]1360            self.popUpMenu.Append(wx_id, name, msg)
1361            wx.EVT_MENU(self, wx_id, self.onResetModel)
[5ce7f17]1362
[f0d720b]1363    def _reset_plotting_range(self, state):
1364        """
1365        Reset the plotting range to a given state
1366        """
1367        self.qmin.SetValue(str(state.qmin))
1368        self.qmax.SetValue(str(state.qmax))
1369
1370    def _save_typeOfmodel(self):
1371        """
1372        save radiobutton containing the type model that can be selected
1373        """
[c8e1996]1374        # self.state.shape_rbutton = self.shape_rbutton.GetValue()
1375        # self.state.shape_indep_rbutton = self.shape_indep_rbutton.GetValue()
1376        # self.state.struct_rbutton = self.struct_rbutton.GetValue()
1377        # self.state.plugin_rbutton = self.plugin_rbutton.GetValue()
[77910cf]1378        self.state.structurecombobox = self.structurebox.GetValue()
1379        self.state.formfactorcombobox = self.formfactorbox.GetValue()
1380        self.state.categorycombobox = self.categorybox.GetValue()
[5ce7f17]1381
[c8e1996]1382        # post state to fit panel
[f0d720b]1383        event = PageInfoEvent(page=self)
1384        wx.PostEvent(self.parent, event)
[5ce7f17]1385
[f0d720b]1386    def _save_plotting_range(self):
1387        """
1388        save the state of plotting range
1389        """
1390        self.state.qmin = self.qmin_x
1391        self.state.qmax = self.qmax_x
1392        self.state.npts = self.npts_x
[5ce7f17]1393
[c8e1996]1394    def _onparamEnter_helper(self, is_modified=False):
[f0d720b]1395        """
1396        check if values entered by the user are changed and valid to replot
1397        model
1398        """
1399        # Flag to register when a parameter has changed.
[c8e1996]1400        # is_modified = False
[f0d720b]1401        self.fitrange = True
1402        is_2Ddata = False
[c8e1996]1403        # self._undo.Enable(True)
[f0d720b]1404        # check if 2d data
1405        if self.data.__class__.__name__ == "Data2D":
1406            is_2Ddata = True
[c8e1996]1407        if self.model is not None:
1408            # Either we get a is_modified = True passed in because
1409            # _update_paramv_on_fit() has been called already or
[8662a58]1410            # we need to check here ourselves.
1411            if not is_modified:
1412                is_modified = (self._check_value_enter(self.fittable_param)
1413                               or self._check_value_enter(self.fixed_param)
1414                               or self._check_value_enter(self.parameters))
[f0d720b]1415
1416            # Here we should check whether the boundaries have been modified.
1417            # If qmin and qmax have been modified, update qmin and qmax and
1418            # set the is_modified flag to True
1419            if self._validate_qrange(self.qmin, self.qmax):
1420                tempmin = float(self.qmin.GetValue())
1421                if tempmin != self.qmin_x:
1422                    self.qmin_x = tempmin
1423                    is_modified = True
1424                tempmax = float(self.qmax.GetValue())
1425                if tempmax != self.qmax_x:
1426                    self.qmax_x = tempmax
1427                    is_modified = True
1428                if is_2Ddata:
1429                    is_modified = self._validate_Npts()
[99f3e053]1430                else:
1431                    is_modified = self._validate_Npts_1D()
[f0d720b]1432            else:
1433                self.fitrange = False
[5ce7f17]1434
[c8e1996]1435            # if any value is modify draw model with new value
[f0d720b]1436            if not self.fitrange:
[c8e1996]1437                # self.btFit.Disable()
[f0d720b]1438                if is_2Ddata:
1439                    self.btEditMask.Disable()
1440            else:
1441                if is_2Ddata and self.data.is_data and not self.batch_on:
1442                    self.btEditMask.Enable(True)
1443            if is_modified and self.fitrange:
1444                # Theory case: need to get npts value to draw
1445                self.npts_x = float(self.Npts_total.GetValue())
[3f8c7bb]1446                self.Npts_fit.SetValue(str(self.Npts_total.GetValue()))
1447                self._save_plotting_range()
[f0d720b]1448                self.create_default_data()
1449                self.state_change = True
1450                self._draw_model()
[012e397]1451                # Time delay has been introduced to prevent _handle error
1452                # on Windows
1453                # This part of code is executed when model is selected and
1454                # it's parameters are changed (with respect to previously
[cf2e6b4]1455                # selected model). There are two Iq evaluations occuring one
[012e397]1456                # after another and therefore there may be compilation error
[cf2e6b4]1457                # if model is calculated for the first time.
1458                # This seems to be Windows only issue - haven't tested on Linux
1459                # though.The proper solution (other than time delay) requires
1460                # more fundemental code refatoring
[012e397]1461                # Wojtek P. Nov 7, 2016
1462                if not ON_MAC:
[880e845]1463                    time.sleep(0.1)
[f0d720b]1464                self.Refresh()
[c65a265]1465
[c8e1996]1466        # logging.info("is_modified flag set to %g",is_modified)
[f0d720b]1467        return is_modified
[5ce7f17]1468
[f0d720b]1469    def _update_paramv_on_fit(self):
1470        """
1471        make sure that update param values just before the fitting
1472        """
[c8e1996]1473        # flag for qmin qmax check values
[f0d720b]1474        flag = True
1475        self.fitrange = True
[8662a58]1476        is_modified = False
[f0d720b]1477
[c8e1996]1478        # wx.PostEvent(self._manager.parent, StatusEvent(status=" \
1479        # updating ... ",type="update"))
[f0d720b]1480
[c8e1996]1481        # So make sure that update param values on_Fit.
1482        # self._undo.Enable(True)
1483        if self.model is not None:
[f0d720b]1484            if self.Npts_total.GetValue() != self.Npts_fit.GetValue():
1485                if not self.data.is_data:
[c8e1996]1486                    self._manager.page_finder[self.uid].set_fit_data(
1487                        data=[self.data])
1488            # Check the values
[8662a58]1489            is_modified = (self._check_value_enter(self.fittable_param)
[c8e1996]1490                           or self._check_value_enter(self.fixed_param)
1491                           or self._check_value_enter(self.parameters))
[f0d720b]1492
[5ce7f17]1493            # If qmin and qmax have been modified, update qmin and qmax and
[f0d720b]1494            # Here we should check whether the boundaries have been modified.
[5ce7f17]1495            # If qmin and qmax have been modified, update qmin and qmax and
[f0d720b]1496            # set the is_modified flag to True
1497            self.fitrange = self._validate_qrange(self.qmin, self.qmax)
1498            if self.fitrange:
1499                tempmin = float(self.qmin.GetValue())
1500                if tempmin != self.qmin_x:
1501                    self.qmin_x = tempmin
1502                tempmax = float(self.qmax.GetValue())
1503                if tempmax != self.qmax_x:
1504                    self.qmax_x = tempmax
1505                if tempmax == tempmin:
1506                    flag = False
1507                temp_smearer = None
1508                if not self.disable_smearer.GetValue():
1509                    temp_smearer = self.current_smearer
1510                    if self.slit_smearer.GetValue():
1511                        flag = self.update_slit_smear()
1512                    elif self.pinhole_smearer.GetValue():
1513                        flag = self.update_pinhole_smear()
1514                    else:
[5ce7f17]1515                        enable_smearer = not self.disable_smearer.GetValue()
[f0d720b]1516                        self._manager.set_smearer(smearer=temp_smearer,
1517                                                  uid=self.uid,
1518                                                  fid=self.data.id,
1519                                                  qmin=float(self.qmin_x),
1520                                                  qmax=float(self.qmax_x),
[cd5e29b]1521                                                  enable_smearer=enable_smearer,
[5ce7f17]1522                                                  draw=False)
[f0d720b]1523                elif not self._is_2D():
[cd5e29b]1524                    enable_smearer = not self.disable_smearer.GetValue()
[f0d720b]1525                    self._manager.set_smearer(smearer=temp_smearer,
1526                                              qmin=float(self.qmin_x),
1527                                              uid=self.uid,
1528                                              fid=self.data.id,
1529                                              qmax=float(self.qmax_x),
[cd5e29b]1530                                              enable_smearer=enable_smearer,
[373d4ee]1531                                              draw=False)
[c8e1996]1532                    if self.data is not None:
1533                        index_data = ((self.qmin_x <= self.data.x) &
[f0d720b]1534                                      (self.data.x <= self.qmax_x))
[d3911e3]1535                        val = str(len(self.data.x[index_data]))
[f0d720b]1536                        self.Npts_fit.SetValue(val)
1537                    else:
1538                        # No data in the panel
1539                        try:
1540                            self.npts_x = float(self.Npts_total.GetValue())
1541                        except:
1542                            flag = False
1543                            return flag
1544                    flag = True
1545                if self._is_2D():
1546                    # only 2D case set mask
1547                    flag = self._validate_Npts()
1548                    if not flag:
1549                        return flag
1550            else:
1551                flag = False
1552        else:
1553            flag = False
1554
[c8e1996]1555        # For invalid q range, disable the mask editor and fit button, vs.
[f0d720b]1556        if not self.fitrange:
1557            if self._is_2D():
1558                self.btEditMask.Disable()
1559        else:
[c8e1996]1560            if self._is_2D() and self.data.is_data and not self.batch_on:
[f0d720b]1561                self.btEditMask.Enable(True)
1562
1563        if not flag:
1564            msg = "Cannot Plot or Fit :Must select a "
1565            msg += " model or Fitting range is not valid!!!  "
1566            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
[5ce7f17]1567
[f0d720b]1568        try:
1569            self.save_current_state()
[7673ecd]1570        except Exception:
1571            logging.error(traceback.format_exc())
[5ce7f17]1572
[c8e1996]1573        return flag, is_modified
[5ce7f17]1574
[f0d720b]1575    def _reset_parameters_state(self, listtorestore, statelist):
1576        """
1577        Reset the parameters at the given state
1578        """
1579        if len(statelist) == 0 or len(listtorestore) == 0:
1580            return
1581
1582        for j in range(len(listtorestore)):
[64c7f39]1583            for param in statelist:
[7602675]1584                if param[1] == listtorestore[j][1]:
1585                    item_page = listtorestore[j]
[0b1d7d3]1586                    item_page_info = param
[7602675]1587                    if (item_page_info[1] == "theta" or item_page_info[1] ==
1588                            "phi") and not self._is_2D():
1589                        break
1590                    # change the state of the check box for simple parameters
1591                    if item_page[0] is not None:
1592                        item_page[0].SetValue(item_page_info[0])
1593                    if item_page[2] is not None:
1594                        item_page[2].SetValue(item_page_info[2])
1595                        if item_page[2].__class__.__name__ == "ComboBox":
1596                            if item_page_info[2] in self.model.fun_list:
1597                                fun_val = self.model.fun_list[item_page_info[2]]
1598                                self.model.setParam(item_page_info[1], fun_val)
1599                    if item_page[3] is not None:
1600                        # show or hide text +/-
1601                        if item_page_info[2]:
1602                            item_page[3].Show(True)
1603                        else:
1604                            item_page[3].Hide()
1605                    if item_page[4] is not None:
1606                        # show of hide the text crtl for fitting error
1607                        if item_page_info[4][0]:
1608                            item_page[4].Show(True)
[a321c52]1609                            item_page[4].SetValue(str(item_page_info[4][1]))
[7602675]1610                        else:
1611                            item_page[3].Hide()
1612                    if item_page[5] is not None:
1613                        # show of hide the text crtl for fitting error
[a321c52]1614                        item_page[5].Show(True)
1615                        item_page[5].SetValue(str(item_page_info[5][1]))
[7602675]1616                    if item_page[6] is not None:
1617                        # show of hide the text crtl for fitting error
[a321c52]1618                        item_page[6].Show(True)
1619                        item_page[6].SetValue(str(item_page_info[6][1]))
[7602675]1620                    break
[5ce7f17]1621
[f0d720b]1622    def _reset_strparam_state(self, listtorestore, statelist):
1623        """
1624        Reset the string parameters at the given state
1625        """
1626        if len(statelist) == 0:
1627            return
1628
1629        listtorestore = copy.deepcopy(statelist)
[5ce7f17]1630
[f0d720b]1631        for j in range(len(listtorestore)):
1632            item_page = listtorestore[j]
1633            item_page_info = statelist[j]
[c8e1996]1634            # change the state of the check box for simple parameters
[5ce7f17]1635
[c8e1996]1636            if item_page[0] is not None:
[f0d720b]1637                item_page[0].SetValue(format_number(item_page_info[0], True))
1638
[c8e1996]1639            if item_page[2] is not None:
[f0d720b]1640                param_name = item_page_info[1]
1641                value = item_page_info[2]
1642                selection = value
1643                if value in self.model.fun_list:
1644                    selection = self.model.fun_list[value]
1645                item_page[2].SetValue(selection)
1646                self.model.setParam(param_name, selection)
[5ce7f17]1647
[f0d720b]1648    def _copy_parameters_state(self, listtocopy, statelist):
1649        """
1650        copy the state of button
[5ce7f17]1651
[f0d720b]1652        :param listtocopy: the list of check button to copy
1653        :param statelist: list of state object to store the current state
[5ce7f17]1654
[f0d720b]1655        """
1656        if len(listtocopy) == 0:
1657            return
[5ce7f17]1658
[f0d720b]1659        for item in listtocopy:
[5ce7f17]1660
[f0d720b]1661            checkbox_state = None
[c8e1996]1662            if item[0] is not None:
[f0d720b]1663                checkbox_state = item[0].GetValue()
1664            parameter_name = item[1]
1665            parameter_value = None
[c8e1996]1666            if item[2] is not None:
[f0d720b]1667                parameter_value = item[2].GetValue()
1668            static_text = None
[c8e1996]1669            if item[3] is not None:
[f0d720b]1670                static_text = item[3].IsShown()
1671            error_value = None
1672            error_state = None
[c8e1996]1673            if item[4] is not None:
[f0d720b]1674                error_value = item[4].GetValue()
1675                error_state = item[4].IsShown()
[5ce7f17]1676
[f0d720b]1677            min_value = None
1678            min_state = None
[c8e1996]1679            if item[5] is not None:
[f0d720b]1680                min_value = item[5].GetValue()
1681                min_state = item[5].IsShown()
[5ce7f17]1682
[f0d720b]1683            max_value = None
1684            max_state = None
[c8e1996]1685            if item[6] is not None:
[f0d720b]1686                max_value = item[6].GetValue()
1687                max_state = item[6].IsShown()
1688            unit = None
[c8e1996]1689            if item[7] is not None:
[f0d720b]1690                unit = item[7].GetLabel()
[5ce7f17]1691
[f0d720b]1692            statelist.append([checkbox_state, parameter_name, parameter_value,
1693                              static_text, [error_state, error_value],
1694                              [min_state, min_value],
1695                              [max_state, max_value], unit])
[5ce7f17]1696
[f0d720b]1697    def _draw_model(self, update_chisqr=True, source='model'):
1698        """
1699        Method to draw or refresh a plotted model.
1700        The method will use the data member from the model page
1701        to build a call to the fitting perspective manager.
[5ce7f17]1702
[f0d720b]1703        :param chisqr: update chisqr value [bool]
1704        """
1705        wx.CallAfter(self._draw_model_after, update_chisqr, source)
[5ce7f17]1706
[f0d720b]1707    def _draw_model_after(self, update_chisqr=True, source='model'):
1708        """
1709        Method to draw or refresh a plotted model.
1710        The method will use the data member from the model page
1711        to build a call to the fitting perspective manager.
[5ce7f17]1712
[f0d720b]1713        :param chisqr: update chisqr value [bool]
1714        """
[c8e1996]1715        # if self.check_invalid_panel():
[f0d720b]1716        #    return
[c8e1996]1717        if self.model is not None:
[f0d720b]1718            temp_smear = None
1719            if hasattr(self, "enable_smearer"):
1720                if not self.disable_smearer.GetValue():
1721                    temp_smear = self.current_smearer
1722            # compute weight for the current data
[d85c194]1723            from sas.sasgui.perspectives.fitting.utils import get_weight
[f0d720b]1724            flag = self.get_weight_flag()
1725            weight = get_weight(data=self.data, is2d=self._is_2D(), flag=flag)
1726            toggle_mode_on = self.model_view.IsEnabled()
1727            is_2d = self._is_2D()
1728            self._manager.draw_model(self.model,
[c8e1996]1729                                     data=self.data,
1730                                     smearer=temp_smear,
1731                                     qmin=float(self.qmin_x),
1732                                     qmax=float(self.qmax_x),
1733                                     page_id=self.uid,
1734                                     toggle_mode_on=toggle_mode_on,
1735                                     state=self.state,
1736                                     enable2D=is_2d,
1737                                     update_chisqr=update_chisqr,
1738                                     source='model',
1739                                     weight=weight)
[5ce7f17]1740
[f0d720b]1741    def _on_show_sld(self, event=None):
1742        """
1743        Plot SLD profile
1744        """
1745        # get profile data
1746        x, y = self.model.getProfile()
1747
[d7bb526]1748        from sas.sasgui.plottools import Data1D as pf_data1d
[c8e1996]1749        # from sas.sasgui.perspectives.theory.profile_dialog import SLDPanel
[d85c194]1750        from sas.sasgui.guiframe.local_perspectives.plotting.profile_dialog \
[c8e1996]1751            import SLDPanel
[f0d720b]1752        sld_data = pf_data1d(x, y)
1753        sld_data.name = 'SLD'
1754        sld_data.axes = self.sld_axes
[6f16e25]1755        self.panel = SLDPanel(self, data=sld_data, axes=self.sld_axes,
1756                              id=wx.ID_ANY)
[f0d720b]1757        self.panel.ShowModal()
[5ce7f17]1758
[f0d720b]1759    def _set_multfactor_combobox(self, multiplicity=10):
1760        """
[e92a352]1761        Set comboBox for multitfactor of CoreMultiShellModel
[f0d720b]1762        :param multiplicit: no. of multi-functionality
1763        """
1764        # build content of the combobox
1765        for idx in range(0, multiplicity):
1766            self.multifactorbox.Append(str(idx), int(idx))
1767        self._hide_multfactor_combobox()
[5ce7f17]1768
[f0d720b]1769    def _show_multfactor_combobox(self):
1770        """
1771        Show the comboBox of muitfactor of CoreMultiShellModel
1772        """
1773        if not self.mutifactor_text.IsShown():
1774            self.mutifactor_text.Show(True)
1775            self.mutifactor_text1.Show(True)
1776        if not self.multifactorbox.IsShown():
1777            self.multifactorbox.Show(True)
[5ce7f17]1778
[f0d720b]1779    def _hide_multfactor_combobox(self):
1780        """
1781        Hide the comboBox of muitfactor of CoreMultiShellModel
1782        """
1783        if self.mutifactor_text.IsShown():
1784            self.mutifactor_text.Hide()
1785            self.mutifactor_text1.Hide()
1786        if self.multifactorbox.IsShown():
1787            self.multifactorbox.Hide()
[5ce7f17]1788
[f0d720b]1789    def formfactor_combo_init(self):
1790        """
1791        First time calls _show_combox_helper
1792        """
1793        self._show_combox(None)
[5ce7f17]1794
[f0d720b]1795    def _show_combox_helper(self):
1796        """
1797        Fill panel's combo box according to the type of model selected
1798        """
[4387385]1799
[f0d720b]1800        mod_cat = self.categorybox.GetStringSelection()
1801        self.structurebox.SetSelection(0)
1802        self.structurebox.Disable()
1803        self.formfactorbox.Clear()
[c8e1996]1804        if mod_cat is None:
[f0d720b]1805            return
1806        m_list = []
1807        try:
[4387385]1808            if mod_cat == CUSTOM_MODEL:
[f0d720b]1809                for model in self.model_list_box[mod_cat]:
[2abe6bf]1810                    m_list.append(self.model_dict[model.name])
[f0d720b]1811            else:
1812                cat_dic = self.master_category_dict[mod_cat]
1813                for (model, enabled) in cat_dic:
1814                    if enabled:
1815                        m_list.append(self.model_dict[model])
[7673ecd]1816        except Exception:
1817            msg = traceback.format_exc()
[f0d720b]1818            wx.PostEvent(self._manager.parent,
1819                         StatusEvent(status=msg, info="error"))
1820        self._populate_box(self.formfactorbox, m_list)
[5ce7f17]1821
1822    def _on_modify_cat(self, event=None):
1823        """
[cd5e29b]1824        Called when category manager is opened
[5ce7f17]1825        """
1826        self._manager.parent.on_category_panel(event)
1827
[f0d720b]1828    def _show_combox(self, event=None):
1829        """
1830        Show combox box associate with type of model selected
1831        """
1832        self.Show(False)
1833        self._show_combox_helper()
1834        self._on_select_model(event=None)
1835        self.Show(True)
1836        self._save_typeOfmodel()
1837        self.sizer4_4.Layout()
1838        self.sizer4.Layout()
1839        self.Layout()
1840        self.Refresh()
[5ce7f17]1841
[f0d720b]1842    def _populate_box(self, combobox, list):
1843        """
1844        fill combox box with dict item
[5ce7f17]1845
[f0d720b]1846        :param list: contains item to fill the combox
1847            item must model class
1848        """
1849        mlist = []
1850        for models in list:
[cb4ef58]1851            if models.name != "NoStructure":
1852                mlist.append((models.name, models))
[5ce7f17]1853
[f0d720b]1854        # Sort the models
1855        mlist_sorted = sorted(mlist)
1856        for item in mlist_sorted:
1857            combobox.Append(item[0], item[1])
1858        return 0
[5ce7f17]1859
[f0d720b]1860    def _onQrangeEnter(self, event):
1861        """
1862        Check validity of value enter in the Q range field
[5ce7f17]1863
[f0d720b]1864        """
1865        tcrtl = event.GetEventObject()
[c8e1996]1866        # Clear msg if previously shown.
[f0d720b]1867        msg = ""
1868        wx.PostEvent(self.parent, StatusEvent(status=msg))
1869        # Flag to register when a parameter has changed.
1870        if tcrtl.GetValue().lstrip().rstrip() != "":
1871            try:
1872                float(tcrtl.GetValue())
1873                tcrtl.SetBackgroundColour(wx.WHITE)
1874                # If qmin and qmax have been modified, update qmin and qmax
1875                if self._validate_qrange(self.qmin, self.qmax):
1876                    tempmin = float(self.qmin.GetValue())
1877                    if tempmin != self.qmin_x:
1878                        self.qmin_x = tempmin
1879                    tempmax = float(self.qmax.GetValue())
1880                    if tempmax != self.qmax_x:
1881                        self.qmax_x = tempmax
1882                else:
1883                    tcrtl.SetBackgroundColour("pink")
[cd5e29b]1884                    msg = "Model Error: wrong value entered: %s" % \
[c8e1996]1885                          sys.exc_info()[1]
[f0d720b]1886                    wx.PostEvent(self.parent, StatusEvent(status=msg))
1887                    return
1888            except:
1889                tcrtl.SetBackgroundColour("pink")
[cd5e29b]1890                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
[f0d720b]1891                wx.PostEvent(self.parent, StatusEvent(status=msg))
1892                return
[c8e1996]1893            # Check if # of points for theory model are valid(>0).
1894            if self.npts is not None:
[f0d720b]1895                if check_float(self.npts):
1896                    temp_npts = float(self.npts.GetValue())
1897                    if temp_npts != self.num_points:
1898                        self.num_points = temp_npts
1899                else:
1900                    msg = "Cannot plot: No points in Q range!!!  "
1901                    wx.PostEvent(self.parent, StatusEvent(status=msg))
1902        else:
1903            tcrtl.SetBackgroundColour("pink")
1904            msg = "Model Error: wrong value entered!!!"
1905            wx.PostEvent(self.parent, StatusEvent(status=msg))
1906        self.save_current_state()
1907        event = PageInfoEvent(page=self)
1908        wx.PostEvent(self.parent, event)
1909        self.state_change = False
[c8e1996]1910        # Draw the model for a different range
[f0d720b]1911        if not self.data.is_data:
1912            self.create_default_data()
1913        self._draw_model()
[5ce7f17]1914
[f0d720b]1915    def _theory_qrange_enter(self, event):
1916        """
1917        Check validity of value enter in the Q range field
1918        """
[5ce7f17]1919
[f0d720b]1920        tcrtl = event.GetEventObject()
[c8e1996]1921        # Clear msg if previously shown.
[f0d720b]1922        msg = ""
1923        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1924        # Flag to register when a parameter has changed.
1925        is_modified = False
1926        if tcrtl.GetValue().lstrip().rstrip() != "":
1927            try:
1928                value = float(tcrtl.GetValue())
1929                tcrtl.SetBackgroundColour(wx.WHITE)
1930
1931                # If qmin and qmax have been modified, update qmin and qmax
1932                if self._validate_qrange(self.theory_qmin, self.theory_qmax):
1933                    tempmin = float(self.theory_qmin.GetValue())
1934                    if tempmin != self.theory_qmin_x:
1935                        self.theory_qmin_x = tempmin
1936                    tempmax = float(self.theory_qmax.GetValue())
1937                    if tempmax != self.qmax_x:
1938                        self.theory_qmax_x = tempmax
1939                else:
1940                    tcrtl.SetBackgroundColour("pink")
[cd5e29b]1941                    msg = "Model Error: wrong value entered: %s" % \
[c8e1996]1942                          sys.exc_info()[1]
[f0d720b]1943                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1944                    return
1945            except:
1946                tcrtl.SetBackgroundColour("pink")
[cd5e29b]1947                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
[f0d720b]1948                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1949                return
[c8e1996]1950            # Check if # of points for theory model are valid(>0).
[f0d720b]1951            if self.Npts_total.IsEditable():
1952                if check_float(self.Npts_total):
1953                    temp_npts = float(self.Npts_total.GetValue())
1954                    if temp_npts != self.num_points:
1955                        self.num_points = temp_npts
1956                        is_modified = True
1957                else:
1958                    msg = "Cannot Plot: No points in Q range!!!  "
1959                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1960        else:
1961            tcrtl.SetBackgroundColour("pink")
1962            msg = "Model Error: wrong value entered!!!"
1963            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1964        self.save_current_state()
1965        event = PageInfoEvent(page=self)
1966        wx.PostEvent(self.parent, event)
1967        self.state_change = False
[c8e1996]1968        # Draw the model for a different range
[f0d720b]1969        self.create_default_data()
1970        self._draw_model()
[5ce7f17]1971
[f0d720b]1972    def _on_select_model_helper(self):
1973        """
1974        call back for model selection
1975        """
[c8e1996]1976        # reset dictionary containing reference to dispersion
[f0d720b]1977        self._disp_obj_dict = {}
1978        self.disp_cb_dict = {}
1979        self.temp_multi_functional = False
1980        f_id = self.formfactorbox.GetCurrentSelection()
[c8e1996]1981        # For MAC
[f0d720b]1982        form_factor = None
1983        if f_id >= 0:
1984            form_factor = self.formfactorbox.GetClientData(f_id)
1985
[4109bd5]1986        if form_factor is None or \
1987            not hasattr(form_factor, 'is_form_factor') or \
[c8e1996]1988                not form_factor.is_form_factor:
[f0d720b]1989            self.structurebox.Hide()
1990            self.text2.Hide()
1991            self.structurebox.Disable()
1992            self.structurebox.SetSelection(0)
1993            self.text2.Disable()
1994        else:
1995            self.structurebox.Show()
1996            self.text2.Show()
1997            self.structurebox.Enable()
1998            self.text2.Enable()
[5ce7f17]1999
[c8e1996]2000        if form_factor is not None:
[f0d720b]2001            # set multifactor for Mutifunctional models
[cb4ef58]2002            if form_factor.is_multiplicity_model:
[f0d720b]2003                m_id = self.multifactorbox.GetCurrentSelection()
[cb4ef58]2004                multiplicity = form_factor.multiplicity_info[0]
[f0d720b]2005                self.multifactorbox.Clear()
2006                self._set_multfactor_combobox(multiplicity)
2007                self._show_multfactor_combobox()
[c8e1996]2008                # ToDo: this info should be called directly from the model
[cb4ef58]2009                text = form_factor.multiplicity_info[1]  # 'No. of Shells: '
[f0d720b]2010
2011                self.mutifactor_text.SetLabel(text)
2012                if m_id > multiplicity - 1:
2013                    # default value
2014                    m_id = 1
[5ce7f17]2015
[f0d720b]2016                self.multi_factor = self.multifactorbox.GetClientData(m_id)
[c8e1996]2017                if self.multi_factor is None:
[f0d720b]2018                    self.multi_factor = 0
2019                self.multifactorbox.SetSelection(m_id)
2020                # Check len of the text1 and max_multiplicity
2021                text = ''
2022                if form_factor.multiplicity_info[0] == \
[c8e1996]2023                        len(form_factor.multiplicity_info[2]):
[f0d720b]2024                    text = form_factor.multiplicity_info[2][self.multi_factor]
2025                self.mutifactor_text1.SetLabel(text)
2026                # Check if model has  get sld profile.
2027                if len(form_factor.multiplicity_info[3]) > 0:
2028                    self.sld_axes = form_factor.multiplicity_info[3]
2029                    self.show_sld_button.Show(True)
2030                else:
2031                    self.sld_axes = ""
2032            else:
2033                self._hide_multfactor_combobox()
2034                self.show_sld_button.Hide()
2035                self.multi_factor = None
2036        else:
2037            self._hide_multfactor_combobox()
2038            self.show_sld_button.Hide()
2039            self.multi_factor = None
[5ce7f17]2040
[f0d720b]2041        s_id = self.structurebox.GetCurrentSelection()
2042        struct_factor = self.structurebox.GetClientData(s_id)
[5ce7f17]2043
[c8e1996]2044        if struct_factor is not None:
[313c5c9]2045            from sasmodels.sasview_model import MultiplicationModel
[cb4ef58]2046            self.model = MultiplicationModel(form_factor(self.multi_factor),
2047                                             struct_factor())
[f0d720b]2048            # multifunctional form factor
2049            if len(form_factor.non_fittable) > 0:
2050                self.temp_multi_functional = True
[c8e1996]2051        elif form_factor is not None:
[5213d22]2052            if self.multi_factor is not None:
2053                self.model = form_factor(self.multi_factor)
2054            else:
2055                # old style plugin models do not accept a multiplicity argument
2056                self.model = form_factor()
[f0d720b]2057        else:
[cb4ef58]2058            self.model = None
2059            return
2060
[f0d720b]2061        # check if model has magnetic parameters
2062        if len(self.model.magnetic_params) > 0:
[5ce7f17]2063            self._has_magnetic = True
[f0d720b]2064        else:
[5ce7f17]2065            self._has_magnetic = False
[c8e1996]2066        # post state to fit panel
[f0d720b]2067        self.state.parameters = []
2068        self.state.model = self.model
2069        self.state.qmin = self.qmin_x
2070        self.state.multi_factor = self.multi_factor
2071        self.disp_list = self.model.getDispParamList()
2072        self.state.disp_list = self.disp_list
2073        self.on_set_focus(None)
2074        self.Layout()
[5ce7f17]2075
[f0d720b]2076    def _validate_qrange(self, qmin_ctrl, qmax_ctrl):
2077        """
2078        Verify that the Q range controls have valid values
2079        and that Qmin < Qmax.
[5ce7f17]2080
[f0d720b]2081        :param qmin_ctrl: text control for Qmin
2082        :param qmax_ctrl: text control for Qmax
[5ce7f17]2083
[f0d720b]2084        :return: True is the Q range is value, False otherwise
[5ce7f17]2085
[f0d720b]2086        """
2087        qmin_validity = check_float(qmin_ctrl)
2088        qmax_validity = check_float(qmax_ctrl)
2089        if not (qmin_validity and qmax_validity):
2090            return False
2091        else:
2092            qmin = float(qmin_ctrl.GetValue())
2093            qmax = float(qmax_ctrl.GetValue())
2094            if qmin < qmax:
[c8e1996]2095                # Make sure to set both colours white.
[f0d720b]2096                qmin_ctrl.SetBackgroundColour(wx.WHITE)
2097                qmin_ctrl.Refresh()
2098                qmax_ctrl.SetBackgroundColour(wx.WHITE)
2099                qmax_ctrl.Refresh()
2100            else:
2101                qmin_ctrl.SetBackgroundColour("pink")
2102                qmin_ctrl.Refresh()
2103                qmax_ctrl.SetBackgroundColour("pink")
2104                qmax_ctrl.Refresh()
2105                msg = "Invalid Q range: Q min must be smaller than Q max"
2106                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2107                return False
2108        return True
[5ce7f17]2109
[f0d720b]2110    def _validate_Npts(self):
2111        """
2112        Validate the number of points for fitting is more than 10 points.
2113        If valid, setvalues Npts_fit otherwise post msg.
2114        """
[c8e1996]2115        # default flag
[f0d720b]2116        flag = True
2117        # Theory
[c8e1996]2118        if self.data is None and self.enable2D:
[f0d720b]2119            return flag
2120        for data in self.data_list:
2121            # q value from qx and qy
2122            radius = numpy.sqrt(data.qx_data * data.qx_data +
2123                                data.qy_data * data.qy_data)
[c8e1996]2124            # get unmasked index
[f0d720b]2125            index_data = (float(self.qmin.GetValue()) <= radius) & \
[c8e1996]2126                         (radius <= float(self.qmax.GetValue()))
[f0d720b]2127            index_data = (index_data) & (data.mask)
2128            index_data = (index_data) & (numpy.isfinite(data.data))
2129
2130            if len(index_data[index_data]) < 10:
2131                # change the color pink.
2132                self.qmin.SetBackgroundColour("pink")
2133                self.qmin.Refresh()
2134                self.qmax.SetBackgroundColour("pink")
2135                self.qmax.Refresh()
2136                msg = "Data Error: "
2137                msg += "Too few points in %s." % data.name
2138                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2139                self.fitrange = False
2140                flag = False
2141            else:
[d7d0182]2142                self.Npts_fit.SetValue(str(len(index_data[index_data])))
[f0d720b]2143                self.fitrange = True
[5ce7f17]2144
[f0d720b]2145        return flag
2146
2147    def _validate_Npts_1D(self):
2148        """
2149        Validate the number of points for fitting is more than 5 points.
2150        If valid, setvalues Npts_fit otherwise post msg.
2151        """
[c8e1996]2152        # default flag
[f0d720b]2153        flag = True
2154        # Theory
[c8e1996]2155        if self.data is None:
[f0d720b]2156            return flag
2157        for data in self.data_list:
2158            # q value from qx and qy
2159            radius = data.x
[c8e1996]2160            # get unmasked index
[f0d720b]2161            index_data = (float(self.qmin.GetValue()) <= radius) & \
[c8e1996]2162                         (radius <= float(self.qmax.GetValue()))
[f0d720b]2163            index_data = (index_data) & (numpy.isfinite(data.y))
2164
2165            if len(index_data[index_data]) < 5:
2166                # change the color pink.
2167                self.qmin.SetBackgroundColour("pink")
2168                self.qmin.Refresh()
2169                self.qmax.SetBackgroundColour("pink")
2170                self.qmax.Refresh()
2171                msg = "Data Error: "
2172                msg += "Too few points in %s." % data.name
2173                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2174                self.fitrange = False
2175                flag = False
2176            else:
[505706a]2177                self.Npts_fit.SetValue(str(len(index_data[index_data])))
[f0d720b]2178                self.fitrange = True
[5ce7f17]2179
[f0d720b]2180        return flag
[5ce7f17]2181
[ee4b3cb]2182    def _check_value_enter(self, list):
[f0d720b]2183        """
2184        :param list: model parameter and panel info
2185        :Note: each item of the list should be as follow:
2186            item=[check button state, parameter's name,
2187                paramater's value, string="+/-",
2188                parameter's error of fit,
2189                parameter's minimum value,
[cb4ef58]2190                parameter's maximum value ,
[f0d720b]2191                parameter's units]
[ee4b3cb]2192
2193        Returns True if the model parameters have changed.
[f0d720b]2194        """
[ee4b3cb]2195        is_modified = False
[f0d720b]2196        for item in list:
[c8e1996]2197            # skip angle parameters for 1D
[ee4b3cb]2198            if not self.enable2D and item in self.orientation_params:
2199                continue
[5ce7f17]2200
[a0373d5]2201            value_ctrl = item[2]
2202            if not value_ctrl.IsEnabled():
2203                # ArrayDispersion disables PD, Min, Max, Npts, Nsigs
[ee4b3cb]2204                continue
2205
[a0373d5]2206            name = item[1]
[ee4b3cb]2207            value_str = value_ctrl.GetValue().strip()
[a0373d5]2208            if name.endswith(".npts"):
2209                validity = check_int(value_ctrl)
2210                if not validity:
2211                    continue
2212                value = int(value_str)
2213
2214            elif name.endswith(".nsigmas"):
2215                validity = check_float(value_ctrl)
2216                if not validity:
2217                    continue
2218                value = float(value_str)
2219
2220            else:  # value or polydispersity
2221
2222                # Check that min, max and value are floats
2223                min_ctrl, max_ctrl = item[5], item[6]
2224                min_str = min_ctrl.GetValue().strip()
2225                max_str = max_ctrl.GetValue().strip()
2226                validity = check_float(value_ctrl)
2227                if min_str != "":
2228                    validity = validity and check_float(min_ctrl)
2229                if max_str != "":
2230                    validity = validity and check_float(max_ctrl)
2231                if not validity:
2232                    continue
2233
2234                # Check that min is less than max
2235                low = -numpy.inf if min_str == "" else float(min_str)
2236                high = numpy.inf if max_str == "" else float(max_str)
2237                if high < low:
2238                    min_ctrl.SetBackgroundColour("pink")
2239                    min_ctrl.Refresh()
2240                    max_ctrl.SetBackgroundColour("pink")
2241                    max_ctrl.Refresh()
[c8e1996]2242                    # msg = "Invalid fit range for %s: min must be smaller
2243                    # than max"%name
2244                    # wx.PostEvent(self._manager.parent,
2245                    # StatusEvent(status=msg))
[a0373d5]2246                    continue
2247
2248                # Force value between min and max
2249                value = float(value_str)
2250                if value < low:
2251                    value = low
2252                    value_ctrl.SetValue(format_number(value))
2253                elif value > high:
2254                    value = high
2255                    value_ctrl.SetValue(format_number(value))
2256
2257                if name not in self.model.details.keys():
2258                    self.model.details[name] = ["", None, None]
2259                old_low, old_high = self.model.details[name][1:3]
2260                if old_low != low or old_high != high:
2261                    # The configuration has changed but it won't change the
2262                    # computed curve so no need to set is_modified to True
[c8e1996]2263                    # is_modified = True
[a0373d5]2264                    self.model.details[name][1:3] = low, high
[ee4b3cb]2265
2266            # Update value in model if it has changed
2267            if value != self.model.getParam(name):
2268                self.model.setParam(name, value)
2269                is_modified = True
[5ce7f17]2270
[f0d720b]2271        return is_modified
[5ce7f17]2272
[f0d720b]2273    def _set_dipers_Param(self, event):
2274        """
2275        respond to self.enable_disp and self.disable_disp radio box.
2276        The dispersity object is reset inside the model into Gaussian.
2277        When the user select yes , this method display a combo box for
2278        more selection when the user selects No,the combo box disappears.
2279        Redraw the model with the default dispersity (Gaussian)
2280        """
[c8e1996]2281        # On selction if no model exists.
2282        if self.model is None:
[f0d720b]2283            self.disable_disp.SetValue(True)
2284            msg = "Please select a Model first..."
2285            wx.MessageBox(msg, 'Info')
2286            wx.PostEvent(self._manager.parent,
2287                         StatusEvent(status="Polydispersion: %s" % msg))
2288            return
2289
2290        self._reset_dispersity()
[5ce7f17]2291
[c8e1996]2292        if self.model is None:
[f0d720b]2293            self.model_disp.Hide()
2294            self.sizer4_4.Clear(True)
2295            return
2296
2297        if self.enable_disp.GetValue():
[c8e1996]2298            # layout for model containing no dispersity parameters
[5ce7f17]2299
[f0d720b]2300            self.disp_list = self.model.getDispParamList()
[5ce7f17]2301
[f0d720b]2302            if len(self.disp_list) == 0 and len(self.disp_cb_dict) == 0:
2303                self._layout_sizer_noDipers()
2304            else:
[c8e1996]2305                # set gaussian sizer
[f0d720b]2306                self._on_select_Disp(event=None)
2307        else:
2308            self.sizer4_4.Clear(True)
[5ce7f17]2309
[c8e1996]2310        # post state to fit panel
[f0d720b]2311        self.save_current_state()
[c8e1996]2312        if event is not None:
[f0d720b]2313            event = PageInfoEvent(page=self)
2314            wx.PostEvent(self.parent, event)
[c8e1996]2315        # draw the model with the current dispersity
[47f2b5d]2316
[c8e1996]2317        # Wojtek P, Oct 8, 2016: Calling draw_model seems to be unessecary.
2318        # By comenting it we save an extra Iq calculation
2319        # self._draw_model()
[47f2b5d]2320
[c8e1996]2321        # Need to use FitInside again here to replace the next four lines.
2322        # Otherwised polydispersity off does not resize the scrollwindow.
2323        # PDB Nov 28, 2015
[1c2bf90]2324        self.FitInside()
2325#        self.sizer4_4.Layout()
2326#        self.sizer5.Layout()
2327#        self.Layout()
2328#        self.Refresh()
[5ce7f17]2329
[f0d720b]2330    def _layout_sizer_noDipers(self):
2331        """
2332        Draw a sizer with no dispersity info
2333        """
2334        ix = 0
2335        iy = 1
2336        self.fittable_param = []
2337        self.fixed_param = []
2338        self.orientation_params_disp = []
[5ce7f17]2339
[f0d720b]2340        self.sizer4_4.Clear(True)
2341        text = "No polydispersity available for this model"
[6f16e25]2342        model_disp = wx.StaticText(self, wx.ID_ANY, text)
[f0d720b]2343        self.sizer4_4.Add(model_disp, (iy, ix), (1, 1),
2344                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
2345        self.sizer4_4.Layout()
2346        self.sizer4.Layout()
[5ce7f17]2347
[f0d720b]2348    def _reset_dispersity(self):
2349        """
2350        put gaussian dispersity into current model
2351        """
2352        if len(self.param_toFit) > 0:
2353            for item in self.fittable_param:
2354                if item in self.param_toFit:
2355                    self.param_toFit.remove(item)
2356
2357            for item in self.orientation_params_disp:
2358                if item in self.param_toFit:
2359                    self.param_toFit.remove(item)
[5ce7f17]2360
[f0d720b]2361        self.fittable_param = []
2362        self.fixed_param = []
2363        self.orientation_params_disp = []
2364        self.values = {}
2365        self.weights = {}
[bac3988]2366
[c8e1996]2367        # from sas.models.dispersion_models import GaussianDispersion
[f0d720b]2368        from sasmodels.weights import GaussianDispersion
2369        if len(self.disp_cb_dict) == 0:
2370            self.save_current_state()
2371            self.sizer4_4.Clear(True)
2372            self.Layout()
2373            return
2374        if (len(self.disp_cb_dict) > 0):
2375            for p in self.disp_cb_dict:
2376                # The parameter was un-selected.
2377                # Go back to Gaussian model (with 0 pts)
2378                disp_model = GaussianDispersion()
[5ce7f17]2379
[f0d720b]2380                self._disp_obj_dict[p] = disp_model
2381                # Set the new model as the dispersion object
2382                # for the selected parameter
2383                try:
2384                    self.model.set_dispersion(p, disp_model)
[7673ecd]2385                except Exception:
2386                    logging.error(traceback.format_exc())
[f0d720b]2387
[c8e1996]2388        # save state into
[f0d720b]2389        self.save_current_state()
2390        self.Layout()
2391        self.Refresh()
[5ce7f17]2392
[f0d720b]2393    def _on_select_Disp(self, event):
2394        """
2395        allow selecting different dispersion
2396        self.disp_list should change type later .now only gaussian
2397        """
2398        self._set_sizer_dispersion()
2399
[c8e1996]2400        # Redraw the model
[cc0f4a8]2401        #  Wojtek P. Nov 7, 2016: Redrawing seems to be unnecessary here
2402        # self._draw_model()
[c8e1996]2403        # self._undo.Enable(True)
[f0d720b]2404        event = PageInfoEvent(page=self)
2405        wx.PostEvent(self.parent, event)
[5ce7f17]2406
[f0d720b]2407        self.sizer4_4.Layout()
2408        self.sizer4.Layout()
2409        self.SetupScrolling()
[5ce7f17]2410
[f0d720b]2411    def _on_disp_func(self, event=None):
2412        """
2413        Select a distribution function for the polydispersion
[5ce7f17]2414
[f0d720b]2415        :Param event: ComboBox event
2416        """
2417        # get ready for new event
[c8e1996]2418        if event is not None:
[f0d720b]2419            event.Skip()
2420        # Get event object
2421        disp_box = event.GetEventObject()
2422
2423        # Try to select a Distr. function
2424        try:
2425            disp_box.SetBackgroundColour("white")
2426            selection = disp_box.GetCurrentSelection()
2427            param_name = disp_box.Name.split('.')[0]
2428            disp_name = disp_box.GetValue()
2429            dispersity = disp_box.GetClientData(selection)
[5ce7f17]2430
[c8e1996]2431            # disp_model =  GaussianDispersion()
[f0d720b]2432            disp_model = dispersity()
2433            # Get param names to reset the values of the param
2434            name1 = param_name + ".width"
2435            name2 = param_name + ".npts"
2436            name3 = param_name + ".nsigmas"
2437            # Check Disp. function whether or not it is 'array'
2438            if disp_name.lower() == "array":
2439                value2 = ""
2440                value3 = ""
2441                value1 = self._set_array_disp(name=name1, disp=disp_model)
2442            else:
2443                self._del_array_values(name1)
[c8e1996]2444                # self._reset_array_disp(param_name)
[f0d720b]2445                self._disp_obj_dict[name1] = disp_model
2446                self.model.set_dispersion(param_name, disp_model)
[6c382da]2447                self.state._disp_obj_dict[name1] = disp_model.type
[5ce7f17]2448
[f0d720b]2449                value1 = str(format_number(self.model.getParam(name1), True))
2450                value2 = str(format_number(self.model.getParam(name2)))
2451                value3 = str(format_number(self.model.getParam(name3)))
2452            # Reset fittable polydispersin parameter value
2453            for item in self.fittable_param:
2454                if item[1] == name1:
2455                    item[2].SetValue(value1)
2456                    item[5].SetValue("")
2457                    item[6].SetValue("")
2458                    # Disable for array
2459                    if disp_name.lower() == "array":
2460                        item[0].SetValue(False)
2461                        item[0].Disable()
2462                        item[2].Disable()
2463                        item[3].Show(False)
2464                        item[4].Show(False)
2465                        item[5].Disable()
2466                        item[6].Disable()
2467                    else:
2468                        item[0].Enable()
2469                        item[2].Enable()
[6c382da]2470                        item[3].Show(True)
2471                        item[4].Show(True)
[f0d720b]2472                        item[5].Enable()
2473                        item[6].Enable()
2474                    break
2475            # Reset fixed polydispersion params
2476            for item in self.fixed_param:
2477                if item[1] == name2:
[5ce7f17]2478                    item[2].SetValue(value2)
[f0d720b]2479                    # Disable Npts for array
2480                    if disp_name.lower() == "array":
2481                        item[2].Disable()
2482                    else:
2483                        item[2].Enable()
2484                if item[1] == name3:
2485                    item[2].SetValue(value3)
2486                    # Disable Nsigs for array
2487                    if disp_name.lower() == "array":
2488                        item[2].Disable()
2489                    else:
2490                        item[2].Enable()
[5ce7f17]2491
[4c3be25]2492            # Make sure the check box updated
2493            self.get_all_checked_params()
[f0d720b]2494
2495            # update params
2496            self._update_paramv_on_fit()
2497            # draw
2498            self._draw_model()
2499            self.Refresh()
[6ed67db]2500        except Exception:
2501            logging.error(traceback.format_exc())
[f0d720b]2502            # Error msg
2503            msg = "Error occurred:"
2504            msg += " Could not select the distribution function..."
2505            msg += " Please select another distribution function."
2506            disp_box.SetBackgroundColour("pink")
2507            # Focus on Fit button so that users can see the pinky box
2508            self.btFit.SetFocus()
2509            wx.PostEvent(self._manager.parent,
2510                         StatusEvent(status=msg, info="error"))
[5ce7f17]2511
[f0d720b]2512    def _set_array_disp(self, name=None, disp=None):
2513        """
2514        Set array dispersion
[5ce7f17]2515
[f0d720b]2516        :param name: name of the parameter for the dispersion to be set
2517        :param disp: the polydisperion object
2518        """
2519        # The user wants this parameter to be averaged.
2520        # Pop up the file selection dialog.
2521        path = self._selectDlg()
2522        # Array data
2523        values = []
2524        weights = []
2525        # If nothing was selected, just return
2526        if path is None:
2527            self.disp_cb_dict[name].SetValue(False)
[c8e1996]2528            # self.noDisper_rbox.SetValue(True)
[f0d720b]2529            return
2530        self._default_save_location = os.path.dirname(path)
[c8e1996]2531        if self._manager is not None:
[5ce7f17]2532            self._manager.parent._default_save_location = \
[f0d720b]2533                             self._default_save_location
2534
2535        basename = os.path.basename(path)
2536        values, weights = self.read_file(path)
[5ce7f17]2537
[f0d720b]2538        # If any of the two arrays is empty, notify the user that we won't
2539        # proceed
2540        if len(self.param_toFit) > 0:
2541            if name in self.param_toFit:
2542                self.param_toFit.remove(name)
2543
2544        # Tell the user that we are about to apply the distribution
2545        msg = "Applying loaded %s distribution: %s" % (name, path)
2546        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2547        self._set_array_disp_model(name=name, disp=disp,
[c8e1996]2548                                   values=values, weights=weights)
[f0d720b]2549        return basename
[5ce7f17]2550
[f0d720b]2551    def _set_array_disp_model(self, name=None, disp=None,
2552                              values=[], weights=[]):
2553        """
2554        Set array dispersion model
[5ce7f17]2555
[f0d720b]2556        :param name: name of the parameter for the dispersion to be set
2557        :param disp: the polydisperion object
2558        """
2559        disp.set_weights(values, weights)
2560        self._disp_obj_dict[name] = disp
2561        self.model.set_dispersion(name.split('.')[0], disp)
[6c382da]2562        self.state._disp_obj_dict[name] = disp.type
[f0d720b]2563        self.values[name] = values
2564        self.weights[name] = weights
2565        # Store the object to make it persist outside the
2566        # scope of this method
[c8e1996]2567        # TODO: refactor model to clean this up?
[f0d720b]2568        self.state.values = {}
2569        self.state.weights = {}
2570        self.state.values = copy.deepcopy(self.values)
2571        self.state.weights = copy.deepcopy(self.weights)
2572
2573        # Set the new model as the dispersion object for the
[c8e1996]2574        # selected parameter
2575        # self.model.set_dispersion(p, disp_model)
[f0d720b]2576        # Store a reference to the weights in the model object
[c8e1996]2577        # so that
[f0d720b]2578        # it's not lost when we use the model within another thread.
2579        self.state.model = self.model.clone()
2580        self.model._persistency_dict[name.split('.')[0]] = \
[c8e1996]2581            [values, weights]
[f0d720b]2582        self.state.model._persistency_dict[name.split('.')[0]] = \
[c8e1996]2583            [values, weights]
[5ce7f17]2584
[f0d720b]2585    def _del_array_values(self, name=None):
2586        """
2587        Reset array dispersion
[5ce7f17]2588
[f0d720b]2589        :param name: name of the parameter for the dispersion to be set
2590        """
2591        # Try to delete values and weight of the names array dic if exists
2592        try:
[6ed67db]2593            if name in self.values:
2594                del self.values[name]
2595                del self.weights[name]
2596                # delete all other dic
2597                del self.state.values[name]
2598                del self.state.weights[name]
2599                del self.model._persistency_dict[name.split('.')[0]]
2600                del self.state.model._persistency_dict[name.split('.')[0]]
[7673ecd]2601        except Exception:
2602            logging.error(traceback.format_exc())
[5ce7f17]2603
[f0d720b]2604    def _lay_out(self):
2605        """
2606        returns self.Layout
[5ce7f17]2607
[f0d720b]2608        :Note: Mac seems to like this better when self.
2609            Layout is called after fitting.
2610        """
2611        self._sleep4sec()
2612        self.Layout()
2613        return
[5ce7f17]2614
[f0d720b]2615    def _sleep4sec(self):
2616        """
2617            sleep for 1 sec only applied on Mac
2618            Note: This 1sec helps for Mac not to crash on self.
2619            Layout after self._draw_model
2620        """
[505706a]2621        if ON_MAC:
[f0d720b]2622            time.sleep(1)
[5ce7f17]2623
[f0d720b]2624    def _find_polyfunc_selection(self, disp_func=None):
2625        """
2626        FInd Comboox selection from disp_func
[5ce7f17]2627
[f0d720b]2628        :param disp_function: dispersion distr. function
2629        """
2630        # Find the selection
[a0373d5]2631        if disp_func is not None:
2632            try:
2633                return POLYDISPERSITY_MODELS.values().index(disp_func.__class__)
2634            except ValueError:
2635                pass  # Fall through to default class
2636        return POLYDISPERSITY_MODELS.keys().index('gaussian')
[5ce7f17]2637
[f0d720b]2638    def on_reset_clicked(self, event):
2639        """
2640        On 'Reset' button  for Q range clicked
2641        """
2642        flag = True
[c8e1996]2643        # For 3 different cases: Data2D, Data1D, and theory
2644        if self.model is None:
[f0d720b]2645            msg = "Please select a model first..."
2646            wx.MessageBox(msg, 'Info')
2647            flag = False
2648            return
[5ce7f17]2649
[f0d720b]2650        elif self.data.__class__.__name__ == "Data2D":
2651            data_min = 0
2652            x = max(math.fabs(self.data.xmin), math.fabs(self.data.xmax))
2653            y = max(math.fabs(self.data.ymin), math.fabs(self.data.ymax))
2654            self.qmin_x = data_min
[5ce7f17]2655            self.qmax_x = math.sqrt(x * x + y * y)
[c8e1996]2656            # self.data.mask = numpy.ones(len(self.data.data),dtype=bool)
[f0d720b]2657            # check smearing
2658            if not self.disable_smearer.GetValue():
[c8e1996]2659                # set smearing value whether or
[f0d720b]2660                # not the data contain the smearing info
2661                if self.pinhole_smearer.GetValue():
2662                    flag = self.update_pinhole_smear()
2663                else:
2664                    flag = True
[5ce7f17]2665
[c8e1996]2666        elif self.data is None:
[f0d720b]2667            self.qmin_x = _QMIN_DEFAULT
2668            self.qmax_x = _QMAX_DEFAULT
2669            self.num_points = _NPTS_DEFAULT
2670            self.state.npts = self.num_points
[5ce7f17]2671
[f0d720b]2672        elif self.data.__class__.__name__ != "Data2D":
2673            self.qmin_x = min(self.data.x)
2674            self.qmax_x = max(self.data.x)
2675            # check smearing
2676            if not self.disable_smearer.GetValue():
[c8e1996]2677                # set smearing value whether or
[f0d720b]2678                # not the data contain the smearing info
2679                if self.slit_smearer.GetValue():
2680                    flag = self.update_slit_smear()
2681                elif self.pinhole_smearer.GetValue():
2682                    flag = self.update_pinhole_smear()
2683                else:
2684                    flag = True
2685        else:
2686            flag = False
[5ce7f17]2687
[c8e1996]2688        if flag is False:
[f0d720b]2689            msg = "Cannot Plot :Must enter a number!!!  "
2690            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2691        else:
2692            # set relative text ctrs.
2693            self.qmin.SetValue(str(self.qmin_x))
2694            self.qmax.SetValue(str(self.qmax_x))
2695            self.show_npts2fit()
2696            # At this point, some button and variables satatus (disabled?)
2697            # should be checked such as color that should be reset to
2698            # white in case that it was pink.
2699            self._onparamEnter_helper()
2700
2701        self.save_current_state()
2702        self.state.qmin = self.qmin_x
2703        self.state.qmax = self.qmax_x
[5ce7f17]2704
[c8e1996]2705        # reset the q range values
[f0d720b]2706        self._reset_plotting_range(self.state)
2707        self._draw_model()
[5ce7f17]2708
[f0d720b]2709    def select_log(self, event):
2710        """
2711        Log checked to generate log spaced points for theory model
2712        """
2713
2714    def get_images(self):
2715        """
2716        Get the images of the plots corresponding this panel for report
[5ce7f17]2717
[f0d720b]2718        : return graphs: list of figures
2719        : Need Move to guiframe
2720        """
2721        # set list of graphs
2722        graphs = []
2723        canvases = []
2724        res_item = None
2725        # call gui_manager
2726        gui_manager = self._manager.parent
2727        # loops through the panels [dic]
2728        for _, item2 in gui_manager.plot_panels.iteritems():
2729            data_title = self.data.group_id
2730            # try to get all plots belonging to this control panel
2731            try:
2732                g_id = item2.group_id
2733                if g_id == data_title or \
2734                        str(g_id).count("res" + str(self.graph_id)) or \
2735                        str(g_id).count(str(self.uid)) > 0:
2736                    if str(g_id).count("res" + str(self.graph_id)) > 0:
2737                        res_item = [item2.figure, item2.canvas]
2738                    else:
2739                        # append to the list
2740                        graphs.append(item2.figure)
[5ce7f17]2741                        canvases.append(item2.canvas)
[7673ecd]2742            except Exception:
[f0d720b]2743                # Not for control panels
[7673ecd]2744                logging.error(traceback.format_exc())
[f0d720b]2745        # Make sure the resduals plot goes to the last
[c8e1996]2746        if res_item is not None:
[f0d720b]2747            graphs.append(res_item[0])
2748            canvases.append(res_item[1])
2749        # return the list of graphs
2750        return graphs, canvases
2751
[5ce7f17]2752    def on_function_help_clicked(self, event):
2753        """
2754        Function called when 'Help' button is pressed next to model
2755        of interest.  This calls DocumentationWindow from
2756        documentation_window.py. It will load the top level of the model
2757        help documenation sphinx generated html if no model is presented.
2758        If a model IS present then if documention for that model exists
2759        it will load to that  point otherwise again it will go to the top.
2760        For Wx2.8 and below is used (i.e. non-released through installer)
2761        a browser is loaded and the top of the model documentation only is
2762        accessible because webbrowser module does not pass anything after
2763        the # to the browser.
2764
[c8e1996]2765        :param event: on Help Button pressed event
[5ce7f17]2766        """
2767
[c8e1996]2768        if self.model is not None:
[3db44fb]2769            name = self.formfactorbox.GetValue()
[c8e1996]2770            _TreeLocation = 'user/models/' + name.lower()+'.html'
[6f16e25]2771            _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
[15fb4fa]2772                                              "", name + " Help")
[5ce7f17]2773        else:
[15fb4fa]2774            _TreeLocation = 'user/index.html'
[6f16e25]2775            _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
2776                                              "", "General Model Help")
[5ce7f17]2777
[f0d720b]2778    def on_model_help_clicked(self, event):
2779        """
[5ce7f17]2780        Function called when 'Description' button is pressed next to model
2781        of interest.  This calls the Description embedded in the model. This
2782        should work with either Wx2.8 and lower or higher. If no model is
2783        selected it will give the message that a model must be chosen first
2784        in the box that would normally contain the description.  If a badly
2785        behaved model is encountered which has no description then it will
2786        give the message that none is available.
2787
[c8e1996]2788        :param event: on Description Button pressed event
[f0d720b]2789        """
[5ce7f17]2790
[c8e1996]2791        if self.model is None:
[5ce7f17]2792            name = 'index.html'
[f0d720b]2793        else:
2794            name = self.formfactorbox.GetValue()
[5ce7f17]2795
2796        msg = 'Model description:\n'
2797        info = "Info"
[c8e1996]2798        if self.model is not None:
2799            # frame.Destroy()
[5ce7f17]2800            if str(self.model.description).rstrip().lstrip() == '':
2801                msg += "Sorry, no information is available for this model."
[f0d720b]2802            else:
[5ce7f17]2803                msg += self.model.description + '\n'
2804            wx.MessageBox(msg, info)
2805        else:
2806            msg += "You must select a model to get information on this"
2807            wx.MessageBox(msg, info)
2808
[7116dffd]2809    def _on_mag_angle_help(self, event):
[5ce7f17]2810        """
2811        Bring up Magnetic Angle definition bmp image whenever the ? button
2812        is clicked. Calls DocumentationWindow with the path of the location
2813        within the documentation tree (after /doc/ ....". When using old
2814        versions of Wx (i.e. before 2.9 and therefore not part of release
2815        versions distributed via installer) it brings up an image viewer
2816        box which allows the user to click through the rest of the images in
2817        the directory.  Not ideal but probably better than alternative which
2818        would bring up the entire discussion of how magnetic models work?
2819        Specially since it is not likely to be accessed.  The normal release
2820        versions bring up the normal image box.
2821
2822        :param evt: Triggers on clicking ? in Magnetic Angles? box
2823        """
2824
2825        _TreeLocation = "_images/M_angles_pic.bmp"
[6f16e25]2826        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
[3db44fb]2827                                          "Magnetic Angle Defintions")
[f0d720b]2828
[7116dffd]2829    def _on_mag_help(self, event):
[f0d720b]2830        """
[7116dffd]2831        Bring up Magnetic Angle definition bmp image whenever the ? button
2832        is clicked. Calls DocumentationWindow with the path of the location
2833        within the documentation tree (after /doc/ ....". When using old
2834        versions of Wx (i.e. before 2.9 and therefore not part of release
2835        versions distributed via installer) it brings up an image viewer
2836        box which allows the user to click through the rest of the images in
2837        the directory.  Not ideal but probably better than alternative which
2838        would bring up the entire discussion of how magnetic models work?
2839        Specially since it is not likely to be accessed.  The normal release
2840        versions bring up the normal image box.
2841
2842        :param evt: Triggers on clicking ? in Magnetic Angles? box
[f0d720b]2843        """
2844
[e4c897b]2845        _TreeLocation = "user/magnetism.html"
[6f16e25]2846        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
[7116dffd]2847                                          "Polarized Beam/Magnetc Help")
[f0d720b]2848
[5ce7f17]2849    def _on_mag_on(self, event):
[f0d720b]2850        """
2851        Magnetic Parameters ON/OFF
2852        """
2853        button = event.GetEventObject()
2854
2855        if button.GetLabel().count('ON') > 0:
2856            self.magnetic_on = True
2857            button.SetLabel("Magnetic OFF")
2858            m_value = 1.0e-06
2859            for key in self.model.magnetic_params:
2860                if key.count('M0') > 0:
2861                    self.model.setParam(key, m_value)
2862                    m_value += 0.5e-06
2863        else:
2864            self.magnetic_on = False
2865            button.SetLabel("Magnetic ON")
2866            for key in self.model.magnetic_params:
2867                if key.count('M0') > 0:
[c8e1996]2868                    # reset mag value to zero fo safety
[f0d720b]2869                    self.model.setParam(key, 0.0)
[5ce7f17]2870
2871        self.Show(False)
[f0d720b]2872        self.set_model_param_sizer(self.model)
[c8e1996]2873        # self._set_sizer_dispersion()
[f0d720b]2874        self.state.magnetic_on = self.magnetic_on
2875        self.SetupScrolling()
2876        self.Show(True)
[5ce7f17]2877
[f0d720b]2878    def on_pd_help_clicked(self, event):
2879        """
[5ce7f17]2880        Bring up Polydispersity Documentation whenever the ? button is clicked.
2881        Calls DocumentationWindow with the path of the location within the
2882        documentation tree (after /doc/ ....".  Note that when using old
2883        versions of Wx (before 2.9) and thus not the release version of
2884        istallers, the help comes up at the top level of the file as
2885        webbrowser does not pass anything past the # to the browser when it is
2886        running "file:///...."
2887
[c8e1996]2888        :param event: Triggers on clicking ? in polydispersity box
[5ce7f17]2889        """
[f0d720b]2890
[eb04d59]2891        _TreeLocation = "user/sasgui/perspectives/fitting/pd_help.html"
[7801df8]2892        _PageAnchor = ""
[6f16e25]2893        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
[3db44fb]2894                                          _PageAnchor, "Polydispersity Help")
[5ce7f17]2895
[f0d720b]2896    def on_left_down(self, event):
2897        """
2898        Get key stroke event
2899        """
2900        # Figuring out key combo: Cmd for copy, Alt for paste
2901        if event.CmdDown() and event.ShiftDown():
2902            self.get_paste()
2903        elif event.CmdDown():
2904            self.get_copy()
2905        else:
2906            event.Skip()
2907            return
2908        # make event free
2909        event.Skip()
[5ce7f17]2910
[f0d720b]2911    def get_copy(self):
2912        """
2913        Get copy params to clipboard
2914        """
2915        content = self.get_copy_params()
2916        flag = self.set_clipboard(content)
2917        self._copy_info(flag)
2918        return flag
[5ce7f17]2919
[f0d720b]2920    def get_copy_params(self):
2921        """
2922        Get the string copies of the param names and values in the tap
2923        """
2924        content = 'sasview_parameter_values:'
2925        # Do it if params exist
[c8e1996]2926        if self.parameters:
[5ce7f17]2927
[f0d720b]2928            # go through the parameters
2929            strings = self._get_copy_helper(self.parameters,
2930                                           self.orientation_params)
2931            content += strings
[5ce7f17]2932
[f0d720b]2933            # go through the fittables
2934            strings = self._get_copy_helper(self.fittable_param,
2935                                           self.orientation_params_disp)
2936            content += strings
2937
2938            # go through the fixed params
2939            strings = self._get_copy_helper(self.fixed_param,
2940                                           self.orientation_params_disp)
2941            content += strings
[5ce7f17]2942
[f0d720b]2943            # go through the str params
2944            strings = self._get_copy_helper(self.str_parameters,
2945                                           self.orientation_params)
2946            content += strings
2947            return content
2948        else:
2949            return False
2950
2951    def get_copy_excel(self):
2952        """
2953        Get copy params to clipboard
2954        """
2955        content = self.get_copy_params_excel()
2956        flag = self.set_clipboard(content)
2957        self._copy_info(flag)
2958        return flag
2959
2960    def get_copy_params_excel(self):
2961        """
2962        Get the string copies of the param names and values in the tap
2963        """
2964        content = ''
2965
2966        crlf = chr(13) + chr(10)
2967        tab = chr(9)
2968
2969        # Do it if params exist
[c8e1996]2970        if self.parameters:
[f0d720b]2971
2972            for param in self.parameters:
[c8e1996]2973                content += param[1]  # parameter name
[f0d720b]2974                content += tab
[5ce7f17]2975                content += param[1] + "_err"
[f0d720b]2976                content += tab
2977
2978            content += crlf
2979
[c8e1996]2980            # row of values and errors...
[f0d720b]2981            for param in self.parameters:
[c8e1996]2982                content += param[2].GetValue()  # value
[5ce7f17]2983                content += tab
[c8e1996]2984                content += param[4].GetValue()  # error
[5ce7f17]2985                content += tab
[f0d720b]2986
2987            return content
2988        else:
2989            return False
2990
2991    def get_copy_latex(self):
2992        """
2993        Get copy params to clipboard
2994        """
2995        content = self.get_copy_params_latex()
2996        flag = self.set_clipboard(content)
2997        self._copy_info(flag)
2998        return flag
2999
3000    def get_copy_params_latex(self):
3001        """
3002        Get the string copies of the param names and values in the tap
3003        """
3004        content = '\\begin{table}'
3005        content += '\\begin{tabular}[h]'
3006
3007        crlf = chr(13) + chr(10)
3008        tab = chr(9)
3009
3010        # Do it if params exist
[c8e1996]3011        if self.parameters:
[f0d720b]3012
3013            content += '{|'
3014            for param in self.parameters:
3015                content += 'l|l|'
3016            content += '}\hline'
3017            content += crlf
3018
3019            for index, param in enumerate(self.parameters):
[c8e1996]3020                content += param[1].replace('_', '\_')  # parameter name
[f0d720b]3021                content += ' & '
[5ce7f17]3022                content += param[1].replace('_', '\_') + "\_err"
3023                if index < len(self.parameters) - 1:
[f0d720b]3024                    content += ' & '
3025            content += '\\\\ \\hline'
3026            content += crlf
3027
[c8e1996]3028            # row of values and errors...
[f0d720b]3029            for index, param in enumerate(self.parameters):
[c8e1996]3030                content += param[2].GetValue()  # parameter value
[f0d720b]3031                content += ' & '
[c8e1996]3032                content += param[4].GetValue()  # parameter error
[5ce7f17]3033                if index < len(self.parameters) - 1:
[f0d720b]3034                    content += ' & '
3035            content += '\\\\ \\hline'
3036            content += crlf
3037
3038            content += '\\end{tabular}'
3039            content += '\\end{table}'
3040            return content
3041        else:
3042            return False
3043
3044    def set_clipboard(self, content=None):
3045        """
3046        Put the string to the clipboard
3047        """
3048        if not content:
3049            return False
3050        if wx.TheClipboard.Open():
3051            wx.TheClipboard.SetData(wx.TextDataObject(str(content)))
3052            wx.TheClipboard.Close()
3053            return True
3054        return None
[5ce7f17]3055
[f0d720b]3056    def _get_copy_helper(self, param, orient_param):
3057        """
3058        Helping get value and name of the params
[5ce7f17]3059
[f0d720b]3060        : param param:  parameters
3061        : param orient_param = oritational params
3062        : return content: strings [list] [name,value:....]
3063        """
3064        content = ''
[5223602]3065        bound_hi = ''
3066        bound_lo = ''
[f0d720b]3067        # go through the str params
3068        for item in param:
3069            # copy only the params shown
3070            if not item[2].IsShown():
3071                continue
3072            disfunc = ''
3073            try:
3074                if item[7].__class__.__name__ == 'ComboBox':
3075                    disfunc = str(item[7].GetValue())
[7673ecd]3076            except Exception:
3077                logging.error(traceback.format_exc())
[5ce7f17]3078
[f0d720b]3079            # 2D
3080            if self.data.__class__.__name__ == "Data2D":
3081                try:
3082                    check = item[0].GetValue()
[7673ecd]3083                except Exception:
[f0d720b]3084                    check = None
3085                name = item[1]
3086                value = item[2].GetValue()
3087            # 1D
3088            else:
[c8e1996]3089                # for 1D all parameters except orientation
[f0d720b]3090                if not item[1] in orient_param:
3091                    try:
3092                        check = item[0].GetValue()
3093                    except:
3094                        check = None
3095                    name = item[1]
3096                    value = item[2].GetValue()
3097
[5223602]3098            # Bounds
3099            try:
3100                bound_lo = item[5].GetValue()
3101                bound_hi = item[6].GetValue()
3102            except Exception:
3103                # harmless - need to just pass
3104                pass
3105
[f0d720b]3106            # add to the content
3107            if disfunc != '':
[5ce7f17]3108
[f0d720b]3109                disfunc = ',' + disfunc
3110            # Need to support array func for copy/paste
3111            try:
3112                if disfunc.count('array') > 0:
3113                    disfunc += ','
3114                    for val in self.values[name]:
3115                        disfunc += ' ' + str(val)
3116                    disfunc += ','
3117                    for weight in self.weights[name]:
3118                        disfunc += ' ' + str(weight)
[7673ecd]3119            except Exception:
3120                logging.error(traceback.format_exc())
[c8e1996]3121            content += name + ',' + str(check) + ',' + value + disfunc + ',' + \
3122                       bound_lo + ',' + bound_hi + ':'
[f0d720b]3123
3124        return content
[5ce7f17]3125
[f0d720b]3126    def get_clipboard(self):
3127        """
3128        Get strings in the clipboard
3129        """
3130        text = ""
3131        # Get text from the clip board
3132        if wx.TheClipboard.Open():
3133            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
3134                data = wx.TextDataObject()
3135                # get wx dataobject
3136                success = wx.TheClipboard.GetData(data)
3137                # get text
3138                if success:
3139                    text = data.GetText()
3140                else:
3141                    text = ''
3142            # close clipboard
3143            wx.TheClipboard.Close()
3144        return text
[5ce7f17]3145
[f0d720b]3146    def get_paste(self):
3147        """
3148        Paste params from the clipboard
3149        """
3150        text = self.get_clipboard()
3151        flag = self.get_paste_params(text)
3152        self._copy_info(flag)
3153        return flag
[5ce7f17]3154
[f0d720b]3155    def get_paste_params(self, text=''):
3156        """
3157        Get the string copies of the param names and values in the tap
3158        """
3159        context = {}
3160        # put the text into dictionary
3161        lines = text.split(':')
3162        if lines[0] != 'sasview_parameter_values':
3163            self._copy_info(False)
3164            return False
3165        for line in lines[1:-1]:
3166            if len(line) != 0:
3167                item = line.split(',')
3168                check = item[1]
3169                name = item[0]
3170                value = item[2]
3171                # Transfer the text to content[dictionary]
3172                context[name] = [check, value]
[5223602]3173
3174                # limits
3175                limit_lo = item[3]
3176                context[name].append(limit_lo)
3177                limit_hi = item[4]
3178                context[name].append(limit_hi)
3179
[f0d720b]3180            # ToDo: PlugIn this poly disp function for pasting
3181            try:
[5223602]3182                poly_func = item[5]
[f0d720b]3183                context[name].append(poly_func)
3184                try:
3185                    # take the vals and weights for  array
[5223602]3186                    array_values = item[6].split(' ')
3187                    array_weights = item[7].split(' ')
[f0d720b]3188                    val = [float(a_val) for a_val in array_values[1:]]
3189                    weit = [float(a_weit) for a_weit in array_weights[1:]]
[5ce7f17]3190
[f0d720b]3191                    context[name].append(val)
3192                    context[name].append(weit)
3193                except:
3194                    raise
3195            except:
3196                poly_func = ''
3197                context[name].append(poly_func)
3198
3199        # Do it if params exist
[c8e1996]3200        if self.parameters:
[f0d720b]3201            # go through the parameters
3202            self._get_paste_helper(self.parameters,
3203                                   self.orientation_params, context)
3204
3205            # go through the fittables
3206            self._get_paste_helper(self.fittable_param,
3207                                   self.orientation_params_disp,
3208                                   context)
3209
3210            # go through the fixed params
3211            self._get_paste_helper(self.fixed_param,
3212                                   self.orientation_params_disp, context)
[5ce7f17]3213
[f0d720b]3214            # go through the str params
3215            self._get_paste_helper(self.str_parameters,
3216                                   self.orientation_params, context)
[5ce7f17]3217
[f0d720b]3218            return True
3219        return None
[5ce7f17]3220
[f0d720b]3221    def _get_paste_helper(self, param, orient_param, content):
3222        """
3223        Helping set values of the params
[5ce7f17]3224
[f0d720b]3225        : param param:  parameters
3226        : param orient_param: oritational params
3227        : param content: dictionary [ name, value: name1.value1,...]
3228        """
3229        # go through the str params
3230        for item in param:
3231            # 2D
3232            if self.data.__class__.__name__ == "Data2D":
3233                name = item[1]
3234                if name in content.keys():
[5223602]3235                    values = content[name]
3236                    check = values[0]
3237                    pd = values[1]
3238
[f0d720b]3239                    if name.count('.') > 0:
[6c382da]3240                        # If this is parameter.width, then pd may be a floating
3241                        # point value or it may be an array distribution.
3242                        # Nothing to do for parameter.npts or parameter.nsigmas.
[f0d720b]3243                        try:
3244                            float(pd)
[6c382da]3245                            if name.endswith('.npts'):
3246                                pd = int(pd)
3247                        except Exception:
[c8e1996]3248                            # continue
[f0d720b]3249                            if not pd and pd != '':
3250                                continue
3251                    item[2].SetValue(str(pd))
3252                    if item in self.fixed_param and pd == '':
3253                        # Only array func has pd == '' case.
3254                        item[2].Enable(False)
[6c382da]3255                    else:
3256                        item[2].Enable(True)
[f0d720b]3257                    if item[2].__class__.__name__ == "ComboBox":
3258                        if content[name][1] in self.model.fun_list:
3259                            fun_val = self.model.fun_list[content[name][1]]
3260                            self.model.setParam(name, fun_val)
[5223602]3261                    try:
3262                        item[5].SetValue(str(values[-3]))
3263                        item[6].SetValue(str(values[-2]))
3264                    except Exception:
3265                        # passing as harmless non-update
3266                        pass
[5ce7f17]3267
[f0d720b]3268                    value = content[name][1:]
3269                    self._paste_poly_help(item, value)
3270                    if check == 'True':
3271                        is_true = True
3272                    elif check == 'False':
3273                        is_true = False
3274                    else:
3275                        is_true = None
[c8e1996]3276                    if is_true is not None:
[f0d720b]3277                        item[0].SetValue(is_true)
3278            # 1D
3279            else:
[c8e1996]3280                # for 1D all parameters except orientation
[f0d720b]3281                if not item[1] in orient_param:
3282                    name = item[1]
3283                    if name in content.keys():
3284                        check = content[name][0]
3285                        # Avoid changing combox content
3286                        value = content[name][1:]
3287                        pd = value[0]
3288                        if name.count('.') > 0:
[c8e1996]3289                            # If this is parameter.width, then pd may be a
3290                            # floating point value or it may be an array
3291                            # distribution. Nothing to do for parameter.npts or
3292                            # parameter.nsigmas.
[f0d720b]3293                            try:
3294                                pd = float(pd)
[6c382da]3295                                if name.endswith('.npts'):
3296                                    pd = int(pd)
[f0d720b]3297                            except:
[c8e1996]3298                                # continue
[f0d720b]3299                                if not pd and pd != '':
3300                                    continue
3301                        item[2].SetValue(str(pd))
3302                        if item in self.fixed_param and pd == '':
3303                            # Only array func has pd == '' case.
3304                            item[2].Enable(False)
[6c382da]3305                        else:
3306                            item[2].Enable(True)
[f0d720b]3307                        if item[2].__class__.__name__ == "ComboBox":
3308                            if value[0] in self.model.fun_list:
3309                                fun_val = self.model.fun_list[value[0]]
3310                                self.model.setParam(name, fun_val)
3311                                # save state
[5223602]3312                        try:
3313                            item[5].SetValue(str(value[-3]))
3314                            item[6].SetValue(str(value[-2]))
3315                        except Exception:
3316                            # passing as harmless non-update
3317                            pass
3318
[f0d720b]3319                        self._paste_poly_help(item, value)
3320                        if check == 'True':
3321                            is_true = True
3322                        elif check == 'False':
3323                            is_true = False
3324                        else:
3325                            is_true = None
[c8e1996]3326                        if is_true is not None:
[f0d720b]3327                            item[0].SetValue(is_true)
[5ce7f17]3328
[01cfd13]3329        self.select_param(event=None)
3330        self.Refresh()
3331
[f0d720b]3332    def _paste_poly_help(self, item, value):
3333        """
3334        Helps get paste for poly function
[5ce7f17]3335
[6c382da]3336        *item* is the parameter name
3337
3338        *value* depends on which parameter is being processed, and whether it
3339        has array polydispersity.
3340
3341        For parameters without array polydispersity:
3342
3343            parameter => ['FLOAT', '']
3344            parameter.width => ['FLOAT', 'DISTRIBUTION', '']
3345            parameter.npts => ['FLOAT', '']
3346            parameter.nsigmas => ['FLOAT', '']
3347
3348        For parameters with array polydispersity:
3349
3350            parameter => ['FLOAT', '']
3351            parameter.width => ['FILENAME', 'array', [x1, ...], [w1, ...]]
3352            parameter.npts => ['FLOAT', '']
3353            parameter.nsigmas => ['FLOAT', '']
[f0d720b]3354        """
[6c382da]3355        # Do nothing if not setting polydispersity
[5223602]3356        if len(value[3]) == 0:
[6c382da]3357            return
[5ce7f17]3358
[6c382da]3359        try:
3360            name = item[7].Name
3361            param_name = name.split('.')[0]
3362            item[7].SetValue(value[1])
3363            selection = item[7].GetCurrentSelection()
3364            dispersity = item[7].GetClientData(selection)
3365            disp_model = dispersity()
3366
3367            if value[1] == 'array':
3368                pd_vals = numpy.array(value[2])
3369                pd_weights = numpy.array(value[3])
3370                if len(pd_vals) == 0 or len(pd_vals) != len(pd_weights):
3371                    msg = ("bad array distribution parameters for %s"
3372                           % param_name)
3373                    raise ValueError(msg)
3374                self._set_disp_cb(True, item=item)
3375                self._set_array_disp_model(name=name,
3376                                           disp=disp_model,
3377                                           values=pd_vals,
3378                                           weights=pd_weights)
3379            else:
3380                self._set_disp_cb(False, item=item)
3381                self._disp_obj_dict[name] = disp_model
3382                self.model.set_dispersion(param_name, disp_model)
3383                self.state._disp_obj_dict[name] = disp_model.type
3384                # TODO: It's not an array, why update values and weights?
3385                self.model._persistency_dict[param_name] = \
3386                    [self.values, self.weights]
3387                self.state.values = self.values
3388                self.state.weights = self.weights
[5ce7f17]3389
[6c382da]3390        except Exception:
3391            logging.error(traceback.format_exc())
3392            print "Error in BasePage._paste_poly_help: %s" % \
[c8e1996]3393                  sys.exc_info()[1]
[6c382da]3394
3395    def _set_disp_cb(self, isarray, item):
[f0d720b]3396        """
3397        Set cb for array disp
3398        """
[6c382da]3399        if isarray:
3400            item[0].SetValue(False)
3401            item[0].Enable(False)
3402            item[2].Enable(False)
3403            item[3].Show(False)
3404            item[4].Show(False)
3405            item[5].SetValue('')
3406            item[5].Enable(False)
3407            item[6].SetValue('')
3408            item[6].Enable(False)
3409        else:
3410            item[0].Enable()
3411            item[2].Enable()
3412            item[3].Show(True)
3413            item[4].Show(True)
3414            item[5].Enable()
3415            item[6].Enable()
[5ce7f17]3416
[f0d720b]3417    def update_pinhole_smear(self):
3418        """
3419            Method to be called by sub-classes
3420            Moveit; This method doesn't belong here
3421        """
3422        print "BasicPage.update_pinhole_smear was called: skipping"
3423        return
3424
3425    def _read_category_info(self):
3426        """
3427        Reads the categories in from file
3428        """
3429        # # ILL mod starts here - July 2012 kieranrcampbell@gmail.com
3430        self.master_category_dict = defaultdict(list)
3431        self.by_model_dict = defaultdict(list)
3432        self.model_enabled_dict = defaultdict(bool)
[212bfc2]3433        categorization_file = CategoryInstaller.get_user_file()
3434        with open(categorization_file, 'rb') as f:
3435            self.master_category_dict = json.load(f)
3436        self._regenerate_model_dict()
[f0d720b]3437
3438    def _regenerate_model_dict(self):
3439        """
[5ce7f17]3440        regenerates self.by_model_dict which has each model name as the
[f0d720b]3441        key and the list of categories belonging to that model
3442        along with the enabled mapping
3443        """
3444        self.by_model_dict = defaultdict(list)
3445        for category in self.master_category_dict:
3446            for (model, enabled) in self.master_category_dict[category]:
3447                self.by_model_dict[model].append(category)
3448                self.model_enabled_dict[model] = enabled
[5ce7f17]3449
[f0d720b]3450    def _populate_listbox(self):
3451        """
3452        fills out the category list box
3453        """
[e92a352]3454        uncat_str = 'Plugin Models'
[f0d720b]3455        self._read_category_info()
3456
3457        self.categorybox.Clear()
3458        cat_list = sorted(self.master_category_dict.keys())
[c8e1996]3459        if uncat_str not in cat_list:
[f0d720b]3460            cat_list.append(uncat_str)
[5ce7f17]3461
[f0d720b]3462        for category in cat_list:
3463            if category != '':
3464                self.categorybox.Append(category)
3465
3466        if self.categorybox.GetSelection() == wx.NOT_FOUND:
3467            self.categorybox.SetSelection(0)
3468        else:
[c8e1996]3469            self.categorybox.SetSelection(
[f0d720b]3470                self.categorybox.GetSelection())
[c8e1996]3471        # self._on_change_cat(None)
[f0d720b]3472
3473    def _on_change_cat(self, event):
3474        """
3475        Callback for category change action
3476        """
3477        self.model_name = None
3478        category = self.categorybox.GetStringSelection()
[c8e1996]3479        if category is None:
[f0d720b]3480            return
3481        self.model_box.Clear()
3482
[e92a352]3483        if category == 'Plugin Models':
[f0d720b]3484            for model in self.model_list_box[category]:
3485                str_m = str(model).split(".")[0]
3486                self.model_box.Append(str_m)
3487
3488        else:
[5ce7f17]3489            for (model, enabled) in sorted(self.master_category_dict[category],
[c8e1996]3490                                           key=lambda name: name[0]):
[f0d720b]3491                if(enabled):
3492                    self.model_box.Append(model)
3493
3494    def _fill_model_sizer(self, sizer):
3495        """
3496        fill sizer containing model info
3497        """
[6f16e25]3498        # This should only be called once per fit tab
[c8e1996]3499        # print "==== Entering _fill_model_sizer"
3500        # Add model function Details button in fitpanel.
3501        # The following 3 lines are for Mac. Let JHC know before modifying...
[f0d720b]3502        title = "Model"
3503        self.formfactorbox = None
3504        self.multifactorbox = None
[6f16e25]3505        self.mbox_description = wx.StaticBox(self, wx.ID_ANY, str(title))
[f0d720b]3506        boxsizer1 = wx.StaticBoxSizer(self.mbox_description, wx.VERTICAL)
3507        sizer_cat = wx.BoxSizer(wx.HORIZONTAL)
3508        self.mbox_description.SetForegroundColour(wx.RED)
[6f16e25]3509        wx_id = self._ids.next()
3510        self.model_func = wx.Button(self, wx_id, 'Help', size=(80, 23))
3511        self.model_func.Bind(wx.EVT_BUTTON, self.on_function_help_clicked,
3512                             id=wx_id)
[5ce7f17]3513        self.model_func.SetToolTipString("Full Model Function Help")
[6f16e25]3514        wx_id = self._ids.next()
3515        self.model_help = wx.Button(self, wx_id, 'Description', size=(80, 23))
3516        self.model_help.Bind(wx.EVT_BUTTON, self.on_model_help_clicked,
3517                             id=wx_id)
[5ce7f17]3518        self.model_help.SetToolTipString("Short Model Function Description")
[6f16e25]3519        wx_id = self._ids.next()
3520        self.model_view = wx.Button(self, wx_id, "Show 2D", size=(80, 23))
3521        self.model_view.Bind(wx.EVT_BUTTON, self._onModel2D, id=wx_id)
[f0d720b]3522        hint = "toggle view of model from 1D to 2D  or 2D to 1D"
3523        self.model_view.SetToolTipString(hint)
[5ce7f17]3524
[6f16e25]3525        cat_set_box = wx.StaticBox(self, wx.ID_ANY, 'Category')
[f0d720b]3526        sizer_cat_box = wx.StaticBoxSizer(cat_set_box, wx.HORIZONTAL)
3527        sizer_cat_box.SetMinSize((200, 50))
[6f16e25]3528        self.categorybox = wx.ComboBox(self, wx.ID_ANY,
3529                                       style=wx.CB_READONLY)
[5ce7f17]3530        self.categorybox.SetToolTip(wx.ToolTip("Select a Category/Type"))
[f0d720b]3531        self._populate_listbox()
[6f16e25]3532        wx.EVT_COMBOBOX(self.categorybox, wx.ID_ANY, self._show_combox)
[c8e1996]3533        # self.shape_rbutton = wx.RadioButton(self, wx.ID_ANY, 'Shapes',
[f0d720b]3534        #                                     style=wx.RB_GROUP)
[c8e1996]3535        # self.shape_indep_rbutton = wx.RadioButton(self, wx.ID_ANY,
[f0d720b]3536        #                                          "Shape-Independent")
[c8e1996]3537        # self.struct_rbutton = wx.RadioButton(self, wx.ID_ANY,
[6f16e25]3538        #                                     "Structure Factor ")
[c8e1996]3539        # self.plugin_rbutton = wx.RadioButton(self, wx.ID_ANY,
[6f16e25]3540        #                                     "Uncategorized")
[5ce7f17]3541
[c8e1996]3542        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
[f0d720b]3543        #                   id=self.shape_rbutton.GetId())
[c8e1996]3544        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
[f0d720b]3545        #                    id=self.shape_indep_rbutton.GetId())
[c8e1996]3546        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
[f0d720b]3547        #                    id=self.struct_rbutton.GetId())
[c8e1996]3548        # self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
[f0d720b]3549        #                    id=self.plugin_rbutton.GetId())
[c8e1996]3550        # MAC needs SetValue
[5ce7f17]3551
[6f16e25]3552        show_cat_button = wx.Button(self, wx.ID_ANY, "Modify")
[f0d720b]3553        cat_tip = "Modify model categories \n"
3554        cat_tip += "(also accessible from the menu bar)."
[c8e1996]3555        show_cat_button.SetToolTip(wx.ToolTip(cat_tip))
[f0d720b]3556        show_cat_button.Bind(wx.EVT_BUTTON, self._on_modify_cat)
3557        sizer_cat_box.Add(self.categorybox, 1, wx.RIGHT, 3)
[c8e1996]3558        sizer_cat_box.Add((10, 10))
[f0d720b]3559        sizer_cat_box.Add(show_cat_button)
[c8e1996]3560        # self.shape_rbutton.SetValue(True)
[bac3988]3561
[f0d720b]3562        sizer_radiobutton = wx.GridSizer(2, 2, 5, 5)
[c8e1996]3563        # sizer_radiobutton.Add(self.shape_rbutton)
3564        # sizer_radiobutton.Add(self.shape_indep_rbutton)
3565        sizer_radiobutton.Add((5, 5))
[5ce7f17]3566        sizer_radiobutton.Add(self.model_view, 1, wx.RIGHT, 5)
[c8e1996]3567        # sizer_radiobutton.Add(self.plugin_rbutton)
3568        # sizer_radiobutton.Add(self.struct_rbutton)
3569        # sizer_radiobutton.Add((5,5))
[5ce7f17]3570        sizer_radiobutton.Add(self.model_help, 1, wx.RIGHT | wx.LEFT, 5)
[c8e1996]3571        # sizer_radiobutton.Add((5,5))
[5ce7f17]3572        sizer_radiobutton.Add(self.model_func, 1, wx.RIGHT, 5)
[f0d720b]3573        sizer_cat.Add(sizer_cat_box, 1, wx.LEFT, 2.5)
3574        sizer_cat.Add(sizer_radiobutton)
3575        sizer_selection = wx.BoxSizer(wx.HORIZONTAL)
3576        mutifactor_selection = wx.BoxSizer(wx.HORIZONTAL)
[5ce7f17]3577
[6f16e25]3578        self.text1 = wx.StaticText(self, wx.ID_ANY, "")
3579        self.text2 = wx.StaticText(self, wx.ID_ANY, "P(Q)*S(Q)")
3580        self.mutifactor_text = wx.StaticText(self, wx.ID_ANY, "No. of Shells: ")
3581        self.mutifactor_text1 = wx.StaticText(self, wx.ID_ANY, "")
3582        self.show_sld_button = wx.Button(self, wx.ID_ANY, "Show SLD Profile")
[f0d720b]3583        self.show_sld_button.Bind(wx.EVT_BUTTON, self._on_show_sld)
3584
[6f16e25]3585        self.formfactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
[5ce7f17]3586        self.formfactorbox.SetToolTip(wx.ToolTip("Select a Model"))
[c8e1996]3587        if self.model is not None:
[f0d720b]3588            self.formfactorbox.SetValue(self.model.name)
[6f16e25]3589        self.structurebox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
3590        self.multifactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
[f0d720b]3591        self.initialize_combox()
[6f16e25]3592        wx.EVT_COMBOBOX(self.formfactorbox, wx.ID_ANY, self._on_select_model)
[f0d720b]3593
[6f16e25]3594        wx.EVT_COMBOBOX(self.structurebox, wx.ID_ANY, self._on_select_model)
3595        wx.EVT_COMBOBOX(self.multifactorbox, wx.ID_ANY, self._on_select_model)
[c8e1996]3596        # check model type to show sizer
3597        if self.model is not None:
[f0d720b]3598            print "_set_model_sizer_selection: disabled."
[c8e1996]3599            # self._set_model_sizer_selection(self.model)
[5ce7f17]3600
[f0d720b]3601        sizer_selection.Add(self.text1)
3602        sizer_selection.Add((10, 5))
3603        sizer_selection.Add(self.formfactorbox)
3604        sizer_selection.Add((5, 5))
3605        sizer_selection.Add(self.text2)
3606        sizer_selection.Add((5, 5))
3607        sizer_selection.Add(self.structurebox)
[5ce7f17]3608
[f0d720b]3609        mutifactor_selection.Add((13, 5))
3610        mutifactor_selection.Add(self.mutifactor_text)
3611        mutifactor_selection.Add(self.multifactorbox)
3612        mutifactor_selection.Add((5, 5))
3613        mutifactor_selection.Add(self.mutifactor_text1)
3614        mutifactor_selection.Add((10, 5))
3615        mutifactor_selection.Add(self.show_sld_button)
3616
3617        boxsizer1.Add(sizer_cat)
3618        boxsizer1.Add((10, 10))
3619        boxsizer1.Add(sizer_selection)
3620        boxsizer1.Add((10, 10))
3621        boxsizer1.Add(mutifactor_selection)
[5ce7f17]3622
[f0d720b]3623        self._set_multfactor_combobox()
3624        self.multifactorbox.SetSelection(1)
3625        self.show_sld_button.Hide()
3626        sizer.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
3627        sizer.Layout()
[5ce7f17]3628
[f0d720b]3629    def on_smear_helper(self, update=False):
3630        """
3631        Help for onSmear if implemented
[5ce7f17]3632
[f0d720b]3633        :param update: force or not to update
3634        """
3635    def reset_page(self, state, first=False):
3636        """
3637        reset the state  if implemented
3638        """
3639    def onSmear(self, event):
3640        """
[5ce7f17]3641        Create a smear object if implemented
[f0d720b]3642        """
3643    def onPinholeSmear(self, event):
3644        """
3645        Create a custom pinhole smear object if implemented
3646        """
3647    def onSlitSmear(self, event):
3648        """
3649        Create a custom slit smear object if implemented
3650        """
3651    def update_slit_smear(self):
3652        """
3653        called by kill_focus on pinhole TextCntrl
3654        to update the changes if implemented
3655        """
3656    def select_param(self, event):
3657        """
3658        Select TextCtrl  checked if implemented
3659        """
3660    def set_data(self, data=None):
3661        """
3662        Sets data if implemented
3663        """
3664    def _is_2D(self):
3665        """
3666        Check if data_name is Data2D if implemented
3667        """
3668    def _on_select_model(self, event=None):
3669        """
3670        call back for model selection if implemented
3671        """
3672    def get_weight_flag(self):
3673        """
3674        Get flag corresponding to a given weighting dI data if implemented
3675        """
3676    def _set_sizer_dispersion(self):
3677        """
3678        draw sizer for dispersity if implemented
3679        """
3680    def get_all_checked_params(self):
3681        """
3682        Found all parameters current check and add them to list of parameters
3683        to fit if implemented
3684        """
3685    def show_npts2fit(self):
3686        """
3687        setValue Npts for fitting if implemented
3688        """
3689    def _onModel2D(self, event):
3690        """
3691        toggle view of model from 1D to 2D  or 2D from 1D if implemented
3692        """
[373d4ee]3693
[c8e1996]3694
[373d4ee]3695class ModelTextCtrl(wx.TextCtrl):
3696    """
3697    Text control for model and fit parameters.
3698    Binds the appropriate events for user interactions.
3699    Default callback methods can be overwritten on initialization
3700
3701    :param kill_focus_callback: callback method for EVT_KILL_FOCUS event
3702    :param set_focus_callback:  callback method for EVT_SET_FOCUS event
3703    :param mouse_up_callback:   callback method for EVT_LEFT_UP event
3704    :param text_enter_callback: callback method for EVT_TEXT_ENTER event
3705
3706    """
[c8e1996]3707    # Set to True when the mouse is clicked while whole string is selected
[373d4ee]3708    full_selection = False
[c8e1996]3709    # Call back for EVT_SET_FOCUS events
[373d4ee]3710    _on_set_focus_callback = None
3711
3712    def __init__(self, parent, id=-1,
3713                 value=wx.EmptyString,
3714                 pos=wx.DefaultPosition,
3715                 size=wx.DefaultSize,
3716                 style=0,
3717                 validator=wx.DefaultValidator,
3718                 name=wx.TextCtrlNameStr,
3719                 kill_focus_callback=None,
3720                 set_focus_callback=None,
3721                 mouse_up_callback=None,
3722                 text_enter_callback=None):
3723
3724        wx.TextCtrl.__init__(self, parent, id, value, pos,
3725                             size, style, validator, name)
3726
3727        # Bind appropriate events
3728        self._on_set_focus_callback = parent.onSetFocus \
3729            if set_focus_callback is None else set_focus_callback
3730        self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
[c8e1996]3731        self.Bind(wx.EVT_KILL_FOCUS, self._silent_kill_focus
3732        if kill_focus_callback is None else kill_focus_callback)
3733        self.Bind(wx.EVT_TEXT_ENTER, parent._onparamEnter
3734        if text_enter_callback is None else text_enter_callback)
[373d4ee]3735        if not ON_MAC:
[c8e1996]3736            self.Bind(wx.EVT_LEFT_UP, self._highlight_text
3737            if mouse_up_callback is None else mouse_up_callback)
[373d4ee]3738
3739    def _on_set_focus(self, event):
3740        """
3741        Catch when the text control is set in focus to highlight the whole
3742        text if necessary
3743
3744        :param event: mouse event
3745
3746        """
3747        event.Skip()
3748        self.full_selection = True
3749        return self._on_set_focus_callback(event)
3750
3751    def _highlight_text(self, event):
3752        """
3753        Highlight text of a TextCtrl only of no text has be selected
3754
3755        :param event: mouse event
3756
3757        """
3758        # Make sure the mouse event is available to other listeners
3759        event.Skip()
3760        control = event.GetEventObject()
3761        if self.full_selection:
3762            self.full_selection = False
3763            # Check that we have a TextCtrl
3764            if issubclass(control.__class__, wx.TextCtrl):
3765                # Check whether text has been selected,
3766                # if not, select the whole string
3767                (start, end) = control.GetSelection()
3768                if start == end:
3769                    control.SetSelection(-1, -1)
3770
3771    def _silent_kill_focus(self, event):
3772        """
3773        Save the state of the page
3774        """
3775
3776        event.Skip()
[c8e1996]3777        # pass
Note: See TracBrowser for help on using the repository browser.