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

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

Fix 2D smearing GUI

Make 2D from data as % of average. Fix input and message boxes to be
the right ones and fix some logic issues

addresses #1206

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