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

magnetic_scattrelease-4.2.2ticket-1009ticket-1249
Last change on this file since 5251ec6 was 5251ec6, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

improved support for py37 in sasgui

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