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

magnetic_scattrelease-4.2.2ticket-1009ticket-1249
Last change on this file since cb64d86 was cb64d86, checked in by Paul Kienzle <pkienzle@…>, 8 months ago

src/sas/sasgui/guiframe/CategoryInstaller.py

consistent python 2/3 handling of json dump

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