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

Last change on this file since d07f863 was 914c49d5, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

Merge branch 'master' into batch_slicer

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