source: sasview/src/sas/sasgui/perspectives/fitting/basepage.py @ 1a8e13f0

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 1a8e13f0 was 1a8e13f0, checked in by Paul Kienzle <pkienzle@…>, 3 years ago

remove unnecessary sleep

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