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

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 277257f was 277257f, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

clean up plugin-model handling code; preserve active parameter values when plugin is updated

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