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

Last change on this file since b2964ef was 82d88d5, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

Merge branch 'master' into py37-sasgui

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