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

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

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

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