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

Last change on this file since c080c943 was c080c943, checked in by GitHub <noreply@…>, 5 years ago

Merge e66f9c1f0e64eb3f2bd4e989067361257af0d83a into fa307ddb8d25a41b44c02bd525f9727d5b4b7e5b

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