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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249
Last change on this file since ca4f40d was ca4f40d, checked in by butler, 9 months ago

replace checking for class name with isinstance throughout fitpage.py

  • Property mode set to 100644
File size: 129.5 KB
Line 
1"""
2    FitPanel class contains fields allowing to display results when
3    fitting  a model and one data
4"""
5from __future__ import print_function
6
7import sys
8import wx
9import wx.lib.newevent
10import numpy as np
11import copy
12import math
13import time
14import traceback
15import logging
16
17from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
18
19from sas.sascalc.fit.qsmearing import smear_selection
20from sas.sascalc.dataloader.data_info import Data1D, Data2D
21
22from sas.sasgui.guiframe.events import StatusEvent, NewPlotEvent, \
23    PlotQrangeEvent
24from sas.sasgui.guiframe.dataFitting import check_data_validity
25from sas.sasgui.guiframe.utils import format_number, check_float
26from sas.sasgui.guiframe.documentation_window import DocumentationWindow
27
28from sas.sasgui.perspectives.fitting.basepage import BasicPage as BasicPage
29from sas.sasgui.perspectives.fitting.basepage import PageInfoEvent as \
30    PageInfoEvent
31from .basepage import ModelTextCtrl
32
33logger = logging.getLogger(__name__)
34
35(Chi2UpdateEvent, EVT_CHI2_UPDATE) = wx.lib.newevent.NewEvent()
36_BOX_WIDTH = 76
37_DATA_BOX_WIDTH = 300
38SMEAR_SIZE_H = 0.00
39CUSTOM_MODEL = 'Plugin Models'
40
41class FitPage(BasicPage):
42    """
43    FitPanel class contains fields allowing to display results when
44    fitting  a model and one data
45
46    :note: For Fit to be performed the user should check at least one parameter
47        on fit Panel window.
48    """
49
50    def __init__(self, parent, color=None):
51        """
52        Initialization of the Panel
53        """
54        BasicPage.__init__(self, parent, color=color)
55
56        # draw sizer
57        self._fill_data_sizer()
58        self.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.iteritems():
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.iteritems():
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:
1381                tcrtl.SetBackgroundColour("pink")
1382                msg = "Model Error:wrong value entered : %s" % sys.exc_value
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                    msg = "Model Error:wrong value entered : %s" % sys.exc_value
1495                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1496                    return
1497            except:
1498                tcrtl.SetBackgroundColour("pink")
1499                msg = "Model Error:wrong value entered : %s" % sys.exc_value
1500                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1501                return
1502            # Check if # of points for theory model are valid(>0).
1503            # check for 2d
1504            if isinstance(self.data, Data2D) or self.enable2D:
1505                # set mask
1506                radius = np.sqrt(self.data.qx_data * self.data.qx_data +
1507                                    self.data.qy_data * self.data.qy_data)
1508                index_data = ((self.qmin_x <= radius) & (radius <= self.qmax_x))
1509                index_data = (index_data) & (self.data.mask)
1510                index_data = (index_data) & (np.isfinite(self.data.data))
1511                if len(index_data[index_data]) < 10:
1512                    msg = "Cannot Plot :No or too little npts in"
1513                    msg += " that data range!!!  "
1514                    wx.PostEvent(self._manager.parent,
1515                                 StatusEvent(status=msg))
1516                    return
1517                else:
1518                    # self.data.mask = index_data
1519                    # self.Npts_fit.SetValue(str(len(self.data.mask)))
1520                    self.show_npts2fit()
1521            else:
1522                index_data = ((self.qmin_x <= self.data.x) &
1523                              (self.data.x <= self.qmax_x))
1524                self.Npts_fit.SetValue(str(len(self.data.x[index_data])))
1525
1526            self.npts_x = self.Npts_total.GetValue()
1527            self.create_default_data()
1528            self._save_plotting_range()
1529        else:
1530            tcrtl.SetBackgroundColour("pink")
1531            msg = "Model Error:wrong value entered!!!"
1532            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1533
1534        self._draw_model()
1535        self.save_current_state()
1536        event = PageInfoEvent(page=self)
1537        wx.PostEvent(self.parent, event)
1538        self.state_change = False
1539        return
1540
1541    def _clear_Err_on_Fit(self):
1542        """
1543        hide the error text control shown
1544        after fitting
1545        """
1546
1547        if self.is_mac:
1548            return
1549        if hasattr(self, "text2_3"):
1550            self.text2_3.Hide()
1551
1552        if len(self.parameters) > 0:
1553            for item in self.parameters:
1554                if item[0].IsShown():
1555                    # Skip the angle parameters if 1D data
1556                    if not isinstance(self.data, Data2D) and not self.enable2D:
1557                        if item in self.orientation_params:
1558                            continue
1559                    if item in self.param_toFit:
1560                        continue
1561                    # hide statictext +/-
1562                    if len(item) < 4:
1563                        continue
1564                    if item[3] is not None and item[3].IsShown():
1565                        item[3].Hide()
1566                    # hide textcrtl  for error after fit
1567                    if item[4] is not None and item[4].IsShown():
1568                        item[4].Hide()
1569
1570        if len(self.fittable_param) > 0:
1571            for item in self.fittable_param:
1572                if item[0].IsShown():
1573                    # Skip the angle parameters if 1D data
1574                    if not isinstance(self.data, Data2D) and not self.enable2D:
1575                        if item in self.orientation_params:
1576                            continue
1577                    if item in self.param_toFit:
1578                        continue
1579                    if len(item) < 4:
1580                        continue
1581                    # hide statictext +/-
1582                    if item[3] is not None and item[3].IsShown():
1583                        item[3].Hide()
1584                    # hide textcrtl  for error after fit
1585                    if item[4] is not None and item[4].IsShown():
1586                        item[4].Hide()
1587        return
1588
1589    def _get_defult_custom_smear(self):
1590        """
1591        Get the defult values for custum smearing.
1592        """
1593        # get the default values
1594        if self.dxl is None:
1595            self.dxl = 0.0
1596        if self.dxw is None:
1597            self.dxw = ""
1598        if self.dx_percent is None:
1599            self.dx_percent = SMEAR_SIZE_H
1600
1601    def _get_smear_info(self):
1602        """
1603        Get the smear info from data.
1604
1605        :return: self.smear_type, self.dq_l and self.dq_r,
1606            respectively the type of the smear, The average <dq/q> radial(p)
1607            and <dq/q> theta (s)s for 2D pinhole resolution in % (slit is not
1608            currently supported in 2D), (dq/q)_min and (dq/q)_max for 1D pinhole
1609            smeared data, again in %, and dxl and/or dxw for slit smeared data
1610            given in 1/A and assumed constant.
1611        """
1612        # set up defaults
1613        self.smear_type = None
1614        self.dq_l = None
1615        self.dq_r = None
1616        data = self.data
1617        #sanity check - this should only be called if data exits
1618        if self.data is None:
1619            return
1620        #First check if data is 2D
1621        #If so check that data set has smearing info and that none are zero.
1622        #Otherwise no smearing can be applied using smear from data (a Gaussian
1623        #width of zero will cause a divide by zero error)
1624        if isinstance(self.data, Data2D):
1625            if data.dqx_data is not None and data.dqy_data is not None \
1626              and data.dqx_data.all()and data.dqy_data.all():
1627                self.smear_type = "Pinhole2d"
1628                #report as average dQ/Q % as for 1D pinhole
1629                self.dq_l = format_number(np.average(data.dqx_data
1630                                                     / abs(data.qx_data)) * 100)
1631                self.dq_r = format_number(np.average(data.dqy_data
1632                                                     / abs(data.qy_data)) * 100)
1633            #if not then log that data did not contain resolutin info                                         / abs(data.qy_data)) * 100)
1634            else:
1635                self.msg = "2D Data did not contain recognizable " \
1636                           "resolution info."
1637                logger.info(self.msg)
1638        #If not check that data is 1D
1639        #If so check for pinhole vs slit by veryfing whehter dx or dxl or dxw
1640        #have data (currently sasview only supports either dx or dxl/dxw but
1641        #not both simultaneously) and, as for 2D, are non zero .
1642        #Otherwise no smearing can be applied using smear from data (a Gaussian
1643        #width of zero will cause a divide by zero error)
1644        elif isinstance(self.data, Data1D):
1645            #is it valid 1D pinhole resolution data?
1646            if data.dx is not None and np.all(data.dx):
1647                self.smear_type = "Pinhole"
1648                #report in % for display makes more sense than absolute value
1649                #for pinhole smearing .. but keep old names of dq_l
1650                self.dq_l = format_number(data.dx[0] / data.x[0] * 100,1)
1651                self.dq_r = format_number(data.dx[-1] / data.x[-1] * 100,1)
1652            #If not, is it valid 1D slit resolution data?
1653            elif (data.dxl is not None or data.dxw is not None) \
1654                and (np.all(data.dxl, 0) or np.all(data.dxw, 0)):
1655                self.smear_type = "Slit"
1656                #for slit units of 1/A make most sense
1657                if data.dxl is not None and np.all(data.dxl, 0):
1658                    self.dq_l = format_number(data.dxl[0],1)
1659                if data.dxw is not None and np.all(data.dxw, 0):
1660                    self.dq_r = format_number(data.dxw[0],1)
1661            #otherwise log that the data did not conatain resolution info
1662            else:
1663                self.msg = "1D Data did not contain recognizable " \
1664                           "resolution info."
1665                logger.info(self.msg)
1666        #If drops to here it is neither data1D or data2D so log that
1667        else:
1668            self.msg = "Data was not recognized as either 1D or 2D data."
1669            logger.info(self.msg)
1670        # return self.smear_type,self.dq_l,self.dq_r
1671
1672    def _show_smear_sizer(self):
1673        """
1674        Show only the sizers depending on smear selection
1675        """
1676        # smear disabled = No Smearing used
1677        if self.disable_smearer.GetValue():
1678            self.smear_description_none.Show(True)
1679        # 2Dsmearing - for use with 2D data only
1680        elif self._is_2D():
1681            self.smear_description_accuracy_type.Show(True)
1682            self.smear_accuracy.Show(True)
1683            self.smear_description_2d.Show(True)
1684            #2D custom pinhole smearing
1685            if self.pinhole_smearer.GetValue():
1686                self.smear_description_pin_percent.Show(True)
1687                self.smear_pinhole_percent.Show(True)
1688            #get 2D smearing from data
1689            elif self.enable_smearer.GetValue():
1690                self.smear_description_2d_x.Show(True)
1691                self.smear_description_2d_y.Show(True)
1692                self.smear_data_left.Show(True)
1693                self.smear_data_right.Show(True)
1694            #Currently 2D custom slit smearing is not currently supported
1695            else:
1696                logger.error("2D custom smearing cannot use slit smearing")
1697
1698        # 1D smearing from data
1699        elif self.enable_smearer.GetValue():
1700            self.smear_description_dqdata.Show(True)
1701            if self.smear_type is not None:
1702                self.smear_description_smear_type.Show(True)
1703                #1D data has slit smearing
1704                if self.smear_type == 'Slit':
1705                    self.smear_description_slit_height.Show(True)
1706                    self.smear_description_slit_width.Show(True)
1707                #1D data has pinhole smearing
1708                elif self.smear_type == 'Pinhole':
1709                    self.smear_description_pin_percent_min.Show(True)
1710                    self.smear_description_pin_percent_max.Show(True)
1711                self.smear_description_smear_type.Show(True)
1712                self.smear_description_type.Show(True)
1713                self.smear_data_left.Show(True)
1714                self.smear_data_right.Show(True)
1715        # 1D custom pinhole smearing
1716        elif self.pinhole_smearer.GetValue():
1717            self.smear_message_new_p.Show(True)
1718            self.smear_description_pin_percent.Show(True)
1719            self.smear_pinhole_percent.Show(True)
1720        # 1D custom slit smear
1721        elif self.slit_smearer.GetValue():
1722            self.smear_message_new_s.Show(True)
1723            self.smear_description_slit_height.Show(True)
1724            self.smear_slit_height.Show(True)
1725            self.smear_description_slit_width.Show(True)
1726            self.smear_slit_width.Show(True)
1727        else:
1728            logger.error("smearing type is not defined")
1729
1730    def _hide_all_smear_info(self):
1731        """
1732        Hide all smearing messages in the set_smearer sizer
1733        """
1734        self.smear_description_none.Hide()
1735        self.smear_description_dqdata.Hide()
1736        self.smear_description_type.Hide()
1737        self.smear_description_smear_type.Hide()
1738        self.smear_description_accuracy_type.Hide()
1739        self.smear_description_2d_x.Hide()
1740        self.smear_description_2d_y.Hide()
1741        self.smear_description_2d.Hide()
1742
1743        self.smear_accuracy.Hide()
1744        self.smear_data_left.Hide()
1745        self.smear_data_right.Hide()
1746        self.smear_description_pin_percent_min.Hide()
1747        self.smear_description_pin_percent_max.Hide()
1748        self.smear_description_pin_percent.Hide()
1749        self.smear_pinhole_percent.Hide()
1750        self.smear_description_slit_height.Hide()
1751        self.smear_slit_height.Hide()
1752        self.smear_description_slit_width.Hide()
1753        self.smear_slit_width.Hide()
1754        self.smear_message_new_p.Hide()
1755        self.smear_message_new_s.Hide()
1756
1757    def _set_accuracy_list(self):
1758        """
1759        Set the list of an accuracy in 2D custum smear:
1760                Xhigh, High, Med, or Low
1761        """
1762        # list of accuracy choices
1763        list = ['Low', 'Med', 'High', 'Xhigh']
1764        for idx in range(len(list)):
1765            self.smear_accuracy.Append(list[idx], idx)
1766
1767    def _set_fun_box_list(self, fun_box):
1768        """
1769        Set the list of func for multifunctional models
1770        """
1771        # Check if it is multi_functional model
1772        if self.model.__class__ not in self.model_list_box["Multi-Functions"] \
1773                and not self.temp_multi_functional:
1774            return None
1775        for index, choice in enumerate(self.model.fun_list):
1776            fun_box.Append(choice, index)
1777
1778    def _on_select_accuracy(self, event):
1779        """
1780        Select an accuracy in 2D custom smear: Xhigh, High, Med, or Low
1781        """
1782        # event.Skip()
1783        # Check if the accuracy is same as before
1784        # self.smear2d_accuracy = event.GetEventObject().GetValue()
1785        self.smear2d_accuracy = self.smear_accuracy.GetValue()
1786        if self.pinhole_smearer.GetValue():
1787            self.onPinholeSmear(event=None)
1788        else:
1789            self.onSmear(event=None)
1790            if self.current_smearer is not None:
1791                self.current_smearer.set_accuracy(accuracy=\
1792                                                  self.smear2d_accuracy)
1793        event.Skip()
1794
1795    def _on_fun_box(self, event):
1796        """
1797        Select an func: Erf,Rparabola,LParabola
1798        """
1799        fun_val = None
1800        fun_box = event.GetEventObject()
1801        name = fun_box.Name
1802        value = fun_box.GetValue()
1803        if value in self.model.fun_list:
1804            fun_val = self.model.fun_list.index(value)
1805
1806        self.model.setParam(name, fun_val)
1807        # save state
1808        self._copy_parameters_state(self.str_parameters,
1809                                    self.state.str_parameters)
1810        # update params
1811        self._update_paramv_on_fit()
1812        # draw
1813        self._draw_model()
1814        self.Refresh()
1815        # get ready for new event
1816        event.Skip()
1817
1818    def _onMask(self, event):
1819        """
1820        Build a panel to allow to edit Mask
1821        """
1822        from sas.sasgui.guiframe.local_perspectives.plotting.masking import \
1823            MaskPanel as MaskDialog
1824
1825        self.panel = MaskDialog(base=self, data=self.data, id=wx.NewId())
1826        self.panel.ShowModal()
1827
1828    def _draw_masked_model(self, event):
1829        """
1830        Draw model image w/mask
1831        """
1832        is_valid_qrange = self._update_paramv_on_fit()
1833
1834        if is_valid_qrange and self.model is not None:
1835            self.panel.MakeModal(False)
1836            event.Skip()
1837            # try re draw the model plot if it exists
1838            self._draw_model()
1839            self.show_npts2fit()
1840        elif self.model is None:
1841            self.panel.MakeModal(False)
1842            event.Skip()
1843            self.show_npts2fit()
1844            msg = "No model is found on updating MASK in the model plot... "
1845            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1846        else:
1847            event.Skip()
1848            msg = ' Please consider your Q range, too.'
1849            self.panel.ShowMessage(msg)
1850
1851    def _set_smear(self, data):
1852        """
1853        Set_smear
1854        """
1855        if data is None:
1856            return
1857        self.current_smearer = smear_selection(data, self.model)
1858        flag = self.disable_smearer.GetValue()
1859        if self.current_smearer is None:
1860            self.enable_smearer.Disable()
1861        else:
1862            self.enable_smearer.Enable()
1863        if not flag:
1864            self.onSmear(None)
1865
1866    def get_view_mode(self):
1867        """
1868        return True if the panel allow 2D or False if 1D
1869        """
1870        return self.enable2D
1871
1872    def compute_data_set_range(self, data_list):
1873        """
1874        find the range that include all data  in the set
1875        return the minimum and the maximum values
1876        """
1877        if data_list is not None and data_list != []:
1878            for data in data_list:
1879                qmin, qmax, npts = self.compute_data_range(data)
1880                self.qmin_data_set = min(self.qmin_data_set, qmin)
1881                self.qmax_data_set = max(self.qmax_data_set, qmax)
1882                self.npts_data_set += npts
1883        return self.qmin_data_set, self.qmax_data_set, self.npts_data_set
1884
1885    def compute_data_range(self, data):
1886        """
1887        compute the minimum and the maximum range of the data
1888        return the npts contains in data
1889        :param data:
1890        """
1891        qmin, qmax, npts = None, None, None
1892        if data is not None:
1893            if not hasattr(data, "data"):
1894                try:
1895                    qmin = min(data.x)
1896                    # Maximum value of data
1897                    qmax = max(data.x)
1898                    npts = len(data.x)
1899                except:
1900                    msg = "Unable to find min/max/length of \n data named %s" %\
1901                                data.filename
1902                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg,
1903                                               info="error"))
1904                    raise ValueError, msg
1905
1906            else:
1907                qmin = 0
1908                try:
1909                    x = max(math.fabs(data.xmin), math.fabs(data.xmax))
1910                    y = max(math.fabs(data.ymin), math.fabs(data.ymax))
1911                except:
1912                    msg = "Unable to find min/max of \n data named %s" % \
1913                                data.filename
1914                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg,
1915                                               info="error"))
1916                    raise ValueError, msg
1917                # Maximum value of data
1918                qmax = math.sqrt(x * x + y * y)
1919                npts = len(data.data)
1920        return qmin, qmax, npts
1921
1922    def set_data(self, data):
1923        """
1924        reset the current data
1925        """
1926        id = None
1927        flag = False
1928        is_data = False
1929        npts = 0
1930        try:
1931            old_id = self.data.id
1932            old_group_id = self.data.group_id
1933        except:
1934            old_id = id
1935            old_group_id = id
1936        if self.data is not None:
1937            is_data = check_data_validity(self.data)
1938        if not is_data and data is not None:
1939                flag = True
1940        if data is not None:
1941            if is_data:
1942                self.graph_id = self.data.group_id
1943                flag = (data.id != self.data.id)
1944        self.data = data
1945        if check_data_validity(data):
1946            self.graph_id = data.group_id
1947        self.data.group_id = self.graph_id
1948
1949        if self.data is None:
1950            data_name = ""
1951            self._set_bookmark_flag(False)
1952            self._keep.Enable(False)
1953            self._set_save_flag(False)
1954        else:
1955            if self.model is not None:
1956                self._set_bookmark_flag(not self.batch_on)
1957                self._keep.Enable(not self.batch_on)
1958            if self.data.is_data:
1959                self._set_save_flag(True)
1960                self._set_preview_flag(True)
1961
1962            # more disables for 2D
1963            di_flag = False
1964            dq_flag = False
1965            if isinstance(self.data, Data2D) or self.enable2D:
1966                self.slit_smearer.Disable()
1967                self.pinhole_smearer.Enable(True)
1968                self.default_mask = copy.deepcopy(self.data.mask)
1969                if self.data.err_data is not None \
1970                        and np.any(self.data.err_data):
1971                    di_flag = True
1972                if self.data.dqx_data is not None \
1973                        and np.any(self.data.dqx_data):
1974                    dq_flag = True
1975            else:
1976                self.slit_smearer.Enable(True)
1977                self.pinhole_smearer.Enable(True)
1978                if self.data.dy is not None and np.any(self.data.dy):
1979                    di_flag = True
1980                if self.data.dx is not None and np.any(self.data.dx):
1981                    dq_flag = True
1982                elif self.data.dxl is not None and np.any(self.data.dxl):
1983                    dq_flag = True
1984
1985            if dq_flag:
1986                self.enable_smearer.Enable(True)
1987                self.enable_smearer.SetValue(True)
1988                self.disable_smearer.SetValue(False)
1989            else:
1990                self.enable_smearer.Disable()
1991                self.disable_smearer.Enable(True)
1992                self.disable_smearer.SetValue(True)
1993
1994            if di_flag:
1995                self.dI_didata.Enable(True)
1996                self.dI_didata.SetValue(True)
1997                self.weightbt_string = self.dI_didata.GetLabelText()
1998            else:
1999                self.dI_didata.Enable(False)
2000                self.dI_noweight.SetValue(True)
2001                self.weightbt_string = self.dI_noweight.GetLabelText()
2002
2003            # Enable weighting radio buttons
2004            self.dI_noweight.Enable(True)
2005            self.dI_sqrdata.Enable(True)
2006            self.dI_idata.Enable(True)
2007
2008            self.formfactorbox.Enable()
2009            self.structurebox.Enable()
2010            data_name = self.data.name
2011            _, _, npts = self.compute_data_range(self.data)
2012            # set maximum range for x in linear scale
2013            if not hasattr(self.data, "data"):  # Display only for 1D data fit
2014                self.btEditMask.Disable()
2015                self.EditMask_title.Disable()
2016            else:
2017                self.btEditMask.Enable()
2018                self.EditMask_title.Enable()
2019
2020        self.Npts_total.SetValue(str(npts))
2021        # default:number of data points selected to fit
2022        self.Npts_fit.SetValue(str(npts))
2023        self.Npts_total.SetEditable(False)
2024        self.Npts_total.SetBackgroundColour(
2025                                    self.GetParent().GetBackgroundColour())
2026
2027        self.Npts_total.Bind(wx.EVT_MOUSE_EVENTS, self._npts_click)
2028        self.pointsbox.Disable()
2029        self.dataSource.SetValue(data_name)
2030        self.state.data = data
2031        self.enable_fit_button()
2032        # send graph_id to page_finder
2033        self._manager.set_graph_id(uid=self.uid, graph_id=self.graph_id)
2034        # focus the page
2035        if check_data_validity(data):
2036            self.data_box_description.SetForegroundColour(wx.BLUE)
2037
2038        if self.batch_on:
2039            self.slit_smearer.Enable(False)
2040            self.pinhole_smearer.Enable(False)
2041            self.btEditMask.Disable()
2042            self.EditMask_title.Disable()
2043
2044        self.on_smear_helper()
2045        self.on_set_focus(None)
2046        self.Layout()
2047        self.Refresh()
2048        # update model plot with new data information
2049        if flag:
2050            if isinstance(self.data, Data2D):
2051                self.enable2D = True
2052                self.model_view.SetLabel("2D Mode")
2053            else:
2054                self.enable2D = False
2055                self.model_view.SetLabel("1D Mode")
2056            self.model_view.Disable()
2057            #  replace data plot on combo box selection
2058            # by removing the previous selected data
2059            try:
2060                wx.PostEvent(self._manager.parent,
2061                             NewPlotEvent(action="delete",
2062                                          group_id=old_group_id, id=old_id))
2063            except:
2064                pass
2065            # plot the current selected data
2066            wx.PostEvent(self._manager.parent,
2067                         NewPlotEvent(action="check", plot=self.data,
2068                                      title=str(self.data.title)))
2069            self._draw_model()
2070
2071    def _npts_click(self, event):
2072        """
2073        Prevent further handling of the mouse event on Npts_total
2074        by not calling Skip().
2075        """
2076        pass
2077
2078    def reset_page(self, state, first=False):
2079        """
2080        reset the state
2081        """
2082        try:
2083            self.reset_page_helper(state)
2084
2085            self.select_param()
2086            # Save state_fit
2087            self.save_current_state_fit()
2088            self.onSmear(None)
2089            self._onDraw(None)
2090        except:
2091            self._show_combox_helper()
2092            msg = "Error: This model state has missing or outdated "
2093            msg += "information.\n"
2094            msg += traceback.format_exc()
2095            wx.PostEvent(self._manager.parent,
2096                         StatusEvent(status=msg, info="error"))
2097        self._lay_out()
2098        self.Refresh()
2099
2100    def get_range(self):
2101        """
2102        return the fitting range
2103        """
2104        return float(self.qmin_x), float(self.qmax_x)
2105
2106    def get_npts2fit(self):
2107        """
2108        return numbers of data points within qrange
2109
2110        :Note: This is to normalize chisq by Npts of fit
2111
2112        """
2113        if self.data is None:
2114            return
2115        npts2fit = 0
2116        qmin, qmax = self.get_range()
2117        if isinstance(self.data, Data2D) or self.enable2D:
2118            radius = np.sqrt(self.data.qx_data * self.data.qx_data +
2119                                self.data.qy_data * self.data.qy_data)
2120            index_data = (self.qmin_x <= radius) & (radius <= self.qmax_x)
2121            index_data = (index_data) & (self.data.mask)
2122            index_data = (index_data) & (np.isfinite(self.data.data))
2123            npts2fit = len(self.data.data[index_data])
2124        else:
2125            for qx in self.data.x:
2126                if qmax >= qx >= qmin:
2127                    npts2fit += 1
2128        return npts2fit
2129
2130    def show_npts2fit(self):
2131        """
2132        setValue Npts for fitting
2133        """
2134        self.Npts_fit.SetValue(str(self.get_npts2fit()))
2135
2136    def get_chi2(self):
2137        """
2138        return the current chi2
2139        """
2140        return self.tcChi.GetValue()
2141
2142    def onsetValues(self, chisqr, p_name, out, cov):
2143        """
2144        Build the panel from the fit result
2145
2146        :param chisqr: Value of the goodness of fit metric
2147        :param p_name: the name of parameters
2148        :param out: list of parameter with the best value found during fitting
2149        :param cov: Covariance matrix
2150
2151        """
2152
2153        # make sure stop button to fit button all the time
2154        self._on_fit_complete()
2155        if out is None or not np.isfinite(chisqr):
2156            raise ValueError, "Fit error occured..."
2157
2158        is_modified = False
2159        has_error = False
2160        dispersity = ''
2161
2162        # Hide textctrl boxes of errors.
2163        self._clear_Err_on_Fit()
2164
2165        # Check if chi2 is finite
2166        if chisqr is not None and np.isfinite(chisqr):
2167            # format chi2
2168            chi2 = format_number(chisqr, True)
2169            self.tcChi.SetValue(chi2)
2170            self.tcChi.Refresh()
2171        else:
2172            self.tcChi.SetValue("-")
2173
2174        # Hide error title
2175        if self.text2_3.IsShown() and not self.is_mac:
2176            self.text2_3.Hide()
2177
2178        try:
2179            if self.enable_disp.GetValue():
2180                if hasattr(self, "text_disp_1"):
2181                    if self.text_disp_1 is not None and not self.is_mac:
2182                        self.text_disp_1.Hide()
2183        except:
2184            dispersity = None
2185            pass
2186
2187        i = 0
2188        # Set the panel when fit result are list
2189
2190        for item in self.param_toFit:
2191            if len(item) > 5 and item is not None:
2192
2193                if item[0].IsShown():
2194                    # reset error value to initial state
2195                    if not self.is_mac:
2196                        item[3].Hide()
2197                        item[4].Hide()
2198                    for ind in range(len(out)):
2199                        if item[1] == p_name[ind]:
2200                            break
2201                    if len(out) > 0 and out[ind] is not None:
2202                        val_out = format_number(out[ind], True)
2203                        item[2].SetValue(val_out)
2204
2205                    if(cov is not None and len(cov) == len(out)):
2206                        try:
2207                            if dispersity is not None:
2208                                if self.enable_disp.GetValue():
2209                                    if hasattr(self, "text_disp_1"):
2210                                        if self.text_disp_1 is not None:
2211                                            if not self.text_disp_1.IsShown()\
2212                                                  and not self.is_mac:
2213                                                self.text_disp_1.Show(True)
2214                        except:
2215                            pass
2216
2217                        if cov[ind] is not None:
2218                            if np.isfinite(float(cov[ind])):
2219                                val_err = format_number(cov[ind], True)
2220                                item[4].SetForegroundColour(wx.BLACK)
2221                            else:
2222                                val_err = 'NaN'
2223                                item[4].SetForegroundColour(wx.RED)
2224                            if not self.is_mac:
2225                                item[3].Show(True)
2226                                item[4].Show(True)
2227                            item[4].SetValue(val_err)
2228                            has_error = True
2229                i += 1
2230            else:
2231                raise ValueError, "onsetValues: Invalid parameters..."
2232        # Show error title when any errors displayed
2233        if has_error:
2234            if not self.text2_3.IsShown():
2235                self.text2_3.Show(True)
2236        # save current state
2237        self.save_current_state()
2238
2239        if not self.is_mac:
2240            self.Layout()
2241            self.Refresh()
2242        # plot model ( when drawing, do not update chisqr value again)
2243        self._draw_model(update_chisqr=False, source='fit')
2244
2245    def onWeighting(self, event):
2246        """
2247        On Weighting radio button event, sets the weightbt_string
2248        """
2249        self.weightbt_string = event.GetEventObject().GetLabelText()
2250        self._set_weight()
2251
2252    def _set_weight(self, is_2D=None):
2253        """
2254        Set weight in fit problem
2255        """
2256        # compute weight for the current data
2257        flag_weight = self.get_weight_flag()
2258        if is_2D is None:
2259            is_2D = self._is_2D()
2260        self._manager.set_fit_weight(uid=self.uid,
2261                                     flag=flag_weight,
2262                                     is2d=is_2D,
2263                                     fid=None)
2264
2265    def onPinholeSmear(self, event):
2266        """
2267        Create a custom pinhole smear object that will change the way residuals
2268        are compute when fitting
2269
2270        :Note: accuracy is given by strings'High','Med', 'Low' FOR 2d,
2271                     None for 1D
2272
2273        """
2274        # Need update param values
2275        self._update_paramv_on_fit()
2276
2277        if event is not None:
2278            tcrtl = event.GetEventObject()
2279            # event case of radio button
2280            if tcrtl.GetValue():
2281                self.dx_percent = 0.0
2282                is_new_pinhole = True
2283            else:
2284                is_new_pinhole = self._is_changed_pinhole()
2285        else:
2286            is_new_pinhole = True
2287        # if any value is changed
2288        if is_new_pinhole:
2289            self._set_pinhole_smear()
2290        # hide all silt sizer
2291        self._hide_all_smear_info()
2292
2293        # show relevant slit sizers
2294        self._show_smear_sizer()
2295
2296        self.sizer_set_smearer.Layout()
2297        # we need FitInside here not just self.Layout to ensure all the sizers
2298        # end up with the necessasary space to in the scroll panel. In
2299        # particular the compute and fit buttons end up on top of each other
2300        # PDB Nov 28 2015.
2301        self.FitInside()
2302
2303        if event is not None:
2304            event.Skip()
2305        # self._undo.Enable(True)
2306        self.save_current_state()
2307        event = PageInfoEvent(page=self)
2308        wx.PostEvent(self.parent, event)
2309
2310    def _is_changed_pinhole(self):
2311        """
2312        check if any of pinhole smear is changed
2313
2314        :return: True or False
2315
2316        """
2317        # get the values
2318        pin_percent = self.smear_pinhole_percent.GetValue()
2319
2320        # Check changes in slit heigth
2321        try:
2322            dx_percent = float(pin_percent)
2323        except:
2324            return True
2325        if self.dx_percent != dx_percent:
2326            return True
2327        return False
2328
2329    def _set_pinhole_smear(self):
2330        """
2331        Set custom pinhole smear
2332
2333        :return: msg
2334
2335        """
2336        # copy data
2337        data = copy.deepcopy(self.data)
2338        if self._is_2D():
2339            self.smear_type = 'Pinhole2d'
2340            len_data = len(data.data)
2341            data.dqx_data = np.zeros(len_data)
2342            data.dqy_data = np.zeros(len_data)
2343        else:
2344            self.smear_type = 'Pinhole'
2345            len_data = len(data.x)
2346            data.dx = np.zeros(len_data)
2347            data.dxl = None
2348            data.dxw = None
2349        msg = None
2350
2351        get_pin_percent = self.smear_pinhole_percent
2352
2353        if not check_float(get_pin_percent):
2354            get_pin_percent.SetBackgroundColour("pink")
2355            msg = "Model Error:wrong value entered!!!"
2356        else:
2357            if len_data < 2:
2358                len_data = 2
2359            self.dx_percent = float(get_pin_percent.GetValue())
2360            if self.dx_percent < 0:
2361                get_pin_percent.SetBackgroundColour("pink")
2362                msg = "Model Error:This value can not be negative!!!"
2363            elif self.dx_percent is not None:
2364                percent = self.dx_percent/100
2365                if self._is_2D():
2366                    data.dqx_data[data.dqx_data == 0] = percent * data.qx_data
2367                    data.dqy_data[data.dqy_data == 0] = percent * data.qy_data
2368                else:
2369                    data.dx = percent * data.x
2370            self.current_smearer = smear_selection(data, self.model)
2371            # 2D need to set accuracy
2372            if self._is_2D():
2373                self.current_smearer.set_accuracy(
2374                    accuracy=self.smear2d_accuracy)
2375
2376        if msg is not None:
2377            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2378        else:
2379            get_pin_percent.SetBackgroundColour("white")
2380        # set smearing value whether or not the data contain the smearing info
2381
2382        enable_smearer = not self.disable_smearer.GetValue()
2383        self._manager.set_smearer(smearer=self.current_smearer,
2384                                  fid=self.data.id,
2385                                  qmin=float(self.qmin_x),
2386                                  qmax=float(self.qmax_x),
2387                                  enable_smearer=enable_smearer,
2388                                  uid=self.uid)
2389        return msg
2390
2391    def update_pinhole_smear(self):
2392        """
2393        called by kill_focus on pinhole TextCntrl
2394        to update the changes
2395
2396        :return: False when wrong value was entered
2397
2398        """
2399        # msg default
2400        msg = None
2401        # check if any value is changed
2402        if self._is_changed_pinhole():
2403            msg = self._set_pinhole_smear()
2404        wx.CallAfter(self.save_current_state)
2405
2406        if msg is not None:
2407            return False
2408        else:
2409            return True
2410
2411    def onSlitSmear(self, event):
2412        """
2413        Create a custom slit smear object that will change the way residuals
2414        are compute when fitting
2415        """
2416        # Need update param values
2417        self._update_paramv_on_fit()
2418
2419        # msg default
2420        msg = None
2421        # for event given
2422        if event is not None:
2423            tcrtl = event.GetEventObject()
2424            # event case of radio button
2425            if tcrtl.GetValue():
2426                self.dxl = 0.0
2427                self.dxw = 0.0
2428                is_new_slit = True
2429            else:
2430                is_new_slit = self._is_changed_slit()
2431        else:
2432            is_new_slit = True
2433
2434        # if any value is changed
2435        if is_new_slit:
2436            msg = self._set_slit_smear()
2437
2438        # hide all silt sizer
2439        self._hide_all_smear_info()
2440        # show relevant slit sizers
2441        self._show_smear_sizer()
2442        self.sizer_set_smearer.Layout()
2443        # we need FitInside here not just self.Layout to ensure all the sizers
2444        # end up with the necessasary space to in the scroll panel. In
2445        # particular the compute and fit buttons end up on top of each other
2446        # PDB Nov 28 2015.
2447        self.FitInside()
2448
2449        if event is not None:
2450            event.Skip()
2451        self.save_current_state()
2452        event = PageInfoEvent(page=self)
2453        wx.PostEvent(self.parent, event)
2454        if msg is not None:
2455            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2456
2457    def _is_changed_slit(self):
2458        """
2459        check if any of slit lengths is changed
2460
2461        :return: True or False
2462
2463        """
2464        # get the values
2465        width = self.smear_slit_width.GetValue()
2466        height = self.smear_slit_height.GetValue()
2467
2468        # check and change the box bg color if it was pink
2469        #    but it should be white now
2470        # because this is the case that _set_slit_smear() will not handle
2471        if height.lstrip().rstrip() == "":
2472            self.smear_slit_height.SetBackgroundColour(wx.WHITE)
2473        if width.lstrip().rstrip() == "":
2474            self.smear_slit_width.SetBackgroundColour(wx.WHITE)
2475
2476        # Check changes in slit width
2477        if width == "":
2478            dxw = 0.0
2479        else:
2480            try:
2481                dxw = float(width)
2482            except:
2483                return True
2484        if self.dxw != dxw:
2485            return True
2486
2487        # Check changes in slit heigth
2488        if height == "":
2489            dxl = 0.0
2490        else:
2491            try:
2492                dxl = float(height)
2493            except:
2494                return True
2495        if self.dxl != dxl:
2496            return True
2497
2498        return False
2499
2500    def _set_slit_smear(self):
2501        """
2502        Set custom slit smear
2503
2504        :return: message to inform the user about the validity
2505            of the values entered for slit smear
2506        """
2507        if isinstance(self.data, Data2D) or self.enable2D:
2508            return
2509        # make sure once more if it is smearer
2510        data = copy.deepcopy(self.data)
2511        data_len = len(data.x)
2512        data.dx = None
2513        data.dxl = None
2514        data.dxw = None
2515        msg = None
2516
2517        try:
2518            self.dxl = float(self.smear_slit_height.GetValue())
2519            data.dxl = self.dxl * np.ones(data_len)
2520            self.smear_slit_height.SetBackgroundColour(wx.WHITE)
2521        except:
2522            self.dxl = None
2523            data.dxl = np.zeros(data_len)
2524            if self.smear_slit_height.GetValue().lstrip().rstrip() != "":
2525                self.smear_slit_height.SetBackgroundColour("pink")
2526                msg = "Wrong value entered... "
2527            else:
2528                self.smear_slit_height.SetBackgroundColour(wx.WHITE)
2529        try:
2530            self.dxw = float(self.smear_slit_width.GetValue())
2531            self.smear_slit_width.SetBackgroundColour(wx.WHITE)
2532            data.dxw = self.dxw * np.ones(data_len)
2533        except:
2534            self.dxw = None
2535            data.dxw = np.zeros(data_len)
2536            if self.smear_slit_width.GetValue().lstrip().rstrip() != "":
2537                self.smear_slit_width.SetBackgroundColour("pink")
2538                msg = "Wrong Fit value entered... "
2539            else:
2540                self.smear_slit_width.SetBackgroundColour(wx.WHITE)
2541
2542        self.current_smearer = smear_selection(data, self.model)
2543        # set smearing value whether or not the data contain the smearing info
2544        enable_smearer = not self.disable_smearer.GetValue()
2545        self._manager.set_smearer(smearer=self.current_smearer,
2546                                  fid=self.data.id,
2547                                  qmin=float(self.qmin_x),
2548                                  qmax=float(self.qmax_x),
2549                                  enable_smearer=enable_smearer,
2550                                  uid=self.uid)
2551        return msg
2552
2553    def update_slit_smear(self):
2554        """
2555        called by kill_focus on pinhole TextCntrl
2556        to update the changes
2557
2558        :return: False when wrong value was entered
2559
2560        """
2561        # msg default
2562        msg = None
2563        # check if any value is changed
2564        if self._is_changed_slit():
2565            msg = self._set_slit_smear()
2566        # self._undo.Enable(True)
2567        self.save_current_state()
2568
2569        if msg is not None:
2570            return False
2571        else:
2572            return True
2573
2574    def onSmear(self, event):
2575        """
2576        Create a smear object that will change the way residuals
2577        are computed when fitting
2578        """
2579        if event is not None:
2580            event.Skip()
2581        if self.data is None:
2582            return
2583
2584        # Need update param values
2585        self._update_paramv_on_fit()
2586        if self.model is not None:
2587            if self.data.is_data:
2588                self._manager.page_finder[self.uid].add_data(data=self.data)
2589        temp_smearer = self.on_smear_helper()
2590
2591        self.sizer_set_smearer.Layout()
2592        # we need FitInside here not just self.Layout to ensure all the sizers
2593        # end up with the necessasary space to in the scroll panel. In
2594        # particular the compute and fit buttons end up on top of each other
2595        # PDB Nov 28 2015.
2596        self.FitInside()
2597        self._set_weight()
2598
2599        # set smearing value whether or not the data contain the smearing info
2600        enable_smearer = not self.disable_smearer.GetValue()
2601        wx.CallAfter(self._manager.set_smearer, uid=self.uid,
2602                     smearer=temp_smearer,
2603                     fid=self.data.id,
2604                     qmin=float(self.qmin_x),
2605                     qmax=float(self.qmax_x),
2606                     enable_smearer=enable_smearer)
2607
2608        self.state.enable_smearer = self.enable_smearer.GetValue()
2609        self.state.disable_smearer = self.disable_smearer.GetValue()
2610        self.state.pinhole_smearer = self.pinhole_smearer.GetValue()
2611        self.state.slit_smearer = self.slit_smearer.GetValue()
2612
2613    def on_smear_helper(self, update=False):
2614        """
2615        Help for onSmear
2616
2617        :param update: force or not to update
2618        """
2619        self._get_smear_info()
2620        # renew smear sizer
2621        if self.smear_type is not None:
2622            self.smear_description_smear_type.SetValue(str(self.smear_type))
2623            self.smear_data_left.SetValue(str(self.dq_l))
2624            self.smear_data_right.SetValue(str(self.dq_r))
2625
2626        self._hide_all_smear_info()
2627        data = copy.deepcopy(self.data)
2628
2629        # make sure once more if it is smearer
2630        temp_smearer = smear_selection(data, self.model)
2631        if self.current_smearer != temp_smearer or update:
2632            self.current_smearer = temp_smearer
2633        if self.enable_smearer.GetValue():
2634            if self.current_smearer is None:
2635                wx.PostEvent(self._manager.parent,
2636                    StatusEvent(status="Data contains no smearing information"))
2637            else:
2638                wx.PostEvent(self._manager.parent,
2639                    StatusEvent(status="Data contains smearing information"))
2640
2641            self.smear_data_left.Show(True)
2642            self.smear_data_right.Show(True)
2643            temp_smearer = self.current_smearer
2644        elif self.disable_smearer.GetValue():
2645            self.smear_description_none.Show(True)
2646        elif self.pinhole_smearer.GetValue():
2647            self.onPinholeSmear(None)
2648        elif self.slit_smearer.GetValue():
2649            self.onSlitSmear(None)
2650        self._show_smear_sizer()
2651
2652        return temp_smearer
2653
2654    def on_complete_chisqr(self, event):
2655        """
2656        Display result chisqr on the panel
2657        :event: activated by fitting/ complete after draw
2658        """
2659        try:
2660            if event is None:
2661                output = "-"
2662            elif not np.isfinite(event.output):
2663                output = "-"
2664            else:
2665                output = event.output
2666            self.tcChi.SetValue(str(format_number(output, True)))
2667            self.state.tcChi = self.tcChi.GetValue()
2668        except:
2669            pass
2670
2671    def get_all_checked_params(self):
2672        """
2673        Found all parameters current check and add them to list of parameters
2674        to fit
2675        """
2676        self.param_toFit = []
2677        for item in self.parameters:
2678            if item[0].GetValue() and item not in self.param_toFit:
2679                if item[0].IsShown():
2680                    self.param_toFit.append(item)
2681        for item in self.fittable_param:
2682            if item[0].GetValue() and item not in self.param_toFit:
2683                if item[0].IsShown():
2684                    self.param_toFit.append(item)
2685        self.save_current_state_fit()
2686
2687        event = PageInfoEvent(page=self)
2688        wx.PostEvent(self.parent, event)
2689        param2fit = []
2690        for item in self.param_toFit:
2691            if item[0] and item[0].IsShown():
2692                param2fit.append(item[1])
2693        self._manager.set_param2fit(self.uid, param2fit)
2694
2695    def select_param(self, event=None):
2696        """
2697        Select TextCtrl  checked for fitting purpose and stores them
2698        in  self.param_toFit=[] list
2699        """
2700        self.param_toFit = []
2701        for item in self.parameters:
2702            # Skip t ifhe angle parameters if 1D data
2703            if not isinstance(self.data, Data2D) and not self.enable2D:
2704                if item in self.orientation_params:
2705                    continue
2706            # Select parameters to fit for list of primary parameters
2707            if item[0].GetValue() and item[0].IsShown():
2708                if not (item in self.param_toFit):
2709                    self.param_toFit.append(item)
2710            else:
2711                #remove parameters from the fitting list
2712                if item in self.param_toFit:
2713                    self.param_toFit.remove(item)
2714
2715        # Select parameters to fit for list of fittable parameters
2716        #        with dispersion
2717        for item in self.fittable_param:
2718            # Skip t ifhe angle parameters if 1D data
2719            if not isinstance(self.data, Data2D) and not self.enable2D:
2720                if item in self.orientation_params:
2721                    continue
2722            if item[0].GetValue() and item[0].IsShown():
2723                if not (item in self.param_toFit):
2724                    self.param_toFit.append(item)
2725            else:
2726                # remove parameters from the fitting list
2727                if item in self.param_toFit:
2728                    self.param_toFit.remove(item)
2729
2730        # Calculate num. of angle parameters
2731        if isinstance(self.data, Data2D) or self.enable2D:
2732            len_orient_para = 0
2733        else:
2734            len_orient_para = len(self.orientation_params)  # assume even len
2735        # Total num. of angle parameters
2736        if len(self.fittable_param) > 0:
2737            len_orient_para *= 2
2738
2739        self.save_current_state_fit()
2740        if event is not None:
2741            # post state to fit panel
2742            event = PageInfoEvent(page=self)
2743            wx.PostEvent(self.parent, event)
2744
2745        param2fit = []
2746        for item in self.param_toFit:
2747            if item[0] and item[0].IsShown():
2748                param2fit.append(item[1])
2749        self._manager.set_param2fit(self.uid, param2fit)
2750
2751    def set_model_param_sizer(self, model):
2752        """
2753        Build the panel from the model content
2754
2755        :param model: the model selected in combo box for fitting purpose
2756
2757        """
2758        self.sizer3.Clear(True)
2759        self.parameters = []
2760        self.str_parameters = []
2761        self.param_toFit = []
2762        self.fittable_param = []
2763        self.fixed_param = []
2764        self.orientation_params = []
2765        self.orientation_params_disp = []
2766
2767        if model is None:
2768            self.sizer3.Layout()
2769            self.SetupScrolling()
2770            return
2771
2772        box_description = wx.StaticBox(self, wx.ID_ANY, str("Model Parameters"))
2773        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
2774        sizer = wx.GridBagSizer(5, 5)
2775        # save the current model
2776        self.model = model
2777
2778        keys = self.model.getParamList()
2779
2780        # list of dispersion parameters
2781        self.disp_list = self.model.getDispParamList()
2782
2783        def custom_compare(a, b):
2784            """
2785            Custom compare to order, first by alphabets then second by number.
2786            """
2787            # number at the last digit
2788            a_last = a[len(a) - 1]
2789            b_last = b[len(b) - 1]
2790            # default
2791            num_a = None
2792            num_b = None
2793            # split the names
2794            a2 = a.lower().split('_')
2795            b2 = b.lower().split('_')
2796            # check length of a2, b2
2797            len_a2 = len(a2)
2798            len_b2 = len(b2)
2799            # check if it contains a int number(<10)
2800            try:
2801                num_a = int(a_last)
2802            except:
2803                pass
2804            try:
2805                num_b = int(b_last)
2806            except:
2807                pass
2808            # Put 'scale' near the top; happens
2809            # when numbered param name exists
2810            if a == 'scale':
2811                return -1
2812            # both have a number
2813            if num_a is not None and num_b is not None:
2814                if num_a > num_b:
2815                    return -1
2816                # same number
2817                elif num_a == num_b:
2818                    # different last names
2819                    if a2[len_a2 - 1] != b2[len_b2 - 1] and num_a != 0:
2820                        return -cmp(a2[len_a2 - 1], b2[len_b2 - 1])
2821                    else:
2822                        return cmp(a, b)
2823                else:
2824                    return 1
2825            # one of them has a number
2826            elif num_a is not None:
2827                return 1
2828            elif num_b is not None:
2829                return -1
2830            # no numbers
2831            else:
2832                return cmp(a.lower(), b.lower())
2833
2834        # keys obtained now from ordered dict, so commenting alphabetical
2835        # ordering keys.sort(custom_compare)
2836
2837        iy = 0
2838        ix = 0
2839        sizer.Add(wx.StaticText(self, wx.ID_ANY, 'Parameter'),
2840                  (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2841        ix += 1
2842        self.text2_2 = wx.StaticText(self, wx.ID_ANY, 'Value')
2843        sizer.Add(self.text2_2, (iy, ix), (1, 1),
2844                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2845        ix += 2
2846        self.text2_3 = wx.StaticText(self, wx.ID_ANY, 'Error')
2847        sizer.Add(self.text2_3, (iy, ix), (1, 1),
2848                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2849        if not self.is_mac:
2850            self.text2_3.Hide()
2851        ix += 1
2852        self.text2_min = wx.StaticText(self, wx.ID_ANY, 'Min')
2853        sizer.Add(self.text2_min, (iy, ix), (1, 1),
2854                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2855        # self.text2_min.Hide()
2856        ix += 1
2857        self.text2_max = wx.StaticText(self, wx.ID_ANY, 'Max')
2858        sizer.Add(self.text2_max, (iy, ix), (1, 1),
2859                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2860        # self.text2_max.Hide()
2861        ix += 1
2862        self.text2_4 = wx.StaticText(self, wx.ID_ANY, '[Units]')
2863        sizer.Add(self.text2_4, (iy, ix), (1, 1),
2864                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2865        self.text2_4.Hide()
2866
2867        CHECK_STATE = False
2868        for item in keys:
2869
2870            if item not in self.disp_list and not item in \
2871                    self.model.orientation_params:
2872
2873                # prepare a spot to store errors
2874                if item not in self.model.details:
2875                    self.model.details[item] = ["", None, None]
2876
2877                iy += 1
2878                ix = 0
2879                if (self.model.__class__ in
2880                    self.model_list_box["Multi-Functions"] or
2881                    self.temp_multi_functional)\
2882                    and (item in self.model.non_fittable):
2883                    non_fittable_name = wx.StaticText(self, wx.ID_ANY, item)
2884                    sizer.Add(non_fittable_name, (iy, ix), (1, 1),
2885                            wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 21)
2886                    # add parameter value
2887                    ix += 1
2888                    value = self.model.getParam(item)
2889                    if len(self.model.fun_list) > 0:
2890                        # num = item.split('_')[1][5:7]
2891                        fun_box = wx.ComboBox(self, wx.ID_ANY, size=(100, -1),
2892                                    style=wx.CB_READONLY, name='%s' % item)
2893                        self._set_fun_box_list(fun_box)
2894                        fun_box.SetSelection(0)
2895                        # self.fun_box.SetToolTipString("A function
2896                        #    describing the interface")
2897                        wx.EVT_COMBOBOX(fun_box, wx.ID_ANY, self._on_fun_box)
2898                    else:
2899                        fun_box = ModelTextCtrl(self, wx.ID_ANY,
2900                                                size=(_BOX_WIDTH, 20),
2901                                style=wx.TE_PROCESS_ENTER, name='%s' % item)
2902                        fun_box.SetToolTipString(
2903                                "Hit 'Enter' after typing to update the plot.")
2904                        fun_box.SetValue(format_number(value, True))
2905                    sizer.Add(fun_box, (iy, ix), (1, 1), wx.EXPAND)
2906                    self.str_parameters.append([None, item, fun_box,
2907                                                None, None, None,
2908                                                None, None])
2909                else:
2910                    # add parameters name with checkbox for selecting to fit
2911                    cb = wx.CheckBox(self, wx.ID_ANY, item)
2912                    cb.SetValue(CHECK_STATE)
2913                    cb.SetToolTipString(" Check mark to fit.")
2914                    # cb.SetValue(True)
2915                    wx.EVT_CHECKBOX(self, cb.GetId(), self.select_param)
2916
2917                    sizer.Add(cb, (iy, ix), (1, 1),
2918                              wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
2919
2920                    # add parameter value
2921                    ix += 1
2922                    value = self.model.getParam(item)
2923                    ctl1 = ModelTextCtrl(self, wx.ID_ANY, size=(_BOX_WIDTH, 20),
2924                                         style=wx.TE_PROCESS_ENTER)
2925                    ctl1.SetToolTipString(
2926                                "Hit 'Enter' after typing to update the plot.")
2927                    ctl1.SetValue(format_number(value, True))
2928                    sizer.Add(ctl1, (iy, ix), (1, 1), wx.EXPAND)
2929                    # text to show error sign
2930                    ix += 1
2931                    text2 = wx.StaticText(self, wx.ID_ANY, '+/-')
2932                    sizer.Add(text2, (iy, ix), (1, 1),
2933                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2934                    if not self.is_mac:
2935                        text2.Hide()
2936                    ix += 1
2937                    ctl2 = BGTextCtrl(self, wx.ID_ANY,
2938                                       size=(_BOX_WIDTH / 1.2, 20))
2939                    sizer.Add(ctl2, (iy, ix), (1, 1),
2940                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2941                    if not self.is_mac:
2942                        ctl2.Hide()
2943
2944                    ix += 1
2945                    ctl3 = ModelTextCtrl(self, wx.ID_ANY,
2946                                         size=(_BOX_WIDTH / 1.9, 20),
2947                                         style=wx.TE_PROCESS_ENTER,
2948                                text_enter_callback=self._onparamRangeEnter)
2949                    min_bound = self.model.details[item][1]
2950                    if min_bound is not None:
2951                        ctl3.SetValue(format_number(min_bound, True))
2952
2953                    sizer.Add(ctl3, (iy, ix), (1, 1),
2954                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2955
2956                    ix += 1
2957                    ctl4 = ModelTextCtrl(self, wx.ID_ANY,
2958                                         size=(_BOX_WIDTH / 1.9, 20),
2959                                         style=wx.TE_PROCESS_ENTER,
2960                                text_enter_callback=self._onparamRangeEnter)
2961                    max_bound = self.model.details[item][2]
2962                    if max_bound is not None:
2963                        ctl4.SetValue(format_number(max_bound, True))
2964                    sizer.Add(ctl4, (iy, ix), (1, 1),
2965                              wx.EXPAND | wx.FIXED_MINSIZE, 0)
2966
2967                    ix += 1
2968                    # Units
2969                    if item in self.model.details:
2970                        units = wx.StaticText(self, wx.ID_ANY,
2971                            self.model.details[item][0], style=wx.ALIGN_LEFT)
2972                    else:
2973                        units = wx.StaticText(self, wx.ID_ANY, "",
2974                                              style=wx.ALIGN_LEFT)
2975                    sizer.Add(units, (iy, ix), (1, 1),
2976                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
2977
2978                    self.parameters.append([cb, item, ctl1,
2979                                            text2, ctl2, ctl3, ctl4, units])
2980
2981        iy += 1
2982        sizer.Add((10, 10), (iy, ix), (1, 1),
2983                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
2984
2985        # type can be either Guassian or Array
2986        if len(self.model.dispersion.values()) > 0:
2987            type = self.model.dispersion.values()[0]["type"]
2988        else:
2989            type = "Gaussian"
2990
2991        iy += 1
2992        ix = 0
2993        # Add tile for orientational angle
2994        for item in keys:
2995            if item in self.model.orientation_params:
2996                orient_angle = wx.StaticText(self, wx.ID_ANY, '[For 2D only]:')
2997                mag_on_button = wx.Button(self, wx.ID_ANY, "Magnetic ON")
2998                mag_on_button.SetToolTipString("Turn Pol Beam/Mag scatt on/off")
2999                mag_on_button.Bind(wx.EVT_BUTTON, self._on_mag_on)
3000                mag_angle_help_button = wx.Button(self, wx.ID_ANY,
3001                                                  "Magnetic angles?")
3002                mag_angle_help_button.SetToolTipString("see angle definitions")
3003                mag_help_button = wx.Button(self, wx.ID_ANY, "Mag HELP")
3004                mag_help_button.SetToolTipString("Help on pol beam/mag fitting")
3005                mag_help_button.Bind(wx.EVT_BUTTON, self._on_mag_help)
3006                mag_angle_help_button.Bind(wx.EVT_BUTTON,
3007                                            self._on_mag_angle_help)
3008                sizer.Add(orient_angle, (iy, ix), (1, 1),
3009                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
3010                iy += 1
3011                sizer.Add(mag_on_button, (iy, ix), (1, 1),
3012                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
3013                ix += 1
3014                sizer.Add(mag_angle_help_button, (iy, ix), (1, 1),
3015                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
3016                sizer.Add(mag_help_button, (iy, ix + 1), (1, 1),
3017                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
3018
3019                # handle the magnetic buttons
3020                # clean this up so that assume mag is off then turn
3021                # all buttons on IF mag has mag and has 2D
3022                if not self._has_magnetic:
3023                    mag_on_button.Show(False)
3024                elif not isinstance(self.data, Data2D):
3025                    mag_on_button.Show(False)
3026                else:
3027                    mag_on_button.Show(True)
3028                mag_help_button.Show(False)
3029                mag_angle_help_button.Show(False)
3030                if mag_on_button.IsShown():
3031                    if self.magnetic_on:
3032                        mag_on_button.SetLabel("Magnetic OFF")
3033                        mag_help_button.Show(True)
3034                        mag_angle_help_button.Show(True)
3035                    else:
3036                        mag_on_button.SetLabel("Magnetic ON")
3037                        mag_help_button.Show(False)
3038                        mag_angle_help_button.Show(False)
3039
3040                if not isinstance(self.data, Data2D) and not self.enable2D:
3041                    orient_angle.Hide()
3042                else:
3043                    orient_angle.Show(True)
3044                break
3045
3046        # For Gaussian only
3047        if type.lower() != "array":
3048            for item in self.model.orientation_params:
3049                if not self.magnetic_on:
3050                    if item in self.model.magnetic_params:
3051                        continue
3052                if item not in self.disp_list:
3053                    # prepare a spot to store min max
3054                    if item not in self.model.details:
3055                        self.model.details[item] = ["", None, None]
3056
3057                    iy += 1
3058                    ix = 0
3059                    # add parameters name with checkbox for selecting to fit
3060                    cb = wx.CheckBox(self, wx.ID_ANY, item)
3061                    cb.SetValue(CHECK_STATE)
3062                    cb.SetToolTipString("Check mark to fit")
3063                    wx.EVT_CHECKBOX(self, cb.GetId(), self.select_param)
3064                    if isinstance(self.data, Data2D) or self.enable2D:
3065                        cb.Show(True)
3066                    else:
3067                        cb.Hide()
3068                    sizer.Add(cb, (iy, ix), (1, 1),
3069                              wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 5)
3070
3071                    # add parameter value
3072                    ix += 1
3073                    value = self.model.getParam(item)
3074                    ctl1 = ModelTextCtrl(self, -1, size=(_BOX_WIDTH, 20),
3075                                         style=wx.TE_PROCESS_ENTER)
3076                    ctl1.SetToolTipString(
3077                                "Hit 'Enter' after typing to update the plot.")
3078                    ctl1.SetValue(format_number(value, True))
3079                    if isinstance(self.data, Data2D) or self.enable2D:
3080                        ctl1.Show(True)
3081                    else:
3082                        ctl1.Hide()
3083                    sizer.Add(ctl1, (iy, ix), (1, 1), wx.EXPAND)
3084                    # text to show error sign
3085                    ix += 1
3086                    text2 = wx.StaticText(self, -1, '+/-')
3087                    sizer.Add(text2, (iy, ix), (1, 1),
3088                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3089
3090                    text2.Hide()
3091                    ix += 1
3092                    ctl2 = wx.TextCtrl(self, -1,
3093                                       size=(_BOX_WIDTH / 1.2, 20), style=0)
3094                    sizer.Add(ctl2, (iy, ix), (1, 1),
3095                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3096
3097                    ctl2.Hide()
3098
3099                    ix += 1
3100                    ctl3 = ModelTextCtrl(self, -1,
3101                                         size=(_BOX_WIDTH / 1.8, 20),
3102                                         style=wx.TE_PROCESS_ENTER,
3103                                text_enter_callback=self._onparamRangeEnter)
3104
3105                    sizer.Add(ctl3, (iy, ix), (1, 1),
3106                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3107                    ctl3.Hide()
3108
3109                    ix += 1
3110                    ctl4 = ModelTextCtrl(self, -1,
3111                                         size=(_BOX_WIDTH / 1.8, 20),
3112                                         style=wx.TE_PROCESS_ENTER,
3113                            text_enter_callback=self._onparamRangeEnter)
3114                    sizer.Add(ctl4, (iy, ix), (1, 1),
3115                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3116
3117                    ctl4.Hide()
3118
3119                    if isinstance(self.data, Data2D) or self.enable2D:
3120                        if self.is_mac:
3121                            text2.Show(True)
3122                            ctl2.Show(True)
3123                        ctl3.Show(True)
3124                        ctl4.Show(True)
3125
3126                    ix += 1
3127                    # Units
3128                    if item in self.model.details:
3129                        units = wx.StaticText(self, -1,
3130                                              self.model.details[item][0],
3131                                              style=wx.ALIGN_LEFT)
3132                    else:
3133                        units = wx.StaticText(self, -1, "",
3134                                              style=wx.ALIGN_LEFT)
3135                    if isinstance(self.data, Data2D) or self.enable2D:
3136                        units.Show(True)
3137                    else:
3138                        units.Hide()
3139
3140                    sizer.Add(units, (iy, ix), (1, 1),
3141                              wx.EXPAND | wx.ADJUST_MINSIZE, 0)
3142
3143                    self.parameters.append([cb, item, ctl1,
3144                                            text2, ctl2, ctl3, ctl4, units])
3145                    self.orientation_params.append([cb, item, ctl1,
3146                                            text2, ctl2, ctl3, ctl4, units])
3147
3148        iy += 1
3149        box_description.SetForegroundColour(wx.BLUE)
3150        # Display units text on panel
3151        for item in keys:
3152            if item in self.model.details:
3153                self.text2_4.Show()
3154        # Fill the list of fittable parameters
3155        self.get_all_checked_params()
3156        self.save_current_state_fit()
3157        boxsizer1.Add(sizer)
3158        self.sizer3.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
3159        self.sizer3.Layout()
3160        self.Layout()
3161
3162    def on_right_down(self, event):
3163        """
3164        Get key stroke event
3165        """
3166        if self.data is None:
3167            return
3168        # Figuring out key combo: Cmd for copy, Alt for paste
3169        if event.AltDown() and event.ShiftDown():
3170            flag = True
3171        elif event.AltDown() or event.ShiftDown():
3172            flag = False
3173        else:
3174            return
3175        # make event free
3176        event.Skip()
3177        # messages depending on the flag
3178        if not flag:
3179            infor = 'warning'
3180            # inform msg to wx
3181            wx.PostEvent(self._manager.parent,
3182                        StatusEvent(status=msg, info=infor))
3183
3184    def _onModel2D(self, event):
3185        """
3186        toggle view of model from 1D to 2D  or 2D from 1D
3187        """
3188        if self.model_view.GetLabelText() == "Show 2D":
3189            self.model_view.SetLabel("Show 1D")
3190            self.enable2D = True
3191
3192        else:
3193            self.model_view.SetLabel("Show 2D")
3194            self.enable2D = False
3195        self.Show(False)
3196        self.create_default_data()
3197        self._manager.store_data(self.uid, data_list=[self.data])
3198
3199        self.set_model_param_sizer(self.model)
3200        self._set_sizer_dispersion()
3201        self._set_weight(is_2D=self.enable2D)
3202        self._set_smear_buttons()
3203        self.Show(True)
3204        self.SetupScrolling()
3205        self._draw_model()
3206
3207        self.state.enable2D = copy.deepcopy(self.enable2D)
3208
3209    def _set_smear_buttons(self):
3210        """
3211        Set semarer radio buttons
3212        """
3213        # more disables for 2D
3214        if isinstance(self.data, Data2D) or self.enable2D:
3215            self.slit_smearer.Disable()
3216            self.default_mask = copy.deepcopy(self.data.mask)
3217            if self.model is not None:
3218                self.pinhole_smearer.Enable(True)
3219
3220        elif isinstance(self.data, Data1D):
3221            if self.model is not None:
3222                self.slit_smearer.Enable(True)
3223                self.pinhole_smearer.Enable(True)
3224        else:
3225            msg="data is not recognized as either 1D or 2D"
3226            logger.info(msg)
3227
3228
3229class BGTextCtrl(wx.TextCtrl):
3230    """
3231    Text control used to display outputs.
3232    No editing allowed. The background is
3233    grayed out. User can't select text.
3234    """
3235    def __init__(self, *args, **kwds):
3236        wx.TextCtrl.__init__(self, *args, **kwds)
3237        self.SetEditable(False)
3238        self.SetBackgroundColour(self.GetParent().parent.GetBackgroundColour())
3239
3240        # Bind to mouse event to avoid text highlighting
3241        # The event will be skipped once the call-back
3242        # is called.
3243        self.Bind(wx.EVT_MOUSE_EVENTS, self._click)
3244
3245    def _click(self, event):
3246        """
3247        Prevent further handling of the mouse event
3248        by not calling Skip().
3249        """
3250        pass
Note: See TracBrowser for help on using the repository browser.