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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ba8d326 was ba8d326, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

code cleanup

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