source: sasview/src/sas/sasgui/perspectives/fitting/fitpage.py @ b5cc06e

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since b5cc06e was fa81e94, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Initial commit of the P(r) inversion perspective.
Code merged from Jeff Krzywon's ESS_GUI_Pr branch.
Also, minor 2to3 mods to sascalc/sasgui to enble error free setup.

  • Property mode set to 100755
File size: 126.7 KB
RevLine 
[682c432]1"""
2    FitPanel class contains fields allowing to display results when
3    fitting  a model and one data
4"""
5import sys
6import wx
7import wx.lib.newevent
[9a5097c]8import numpy as np
[682c432]9import copy
10import math
11import time
[6c382da]12import traceback
[cb4ef58]13
14from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
15
[50fcb09]16from sas.sascalc.fit.qsmearing import smear_selection
17
[c8e1996]18from sas.sasgui.guiframe.events import StatusEvent, NewPlotEvent, \
19    PlotQrangeEvent
[d85c194]20from sas.sasgui.guiframe.dataFitting import check_data_validity
[c8e1996]21from sas.sasgui.guiframe.utils import format_number, check_float
[d85c194]22from sas.sasgui.guiframe.documentation_window import DocumentationWindow
[682c432]23
[c8e1996]24from sas.sasgui.perspectives.fitting.basepage import BasicPage as BasicPage
25from sas.sasgui.perspectives.fitting.basepage import PageInfoEvent as \
26    PageInfoEvent
27from .basepage import ModelTextCtrl
28
[682c432]29(Chi2UpdateEvent, EVT_CHI2_UPDATE) = wx.lib.newevent.NewEvent()
30_BOX_WIDTH = 76
31_DATA_BOX_WIDTH = 300
32SMEAR_SIZE_H = 0.00
[4387385]33CUSTOM_MODEL = 'Plugin Models'
[682c432]34
35class FitPage(BasicPage):
36    """
37    FitPanel class contains fields allowing to display results when
38    fitting  a model and one data
39
40    :note: For Fit to be performed the user should check at least one parameter
41        on fit Panel window.
42    """
43
44    def __init__(self, parent, color=None):
45        """
46        Initialization of the Panel
47        """
48        BasicPage.__init__(self, parent, color=color)
49
[c8e1996]50        # draw sizer
[682c432]51        self._fill_data_sizer()
52        self.is_2D = None
53        self.fit_started = False
54        self.weightbt_string = None
55        self.m_name = None
56        # get smear info from data
57        self._get_smear_info()
58        self._fill_model_sizer(self.sizer1)
59        self._get_defult_custom_smear()
60        self._fill_range_sizer()
61        self._set_smear(self.data)
62        self.Bind(EVT_CHI2_UPDATE, self.on_complete_chisqr)
63        # bind key event
64        self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_down)
65        self._set_bookmark_flag(False)
66        self._set_save_flag(False)
67        self._set_preview_flag(False)
68        self._set_copy_flag(False)
69        self._set_paste_flag(False)
70        self.btFit.SetFocus()
71        self.enable_fit_button()
72        self.fill_data_combobox(data_list=self.data_list)
[c8e1996]73        # create a default data for an empty panel
[682c432]74        self.create_default_data()
75        self._manager.frame.Bind(wx.EVT_SET_FOCUS, self.on_set_focus)
76
77    def enable_fit_button(self):
78        """
79        Enable fit button if data is valid and model is valid
80        """
81        flag = check_data_validity(self.data) & (self.model is not None)
82        self.btFit.Enable(flag)
[bf44249e]83
[682c432]84    def on_set_focus(self, event):
85        """
[bf44249e]86        Override the basepage focus method to ensure the save flag is set
[682c432]87        properly when focusing on the fit page.
88        """
89        flag = check_data_validity(self.data) & (self.model is not None)
90        self._set_save_flag(flag)
91        self.parent.on_set_focus(event)
[85ccd3a]92        self.on_tap_focus()
[682c432]93
94    def _fill_data_sizer(self):
95        """
96        fill sizer 0 with data info
97        """
[6f16e25]98        self.data_box_description = wx.StaticBox(self, wx.ID_ANY,
99                                                 'I(q) Data Source')
[682c432]100        if check_data_validity(self.data):
101            dname_color = wx.BLUE
102        else:
103            dname_color = wx.RED
104        self.data_box_description.SetForegroundColour(dname_color)
105        boxsizer1 = wx.StaticBoxSizer(self.data_box_description, wx.VERTICAL)
[c8e1996]106        # ----------------------------------------------------------
[682c432]107        sizer_data = wx.BoxSizer(wx.HORIZONTAL)
[6f16e25]108        self.dataSource = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
109        wx.EVT_COMBOBOX(self.dataSource, wx.ID_ANY, self.on_select_data)
[682c432]110        self.dataSource.SetMinSize((_DATA_BOX_WIDTH, -1))
[6f16e25]111        sizer_data.Add(wx.StaticText(self, wx.ID_ANY, 'Name : '))
[682c432]112        sizer_data.Add(self.dataSource)
113        sizer_data.Add((0, 5))
114        boxsizer1.Add(sizer_data, 0, wx.ALL, 10)
115        self.sizer0.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
116        self.sizer0.Layout()
117
118    def enable_datasource(self):
119        """
120        Enable or disable data source control depending on existing data
121        """
122        if not self.data_list:
123            self.dataSource.Disable()
124        else:
125            self.dataSource.Enable()
126
127    def fill_data_combobox(self, data_list):
128        """
129        Get a list of data and fill the corresponding combobox
130        """
131        self.dataSource.Clear()
132        self.data_list = data_list
133        self.enable_datasource()
134        if len(data_list) > 0:
[c8e1996]135            # find the maximum range covering all data
[682c432]136            qmin, qmax, npts = self.compute_data_set_range(data_list)
137            self.qmin_data_set = qmin
138            self.qmax_data_set = qmax
139            self.npts_data_set = npts
140
141            self.qmin.SetValue(str(self.qmin_data_set))
142            self.qmax.SetValue(str(self.qmax_data_set))
143            self.qmin.SetBackgroundColour("white")
144            self.qmax.SetBackgroundColour("white")
145            self.qmin_x = self.qmin_data_set
146            self.qmax_x = self.qmax_data_set
147            self.state.qmin = self.qmin_x
148            self.state.qmax = self.qmax_x
149        is_data = False
150        for data in self.data_list:
151            if data is not None:
152                self.dataSource.Append(str(data.name), clientData=data)
153                if not is_data:
154                    is_data = check_data_validity(data)
155        if is_data:
156            self.dataSource.SetSelection(0)
157            self.on_select_data(event=None)
158
159        if len(data_list) == 1:
160            self.dataSource.Disable()
161
162    def on_select_data(self, event=None):
163        """
164        On_select_data
165        """
[27ba79c]166        if self.dataSource.GetCount() > 0:
167            pos = self.dataSource.GetSelection() if event is not None else 0
[682c432]168            data = self.dataSource.GetClientData(pos)
169            self.set_data(data)
170
171    def _on_fit_complete(self):
172        """
173        When fit is complete ,reset the fit button label.
174        """
175        self.fit_started = False
176        self.set_fitbutton()
177
178    def _is_2D(self):
179        """
180        Check if data_name is Data2D
181
182        :return: True or False
183        """
184        if self.data.__class__.__name__ == "Data2D" or \
185                        self.enable2D:
186            return True
187        return False
188
189    def _fill_range_sizer(self):
190        """
[7801df8]191        Fill the Fitting sizer on the fit panel which contains: the smearing
192        information (dq), the weighting information (dI or other), the plotting
193        range, access to the 2D mask editor, the compute, fit, and help
194        buttons, xi^2, number of points etc.
[682c432]195        """
[c8e1996]196        is_2d_data = False
[682c432]197
198        # Check if data is 2D
199        if self.data.__class__.__name__ == "Data2D" or \
200                        self.enable2D:
[c8e1996]201            is_2d_data = True
[682c432]202
203        title = "Fitting"
[c8e1996]204        # smear messages & titles
[682c432]205        smear_message_none = "No smearing is selected..."
206        smear_message_dqdata = "The dQ data is being used for smearing..."
207        smear_message_2d = \
208              "Higher accuracy is very time-expensive. Use it with care..."
209        smear_message_new_ssmear = \
210              "Please enter only the value of interest to customize smearing..."
211        smear_message_new_psmear = \
[7e98655]212              "Please enter a fixed percentage to be applied to all Q values..."
[682c432]213        smear_message_2d_x_title = "<dQp>[1/A]:"
214        smear_message_2d_y_title = "<dQs>[1/A]:"
[7e98655]215        smear_message_pinhole_percent_title = "dQ[%]:"
[682c432]216        smear_message_slit_height_title = "Slit height[1/A]:"
217        smear_message_slit_width_title = "Slit width[1/A]:"
218
219        self._get_smear_info()
220
[c8e1996]221        # Sizers
[6f16e25]222        box_description_range = wx.StaticBox(self, wx.ID_ANY, str(title))
[682c432]223        box_description_range.SetForegroundColour(wx.BLUE)
224        boxsizer_range = wx.StaticBoxSizer(box_description_range, wx.VERTICAL)
225        self.sizer_set_smearer = wx.BoxSizer(wx.VERTICAL)
226        sizer_smearer = wx.BoxSizer(wx.HORIZONTAL)
227        self.sizer_new_smear = wx.BoxSizer(wx.HORIZONTAL)
228        self.sizer_set_masking = wx.BoxSizer(wx.HORIZONTAL)
229        sizer_chi2 = wx.BoxSizer(wx.VERTICAL)
[6f16e25]230        smear_set_box = wx.StaticBox(self, wx.ID_ANY,
231                                     'Set Instrumental Smearing')
[682c432]232        sizer_smearer_box = wx.StaticBoxSizer(smear_set_box, wx.HORIZONTAL)
233        sizer_smearer_box.SetMinSize((_DATA_BOX_WIDTH, 60))
234
[6f16e25]235        weighting_set_box = wx.StaticBox(self, wx.ID_ANY,
[bf44249e]236                                         'Set Weighting by Selecting dI Source')
[682c432]237        weighting_box = wx.StaticBoxSizer(weighting_set_box, wx.HORIZONTAL)
238        sizer_weighting = wx.BoxSizer(wx.HORIZONTAL)
239        weighting_box.SetMinSize((_DATA_BOX_WIDTH, 40))
[c8e1996]240        # Filling the sizer containing weighting info.
[6f16e25]241        self.dI_noweight = wx.RadioButton(self, wx.ID_ANY,
242                                          'No Weighting', style=wx.RB_GROUP)
243        self.dI_didata = wx.RadioButton(self, wx.ID_ANY, 'Use dI Data')
244        self.dI_sqrdata = wx.RadioButton(self, wx.ID_ANY, 'Use |sqrt(I Data)|')
245        self.dI_idata = wx.RadioButton(self, wx.ID_ANY, 'Use |I Data|')
[682c432]246        self.Bind(wx.EVT_RADIOBUTTON, self.onWeighting,
247                  id=self.dI_noweight.GetId())
248        self.Bind(wx.EVT_RADIOBUTTON, self.onWeighting,
249                  id=self.dI_didata.GetId())
250        self.Bind(wx.EVT_RADIOBUTTON, self.onWeighting,
251                  id=self.dI_sqrdata.GetId())
252        self.Bind(wx.EVT_RADIOBUTTON, self.onWeighting,
253                  id=self.dI_idata.GetId())
[be36447]254        self.dI_noweight.SetValue(True)
[682c432]255        # add 4 types of weighting to the sizer
256        sizer_weighting.Add(self.dI_noweight, 0, wx.LEFT, 10)
257        sizer_weighting.Add((14, 10))
258        sizer_weighting.Add(self.dI_didata)
259        sizer_weighting.Add((14, 10))
260        sizer_weighting.Add(self.dI_sqrdata)
261        sizer_weighting.Add((14, 10))
262        sizer_weighting.Add(self.dI_idata)
263        sizer_weighting.Add((10, 10))
[be36447]264        self.dI_noweight.Enable(True)
[682c432]265        self.dI_didata.Enable(False)
266        self.dI_sqrdata.Enable(False)
267        self.dI_idata.Enable(False)
268        weighting_box.Add(sizer_weighting)
269
270        # combobox for smear2d accuracy selection
[6f16e25]271        self.smear_accuracy = wx.ComboBox(self, wx.ID_ANY,
272                                          size=(50, -1), style=wx.CB_READONLY)
[682c432]273        self._set_accuracy_list()
274        self.smear_accuracy.SetValue(self.smear2d_accuracy)
275        self.smear_accuracy.SetSelection(0)
[6f16e25]276        self.smear_accuracy.SetToolTipString(
[682c432]277            "'Higher' uses more Gaussian points for smearing computation.")
278
[6f16e25]279        wx.EVT_COMBOBOX(self.smear_accuracy, wx.ID_ANY,
280                        self._on_select_accuracy)
[682c432]281
[c8e1996]282        # Fit button
[fa81e94]283        self.btFit = wx.Button(self, next(self._ids), 'Fit')
[682c432]284        self.default_bt_colour = self.btFit.GetDefaultAttributes()
285        self.btFit.Bind(wx.EVT_BUTTON, self._onFit, id=self.btFit.GetId())
286        self.btFit.SetToolTipString("Start fitting.")
287
[c8e1996]288        # General Help button
[6f16e25]289        self.btFitHelp = wx.Button(self, wx.ID_ANY, 'Help')
[d06c34c]290        self.btFitHelp.SetToolTipString("General fitting help.")
[7801df8]291        self.btFitHelp.Bind(wx.EVT_BUTTON, self._onFitHelp)
[50fcb09]292
[c8e1996]293        # Resolution Smearing Help button (for now use same technique as
294        # used for dI help to get tiniest possible button that works
295        # both on MAC and PC.  Should completely rewrite the fitting sizer
296        # in future.  This is minimum to get out release 3.1
[f3dc56c]297        #        comment June 14, 2015     --- PDB
298        if sys.platform.count("win32") > 0:
[c8e1996]299            size_q = (20, 15)  # on PC
[f3dc56c]300        else:
[c8e1996]301            size_q = (30, 20)  # on MAC
[6f16e25]302        self.btSmearHelp = wx.Button(self, wx.ID_ANY, '?',
303                                     style=wx.BU_EXACTFIT, size=size_q)
[d06c34c]304        self.btSmearHelp.SetToolTipString("Resolution smearing help.")
[7801df8]305        self.btSmearHelp.Bind(wx.EVT_BUTTON, self._onSmearHelp)
[50fcb09]306
[c8e1996]307        # textcntrl for custom resolution
[7e98655]308        self.smear_pinhole_percent = ModelTextCtrl(self, wx.ID_ANY,
309                                                   size=(_BOX_WIDTH - 25, 20),
310                                                   style=wx.TE_PROCESS_ENTER,
311                                                   text_enter_callback=
312                                                   self.onPinholeSmear)
[6f16e25]313        self.smear_slit_height = ModelTextCtrl(self, wx.ID_ANY,
[682c432]314                            size=(_BOX_WIDTH - 25, 20),
315                            style=wx.TE_PROCESS_ENTER,
316                            text_enter_callback=self.onSlitSmear)
[6f16e25]317        self.smear_slit_width = ModelTextCtrl(self, wx.ID_ANY,
[682c432]318                            size=(_BOX_WIDTH - 25, 20),
319                            style=wx.TE_PROCESS_ENTER,
320                            text_enter_callback=self.onSlitSmear)
321
[c8e1996]322        # smear
[6f16e25]323        self.smear_data_left = BGTextCtrl(self, wx.ID_ANY,
[373d4ee]324                                          size=(_BOX_WIDTH - 25, 20), style=0)
[682c432]325        self.smear_data_left.SetValue(str(self.dq_l))
[6f16e25]326        self.smear_data_right = BGTextCtrl(self, wx.ID_ANY,
[373d4ee]327                                           size=(_BOX_WIDTH - 25, 20), style=0)
[682c432]328        self.smear_data_right.SetValue(str(self.dq_r))
329
[c8e1996]330        # set default values for smear
[7e98655]331        self.smear_pinhole_percent.SetValue(str(self.dx_percent))
[682c432]332        self.smear_slit_height.SetValue(str(self.dxl))
333        self.smear_slit_width.SetValue(str(self.dxw))
334
[c8e1996]335        # Filling the sizer containing instruments smearing info.
[6f16e25]336        self.disable_smearer = wx.RadioButton(self, wx.ID_ANY,
[682c432]337                                              'None', style=wx.RB_GROUP)
[6f16e25]338        self.enable_smearer = wx.RadioButton(self, wx.ID_ANY, 'Use dQ Data')
[c8e1996]339        # self.enable_smearer.SetToolTipString(
340        # "Click to use the loaded dQ data for smearing.")
[6f16e25]341        self.pinhole_smearer = wx.RadioButton(self, wx.ID_ANY,
[682c432]342                                              'Custom Pinhole Smear')
[c8e1996]343        # self.pinhole_smearer.SetToolTipString
344        # ("Click to input custom resolution for pinhole smearing.")
[6f16e25]345        self.slit_smearer = wx.RadioButton(self, wx.ID_ANY, 'Custom Slit Smear')
[c8e1996]346        # self.slit_smearer.SetToolTipString
347        # ("Click to input custom resolution for slit smearing.")
[682c432]348        self.Bind(wx.EVT_RADIOBUTTON, self.onSmear,
349                  id=self.disable_smearer.GetId())
350        self.Bind(wx.EVT_RADIOBUTTON, self.onSmear,
351                  id=self.enable_smearer.GetId())
352        self.Bind(wx.EVT_RADIOBUTTON, self.onPinholeSmear,
353                  id=self.pinhole_smearer.GetId())
354        self.Bind(wx.EVT_RADIOBUTTON, self.onSlitSmear,
355                  id=self.slit_smearer.GetId())
[03137f1]356        self.disable_smearer.SetValue(True)
[682c432]357
358        sizer_smearer.Add(self.disable_smearer, 0, wx.LEFT, 10)
359        sizer_smearer.Add(self.enable_smearer)
360        sizer_smearer.Add(self.pinhole_smearer)
361        sizer_smearer.Add(self.slit_smearer)
[f3dc56c]362        sizer_smearer.Add(self.btSmearHelp)
[682c432]363        sizer_smearer.Add((10, 10))
364
365        # StaticText for chi2, N(for fitting), Npts + Log/linear spacing
[6f16e25]366        self.tcChi = BGTextCtrl(self, wx.ID_ANY, "-", size=(75, 20), style=0)
[682c432]367        self.tcChi.SetToolTipString("Chi2/Npts(Fit)")
[6f16e25]368        self.Npts_fit = BGTextCtrl(self, wx.ID_ANY, "-", size=(75, 20), style=0)
369        self.Npts_fit.SetToolTipString(
[c8e1996]370            " Npts : number of points selected for fitting")
[6f16e25]371        self.Npts_total = ModelTextCtrl(self, wx.ID_ANY, size=(_BOX_WIDTH, 20),
[c8e1996]372                                        style=wx.TE_PROCESS_ENTER,
373                                        text_enter_callback=self._onQrangeEnter)
[682c432]374        self.Npts_total.SetValue(format_number(self.npts_x))
[c8e1996]375        self.Npts_total.SetToolTipString(
376            " Total Npts : total number of data points")
[682c432]377
378        # Update and Draw button
[fa81e94]379        self.draw_button = wx.Button(self, next(self._ids), 'Compute')
[6f16e25]380        self.draw_button.Bind(wx.EVT_BUTTON,
[682c432]381                              self._onDraw, id=self.draw_button.GetId())
382        self.draw_button.SetToolTipString("Compute and Draw.")
383
384        self.points_sizer = wx.BoxSizer(wx.HORIZONTAL)
[6f16e25]385        self.pointsbox = wx.CheckBox(self, wx.ID_ANY, 'Log?', (10, 10))
[682c432]386        self.pointsbox.SetValue(False)
387        self.pointsbox.SetToolTipString("Check mark to use log spaced points")
388        wx.EVT_CHECKBOX(self, self.pointsbox.GetId(), self.select_log)
389
[6f16e25]390        self.points_sizer.Add(wx.StaticText(self, wx.ID_ANY, 'Npts    '))
[682c432]391        self.points_sizer.Add(self.pointsbox)
392
[6f16e25]393        box_description_1 = wx.StaticText(self, wx.ID_ANY, '   Chi2/Npts')
394        box_description_2 = wx.StaticText(self, wx.ID_ANY, 'Npts(Fit)')
[682c432]395
396        # StaticText for smear
[6f16e25]397        self.smear_description_none = wx.StaticText(self, wx.ID_ANY,
[682c432]398                                    smear_message_none, style=wx.ALIGN_LEFT)
[6f16e25]399        self.smear_description_dqdata = wx.StaticText(self, wx.ID_ANY,
400                                 smear_message_dqdata, style=wx.ALIGN_LEFT)
401        self.smear_description_type = wx.StaticText(self, wx.ID_ANY,
402                                    "Type:", style=wx.ALIGN_LEFT)
403        self.smear_description_accuracy_type = wx.StaticText(self, wx.ID_ANY,
404                                    "Accuracy:", style=wx.ALIGN_LEFT)
405        self.smear_description_smear_type = BGTextCtrl(self, wx.ID_ANY,
[682c432]406                                                       size=(57, 20), style=0)
407        self.smear_description_smear_type.SetValue(str(self.dq_l))
408        self.SetBackgroundColour(self.GetParent().GetBackgroundColour())
[6f16e25]409        self.smear_description_2d = wx.StaticText(self, wx.ID_ANY,
[682c432]410                                    smear_message_2d, style=wx.ALIGN_LEFT)
[6f16e25]411        self.smear_message_new_s = wx.StaticText(self, wx.ID_ANY,
[682c432]412                         smear_message_new_ssmear, style=wx.ALIGN_LEFT)
[6f16e25]413        self.smear_message_new_p = wx.StaticText(self, wx.ID_ANY,
[682c432]414                            smear_message_new_psmear, style=wx.ALIGN_LEFT)
[6f16e25]415        self.smear_description_2d_x = wx.StaticText(self, wx.ID_ANY,
[682c432]416                            smear_message_2d_x_title, style=wx.ALIGN_LEFT)
[6f16e25]417        self.smear_description_2d_x.SetToolTipString(
[682c432]418                                        "  dQp(parallel) in q_r direction.")
[6f16e25]419        self.smear_description_2d_y = wx.StaticText(self, wx.ID_ANY,
[682c432]420                            smear_message_2d_y_title, style=wx.ALIGN_LEFT)
[c8e1996]421        self.smear_description_2d_y.SetToolTipString(
[682c432]422                                    " dQs(perpendicular) in q_phi direction.")
[7e98655]423        self.smear_description_pin_percent = wx.StaticText(self, wx.ID_ANY,
424                                            smear_message_pinhole_percent_title,
425                                            style=wx.ALIGN_LEFT)
[6f16e25]426        self.smear_description_slit_height = wx.StaticText(self, wx.ID_ANY,
[682c432]427                        smear_message_slit_height_title, style=wx.ALIGN_LEFT)
[6f16e25]428        self.smear_description_slit_width = wx.StaticText(self, wx.ID_ANY,
[682c432]429                        smear_message_slit_width_title, style=wx.ALIGN_LEFT)
430
[c8e1996]431        # arrange sizers
[682c432]432        self.sizer_set_smearer.Add(sizer_smearer)
433        self.sizer_set_smearer.Add((10, 10))
434        self.sizer_set_smearer.Add(self.smear_description_none,
[c8e1996]435                                   0, wx.CENTER, 10)
[682c432]436        self.sizer_set_smearer.Add(self.smear_description_dqdata,
[c8e1996]437                                   0, wx.CENTER, 10)
[682c432]438        self.sizer_set_smearer.Add(self.smear_description_2d,
[c8e1996]439                                   0, wx.CENTER, 10)
[682c432]440        self.sizer_new_smear.Add(self.smear_description_type,
[c8e1996]441                                 0, wx.CENTER, 10)
[682c432]442        self.sizer_new_smear.Add(self.smear_description_accuracy_type,
[c8e1996]443                                 0, wx.CENTER, 10)
[682c432]444        self.sizer_new_smear.Add(self.smear_accuracy)
445        self.sizer_new_smear.Add(self.smear_description_smear_type,
[c8e1996]446                                 0, wx.CENTER, 10)
[682c432]447        self.sizer_new_smear.Add((15, -1))
[c8e1996]448        self.sizer_new_smear.Add(self.smear_description_2d_x, 0, wx.CENTER, 10)
[682c432]449        self.sizer_new_smear.Add(self.smear_description_slit_height,
[c8e1996]450                                 0, wx.CENTER, 10)
451
452        self.sizer_new_smear.Add(self.smear_slit_height, 0, wx.CENTER, 10)
453        self.sizer_new_smear.Add(self.smear_data_left, 0, wx.CENTER, 10)
[682c432]454        self.sizer_new_smear.Add((20, -1))
455        self.sizer_new_smear.Add(self.smear_description_2d_y,
[c8e1996]456                                 0, wx.CENTER, 10)
[7e98655]457        self.sizer_new_smear.Add(self.smear_description_pin_percent,
[c8e1996]458                                 0, wx.CENTER, 10)
[682c432]459        self.sizer_new_smear.Add(self.smear_description_slit_width,
[c8e1996]460                                 0, wx.CENTER, 10)
[682c432]461
[7e98655]462        self.sizer_new_smear.Add(self.smear_pinhole_percent, 0, wx.CENTER, 10)
[682c432]463        self.sizer_new_smear.Add(self.smear_slit_width, 0, wx.CENTER, 10)
464        self.sizer_new_smear.Add(self.smear_data_right, 0, wx.CENTER, 10)
465
466        self.sizer_set_smearer.Add(self.smear_message_new_s, 0, wx.CENTER, 10)
467        self.sizer_set_smearer.Add(self.smear_message_new_p, 0, wx.CENTER, 10)
468        self.sizer_set_smearer.Add((5, 2))
469        self.sizer_set_smearer.Add(self.sizer_new_smear, 0, wx.CENTER, 10)
470
471        # add all to chi2 sizer
472        sizer_smearer_box.Add(self.sizer_set_smearer)
473        sizer_chi2.Add(sizer_smearer_box)
474        sizer_chi2.Add((-1, 5))
475        sizer_chi2.Add(weighting_box)
476        sizer_chi2.Add((-1, 5))
477
478        # hide all smear messages and textctrl
479        self._hide_all_smear_info()
480
481        # get smear_selection
482        self.current_smearer = smear_selection(self.data, self.model)
483
484        # Show only the relevant smear messages, etc
[c8e1996]485        if self.current_smearer is None:
486            if not is_2d_data:
[682c432]487                self.smear_description_none.Show(True)
488                self.enable_smearer.Disable()
489            else:
490                self.smear_description_none.Show(True)
491                self.slit_smearer.Disable()
[c8e1996]492            if self.data is None:
[682c432]493                self.slit_smearer.Disable()
494                self.pinhole_smearer.Disable()
495                self.enable_smearer.Disable()
496        else:
497            self._show_smear_sizer()
498        boxsizer_range.Add(self.sizer_set_masking)
[c8e1996]499        # 2D data? default
500        is_2d_data = False
[682c432]501
[c8e1996]502        # check if it is 2D data
[682c432]503        if self.data.__class__.__name__ == "Data2D" or self.enable2D:
[c8e1996]504            is_2d_data = True
[682c432]505
506        self.sizer5.Clear(True)
507
[6f16e25]508        self.qmin = ModelTextCtrl(self, wx.ID_ANY, size=(_BOX_WIDTH, 20),
[373d4ee]509                                  style=wx.TE_PROCESS_ENTER,
510                                  set_focus_callback=self.qrang_set_focus,
511                                  text_enter_callback=self._onQrangeEnter,
512                                  name='qmin')
[682c432]513        self.qmin.SetValue(str(self.qmin_x))
514        q_tip = "Click outside of the axes\n to remove the lines."
515        qmin_tip = "Minimun value of Q.\n"
516        qmin_tip += q_tip
517        self.qmin.SetToolTipString(qmin_tip)
518
[6f16e25]519        self.qmax = ModelTextCtrl(self, wx.ID_ANY, size=(_BOX_WIDTH, 20),
[373d4ee]520                                  style=wx.TE_PROCESS_ENTER,
521                                  set_focus_callback=self.qrang_set_focus,
522                                  text_enter_callback=self._onQrangeEnter,
523                                  name='qmax')
[682c432]524        self.qmax.SetValue(str(self.qmax_x))
525        qmax_tip = "Maximum value of Q.\n"
526        qmax_tip += q_tip
527        self.qmax.SetToolTipString(qmax_tip)
528        self.qmin.Bind(wx.EVT_MOUSE_EVENTS, self.qrange_click)
529        self.qmax.Bind(wx.EVT_MOUSE_EVENTS, self.qrange_click)
530        self.qmin.Bind(wx.EVT_KEY_DOWN, self.on_key)
531        self.qmax.Bind(wx.EVT_KEY_DOWN, self.on_key)
532        self.qmin.Bind(wx.EVT_TEXT, self.on_qrange_text)
533        self.qmax.Bind(wx.EVT_TEXT, self.on_qrange_text)
[fa81e94]534        wx_id = next(self._ids)
[d06c34c]535        self.reset_qrange = wx.Button(self, wx_id, 'Reset')
[682c432]536
[d06c34c]537        self.reset_qrange.Bind(wx.EVT_BUTTON, self.on_reset_clicked, id=wx_id)
[682c432]538        self.reset_qrange.SetToolTipString("Reset Q range to the default")
539
[d06c34c]540        sizer = wx.GridSizer(5, 5, 2, 6)
[682c432]541
[fa81e94]542        self.btEditMask = wx.Button(self, next(self._ids), 'Editor')
[682c432]543        self.btEditMask.Bind(wx.EVT_BUTTON, self._onMask,
544                             id=self.btEditMask.GetId())
545        self.btEditMask.SetToolTipString("Edit Mask.")
[6f16e25]546        self.EditMask_title = wx.StaticText(self, wx.ID_ANY, ' Masking(2D)')
[682c432]547
[6f16e25]548        sizer.Add(wx.StaticText(self, wx.ID_ANY, '   Q range'))
549        sizer.Add(wx.StaticText(self, wx.ID_ANY, ' Min[1/A]'))
550        sizer.Add(wx.StaticText(self, wx.ID_ANY, ' Max[1/A]'))
[682c432]551        sizer.Add(self.EditMask_title)
[c8e1996]552        sizer.Add((-1, 5))
[d06c34c]553
[682c432]554        sizer.Add(self.reset_qrange)
555        sizer.Add(self.qmin)
556        sizer.Add(self.qmax)
557        sizer.Add(self.btEditMask)
[c8e1996]558        sizer.Add((-1, 5))
[d06c34c]559
[c8e1996]560        sizer.AddMany(5*[(-1, 5)])
[d06c34c]561
562        sizer.Add(box_description_1, 0, 0)
563        sizer.Add(box_description_2, 0, 0)
564        sizer.Add(self.points_sizer, 0, 0)
565        sizer.Add(self.draw_button, 0, 0)
[c8e1996]566        sizer.Add((-1, 5))
[50fcb09]567
[d06c34c]568        sizer.Add(self.tcChi, 0, 0)
569        sizer.Add(self.Npts_fit, 0, 0)
570        sizer.Add(self.Npts_total, 0, 0)
571        sizer.Add(self.btFit, 0, 0)
572        sizer.Add(self.btFitHelp, 0, 0)
[50fcb09]573
[682c432]574        boxsizer_range.Add(sizer_chi2)
575        boxsizer_range.Add(sizer)
[c8e1996]576        if is_2d_data:
[682c432]577            self.btEditMask.Enable()
578            self.EditMask_title.Enable()
579        else:
580            self.btEditMask.Disable()
581            self.EditMask_title.Disable()
[c8e1996]582        # save state
[682c432]583        self.save_current_state()
584        self.sizer5.Add(boxsizer_range, 0, wx.EXPAND | wx.ALL, 10)
585        self.sizer5.Layout()
586
587    def _set_sizer_dispersion(self):
588        """
589        draw sizer with gaussian dispersity parameters
590        """
591        self.fittable_param = []
592        self.fixed_param = []
593        self.orientation_params_disp = []
594
595        self.sizer4_4.Clear(True)
[c8e1996]596        if self.model is None:
597            # no model is selected
[682c432]598            return
599        if not self.enable_disp.GetValue():
[c8e1996]600            # the user didn't select dispersity display
[682c432]601            return
602
603        self._reset_dispersity()
604
[c8e1996]605        # fill a sizer with the combobox to select dispersion type
[6f16e25]606        model_disp = wx.StaticText(self, wx.ID_ANY, 'Function')
[4c3be25]607        CHECK_STATE = False
[682c432]608
609        ix = 0
610        iy = 0
[6f16e25]611        disp = wx.StaticText(self, wx.ID_ANY, ' ')
[682c432]612        self.sizer4_4.Add(disp, (iy, ix), (1, 1),
613                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
614        ix += 1
[6f16e25]615        values = wx.StaticText(self, wx.ID_ANY, 'PD[ratio]')
[682c432]616        polytext = "Polydispersity (= STD/mean); "
617        polytext += "the standard deviation over the mean value."
618        values.SetToolTipString(polytext)
619
620        self.sizer4_4.Add(values, (iy, ix), (1, 1),
621                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
622        ix += 2
623        if self.is_mac:
624            err_text = 'Error'
625        else:
626            err_text = ''
[6f16e25]627        self.text_disp_1 = wx.StaticText(self, wx.ID_ANY, err_text)
[c8e1996]628        self.sizer4_4.Add(self.text_disp_1, (iy, ix), (1, 1),
[682c432]629                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
630
631        ix += 1
[6f16e25]632        self.text_disp_min = wx.StaticText(self, wx.ID_ANY, 'Min')
[c8e1996]633        self.sizer4_4.Add(self.text_disp_min, (iy, ix), (1, 1),
[682c432]634                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
635
636        ix += 1
[6f16e25]637        self.text_disp_max = wx.StaticText(self, wx.ID_ANY, 'Max')
[682c432]638        self.sizer4_4.Add(self.text_disp_max, (iy, ix), (1, 1),
639                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
640
641        ix += 1
[6f16e25]642        npts = wx.StaticText(self, wx.ID_ANY, 'Npts')
[682c432]643        npts.SetToolTipString("Number of sampling points for the numerical\n\
644        integration over the distribution function.")
645        self.sizer4_4.Add(npts, (iy, ix), (1, 1),
646                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
647        ix += 1
[6f16e25]648        nsigmas = wx.StaticText(self, wx.ID_ANY, 'Nsigs')
[682c432]649        nsigmas.SetToolTipString("Number of sigmas between which the range\n\
650         of the distribution function will be used for weighting. \n\
651        The value '3' covers 99.5% for Gaussian distribution \n\
652        function. Note: Not recommended to change this value.")
653        self.sizer4_4.Add(nsigmas, (iy, ix), (1, 1),
654                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
655        ix += 1
656        self.sizer4_4.Add(model_disp, (iy, ix), (1, 1),
657                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
658
659        self.text_disp_max.Show(True)
660        self.text_disp_min.Show(True)
661
[fa81e94]662        for item in list(self.model.dispersion.keys()):
[682c432]663            if not self.magnetic_on:
664                if item in self.model.magnetic_params:
665                    continue
[c8e1996]666            if item not in self.model.orientation_params:
667                if item not in self.disp_cb_dict:
[682c432]668                    self.disp_cb_dict[item] = None
669                name0 = "Distribution of " + item
670                name1 = item + ".width"
671                name2 = item + ".npts"
672                name3 = item + ".nsigmas"
[c8e1996]673                if name1 not in self.model.details:
[682c432]674                    self.model.details[name1] = ["", None, None]
675
676                iy += 1
[fa81e94]677                for p in list(self.model.dispersion[item].keys()):
[682c432]678
679                    if p == "width":
680                        ix = 0
[6f16e25]681                        cb = wx.CheckBox(self, wx.ID_ANY, name0, (10, 10))
[682c432]682                        cb.SetValue(CHECK_STATE)
683                        cb.SetToolTipString("Check mark to fit")
684                        wx.EVT_CHECKBOX(self, cb.GetId(), self.select_param)
685                        self.sizer4_4.Add(cb, (iy, ix), (1, 1),
686                                wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
687                        ix = 1
688                        value = self.model.getParam(name1)
[6f16e25]689                        ctl1 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]690                                             size=(_BOX_WIDTH / 1.3, 20),
691                                             style=wx.TE_PROCESS_ENTER)
[682c432]692                        ctl1.SetLabel('PD[ratio]')
693                        poly_text = "Polydispersity (STD/mean) of %s\n" % item
694                        poly_text += "STD: the standard deviation"
695                        poly_text += " from the mean value."
696                        ctl1.SetToolTipString(poly_text)
697                        ctl1.SetValue(str(format_number(value, True)))
698                        self.sizer4_4.Add(ctl1, (iy, ix), (1, 1), wx.EXPAND)
[c8e1996]699                        # text to show error sign
[682c432]700                        ix = 2
[6f16e25]701                        text2 = wx.StaticText(self, wx.ID_ANY, '+/-')
[682c432]702                        self.sizer4_4.Add(text2, (iy, ix), (1, 1),
703                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
704                        if not self.is_mac:
705                            text2.Hide()
706
707                        ix = 3
[6f16e25]708                        ctl2 = wx.TextCtrl(self, wx.ID_ANY,
[682c432]709                                           size=(_BOX_WIDTH / 1.3, 20),
710                                           style=0)
711
712                        self.sizer4_4.Add(ctl2, (iy, ix), (1, 1),
713                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
714                        if not self.is_mac:
715                            ctl2.Hide()
716
717                        ix = 4
[6f16e25]718                        ctl3 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]719                                             size=(_BOX_WIDTH / 2, 20),
720                                             style=wx.TE_PROCESS_ENTER,
721                            text_enter_callback=self._onparamRangeEnter)
[682c432]722
723                        self.sizer4_4.Add(ctl3, (iy, ix), (1, 1),
724                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
725
726                        ix = 5
[6f16e25]727                        ctl4 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]728                                             size=(_BOX_WIDTH / 2, 20),
729                                             style=wx.TE_PROCESS_ENTER,
[682c432]730                            text_enter_callback=self._onparamRangeEnter)
731
732                        self.sizer4_4.Add(ctl4, (iy, ix), (1, 1),
733                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
734
735                        ctl3.Show(True)
736                        ctl4.Show(True)
737
738                    elif p == "npts":
739                        ix = 6
740                        value = self.model.getParam(name2)
[6f16e25]741                        Tctl = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]742                                             size=(_BOX_WIDTH / 2.2, 20),
743                                             style=wx.TE_PROCESS_ENTER)
[682c432]744
745                        Tctl.SetValue(str(format_number(value)))
746                        self.sizer4_4.Add(Tctl, (iy, ix), (1, 1),
747                                           wx.EXPAND | wx.ADJUST_MINSIZE, 0)
748                        self.fixed_param.append([None, name2, Tctl, None, None,
749                                                 None, None, None])
750                    elif p == "nsigmas":
751                        ix = 7
752                        value = self.model.getParam(name3)
[6f16e25]753                        Tct2 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]754                                             size=(_BOX_WIDTH / 2.2, 20),
755                                             style=wx.TE_PROCESS_ENTER)
[682c432]756
757                        Tct2.SetValue(str(format_number(value)))
758                        self.sizer4_4.Add(Tct2, (iy, ix), (1, 1),
[c8e1996]759                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[682c432]760                        self.fixed_param.append([None, name3, Tct2,
761                                                 None, None, None,
762                                                 None, None])
763
764                ix = 8
[6f16e25]765                disp_box = wx.ComboBox(self, wx.ID_ANY, size=(65, -1),
[682c432]766                                       style=wx.CB_READONLY, name='%s' % name1)
[fa81e94]767                for key, value in POLYDISPERSITY_MODELS.items():
[682c432]768                    name_disp = str(key)
769                    disp_box.Append(name_disp, value)
770                    disp_box.SetStringSelection("gaussian")
[6f16e25]771                wx.EVT_COMBOBOX(disp_box, wx.ID_ANY, self._on_disp_func)
[682c432]772                self.sizer4_4.Add(disp_box, (iy, ix), (1, 1), wx.EXPAND)
773                self.fittable_param.append([cb, name1, ctl1, text2,
774                                            ctl2, ctl3, ctl4, disp_box])
775
776        ix = 0
777        iy += 1
778        self.sizer4_4.Add((20, 20), (iy, ix), (1, 1),
779                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
780        first_orient = True
[fa81e94]781        for item in list(self.model.dispersion.keys()):
[682c432]782            if not self.magnetic_on:
783                if item in self.model.magnetic_params:
784                    continue
[c8e1996]785            if item in self.model.orientation_params:
786                if item not in self.disp_cb_dict:
[682c432]787                    self.disp_cb_dict[item] = None
788                name0 = "Distribution of " + item
789                name1 = item + ".width"
790                name2 = item + ".npts"
791                name3 = item + ".nsigmas"
792
[c8e1996]793                if name1 not in self.model.details:
[682c432]794                    self.model.details[name1] = ["", None, None]
795
796                iy += 1
[fa81e94]797                for p in list(self.model.dispersion[item].keys()):
[682c432]798
799                    if p == "width":
800                        ix = 0
[6f16e25]801                        cb = wx.CheckBox(self, wx.ID_ANY, name0, (10, 10))
[682c432]802                        cb.SetValue(CHECK_STATE)
803                        cb.SetToolTipString("Check mark to fit")
804                        wx.EVT_CHECKBOX(self, cb.GetId(), self.select_param)
805                        self.sizer4_4.Add(cb, (iy, ix), (1, 1),
806                                wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
807                        if self.data.__class__.__name__ == "Data2D" or \
808                                    self.enable2D:
809                            cb.Show(True)
810                        elif cb.IsShown():
811                            cb.Hide()
812                        ix = 1
813                        value = self.model.getParam(name1)
[6f16e25]814                        ctl1 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]815                                             size=(_BOX_WIDTH / 1.3, 20),
816                                             style=wx.TE_PROCESS_ENTER)
[682c432]817                        poly_tip = "Absolute Sigma for %s." % item
818                        ctl1.SetToolTipString(poly_tip)
819                        ctl1.SetValue(str(format_number(value, True)))
820                        if self.data.__class__.__name__ == "Data2D" or \
821                                    self.enable2D:
822                            if first_orient:
823                                values.SetLabel('PD[ratio], Sig[deg]')
824                                poly_text = "PD(polydispersity for lengths):\n"
825                                poly_text += "It should be a value between"
826                                poly_text += "0 and 1\n"
827                                poly_text += "Sigma for angles: \n"
828                                poly_text += "It is the STD (ratio*mean)"
829                                poly_text += " of the distribution.\n "
830
831                                values.SetToolTipString(poly_text)
832                                first_orient = False
833                            ctl1.Show(True)
834                        elif ctl1.IsShown():
835                            ctl1.Hide()
836
837                        self.sizer4_4.Add(ctl1, (iy, ix), (1, 1), wx.EXPAND)
[c8e1996]838                        # text to show error sign
[682c432]839                        ix = 2
[6f16e25]840                        text2 = wx.StaticText(self, wx.ID_ANY, '+/-')
[682c432]841                        self.sizer4_4.Add(text2, (iy, ix), (1, 1),
842                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
843
844                        text2.Hide()
845
846                        ix = 3
[6f16e25]847                        ctl2 = wx.TextCtrl(self, wx.ID_ANY,
[682c432]848                                           size=(_BOX_WIDTH / 1.3, 20),
849                                           style=0)
850
851                        self.sizer4_4.Add(ctl2, (iy, ix), (1, 1),
852                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
853
854                        ctl2.Hide()
855                        if self.data.__class__.__name__ == "Data2D" or \
856                                self.enable2D:
857                            if self.is_mac:
858                                text2.Show(True)
859                                ctl2.Show(True)
860
861                        ix = 4
[6f16e25]862                        ctl3 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]863                                             size=(_BOX_WIDTH / 2, 20),
864                                             style=wx.TE_PROCESS_ENTER,
[682c432]865                                text_enter_callback=self._onparamRangeEnter)
866
867                        self.sizer4_4.Add(ctl3, (iy, ix), (1, 1),
868                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
869
870                        ctl3.Hide()
871
872                        ix = 5
[6f16e25]873                        ctl4 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]874                                             size=(_BOX_WIDTH / 2, 20),
875                                             style=wx.TE_PROCESS_ENTER,
[682c432]876                            text_enter_callback=self._onparamRangeEnter)
877                        self.sizer4_4.Add(ctl4, (iy, ix), (1, 1),
878                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
879                        ctl4.Hide()
880
881                        if self.data.__class__.__name__ == "Data2D" or \
882                                self.enable2D:
883                            ctl3.Show(True)
884                            ctl4.Show(True)
885
886                    elif p == "npts":
887                        ix = 6
888                        value = self.model.getParam(name2)
[6f16e25]889                        Tctl = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]890                                             size=(_BOX_WIDTH / 2.2, 20),
891                                             style=wx.TE_PROCESS_ENTER)
[682c432]892
893                        Tctl.SetValue(str(format_number(value)))
894                        if self.data.__class__.__name__ == "Data2D" or \
895                                self.enable2D:
896                            Tctl.Show(True)
897                        else:
898                            Tctl.Hide()
899                        self.sizer4_4.Add(Tctl, (iy, ix), (1, 1),
900                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
901                        self.fixed_param.append([None, name2, Tctl, None, None,
902                                                 None, None, None])
903                        self.orientation_params_disp.append([None, name2,
904                                                             Tctl, None, None,
905                                                             None, None, None])
906                    elif p == "nsigmas":
907                        ix = 7
908                        value = self.model.getParam(name3)
[6f16e25]909                        Tct2 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]910                                             size=(_BOX_WIDTH / 2.2, 20),
911                                             style=wx.TE_PROCESS_ENTER)
[682c432]912
913                        Tct2.SetValue(str(format_number(value)))
914                        if self.data.__class__.__name__ == "Data2D" or \
915                                self.enable2D:
916                            Tct2.Show(True)
917                        else:
918                            Tct2.Hide()
919                        self.sizer4_4.Add(Tct2, (iy, ix), (1, 1),
920                                          wx.EXPAND | wx.ADJUST_MINSIZE, 0)
921
922                        self.fixed_param.append([None, name3, Tct2,
923                                                 None, None, None, None, None])
924
925                        self.orientation_params_disp.append([None, name3,
926                                        Tct2, None, None, None, None, None])
927
928                ix = 8
[6f16e25]929                disp_box = wx.ComboBox(self, wx.ID_ANY, size=(65, -1),
[682c432]930                                style=wx.CB_READONLY, name='%s' % name1)
[fa81e94]931                for key, value in POLYDISPERSITY_MODELS.items():
[682c432]932                    name_disp = str(key)
933                    disp_box.Append(name_disp, value)
934                    disp_box.SetStringSelection("gaussian")
[6f16e25]935                wx.EVT_COMBOBOX(disp_box, wx.ID_ANY, self._on_disp_func)
[682c432]936                self.sizer4_4.Add(disp_box, (iy, ix), (1, 1), wx.EXPAND)
937                self.fittable_param.append([cb, name1, ctl1, text2,
938                                            ctl2, ctl3, ctl4, disp_box])
939                self.orientation_params_disp.append([cb, name1, ctl1,
940                                            text2, ctl2, ctl3, ctl4, disp_box])
941
942                if self.data.__class__.__name__ == "Data2D" or \
943                                self.enable2D:
944                    disp_box.Show(True)
945                else:
946                    disp_box.Hide()
947
948        self.state.disp_cb_dict = copy.deepcopy(self.disp_cb_dict)
949
950        self.state.model = self.model.clone()
[c8e1996]951        # save state into
[682c432]952        self._copy_parameters_state(self.parameters, self.state.parameters)
953        self._copy_parameters_state(self.orientation_params_disp,
954                                     self.state.orientation_params_disp)
955        self._copy_parameters_state(self.fittable_param,
956                                    self.state.fittable_param)
957        self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
958
959        wx.PostEvent(self.parent,
960                     StatusEvent(status=" Selected Distribution: Gaussian"))
[c8e1996]961        # Fill the list of fittable parameters
[682c432]962        self.get_all_checked_params()
963        self.Layout()
964
965    def _onDraw(self, event):
966        """
967        Update and Draw the model
968        """
[c8e1996]969        if self.model is None:
[682c432]970            msg = "Please select a Model first..."
971            wx.MessageBox(msg, 'Info')
972            return
973        """
974        if not self.data.is_data:
975            self.npts_x = self.Npts_total.GetValue()
976            self.Npts_fit.SetValue(self.npts_x)
977            self.create_default_data()
978        """
[c8e1996]979        flag, is_modified = self._update_paramv_on_fit()
[682c432]980
[c8e1996]981        wx.CallAfter(self._onparamEnter_helper, is_modified)
[682c432]982        if not flag:
983            msg = "The parameters are invalid"
984            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
985            return
986
987    def _onFit(self, event):
988        """
989        Allow to fit
990        """
[c8e1996]991        if event is not None:
[682c432]992            event.Skip()
993        if self.fit_started:
994            self._StopFit()
995            self.fit_started = False
996            wx.CallAfter(self.set_fitbutton)
997            return
998
999        if self.data is None:
1000            msg = "Please get Data first..."
1001            wx.MessageBox(msg, 'Info')
1002            wx.PostEvent(self._manager.parent,
1003                         StatusEvent(status="Fit: %s" % msg))
1004            return
1005        if self.model is None:
1006            msg = "Please select a Model first..."
1007            wx.MessageBox(msg, 'Info')
1008            wx.PostEvent(self._manager.parent,
1009                         StatusEvent(status="Fit: %s" % msg, type="stop"))
1010            return
1011
1012        if len(self.param_toFit) <= 0:
1013            msg = "Select at least one parameter to fit"
1014            wx.MessageBox(msg, 'Info')
1015            wx.PostEvent(self._manager.parent,
1016                         StatusEvent(status=msg, type="stop"))
1017            return
1018
1019        flag = self._update_paramv_on_fit()
1020
1021        if self.batch_on and not self._is_2D():
1022            if not self._validate_Npts_1D():
1023                return
1024
1025        if not flag:
1026            msg = "Fitting range or parameters are invalid"
1027            wx.PostEvent(self._manager.parent,
1028                         StatusEvent(status=msg, type="stop"))
1029            return
1030
[c8e1996]1031        self.select_param()
[682c432]1032
1033        # Remove or do not allow fitting on the Q=0 point, especially
1034        # when y(q=0)=None at x[0].
1035        self.qmin_x = float(self.qmin.GetValue())
1036        self.qmax_x = float(self.qmax.GetValue())
1037        self._manager._reset_schedule_problem(value=0, uid=self.uid)
1038        self._manager.schedule_for_fit(uid=self.uid, value=1)
1039        self._manager.set_fit_range(uid=self.uid, qmin=self.qmin_x,
1040                                    qmax=self.qmax_x)
1041
[c8e1996]1042        # single fit
1043        # self._manager.onFit(uid=self.uid)
[682c432]1044        self.fit_started = self._manager.onFit(uid=self.uid)
1045        wx.CallAfter(self.set_fitbutton)
1046
[7801df8]1047    def _onFitHelp(self, event):
1048        """
1049        Bring up the Full Fitting Documentation whenever the HELP button is
1050        clicked.
1051
1052        Calls DocumentationWindow with the path of the location within the
1053        documentation tree (after /doc/ ....".  Note that when using old
1054        versions of Wx (before 2.9) and thus not the release version of
1055        installers, the help comes up at the top level of the file as
[c8e1996]1056        web browser does not pass anything past the # to the browser when it is
[7801df8]1057        running "file:///...."
1058
[c8e1996]1059        :param evt: Triggers on clicking the help button
1060        """
[7801df8]1061
[f27dd1e]1062        _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html"
[6f16e25]1063        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
[7801df8]1064                                          "General Fitting Help")
1065
1066    def _onSmearHelp(self, event):
1067        """
1068        Bring up the instrumental resolution smearing Documentation whenever
1069        the ? button in the smearing box is clicked.
1070
1071        Calls DocumentationWindow with the path of the location within the
1072        documentation tree (after /doc/ ....".  Note that when using old
1073        versions of Wx (before 2.9) and thus not the release version of
1074        installers, the help comes up at the top level of the file as
[c8e1996]1075        web browser does not pass anything past the # to the browser when it is
[7801df8]1076        running "file:///...."
1077
[c8e1996]1078        :param evt: Triggers on clicking the help button
1079        """
[7801df8]1080
[3bd677b]1081        _TreeLocation = "user/sasgui/perspectives/fitting/resolution.html"
[6f16e25]1082        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
[7801df8]1083                                          "Instrumental Resolution Smearing \
1084                                          Help")
1085
[682c432]1086    def set_fitbutton(self):
1087        """
1088        Set fit button label depending on the fit_started[bool]
1089        """
1090        # Skip this feature if we are not on Windows
[c8e1996]1091        # NOTE: the is_mac data member actually means "is no Windows".
[682c432]1092        if self.is_mac:
1093            return
1094
1095        if self.fit_started:
1096            label = "Stop"
1097            color = "red"
1098        else:
1099            label = "Fit"
1100            color = "black"
[c8e1996]1101        # self.btFit.Enable(False)
[682c432]1102        self.btFit.SetLabel(label)
1103        self.btFit.SetForegroundColour(color)
1104        self.btFit.Enable(True)
1105
1106    def get_weight_flag(self):
1107        """
1108        Get flag corresponding to a given weighting dI data.
1109        """
1110        button_list = [self.dI_noweight,
1111                       self.dI_didata,
1112                       self.dI_sqrdata,
1113                       self.dI_idata]
1114        flag = 1
1115        for item in button_list:
1116            if item.GetValue():
1117                if button_list.index(item) == 0:
[9a5097c]1118                    flag = 0  # dy = np.ones_like(dy_data)
[682c432]1119                elif button_list.index(item) == 1:
1120                    flag = 1  # dy = dy_data
1121                elif button_list.index(item) == 2:
[9a5097c]1122                    flag = 2  # dy = np.sqrt(np.abs(data))
[682c432]1123                elif button_list.index(item) == 3:
[9a5097c]1124                    flag = 3  # dy = np.abs(data)
[682c432]1125                break
1126        return flag
1127
1128    def _StopFit(self, event=None):
1129        """
1130        Stop fit
1131        """
[c8e1996]1132        if event is not None:
[682c432]1133            event.Skip()
1134        self._manager.stop_fit(self.uid)
1135        self._manager._reset_schedule_problem(value=0)
1136        self._on_fit_complete()
1137
1138    def rename_model(self):
1139        """
1140        find a short name for model
1141        """
1142        if self.model is not None:
1143            self.model.name = "M" + str(self.index_model)
1144
[277257f]1145    def _on_select_model(self, event=None, keep_pars=False):
[682c432]1146        """
1147        call back for model selection
1148        """
1149        self.Show(False)
[c8e1996]1150        if event is not None:
[277257f]1151            control = event.GetEventObject()
1152            if ((control == self.formfactorbox
1153                 and self.structurebox.GetLabel() != 'None')
1154                    or control == self.structurebox
1155                    or control == self.multifactorbox):
1156                keep_pars = True
1157
1158        if keep_pars:
1159            saved_pars = self.get_copy_params()
1160            is_poly_enabled = self.enable_disp.GetValue()
1161        else:
1162            saved_pars = None
1163            is_poly_enabled = None
[682c432]1164
[79e6a33]1165        try:
1166            self._on_select_model_helper()
1167        except Exception as e:
1168            evt = StatusEvent(status=e.message, info="error")
1169            wx.PostEvent(self._manager.parent, evt)
1170            # Set S(Q) to None
1171            self.structurebox.SetSelection(0)
1172            self._on_select_model()
1173            return
[682c432]1174        self.set_model_param_sizer(self.model)
1175        if self.model is None:
1176            self._set_bookmark_flag(False)
1177            self._keep.Enable(False)
1178            self._set_save_flag(False)
1179        self.enable_disp.SetValue(False)
1180        self.disable_disp.SetValue(True)
[6f16e25]1181        # TODO: should not have an untrapped exception when displaying disperser
1182        # TODO: do we need to create the disperser panel on every model change?
1183        # Note: if we fix this, then remove ID_DISPERSER_HELP from basepage
[682c432]1184        try:
1185            self.set_dispers_sizer()
[277257f]1186        except Exception:
[682c432]1187            pass
1188        self.state.enable_disp = self.enable_disp.GetValue()
1189        self.state.disable_disp = self.disable_disp.GetValue()
1190        self.state.pinhole_smearer = self.pinhole_smearer.GetValue()
1191        self.state.slit_smearer = self.slit_smearer.GetValue()
1192
[77910cf]1193        self.state.structurecombobox = self.structurebox.GetValue()
1194        self.state.formfactorcombobox = self.formfactorbox.GetValue()
1195        self.state.categorycombobox = self.categorybox.GetValue()
[682c432]1196        self.enable_fit_button()
[373d4ee]1197        if self.model is not None:
[682c432]1198            self.m_name = self.model.name
1199            self.state.m_name = self.m_name
1200            self.rename_model()
1201            self._set_copy_flag(True)
1202            self._set_paste_flag(True)
1203            if self.data is not None:
1204                is_data = check_data_validity(self.data)
1205                if is_data:
1206                    self._set_bookmark_flag(not self.batch_on)
1207                    self._keep.Enable(not self.batch_on)
1208                    self._set_save_flag(True)
[e0d9ed2]1209            #Setting smearing for cases with and without data.
1210            self._set_smear(self.data)
[682c432]1211
1212            # more disables for 2D
1213            self._set_smear_buttons()
1214
1215            try:
1216                # update smearer sizer
[e0d9ed2]1217                #This call for smearing set up caused double evaluation of
1218                #I(q) and double compilation as results
1219                #self.onSmear(None)
[682c432]1220                temp_smear = None
1221                if not self.disable_smearer.GetValue():
1222                    # Set the smearer environments
1223                    temp_smear = self.current_smearer
1224            except:
1225                raise
[c8e1996]1226                # error occured on chisqr computation
1227                # pass
1228            # event to post model to fit to fitting plugins
[682c432]1229            (ModelEventbox, EVT_MODEL_BOX) = wx.lib.newevent.NewEvent()
1230
[c8e1996]1231            # set smearing value whether or not data contain the smearing info
[682c432]1232            evt = ModelEventbox(model=self.model,
[bf44249e]1233                                smearer=temp_smear,
1234                                enable_smearer=not self.disable_smearer.GetValue(),
1235                                qmin=float(self.qmin_x),
1236                                uid=self.uid,
1237                                caption=self.window_caption,
1238                                qmax=float(self.qmax_x))
[682c432]1239
1240            self._manager._on_model_panel(evt=evt)
[c8e1996]1241            self.mbox_description.SetLabel("Model [ %s ]" %
1242                                           str(self.model.name))
[682c432]1243            self.mbox_description.SetForegroundColour(wx.BLUE)
1244            self.state.model = self.model.clone()
1245            self.state.model.name = self.model.name
1246
[277257f]1247        # when select a model only from guictr/button
1248        if is_poly_enabled is not None:
1249            self.enable_disp.SetValue(is_poly_enabled)
1250            self.disable_disp.SetValue(not is_poly_enabled)
1251            self._set_dipers_Param(event=None)
1252            self.state.enable_disp = self.enable_disp.GetValue()
1253            self.state.disable_disp = self.disable_disp.GetValue()
1254
1255        # Keep the previous param values
1256        if saved_pars:
1257            self.get_paste_params(saved_pars)
1258
[c8e1996]1259        if event is not None:
[277257f]1260            # update list of plugins if new plugin is available
1261            # mod_cat = self.categorybox.GetStringSelection()
1262            # if mod_cat == CUSTOM_MODEL:
1263            #     temp = self.parent.update_model_list()
[69363c7]1264            #     for v in self.parent.model_dictionary.values():
1265            #         if v.id == self.model.id:
1266            #             self.model = v()
1267            #             break
[277257f]1268            #     if temp:
1269            #         self.model_list_box = temp
1270            #         current_val = self.formfactorbox.GetLabel()
1271            #         pos = self.formfactorbox.GetSelection()
1272            #         self._show_combox_helper()
1273            #         self.formfactorbox.SetStringSelection(current_val)
1274            #         self.formfactorbox.SetValue(current_val)
[c8e1996]1275            # post state to fit panel
[682c432]1276            new_event = PageInfoEvent(page=self)
1277            wx.PostEvent(self.parent, new_event)
[fdaf2b1]1278            wx.CallAfter(self._onDraw, None)
[682c432]1279
1280        else:
1281            self._draw_model()
1282
1283        if self.batch_on:
1284            self.slit_smearer.Enable(False)
1285            self.pinhole_smearer.Enable(False)
1286            self.btEditMask.Disable()
1287            self.EditMask_title.Disable()
1288
1289        self.Show(True)
1290        self.SetupScrolling()
1291
1292    def _onparamEnter(self, event):
1293        """
1294        when enter value on panel redraw model according to changed
1295        """
[c8e1996]1296        if self.model is None:
[682c432]1297            msg = "Please select a Model first..."
1298            wx.MessageBox(msg, 'Info')
1299            return
1300
[c8e1996]1301        # default flag
[682c432]1302        flag = False
1303        self.fitrange = True
[c8e1996]1304        # get event object
[682c432]1305        tcrtl = event.GetEventObject()
[c8e1996]1306        # Clear msg if previously shown.
[682c432]1307        msg = ""
1308        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1309
1310        if check_float(tcrtl):
1311            flag = self._onparamEnter_helper()
1312            self.show_npts2fit()
1313            if self.fitrange:
1314                temp_smearer = None
1315                if not self.disable_smearer.GetValue():
1316                    temp_smearer = self.current_smearer
[c8e1996]1317                    # set smearing value whether or not data contain the
1318                    # smearing info
[682c432]1319                    if self.slit_smearer.GetValue():
1320                        flag1 = self.update_slit_smear()
1321                        flag = flag or flag1
1322                    elif self.pinhole_smearer.GetValue():
1323                        flag1 = self.update_pinhole_smear()
1324                        flag = flag or flag1
1325                elif self.data.__class__.__name__ != "Data2D" and \
1326                        not self.enable2D:
[373d4ee]1327                    enable_smearer = not self.disable_smearer.GetValue()
[682c432]1328                    self._manager.set_smearer(smearer=temp_smearer,
1329                                              fid=self.data.id,
1330                                              uid=self.uid,
1331                                              qmin=float(self.qmin_x),
1332                                              qmax=float(self.qmax_x),
[934ce649]1333                                              enable_smearer=enable_smearer)
[682c432]1334                if flag:
[c8e1996]1335                    # self.compute_chisqr(smearer= temp_smearer)
[682c432]1336
[c8e1996]1337                    # new state posted
[682c432]1338                    if self.state_change:
[c8e1996]1339                        # self._undo.Enable(True)
[682c432]1340                        event = PageInfoEvent(page=self)
1341                        wx.PostEvent(self.parent, event)
1342                    self.state_change = False
1343            else:
1344                # invalid fit range: do nothing here:
1345                # msg already displayed in validate
1346                return
1347        else:
1348            self.save_current_state()
[c8e1996]1349            msg = "Cannot Plot: Must enter a number!!!  "
[682c432]1350            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1351
1352        self.save_current_state()
1353        return
1354
1355    def _onparamRangeEnter(self, event):
1356        """
1357        Check validity of value enter in the parameters range field
1358        """
1359        tcrtl = event.GetEventObject()
[c8e1996]1360        # Clear msg if previously shown.
[682c432]1361        msg = ""
1362        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1363        # Flag to register when a parameter has changed.
1364        is_modified = False
1365        if tcrtl.GetValue().lstrip().rstrip() != "":
1366            try:
1367                tcrtl.SetBackgroundColour(wx.WHITE)
[ee4b3cb]1368                self._check_value_enter(self.fittable_param)
1369                self._check_value_enter(self.parameters)
[682c432]1370            except:
1371                tcrtl.SetBackgroundColour("pink")
[fa81e94]1372                msg = "Model Error:wrong value entered : %s" % sys.exc_info()[1]
[682c432]1373                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1374                return
1375        else:
1376            tcrtl.SetBackgroundColour(wx.WHITE)
1377
[c8e1996]1378        # self._undo.Enable(True)
[682c432]1379        self.save_current_state()
1380        event = PageInfoEvent(page=self)
1381        wx.PostEvent(self.parent, event)
1382        self.state_change = False
1383
1384    def qrang_set_focus(self, event=None):
1385        """
1386        ON Qrange focus
1387        """
[c8e1996]1388        if event is not None:
[682c432]1389            event.Skip()
[c8e1996]1390        # tcrtl = event.GetEventObject()
[682c432]1391        self._validate_qrange(self.qmin, self.qmax)
1392
1393    def qrange_click(self, event):
1394        """
1395        On Qrange textctrl click, make the qrange lines in the plot
1396        """
[c8e1996]1397        if event is not None:
[682c432]1398            event.Skip()
1399        if self.data.__class__.__name__ == "Data2D":
1400            return
1401        is_click = event.LeftDown()
1402        if is_click:
1403            d_id = self.data.id
1404            d_group_id = self.data.group_id
1405            act_ctrl = event.GetEventObject()
1406            wx.PostEvent(self._manager.parent,
1407                         PlotQrangeEvent(ctrl=[self.qmin, self.qmax], id=d_id,
1408                                     group_id=d_group_id, leftdown=is_click,
1409                                     active=act_ctrl))
1410
1411    def on_qrange_text(self, event):
1412        """
1413        #On q range value updated. DO not combine with qrange_click().
1414        """
[c8e1996]1415        if event is not None:
[682c432]1416            event.Skip()
1417        if self.data.__class__.__name__ == "Data2D":
1418            return
1419        act_ctrl = event.GetEventObject()
1420        d_id = self.data.id
1421        d_group_id = self.data.group_id
1422        wx.PostEvent(self._manager.parent,
1423                     PlotQrangeEvent(ctrl=[self.qmin, self.qmax], id=d_id,
1424                                     group_id=d_group_id, leftdown=False,
1425                                     active=act_ctrl))
1426        self._validate_qrange(self.qmin, self.qmax)
1427
1428    def on_key(self, event):
1429        """
1430        On Key down
1431        """
1432        event.Skip()
1433        if self.data.__class__.__name__ == "Data2D":
1434            return
1435        ctrl = event.GetEventObject()
1436        try:
1437            x_data = float(ctrl.GetValue())
1438        except:
1439            return
1440        key = event.GetKeyCode()
1441        length = len(self.data.x)
[9a5097c]1442        indx = (np.abs(self.data.x - x_data)).argmin()
[c8e1996]1443        # return array.flat[idx]
[682c432]1444        if key == wx.WXK_PAGEUP or key == wx.WXK_NUMPAD_PAGEUP:
1445            indx += 1
1446            if indx >= length:
1447                indx = length - 1
1448        elif key == wx.WXK_PAGEDOWN or key == wx.WXK_NUMPAD_PAGEDOWN:
1449            indx -= 1
1450            if indx < 0:
1451                indx = 0
1452        else:
1453            return
1454        ctrl.SetValue(str(self.data.x[indx]))
1455        self._validate_qrange(self.qmin, self.qmax)
1456
1457    def _onQrangeEnter(self, event):
1458        """
1459        Check validity of value enter in the Q range field
1460        """
1461        tcrtl = event.GetEventObject()
[c8e1996]1462        # Clear msg if previously shown.
[682c432]1463        msg = ""
1464        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1465        # For theory mode
1466        if not self.data.is_data:
1467            self.npts_x = self.Npts_total.GetValue()
1468            self.Npts_fit.SetValue(self.npts_x)
1469            self.create_default_data()
1470        # Flag to register when a parameter has changed.
1471        if tcrtl.GetValue().lstrip().rstrip() != "":
1472            try:
1473                tcrtl.SetBackgroundColour(wx.WHITE)
1474                # If qmin and qmax have been modified, update qmin and qmax
1475                if self._validate_qrange(self.qmin, self.qmax):
1476                    tempmin = float(self.qmin.GetValue())
1477                    if tempmin != self.qmin_x:
1478                        self.qmin_x = tempmin
1479                    tempmax = float(self.qmax.GetValue())
1480                    if tempmax != self.qmax_x:
1481                        self.qmax_x = tempmax
1482                else:
1483                    tcrtl.SetBackgroundColour("pink")
[fa81e94]1484                    msg = "Model Error:wrong value entered : %s" % sys.exc_info()[1]
[682c432]1485                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1486                    return
1487            except:
1488                tcrtl.SetBackgroundColour("pink")
[fa81e94]1489                msg = "Model Error:wrong value entered : %s" % sys.exc_info()[1]
[682c432]1490                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1491                return
[c8e1996]1492            # Check if # of points for theory model are valid(>0).
[682c432]1493            # check for 2d
1494            if self.data.__class__.__name__ == "Data2D" or \
1495                    self.enable2D:
1496                # set mask
[9a5097c]1497                radius = np.sqrt(self.data.qx_data * self.data.qx_data +
[682c432]1498                                    self.data.qy_data * self.data.qy_data)
[c8e1996]1499                index_data = ((self.qmin_x <= radius) & (radius <= self.qmax_x))
[682c432]1500                index_data = (index_data) & (self.data.mask)
[9a5097c]1501                index_data = (index_data) & (np.isfinite(self.data.data))
[682c432]1502                if len(index_data[index_data]) < 10:
1503                    msg = "Cannot Plot :No or too little npts in"
1504                    msg += " that data range!!!  "
1505                    wx.PostEvent(self._manager.parent,
1506                                 StatusEvent(status=msg))
1507                    return
1508                else:
[c8e1996]1509                    # self.data.mask = index_data
1510                    # self.Npts_fit.SetValue(str(len(self.data.mask)))
[682c432]1511                    self.show_npts2fit()
1512            else:
[c8e1996]1513                index_data = ((self.qmin_x <= self.data.x) &
[682c432]1514                              (self.data.x <= self.qmax_x))
1515                self.Npts_fit.SetValue(str(len(self.data.x[index_data])))
1516
1517            self.npts_x = self.Npts_total.GetValue()
1518            self.create_default_data()
1519            self._save_plotting_range()
1520        else:
1521            tcrtl.SetBackgroundColour("pink")
1522            msg = "Model Error:wrong value entered!!!"
1523            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1524
1525        self._draw_model()
1526        self.save_current_state()
1527        event = PageInfoEvent(page=self)
1528        wx.PostEvent(self.parent, event)
1529        self.state_change = False
1530        return
1531
1532    def _clear_Err_on_Fit(self):
1533        """
1534        hide the error text control shown
1535        after fitting
1536        """
1537
1538        if self.is_mac:
1539            return
1540        if hasattr(self, "text2_3"):
1541            self.text2_3.Hide()
1542
1543        if len(self.parameters) > 0:
1544            for item in self.parameters:
1545                if item[0].IsShown():
[c8e1996]1546                    # Skip the angle parameters if 1D data
[682c432]1547                    if self.data.__class__.__name__ != "Data2D" and \
1548                            not self.enable2D:
1549                        if item in self.orientation_params:
1550                            continue
1551                    if item in self.param_toFit:
1552                        continue
[c8e1996]1553                    # hide statictext +/-
[682c432]1554                    if len(item) < 4:
1555                        continue
[c8e1996]1556                    if item[3] is not None and item[3].IsShown():
[682c432]1557                        item[3].Hide()
[c8e1996]1558                    # hide textcrtl  for error after fit
1559                    if item[4] is not None and item[4].IsShown():
[682c432]1560                        item[4].Hide()
1561
1562        if len(self.fittable_param) > 0:
1563            for item in self.fittable_param:
1564                if item[0].IsShown():
[c8e1996]1565                    # Skip the angle parameters if 1D data
[682c432]1566                    if self.data.__class__.__name__ != "Data2D" and \
1567                            not self.enable2D:
1568                        if item in self.orientation_params:
1569                            continue
1570                    if item in self.param_toFit:
1571                        continue
1572                    if len(item) < 4:
1573                        continue
[c8e1996]1574                    # hide statictext +/-
1575                    if item[3] is not None and item[3].IsShown():
[682c432]1576                        item[3].Hide()
[c8e1996]1577                    # hide textcrtl  for error after fit
1578                    if item[4] is not None and item[4].IsShown():
[682c432]1579                        item[4].Hide()
1580        return
1581
1582    def _get_defult_custom_smear(self):
1583        """
1584        Get the defult values for custum smearing.
1585        """
1586        # get the default values
[c8e1996]1587        if self.dxl is None:
[682c432]1588            self.dxl = 0.0
[c8e1996]1589        if self.dxw is None:
[682c432]1590            self.dxw = ""
[7e98655]1591        if self.dx_percent is None:
1592            self.dx_percent = SMEAR_SIZE_H
[682c432]1593
1594    def _get_smear_info(self):
1595        """
1596        Get the smear info from data.
1597
1598        :return: self.smear_type, self.dq_l and self.dq_r,
1599            respectively the type of the smear, dq_min and
1600            dq_max for pinhole smear data
1601            while dxl and dxw for slit smear
1602        """
1603        # default
1604        self.smear_type = None
1605        self.dq_l = None
1606        self.dq_r = None
1607        data = self.data
1608        if self.data is None:
1609            return
1610        elif self.data.__class__.__name__ == "Data2D" or \
1611            self.enable2D:
[c8e1996]1612            if data.dqx_data is None or data.dqy_data is None:
[682c432]1613                return
[c8e1996]1614            elif self.current_smearer is not None \
[682c432]1615                and data.dqx_data.any() != 0 \
1616                and data.dqx_data.any() != 0:
1617                self.smear_type = "Pinhole2d"
[9a5097c]1618                self.dq_l = format_number(np.average(data.dqx_data))
1619                self.dq_r = format_number(np.average(data.dqy_data))
[682c432]1620                return
1621            else:
1622                return
1623        # check if it is pinhole smear and get min max if it is.
[9a5097c]1624        if data.dx is not None and np.any(data.dx):
[682c432]1625            self.smear_type = "Pinhole"
1626            self.dq_l = data.dx[0]
1627            self.dq_r = data.dx[-1]
1628
1629        # check if it is slit smear and get min max if it is.
[c8e1996]1630        elif data.dxl is not None or data.dxw is not None:
[682c432]1631            self.smear_type = "Slit"
[9a5097c]1632            if data.dxl is not None and np.all(data.dxl, 0):
[682c432]1633                self.dq_l = data.dxl[0]
[9a5097c]1634            if data.dxw is not None and np.all(data.dxw, 0):
[682c432]1635                self.dq_r = data.dxw[0]
[c8e1996]1636        # return self.smear_type,self.dq_l,self.dq_r
[682c432]1637
1638    def _show_smear_sizer(self):
1639        """
1640        Show only the sizers depending on smear selection
1641        """
1642        # smear disabled
1643        if self.disable_smearer.GetValue():
1644            self.smear_description_none.Show(True)
1645        # 2Dsmear
1646        elif self._is_2D():
1647            self.smear_description_accuracy_type.Show(True)
1648            self.smear_accuracy.Show(True)
1649            self.smear_description_accuracy_type.Show(True)
1650            self.smear_description_2d.Show(True)
1651            self.smear_description_2d_x.Show(True)
1652            self.smear_description_2d_y.Show(True)
1653            if self.pinhole_smearer.GetValue():
[7e98655]1654                self.smear_pinhole_percent.Show(True)
[682c432]1655        # smear from data
1656        elif self.enable_smearer.GetValue():
1657
1658            self.smear_description_dqdata.Show(True)
[c8e1996]1659            if self.smear_type is not None:
[682c432]1660                self.smear_description_smear_type.Show(True)
1661                if self.smear_type == 'Slit':
1662                    self.smear_description_slit_height.Show(True)
1663                    self.smear_description_slit_width.Show(True)
1664                elif self.smear_type == 'Pinhole':
[7e98655]1665                    self.smear_description_pin_percent.Show(True)
[682c432]1666                self.smear_description_smear_type.Show(True)
1667                self.smear_description_type.Show(True)
1668                self.smear_data_left.Show(True)
1669                self.smear_data_right.Show(True)
1670        # custom pinhole smear
1671        elif self.pinhole_smearer.GetValue():
1672            if self.smear_type == 'Pinhole':
1673                self.smear_message_new_p.Show(True)
[7e98655]1674                self.smear_description_pin_percent.Show(True)
[682c432]1675
[7e98655]1676            self.smear_pinhole_percent.Show(True)
[682c432]1677        # custom slit smear
1678        elif self.slit_smearer.GetValue():
1679            self.smear_message_new_s.Show(True)
1680            self.smear_description_slit_height.Show(True)
1681            self.smear_slit_height.Show(True)
1682            self.smear_description_slit_width.Show(True)
1683            self.smear_slit_width.Show(True)
1684
1685    def _hide_all_smear_info(self):
1686        """
1687        Hide all smearing messages in the set_smearer sizer
1688        """
1689        self.smear_description_none.Hide()
1690        self.smear_description_dqdata.Hide()
1691        self.smear_description_type.Hide()
1692        self.smear_description_smear_type.Hide()
1693        self.smear_description_accuracy_type.Hide()
1694        self.smear_description_2d_x.Hide()
1695        self.smear_description_2d_y.Hide()
1696        self.smear_description_2d.Hide()
1697
1698        self.smear_accuracy.Hide()
1699        self.smear_data_left.Hide()
1700        self.smear_data_right.Hide()
[7e98655]1701        self.smear_description_pin_percent.Hide()
1702        self.smear_pinhole_percent.Hide()
[682c432]1703        self.smear_description_slit_height.Hide()
1704        self.smear_slit_height.Hide()
1705        self.smear_description_slit_width.Hide()
1706        self.smear_slit_width.Hide()
1707        self.smear_message_new_p.Hide()
1708        self.smear_message_new_s.Hide()
1709
1710    def _set_accuracy_list(self):
1711        """
1712        Set the list of an accuracy in 2D custum smear:
1713                Xhigh, High, Med, or Low
1714        """
1715        # list of accuracy choices
1716        list = ['Low', 'Med', 'High', 'Xhigh']
1717        for idx in range(len(list)):
1718            self.smear_accuracy.Append(list[idx], idx)
1719
1720    def _set_fun_box_list(self, fun_box):
1721        """
1722        Set the list of func for multifunctional models
1723        """
1724        # Check if it is multi_functional model
1725        if self.model.__class__ not in self.model_list_box["Multi-Functions"] \
1726                and not self.temp_multi_functional:
1727            return None
1728        # Get the func name list
1729        list = self.model.fun_list
1730        if len(list) == 0:
1731            return None
1732        # build function (combo)box
1733        ind = 0
1734        while(ind < len(list)):
[fa81e94]1735            for key, val in list(list.items()):
[277257f]1736                if val == ind:
[682c432]1737                    fun_box.Append(key, val)
1738                    break
1739            ind += 1
1740
1741    def _on_select_accuracy(self, event):
1742        """
1743        Select an accuracy in 2D custom smear: Xhigh, High, Med, or Low
1744        """
[c8e1996]1745        # event.Skip()
[682c432]1746        # Check if the accuracy is same as before
[c8e1996]1747        # self.smear2d_accuracy = event.GetEventObject().GetValue()
[682c432]1748        self.smear2d_accuracy = self.smear_accuracy.GetValue()
1749        if self.pinhole_smearer.GetValue():
1750            self.onPinholeSmear(event=None)
1751        else:
1752            self.onSmear(event=None)
[c8e1996]1753            if self.current_smearer is not None:
[682c432]1754                self.current_smearer.set_accuracy(accuracy=\
1755                                                  self.smear2d_accuracy)
1756        event.Skip()
1757
1758    def _on_fun_box(self, event):
1759        """
1760        Select an func: Erf,Rparabola,LParabola
1761        """
1762        fun_val = None
1763        fun_box = event.GetEventObject()
1764        name = fun_box.Name
1765        value = fun_box.GetValue()
1766        if value in self.model.fun_list:
1767            fun_val = self.model.fun_list[value]
1768
1769        self.model.setParam(name, fun_val)
1770        # save state
1771        self._copy_parameters_state(self.str_parameters,
1772                                    self.state.str_parameters)
1773        # update params
1774        self._update_paramv_on_fit()
1775        # draw
1776        self._draw_model()
1777        self.Refresh()
1778        # get ready for new event
1779        event.Skip()
1780
1781    def _onMask(self, event):
1782        """
1783        Build a panel to allow to edit Mask
1784        """
[c8e1996]1785        from sas.sasgui.guiframe.local_perspectives.plotting.masking import \
1786            MaskPanel as MaskDialog
[682c432]1787
1788        self.panel = MaskDialog(base=self, data=self.data, id=wx.NewId())
1789        self.panel.ShowModal()
1790
1791    def _draw_masked_model(self, event):
1792        """
1793        Draw model image w/mask
1794        """
1795        is_valid_qrange = self._update_paramv_on_fit()
1796
[c8e1996]1797        if is_valid_qrange and self.model is not None:
[682c432]1798            self.panel.MakeModal(False)
1799            event.Skip()
1800            # try re draw the model plot if it exists
1801            self._draw_model()
1802            self.show_npts2fit()
[c8e1996]1803        elif self.model is None:
[682c432]1804            self.panel.MakeModal(False)
1805            event.Skip()
1806            self.show_npts2fit()
1807            msg = "No model is found on updating MASK in the model plot... "
1808            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1809        else:
1810            event.Skip()
1811            msg = ' Please consider your Q range, too.'
1812            self.panel.ShowMessage(msg)
1813
1814    def _set_smear(self, data):
1815        """
1816        Set_smear
1817        """
1818        if data is None:
1819            return
1820        self.current_smearer = smear_selection(data, self.model)
1821        flag = self.disable_smearer.GetValue()
[373d4ee]1822        if self.current_smearer is None:
[682c432]1823            self.enable_smearer.Disable()
1824        else:
1825            self.enable_smearer.Enable()
1826        if not flag:
1827            self.onSmear(None)
1828
1829    def get_view_mode(self):
1830        """
1831        return True if the panel allow 2D or False if 1D
1832        """
1833        return self.enable2D
1834
1835    def compute_data_set_range(self, data_list):
1836        """
1837        find the range that include all data  in the set
1838        return the minimum and the maximum values
1839        """
1840        if data_list is not None and data_list != []:
1841            for data in data_list:
1842                qmin, qmax, npts = self.compute_data_range(data)
1843                self.qmin_data_set = min(self.qmin_data_set, qmin)
1844                self.qmax_data_set = max(self.qmax_data_set, qmax)
1845                self.npts_data_set += npts
1846        return self.qmin_data_set, self.qmax_data_set, self.npts_data_set
1847
1848    def compute_data_range(self, data):
1849        """
1850        compute the minimum and the maximum range of the data
1851        return the npts contains in data
1852        :param data:
1853        """
1854        qmin, qmax, npts = None, None, None
1855        if data is not None:
1856            if not hasattr(data, "data"):
1857                try:
1858                    qmin = min(data.x)
1859                    # Maximum value of data
1860                    qmax = max(data.x)
1861                    npts = len(data.x)
1862                except:
[c8e1996]1863                    msg = "Unable to find min/max/length of \n data named %s" %\
[682c432]1864                                data.filename
1865                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg,
1866                                               info="error"))
[fa81e94]1867                    raise ValueError(msg)
[682c432]1868
1869            else:
1870                qmin = 0
1871                try:
1872                    x = max(math.fabs(data.xmin), math.fabs(data.xmax))
1873                    y = max(math.fabs(data.ymin), math.fabs(data.ymax))
1874                except:
1875                    msg = "Unable to find min/max of \n data named %s" % \
1876                                data.filename
1877                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg,
1878                                               info="error"))
[fa81e94]1879                    raise ValueError(msg)
[c8e1996]1880                # Maximum value of data
[682c432]1881                qmax = math.sqrt(x * x + y * y)
1882                npts = len(data.data)
1883        return qmin, qmax, npts
1884
1885    def set_data(self, data):
1886        """
1887        reset the current data
1888        """
1889        id = None
1890        flag = False
1891        is_data = False
[c8e1996]1892        npts = 0
[682c432]1893        try:
1894            old_id = self.data.id
1895            old_group_id = self.data.group_id
1896        except:
1897            old_id = id
1898            old_group_id = id
1899        if self.data is not None:
1900            is_data = check_data_validity(self.data)
1901        if not is_data and data is not None:
1902                flag = True
1903        if data is not None:
1904            if is_data:
1905                self.graph_id = self.data.group_id
1906                flag = (data.id != self.data.id)
1907        self.data = data
1908        if check_data_validity(data):
1909            self.graph_id = data.group_id
1910        self.data.group_id = self.graph_id
1911
1912        if self.data is None:
1913            data_name = ""
1914            self._set_bookmark_flag(False)
1915            self._keep.Enable(False)
1916            self._set_save_flag(False)
1917        else:
[c8e1996]1918            if self.model is not None:
[682c432]1919                self._set_bookmark_flag(not self.batch_on)
1920                self._keep.Enable(not self.batch_on)
1921            if self.data.is_data:
1922                self._set_save_flag(True)
1923                self._set_preview_flag(True)
1924
1925            # more disables for 2D
[03137f1]1926            di_flag = False
1927            dq_flag = False
[682c432]1928            if self.data.__class__.__name__ == "Data2D" or \
1929                        self.enable2D:
1930                self.slit_smearer.Disable()
1931                self.pinhole_smearer.Enable(True)
1932                self.default_mask = copy.deepcopy(self.data.mask)
[3d55219]1933                if self.data.err_data is not None \
[9a5097c]1934                        and np.any(self.data.err_data):
[03137f1]1935                    di_flag = True
[3d55219]1936                if self.data.dqx_data is not None \
[9a5097c]1937                        and np.any(self.data.dqx_data):
[03137f1]1938                    dq_flag = True
[682c432]1939            else:
1940                self.slit_smearer.Enable(True)
1941                self.pinhole_smearer.Enable(True)
[9a5097c]1942                if self.data.dy is not None and np.any(self.data.dy):
[03137f1]1943                    di_flag = True
[9a5097c]1944                if self.data.dx is not None and np.any(self.data.dx):
[3d55219]1945                    dq_flag = True
[9a5097c]1946                elif self.data.dxl is not None and np.any(self.data.dxl):
[03137f1]1947                    dq_flag = True
1948
1949            if dq_flag:
1950                self.enable_smearer.Enable(True)
1951                self.enable_smearer.SetValue(True)
1952                self.disable_smearer.SetValue(False)
[3d55219]1953            else:
1954                self.enable_smearer.Disable()
1955                self.disable_smearer.Enable(True)
1956                self.disable_smearer.SetValue(True)
[03137f1]1957
1958            if di_flag:
1959                self.dI_didata.Enable(True)
1960                self.dI_didata.SetValue(True)
1961                self.weightbt_string = self.dI_didata.GetLabelText()
[3d55219]1962            else:
1963                self.dI_didata.Enable(False)
1964                self.dI_noweight.SetValue(True)
1965                self.weightbt_string = self.dI_noweight.GetLabelText()
[c8e1996]1966
1967            # Enable weighting radio buttons
[682c432]1968            self.dI_noweight.Enable(True)
1969            self.dI_sqrdata.Enable(True)
1970            self.dI_idata.Enable(True)
1971
1972            self.formfactorbox.Enable()
1973            self.structurebox.Enable()
1974            data_name = self.data.name
1975            _, _, npts = self.compute_data_range(self.data)
[c8e1996]1976            # set maximum range for x in linear scale
[682c432]1977            if not hasattr(self.data, "data"):  # Display only for 1D data fit
1978                self.btEditMask.Disable()
1979                self.EditMask_title.Disable()
1980            else:
1981                self.btEditMask.Enable()
1982                self.EditMask_title.Enable()
1983
1984        self.Npts_total.SetValue(str(npts))
[c8e1996]1985        # default:number of data points selected to fit
[682c432]1986        self.Npts_fit.SetValue(str(npts))
1987        self.Npts_total.SetEditable(False)
[c8e1996]1988        self.Npts_total.SetBackgroundColour(
[682c432]1989                                    self.GetParent().GetBackgroundColour())
1990
1991        self.Npts_total.Bind(wx.EVT_MOUSE_EVENTS, self._npts_click)
1992        self.pointsbox.Disable()
1993        self.dataSource.SetValue(data_name)
1994        self.state.data = data
1995        self.enable_fit_button()
1996        # send graph_id to page_finder
1997        self._manager.set_graph_id(uid=self.uid, graph_id=self.graph_id)
[c8e1996]1998        # focus the page
[682c432]1999        if check_data_validity(data):
2000            self.data_box_description.SetForegroundColour(wx.BLUE)
2001
2002        if self.batch_on:
2003            self.slit_smearer.Enable(False)
2004            self.pinhole_smearer.Enable(False)
2005            self.btEditMask.Disable()
2006            self.EditMask_title.Disable()
2007
[2d568f6]2008        self.on_smear_helper()
[682c432]2009        self.on_set_focus(None)
2010        self.Refresh()
[c8e1996]2011        # update model plot with new data information
[682c432]2012        if flag:
2013            if self.data.__class__.__name__ == "Data2D":
2014                self.enable2D = True
2015                self.model_view.SetLabel("2D Mode")
2016            else:
2017                self.enable2D = False
2018                self.model_view.SetLabel("1D Mode")
2019            self.model_view.Disable()
[c8e1996]2020            #  replace data plot on combo box selection
2021            # by removing the previous selected data
[682c432]2022            try:
2023                wx.PostEvent(self._manager.parent,
2024                             NewPlotEvent(action="delete",
2025                                          group_id=old_group_id, id=old_id))
2026            except:
2027                pass
[c8e1996]2028            # plot the current selected data
[682c432]2029            wx.PostEvent(self._manager.parent,
2030                         NewPlotEvent(action="check", plot=self.data,
2031                                      title=str(self.data.title)))
2032            self._draw_model()
2033
2034    def _npts_click(self, event):
2035        """
2036        Prevent further handling of the mouse event on Npts_total
2037        by not calling Skip().
2038        """
2039        pass
2040
2041    def reset_page(self, state, first=False):
2042        """
2043        reset the state
2044        """
2045        try:
2046            self.reset_page_helper(state)
2047
[c8e1996]2048            self.select_param()
2049            # Save state_fit
[682c432]2050            self.save_current_state_fit()
[48154abb]2051            self.onSmear(None)
2052            self._onDraw(None)
[682c432]2053        except:
2054            self._show_combox_helper()
2055            msg = "Error: This model state has missing or outdated "
2056            msg += "information.\n"
[6c382da]2057            msg += traceback.format_exc()
[682c432]2058            wx.PostEvent(self._manager.parent,
2059                         StatusEvent(status=msg, info="error"))
2060        self._lay_out()
2061        self.Refresh()
2062
2063    def get_range(self):
2064        """
2065        return the fitting range
2066        """
2067        return float(self.qmin_x), float(self.qmax_x)
2068
2069    def get_npts2fit(self):
2070        """
2071        return numbers of data points within qrange
2072
2073        :Note: This is to normalize chisq by Npts of fit
2074
2075        """
2076        if self.data is None:
2077            return
2078        npts2fit = 0
2079        qmin, qmax = self.get_range()
2080        if self.data.__class__.__name__ == "Data2D" or \
2081                        self.enable2D:
[9a5097c]2082            radius = np.sqrt(self.data.qx_data * self.data.qx_data +
[682c432]2083                                self.data.qy_data * self.data.qy_data)
2084            index_data = (self.qmin_x <= radius) & (radius <= self.qmax_x)
2085            index_data = (index_data) & (self.data.mask)
[9a5097c]2086            index_data = (index_data) & (np.isfinite(self.data.data))
[682c432]2087            npts2fit = len(self.data.data[index_data])
2088        else:
2089            for qx in self.data.x:
[c8e1996]2090                if qmax >= qx >= qmin:
[682c432]2091                    npts2fit += 1
2092        return npts2fit
2093
2094    def show_npts2fit(self):
2095        """
2096        setValue Npts for fitting
2097        """
2098        self.Npts_fit.SetValue(str(self.get_npts2fit()))
2099
2100    def get_chi2(self):
2101        """
2102        return the current chi2
2103        """
2104        return self.tcChi.GetValue()
2105
2106    def onsetValues(self, chisqr, p_name, out, cov):
2107        """
2108        Build the panel from the fit result
2109
2110        :param chisqr: Value of the goodness of fit metric
2111        :param p_name: the name of parameters
2112        :param out: list of parameter with the best value found during fitting
2113        :param cov: Covariance matrix
2114
2115        """
2116
2117        # make sure stop button to fit button all the time
2118        self._on_fit_complete()
[9a5097c]2119        if out is None or not np.isfinite(chisqr):
[fa81e94]2120            raise ValueError("Fit error occured...")
[682c432]2121
2122        is_modified = False
2123        has_error = False
2124        dispersity = ''
2125
[c8e1996]2126        # Hide textctrl boxes of errors.
[682c432]2127        self._clear_Err_on_Fit()
2128
[c8e1996]2129        # Check if chi2 is finite
[9a5097c]2130        if chisqr is not None and np.isfinite(chisqr):
[c8e1996]2131            # format chi2
[682c432]2132            chi2 = format_number(chisqr, True)
2133            self.tcChi.SetValue(chi2)
2134            self.tcChi.Refresh()
2135        else:
2136            self.tcChi.SetValue("-")
2137
[c8e1996]2138        # Hide error title
[682c432]2139        if self.text2_3.IsShown() and not self.is_mac:
2140            self.text2_3.Hide()
2141
2142        try:
2143            if self.enable_disp.GetValue():
2144                if hasattr(self, "text_disp_1"):
[c8e1996]2145                    if self.text_disp_1 is not None and not self.is_mac:
[682c432]2146                        self.text_disp_1.Hide()
2147        except:
2148            dispersity = None
2149            pass
2150
2151        i = 0
[c8e1996]2152        # Set the panel when fit result are list
[682c432]2153
2154        for item in self.param_toFit:
[c8e1996]2155            if len(item) > 5 and item is not None:
[682c432]2156
2157                if item[0].IsShown():
[c8e1996]2158                    # reset error value to initial state
[682c432]2159                    if not self.is_mac:
2160                        item[3].Hide()
2161                        item[4].Hide()
2162                    for ind in range(len(out)):
2163                        if item[1] == p_name[ind]:
2164                            break
[c8e1996]2165                    if len(out) > 0 and out[ind] is not None:
[682c432]2166                        val_out = format_number(out[ind], True)
2167                        item[2].SetValue(val_out)
2168
[c8e1996]2169                    if(cov is not None and len(cov) == len(out)):
[682c432]2170                        try:
[c8e1996]2171                            if dispersity is not None:
[682c432]2172                                if self.enable_disp.GetValue():
2173                                    if hasattr(self, "text_disp_1"):
[c8e1996]2174                                        if self.text_disp_1 is not None:
[682c432]2175                                            if not self.text_disp_1.IsShown()\
[c8e1996]2176                                                  and not self.is_mac:
[682c432]2177                                                self.text_disp_1.Show(True)
2178                        except:
2179                            pass
2180
[c8e1996]2181                        if cov[ind] is not None:
[9a5097c]2182                            if np.isfinite(float(cov[ind])):
[682c432]2183                                val_err = format_number(cov[ind], True)
[c8e1996]2184                                item[4].SetForegroundColour(wx.BLACK)
[373d4ee]2185                            else:
2186                                val_err = 'NaN'
[60d08fd]2187                                item[4].SetForegroundColour(wx.RED)
[373d4ee]2188                            if not self.is_mac:
2189                                item[3].Show(True)
2190                                item[4].Show(True)
2191                            item[4].SetValue(val_err)
2192                            has_error = True
[682c432]2193                i += 1
2194            else:
[fa81e94]2195                raise ValueError("onsetValues: Invalid parameters...")
[c8e1996]2196        # Show error title when any errors displayed
[682c432]2197        if has_error:
2198            if not self.text2_3.IsShown():
2199                self.text2_3.Show(True)
[c8e1996]2200        # save current state
[682c432]2201        self.save_current_state()
2202
[0b6f83c]2203        if not self.is_mac:
2204            self.Layout()
2205            self.Refresh()
[c8e1996]2206        # plot model ( when drawing, do not update chisqr value again)
[682c432]2207        self._draw_model(update_chisqr=False, source='fit')
2208
2209    def onWeighting(self, event):
2210        """
2211        On Weighting radio button event, sets the weightbt_string
2212        """
2213        self.weightbt_string = event.GetEventObject().GetLabelText()
2214        self._set_weight()
2215
2216    def _set_weight(self, is_2D=None):
2217        """
2218        Set weight in fit problem
2219        """
2220        # compute weight for the current data
2221        flag_weight = self.get_weight_flag()
[c8e1996]2222        if is_2D is None:
[682c432]2223            is_2D = self._is_2D()
2224        self._manager.set_fit_weight(uid=self.uid,
2225                                     flag=flag_weight,
2226                                     is2d=is_2D,
2227                                     fid=None)
2228
2229    def onPinholeSmear(self, event):
2230        """
2231        Create a custom pinhole smear object that will change the way residuals
2232        are compute when fitting
2233
2234        :Note: accuracy is given by strings'High','Med', 'Low' FOR 2d,
2235                     None for 1D
2236
2237        """
2238        # Need update param values
2239        self._update_paramv_on_fit()
2240
[c8e1996]2241        if event is not None:
[682c432]2242            tcrtl = event.GetEventObject()
2243            # event case of radio button
[c8e1996]2244            if tcrtl.GetValue():
[7e98655]2245                self.dx_percent = 0.0
[682c432]2246                is_new_pinhole = True
2247            else:
2248                is_new_pinhole = self._is_changed_pinhole()
2249        else:
2250            is_new_pinhole = True
2251        # if any value is changed
2252        if is_new_pinhole:
[098f3d2]2253            self._set_pinhole_smear()
[682c432]2254        # hide all silt sizer
2255        self._hide_all_smear_info()
2256
2257        # show relevant slit sizers
2258        self._show_smear_sizer()
2259
2260        self.sizer_set_smearer.Layout()
[c8e1996]2261        # we need FitInside here not just self.Layout to ensure all the sizers
2262        # end up with the necessasary space to in the scroll panel. In
2263        # particular the compute and fit buttons end up on top of each other
2264        # PDB Nov 28 2015.
[1c2bf90]2265        self.FitInside()
[682c432]2266
[c8e1996]2267        if event is not None:
[682c432]2268            event.Skip()
[c8e1996]2269        # self._undo.Enable(True)
[682c432]2270        self.save_current_state()
2271        event = PageInfoEvent(page=self)
2272        wx.PostEvent(self.parent, event)
2273
2274    def _is_changed_pinhole(self):
2275        """
2276        check if any of pinhole smear is changed
2277
2278        :return: True or False
2279
2280        """
2281        # get the values
[7e98655]2282        pin_percent = self.smear_pinhole_percent.GetValue()
[682c432]2283
2284        # Check changes in slit heigth
2285        try:
[7e98655]2286            dx_percent = float(pin_percent)
[682c432]2287        except:
2288            return True
[7e98655]2289        if self.dx_percent != dx_percent:
[682c432]2290            return True
2291        return False
2292
2293    def _set_pinhole_smear(self):
2294        """
2295        Set custom pinhole smear
2296
2297        :return: msg
2298
2299        """
2300        # copy data
2301        data = copy.deepcopy(self.data)
2302        if self._is_2D():
2303            self.smear_type = 'Pinhole2d'
2304            len_data = len(data.data)
[9a5097c]2305            data.dqx_data = np.zeros(len_data)
2306            data.dqy_data = np.zeros(len_data)
[682c432]2307        else:
2308            self.smear_type = 'Pinhole'
2309            len_data = len(data.x)
[9a5097c]2310            data.dx = np.zeros(len_data)
[682c432]2311            data.dxl = None
2312            data.dxw = None
2313        msg = None
2314
[7e98655]2315        get_pin_percent = self.smear_pinhole_percent
[682c432]2316
[7e98655]2317        if not check_float(get_pin_percent):
2318            get_pin_percent.SetBackgroundColour("pink")
[682c432]2319            msg = "Model Error:wrong value entered!!!"
2320        else:
2321            if len_data < 2:
2322                len_data = 2
[d85f1d8a]2323            self.dx_percent = float(get_pin_percent.GetValue())
[7e98655]2324            if self.dx_percent < 0:
2325                get_pin_percent.SetBackgroundColour("pink")
[682c432]2326                msg = "Model Error:This value can not be negative!!!"
[7e98655]2327            elif self.dx_percent is not None:
[d85f1d8a]2328                percent = self.dx_percent/100
[682c432]2329                if self._is_2D():
[d85f1d8a]2330                    data.dqx_data[data.dqx_data == 0] = percent * data.qx_data
2331                    data.dqy_data[data.dqy_data == 0] = percent * data.qy_data
[682c432]2332                else:
[d85f1d8a]2333                    data.dx = percent * data.x
[682c432]2334            self.current_smearer = smear_selection(data, self.model)
2335            # 2D need to set accuracy
2336            if self._is_2D():
[c8e1996]2337                self.current_smearer.set_accuracy(
2338                    accuracy=self.smear2d_accuracy)
[682c432]2339
[c8e1996]2340        if msg is not None:
[682c432]2341            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2342        else:
[7e98655]2343            get_pin_percent.SetBackgroundColour("white")
[c8e1996]2344        # set smearing value whether or not the data contain the smearing info
[682c432]2345
[373d4ee]2346        enable_smearer = not self.disable_smearer.GetValue()
[682c432]2347        self._manager.set_smearer(smearer=self.current_smearer,
[373d4ee]2348                                  fid=self.data.id,
2349                                  qmin=float(self.qmin_x),
2350                                  qmax=float(self.qmax_x),
2351                                  enable_smearer=enable_smearer,
2352                                  uid=self.uid)
[682c432]2353        return msg
2354
2355    def update_pinhole_smear(self):
2356        """
2357        called by kill_focus on pinhole TextCntrl
2358        to update the changes
2359
2360        :return: False when wrong value was entered
2361
2362        """
2363        # msg default
2364        msg = None
2365        # check if any value is changed
2366        if self._is_changed_pinhole():
2367            msg = self._set_pinhole_smear()
2368        wx.CallAfter(self.save_current_state)
2369
[c8e1996]2370        if msg is not None:
[682c432]2371            return False
2372        else:
2373            return True
2374
2375    def onSlitSmear(self, event):
2376        """
2377        Create a custom slit smear object that will change the way residuals
2378        are compute when fitting
2379        """
2380        # Need update param values
2381        self._update_paramv_on_fit()
2382
2383        # msg default
2384        msg = None
2385        # for event given
[c8e1996]2386        if event is not None:
[682c432]2387            tcrtl = event.GetEventObject()
2388            # event case of radio button
2389            if tcrtl.GetValue():
2390                self.dxl = 0.0
2391                self.dxw = 0.0
2392                is_new_slit = True
2393            else:
2394                is_new_slit = self._is_changed_slit()
2395        else:
2396            is_new_slit = True
2397
2398        # if any value is changed
2399        if is_new_slit:
2400            msg = self._set_slit_smear()
2401
2402        # hide all silt sizer
2403        self._hide_all_smear_info()
2404        # show relevant slit sizers
2405        self._show_smear_sizer()
2406        self.sizer_set_smearer.Layout()
[c8e1996]2407        # we need FitInside here not just self.Layout to ensure all the sizers
2408        # end up with the necessasary space to in the scroll panel. In
2409        # particular the compute and fit buttons end up on top of each other
2410        # PDB Nov 28 2015.
[1c2bf90]2411        self.FitInside()
[682c432]2412
[c8e1996]2413        if event is not None:
[682c432]2414            event.Skip()
2415        self.save_current_state()
2416        event = PageInfoEvent(page=self)
2417        wx.PostEvent(self.parent, event)
[c8e1996]2418        if msg is not None:
[682c432]2419            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2420
2421    def _is_changed_slit(self):
2422        """
2423        check if any of slit lengths is changed
2424
2425        :return: True or False
2426
2427        """
2428        # get the values
2429        width = self.smear_slit_width.GetValue()
2430        height = self.smear_slit_height.GetValue()
2431
2432        # check and change the box bg color if it was pink
2433        #    but it should be white now
2434        # because this is the case that _set_slit_smear() will not handle
2435        if height.lstrip().rstrip() == "":
2436            self.smear_slit_height.SetBackgroundColour(wx.WHITE)
2437        if width.lstrip().rstrip() == "":
2438            self.smear_slit_width.SetBackgroundColour(wx.WHITE)
2439
2440        # Check changes in slit width
2441        if width == "":
2442            dxw = 0.0
2443        else:
2444            try:
2445                dxw = float(width)
2446            except:
2447                return True
2448        if self.dxw != dxw:
2449            return True
2450
2451        # Check changes in slit heigth
2452        if height == "":
2453            dxl = 0.0
2454        else:
2455            try:
2456                dxl = float(height)
2457            except:
2458                return True
2459        if self.dxl != dxl:
2460            return True
2461
2462        return False
2463
2464    def _set_slit_smear(self):
2465        """
2466        Set custom slit smear
2467
2468        :return: message to inform the user about the validity
2469            of the values entered for slit smear
2470        """
[373d4ee]2471        if self.data.__class__.__name__ == "Data2D" or self.enable2D:
[682c432]2472            return
2473        # make sure once more if it is smearer
2474        data = copy.deepcopy(self.data)
2475        data_len = len(data.x)
2476        data.dx = None
2477        data.dxl = None
2478        data.dxw = None
2479        msg = None
2480
2481        try:
2482            self.dxl = float(self.smear_slit_height.GetValue())
[9a5097c]2483            data.dxl = self.dxl * np.ones(data_len)
[682c432]2484            self.smear_slit_height.SetBackgroundColour(wx.WHITE)
2485        except:
2486            self.dxl = None
[9a5097c]2487            data.dxl = np.zeros(data_len)
[682c432]2488            if self.smear_slit_height.GetValue().lstrip().rstrip() != "":
2489                self.smear_slit_height.SetBackgroundColour("pink")
2490                msg = "Wrong value entered... "
2491            else:
2492                self.smear_slit_height.SetBackgroundColour(wx.WHITE)
2493        try:
2494            self.dxw = float(self.smear_slit_width.GetValue())
2495            self.smear_slit_width.SetBackgroundColour(wx.WHITE)
[9a5097c]2496            data.dxw = self.dxw * np.ones(data_len)
[682c432]2497        except:
2498            self.dxw = None
[9a5097c]2499            data.dxw = np.zeros(data_len)
[682c432]2500            if self.smear_slit_width.GetValue().lstrip().rstrip() != "":
2501                self.smear_slit_width.SetBackgroundColour("pink")
2502                msg = "Wrong Fit value entered... "
2503            else:
2504                self.smear_slit_width.SetBackgroundColour(wx.WHITE)
2505
2506        self.current_smearer = smear_selection(data, self.model)
[c8e1996]2507        # set smearing value whether or not the data contain the smearing info
[373d4ee]2508        enable_smearer = not self.disable_smearer.GetValue()
[682c432]2509        self._manager.set_smearer(smearer=self.current_smearer,
[373d4ee]2510                                  fid=self.data.id,
2511                                  qmin=float(self.qmin_x),
2512                                  qmax=float(self.qmax_x),
2513                                  enable_smearer=enable_smearer,
2514                                  uid=self.uid)
[682c432]2515        return msg
2516
2517    def update_slit_smear(self):
2518        """
2519        called by kill_focus on pinhole TextCntrl
2520        to update the changes
2521
2522        :return: False when wrong value was entered
2523
2524        """
2525        # msg default
2526        msg = None
2527        # check if any value is changed
2528        if self._is_changed_slit():
2529            msg = self._set_slit_smear()
[c8e1996]2530        # self._undo.Enable(True)
[682c432]2531        self.save_current_state()
2532
[c8e1996]2533        if msg is not None:
[682c432]2534            return False
2535        else:
2536            return True
2537
2538    def onSmear(self, event):
2539        """
2540        Create a smear object that will change the way residuals
[1c2bf90]2541        are computed when fitting
[682c432]2542        """
[c8e1996]2543        if event is not None:
[682c432]2544            event.Skip()
2545        if self.data is None:
2546            return
2547
2548        # Need update param values
2549        self._update_paramv_on_fit()
[373d4ee]2550        if self.model is not None:
[682c432]2551            if self.data.is_data:
2552                self._manager.page_finder[self.uid].add_data(data=self.data)
2553        temp_smearer = self.on_smear_helper()
2554
2555        self.sizer_set_smearer.Layout()
[c8e1996]2556        # we need FitInside here not just self.Layout to ensure all the sizers
2557        # end up with the necessasary space to in the scroll panel. In
2558        # particular the compute and fit buttons end up on top of each other
2559        # PDB Nov 28 2015.
[1c2bf90]2560        self.FitInside()
[682c432]2561        self._set_weight()
2562
[c8e1996]2563        # set smearing value whether or not the data contain the smearing info
[373d4ee]2564        enable_smearer = not self.disable_smearer.GetValue()
[682c432]2565        wx.CallAfter(self._manager.set_smearer, uid=self.uid,
2566                     smearer=temp_smearer,
2567                     fid=self.data.id,
2568                     qmin=float(self.qmin_x),
2569                     qmax=float(self.qmax_x),
[934ce649]2570                     enable_smearer=enable_smearer)
[682c432]2571
2572        self.state.enable_smearer = self.enable_smearer.GetValue()
2573        self.state.disable_smearer = self.disable_smearer.GetValue()
2574        self.state.pinhole_smearer = self.pinhole_smearer.GetValue()
2575        self.state.slit_smearer = self.slit_smearer.GetValue()
2576
2577    def on_smear_helper(self, update=False):
2578        """
2579        Help for onSmear
2580
2581        :param update: force or not to update
2582        """
2583        self._get_smear_info()
[c8e1996]2584        # renew smear sizer
[373d4ee]2585        if self.smear_type is not None:
[682c432]2586            self.smear_description_smear_type.SetValue(str(self.smear_type))
2587            self.smear_data_left.SetValue(str(self.dq_l))
2588            self.smear_data_right.SetValue(str(self.dq_r))
2589
2590        self._hide_all_smear_info()
2591        data = copy.deepcopy(self.data)
2592
2593        # make sure once more if it is smearer
2594        temp_smearer = smear_selection(data, self.model)
2595        if self.current_smearer != temp_smearer or update:
2596            self.current_smearer = temp_smearer
2597        if self.enable_smearer.GetValue():
[373d4ee]2598            if self.current_smearer is None:
[682c432]2599                wx.PostEvent(self._manager.parent,
2600                    StatusEvent(status="Data contains no smearing information"))
2601            else:
2602                wx.PostEvent(self._manager.parent,
2603                    StatusEvent(status="Data contains smearing information"))
2604
2605            self.smear_data_left.Show(True)
2606            self.smear_data_right.Show(True)
2607            temp_smearer = self.current_smearer
2608        elif self.disable_smearer.GetValue():
2609            self.smear_description_none.Show(True)
2610        elif self.pinhole_smearer.GetValue():
2611            self.onPinholeSmear(None)
2612        elif self.slit_smearer.GetValue():
2613            self.onSlitSmear(None)
2614        self._show_smear_sizer()
2615
2616        return temp_smearer
2617
2618    def on_complete_chisqr(self, event):
2619        """
2620        Display result chisqr on the panel
2621        :event: activated by fitting/ complete after draw
2622        """
2623        try:
[c8e1996]2624            if event is None:
[682c432]2625                output = "-"
[9a5097c]2626            elif not np.isfinite(event.output):
[682c432]2627                output = "-"
2628            else:
2629                output = event.output
2630            self.tcChi.SetValue(str(format_number(output, True)))
2631            self.state.tcChi = self.tcChi.GetValue()
2632        except:
2633            pass
2634
2635    def get_all_checked_params(self):
2636        """
2637        Found all parameters current check and add them to list of parameters
2638        to fit
2639        """
2640        self.param_toFit = []
2641        for item in self.parameters:
2642            if item[0].GetValue() and item not in self.param_toFit:
2643                if item[0].IsShown():
2644                    self.param_toFit.append(item)
2645        for item in self.fittable_param:
2646            if item[0].GetValue() and item not in self.param_toFit:
2647                if item[0].IsShown():
2648                    self.param_toFit.append(item)
2649        self.save_current_state_fit()
2650
2651        event = PageInfoEvent(page=self)
2652        wx.PostEvent(self.parent, event)
2653        param2fit = []
2654        for item in self.param_toFit:
2655            if item[0] and item[0].IsShown():
2656                param2fit.append(item[1])
2657        self._manager.set_param2fit(self.uid, param2fit)
2658
[c8e1996]2659    def select_param(self, event=None):
[682c432]2660        """
2661        Select TextCtrl  checked for fitting purpose and stores them
2662        in  self.param_toFit=[] list
2663        """
2664        self.param_toFit = []
2665        for item in self.parameters:
[c8e1996]2666            # Skip t ifhe angle parameters if 1D data
[682c432]2667            if self.data.__class__.__name__ != "Data2D" and\
2668                        not self.enable2D:
2669                if item in self.orientation_params:
2670                    continue
[c8e1996]2671            # Select parameters to fit for list of primary parameters
[682c432]2672            if item[0].GetValue() and item[0].IsShown():
2673                if not (item in self.param_toFit):
2674                    self.param_toFit.append(item)
2675            else:
2676                #remove parameters from the fitting list
2677                if item in self.param_toFit:
2678                    self.param_toFit.remove(item)
2679
[c8e1996]2680        # Select parameters to fit for list of fittable parameters
[682c432]2681        #        with dispersion
2682        for item in self.fittable_param:
[c8e1996]2683            # Skip t ifhe angle parameters if 1D data
[682c432]2684            if self.data.__class__.__name__ != "Data2D" and\
2685                        not self.enable2D:
2686                if item in self.orientation_params:
2687                    continue
2688            if item[0].GetValue() and item[0].IsShown():
2689                if not (item in self.param_toFit):
2690                    self.param_toFit.append(item)
2691            else:
[c8e1996]2692                # remove parameters from the fitting list
[682c432]2693                if item in self.param_toFit:
2694                    self.param_toFit.remove(item)
2695
[c8e1996]2696        # Calculate num. of angle parameters
[682c432]2697        if self.data.__class__.__name__ == "Data2D" or \
2698                       self.enable2D:
2699            len_orient_para = 0
2700        else:
2701            len_orient_para = len(self.orientation_params)  # assume even len
[c8e1996]2702        # Total num. of angle parameters
[682c432]2703        if len(self.fittable_param) > 0:
2704            len_orient_para *= 2
2705
2706        self.save_current_state_fit()
[c8e1996]2707        if event is not None:
2708            # post state to fit panel
[682c432]2709            event = PageInfoEvent(page=self)
2710            wx.PostEvent(self.parent, event)
2711
2712        param2fit = []
2713        for item in self.param_toFit:
2714            if item[0] and item[0].IsShown():
2715                param2fit.append(item[1])
2716        self._manager.set_param2fit(self.uid, param2fit)
2717
2718    def set_model_param_sizer(self, model):
2719        """
2720        Build the panel from the model content
2721
2722        :param model: the model selected in combo box for fitting purpose
2723
2724        """
2725        self.sizer3.Clear(True)
2726        self.parameters = []
2727        self.str_parameters = []
2728        self.param_toFit = []
2729        self.fittable_param = []
2730        self.fixed_param = []
2731        self.orientation_params = []
2732        self.orientation_params_disp = []
2733
[c8e1996]2734        if model is None:
[682c432]2735            self.sizer3.Layout()
2736            self.SetupScrolling()
2737            return
2738
[6f16e25]2739        box_description = wx.StaticBox(self, wx.ID_ANY, str("Model Parameters"))
[682c432]2740        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
2741        sizer = wx.GridBagSizer(5, 5)
[c8e1996]2742        # save the current model
[682c432]2743        self.model = model
2744
2745        keys = self.model.getParamList()
2746
[c8e1996]2747        # list of dispersion parameters
[682c432]2748        self.disp_list = self.model.getDispParamList()
2749
2750        def custom_compare(a, b):
2751            """
2752            Custom compare to order, first by alphabets then second by number.
2753            """
2754            # number at the last digit
2755            a_last = a[len(a) - 1]
2756            b_last = b[len(b) - 1]
2757            # default
2758            num_a = None
2759            num_b = None
2760            # split the names
2761            a2 = a.lower().split('_')
2762            b2 = b.lower().split('_')
2763            # check length of a2, b2
2764            len_a2 = len(a2)
2765            len_b2 = len(b2)
2766            # check if it contains a int number(<10)
2767            try:
2768                num_a = int(a_last)
2769            except:
2770                pass
2771            try:
2772                num_b = int(b_last)
2773            except:
2774                pass
2775            # Put 'scale' near the top; happens
2776            # when numbered param name exists
2777            if a == 'scale':
2778                return -1
2779            # both have a number
[c8e1996]2780            if num_a is not None and num_b is not None:
[682c432]2781                if num_a > num_b:
2782                    return -1
2783                # same number
2784                elif num_a == num_b:
2785                    # different last names
2786                    if a2[len_a2 - 1] != b2[len_b2 - 1] and num_a != 0:
2787                        return -cmp(a2[len_a2 - 1], b2[len_b2 - 1])
2788                    else:
2789                        return cmp(a, b)
2790                else:
2791                    return 1
2792            # one of them has a number
[c8e1996]2793            elif num_a is not None:
[682c432]2794                return 1
[c8e1996]2795            elif num_b is not None:
[682c432]2796                return -1
2797            # no numbers
2798            else:
2799                return cmp(a.lower(), b.lower())
[50fcb09]2800
[c8e1996]2801        # keys obtained now from ordered dict, so commenting alphabetical
2802        # ordering keys.sort(custom_compare)
[682c432]2803
2804        iy = 0
2805        ix = 0
[4c3be25]2806        sizer.Add(wx.StaticText(self, wx.ID_ANY, 'Parameter'),
2807                  (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[682c432]2808        ix += 1
[6f16e25]2809        self.text2_2 = wx.StaticText(self, wx.ID_ANY, 'Value')
[c8e1996]2810        sizer.Add(self.text2_2, (iy, ix), (1, 1),
[682c432]2811                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2812        ix += 2
[6f16e25]2813        self.text2_3 = wx.StaticText(self, wx.ID_ANY, 'Error')
[c8e1996]2814        sizer.Add(self.text2_3, (iy, ix), (1, 1),
2815                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[682c432]2816        if not self.is_mac:
2817            self.text2_3.Hide()
2818        ix += 1
[6f16e25]2819        self.text2_min = wx.StaticText(self, wx.ID_ANY, 'Min')
[c8e1996]2820        sizer.Add(self.text2_min, (iy, ix), (1, 1),
2821                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2822        # self.text2_min.Hide()
[682c432]2823        ix += 1
[6f16e25]2824        self.text2_max = wx.StaticText(self, wx.ID_ANY, 'Max')
[c8e1996]2825        sizer.Add(self.text2_max, (iy, ix), (1, 1),
2826                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2827        # self.text2_max.Hide()
[682c432]2828        ix += 1
[6f16e25]2829        self.text2_4 = wx.StaticText(self, wx.ID_ANY, '[Units]')
[c8e1996]2830        sizer.Add(self.text2_4, (iy, ix), (1, 1),
2831                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[682c432]2832        self.text2_4.Hide()
2833
[4c3be25]2834        CHECK_STATE = False
[682c432]2835        for item in keys:
2836
[c8e1996]2837            if item not in self.disp_list and not item in \
[682c432]2838                    self.model.orientation_params:
2839
[c8e1996]2840                # prepare a spot to store errors
2841                if item not in self.model.details:
[682c432]2842                    self.model.details[item] = ["", None, None]
2843
2844                iy += 1
2845                ix = 0
[c8e1996]2846                if (self.model.__class__ in
2847                    self.model_list_box["Multi-Functions"] or
[682c432]2848                    self.temp_multi_functional)\
2849                    and (item in self.model.non_fittable):
[6f16e25]2850                    non_fittable_name = wx.StaticText(self, wx.ID_ANY, item)
[c8e1996]2851                    sizer.Add(non_fittable_name, (iy, ix), (1, 1),
[682c432]2852                            wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 21)
[c8e1996]2853                    # add parameter value
[682c432]2854                    ix += 1
2855                    value = self.model.getParam(item)
2856                    if len(self.model.fun_list) > 0:
[c8e1996]2857                        # num = item.split('_')[1][5:7]
[6f16e25]2858                        fun_box = wx.ComboBox(self, wx.ID_ANY, size=(100, -1),
[682c432]2859                                    style=wx.CB_READONLY, name='%s' % item)
2860                        self._set_fun_box_list(fun_box)
2861                        fun_box.SetSelection(0)
[c8e1996]2862                        # self.fun_box.SetToolTipString("A function
[682c432]2863                        #    describing the interface")
[6f16e25]2864                        wx.EVT_COMBOBOX(fun_box, wx.ID_ANY, self._on_fun_box)
[682c432]2865                    else:
[6f16e25]2866                        fun_box = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]2867                                                size=(_BOX_WIDTH, 20),
[682c432]2868                                style=wx.TE_PROCESS_ENTER, name='%s' % item)
[c8e1996]2869                        fun_box.SetToolTipString(
[682c432]2870                                "Hit 'Enter' after typing to update the plot.")
2871                        fun_box.SetValue(format_number(value, True))
2872                    sizer.Add(fun_box, (iy, ix), (1, 1), wx.EXPAND)
2873                    self.str_parameters.append([None, item, fun_box,
2874                                                None, None, None,
2875                                                None, None])
2876                else:
[c8e1996]2877                    # add parameters name with checkbox for selecting to fit
[6f16e25]2878                    cb = wx.CheckBox(self, wx.ID_ANY, item)
[682c432]2879                    cb.SetValue(CHECK_STATE)
2880                    cb.SetToolTipString(" Check mark to fit.")
[c8e1996]2881                    # cb.SetValue(True)
[682c432]2882                    wx.EVT_CHECKBOX(self, cb.GetId(), self.select_param)
2883
2884                    sizer.Add(cb, (iy, ix), (1, 1),
2885                              wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
2886
[c8e1996]2887                    # add parameter value
[682c432]2888                    ix += 1
2889                    value = self.model.getParam(item)
[6f16e25]2890                    ctl1 = ModelTextCtrl(self, wx.ID_ANY, size=(_BOX_WIDTH, 20),
[373d4ee]2891                                         style=wx.TE_PROCESS_ENTER)
[c8e1996]2892                    ctl1.SetToolTipString(
[682c432]2893                                "Hit 'Enter' after typing to update the plot.")
2894                    ctl1.SetValue(format_number(value, True))
2895                    sizer.Add(ctl1, (iy, ix), (1, 1), wx.EXPAND)
[c8e1996]2896                    # text to show error sign
[682c432]2897                    ix += 1
[6f16e25]2898                    text2 = wx.StaticText(self, wx.ID_ANY, '+/-')
[c8e1996]2899                    sizer.Add(text2, (iy, ix), (1, 1),
[682c432]2900                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2901                    if not self.is_mac:
2902                        text2.Hide()
2903                    ix += 1
[6f16e25]2904                    ctl2 = wx.TextCtrl(self, wx.ID_ANY,
[682c432]2905                                       size=(_BOX_WIDTH / 1.2, 20), style=0)
2906                    sizer.Add(ctl2, (iy, ix), (1, 1),
2907                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2908                    if not self.is_mac:
2909                        ctl2.Hide()
2910
2911                    ix += 1
[6f16e25]2912                    ctl3 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]2913                                         size=(_BOX_WIDTH / 1.9, 20),
2914                                         style=wx.TE_PROCESS_ENTER,
[682c432]2915                                text_enter_callback=self._onparamRangeEnter)
2916                    min_bound = self.model.details[item][1]
2917                    if min_bound is not None:
2918                        ctl3.SetValue(format_number(min_bound, True))
2919
2920                    sizer.Add(ctl3, (iy, ix), (1, 1),
2921                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2922
2923                    ix += 1
[6f16e25]2924                    ctl4 = ModelTextCtrl(self, wx.ID_ANY,
[373d4ee]2925                                         size=(_BOX_WIDTH / 1.9, 20),
2926                                         style=wx.TE_PROCESS_ENTER,
[682c432]2927                                text_enter_callback=self._onparamRangeEnter)
2928                    max_bound = self.model.details[item][2]
2929                    if max_bound is not None:
2930                        ctl4.SetValue(format_number(max_bound, True))
2931                    sizer.Add(ctl4, (iy, ix), (1, 1),
2932                              wx.EXPAND | wx.FIXED_MINSIZE, 0)
2933
2934                    ix += 1
2935                    # Units
2936                    if item in self.model.details:
[6f16e25]2937                        units = wx.StaticText(self, wx.ID_ANY,
[682c432]2938                            self.model.details[item][0], style=wx.ALIGN_LEFT)
2939                    else:
[6f16e25]2940                        units = wx.StaticText(self, wx.ID_ANY, "",
[682c432]2941                                              style=wx.ALIGN_LEFT)
2942                    sizer.Add(units, (iy, ix), (1, 1),
2943                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2944
2945                    self.parameters.append([cb, item, ctl1,
2946                                            text2, ctl2, ctl3, ctl4, units])
2947
2948        iy += 1
2949        sizer.Add((10, 10), (iy, ix), (1, 1),
2950                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
2951
2952        # type can be either Guassian or Array
[fa81e94]2953        if len(list(self.model.dispersion.values())) > 0:
2954            type = list(self.model.dispersion.values())[0]["type"]
[682c432]2955        else:
2956            type = "Gaussian"
2957
2958        iy += 1
2959        ix = 0
[c8e1996]2960        # Add tile for orientational angle
[682c432]2961        for item in keys:
2962            if item in self.model.orientation_params:
[6f16e25]2963                orient_angle = wx.StaticText(self, wx.ID_ANY, '[For 2D only]:')
2964                mag_on_button = wx.Button(self, wx.ID_ANY, "Magnetic ON")
[7116dffd]2965                mag_on_button.SetToolTipString("Turn Pol Beam/Mag scatt on/off")
[682c432]2966                mag_on_button.Bind(wx.EVT_BUTTON, self._on_mag_on)
[c8e1996]2967                mag_angle_help_button = wx.Button(self, wx.ID_ANY,
2968                                                  "Magnetic angles?")
[7116dffd]2969                mag_angle_help_button.SetToolTipString("see angle definitions")
[6f16e25]2970                mag_help_button = wx.Button(self, wx.ID_ANY, "Mag HELP")
[7116dffd]2971                mag_help_button.SetToolTipString("Help on pol beam/mag fitting")
[682c432]2972                mag_help_button.Bind(wx.EVT_BUTTON, self._on_mag_help)
[c8e1996]2973                mag_angle_help_button.Bind(wx.EVT_BUTTON,
[7116dffd]2974                                            self._on_mag_angle_help)
[682c432]2975                sizer.Add(orient_angle, (iy, ix), (1, 1),
2976                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
2977                iy += 1
2978                sizer.Add(mag_on_button, (iy, ix), (1, 1),
2979                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[7116dffd]2980                ix += 1
2981                sizer.Add(mag_angle_help_button, (iy, ix), (1, 1),
2982                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[682c432]2983                sizer.Add(mag_help_button, (iy, ix + 1), (1, 1),
2984                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
2985
[c8e1996]2986                # handle the magnetic buttons
2987                # clean this up so that assume mag is off then turn
2988                # all buttons on IF mag has mag and has 2D
[682c432]2989                if not self._has_magnetic:
2990                    mag_on_button.Show(False)
2991                elif not self.data.__class__.__name__ == "Data2D":
2992                    mag_on_button.Show(False)
2993                else:
2994                    mag_on_button.Show(True)
2995                mag_help_button.Show(False)
[2b58fa5]2996                mag_angle_help_button.Show(False)
[682c432]2997                if mag_on_button.IsShown():
2998                    if self.magnetic_on:
2999                        mag_on_button.SetLabel("Magnetic OFF")
3000                        mag_help_button.Show(True)
[7116dffd]3001                        mag_angle_help_button.Show(True)
[682c432]3002                    else:
3003                        mag_on_button.SetLabel("Magnetic ON")
3004                        mag_help_button.Show(False)
[7116dffd]3005                        mag_angle_help_button.Show(False)
[682c432]3006
3007                if not self.data.__class__.__name__ == "Data2D" and \
3008                        not self.enable2D:
3009                    orient_angle.Hide()
3010                else:
3011                    orient_angle.Show(True)
3012                break
3013
[c8e1996]3014        # For Gaussian only
[682c432]3015        if type.lower() != "array":
3016            for item in self.model.orientation_params:
3017                if not self.magnetic_on:
3018                    if item in self.model.magnetic_params:
3019                        continue
[c8e1996]3020                if item not in self.disp_list:
3021                    # prepare a spot to store min max
3022                    if item not in self.model.details:
[682c432]3023                        self.model.details[item] = ["", None, None]
3024
3025                    iy += 1
3026                    ix = 0
[c8e1996]3027                    # add parameters name with checkbox for selecting to fit
[6f16e25]3028                    cb = wx.CheckBox(self, wx.ID_ANY, item)
[682c432]3029                    cb.SetValue(CHECK_STATE)
3030                    cb.SetToolTipString("Check mark to fit")
3031                    wx.EVT_CHECKBOX(self, cb.GetId(), self.select_param)
3032                    if self.data.__class__.__name__ == "Data2D" or \
3033                            self.enable2D:
3034                        cb.Show(True)
3035                    else:
3036                        cb.Hide()
3037                    sizer.Add(cb, (iy, ix), (1, 1),
3038                              wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
3039
[c8e1996]3040                    # add parameter value
[682c432]3041                    ix += 1
3042                    value = self.model.getParam(item)
[373d4ee]3043                    ctl1 = ModelTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
3044                                         style=wx.TE_PROCESS_ENTER)
[c8e1996]3045                    ctl1.SetToolTipString(
[682c432]3046                                "Hit 'Enter' after typing to update the plot.")
3047                    ctl1.SetValue(format_number(value, True))
3048                    if self.data.__class__.__name__ == "Data2D" or \
3049                            self.enable2D:
3050                        ctl1.Show(True)
3051                    else:
3052                        ctl1.Hide()
3053                    sizer.Add(ctl1, (iy, ix), (1, 1), wx.EXPAND)
[c8e1996]3054                    # text to show error sign
[682c432]3055                    ix += 1
3056                    text2 = wx.StaticText(self, -1, '+/-')
[c8e1996]3057                    sizer.Add(text2, (iy, ix), (1, 1),
[682c432]3058                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3059
3060                    text2.Hide()
3061                    ix += 1
3062                    ctl2 = wx.TextCtrl(self, -1,
3063                                       size=(_BOX_WIDTH / 1.2, 20), style=0)
3064                    sizer.Add(ctl2, (iy, ix), (1, 1),
3065                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3066
3067                    ctl2.Hide()
3068
3069                    ix += 1
[373d4ee]3070                    ctl3 = ModelTextCtrl(self, -1,
3071                                         size=(_BOX_WIDTH / 1.8, 20),
3072                                         style=wx.TE_PROCESS_ENTER,
[682c432]3073                                text_enter_callback=self._onparamRangeEnter)
3074
3075                    sizer.Add(ctl3, (iy, ix), (1, 1),
3076                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3077                    ctl3.Hide()
3078
3079                    ix += 1
[373d4ee]3080                    ctl4 = ModelTextCtrl(self, -1,
3081                                         size=(_BOX_WIDTH / 1.8, 20),
3082                                         style=wx.TE_PROCESS_ENTER,
[682c432]3083                            text_enter_callback=self._onparamRangeEnter)
3084                    sizer.Add(ctl4, (iy, ix), (1, 1),
3085                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3086
3087                    ctl4.Hide()
3088
3089                    if self.data.__class__.__name__ == "Data2D" or \
3090                            self.enable2D:
3091                        if self.is_mac:
3092                            text2.Show(True)
3093                            ctl2.Show(True)
3094                        ctl3.Show(True)
3095                        ctl4.Show(True)
3096
3097                    ix += 1
3098                    # Units
3099                    if item in self.model.details:
3100                        units = wx.StaticText(self, -1,
3101                                              self.model.details[item][0],
3102                                              style=wx.ALIGN_LEFT)
3103                    else:
3104                        units = wx.StaticText(self, -1, "",
3105                                              style=wx.ALIGN_LEFT)
3106                    if self.data.__class__.__name__ == "Data2D" or \
3107                            self.enable2D:
3108                        units.Show(True)
3109                    else:
3110                        units.Hide()
3111
3112                    sizer.Add(units, (iy, ix), (1, 1),
3113                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3114
3115                    self.parameters.append([cb, item, ctl1,
3116                                            text2, ctl2, ctl3, ctl4, units])
3117                    self.orientation_params.append([cb, item, ctl1,
3118                                            text2, ctl2, ctl3, ctl4, units])
3119
3120        iy += 1
3121        box_description.SetForegroundColour(wx.BLUE)
[c8e1996]3122        # Display units text on panel
[682c432]3123        for item in keys:
3124            if item in self.model.details:
3125                self.text2_4.Show()
[c8e1996]3126        # Fill the list of fittable parameters
[682c432]3127        self.get_all_checked_params()
3128        self.save_current_state_fit()
3129        boxsizer1.Add(sizer)
3130        self.sizer3.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
3131        self.sizer3.Layout()
3132        self.Layout()
3133
3134    def on_right_down(self, event):
3135        """
3136        Get key stroke event
3137        """
[c8e1996]3138        if self.data is None:
[682c432]3139            return
3140        # Figuring out key combo: Cmd for copy, Alt for paste
3141        if event.AltDown() and event.ShiftDown():
3142            flag = True
3143        elif event.AltDown() or event.ShiftDown():
3144            flag = False
3145        else:
3146            return
3147        # make event free
3148        event.Skip()
3149        # messages depending on the flag
3150        if not flag:
3151            infor = 'warning'
3152            # inform msg to wx
3153            wx.PostEvent(self._manager.parent,
3154                        StatusEvent(status=msg, info=infor))
3155
3156    def _onModel2D(self, event):
3157        """
3158        toggle view of model from 1D to 2D  or 2D from 1D
3159        """
3160        if self.model_view.GetLabelText() == "Show 2D":
3161            self.model_view.SetLabel("Show 1D")
3162            self.enable2D = True
3163
3164        else:
3165            self.model_view.SetLabel("Show 2D")
3166            self.enable2D = False
3167        self.Show(False)
3168        self.create_default_data()
3169        self._manager.store_data(self.uid, data_list=[self.data])
3170
3171        self.set_model_param_sizer(self.model)
3172        self._set_sizer_dispersion()
3173        self._set_weight(is_2D=self.enable2D)
3174        self._set_smear_buttons()
3175        self.Show(True)
3176        self.SetupScrolling()
3177        self._draw_model()
3178
3179        self.state.enable2D = copy.deepcopy(self.enable2D)
3180
3181    def _set_smear_buttons(self):
3182        """
3183        Set semarer radio buttons
3184        """
3185        # more disables for 2D
3186        if self.data.__class__.__name__ == "Data2D" or \
3187                    self.enable2D:
3188            self.slit_smearer.Disable()
3189            self.pinhole_smearer.Enable(True)
3190            self.default_mask = copy.deepcopy(self.data.mask)
3191        else:
3192            self.slit_smearer.Enable(True)
3193            self.pinhole_smearer.Enable(True)
3194
3195
3196class BGTextCtrl(wx.TextCtrl):
3197    """
3198    Text control used to display outputs.
3199    No editing allowed. The background is
3200    grayed out. User can't select text.
3201    """
3202    def __init__(self, *args, **kwds):
3203        wx.TextCtrl.__init__(self, *args, **kwds)
3204        self.SetEditable(False)
3205        self.SetBackgroundColour(self.GetParent().parent.GetBackgroundColour())
3206
3207        # Bind to mouse event to avoid text highlighting
3208        # The event will be skipped once the call-back
3209        # is called.
3210        self.Bind(wx.EVT_MOUSE_EVENTS, self._click)
3211
3212    def _click(self, event):
3213        """
3214        Prevent further handling of the mouse event
3215        by not calling Skip().
3216        """
3217        pass
Note: See TracBrowser for help on using the repository browser.