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

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

remove wx references from fitting pagestate

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