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

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 a0469a1 was a0469a1, checked in by Tim Snow <tim.snow@…>, 7 years ago

Removed 'non-Pythonic' variable names

As per Quantified Code warnings, however, unsure why ‘from time import
time’ comes up as an error.

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