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

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 251ef684 was 65f3930, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

move models.py to sascalc/fit

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