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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 7cde638 was ed2276f, checked in by GitHub <noreply@…>, 8 years ago

Merge branch 'master' into numpy_import

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