source: sasview/src/sas/sasgui/perspectives/fitting/basepage.py @ 2abe6bf

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 2abe6bf was 2abe6bf, checked in by mathieu, 8 years ago

Let custom models have different class names. Fixes #732

  • Property mode set to 100644
File size: 139.6 KB
RevLine 
[f0d720b]1"""
2Base Page for fitting
3"""
4import sys
5import os
6import wx
7import numpy
8import time
9import copy
10import math
11import json
[5ce7f17]12import logging
[7673ecd]13import traceback
14
[f0d720b]15from collections import defaultdict
16from wx.lib.scrolledpanel import ScrolledPanel
[7673ecd]17
[a0373d5]18from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
19
[d85c194]20from sas.sasgui.guiframe.panel_base import PanelBase
[a0373d5]21from sas.sasgui.guiframe.utils import format_number, check_float, IdList, check_int
[d85c194]22from sas.sasgui.guiframe.events import PanelOnFocusEvent
23from sas.sasgui.guiframe.events import StatusEvent
24from sas.sasgui.guiframe.events import AppendBookmarkEvent
25from sas.sasgui.guiframe.dataFitting import Data2D
26from sas.sasgui.guiframe.dataFitting import Data1D
27from sas.sasgui.guiframe.dataFitting import check_data_validity
28from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
[b699768]29from sas.sascalc.dataloader.data_info import Detector
30from sas.sascalc.dataloader.data_info import Source
[d85c194]31from sas.sasgui.perspectives.fitting.pagestate import PageState
32from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
33from sas.sasgui.guiframe.documentation_window import DocumentationWindow
[5ce7f17]34
[f0d720b]35
36(PageInfoEvent, EVT_PAGE_INFO) = wx.lib.newevent.NewEvent()
37(PreviousStateEvent, EVT_PREVIOUS_STATE) = wx.lib.newevent.NewEvent()
38(NextStateEvent, EVT_NEXT_STATE) = wx.lib.newevent.NewEvent()
39
40_BOX_WIDTH = 76
41_QMIN_DEFAULT = 0.0005
42_QMAX_DEFAULT = 0.5
43_NPTS_DEFAULT = 50
44#Control panel width
45if sys.platform.count("win32") > 0:
46    PANEL_WIDTH = 450
47    FONT_VARIANT = 0
48    ON_MAC = False
49else:
50    PANEL_WIDTH = 500
51    FONT_VARIANT = 1
52    ON_MAC = True
53
54
55class BasicPage(ScrolledPanel, PanelBase):
56    """
57    This class provide general structure of  fitpanel page
58    """
59    ## Internal name for the AUI manager
60    window_name = "Fit Page"
61    ## Title to appear on top of the window
62    window_caption = "Fit Page "
[02098e3]63
[6f16e25]64    # These two buttons have specific IDs since they seem to be created more
65    # frequently than they need to.  In particular, set_dispers_sizer() is
66    # called by _on_select_model
67    ID_BOOKMARK = wx.NewId()
68    ID_DISPERSER_HELP = wx.NewId()
69    _id_pool = IdList()
[5ce7f17]70
[f0d720b]71    def __init__(self, parent, color='blue', **kwargs):
72        """
73        """
74        ScrolledPanel.__init__(self, parent, **kwargs)
75        PanelBase.__init__(self, parent)
76        self.SetupScrolling()
77        #Set window's font size
78        self.SetWindowVariant(variant=FONT_VARIANT)
79        self.SetBackgroundColour(color)
[6f16e25]80
81        self._ids = iter(self._id_pool)
[f0d720b]82        ## parent of the page
83        self.parent = parent
84        ## manager is the fitting plugin
85        ## owner of the page (fitting plugin)
86        self.event_owner = None
87        ## current model
88        self.model = None
89        self.m_name = None
90        self.index_model = None
91        self.panel = None
92        ## data
93        self.data = None
94        #list of available data
95        self.data_list = []
96        self.mask = None
97        self.uid = wx.NewId()
98        self.graph_id = None
99        #Q range for data set
100        self.qmin_data_set = numpy.inf
101        self.qmax_data_set = None
102        self.npts_data_set = 0
103        ## Q range
104        self.qmin = None
105        self.qmax = None
106        self.qmax_x = _QMAX_DEFAULT
107        self.qmin_x = _QMIN_DEFAULT
108        self.npts_x = _NPTS_DEFAULT
109        ## total number of point: float
110        self.npts = None
111        self.num_points = None
112        ## smear default
113        self.current_smearer = None
114        ## 2D smear accuracy default
115        self.smear2d_accuracy = 'Low'
116        ## slit smear:
117        self.dxl = None
118        self.dxw = None
119        ## pinhole smear
120        self.dx_min = None
121        self.dx_max = None
122        ##semar attrbs
123        self.enable_smearer = None
124        self.disable_smearer = None
125        self.pinhole_smearer = None
126        self.slit_smearer = None
127        ##weigth attrbs
128        self.dI_noweight = None
129        self.dI_didata = None
130        self.dI_sqrdata = None
131        self.dI_idata = None
132        ##other attrbs
133        self.dq_l = None
134        self.dq_r = None
135        self.tcChi = None
136        self.disp_box = None
137        self.model_disp = None
138        self.Npts_fit = None
139        self.Npts_total = None
[5ce7f17]140        self.theory_qmin = None
[f0d720b]141        self.theory_qmax = None
142        self.theory_qmin_x = None
143        self.theory_qmax_x = None
144        self.btEditMask = None
145        self.btFit = None
146        self.sld_axes = None
147        self.multi_factor = None
[5ce7f17]148
[f0d720b]149        self.disp_cb_dict = {}
[5ce7f17]150
[f0d720b]151        #self.state = PageState(parent=parent)
152        ## dictionary containing list of models
153        self.model_list_box = {}
[5ce7f17]154
[f0d720b]155        ## Data member to store the dispersion object created
156        self._disp_obj_dict = {}
157        ## selected parameters to apply dispersion
158        self.disp_cb_dict = {}
159        ## smearer object
160        self.enable2D = False
161        self._has_magnetic = False
162        self.magnetic_on = False
163        self.is_mac = ON_MAC
164        self.formfactorbox = None
165        self.structurebox = None
166        self.categorybox = None
167        ##list of model parameters. each item must have same length
168        ## each item related to a given parameters
169        ##[cb state, name, value, "+/-", error of fit, min, max , units]
170        self.parameters = []
171        # non-fittable parameter whose value is astring
172        self.str_parameters = []
173        ## list of parameters to fit , must be like self.parameters
174        self.param_toFit = []
175        ## list of looking like parameters but with non fittable parameters info
176        self.fixed_param = []
177        ## list of looking like parameters but with  fittable parameters info
178        self.fittable_param = []
179        ##list of dispersion parameters
180        self.disp_list = []
181        self.disp_name = ""
[5ce7f17]182
[f0d720b]183        ## list of orientation parameters
184        self.orientation_params = []
185        self.orientation_params_disp = []
[5ce7f17]186#       Self.model should ALWAYS be None here.  It was set to none above in
[f0d720b]187#       this long init setting.  no obvious function call in between setting
[5ce7f17]188#       and this - commenting out on 4/8/2014 by PDB.  Remove once clear
[f0d720b]189#       it is pointless.
190#        if self.model != None:
191#            self.disp_list = self.model.getDispParamList()
192        self.temp_multi_functional = False
193        ##enable model 2D draw
194        self.enable2D = False
195        ## check that the fit range is correct to plot the model again
196        self.fitrange = True
197        ## Create memento to save the current state
198        self.state = PageState(parent=self.parent,
199                               model=self.model, data=self.data)
200        ## flag to determine if state has change
201        self.state_change = False
202        ## save customized array
[6ed67db]203        self.values = {}   # type: Dict[str, List[float, ...]]
204        self.weights = {}   # type: Dict[str, List[float, ...]]
[f0d720b]205        ## retrieve saved state
206        self.number_saved_state = 0
207        ## dictionary of saved state
208        self.saved_states = {}
209        ## Create context menu for page
210        self.popUpMenu = wx.Menu()
[5ce7f17]211
[6f16e25]212        wx_id = self._ids.next()
213        self._keep = wx.MenuItem(self.popUpMenu, wx_id, "Add bookmark",
[f0d720b]214                                 " Keep the panel status to recall it later")
215        self.popUpMenu.AppendItem(self._keep)
216        self._keep.Enable(False)
217        self._set_bookmark_flag(False)
218        self._set_save_flag(False)
[6f16e25]219        wx.EVT_MENU(self, wx_id, self.on_bookmark)
[f0d720b]220        self.popUpMenu.AppendSeparator()
[5ce7f17]221
[f0d720b]222        ## Default locations
223        self._default_save_location = os.getcwd()
224        ## save initial state on context menu
225        #self.onSave(event=None)
226        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
[5ce7f17]227
[f0d720b]228        # bind key event
229        self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
[5ce7f17]230
[f0d720b]231        ## create the basic structure of the panel with empty sizer
232        self.define_page_structure()
233        ## drawing Initial dispersion parameters sizer
234        self.set_dispers_sizer()
[5ce7f17]235
[f0d720b]236        ## layout
237        self.set_layout()
[5ce7f17]238
[f0d720b]239    def set_index_model(self, index):
240        """
241        Index related to this page
242        """
243        self.index_model = index
[5ce7f17]244
[f0d720b]245    def create_default_data(self):
246        """
247        Given the user selection, creates a 1D or 2D data
248        Only when the page is on theory mode.
249        """
250        if not hasattr(self, "model_view"):
251            return
252        toggle_mode_on = self.model_view.IsEnabled()
253        if toggle_mode_on:
254            if self.enable2D and not check_data_validity(self.data):
255                self._create_default_2d_data()
256            else:
257                if self.pointsbox.GetValue():
258                    self._create_log_1d_data()
259                else:
260                    self._create_default_1d_data()
[5ce7f17]261
[f0d720b]262            if self.model != None:
263                if not self.data.is_data:
264                    self._manager.page_finder[self.uid].set_fit_data(data=\
265                                                                [self.data])
266            self.on_smear_helper(update=True)
267            self.state.enable_smearer = self.enable_smearer.GetValue()
268            self.state.disable_smearer = self.disable_smearer.GetValue()
269            self.state.pinhole_smearer = self.pinhole_smearer.GetValue()
270            self.state.slit_smearer = self.slit_smearer.GetValue()
[5ce7f17]271
[f0d720b]272    def _create_default_1d_data(self):
273        """
274        Create default data for fitting perspective
275        Only when the page is on theory mode.
276        :warning: This data is never plotted.
[5ce7f17]277
[f0d720b]278        """
279        x = numpy.linspace(start=self.qmin_x, stop=self.qmax_x,
280                           num=self.npts_x, endpoint=True)
281        self.data = Data1D(x=x)
282        self.data.xaxis('\\rm{Q}', "A^{-1}")
283        self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
284        self.data.is_data = False
285        self.data.id = str(self.uid) + " data"
286        self.data.group_id = str(self.uid) + " Model1D"
[5ce7f17]287
[f0d720b]288    def _create_log_1d_data(self):
289        """
290        Create log-spaced data for fitting perspective
291        Only when the page is on theory mode.
292        :warning: This data is never plotted.
[5ce7f17]293
[f0d720b]294        """
295        if self.qmin_x >= 1.e-10:
296            qmin = numpy.log10(self.qmin_x)
297        else:
[5ce7f17]298            qmin = -10.
299
[f0d720b]300        if self.qmax_x <= 1.e10:
301            qmax = numpy.log10(self.qmax_x)
302        else:
[5ce7f17]303            qmax = 10.
304
[f0d720b]305        x = numpy.logspace(start=qmin, stop=qmax,
306                           num=self.npts_x, endpoint=True, base=10.0)
307        self.data = Data1D(x=x)
308        self.data.xaxis('\\rm{Q}', "A^{-1}")
309        self.data.yaxis('\\rm{Intensity}', "cm^{-1}")
310        self.data.is_data = False
311        self.data.id = str(self.uid) + " data"
312        self.data.group_id = str(self.uid) + " Model1D"
[5ce7f17]313
[f0d720b]314    def _create_default_2d_data(self):
315        """
316        Create 2D data by default
317        Only when the page is on theory mode.
318        :warning: This data is never plotted.
319        """
320        self.data = Data2D()
321        qmax = self.qmax_x / math.sqrt(2)
322        self.data.xaxis('\\rm{Q_{x}}', 'A^{-1}')
323        self.data.yaxis('\\rm{Q_{y}}', 'A^{-1}')
324        self.data.is_data = False
325        self.data.id = str(self.uid) + " data"
326        self.data.group_id = str(self.uid) + " Model2D"
327        ## Default values
328        self.data.detector.append(Detector())
329        index = len(self.data.detector) - 1
330        self.data.detector[index].distance = 8000   # mm
331        self.data.source.wavelength = 6             # A
332        self.data.detector[index].pixel_size.x = 5  # mm
333        self.data.detector[index].pixel_size.y = 5  # mm
334        self.data.detector[index].beam_center.x = qmax
335        self.data.detector[index].beam_center.y = qmax
336        xmax = qmax
337        xmin = -qmax
338        ymax = qmax
339        ymin = -qmax
340        qstep = self.npts_x
341
342        x = numpy.linspace(start=xmin, stop=xmax, num=qstep, endpoint=True)
343        y = numpy.linspace(start=ymin, stop=ymax, num=qstep, endpoint=True)
344        ## use data info instead
345        new_x = numpy.tile(x, (len(y), 1))
346        new_y = numpy.tile(y, (len(x), 1))
347        new_y = new_y.swapaxes(0, 1)
348        # all data reuire now in 1d array
349        qx_data = new_x.flatten()
350        qy_data = new_y.flatten()
351        q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
352        # set all True (standing for unmasked) as default
353        mask = numpy.ones(len(qx_data), dtype=bool)
354        # store x and y bin centers in q space
355        x_bins = x
356        y_bins = y
[5ce7f17]357
[f0d720b]358        self.data.source = Source()
359        self.data.data = numpy.ones(len(mask))
360        self.data.err_data = numpy.ones(len(mask))
361        self.data.qx_data = qx_data
362        self.data.qy_data = qy_data
363        self.data.q_data = q_data
364        self.data.mask = mask
365        self.data.x_bins = x_bins
366        self.data.y_bins = y_bins
367        # max and min taking account of the bin sizes
368        self.data.xmin = xmin
369        self.data.xmax = xmax
370        self.data.ymin = ymin
371        self.data.ymax = ymax
372
373    def on_set_focus(self, event):
374        """
375        On Set Focus, update guimanger and menu
376        """
377        if self._manager is not None:
378            wx.PostEvent(self._manager.parent, PanelOnFocusEvent(panel=self))
379            self.on_tap_focus()
[5ce7f17]380
[f0d720b]381    def on_tap_focus(self):
382        """
383        Update menu1 on cliking the page tap
384        """
385        if self._manager.menu1 != None:
386            chain_menu = self._manager.menu1.FindItemById(\
387                                                   self._manager.id_reset_flag)
388            chain_menu.Enable(self.batch_on)
389            sim_menu = self._manager.menu1.FindItemById(self._manager.id_simfit)
390            flag = self.data.is_data\
391                            and (self.model != None)
392            sim_menu.Enable(not self.batch_on and flag)
393            batch_menu = \
394                    self._manager.menu1.FindItemById(self._manager.id_batchfit)
395            batch_menu.Enable(self.batch_on and flag)
[5ce7f17]396
[f0d720b]397    def onContextMenu(self, event):
398        """
399        Retrieve the state selected state
400        """
401        pos = event.GetPosition()
402        pos = self.ScreenToClient(pos)
403        self.PopupMenu(self.popUpMenu, pos)
[5ce7f17]404
[f0d720b]405    def onUndo(self, event):
406        """
407        Cancel the previous action
408        """
409        event = PreviousStateEvent(page=self)
410        wx.PostEvent(self.parent, event)
[5ce7f17]411
[f0d720b]412    def onRedo(self, event):
413        """
414        Restore the previous action cancelled
415        """
416        event = NextStateEvent(page=self)
417        wx.PostEvent(self.parent, event)
[5ce7f17]418
[f0d720b]419    def define_page_structure(self):
420        """
421        Create empty sizer for a panel
422        """
423        self.vbox = wx.BoxSizer(wx.VERTICAL)
424        self.sizer0 = wx.BoxSizer(wx.VERTICAL)
425        self.sizer1 = wx.BoxSizer(wx.VERTICAL)
426        self.sizer2 = wx.BoxSizer(wx.VERTICAL)
427        self.sizer3 = wx.BoxSizer(wx.VERTICAL)
428        self.sizer4 = wx.BoxSizer(wx.VERTICAL)
429        self.sizer5 = wx.BoxSizer(wx.VERTICAL)
430        self.sizer6 = wx.BoxSizer(wx.VERTICAL)
[5ce7f17]431
[f0d720b]432        self.sizer0.SetMinSize((PANEL_WIDTH, -1))
433        self.sizer1.SetMinSize((PANEL_WIDTH, -1))
434        self.sizer2.SetMinSize((PANEL_WIDTH, -1))
435        self.sizer3.SetMinSize((PANEL_WIDTH, -1))
436        self.sizer4.SetMinSize((PANEL_WIDTH, -1))
437        self.sizer5.SetMinSize((PANEL_WIDTH, -1))
438        self.sizer6.SetMinSize((PANEL_WIDTH, -1))
[5ce7f17]439
[f0d720b]440        self.vbox.Add(self.sizer0)
441        self.vbox.Add(self.sizer1)
442        self.vbox.Add(self.sizer2)
443        self.vbox.Add(self.sizer3)
444        self.vbox.Add(self.sizer4)
445        self.vbox.Add(self.sizer5)
446        self.vbox.Add(self.sizer6)
[5ce7f17]447
[f0d720b]448    def set_layout(self):
449        """
450        layout
451        """
452        self.vbox.Layout()
453        self.vbox.Fit(self)
454        self.SetSizer(self.vbox)
455        self.Centre()
[5ce7f17]456
[f0d720b]457    def set_owner(self, owner):
458        """
459        set owner of fitpage
[5ce7f17]460
[f0d720b]461        :param owner: the class responsible of plotting
[5ce7f17]462
[f0d720b]463        """
464        self.event_owner = owner
465        self.state.event_owner = owner
[5ce7f17]466
[f0d720b]467    def get_state(self):
468        """
[5ce7f17]469        return the current page state
[f0d720b]470        """
471        return self.state
[5ce7f17]472
[f0d720b]473    def get_data(self):
474        """
475        return the current data
476        """
477        return self.data
[5ce7f17]478
[f0d720b]479    def get_data_list(self):
480        """
481        return the current data
482        """
483        return self.data_list
[5ce7f17]484
[f0d720b]485    def set_manager(self, manager):
486        """
487        set panel manager
[5ce7f17]488
[f0d720b]489        :param manager: instance of plugin fitting
[5ce7f17]490
[f0d720b]491        """
492        self._manager = manager
493        self.state.manager = manager
[5ce7f17]494
[f0d720b]495    def populate_box(self, model_dict):
496        """
497        Store list of model
[5ce7f17]498
[f0d720b]499        :param model_dict: dictionary containing list of models
[5ce7f17]500
[f0d720b]501        """
502        self.model_list_box = model_dict
503        self.state.model_list_box = self.model_list_box
504        self.initialize_combox()
[5ce7f17]505
[f0d720b]506    def set_model_dictionary(self, model_dict):
507        """
508        Store a dictionary linking model name -> model object
509
510        :param model_dict: dictionary containing list of models
511        """
512        self.model_dict = model_dict
513
514    def initialize_combox(self):
515        """
516        put default value in the combobox
[5ce7f17]517        """
[f0d720b]518        ## fill combox box
519        if self.model_list_box is None:
520            return
521        if len(self.model_list_box) > 0:
522            self._populate_box(self.structurebox,
[5ce7f17]523                               self.model_list_box["Structure Factors"])
[f0d720b]524            self.structurebox.Insert("None", 0, None)
525            self.structurebox.SetSelection(0)
526            self.structurebox.Hide()
527            self.text2.Hide()
528            self.structurebox.Disable()
529            self.text2.Disable()
[5ce7f17]530
[f0d720b]531            if self.model.__class__ in self.model_list_box["P(Q)*S(Q)"]:
532                self.structurebox.Show()
533                self.text2.Show()
534                self.structurebox.Enable()
535                self.text2.Enable()
536
537    def set_dispers_sizer(self):
538        """
539        fill sizer containing dispersity info
540        """
[6f16e25]541        #print "==== entering set_dispers_sizer ==="
[f0d720b]542        self.sizer4.Clear(True)
543        name = "Polydispersity and Orientational Distribution"
[6f16e25]544        box_description = wx.StaticBox(self, wx.ID_ANY, name)
[f0d720b]545        box_description.SetForegroundColour(wx.BLUE)
546        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
547        #----------------------------------------------------
[6f16e25]548        self.disable_disp = wx.RadioButton(self, wx.ID_ANY, 'Off', (10, 10),
[5ce7f17]549                                           style=wx.RB_GROUP)
[6f16e25]550        self.enable_disp = wx.RadioButton(self, wx.ID_ANY, 'On', (10, 30))
[f0d720b]551        # best size for MAC and PC
552        if ON_MAC:
553            size_q = (30, 20)
554        else:
555            size_q = (20, 15)
[6f16e25]556        self.disp_help_bt = wx.Button(self, self.ID_DISPERSER_HELP, '?',
[f0d720b]557                                      style=wx.BU_EXACTFIT,
558                                      size=size_q)
[5ce7f17]559        self.disp_help_bt.Bind(wx.EVT_BUTTON, self.on_pd_help_clicked,
560                               id=self.disp_help_bt.GetId())
[a0373d5]561        self.disp_help_bt.SetToolTipString("Help for polydispersion.")
[5ce7f17]562
[f0d720b]563        self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
[5ce7f17]564                  id=self.disable_disp.GetId())
[f0d720b]565        self.Bind(wx.EVT_RADIOBUTTON, self._set_dipers_Param,
[5ce7f17]566                  id=self.enable_disp.GetId())
[f0d720b]567        #MAC needs SetValue
568        self.disable_disp.SetValue(True)
569        sizer_dispersion = wx.BoxSizer(wx.HORIZONTAL)
570        sizer_dispersion.Add((20, 20))
571        name = ""  # Polydispersity and \nOrientational Distribution "
[6f16e25]572        sizer_dispersion.Add(wx.StaticText(self, wx.ID_ANY, name))
[f0d720b]573        sizer_dispersion.Add(self.enable_disp)
574        sizer_dispersion.Add((20, 20))
575        sizer_dispersion.Add(self.disable_disp)
576        sizer_dispersion.Add((25, 20))
577        sizer_dispersion.Add(self.disp_help_bt)
[5ce7f17]578
[f0d720b]579        ## fill a sizer for dispersion
580        boxsizer1.Add(sizer_dispersion, 0,
[5ce7f17]581                      wx.TOP|wx.BOTTOM|wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE,
582                      border=5)
[f0d720b]583        self.sizer4_4 = wx.GridBagSizer(6, 5)
584
585        boxsizer1.Add(self.sizer4_4)
586        #-----------------------------------------------------
587        self.sizer4.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
588        self.sizer4_4.Layout()
589        self.sizer4.Layout()
590        self.Layout()
[5ce7f17]591
[f0d720b]592        self.Refresh()
593        ## saving the state of enable dispersity button
594        self.state.enable_disp = self.enable_disp.GetValue()
595        self.state.disable_disp = self.disable_disp.GetValue()
596        self.SetupScrolling()
[5ce7f17]597
[f0d720b]598    def onResetModel(self, event):
599        """
600        Reset model state
601        """
602        menu = event.GetEventObject()
603        ## post help message for the selected model
604        msg = menu.GetHelpString(event.GetId())
605        msg += " reloaded"
606        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
607        self.Show(False)
608        name = menu.GetLabel(event.GetId())
609        self._on_select_model_helper()
610        if self.model != None:
611            self.m_name = self.model.name
612        if name in self.saved_states.keys():
613            previous_state = self.saved_states[name]
614            ## reset state of checkbox,textcrtl  and  regular parameters value
[5ce7f17]615
[f0d720b]616            self.reset_page(previous_state)
617        self.state.m_name = self.m_name
618        self.Show(True)
[5ce7f17]619
[f0d720b]620    def on_preview(self, event):
621        """
622        Report the current fit results
623        """
624        # Get plot image from plotpanel
625        images, canvases = self.get_images()
626        # get the report dialog
627        self.state.report(images, canvases)
[5ce7f17]628
[f0d720b]629    def on_save(self, event):
630        """
631        Save the current state into file
632        """
633        self.save_current_state()
634        new_state = self.state.clone()
635        # Ask the user the location of the file to write to.
636        path = None
637        if self.parent != None:
638            self._default_save_location = \
639                        self._manager.parent._default_save_location
640        dlg = wx.FileDialog(self, "Choose a file", self._default_save_location,
[5ce7f17]641                            self.window_caption, "*.fitv", wx.SAVE)
[f0d720b]642
643        if dlg.ShowModal() == wx.ID_OK:
644            path = dlg.GetPath()
645            self._default_save_location = os.path.dirname(path)
[5ce7f17]646            self._manager.parent._default_save_location = \
647                                self._default_save_location
[f0d720b]648        else:
649            return None
650        # MAC always needs the extension for saving
651        extens = ".fitv"
652        # Make sure the ext included in the file name
653        fName = os.path.splitext(path)[0] + extens
654        #the manager write the state into file
655        self._manager.save_fit_state(filepath=fName, fitstate=new_state)
656        return new_state
[5ce7f17]657
[f0d720b]658    def on_copy(self, event):
659        """
660        Copy Parameter values to the clipboad
661        """
662        if event != None:
663            event.Skip()
664        # It seems MAC needs wxCallAfter
665        if event.GetId() == GUIFRAME_ID.COPYEX_ID:
666            print "copy excel"
667            wx.CallAfter(self.get_copy_excel)
668        elif event.GetId() == GUIFRAME_ID.COPYLAT_ID:
669            print "copy latex"
670            wx.CallAfter(self.get_copy_latex)
671        else:
672            wx.CallAfter(self.get_copy)
673
674    def on_paste(self, event):
675        """
676        Paste Parameter values to the panel if possible
677        """
678        #if event != None:
679        #    event.Skip()
680        # It seems MAC needs wxCallAfter for the setvalues
681        # for multiple textctrl items, otherwise it tends to crash once a while
682        wx.CallAfter(self.get_paste)
683        # messages depending on the flag
684        #self._copy_info(True)
[5ce7f17]685
[f0d720b]686    def _copy_info(self, flag):
687        """
688        Send event dpemding on flag
[5ce7f17]689
[f0d720b]690        : Param flag: flag that distinguish event
691        """
692        # messages depending on the flag
693        if flag == None:
694            msg = " Parameter values are copied to the clipboard..."
695            infor = 'warning'
696        elif flag:
697            msg = " Parameter values are pasted from the clipboard..."
698            infor = "warning"
699        else:
700            msg = "Error occurred: "
701            msg += "No valid parameter values to paste from the clipboard..."
702            infor = "warning"
703        # inform msg to wx
704        wx.PostEvent(self._manager.parent,
[5ce7f17]705                     StatusEvent(status=msg, info=infor))
706
[f0d720b]707    def _get_time_stamp(self):
708        """
709        return time and date stings
710        """
711        # date and time
712        year, month, day, hour, minute, second, _, _, _ = time.localtime()
713        current_time = str(hour) + ":" + str(minute) + ":" + str(second)
714        current_date = str(month) + "/" + str(day) + "/" + str(year)
715        return current_time, current_date
[5ce7f17]716
[f0d720b]717    def on_bookmark(self, event):
718        """
719        save history of the data and model
720        """
721        if self.model == None:
722            msg = "Can not bookmark; Please select Data and Model first..."
723            wx.MessageBox(msg, 'Info')
724            return
725        self.save_current_state()
726        new_state = self.state.clone()
727        ##Add model state on context menu
728        self.number_saved_state += 1
729        current_time, current_date = self._get_time_stamp()
730        #name= self.model.name+"[%g]"%self.number_saved_state
731        name = "Fitting: %g]" % self.number_saved_state
732        name += self.model.__class__.__name__
733        name += "bookmarked at %s on %s" % (current_time, current_date)
734        self.saved_states[name] = new_state
[5ce7f17]735
[f0d720b]736        ## Add item in the context menu
737        msg = "Model saved at %s on %s" % (current_time, current_date)
738        ## post help message for the selected model
739        msg += " Saved! right click on this page to retrieve this model"
740        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
[5ce7f17]741
[6f16e25]742        self.popUpMenu.Append(self.ID_BOOKMARK, name, str(msg))
743        wx.EVT_MENU(self, self.ID_BOOKMARK, self.onResetModel)
[f0d720b]744        wx.PostEvent(self._manager.parent,
745                     AppendBookmarkEvent(title=name,
746                                         hint=str(msg),
747                                         handler=self._back_to_bookmark))
[5ce7f17]748
[f0d720b]749    def _back_to_bookmark(self, event):
750        """
751        Back to bookmark
752        """
753        self._manager.on_perspective(event)
754        self.onResetModel(event)
755        self._draw_model()
[5ce7f17]756
[f0d720b]757    def onSetFocus(self, evt):
758        """
759        highlight the current textcrtl and hide the error text control shown
760        after fitting
761        """
762        return
[5ce7f17]763
[f0d720b]764    def read_file(self, path):
765        """
766        Read two columns file
[5ce7f17]767
[f0d720b]768        :param path: the path to the file to read
[5ce7f17]769
[f0d720b]770        """
771        try:
772            if path == None:
[5ce7f17]773                status = " Selected Distribution was not loaded: %s" % path
[f0d720b]774                wx.PostEvent(self._manager.parent,
[5ce7f17]775                             StatusEvent(status=status))
[f0d720b]776                return None, None
777            input_f = open(path, 'r')
778            buff = input_f.read()
779            lines = buff.split('\n')
780            input_f.close()
781            angles = []
782            weights = []
783            for line in lines:
784                toks = line.split()
785                try:
786                    angle = float(toks[0])
787                    weight = float(toks[1])
788                    angles.append(angle)
789                    weights.append(weight)
[7673ecd]790                except Exception:
[f0d720b]791                    # Skip non-data lines
[7673ecd]792                    logging.error(traceback.format_exc())
[f0d720b]793            return numpy.array(angles), numpy.array(weights)
794        except:
795            raise
796
797    def createMemento(self):
798        """
799        return the current state of the page
800        """
801        return self.state.clone()
[5ce7f17]802
[f0d720b]803    def save_current_state(self):
804        """
805        Store current state
806        """
807        ## save model option
808        if self.model != None:
809            self.disp_list = self.model.getDispParamList()
810            self.state.disp_list = copy.deepcopy(self.disp_list)
811            self.state.model = self.model.clone()
[5ce7f17]812
[f0d720b]813            #model combobox: complex code because of mac's silent error
814            if self.structurebox != None:
815                if self.structurebox.IsShown():
816                    self.state.structurecombobox = 'None'
817                    s_select = self.structurebox.GetSelection()
818                    if s_select > 0:
819                        self.state.structurecombobox = self.structurebox.\
820                        GetString(s_select)
821            if self.formfactorbox != None:
822                f_select = self.formfactorbox.GetSelection()
823                if f_select > 0:
824                    self.state.formfactorcombobox = self.formfactorbox.\
825                    GetString(f_select)
826        if self.categorybox != None:
827            cb_select = self.categorybox.GetSelection()
828            if cb_select > 0:
829                self.state.categorycombobox = self.categorybox.\
830                GetString(cb_select)
[5ce7f17]831
[f0d720b]832        self.state.enable2D = copy.deepcopy(self.enable2D)
833        self.state.values = copy.deepcopy(self.values)
834        self.state.weights = copy.deepcopy(self.weights)
835        ## save data
836        self.state.data = copy.deepcopy(self.data)
837        self.state.qmax_x = self.qmax_x
838        self.state.qmin_x = self.qmin_x
839        self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
840        self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
841        self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
842        self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
843        self.state.dq_l = self.dq_l
844        self.state.dq_r = self.dq_r
845        if hasattr(self, "enable_disp"):
846            self.state.enable_disp = self.enable_disp.GetValue()
847            self.state.disable_disp = self.disable_disp.GetValue()
[5ce7f17]848
[f0d720b]849        self.state.smearer = copy.deepcopy(self.current_smearer)
850        if hasattr(self, "enable_smearer"):
851            self.state.enable_smearer = \
852                                copy.deepcopy(self.enable_smearer.GetValue())
853            self.state.disable_smearer = \
854                                copy.deepcopy(self.disable_smearer.GetValue())
855
856        self.state.pinhole_smearer = \
857                                copy.deepcopy(self.pinhole_smearer.GetValue())
858        self.state.dx_max = copy.deepcopy(self.dx_max)
859        self.state.dx_min = copy.deepcopy(self.dx_min)
860        self.state.dxl = copy.deepcopy(self.dxl)
861        self.state.dxw = copy.deepcopy(self.dxw)
862        self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())
[5ce7f17]863
[f0d720b]864        if len(self._disp_obj_dict) > 0:
865            for k, v in self._disp_obj_dict.iteritems():
[6c382da]866                self.state._disp_obj_dict[k] = v.type
[f0d720b]867
868            self.state.values = copy.deepcopy(self.values)
869            self.state.weights = copy.deepcopy(self.weights)
870        ## save plotting range
871        self._save_plotting_range()
[5ce7f17]872
[f0d720b]873        self.state.orientation_params = []
874        self.state.orientation_params_disp = []
875        self.state.parameters = []
876        self.state.fittable_param = []
877        self.state.fixed_param = []
878        self.state.str_parameters = []
879
880        ## save checkbutton state and txtcrtl values
881        self._copy_parameters_state(self.str_parameters,
882                                    self.state.str_parameters)
883        self._copy_parameters_state(self.orientation_params,
884                                     self.state.orientation_params)
885        self._copy_parameters_state(self.orientation_params_disp,
[2abe6bf]886                                    self.state.orientation_params_disp)
[5ce7f17]887
[f0d720b]888        self._copy_parameters_state(self.parameters, self.state.parameters)
889        self._copy_parameters_state(self.fittable_param,
[2abe6bf]890                                    self.state.fittable_param)
[f0d720b]891        self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
892        #save chisqr
893        self.state.tcChi = self.tcChi.GetValue()
[5ce7f17]894
[f0d720b]895    def save_current_state_fit(self):
896        """
897        Store current state for fit_page
898        """
899        ## save model option
900        if self.model != None:
901            self.disp_list = self.model.getDispParamList()
902            self.state.disp_list = copy.deepcopy(self.disp_list)
903            self.state.model = self.model.clone()
[5ce7f17]904
[f0d720b]905        self.state.enable2D = copy.deepcopy(self.enable2D)
906        self.state.values = copy.deepcopy(self.values)
907        self.state.weights = copy.deepcopy(self.weights)
908        ## save data
909        self.state.data = copy.deepcopy(self.data)
[5ce7f17]910
[f0d720b]911        if hasattr(self, "enable_disp"):
912            self.state.enable_disp = self.enable_disp.GetValue()
913            self.state.disable_disp = self.disable_disp.GetValue()
[5ce7f17]914
[f0d720b]915        self.state.smearer = copy.deepcopy(self.current_smearer)
916        if hasattr(self, "enable_smearer"):
917            self.state.enable_smearer = \
918                                copy.deepcopy(self.enable_smearer.GetValue())
919            self.state.disable_smearer = \
920                                copy.deepcopy(self.disable_smearer.GetValue())
[5ce7f17]921
[f0d720b]922        self.state.pinhole_smearer = \
923                                copy.deepcopy(self.pinhole_smearer.GetValue())
924        self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue())
925        self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue())
926        self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue())
927        self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue())
928        self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue())
929        if hasattr(self, "disp_box") and self.disp_box != None:
930            self.state.disp_box = self.disp_box.GetCurrentSelection()
931
932            if len(self.disp_cb_dict) > 0:
933                for k, v in self.disp_cb_dict.iteritems():
934                    if v == None:
935                        self.state.disp_cb_dict[k] = v
936                    else:
937                        try:
938                            self.state.disp_cb_dict[k] = v.GetValue()
939                        except:
940                            self.state.disp_cb_dict[k] = None
941            if len(self._disp_obj_dict) > 0:
942                for k, v in self._disp_obj_dict.iteritems():
[6c382da]943                    self.state._disp_obj_dict[k] = v.type
[5ce7f17]944
[f0d720b]945            self.state.values = copy.deepcopy(self.values)
946            self.state.weights = copy.deepcopy(self.weights)
[5ce7f17]947
[f0d720b]948        ## save plotting range
949        self._save_plotting_range()
[5ce7f17]950
[f0d720b]951        ## save checkbutton state and txtcrtl values
952        self._copy_parameters_state(self.orientation_params,
[5ce7f17]953                                    self.state.orientation_params)
[f0d720b]954        self._copy_parameters_state(self.orientation_params_disp,
[5ce7f17]955                                    self.state.orientation_params_disp)
[f0d720b]956        self._copy_parameters_state(self.parameters, self.state.parameters)
957        self._copy_parameters_state(self.fittable_param,
[5ce7f17]958                                    self.state.fittable_param)
[f0d720b]959        self._copy_parameters_state(self.fixed_param, self.state.fixed_param)
[5ce7f17]960
[f0d720b]961    def check_invalid_panel(self):
962        """
963        check if the user can already perform some action with this panel
964        """
965        if self.data is None:
966            self.disable_smearer.SetValue(True)
967            self.disable_disp.SetValue(True)
968            msg = "Please load Data and select Model to start..."
969            wx.MessageBox(msg, 'Info')
970            return  True
[5ce7f17]971
[f0d720b]972    def set_model_state(self, state):
973        """
974        reset page given a model state
975        """
976        self.disp_cb_dict = state.disp_cb_dict
977        self.disp_list = state.disp_list
[5ce7f17]978
[f0d720b]979        ## fill model combobox
980        self._show_combox_helper()
981        #select the current model
982        try:
983            # to support older version
984            category_pos = int(state.categorycombobox)
985        except:
986            category_pos = 0
987            for ind_cat in range(self.categorybox.GetCount()):
[5ce7f17]988                if self.categorycombobox.GetString(ind_cat) == \
[f0d720b]989                                        state.categorycombobox:
990                    category_pos = int(ind_cat)
991                    break
[5ce7f17]992
[f0d720b]993        self.categorybox.Select(category_pos)
994        try:
995            # to support older version
996            formfactor_pos = int(state.formfactorcombobox)
997        except:
998            formfactor_pos = 0
999            for ind_form in range(self.formfactorbox.GetCount()):
1000                if self.formfactorbox.GetString(ind_form) == \
1001                                        state.formfactorcombobox:
1002                    formfactor_pos = int(ind_form)
1003                    break
[5ce7f17]1004
[f0d720b]1005        self.formfactorbox.Select(formfactor_pos)
[5ce7f17]1006
[f0d720b]1007        try:
1008            # to support older version
1009            structfactor_pos = int(state.structurecombobox)
1010        except:
1011            structfactor_pos = 0
1012            for ind_struct in range(self.structurebox.GetCount()):
1013                if self.structurebox.GetString(ind_struct) == \
1014                                        state.structurecombobox:
1015                    structfactor_pos = int(ind_struct)
1016                    break
[5ce7f17]1017
[f0d720b]1018        self.structurebox.SetSelection(structfactor_pos)
[5ce7f17]1019
[f0d720b]1020        if state.multi_factor != None:
1021            self.multifactorbox.SetSelection(state.multi_factor)
[5ce7f17]1022
[f0d720b]1023        ## reset state of checkbox,textcrtl  and  regular parameters value
1024        self._reset_parameters_state(self.orientation_params_disp,
1025                                     state.orientation_params_disp)
1026        self._reset_parameters_state(self.orientation_params,
1027                                     state.orientation_params)
1028        self._reset_parameters_state(self.str_parameters,
1029                                     state.str_parameters)
1030        self._reset_parameters_state(self.parameters, state.parameters)
1031        ## display dispersion info layer
1032        self.enable_disp.SetValue(state.enable_disp)
1033        self.disable_disp.SetValue(state.disable_disp)
[5ce7f17]1034
[f0d720b]1035        if hasattr(self, "disp_box") and self.disp_box != None:
1036            self.disp_box.SetSelection(state.disp_box)
1037            n = self.disp_box.GetCurrentSelection()
1038            dispersity = self.disp_box.GetClientData(n)
1039            name = dispersity.__name__
1040
1041            self._set_dipers_Param(event=None)
[5ce7f17]1042
[f0d720b]1043            if name == "ArrayDispersion":
[5ce7f17]1044
[f0d720b]1045                for item in self.disp_cb_dict.keys():
[5ce7f17]1046
[f0d720b]1047                    if hasattr(self.disp_cb_dict[item], "SetValue"):
1048                        self.disp_cb_dict[item].SetValue(\
1049                                                    state.disp_cb_dict[item])
1050                        # Create the dispersion objects
[a0373d5]1051                        disp_model = POLYDISPERSITY_MODELS['array']()
[f0d720b]1052                        if hasattr(state, "values") and \
1053                                 self.disp_cb_dict[item].GetValue() == True:
1054                            if len(state.values) > 0:
1055                                self.values = state.values
1056                                self.weights = state.weights
1057                                disp_model.set_weights(self.values,
1058                                                       state.weights)
1059                            else:
1060                                self._reset_dispersity()
[5ce7f17]1061
[f0d720b]1062                        self._disp_obj_dict[item] = disp_model
1063                        # Set the new model as the dispersion object
1064                        #for the selected parameter
1065                        self.model.set_dispersion(item, disp_model)
[5ce7f17]1066
[f0d720b]1067                        self.model._persistency_dict[item] = \
1068                                                [state.values, state.weights]
[5ce7f17]1069
[f0d720b]1070            else:
1071                keys = self.model.getParamList()
1072                for item in keys:
1073                    if item in self.disp_list and \
1074                        not item in self.model.details:
1075                        self.model.details[item] = ["", None, None]
1076                self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
1077                self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
1078        ## smearing info  restore
1079        if hasattr(self, "enable_smearer"):
1080            ## set smearing value whether or not the data
1081            #contain the smearing info
1082            self.enable_smearer.SetValue(state.enable_smearer)
1083            self.disable_smearer.SetValue(state.disable_smearer)
1084            self.onSmear(event=None)
1085        self.pinhole_smearer.SetValue(state.pinhole_smearer)
1086        self.slit_smearer.SetValue(state.slit_smearer)
[5ce7f17]1087
[f0d720b]1088        self.dI_noweight.SetValue(state.dI_noweight)
1089        self.dI_didata.SetValue(state.dI_didata)
1090        self.dI_sqrdata.SetValue(state.dI_sqrdata)
1091        self.dI_idata.SetValue(state.dI_idata)
[5ce7f17]1092
[f0d720b]1093        ## we have two more options for smearing
1094        if self.pinhole_smearer.GetValue():
1095            self.onPinholeSmear(event=None)
1096        elif self.slit_smearer.GetValue():
1097            self.onSlitSmear(event=None)
[5ce7f17]1098
[f0d720b]1099        ## reset state of checkbox,textcrtl  and dispersity parameters value
1100        self._reset_parameters_state(self.fittable_param, state.fittable_param)
1101        self._reset_parameters_state(self.fixed_param, state.fixed_param)
[5ce7f17]1102
[f0d720b]1103        ## draw the model with previous parameters value
1104        self._onparamEnter_helper()
1105        self.select_param(event=None)
1106        #Save state_fit
1107        self.save_current_state_fit()
1108        self._lay_out()
1109        self.Refresh()
[5ce7f17]1110
[f0d720b]1111    def reset_page_helper(self, state):
1112        """
1113        Use page_state and change the state of existing page
[5ce7f17]1114
[f0d720b]1115        :precondition: the page is already drawn or created
[5ce7f17]1116
[f0d720b]1117        :postcondition: the state of the underlying data change as well as the
1118            state of the graphic interface
1119        """
1120        if state == None:
1121            return
1122        # set data, etc. from the state
1123        # reset page between theory and fitting from bookmarking
1124        data = state.data
1125
1126        if data == None:
1127            data_min = state.qmin
1128            data_max = state.qmax
1129            self.qmin_x = data_min
1130            self.qmax_x = data_max
1131            self.qmin.SetValue(str(data_min))
1132            self.qmax.SetValue(str(data_max))
1133
1134            self.state.data = data
1135            self.state.qmin = self.qmin_x
1136            self.state.qmax = self.qmax_x
1137        else:
1138            self.set_data(data)
[5ce7f17]1139
[f0d720b]1140        self.enable2D = state.enable2D
1141        try:
1142            self.magnetic_on = state.magnetic_on
1143        except:
1144            # Backward compatibility (for older state files)
1145            self.magnetic_on = False
1146
1147        self.disp_cb_dict = state.disp_cb_dict
1148        self.disp_list = state.disp_list
[5ce7f17]1149
[f0d720b]1150        ## fill model combobox
1151        self._show_combox_helper()
1152        #select the current model
1153        try:
1154            # to support older version
1155            category_pos = int(state.categorycombobox)
1156        except:
1157            category_pos = 0
1158            for ind_cat in range(self.categorybox.GetCount()):
1159                if self.categorybox.GetString(ind_cat) == \
1160                                        state.categorycombobox:
1161                    category_pos = int(ind_cat)
1162                    break
[5ce7f17]1163
[f0d720b]1164        self.categorybox.Select(category_pos)
1165        self._show_combox(None)
1166        try:
1167            # to support older version
1168            formfactor_pos = int(state.formfactorcombobox)
1169        except:
1170            formfactor_pos = 0
1171            for ind_form in range(self.formfactorbox.GetCount()):
1172                if self.formfactorbox.GetString(ind_form) == \
1173                                                    (state.formfactorcombobox):
1174                    formfactor_pos = int(ind_form)
1175                    break
[5ce7f17]1176
[f0d720b]1177        self.formfactorbox.Select(formfactor_pos)
[5ce7f17]1178
[f0d720b]1179        try:
1180            # to support older version
1181            structfactor_pos = int(state.structurecombobox)
1182        except:
1183            structfactor_pos = 0
1184            for ind_struct in range(self.structurebox.GetCount()):
1185                if self.structurebox.GetString(ind_struct) == \
1186                                                    (state.structurecombobox):
1187                    structfactor_pos = int(ind_struct)
1188                    break
[5ce7f17]1189
[f0d720b]1190        self.structurebox.SetSelection(structfactor_pos)
1191
1192        if state.multi_factor != None:
1193            self.multifactorbox.SetSelection(state.multi_factor)
1194
[acf8e4a5]1195        #draw the panel according to the new model parameter
[f0d720b]1196        self._on_select_model(event=None)
[5ce7f17]1197
[f0d720b]1198        # take care of 2D button
1199        if data == None and self.model_view.IsEnabled():
1200            if self.enable2D:
1201                self.model_view.SetLabel("2D Mode")
1202            else:
1203                self.model_view.SetLabel("1D Mode")
[bac3988]1204
[f0d720b]1205        ## reset state of checkbox,textcrtl  and  regular parameters value
1206        self._reset_parameters_state(self.orientation_params_disp,
1207                                     state.orientation_params_disp)
1208        self._reset_parameters_state(self.orientation_params,
1209                                     state.orientation_params)
1210        self._reset_parameters_state(self.str_parameters,
1211                                     state.str_parameters)
1212        self._reset_parameters_state(self.parameters, state.parameters)
1213        ## display dispersion info layer
1214        self.enable_disp.SetValue(state.enable_disp)
1215        self.disable_disp.SetValue(state.disable_disp)
1216        # If the polydispersion is ON
1217        if state.enable_disp:
1218            # reset dispersion according the state
1219            self._set_dipers_Param(event=None)
1220            self._reset_page_disp_helper(state)
1221        ##plotting range restore
1222        self._reset_plotting_range(state)
1223        ## smearing info  restore
1224        if hasattr(self, "enable_smearer"):
1225            ## set smearing value whether or not the data
1226            #contain the smearing info
1227            self.enable_smearer.SetValue(state.enable_smearer)
1228            self.disable_smearer.SetValue(state.disable_smearer)
1229            self.onSmear(event=None)
1230        self.pinhole_smearer.SetValue(state.pinhole_smearer)
1231        self.slit_smearer.SetValue(state.slit_smearer)
1232        try:
1233            self.dI_noweight.SetValue(state.dI_noweight)
1234            self.dI_didata.SetValue(state.dI_didata)
1235            self.dI_sqrdata.SetValue(state.dI_sqrdata)
1236            self.dI_idata.SetValue(state.dI_idata)
1237        except:
1238            # to support older state file formats
1239            self.dI_noweight.SetValue(False)
1240            self.dI_didata.SetValue(True)
1241            self.dI_sqrdata.SetValue(False)
1242            self.dI_idata.SetValue(False)
[5ce7f17]1243
[f0d720b]1244        ## we have two more options for smearing
1245        if self.pinhole_smearer.GetValue():
1246            self.dx_min = state.dx_min
1247            self.dx_max = state.dx_max
1248            if self.dx_min != None:
1249                self.smear_pinhole_min.SetValue(str(self.dx_min))
1250            if self.dx_max != None:
1251                self.smear_pinhole_max.SetValue(str(self.dx_max))
1252            self.onPinholeSmear(event=None)
1253        elif self.slit_smearer.GetValue():
1254            self.dxl = state.dxl
1255            self.dxw = state.dxw
1256            if self.dxl != None:
1257                self.smear_slit_height.SetValue(str(self.dxl))
1258            if self.dxw != None:
[5ce7f17]1259                self.smear_slit_width.SetValue(str(self.dxw))
[f0d720b]1260            else:
[5ce7f17]1261                self.smear_slit_width.SetValue('')
[f0d720b]1262            self.onSlitSmear(event=None)
[5ce7f17]1263
[f0d720b]1264        ## reset state of checkbox,textcrtl  and dispersity parameters value
1265        self._reset_parameters_state(self.fittable_param, state.fittable_param)
1266        self._reset_parameters_state(self.fixed_param, state.fixed_param)
[5ce7f17]1267
[f0d720b]1268        ## draw the model with previous parameters value
1269        self._onparamEnter_helper()
1270        #reset the value of chisqr when not consistent with the value computed
1271        self.tcChi.SetValue(str(self.state.tcChi))
1272        ## reset context menu items
1273        self._reset_context_menu()
[5ce7f17]1274
[f0d720b]1275        ## set the value of the current state to the state given as parameter
1276        self.state = state.clone()
1277        self.state.m_name = self.m_name
[5ce7f17]1278
[f0d720b]1279    def _reset_page_disp_helper(self, state):
1280        """
1281        Help to rest page for dispersions
1282        """
1283        keys = self.model.getParamList()
1284        for item in keys:
1285            if item in self.disp_list and \
1286                not item in self.model.details:
1287                self.model.details[item] = ["", None, None]
1288        #for k,v in self.state.disp_cb_dict.iteritems():
1289        self.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
1290        self.state.disp_cb_dict = copy.deepcopy(state.disp_cb_dict)
1291        self.values = copy.deepcopy(state.values)
1292        self.weights = copy.deepcopy(state.weights)
[5ce7f17]1293
[6c382da]1294        for key, disp_type in state._disp_obj_dict.iteritems():
1295            #disp_model = disp
1296            disp_model = POLYDISPERSITY_MODELS[disp_type]()
[f0d720b]1297            self._disp_obj_dict[key] = disp_model
1298            param_name = key.split('.')[0]
1299            # Try to set dispersion only when available
1300            # for eg., pass the orient. angles for 1D Cal
1301            try:
1302                self.model.set_dispersion(param_name, disp_model)
1303                self.model._persistency_dict[key] = \
1304                                 [state.values, state.weights]
[7673ecd]1305            except Exception:
1306                logging.error(traceback.format_exc())
[f0d720b]1307            selection = self._find_polyfunc_selection(disp_model)
1308            for list in self.fittable_param:
1309                if list[1] == key and list[7] != None:
1310                    list[7].SetSelection(selection)
1311                    # For the array disp_model, set the values and weights
1312                    if selection == 1:
1313                        disp_model.set_weights(self.values[key],
1314                                               self.weights[key])
1315                        try:
1316                            # Diables all fittable params for array
1317                            list[0].SetValue(False)
1318                            list[0].Disable()
1319                            list[2].Disable()
1320                            list[5].Disable()
1321                            list[6].Disable()
[7673ecd]1322                        except Exception:
1323                            logging.error(traceback.format_exc())
[f0d720b]1324            # For array, disable all fixed params
1325            if selection == 1:
1326                for item in self.fixed_param:
1327                    if item[1].split(".")[0] == key.split(".")[0]:
1328                        # try it and pass it for the orientation for 1D
1329                        try:
1330                            item[2].Disable()
[7673ecd]1331                        except Exception:
1332                            logging.error(traceback.format_exc())
[5ce7f17]1333
[f0d720b]1334    def _selectDlg(self):
1335        """
[5ce7f17]1336        open a dialog file to selected the customized dispersity
[f0d720b]1337        """
1338        if self.parent != None:
1339            self._default_save_location = \
1340                        self._manager.parent.get_save_location()
1341        dlg = wx.FileDialog(self, "Choose a weight file",
[5ce7f17]1342                            self._default_save_location, "",
1343                            "*.*", wx.OPEN)
[f0d720b]1344        path = None
1345        if dlg.ShowModal() == wx.ID_OK:
1346            path = dlg.GetPath()
1347        dlg.Destroy()
1348        return path
1349
1350    def _reset_context_menu(self):
1351        """
1352        reset the context menu
1353        """
[6f16e25]1354        ids = iter(self._id_pool)  # Reusing ids for context menu
[f0d720b]1355        for name, _ in self.state.saved_states.iteritems():
1356            self.number_saved_state += 1
1357            ## Add item in the context menu
[6f16e25]1358            wx_id = ids.next()
[f0d720b]1359            msg = 'Save model and state %g' % self.number_saved_state
[6f16e25]1360            self.popUpMenu.Append(wx_id, name, msg)
1361            wx.EVT_MENU(self, wx_id, self.onResetModel)
[5ce7f17]1362
[f0d720b]1363    def _reset_plotting_range(self, state):
1364        """
1365        Reset the plotting range to a given state
1366        """
1367        self.qmin.SetValue(str(state.qmin))
1368        self.qmax.SetValue(str(state.qmax))
1369
1370    def _save_typeOfmodel(self):
1371        """
1372        save radiobutton containing the type model that can be selected
1373        """
1374        #self.state.shape_rbutton = self.shape_rbutton.GetValue()
1375        #self.state.shape_indep_rbutton = self.shape_indep_rbutton.GetValue()
1376        #self.state.struct_rbutton = self.struct_rbutton.GetValue()
1377        #self.state.plugin_rbutton = self.plugin_rbutton.GetValue()
1378        self.state.structurecombobox = self.structurebox.GetLabel()
1379        self.state.formfactorcombobox = self.formfactorbox.GetLabel()
1380        self.state.categorycombobox = self.categorybox.GetLabel()
[5ce7f17]1381
[f0d720b]1382        ## post state to fit panel
1383        event = PageInfoEvent(page=self)
1384        wx.PostEvent(self.parent, event)
[5ce7f17]1385
[f0d720b]1386    def _save_plotting_range(self):
1387        """
1388        save the state of plotting range
1389        """
1390        self.state.qmin = self.qmin_x
1391        self.state.qmax = self.qmax_x
1392        self.state.npts = self.npts_x
[5ce7f17]1393
[f0d720b]1394    def _onparamEnter_helper(self):
1395        """
1396        check if values entered by the user are changed and valid to replot
1397        model
1398        """
1399        # Flag to register when a parameter has changed.
1400        is_modified = False
1401        self.fitrange = True
1402        is_2Ddata = False
1403        #self._undo.Enable(True)
1404        # check if 2d data
1405        if self.data.__class__.__name__ == "Data2D":
1406            is_2Ddata = True
1407        if self.model != None:
[ee4b3cb]1408            is_modified = (self._check_value_enter(self.fittable_param)
1409                           or self._check_value_enter(self.fixed_param)
1410                           or self._check_value_enter(self.parameters))
[f0d720b]1411
1412            # Here we should check whether the boundaries have been modified.
1413            # If qmin and qmax have been modified, update qmin and qmax and
1414            # set the is_modified flag to True
1415            if self._validate_qrange(self.qmin, self.qmax):
1416                tempmin = float(self.qmin.GetValue())
1417                if tempmin != self.qmin_x:
1418                    self.qmin_x = tempmin
1419                    is_modified = True
1420                tempmax = float(self.qmax.GetValue())
1421                if tempmax != self.qmax_x:
1422                    self.qmax_x = tempmax
1423                    is_modified = True
[5ce7f17]1424
[f0d720b]1425                if is_2Ddata:
1426                    # set mask
1427                    is_modified = self._validate_Npts()
[5ce7f17]1428
[f0d720b]1429            else:
1430                self.fitrange = False
[5ce7f17]1431
[f0d720b]1432            ## if any value is modify draw model with new value
1433            if not self.fitrange:
1434                #self.btFit.Disable()
1435                if is_2Ddata:
1436                    self.btEditMask.Disable()
1437            else:
1438                if is_2Ddata and self.data.is_data and not self.batch_on:
1439                    self.btEditMask.Enable(True)
1440            if is_modified and self.fitrange:
1441                # Theory case: need to get npts value to draw
1442                self.npts_x = float(self.Npts_total.GetValue())
1443                self.create_default_data()
1444                self.state_change = True
1445                self._draw_model()
1446                self.Refresh()
[c65a265]1447
1448        logging.info("is_modified flag set to %g",is_modified)
[f0d720b]1449        return is_modified
[5ce7f17]1450
[f0d720b]1451    def _update_paramv_on_fit(self):
1452        """
1453        make sure that update param values just before the fitting
1454        """
1455        #flag for qmin qmax check values
1456        flag = True
1457        self.fitrange = True
1458
1459        #wx.PostEvent(self._manager.parent, StatusEvent(status=" \
1460        #updating ... ",type="update"))
1461
1462        ##So make sure that update param values on_Fit.
1463        #self._undo.Enable(True)
1464        if self.model != None:
1465            if self.Npts_total.GetValue() != self.Npts_fit.GetValue():
1466                if not self.data.is_data:
1467                    self._manager.page_finder[self.uid].set_fit_data(data=\
1468                                                                [self.data])
1469            ##Check the values
[ee4b3cb]1470            self._check_value_enter(self.fittable_param)
1471            self._check_value_enter(self.fixed_param)
1472            self._check_value_enter(self.parameters)
[f0d720b]1473
[5ce7f17]1474            # If qmin and qmax have been modified, update qmin and qmax and
[f0d720b]1475            # Here we should check whether the boundaries have been modified.
[5ce7f17]1476            # If qmin and qmax have been modified, update qmin and qmax and
[f0d720b]1477            # set the is_modified flag to True
1478            self.fitrange = self._validate_qrange(self.qmin, self.qmax)
1479            if self.fitrange:
1480                tempmin = float(self.qmin.GetValue())
1481                if tempmin != self.qmin_x:
1482                    self.qmin_x = tempmin
1483                tempmax = float(self.qmax.GetValue())
1484                if tempmax != self.qmax_x:
1485                    self.qmax_x = tempmax
1486                if tempmax == tempmin:
1487                    flag = False
1488                temp_smearer = None
1489                if not self.disable_smearer.GetValue():
1490                    temp_smearer = self.current_smearer
1491                    if self.slit_smearer.GetValue():
1492                        flag = self.update_slit_smear()
1493                    elif self.pinhole_smearer.GetValue():
1494                        flag = self.update_pinhole_smear()
1495                    else:
[5ce7f17]1496                        enable_smearer = not self.disable_smearer.GetValue()
[f0d720b]1497                        self._manager.set_smearer(smearer=temp_smearer,
1498                                                  uid=self.uid,
1499                                                  fid=self.data.id,
1500                                                  qmin=float(self.qmin_x),
1501                                                  qmax=float(self.qmax_x),
[cd5e29b]1502                                                  enable_smearer=enable_smearer,
[5ce7f17]1503                                                  draw=False)
[f0d720b]1504                elif not self._is_2D():
[cd5e29b]1505                    enable_smearer = not self.disable_smearer.GetValue()
[f0d720b]1506                    self._manager.set_smearer(smearer=temp_smearer,
1507                                              qmin=float(self.qmin_x),
1508                                              uid=self.uid,
1509                                              fid=self.data.id,
1510                                              qmax=float(self.qmax_x),
[cd5e29b]1511                                              enable_smearer=enable_smearer,
[373d4ee]1512                                              draw=False)
[f0d720b]1513                    if self.data != None:
1514                        index_data = ((self.qmin_x <= self.data.x) & \
1515                                      (self.data.x <= self.qmax_x))
1516                        val = str(len(self.data.x[index_data == True]))
1517                        self.Npts_fit.SetValue(val)
1518                    else:
1519                        # No data in the panel
1520                        try:
1521                            self.npts_x = float(self.Npts_total.GetValue())
1522                        except:
1523                            flag = False
1524                            return flag
1525                    flag = True
1526                if self._is_2D():
1527                    # only 2D case set mask
1528                    flag = self._validate_Npts()
1529                    if not flag:
1530                        return flag
1531            else:
1532                flag = False
1533        else:
1534            flag = False
1535
1536        #For invalid q range, disable the mask editor and fit button, vs.
1537        if not self.fitrange:
1538            if self._is_2D():
1539                self.btEditMask.Disable()
1540        else:
1541            if self._is_2D() and  self.data.is_data and not self.batch_on:
1542                self.btEditMask.Enable(True)
1543
1544        if not flag:
1545            msg = "Cannot Plot or Fit :Must select a "
1546            msg += " model or Fitting range is not valid!!!  "
1547            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
[5ce7f17]1548
[f0d720b]1549        try:
1550            self.save_current_state()
[7673ecd]1551        except Exception:
1552            logging.error(traceback.format_exc())
[5ce7f17]1553
[f0d720b]1554        return flag
[5ce7f17]1555
[f0d720b]1556    def _reset_parameters_state(self, listtorestore, statelist):
1557        """
1558        Reset the parameters at the given state
1559        """
1560        if len(statelist) == 0 or len(listtorestore) == 0:
1561            return
1562        if len(statelist) != len(listtorestore):
1563            return
1564
1565        for j in range(len(listtorestore)):
1566            item_page = listtorestore[j]
1567            item_page_info = statelist[j]
1568            ##change the state of the check box for simple parameters
1569            if item_page[0] != None:
1570                item_page[0].SetValue(item_page_info[0])
1571            if item_page[2] != None:
1572                item_page[2].SetValue(item_page_info[2])
1573                if item_page[2].__class__.__name__ == "ComboBox":
1574                    if item_page_info[2] in self.model.fun_list:
1575                        fun_val = self.model.fun_list[item_page_info[2]]
1576                        self.model.setParam(item_page_info[1], fun_val)
1577            if item_page[3] != None:
1578                ## show or hide text +/-
1579                if item_page_info[2]:
1580                    item_page[3].Show(True)
1581                else:
1582                    item_page[3].Hide()
1583            if item_page[4] != None:
1584                ## show of hide the text crtl for fitting error
1585                if item_page_info[4][0]:
1586                    item_page[4].Show(True)
1587                    item_page[4].SetValue(item_page_info[4][1])
1588                else:
1589                    item_page[3].Hide()
1590            if item_page[5] != None:
1591                ## show of hide the text crtl for fitting error
1592                item_page[5].Show(item_page_info[5][0])
1593                item_page[5].SetValue(item_page_info[5][1])
[5ce7f17]1594
[f0d720b]1595            if item_page[6] != None:
1596                ## show of hide the text crtl for fitting error
1597                item_page[6].Show(item_page_info[6][0])
1598                item_page[6].SetValue(item_page_info[6][1])
[5ce7f17]1599
[f0d720b]1600    def _reset_strparam_state(self, listtorestore, statelist):
1601        """
1602        Reset the string parameters at the given state
1603        """
1604        if len(statelist) == 0:
1605            return
1606
1607        listtorestore = copy.deepcopy(statelist)
[5ce7f17]1608
[f0d720b]1609        for j in range(len(listtorestore)):
1610            item_page = listtorestore[j]
1611            item_page_info = statelist[j]
1612            ##change the state of the check box for simple parameters
[5ce7f17]1613
[f0d720b]1614            if item_page[0] != None:
1615                item_page[0].SetValue(format_number(item_page_info[0], True))
1616
1617            if item_page[2] != None:
1618                param_name = item_page_info[1]
1619                value = item_page_info[2]
1620                selection = value
1621                if value in self.model.fun_list:
1622                    selection = self.model.fun_list[value]
1623                item_page[2].SetValue(selection)
1624                self.model.setParam(param_name, selection)
[5ce7f17]1625
[f0d720b]1626    def _copy_parameters_state(self, listtocopy, statelist):
1627        """
1628        copy the state of button
[5ce7f17]1629
[f0d720b]1630        :param listtocopy: the list of check button to copy
1631        :param statelist: list of state object to store the current state
[5ce7f17]1632
[f0d720b]1633        """
1634        if len(listtocopy) == 0:
1635            return
[5ce7f17]1636
[f0d720b]1637        for item in listtocopy:
[5ce7f17]1638
[f0d720b]1639            checkbox_state = None
1640            if item[0] != None:
1641                checkbox_state = item[0].GetValue()
1642            parameter_name = item[1]
1643            parameter_value = None
1644            if item[2] != None:
1645                parameter_value = item[2].GetValue()
1646            static_text = None
1647            if item[3] != None:
1648                static_text = item[3].IsShown()
1649            error_value = None
1650            error_state = None
1651            if item[4] != None:
1652                error_value = item[4].GetValue()
1653                error_state = item[4].IsShown()
[5ce7f17]1654
[f0d720b]1655            min_value = None
1656            min_state = None
1657            if item[5] != None:
1658                min_value = item[5].GetValue()
1659                min_state = item[5].IsShown()
[5ce7f17]1660
[f0d720b]1661            max_value = None
1662            max_state = None
1663            if item[6] != None:
1664                max_value = item[6].GetValue()
1665                max_state = item[6].IsShown()
1666            unit = None
1667            if item[7] != None:
1668                unit = item[7].GetLabel()
[5ce7f17]1669
[f0d720b]1670            statelist.append([checkbox_state, parameter_name, parameter_value,
1671                              static_text, [error_state, error_value],
1672                              [min_state, min_value],
1673                              [max_state, max_value], unit])
[5ce7f17]1674
1675
[f0d720b]1676    def _draw_model(self, update_chisqr=True, source='model'):
1677        """
1678        Method to draw or refresh a plotted model.
1679        The method will use the data member from the model page
1680        to build a call to the fitting perspective manager.
[5ce7f17]1681
[f0d720b]1682        :param chisqr: update chisqr value [bool]
1683        """
1684        wx.CallAfter(self._draw_model_after, update_chisqr, source)
[5ce7f17]1685
[f0d720b]1686    def _draw_model_after(self, update_chisqr=True, source='model'):
1687        """
1688        Method to draw or refresh a plotted model.
1689        The method will use the data member from the model page
1690        to build a call to the fitting perspective manager.
[5ce7f17]1691
[f0d720b]1692        :param chisqr: update chisqr value [bool]
1693        """
1694        #if self.check_invalid_panel():
1695        #    return
1696        if self.model != None:
1697            temp_smear = None
1698            if hasattr(self, "enable_smearer"):
1699                if not self.disable_smearer.GetValue():
1700                    temp_smear = self.current_smearer
1701            # compute weight for the current data
[d85c194]1702            from sas.sasgui.perspectives.fitting.utils import get_weight
[f0d720b]1703            flag = self.get_weight_flag()
1704            weight = get_weight(data=self.data, is2d=self._is_2D(), flag=flag)
1705            toggle_mode_on = self.model_view.IsEnabled()
1706            is_2d = self._is_2D()
1707            self._manager.draw_model(self.model,
1708                                    data=self.data,
1709                                    smearer=temp_smear,
1710                                    qmin=float(self.qmin_x),
1711                                    qmax=float(self.qmax_x),
1712                                    page_id=self.uid,
1713                                    toggle_mode_on=toggle_mode_on,
1714                                    state=self.state,
1715                                    enable2D=is_2d,
1716                                    update_chisqr=update_chisqr,
1717                                    source='model',
1718                                    weight=weight)
[5ce7f17]1719
[f0d720b]1720    def _on_show_sld(self, event=None):
1721        """
1722        Plot SLD profile
1723        """
1724        # get profile data
1725        x, y = self.model.getProfile()
1726
[d7bb526]1727        from sas.sasgui.plottools import Data1D as pf_data1d
[d85c194]1728        #from sas.sasgui.perspectives.theory.profile_dialog import SLDPanel
1729        from sas.sasgui.guiframe.local_perspectives.plotting.profile_dialog \
[f0d720b]1730        import SLDPanel
1731        sld_data = pf_data1d(x, y)
1732        sld_data.name = 'SLD'
1733        sld_data.axes = self.sld_axes
[6f16e25]1734        self.panel = SLDPanel(self, data=sld_data, axes=self.sld_axes,
1735                              id=wx.ID_ANY)
[f0d720b]1736        self.panel.ShowModal()
[5ce7f17]1737
[f0d720b]1738    def _set_multfactor_combobox(self, multiplicity=10):
1739        """
1740        Set comboBox for muitfactor of CoreMultiShellModel
1741        :param multiplicit: no. of multi-functionality
1742        """
1743        # build content of the combobox
1744        for idx in range(0, multiplicity):
1745            self.multifactorbox.Append(str(idx), int(idx))
1746        self._hide_multfactor_combobox()
[5ce7f17]1747
[f0d720b]1748    def _show_multfactor_combobox(self):
1749        """
1750        Show the comboBox of muitfactor of CoreMultiShellModel
1751        """
1752        if not self.mutifactor_text.IsShown():
1753            self.mutifactor_text.Show(True)
1754            self.mutifactor_text1.Show(True)
1755        if not self.multifactorbox.IsShown():
1756            self.multifactorbox.Show(True)
[5ce7f17]1757
[f0d720b]1758    def _hide_multfactor_combobox(self):
1759        """
1760        Hide the comboBox of muitfactor of CoreMultiShellModel
1761        """
1762        if self.mutifactor_text.IsShown():
1763            self.mutifactor_text.Hide()
1764            self.mutifactor_text1.Hide()
1765        if self.multifactorbox.IsShown():
1766            self.multifactorbox.Hide()
[5ce7f17]1767
[f0d720b]1768    def formfactor_combo_init(self):
1769        """
1770        First time calls _show_combox_helper
1771        """
1772        self._show_combox(None)
[5ce7f17]1773
[f0d720b]1774    def _show_combox_helper(self):
1775        """
1776        Fill panel's combo box according to the type of model selected
1777        """
1778        custom_model = 'Customized Models'
1779        mod_cat = self.categorybox.GetStringSelection()
1780        self.structurebox.SetSelection(0)
1781        self.structurebox.Disable()
1782        self.formfactorbox.Clear()
1783        if mod_cat == None:
1784            return
1785        m_list = []
1786        try:
1787            if mod_cat == custom_model:
1788                for model in self.model_list_box[mod_cat]:
[2abe6bf]1789                    m_list.append(self.model_dict[model.name])
[f0d720b]1790            else:
1791                cat_dic = self.master_category_dict[mod_cat]
1792                for (model, enabled) in cat_dic:
1793                    if enabled:
1794                        m_list.append(self.model_dict[model])
[7673ecd]1795        except Exception:
1796            msg = traceback.format_exc()
[f0d720b]1797            wx.PostEvent(self._manager.parent,
1798                         StatusEvent(status=msg, info="error"))
1799        self._populate_box(self.formfactorbox, m_list)
[5ce7f17]1800
1801    def _on_modify_cat(self, event=None):
1802        """
[cd5e29b]1803        Called when category manager is opened
[5ce7f17]1804        """
1805        self._manager.parent.on_category_panel(event)
1806
[f0d720b]1807    def _show_combox(self, event=None):
1808        """
1809        Show combox box associate with type of model selected
1810        """
1811        self.Show(False)
1812        self._show_combox_helper()
1813        self._on_select_model(event=None)
1814        self.Show(True)
1815        self._save_typeOfmodel()
1816        self.sizer4_4.Layout()
1817        self.sizer4.Layout()
1818        self.Layout()
1819        self.Refresh()
[5ce7f17]1820
[f0d720b]1821    def _populate_box(self, combobox, list):
1822        """
1823        fill combox box with dict item
[5ce7f17]1824
[f0d720b]1825        :param list: contains item to fill the combox
1826            item must model class
1827        """
1828        mlist = []
1829        for models in list:
[cb4ef58]1830            if models.name != "NoStructure":
1831                mlist.append((models.name, models))
[5ce7f17]1832
[f0d720b]1833        # Sort the models
1834        mlist_sorted = sorted(mlist)
1835        for item in mlist_sorted:
1836            combobox.Append(item[0], item[1])
1837        return 0
[5ce7f17]1838
[f0d720b]1839    def _onQrangeEnter(self, event):
1840        """
1841        Check validity of value enter in the Q range field
[5ce7f17]1842
[f0d720b]1843        """
1844        tcrtl = event.GetEventObject()
1845        #Clear msg if previously shown.
1846        msg = ""
1847        wx.PostEvent(self.parent, StatusEvent(status=msg))
1848        # Flag to register when a parameter has changed.
1849        if tcrtl.GetValue().lstrip().rstrip() != "":
1850            try:
1851                float(tcrtl.GetValue())
1852                tcrtl.SetBackgroundColour(wx.WHITE)
1853                # If qmin and qmax have been modified, update qmin and qmax
1854                if self._validate_qrange(self.qmin, self.qmax):
1855                    tempmin = float(self.qmin.GetValue())
1856                    if tempmin != self.qmin_x:
1857                        self.qmin_x = tempmin
1858                    tempmax = float(self.qmax.GetValue())
1859                    if tempmax != self.qmax_x:
1860                        self.qmax_x = tempmax
1861                else:
1862                    tcrtl.SetBackgroundColour("pink")
[cd5e29b]1863                    msg = "Model Error: wrong value entered: %s" % \
1864                                    sys.exc_info()[1]
[f0d720b]1865                    wx.PostEvent(self.parent, StatusEvent(status=msg))
1866                    return
1867            except:
1868                tcrtl.SetBackgroundColour("pink")
[cd5e29b]1869                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
[f0d720b]1870                wx.PostEvent(self.parent, StatusEvent(status=msg))
1871                return
1872            #Check if # of points for theory model are valid(>0).
1873            if self.npts != None:
1874                if check_float(self.npts):
1875                    temp_npts = float(self.npts.GetValue())
1876                    if temp_npts != self.num_points:
1877                        self.num_points = temp_npts
1878                else:
1879                    msg = "Cannot plot: No points in Q range!!!  "
1880                    wx.PostEvent(self.parent, StatusEvent(status=msg))
1881        else:
1882            tcrtl.SetBackgroundColour("pink")
1883            msg = "Model Error: wrong value entered!!!"
1884            wx.PostEvent(self.parent, StatusEvent(status=msg))
1885        self.save_current_state()
1886        event = PageInfoEvent(page=self)
1887        wx.PostEvent(self.parent, event)
1888        self.state_change = False
1889        #Draw the model for a different range
1890        if not self.data.is_data:
1891            self.create_default_data()
1892        self._draw_model()
[5ce7f17]1893
[f0d720b]1894    def _theory_qrange_enter(self, event):
1895        """
1896        Check validity of value enter in the Q range field
1897        """
[5ce7f17]1898
[f0d720b]1899        tcrtl = event.GetEventObject()
1900        #Clear msg if previously shown.
1901        msg = ""
1902        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1903        # Flag to register when a parameter has changed.
1904        is_modified = False
1905        if tcrtl.GetValue().lstrip().rstrip() != "":
1906            try:
1907                value = float(tcrtl.GetValue())
1908                tcrtl.SetBackgroundColour(wx.WHITE)
1909
1910                # If qmin and qmax have been modified, update qmin and qmax
1911                if self._validate_qrange(self.theory_qmin, self.theory_qmax):
1912                    tempmin = float(self.theory_qmin.GetValue())
1913                    if tempmin != self.theory_qmin_x:
1914                        self.theory_qmin_x = tempmin
1915                    tempmax = float(self.theory_qmax.GetValue())
1916                    if tempmax != self.qmax_x:
1917                        self.theory_qmax_x = tempmax
1918                else:
1919                    tcrtl.SetBackgroundColour("pink")
[cd5e29b]1920                    msg = "Model Error: wrong value entered: %s" % \
1921                                        sys.exc_info()[1]
[f0d720b]1922                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1923                    return
1924            except:
1925                tcrtl.SetBackgroundColour("pink")
[cd5e29b]1926                msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
[f0d720b]1927                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1928                return
1929            #Check if # of points for theory model are valid(>0).
1930            if self.Npts_total.IsEditable():
1931                if check_float(self.Npts_total):
1932                    temp_npts = float(self.Npts_total.GetValue())
1933                    if temp_npts != self.num_points:
1934                        self.num_points = temp_npts
1935                        is_modified = True
1936                else:
1937                    msg = "Cannot Plot: No points in Q range!!!  "
1938                    wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1939        else:
1940            tcrtl.SetBackgroundColour("pink")
1941            msg = "Model Error: wrong value entered!!!"
1942            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
1943        self.save_current_state()
1944        event = PageInfoEvent(page=self)
1945        wx.PostEvent(self.parent, event)
1946        self.state_change = False
1947        #Draw the model for a different range
1948        self.create_default_data()
1949        self._draw_model()
[5ce7f17]1950
[f0d720b]1951    def _on_select_model_helper(self):
1952        """
1953        call back for model selection
1954        """
1955        ## reset dictionary containing reference to dispersion
1956        self._disp_obj_dict = {}
1957        self.disp_cb_dict = {}
1958        self.temp_multi_functional = False
1959        f_id = self.formfactorbox.GetCurrentSelection()
1960        #For MAC
1961        form_factor = None
1962        if f_id >= 0:
1963            form_factor = self.formfactorbox.GetClientData(f_id)
1964
1965        if not form_factor in  self.model_list_box["multiplication"]:
1966            self.structurebox.Hide()
1967            self.text2.Hide()
1968            self.structurebox.Disable()
1969            self.structurebox.SetSelection(0)
1970            self.text2.Disable()
1971        else:
1972            self.structurebox.Show()
1973            self.text2.Show()
1974            self.structurebox.Enable()
1975            self.text2.Enable()
[5ce7f17]1976
[f0d720b]1977        if form_factor != None:
1978            # set multifactor for Mutifunctional models
[cb4ef58]1979            if form_factor.is_multiplicity_model:
[f0d720b]1980                m_id = self.multifactorbox.GetCurrentSelection()
[cb4ef58]1981                multiplicity = form_factor.multiplicity_info[0]
[f0d720b]1982                self.multifactorbox.Clear()
1983                self._set_multfactor_combobox(multiplicity)
1984                self._show_multfactor_combobox()
1985                #ToDo:  this info should be called directly from the model
[cb4ef58]1986                text = form_factor.multiplicity_info[1]  # 'No. of Shells: '
[f0d720b]1987
1988                self.mutifactor_text.SetLabel(text)
1989                if m_id > multiplicity - 1:
1990                    # default value
1991                    m_id = 1
[5ce7f17]1992
[f0d720b]1993                self.multi_factor = self.multifactorbox.GetClientData(m_id)
1994                if self.multi_factor == None:
1995                    self.multi_factor = 0
1996                self.multifactorbox.SetSelection(m_id)
1997                # Check len of the text1 and max_multiplicity
1998                text = ''
1999                if form_factor.multiplicity_info[0] == \
2000                                        len(form_factor.multiplicity_info[2]):
2001                    text = form_factor.multiplicity_info[2][self.multi_factor]
2002                self.mutifactor_text1.SetLabel(text)
2003                # Check if model has  get sld profile.
2004                if len(form_factor.multiplicity_info[3]) > 0:
2005                    self.sld_axes = form_factor.multiplicity_info[3]
2006                    self.show_sld_button.Show(True)
2007                else:
2008                    self.sld_axes = ""
2009            else:
2010                self._hide_multfactor_combobox()
2011                self.show_sld_button.Hide()
2012                self.multi_factor = None
2013        else:
2014            self._hide_multfactor_combobox()
2015            self.show_sld_button.Hide()
2016            self.multi_factor = None
[5ce7f17]2017
[f0d720b]2018        s_id = self.structurebox.GetCurrentSelection()
2019        struct_factor = self.structurebox.GetClientData(s_id)
[5ce7f17]2020
[f0d720b]2021        if  struct_factor != None:
[08959b8]2022            from sas.sascalc.fit.MultiplicationModel import MultiplicationModel
[cb4ef58]2023            self.model = MultiplicationModel(form_factor(self.multi_factor),
2024                                             struct_factor())
[f0d720b]2025            # multifunctional form factor
2026            if len(form_factor.non_fittable) > 0:
2027                self.temp_multi_functional = True
[cb4ef58]2028        elif form_factor != None:
[5213d22]2029            if self.multi_factor is not None:
2030                self.model = form_factor(self.multi_factor)
2031            else:
2032                # old style plugin models do not accept a multiplicity argument
2033                self.model = form_factor()
[f0d720b]2034        else:
[cb4ef58]2035            self.model = None
2036            return
2037
[f0d720b]2038        # check if model has magnetic parameters
2039        if len(self.model.magnetic_params) > 0:
[5ce7f17]2040            self._has_magnetic = True
[f0d720b]2041        else:
[5ce7f17]2042            self._has_magnetic = False
[f0d720b]2043        ## post state to fit panel
2044        self.state.parameters = []
2045        self.state.model = self.model
2046        self.state.qmin = self.qmin_x
2047        self.state.multi_factor = self.multi_factor
2048        self.disp_list = self.model.getDispParamList()
2049        self.state.disp_list = self.disp_list
2050        self.on_set_focus(None)
2051        self.Layout()
[5ce7f17]2052
[ee4b3cb]2053
[f0d720b]2054    def _validate_qrange(self, qmin_ctrl, qmax_ctrl):
2055        """
2056        Verify that the Q range controls have valid values
2057        and that Qmin < Qmax.
[5ce7f17]2058
[f0d720b]2059        :param qmin_ctrl: text control for Qmin
2060        :param qmax_ctrl: text control for Qmax
[5ce7f17]2061
[f0d720b]2062        :return: True is the Q range is value, False otherwise
[5ce7f17]2063
[f0d720b]2064        """
2065        qmin_validity = check_float(qmin_ctrl)
2066        qmax_validity = check_float(qmax_ctrl)
2067        if not (qmin_validity and qmax_validity):
2068            return False
2069        else:
2070            qmin = float(qmin_ctrl.GetValue())
2071            qmax = float(qmax_ctrl.GetValue())
2072            if qmin < qmax:
2073                #Make sure to set both colours white.
2074                qmin_ctrl.SetBackgroundColour(wx.WHITE)
2075                qmin_ctrl.Refresh()
2076                qmax_ctrl.SetBackgroundColour(wx.WHITE)
2077                qmax_ctrl.Refresh()
2078            else:
2079                qmin_ctrl.SetBackgroundColour("pink")
2080                qmin_ctrl.Refresh()
2081                qmax_ctrl.SetBackgroundColour("pink")
2082                qmax_ctrl.Refresh()
2083                msg = "Invalid Q range: Q min must be smaller than Q max"
2084                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2085                return False
2086        return True
[5ce7f17]2087
[f0d720b]2088    def _validate_Npts(self):
2089        """
2090        Validate the number of points for fitting is more than 10 points.
2091        If valid, setvalues Npts_fit otherwise post msg.
2092        """
2093        #default flag
2094        flag = True
2095        # Theory
2096        if self.data == None and self.enable2D:
2097            return flag
2098        for data in self.data_list:
2099            # q value from qx and qy
2100            radius = numpy.sqrt(data.qx_data * data.qx_data +
2101                                data.qy_data * data.qy_data)
2102            #get unmasked index
2103            index_data = (float(self.qmin.GetValue()) <= radius) & \
2104                            (radius <= float(self.qmax.GetValue()))
2105            index_data = (index_data) & (data.mask)
2106            index_data = (index_data) & (numpy.isfinite(data.data))
2107
2108            if len(index_data[index_data]) < 10:
2109                # change the color pink.
2110                self.qmin.SetBackgroundColour("pink")
2111                self.qmin.Refresh()
2112                self.qmax.SetBackgroundColour("pink")
2113                self.qmax.Refresh()
2114                msg = "Data Error: "
2115                msg += "Too few points in %s." % data.name
2116                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2117                self.fitrange = False
2118                flag = False
2119            else:
2120                self.Npts_fit.SetValue(str(len(index_data[index_data == True])))
2121                self.fitrange = True
[5ce7f17]2122
[f0d720b]2123        return flag
2124
2125    def _validate_Npts_1D(self):
2126        """
2127        Validate the number of points for fitting is more than 5 points.
2128        If valid, setvalues Npts_fit otherwise post msg.
2129        """
2130        #default flag
2131        flag = True
2132        # Theory
2133        if self.data == None:
2134            return flag
2135        for data in self.data_list:
2136            # q value from qx and qy
2137            radius = data.x
2138            #get unmasked index
2139            index_data = (float(self.qmin.GetValue()) <= radius) & \
2140                            (radius <= float(self.qmax.GetValue()))
2141            index_data = (index_data) & (numpy.isfinite(data.y))
2142
2143            if len(index_data[index_data]) < 5:
2144                # change the color pink.
2145                self.qmin.SetBackgroundColour("pink")
2146                self.qmin.Refresh()
2147                self.qmax.SetBackgroundColour("pink")
2148                self.qmax.Refresh()
2149                msg = "Data Error: "
2150                msg += "Too few points in %s." % data.name
2151                wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2152                self.fitrange = False
2153                flag = False
2154            else:
2155                self.Npts_fit.SetValue(str(len(index_data[index_data == True])))
2156                self.fitrange = True
[5ce7f17]2157
[f0d720b]2158        return flag
[5ce7f17]2159
[ee4b3cb]2160    def _check_value_enter(self, list):
[f0d720b]2161        """
2162        :param list: model parameter and panel info
2163        :Note: each item of the list should be as follow:
2164            item=[check button state, parameter's name,
2165                paramater's value, string="+/-",
2166                parameter's error of fit,
2167                parameter's minimum value,
[cb4ef58]2168                parameter's maximum value ,
[f0d720b]2169                parameter's units]
[ee4b3cb]2170
2171        Returns True if the model parameters have changed.
[f0d720b]2172        """
[ee4b3cb]2173        is_modified = False
[f0d720b]2174        for item in list:
2175            #skip angle parameters for 1D
[ee4b3cb]2176            if not self.enable2D and item in self.orientation_params:
2177                continue
[5ce7f17]2178
[a0373d5]2179            value_ctrl = item[2]
2180            if not value_ctrl.IsEnabled():
2181                # ArrayDispersion disables PD, Min, Max, Npts, Nsigs
[ee4b3cb]2182                continue
2183
[a0373d5]2184            name = item[1]
[ee4b3cb]2185            value_str = value_ctrl.GetValue().strip()
[a0373d5]2186            if name.endswith(".npts"):
2187                validity = check_int(value_ctrl)
2188                if not validity:
2189                    continue
2190                value = int(value_str)
2191
2192            elif name.endswith(".nsigmas"):
2193                validity = check_float(value_ctrl)
2194                if not validity:
2195                    continue
2196                value = float(value_str)
2197
2198            else:  # value or polydispersity
2199
2200                # Check that min, max and value are floats
2201                min_ctrl, max_ctrl = item[5], item[6]
2202                min_str = min_ctrl.GetValue().strip()
2203                max_str = max_ctrl.GetValue().strip()
2204                validity = check_float(value_ctrl)
2205                if min_str != "":
2206                    validity = validity and check_float(min_ctrl)
2207                if max_str != "":
2208                    validity = validity and check_float(max_ctrl)
2209                if not validity:
2210                    continue
2211
2212                # Check that min is less than max
2213                low = -numpy.inf if min_str == "" else float(min_str)
2214                high = numpy.inf if max_str == "" else float(max_str)
2215                if high < low:
2216                    min_ctrl.SetBackgroundColour("pink")
2217                    min_ctrl.Refresh()
2218                    max_ctrl.SetBackgroundColour("pink")
2219                    max_ctrl.Refresh()
2220                    #msg = "Invalid fit range for %s: min must be smaller than max"%name
2221                    #wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2222                    continue
2223
2224                # Force value between min and max
2225                value = float(value_str)
2226                if value < low:
2227                    value = low
2228                    value_ctrl.SetValue(format_number(value))
2229                elif value > high:
2230                    value = high
2231                    value_ctrl.SetValue(format_number(value))
2232
2233                if name not in self.model.details.keys():
2234                    self.model.details[name] = ["", None, None]
2235                old_low, old_high = self.model.details[name][1:3]
2236                if old_low != low or old_high != high:
2237                    # The configuration has changed but it won't change the
2238                    # computed curve so no need to set is_modified to True
2239                    #is_modified = True
2240                    self.model.details[name][1:3] = low, high
[ee4b3cb]2241
2242            # Update value in model if it has changed
2243            if value != self.model.getParam(name):
2244                self.model.setParam(name, value)
2245                is_modified = True
[5ce7f17]2246
[f0d720b]2247        return is_modified
[5ce7f17]2248
[f0d720b]2249    def _set_dipers_Param(self, event):
2250        """
2251        respond to self.enable_disp and self.disable_disp radio box.
2252        The dispersity object is reset inside the model into Gaussian.
2253        When the user select yes , this method display a combo box for
2254        more selection when the user selects No,the combo box disappears.
2255        Redraw the model with the default dispersity (Gaussian)
2256        """
2257        ## On selction if no model exists.
2258        if self.model == None:
2259            self.disable_disp.SetValue(True)
2260            msg = "Please select a Model first..."
2261            wx.MessageBox(msg, 'Info')
2262            wx.PostEvent(self._manager.parent,
2263                         StatusEvent(status="Polydispersion: %s" % msg))
2264            return
2265
2266        self._reset_dispersity()
[5ce7f17]2267
[f0d720b]2268        if self.model == None:
2269            self.model_disp.Hide()
2270            self.sizer4_4.Clear(True)
2271            return
2272
2273        if self.enable_disp.GetValue():
2274            ## layout for model containing no dispersity parameters
[5ce7f17]2275
[f0d720b]2276            self.disp_list = self.model.getDispParamList()
[5ce7f17]2277
[f0d720b]2278            if len(self.disp_list) == 0 and len(self.disp_cb_dict) == 0:
2279                self._layout_sizer_noDipers()
2280            else:
2281                ## set gaussian sizer
2282                self._on_select_Disp(event=None)
2283        else:
2284            self.sizer4_4.Clear(True)
[5ce7f17]2285
[f0d720b]2286        ## post state to fit panel
2287        self.save_current_state()
2288        if event != None:
2289            event = PageInfoEvent(page=self)
2290            wx.PostEvent(self.parent, event)
2291        #draw the model with the current dispersity
2292        self._draw_model()
[1c2bf90]2293        ## Need to use FitInside again here to replace the next four lines.
[bac3988]2294        ## Otherwised polydispersity off does not resize the scrollwindow.
[1c2bf90]2295        ## PDB Nov 28, 2015
2296        self.FitInside()
2297#        self.sizer4_4.Layout()
2298#        self.sizer5.Layout()
2299#        self.Layout()
2300#        self.Refresh()
[5ce7f17]2301
[f0d720b]2302    def _layout_sizer_noDipers(self):
2303        """
2304        Draw a sizer with no dispersity info
2305        """
2306        ix = 0
2307        iy = 1
2308        self.fittable_param = []
2309        self.fixed_param = []
2310        self.orientation_params_disp = []
[5ce7f17]2311
[f0d720b]2312        self.sizer4_4.Clear(True)
2313        text = "No polydispersity available for this model"
[6f16e25]2314        model_disp = wx.StaticText(self, wx.ID_ANY, text)
[f0d720b]2315        self.sizer4_4.Add(model_disp, (iy, ix), (1, 1),
2316                          wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
2317        self.sizer4_4.Layout()
2318        self.sizer4.Layout()
[5ce7f17]2319
[f0d720b]2320    def _reset_dispersity(self):
2321        """
2322        put gaussian dispersity into current model
2323        """
2324        if len(self.param_toFit) > 0:
2325            for item in self.fittable_param:
2326                if item in self.param_toFit:
2327                    self.param_toFit.remove(item)
2328
2329            for item in self.orientation_params_disp:
2330                if item in self.param_toFit:
2331                    self.param_toFit.remove(item)
[5ce7f17]2332
[f0d720b]2333        self.fittable_param = []
2334        self.fixed_param = []
2335        self.orientation_params_disp = []
2336        self.values = {}
2337        self.weights = {}
[bac3988]2338
[f0d720b]2339        #from sas.models.dispersion_models import GaussianDispersion
2340        from sasmodels.weights import GaussianDispersion
2341        if len(self.disp_cb_dict) == 0:
2342            self.save_current_state()
2343            self.sizer4_4.Clear(True)
2344            self.Layout()
2345            return
2346        if (len(self.disp_cb_dict) > 0):
2347            for p in self.disp_cb_dict:
2348                # The parameter was un-selected.
2349                # Go back to Gaussian model (with 0 pts)
2350                disp_model = GaussianDispersion()
[5ce7f17]2351
[f0d720b]2352                self._disp_obj_dict[p] = disp_model
2353                # Set the new model as the dispersion object
2354                # for the selected parameter
2355                try:
2356                    self.model.set_dispersion(p, disp_model)
[7673ecd]2357                except Exception:
2358                    logging.error(traceback.format_exc())
[f0d720b]2359
2360        ## save state into
2361        self.save_current_state()
2362        self.Layout()
2363        self.Refresh()
[5ce7f17]2364
[f0d720b]2365    def _on_select_Disp(self, event):
2366        """
2367        allow selecting different dispersion
2368        self.disp_list should change type later .now only gaussian
2369        """
2370        self._set_sizer_dispersion()
2371
2372        ## Redraw the model
2373        self._draw_model()
2374        #self._undo.Enable(True)
2375        event = PageInfoEvent(page=self)
2376        wx.PostEvent(self.parent, event)
[5ce7f17]2377
[f0d720b]2378        self.sizer4_4.Layout()
2379        self.sizer4.Layout()
2380        self.SetupScrolling()
[5ce7f17]2381
[f0d720b]2382    def _on_disp_func(self, event=None):
2383        """
2384        Select a distribution function for the polydispersion
[5ce7f17]2385
[f0d720b]2386        :Param event: ComboBox event
2387        """
2388        # get ready for new event
2389        if event != None:
2390            event.Skip()
2391        # Get event object
2392        disp_box = event.GetEventObject()
2393
2394        # Try to select a Distr. function
2395        try:
2396            disp_box.SetBackgroundColour("white")
2397            selection = disp_box.GetCurrentSelection()
2398            param_name = disp_box.Name.split('.')[0]
2399            disp_name = disp_box.GetValue()
2400            dispersity = disp_box.GetClientData(selection)
[5ce7f17]2401
[f0d720b]2402            #disp_model =  GaussianDispersion()
2403            disp_model = dispersity()
2404            # Get param names to reset the values of the param
2405            name1 = param_name + ".width"
2406            name2 = param_name + ".npts"
2407            name3 = param_name + ".nsigmas"
2408            # Check Disp. function whether or not it is 'array'
2409            if disp_name.lower() == "array":
2410                value2 = ""
2411                value3 = ""
2412                value1 = self._set_array_disp(name=name1, disp=disp_model)
2413            else:
2414                self._del_array_values(name1)
2415                #self._reset_array_disp(param_name)
2416                self._disp_obj_dict[name1] = disp_model
2417                self.model.set_dispersion(param_name, disp_model)
[6c382da]2418                self.state._disp_obj_dict[name1] = disp_model.type
[5ce7f17]2419
[f0d720b]2420                value1 = str(format_number(self.model.getParam(name1), True))
2421                value2 = str(format_number(self.model.getParam(name2)))
2422                value3 = str(format_number(self.model.getParam(name3)))
2423            # Reset fittable polydispersin parameter value
2424            for item in self.fittable_param:
2425                if item[1] == name1:
2426                    item[2].SetValue(value1)
2427                    item[5].SetValue("")
2428                    item[6].SetValue("")
2429                    # Disable for array
2430                    if disp_name.lower() == "array":
2431                        item[0].SetValue(False)
2432                        item[0].Disable()
2433                        item[2].Disable()
2434                        item[3].Show(False)
2435                        item[4].Show(False)
2436                        item[5].Disable()
2437                        item[6].Disable()
2438                    else:
2439                        item[0].Enable()
2440                        item[2].Enable()
[6c382da]2441                        item[3].Show(True)
2442                        item[4].Show(True)
[f0d720b]2443                        item[5].Enable()
2444                        item[6].Enable()
2445                    break
2446            # Reset fixed polydispersion params
2447            for item in self.fixed_param:
2448                if item[1] == name2:
[5ce7f17]2449                    item[2].SetValue(value2)
[f0d720b]2450                    # Disable Npts for array
2451                    if disp_name.lower() == "array":
2452                        item[2].Disable()
2453                    else:
2454                        item[2].Enable()
2455                if item[1] == name3:
2456                    item[2].SetValue(value3)
2457                    # Disable Nsigs for array
2458                    if disp_name.lower() == "array":
2459                        item[2].Disable()
2460                    else:
2461                        item[2].Enable()
[5ce7f17]2462
[4c3be25]2463            # Make sure the check box updated
2464            self.get_all_checked_params()
[f0d720b]2465
2466            # update params
2467            self._update_paramv_on_fit()
2468            # draw
2469            self._draw_model()
2470            self.Refresh()
[6ed67db]2471        except Exception:
2472            logging.error(traceback.format_exc())
[f0d720b]2473            # Error msg
2474            msg = "Error occurred:"
2475            msg += " Could not select the distribution function..."
2476            msg += " Please select another distribution function."
2477            disp_box.SetBackgroundColour("pink")
2478            # Focus on Fit button so that users can see the pinky box
2479            self.btFit.SetFocus()
2480            wx.PostEvent(self._manager.parent,
2481                         StatusEvent(status=msg, info="error"))
[5ce7f17]2482
[f0d720b]2483    def _set_array_disp(self, name=None, disp=None):
2484        """
2485        Set array dispersion
[5ce7f17]2486
[f0d720b]2487        :param name: name of the parameter for the dispersion to be set
2488        :param disp: the polydisperion object
2489        """
2490        # The user wants this parameter to be averaged.
2491        # Pop up the file selection dialog.
2492        path = self._selectDlg()
2493        # Array data
2494        values = []
2495        weights = []
2496        # If nothing was selected, just return
2497        if path is None:
2498            self.disp_cb_dict[name].SetValue(False)
2499            #self.noDisper_rbox.SetValue(True)
2500            return
2501        self._default_save_location = os.path.dirname(path)
2502        if self._manager != None:
[5ce7f17]2503            self._manager.parent._default_save_location = \
[f0d720b]2504                             self._default_save_location
2505
2506        basename = os.path.basename(path)
2507        values, weights = self.read_file(path)
[5ce7f17]2508
[f0d720b]2509        # If any of the two arrays is empty, notify the user that we won't
2510        # proceed
2511        if len(self.param_toFit) > 0:
2512            if name in self.param_toFit:
2513                self.param_toFit.remove(name)
2514
2515        # Tell the user that we are about to apply the distribution
2516        msg = "Applying loaded %s distribution: %s" % (name, path)
2517        wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2518        self._set_array_disp_model(name=name, disp=disp,
2519                                    values=values, weights=weights)
2520        return basename
[5ce7f17]2521
[f0d720b]2522    def _set_array_disp_model(self, name=None, disp=None,
2523                              values=[], weights=[]):
2524        """
2525        Set array dispersion model
[5ce7f17]2526
[f0d720b]2527        :param name: name of the parameter for the dispersion to be set
2528        :param disp: the polydisperion object
2529        """
2530        disp.set_weights(values, weights)
2531        self._disp_obj_dict[name] = disp
2532        self.model.set_dispersion(name.split('.')[0], disp)
[6c382da]2533        self.state._disp_obj_dict[name] = disp.type
[f0d720b]2534        self.values[name] = values
2535        self.weights[name] = weights
2536        # Store the object to make it persist outside the
2537        # scope of this method
2538        #TODO: refactor model to clean this up?
2539        self.state.values = {}
2540        self.state.weights = {}
2541        self.state.values = copy.deepcopy(self.values)
2542        self.state.weights = copy.deepcopy(self.weights)
2543
2544        # Set the new model as the dispersion object for the
2545        #selected parameter
2546        #self.model.set_dispersion(p, disp_model)
2547        # Store a reference to the weights in the model object
2548        #so that
2549        # it's not lost when we use the model within another thread.
2550        self.state.model = self.model.clone()
2551        self.model._persistency_dict[name.split('.')[0]] = \
2552                                        [values, weights]
2553        self.state.model._persistency_dict[name.split('.')[0]] = \
2554                                        [values, weights]
[5ce7f17]2555
[f0d720b]2556    def _del_array_values(self, name=None):
2557        """
2558        Reset array dispersion
[5ce7f17]2559
[f0d720b]2560        :param name: name of the parameter for the dispersion to be set
2561        """
2562        # Try to delete values and weight of the names array dic if exists
2563        try:
[6ed67db]2564            if name in self.values:
2565                del self.values[name]
2566                del self.weights[name]
2567                # delete all other dic
2568                del self.state.values[name]
2569                del self.state.weights[name]
2570                del self.model._persistency_dict[name.split('.')[0]]
2571                del self.state.model._persistency_dict[name.split('.')[0]]
[7673ecd]2572        except Exception:
2573            logging.error(traceback.format_exc())
[5ce7f17]2574
[f0d720b]2575    def _lay_out(self):
2576        """
2577        returns self.Layout
[5ce7f17]2578
[f0d720b]2579        :Note: Mac seems to like this better when self.
2580            Layout is called after fitting.
2581        """
2582        self._sleep4sec()
2583        self.Layout()
2584        return
[5ce7f17]2585
[f0d720b]2586    def _sleep4sec(self):
2587        """
2588            sleep for 1 sec only applied on Mac
2589            Note: This 1sec helps for Mac not to crash on self.
2590            Layout after self._draw_model
2591        """
2592        if ON_MAC == True:
2593            time.sleep(1)
[5ce7f17]2594
[f0d720b]2595    def _find_polyfunc_selection(self, disp_func=None):
2596        """
2597        FInd Comboox selection from disp_func
[5ce7f17]2598
[f0d720b]2599        :param disp_function: dispersion distr. function
2600        """
2601        # Find the selection
[a0373d5]2602        if disp_func is not None:
2603            try:
2604                return POLYDISPERSITY_MODELS.values().index(disp_func.__class__)
2605            except ValueError:
2606                pass  # Fall through to default class
2607        return POLYDISPERSITY_MODELS.keys().index('gaussian')
[5ce7f17]2608
[f0d720b]2609    def on_reset_clicked(self, event):
2610        """
2611        On 'Reset' button  for Q range clicked
2612        """
2613        flag = True
2614        ##For 3 different cases: Data2D, Data1D, and theory
2615        if self.model == None:
2616            msg = "Please select a model first..."
2617            wx.MessageBox(msg, 'Info')
2618            flag = False
2619            return
[5ce7f17]2620
[f0d720b]2621        elif self.data.__class__.__name__ == "Data2D":
2622            data_min = 0
2623            x = max(math.fabs(self.data.xmin), math.fabs(self.data.xmax))
2624            y = max(math.fabs(self.data.ymin), math.fabs(self.data.ymax))
2625            self.qmin_x = data_min
[5ce7f17]2626            self.qmax_x = math.sqrt(x * x + y * y)
[f0d720b]2627            #self.data.mask = numpy.ones(len(self.data.data),dtype=bool)
2628            # check smearing
2629            if not self.disable_smearer.GetValue():
2630                ## set smearing value whether or
2631                # not the data contain the smearing info
2632                if self.pinhole_smearer.GetValue():
2633                    flag = self.update_pinhole_smear()
2634                else:
2635                    flag = True
[5ce7f17]2636
[f0d720b]2637        elif self.data == None:
2638            self.qmin_x = _QMIN_DEFAULT
2639            self.qmax_x = _QMAX_DEFAULT
2640            self.num_points = _NPTS_DEFAULT
2641            self.state.npts = self.num_points
[5ce7f17]2642
[f0d720b]2643        elif self.data.__class__.__name__ != "Data2D":
2644            self.qmin_x = min(self.data.x)
2645            self.qmax_x = max(self.data.x)
2646            # check smearing
2647            if not self.disable_smearer.GetValue():
2648                ## set smearing value whether or
2649                # not the data contain the smearing info
2650                if self.slit_smearer.GetValue():
2651                    flag = self.update_slit_smear()
2652                elif self.pinhole_smearer.GetValue():
2653                    flag = self.update_pinhole_smear()
2654                else:
2655                    flag = True
2656        else:
2657            flag = False
[5ce7f17]2658
[f0d720b]2659        if flag == False:
2660            msg = "Cannot Plot :Must enter a number!!!  "
2661            wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
2662        else:
2663            # set relative text ctrs.
2664            self.qmin.SetValue(str(self.qmin_x))
2665            self.qmax.SetValue(str(self.qmax_x))
2666            self.show_npts2fit()
2667            # At this point, some button and variables satatus (disabled?)
2668            # should be checked such as color that should be reset to
2669            # white in case that it was pink.
2670            self._onparamEnter_helper()
2671
2672        self.save_current_state()
2673        self.state.qmin = self.qmin_x
2674        self.state.qmax = self.qmax_x
[5ce7f17]2675
[f0d720b]2676        #reset the q range values
2677        self._reset_plotting_range(self.state)
2678        self._draw_model()
[5ce7f17]2679
[f0d720b]2680    def select_log(self, event):
2681        """
2682        Log checked to generate log spaced points for theory model
2683        """
2684
2685    def get_images(self):
2686        """
2687        Get the images of the plots corresponding this panel for report
[5ce7f17]2688
[f0d720b]2689        : return graphs: list of figures
2690        : Need Move to guiframe
2691        """
2692        # set list of graphs
2693        graphs = []
2694        canvases = []
2695        res_item = None
2696        # call gui_manager
2697        gui_manager = self._manager.parent
2698        # loops through the panels [dic]
2699        for _, item2 in gui_manager.plot_panels.iteritems():
2700            data_title = self.data.group_id
2701            # try to get all plots belonging to this control panel
2702            try:
2703                g_id = item2.group_id
2704                if g_id == data_title or \
2705                        str(g_id).count("res" + str(self.graph_id)) or \
2706                        str(g_id).count(str(self.uid)) > 0:
2707                    if str(g_id).count("res" + str(self.graph_id)) > 0:
2708                        res_item = [item2.figure, item2.canvas]
2709                    else:
2710                        # append to the list
2711                        graphs.append(item2.figure)
[5ce7f17]2712                        canvases.append(item2.canvas)
[7673ecd]2713            except Exception:
[f0d720b]2714                # Not for control panels
[7673ecd]2715                logging.error(traceback.format_exc())
[f0d720b]2716        # Make sure the resduals plot goes to the last
2717        if res_item != None:
2718            graphs.append(res_item[0])
2719            canvases.append(res_item[1])
2720        # return the list of graphs
2721        return graphs, canvases
2722
[5ce7f17]2723    def on_function_help_clicked(self, event):
2724        """
2725        Function called when 'Help' button is pressed next to model
2726        of interest.  This calls DocumentationWindow from
2727        documentation_window.py. It will load the top level of the model
2728        help documenation sphinx generated html if no model is presented.
2729        If a model IS present then if documention for that model exists
2730        it will load to that  point otherwise again it will go to the top.
2731        For Wx2.8 and below is used (i.e. non-released through installer)
2732        a browser is loaded and the top of the model documentation only is
2733        accessible because webbrowser module does not pass anything after
2734        the # to the browser.
2735
2736        :param evt: on Help Button pressed event
2737        """
2738
2739        if self.model != None:
[3db44fb]2740            name = self.formfactorbox.GetValue()
[15fb4fa]2741            _TreeLocation = 'user/models/'+ name.lower()+'.html'
[6f16e25]2742            _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
[15fb4fa]2743                                              "", name + " Help")
[5ce7f17]2744        else:
[15fb4fa]2745            _TreeLocation = 'user/index.html'
[6f16e25]2746            _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
2747                                              "", "General Model Help")
[5ce7f17]2748
2749
[f0d720b]2750    def on_model_help_clicked(self, event):
2751        """
[5ce7f17]2752        Function called when 'Description' button is pressed next to model
2753        of interest.  This calls the Description embedded in the model. This
2754        should work with either Wx2.8 and lower or higher. If no model is
2755        selected it will give the message that a model must be chosen first
2756        in the box that would normally contain the description.  If a badly
2757        behaved model is encountered which has no description then it will
2758        give the message that none is available.
2759
2760        :param evt: on Description Button pressed event
[f0d720b]2761        """
[5ce7f17]2762
[f0d720b]2763        if self.model == None:
[5ce7f17]2764            name = 'index.html'
[f0d720b]2765        else:
2766            name = self.formfactorbox.GetValue()
[5ce7f17]2767
2768        msg = 'Model description:\n'
2769        info = "Info"
2770        if self.model != None:
2771#                frame.Destroy()
2772            if str(self.model.description).rstrip().lstrip() == '':
2773                msg += "Sorry, no information is available for this model."
[f0d720b]2774            else:
[5ce7f17]2775                msg += self.model.description + '\n'
2776            wx.MessageBox(msg, info)
2777        else:
2778            msg += "You must select a model to get information on this"
2779            wx.MessageBox(msg, info)
2780
[7116dffd]2781    def _on_mag_angle_help(self, event):
[5ce7f17]2782        """
2783        Bring up Magnetic Angle definition bmp image whenever the ? button
2784        is clicked. Calls DocumentationWindow with the path of the location
2785        within the documentation tree (after /doc/ ....". When using old
2786        versions of Wx (i.e. before 2.9 and therefore not part of release
2787        versions distributed via installer) it brings up an image viewer
2788        box which allows the user to click through the rest of the images in
2789        the directory.  Not ideal but probably better than alternative which
2790        would bring up the entire discussion of how magnetic models work?
2791        Specially since it is not likely to be accessed.  The normal release
2792        versions bring up the normal image box.
2793
2794        :param evt: Triggers on clicking ? in Magnetic Angles? box
2795        """
2796
2797        _TreeLocation = "_images/M_angles_pic.bmp"
[6f16e25]2798        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
[3db44fb]2799                                          "Magnetic Angle Defintions")
[f0d720b]2800
[7116dffd]2801    def _on_mag_help(self, event):
[f0d720b]2802        """
[7116dffd]2803        Bring up Magnetic Angle definition bmp image whenever the ? button
2804        is clicked. Calls DocumentationWindow with the path of the location
2805        within the documentation tree (after /doc/ ....". When using old
2806        versions of Wx (i.e. before 2.9 and therefore not part of release
2807        versions distributed via installer) it brings up an image viewer
2808        box which allows the user to click through the rest of the images in
2809        the directory.  Not ideal but probably better than alternative which
2810        would bring up the entire discussion of how magnetic models work?
2811        Specially since it is not likely to be accessed.  The normal release
2812        versions bring up the normal image box.
2813
2814        :param evt: Triggers on clicking ? in Magnetic Angles? box
[f0d720b]2815        """
2816
[e4c897b]2817        _TreeLocation = "user/magnetism.html"
[6f16e25]2818        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation, "",
[7116dffd]2819                                          "Polarized Beam/Magnetc Help")
[f0d720b]2820
[5ce7f17]2821    def _on_mag_on(self, event):
[f0d720b]2822        """
2823        Magnetic Parameters ON/OFF
2824        """
2825        button = event.GetEventObject()
2826
2827        if button.GetLabel().count('ON') > 0:
2828            self.magnetic_on = True
2829            button.SetLabel("Magnetic OFF")
2830            m_value = 1.0e-06
2831            for key in self.model.magnetic_params:
2832                if key.count('M0') > 0:
2833                    self.model.setParam(key, m_value)
2834                    m_value += 0.5e-06
2835        else:
2836            self.magnetic_on = False
2837            button.SetLabel("Magnetic ON")
2838            for key in self.model.magnetic_params:
2839                if key.count('M0') > 0:
2840                    #reset mag value to zero fo safety
2841                    self.model.setParam(key, 0.0)
[5ce7f17]2842
2843        self.Show(False)
[f0d720b]2844        self.set_model_param_sizer(self.model)
[bac3988]2845        #self._set_sizer_dispersion()
[f0d720b]2846        self.state.magnetic_on = self.magnetic_on
2847        self.SetupScrolling()
2848        self.Show(True)
[5ce7f17]2849
[f0d720b]2850    def on_pd_help_clicked(self, event):
2851        """
[5ce7f17]2852        Bring up Polydispersity Documentation whenever the ? button is clicked.
2853        Calls DocumentationWindow with the path of the location within the
2854        documentation tree (after /doc/ ....".  Note that when using old
2855        versions of Wx (before 2.9) and thus not the release version of
2856        istallers, the help comes up at the top level of the file as
2857        webbrowser does not pass anything past the # to the browser when it is
2858        running "file:///...."
2859
2860        :param evt: Triggers on clicking ? in polydispersity box
2861        """
[f0d720b]2862
[eb04d59]2863        _TreeLocation = "user/sasgui/perspectives/fitting/pd_help.html"
[7801df8]2864        _PageAnchor = ""
[6f16e25]2865        _doc_viewer = DocumentationWindow(self, wx.ID_ANY, _TreeLocation,
[3db44fb]2866                                          _PageAnchor, "Polydispersity Help")
[5ce7f17]2867
[f0d720b]2868    def on_left_down(self, event):
2869        """
2870        Get key stroke event
2871        """
2872        # Figuring out key combo: Cmd for copy, Alt for paste
2873        if event.CmdDown() and event.ShiftDown():
2874            self.get_paste()
2875        elif event.CmdDown():
2876            self.get_copy()
2877        else:
2878            event.Skip()
2879            return
2880        # make event free
2881        event.Skip()
[5ce7f17]2882
[f0d720b]2883    def get_copy(self):
2884        """
2885        Get copy params to clipboard
2886        """
2887        content = self.get_copy_params()
2888        flag = self.set_clipboard(content)
2889        self._copy_info(flag)
2890        return flag
[5ce7f17]2891
[f0d720b]2892    def get_copy_params(self):
2893        """
2894        Get the string copies of the param names and values in the tap
2895        """
2896        content = 'sasview_parameter_values:'
2897        # Do it if params exist
2898        if  self.parameters != []:
[5ce7f17]2899
[f0d720b]2900            # go through the parameters
2901            strings = self._get_copy_helper(self.parameters,
2902                                           self.orientation_params)
2903            content += strings
[5ce7f17]2904
[f0d720b]2905            # go through the fittables
2906            strings = self._get_copy_helper(self.fittable_param,
2907                                           self.orientation_params_disp)
2908            content += strings
2909
2910            # go through the fixed params
2911            strings = self._get_copy_helper(self.fixed_param,
2912                                           self.orientation_params_disp)
2913            content += strings
[5ce7f17]2914
[f0d720b]2915            # go through the str params
2916            strings = self._get_copy_helper(self.str_parameters,
2917                                           self.orientation_params)
2918            content += strings
2919            return content
2920        else:
2921            return False
2922
2923    def get_copy_excel(self):
2924        """
2925        Get copy params to clipboard
2926        """
2927        content = self.get_copy_params_excel()
2928        flag = self.set_clipboard(content)
2929        self._copy_info(flag)
2930        return flag
2931
2932    def get_copy_params_excel(self):
2933        """
2934        Get the string copies of the param names and values in the tap
2935        """
2936        content = ''
2937
2938        crlf = chr(13) + chr(10)
2939        tab = chr(9)
2940
2941        # Do it if params exist
2942        if  self.parameters != []:
2943
2944            for param in self.parameters:
2945                content += param[1] #parameter name
2946                content += tab
[5ce7f17]2947                content += param[1] + "_err"
[f0d720b]2948                content += tab
2949
2950            content += crlf
2951
2952            #row of values and errors...
2953            for param in self.parameters:
2954                content += param[2].GetValue() #value
[5ce7f17]2955                content += tab
[f0d720b]2956                content += param[4].GetValue() #error
[5ce7f17]2957                content += tab
[f0d720b]2958
2959            return content
2960        else:
2961            return False
2962
2963
2964    def get_copy_latex(self):
2965        """
2966        Get copy params to clipboard
2967        """
2968        content = self.get_copy_params_latex()
2969        flag = self.set_clipboard(content)
2970        self._copy_info(flag)
2971        return flag
2972
2973    def get_copy_params_latex(self):
2974        """
2975        Get the string copies of the param names and values in the tap
2976        """
2977        content = '\\begin{table}'
2978        content += '\\begin{tabular}[h]'
2979
2980        crlf = chr(13) + chr(10)
2981        tab = chr(9)
2982
2983        # Do it if params exist
2984        if  self.parameters != []:
2985
2986            content += '{|'
2987            for param in self.parameters:
2988                content += 'l|l|'
2989            content += '}\hline'
2990            content += crlf
2991
2992            for index, param in enumerate(self.parameters):
[5ce7f17]2993                content += param[1].replace('_', '\_') #parameter name
[f0d720b]2994                content += ' & '
[5ce7f17]2995                content += param[1].replace('_', '\_') + "\_err"
2996                if index < len(self.parameters) - 1:
[f0d720b]2997                    content += ' & '
2998            content += '\\\\ \\hline'
2999            content += crlf
3000
3001            #row of values and errors...
3002            for index, param in enumerate(self.parameters):
3003                content += param[2].GetValue() #parameter value
3004                content += ' & '
3005                content += param[4].GetValue() #parameter error
[5ce7f17]3006                if index < len(self.parameters) - 1:
[f0d720b]3007                    content += ' & '
3008            content += '\\\\ \\hline'
3009            content += crlf
3010
3011            content += '\\end{tabular}'
3012            content += '\\end{table}'
3013            return content
3014        else:
3015            return False
3016
3017
3018    def set_clipboard(self, content=None):
3019        """
3020        Put the string to the clipboard
3021        """
3022        if not content:
3023            return False
3024        if wx.TheClipboard.Open():
3025            wx.TheClipboard.SetData(wx.TextDataObject(str(content)))
3026            wx.TheClipboard.Close()
3027            return True
3028        return None
[5ce7f17]3029
[f0d720b]3030    def _get_copy_helper(self, param, orient_param):
3031        """
3032        Helping get value and name of the params
[5ce7f17]3033
[f0d720b]3034        : param param:  parameters
3035        : param orient_param = oritational params
3036        : return content: strings [list] [name,value:....]
3037        """
3038        content = ''
3039        # go through the str params
3040        for item in param:
3041            # copy only the params shown
3042            if not item[2].IsShown():
3043                continue
3044            disfunc = ''
3045            try:
3046                if item[7].__class__.__name__ == 'ComboBox':
3047                    disfunc = str(item[7].GetValue())
[7673ecd]3048            except Exception:
3049                logging.error(traceback.format_exc())
[5ce7f17]3050
[f0d720b]3051            # 2D
3052            if self.data.__class__.__name__ == "Data2D":
3053                try:
3054                    check = item[0].GetValue()
[7673ecd]3055                except Exception:
[f0d720b]3056                    check = None
3057                name = item[1]
3058                value = item[2].GetValue()
3059            # 1D
3060            else:
3061                ## for 1D all parameters except orientation
3062                if not item[1] in orient_param:
3063                    try:
3064                        check = item[0].GetValue()
3065                    except:
3066                        check = None
3067                    name = item[1]
3068                    value = item[2].GetValue()
3069
3070            # add to the content
3071            if disfunc != '':
[5ce7f17]3072
[f0d720b]3073                disfunc = ',' + disfunc
3074            # Need to support array func for copy/paste
3075            try:
3076                if disfunc.count('array') > 0:
3077                    disfunc += ','
3078                    for val in self.values[name]:
3079                        disfunc += ' ' + str(val)
3080                    disfunc += ','
3081                    for weight in self.weights[name]:
3082                        disfunc += ' ' + str(weight)
[7673ecd]3083            except Exception:
3084                logging.error(traceback.format_exc())
[f0d720b]3085            content += name + ',' + str(check) + ',' + value + disfunc + ':'
3086
3087        return content
[5ce7f17]3088
[f0d720b]3089    def get_clipboard(self):
3090        """
3091        Get strings in the clipboard
3092        """
3093        text = ""
3094        # Get text from the clip board
3095        if wx.TheClipboard.Open():
3096            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
3097                data = wx.TextDataObject()
3098                # get wx dataobject
3099                success = wx.TheClipboard.GetData(data)
3100                # get text
3101                if success:
3102                    text = data.GetText()
3103                else:
3104                    text = ''
3105            # close clipboard
3106            wx.TheClipboard.Close()
3107        return text
[5ce7f17]3108
[f0d720b]3109    def get_paste(self):
3110        """
3111        Paste params from the clipboard
3112        """
3113        text = self.get_clipboard()
3114        flag = self.get_paste_params(text)
3115        self._copy_info(flag)
3116        return flag
[5ce7f17]3117
[f0d720b]3118    def get_paste_params(self, text=''):
3119        """
3120        Get the string copies of the param names and values in the tap
3121        """
3122        context = {}
3123        # put the text into dictionary
3124        lines = text.split(':')
3125        if lines[0] != 'sasview_parameter_values':
3126            self._copy_info(False)
3127            return False
3128        for line in lines[1:-1]:
3129            if len(line) != 0:
3130                item = line.split(',')
3131                check = item[1]
3132                name = item[0]
3133                value = item[2]
3134                # Transfer the text to content[dictionary]
3135                context[name] = [check, value]
3136            # ToDo: PlugIn this poly disp function for pasting
3137            try:
3138                poly_func = item[3]
3139                context[name].append(poly_func)
3140                try:
3141                    # take the vals and weights for  array
3142                    array_values = item[4].split(' ')
3143                    array_weights = item[5].split(' ')
3144                    val = [float(a_val) for a_val in array_values[1:]]
3145                    weit = [float(a_weit) for a_weit in array_weights[1:]]
[5ce7f17]3146
[f0d720b]3147                    context[name].append(val)
3148                    context[name].append(weit)
3149                except:
3150                    raise
3151            except:
3152                poly_func = ''
3153                context[name].append(poly_func)
3154
3155        # Do it if params exist
3156        if  self.parameters != []:
3157            # go through the parameters
3158            self._get_paste_helper(self.parameters,
3159                                   self.orientation_params, context)
3160
3161            # go through the fittables
3162            self._get_paste_helper(self.fittable_param,
3163                                   self.orientation_params_disp,
3164                                   context)
3165
3166            # go through the fixed params
3167            self._get_paste_helper(self.fixed_param,
3168                                   self.orientation_params_disp, context)
[5ce7f17]3169
[f0d720b]3170            # go through the str params
3171            self._get_paste_helper(self.str_parameters,
3172                                   self.orientation_params, context)
[5ce7f17]3173
[f0d720b]3174            return True
3175        return None
[5ce7f17]3176
[f0d720b]3177    def _get_paste_helper(self, param, orient_param, content):
3178        """
3179        Helping set values of the params
[5ce7f17]3180
[f0d720b]3181        : param param:  parameters
3182        : param orient_param: oritational params
3183        : param content: dictionary [ name, value: name1.value1,...]
3184        """
3185        # go through the str params
3186        for item in param:
3187            # 2D
3188            if self.data.__class__.__name__ == "Data2D":
3189                name = item[1]
3190                if name in content.keys():
3191                    check = content[name][0]
3192                    pd = content[name][1]
3193                    if name.count('.') > 0:
[6c382da]3194                        # If this is parameter.width, then pd may be a floating
3195                        # point value or it may be an array distribution.
3196                        # Nothing to do for parameter.npts or parameter.nsigmas.
[f0d720b]3197                        try:
3198                            float(pd)
[6c382da]3199                            if name.endswith('.npts'):
3200                                pd = int(pd)
3201                        except Exception:
[f0d720b]3202                            #continue
3203                            if not pd and pd != '':
3204                                continue
3205                    item[2].SetValue(str(pd))
3206                    if item in self.fixed_param and pd == '':
3207                        # Only array func has pd == '' case.
3208                        item[2].Enable(False)
[6c382da]3209                    else:
3210                        item[2].Enable(True)
[f0d720b]3211                    if item[2].__class__.__name__ == "ComboBox":
3212                        if content[name][1] in self.model.fun_list:
3213                            fun_val = self.model.fun_list[content[name][1]]
3214                            self.model.setParam(name, fun_val)
[5ce7f17]3215
[f0d720b]3216                    value = content[name][1:]
3217                    self._paste_poly_help(item, value)
3218                    if check == 'True':
3219                        is_true = True
3220                    elif check == 'False':
3221                        is_true = False
3222                    else:
3223                        is_true = None
3224                    if is_true != None:
3225                        item[0].SetValue(is_true)
3226            # 1D
3227            else:
3228                ## for 1D all parameters except orientation
3229                if not item[1] in orient_param:
3230                    name = item[1]
3231                    if name in content.keys():
3232                        check = content[name][0]
3233                        # Avoid changing combox content
3234                        value = content[name][1:]
3235                        pd = value[0]
3236                        if name.count('.') > 0:
[6c382da]3237                            # If this is parameter.width, then pd may be a floating
3238                            # point value or it may be an array distribution.
3239                            # Nothing to do for parameter.npts or parameter.nsigmas.
[f0d720b]3240                            try:
3241                                pd = float(pd)
[6c382da]3242                                if name.endswith('.npts'):
3243                                    pd = int(pd)
[f0d720b]3244                            except:
3245                                #continue
3246                                if not pd and pd != '':
3247                                    continue
3248                        item[2].SetValue(str(pd))
3249                        if item in self.fixed_param and pd == '':
3250                            # Only array func has pd == '' case.
3251                            item[2].Enable(False)
[6c382da]3252                        else:
3253                            item[2].Enable(True)
[f0d720b]3254                        if item[2].__class__.__name__ == "ComboBox":
3255                            if value[0] in self.model.fun_list:
3256                                fun_val = self.model.fun_list[value[0]]
3257                                self.model.setParam(name, fun_val)
3258                                # save state
3259                        self._paste_poly_help(item, value)
3260                        if check == 'True':
3261                            is_true = True
3262                        elif check == 'False':
3263                            is_true = False
3264                        else:
3265                            is_true = None
3266                        if is_true != None:
3267                            item[0].SetValue(is_true)
[5ce7f17]3268
[f0d720b]3269    def _paste_poly_help(self, item, value):
3270        """
3271        Helps get paste for poly function
[5ce7f17]3272
[6c382da]3273        *item* is the parameter name
3274
3275        *value* depends on which parameter is being processed, and whether it
3276        has array polydispersity.
3277
3278        For parameters without array polydispersity:
3279
3280            parameter => ['FLOAT', '']
3281            parameter.width => ['FLOAT', 'DISTRIBUTION', '']
3282            parameter.npts => ['FLOAT', '']
3283            parameter.nsigmas => ['FLOAT', '']
3284
3285        For parameters with array polydispersity:
3286
3287            parameter => ['FLOAT', '']
3288            parameter.width => ['FILENAME', 'array', [x1, ...], [w1, ...]]
3289            parameter.npts => ['FLOAT', '']
3290            parameter.nsigmas => ['FLOAT', '']
[f0d720b]3291        """
[6c382da]3292        # Do nothing if not setting polydispersity
3293        if len(value[1]) == 0:
3294            return
[5ce7f17]3295
[6c382da]3296        try:
3297            name = item[7].Name
3298            param_name = name.split('.')[0]
3299            item[7].SetValue(value[1])
3300            selection = item[7].GetCurrentSelection()
3301            dispersity = item[7].GetClientData(selection)
3302            disp_model = dispersity()
3303
3304            if value[1] == 'array':
3305                pd_vals = numpy.array(value[2])
3306                pd_weights = numpy.array(value[3])
3307                if len(pd_vals) == 0 or len(pd_vals) != len(pd_weights):
3308                    msg = ("bad array distribution parameters for %s"
3309                           % param_name)
3310                    raise ValueError(msg)
3311                self._set_disp_cb(True, item=item)
3312                self._set_array_disp_model(name=name,
3313                                           disp=disp_model,
3314                                           values=pd_vals,
3315                                           weights=pd_weights)
3316            else:
3317                self._set_disp_cb(False, item=item)
3318                self._disp_obj_dict[name] = disp_model
3319                self.model.set_dispersion(param_name, disp_model)
3320                self.state._disp_obj_dict[name] = disp_model.type
3321                # TODO: It's not an array, why update values and weights?
3322                self.model._persistency_dict[param_name] = \
3323                    [self.values, self.weights]
3324                self.state.values = self.values
3325                self.state.weights = self.weights
[5ce7f17]3326
[6c382da]3327        except Exception:
3328            logging.error(traceback.format_exc())
3329            print "Error in BasePage._paste_poly_help: %s" % \
3330                                    sys.exc_info()[1]
3331
3332    def _set_disp_cb(self, isarray, item):
[f0d720b]3333        """
3334        Set cb for array disp
3335        """
[6c382da]3336        if isarray:
3337            item[0].SetValue(False)
3338            item[0].Enable(False)
3339            item[2].Enable(False)
3340            item[3].Show(False)
3341            item[4].Show(False)
3342            item[5].SetValue('')
3343            item[5].Enable(False)
3344            item[6].SetValue('')
3345            item[6].Enable(False)
3346        else:
3347            item[0].Enable()
3348            item[2].Enable()
3349            item[3].Show(True)
3350            item[4].Show(True)
3351            item[5].Enable()
3352            item[6].Enable()
[5ce7f17]3353
[f0d720b]3354    def update_pinhole_smear(self):
3355        """
3356            Method to be called by sub-classes
3357            Moveit; This method doesn't belong here
3358        """
3359        print "BasicPage.update_pinhole_smear was called: skipping"
3360        return
3361
3362    def _read_category_info(self):
3363        """
3364        Reads the categories in from file
3365        """
3366        # # ILL mod starts here - July 2012 kieranrcampbell@gmail.com
3367        self.master_category_dict = defaultdict(list)
3368        self.by_model_dict = defaultdict(list)
3369        self.model_enabled_dict = defaultdict(bool)
[212bfc2]3370        categorization_file = CategoryInstaller.get_user_file()
3371        with open(categorization_file, 'rb') as f:
3372            self.master_category_dict = json.load(f)
3373        self._regenerate_model_dict()
[f0d720b]3374
3375    def _regenerate_model_dict(self):
3376        """
[5ce7f17]3377        regenerates self.by_model_dict which has each model name as the
[f0d720b]3378        key and the list of categories belonging to that model
3379        along with the enabled mapping
3380        """
3381        self.by_model_dict = defaultdict(list)
3382        for category in self.master_category_dict:
3383            for (model, enabled) in self.master_category_dict[category]:
3384                self.by_model_dict[model].append(category)
3385                self.model_enabled_dict[model] = enabled
[5ce7f17]3386
[f0d720b]3387    def _populate_listbox(self):
3388        """
3389        fills out the category list box
3390        """
3391        uncat_str = 'Customized Models'
3392        self._read_category_info()
3393
3394        self.categorybox.Clear()
3395        cat_list = sorted(self.master_category_dict.keys())
3396        if not uncat_str in cat_list:
3397            cat_list.append(uncat_str)
[5ce7f17]3398
[f0d720b]3399        for category in cat_list:
3400            if category != '':
3401                self.categorybox.Append(category)
3402
3403        if self.categorybox.GetSelection() == wx.NOT_FOUND:
3404            self.categorybox.SetSelection(0)
3405        else:
[5ce7f17]3406            self.categorybox.SetSelection(\
[f0d720b]3407                self.categorybox.GetSelection())
3408        #self._on_change_cat(None)
3409
3410    def _on_change_cat(self, event):
3411        """
3412        Callback for category change action
3413        """
3414        self.model_name = None
3415        category = self.categorybox.GetStringSelection()
3416        if category == None:
3417            return
3418        self.model_box.Clear()
3419
3420        if category == 'Customized Models':
3421            for model in self.model_list_box[category]:
3422                str_m = str(model).split(".")[0]
3423                self.model_box.Append(str_m)
3424
3425        else:
[5ce7f17]3426            for (model, enabled) in sorted(self.master_category_dict[category],
3427                                      key=lambda name: name[0]):
[f0d720b]3428                if(enabled):
3429                    self.model_box.Append(model)
3430
3431    def _fill_model_sizer(self, sizer):
3432        """
3433        fill sizer containing model info
3434        """
[6f16e25]3435        # This should only be called once per fit tab
3436        #print "==== Entering _fill_model_sizer"
[f0d720b]3437        ##Add model function Details button in fitpanel.
3438        ##The following 3 lines are for Mac. Let JHC know before modifying...
3439        title = "Model"
3440        self.formfactorbox = None
3441        self.multifactorbox = None
[6f16e25]3442        self.mbox_description = wx.StaticBox(self, wx.ID_ANY, str(title))
[f0d720b]3443        boxsizer1 = wx.StaticBoxSizer(self.mbox_description, wx.VERTICAL)
3444        sizer_cat = wx.BoxSizer(wx.HORIZONTAL)
3445        self.mbox_description.SetForegroundColour(wx.RED)
[6f16e25]3446        wx_id = self._ids.next()
3447        self.model_func = wx.Button(self, wx_id, 'Help', size=(80, 23))
3448        self.model_func.Bind(wx.EVT_BUTTON, self.on_function_help_clicked,
3449                             id=wx_id)
[5ce7f17]3450        self.model_func.SetToolTipString("Full Model Function Help")
[6f16e25]3451        wx_id = self._ids.next()
3452        self.model_help = wx.Button(self, wx_id, 'Description', size=(80, 23))
3453        self.model_help.Bind(wx.EVT_BUTTON, self.on_model_help_clicked,
3454                             id=wx_id)
[5ce7f17]3455        self.model_help.SetToolTipString("Short Model Function Description")
[6f16e25]3456        wx_id = self._ids.next()
3457        self.model_view = wx.Button(self, wx_id, "Show 2D", size=(80, 23))
3458        self.model_view.Bind(wx.EVT_BUTTON, self._onModel2D, id=wx_id)
[f0d720b]3459        hint = "toggle view of model from 1D to 2D  or 2D to 1D"
3460        self.model_view.SetToolTipString(hint)
[5ce7f17]3461
[6f16e25]3462        cat_set_box = wx.StaticBox(self, wx.ID_ANY, 'Category')
[f0d720b]3463        sizer_cat_box = wx.StaticBoxSizer(cat_set_box, wx.HORIZONTAL)
3464        sizer_cat_box.SetMinSize((200, 50))
[6f16e25]3465        self.categorybox = wx.ComboBox(self, wx.ID_ANY,
3466                                       style=wx.CB_READONLY)
[5ce7f17]3467        self.categorybox.SetToolTip(wx.ToolTip("Select a Category/Type"))
[f0d720b]3468        self._populate_listbox()
[6f16e25]3469        wx.EVT_COMBOBOX(self.categorybox, wx.ID_ANY, self._show_combox)
3470        #self.shape_rbutton = wx.RadioButton(self, wx.ID_ANY, 'Shapes',
[f0d720b]3471        #                                     style=wx.RB_GROUP)
[6f16e25]3472        #self.shape_indep_rbutton = wx.RadioButton(self, wx.ID_ANY,
[f0d720b]3473        #                                          "Shape-Independent")
[6f16e25]3474        #self.struct_rbutton = wx.RadioButton(self, wx.ID_ANY,
3475        #                                     "Structure Factor ")
3476        #self.plugin_rbutton = wx.RadioButton(self, wx.ID_ANY,
3477        #                                     "Uncategorized")
[5ce7f17]3478
[f0d720b]3479        #self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
3480        #                   id=self.shape_rbutton.GetId())
3481        #self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
3482        #                    id=self.shape_indep_rbutton.GetId())
3483        #self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
3484        #                    id=self.struct_rbutton.GetId())
3485        #self.Bind(wx.EVT_RADIOBUTTON, self._show_combox,
3486        #                    id=self.plugin_rbutton.GetId())
3487        #MAC needs SetValue
[5ce7f17]3488
[6f16e25]3489        show_cat_button = wx.Button(self, wx.ID_ANY, "Modify")
[f0d720b]3490        cat_tip = "Modify model categories \n"
3491        cat_tip += "(also accessible from the menu bar)."
3492        show_cat_button.SetToolTip( wx.ToolTip(cat_tip) )
3493        show_cat_button.Bind(wx.EVT_BUTTON, self._on_modify_cat)
3494        sizer_cat_box.Add(self.categorybox, 1, wx.RIGHT, 3)
3495        sizer_cat_box.Add((10,10))
3496        sizer_cat_box.Add(show_cat_button)
3497        #self.shape_rbutton.SetValue(True)
[bac3988]3498
[f0d720b]3499        sizer_radiobutton = wx.GridSizer(2, 2, 5, 5)
3500        #sizer_radiobutton.Add(self.shape_rbutton)
3501        #sizer_radiobutton.Add(self.shape_indep_rbutton)
3502        sizer_radiobutton.Add((5,5))
[5ce7f17]3503        sizer_radiobutton.Add(self.model_view, 1, wx.RIGHT, 5)
[f0d720b]3504        #sizer_radiobutton.Add(self.plugin_rbutton)
3505        #sizer_radiobutton.Add(self.struct_rbutton)
[5ce7f17]3506#        sizer_radiobutton.Add((5,5))
3507        sizer_radiobutton.Add(self.model_help, 1, wx.RIGHT | wx.LEFT, 5)
3508#        sizer_radiobutton.Add((5,5))
3509        sizer_radiobutton.Add(self.model_func, 1, wx.RIGHT, 5)
[f0d720b]3510        sizer_cat.Add(sizer_cat_box, 1, wx.LEFT, 2.5)
3511        sizer_cat.Add(sizer_radiobutton)
3512        sizer_selection = wx.BoxSizer(wx.HORIZONTAL)
3513        mutifactor_selection = wx.BoxSizer(wx.HORIZONTAL)
[5ce7f17]3514
[6f16e25]3515        self.text1 = wx.StaticText(self, wx.ID_ANY, "")
3516        self.text2 = wx.StaticText(self, wx.ID_ANY, "P(Q)*S(Q)")
3517        self.mutifactor_text = wx.StaticText(self, wx.ID_ANY, "No. of Shells: ")
3518        self.mutifactor_text1 = wx.StaticText(self, wx.ID_ANY, "")
3519        self.show_sld_button = wx.Button(self, wx.ID_ANY, "Show SLD Profile")
[f0d720b]3520        self.show_sld_button.Bind(wx.EVT_BUTTON, self._on_show_sld)
3521
[6f16e25]3522        self.formfactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
[5ce7f17]3523        self.formfactorbox.SetToolTip(wx.ToolTip("Select a Model"))
[f0d720b]3524        if self.model != None:
3525            self.formfactorbox.SetValue(self.model.name)
[6f16e25]3526        self.structurebox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
3527        self.multifactorbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
[f0d720b]3528        self.initialize_combox()
[6f16e25]3529        wx.EVT_COMBOBOX(self.formfactorbox, wx.ID_ANY, self._on_select_model)
[f0d720b]3530
[6f16e25]3531        wx.EVT_COMBOBOX(self.structurebox, wx.ID_ANY, self._on_select_model)
3532        wx.EVT_COMBOBOX(self.multifactorbox, wx.ID_ANY, self._on_select_model)
[f0d720b]3533        ## check model type to show sizer
3534        if self.model != None:
3535            print "_set_model_sizer_selection: disabled."
3536            #self._set_model_sizer_selection(self.model)
[5ce7f17]3537
[f0d720b]3538        sizer_selection.Add(self.text1)
3539        sizer_selection.Add((10, 5))
3540        sizer_selection.Add(self.formfactorbox)
3541        sizer_selection.Add((5, 5))
3542        sizer_selection.Add(self.text2)
3543        sizer_selection.Add((5, 5))
3544        sizer_selection.Add(self.structurebox)
[5ce7f17]3545
[f0d720b]3546        mutifactor_selection.Add((13, 5))
3547        mutifactor_selection.Add(self.mutifactor_text)
3548        mutifactor_selection.Add(self.multifactorbox)
3549        mutifactor_selection.Add((5, 5))
3550        mutifactor_selection.Add(self.mutifactor_text1)
3551        mutifactor_selection.Add((10, 5))
3552        mutifactor_selection.Add(self.show_sld_button)
3553
3554        boxsizer1.Add(sizer_cat)
3555        boxsizer1.Add((10, 10))
3556        boxsizer1.Add(sizer_selection)
3557        boxsizer1.Add((10, 10))
3558        boxsizer1.Add(mutifactor_selection)
[5ce7f17]3559
[f0d720b]3560        self._set_multfactor_combobox()
3561        self.multifactorbox.SetSelection(1)
3562        self.show_sld_button.Hide()
3563        sizer.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
3564        sizer.Layout()
[5ce7f17]3565
[f0d720b]3566    def on_smear_helper(self, update=False):
3567        """
3568        Help for onSmear if implemented
[5ce7f17]3569
[f0d720b]3570        :param update: force or not to update
3571        """
3572    def reset_page(self, state, first=False):
3573        """
3574        reset the state  if implemented
3575        """
3576    def onSmear(self, event):
3577        """
[5ce7f17]3578        Create a smear object if implemented
[f0d720b]3579        """
3580    def onPinholeSmear(self, event):
3581        """
3582        Create a custom pinhole smear object if implemented
3583        """
3584    def onSlitSmear(self, event):
3585        """
3586        Create a custom slit smear object if implemented
3587        """
3588    def update_slit_smear(self):
3589        """
3590        called by kill_focus on pinhole TextCntrl
3591        to update the changes if implemented
3592        """
3593    def select_param(self, event):
3594        """
3595        Select TextCtrl  checked if implemented
3596        """
3597    def set_data(self, data=None):
3598        """
3599        Sets data if implemented
3600        """
3601    def _is_2D(self):
3602        """
3603        Check if data_name is Data2D if implemented
3604        """
3605    def _on_select_model(self, event=None):
3606        """
3607        call back for model selection if implemented
3608        """
3609    def get_weight_flag(self):
3610        """
3611        Get flag corresponding to a given weighting dI data if implemented
3612        """
3613    def _set_sizer_dispersion(self):
3614        """
3615        draw sizer for dispersity if implemented
3616        """
3617    def get_all_checked_params(self):
3618        """
3619        Found all parameters current check and add them to list of parameters
3620        to fit if implemented
3621        """
3622    def show_npts2fit(self):
3623        """
3624        setValue Npts for fitting if implemented
3625        """
3626    def _onModel2D(self, event):
3627        """
3628        toggle view of model from 1D to 2D  or 2D from 1D if implemented
3629        """
[373d4ee]3630
3631class ModelTextCtrl(wx.TextCtrl):
3632    """
3633    Text control for model and fit parameters.
3634    Binds the appropriate events for user interactions.
3635    Default callback methods can be overwritten on initialization
3636
3637    :param kill_focus_callback: callback method for EVT_KILL_FOCUS event
3638    :param set_focus_callback:  callback method for EVT_SET_FOCUS event
3639    :param mouse_up_callback:   callback method for EVT_LEFT_UP event
3640    :param text_enter_callback: callback method for EVT_TEXT_ENTER event
3641
3642    """
3643    ## Set to True when the mouse is clicked while whole string is selected
3644    full_selection = False
3645    ## Call back for EVT_SET_FOCUS events
3646    _on_set_focus_callback = None
3647
3648    def __init__(self, parent, id=-1,
3649                 value=wx.EmptyString,
3650                 pos=wx.DefaultPosition,
3651                 size=wx.DefaultSize,
3652                 style=0,
3653                 validator=wx.DefaultValidator,
3654                 name=wx.TextCtrlNameStr,
3655                 kill_focus_callback=None,
3656                 set_focus_callback=None,
3657                 mouse_up_callback=None,
3658                 text_enter_callback=None):
3659
3660        wx.TextCtrl.__init__(self, parent, id, value, pos,
3661                             size, style, validator, name)
3662
3663        # Bind appropriate events
3664        self._on_set_focus_callback = parent.onSetFocus \
3665            if set_focus_callback is None else set_focus_callback
3666        self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
3667        self.Bind(wx.EVT_KILL_FOCUS, self._silent_kill_focus \
3668            if kill_focus_callback is None else kill_focus_callback)
3669        self.Bind(wx.EVT_TEXT_ENTER, parent._onparamEnter \
3670            if text_enter_callback is None else text_enter_callback)
3671        if not ON_MAC:
3672            self.Bind(wx.EVT_LEFT_UP, self._highlight_text \
3673                if mouse_up_callback is None else mouse_up_callback)
3674
3675    def _on_set_focus(self, event):
3676        """
3677        Catch when the text control is set in focus to highlight the whole
3678        text if necessary
3679
3680        :param event: mouse event
3681
3682        """
3683        event.Skip()
3684        self.full_selection = True
3685        return self._on_set_focus_callback(event)
3686
3687    def _highlight_text(self, event):
3688        """
3689        Highlight text of a TextCtrl only of no text has be selected
3690
3691        :param event: mouse event
3692
3693        """
3694        # Make sure the mouse event is available to other listeners
3695        event.Skip()
3696        control = event.GetEventObject()
3697        if self.full_selection:
3698            self.full_selection = False
3699            # Check that we have a TextCtrl
3700            if issubclass(control.__class__, wx.TextCtrl):
3701                # Check whether text has been selected,
3702                # if not, select the whole string
3703                (start, end) = control.GetSelection()
3704                if start == end:
3705                    control.SetSelection(-1, -1)
3706
3707    def _silent_kill_focus(self, event):
3708        """
3709        Save the state of the page
3710        """
3711
3712        event.Skip()
3713        #pass
Note: See TracBrowser for help on using the repository browser.