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

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 1cdbcd8 was 69363c7, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

Merge branch 'master' into ticket-853-fit-gui-to-calc

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