source: sasview/src/sas/sasgui/perspectives/fitting/simfitpage.py @ 69363c7

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

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

  • Property mode set to 100644
File size: 42.3 KB
Line 
1"""
2    Simultaneous or Batch fit page
3"""
4# Note that this is used for both Simultaneous/Constrained fit AND for
5# combined batch fit.  This is done through setting of the batch_on parameter.
6# There are the a half dozen or so places where an if statement is used as in
7# if not batch_on:
8#     xxxx
9# else:
10#     xxxx
11# This is just wrong but dont have time to fix this go. Proper approach would be
12# to strip all parts of the code that depend on batch_on and create the top
13# level class from which a contrained/simultaneous fit page and a combined
14# batch page inherit.
15#
16#            04/09/2017   --PDB
17
18import sys
19from collections import namedtuple
20
21import wx
22import wx.lib.newevent
23from wx.lib.scrolledpanel import ScrolledPanel
24
25from sas.sascalc.fit.pagestate import SimFitPageState
26from sas.sasgui.guiframe.events import StatusEvent, PanelOnFocusEvent
27from sas.sasgui.guiframe.panel_base import PanelBase
28from sas.sasgui.guiframe.utils import IdList
29from sas.sasgui.guiframe.documentation_window import DocumentationWindow
30
31# Control panel width
32if sys.platform.count("darwin") == 0:
33    PANEL_WID = 420
34    FONT_VARIANT = 0
35else:
36    PANEL_WID = 490
37    FONT_VARIANT = 1
38
39
40# Each constraint requires five widgets and sizer.  Package them in
41# a named tuple for easy access.
42ConstraintLine = namedtuple('ConstraintLine',
43        'model_cbox param_cbox egal_txt constraint btRemove sizer')
44
45
46def get_fittableParam(model):
47    """
48    return list of fittable parameters from a model
49
50    :param model: the model used
51
52    """
53    fittable_param = []
54    for item in model.getParamList():
55        if not item  in model.getDispParamList():
56            if not item in model.non_fittable:
57                fittable_param.append(item)
58
59    for item in model.fixed:
60        fittable_param.append(item)
61
62    return fittable_param
63
64class SimultaneousFitPage(ScrolledPanel, PanelBase):
65    """
66    Simultaneous fitting panel
67    All that needs to be defined are the
68    two data members window_name and window_caption
69    """
70    # Internal name for the AUI manager
71    window_name = "Simultaneous Fit Page"
72    # Title to appear on top of the window
73    window_caption = "Simultaneous Fit Page"
74    ID_DOC = wx.NewId()
75    ID_SET_ALL = wx.NewId()
76    ID_FIT = wx.NewId()
77    ID_ADD = wx.NewId()
78    _id_pool = IdList()
79
80    def __init__(self, parent, page_finder={}, id=wx.ID_ANY, batch_on=False,
81                 *args, **kwargs):
82        ScrolledPanel.__init__(self, parent, id=id,
83                               style=wx.FULL_REPAINT_ON_RESIZE,
84                               *args, **kwargs)
85        PanelBase.__init__(self, parent)
86        """
87        Simultaneous page display
88        """
89        self._ids = iter(self._id_pool)
90        self.SetupScrolling()
91        # Font size
92        self.SetWindowVariant(variant=FONT_VARIANT)
93        self.uid = wx.NewId()
94        self.parent = parent
95        self.batch_on = batch_on
96        # store page_finder
97        self.page_finder = page_finder
98        # list containing info to set constraint
99        # look like self.constraint_dict[page_id]= page
100        self.constraint_dict = {}
101        # item list
102        # self.constraints_list=[combobox1, combobox2,=,textcrtl, button ]
103        self.constraints_list = []
104        # list of current model
105        self.model_list = []
106        # selected model to fit
107        self.model_to_fit = []
108        # Control the fit state
109        self.fit_started = False
110        # number of constraint
111        self.nb_constraint = 0
112        self.state = SimFitPageState()
113        self.model_cbox_left = None
114        self.model_cbox_right = None
115        # draw page
116        self.define_page_structure()
117        self.draw_page()
118        self._set_save_flag(False)
119
120    def define_page_structure(self):
121        """
122        Create empty sizers, their hierarchy and set the sizer for the panel
123        """
124        self.vbox = wx.BoxSizer(wx.VERTICAL)
125        self.data_selection_sizer = wx.BoxSizer(wx.VERTICAL)
126        self.constraints_sizer = wx.BoxSizer(wx.VERTICAL)
127        self.run_fit_sizer = wx.BoxSizer(wx.VERTICAL)
128
129        self.data_selection_sizer.SetMinSize((PANEL_WID, -1))
130        self.constraints_sizer.SetMinSize((PANEL_WID, -1))
131        self.run_fit_sizer.SetMinSize((PANEL_WID, -1))
132        self.vbox.Add(self.data_selection_sizer)
133        self.vbox.Add(self.constraints_sizer)
134        self.vbox.Add(self.run_fit_sizer)
135        self.SetSizer(self.vbox)
136        self.Centre()
137
138    def set_state(self):
139        """
140        Define a set of state parameters for saving simultaneous fits.
141        """
142        self._set_constraint()
143        self.state.fit_page_no = self.uid
144        self.state.select_all = self.cb1.GetValue()
145        self.state.model_list = self.model_list
146        self.state.model_to_fit = self.model_to_fit
147        self.state.no_constraint = self.nb_constraint
148        self.state.constraint_dict = self.constraint_dict
149        self.state.constraints_list = self.constraints_list
150        return self.get_state()
151
152    def get_state(self):
153        """
154        Return the state of the current page
155        :return: self.state
156        """
157        return self.state
158
159    def load_from_save_state(self, sim_state):
160        """
161        Load in a simultaneous/constrained fit from a save state
162        :param fit: Fitpanel object
163        :return: None
164        """
165        model_map = {}
166        # Process each model and associate old M# with new M#
167        i = 0
168        for model in self.model_list:
169            model_id = self._format_id(model[1].keys()[0])
170            for saved_model in sim_state.model_list:
171                save_id = saved_model.pop('name')
172                saved_model['name'] = save_id
173                save_id = self._format_id(save_id)
174                if save_id == model_id:
175                    model_map[saved_model.pop('fit_page_source')] = \
176                        model[3].name
177                    check = bool(saved_model.pop('checked'))
178                    self.model_list[i][0].SetValue(check)
179                    break
180            i += 1
181
182        self.check_model_name(None)
183
184        if len(sim_state.constraints_list) > 0:
185            self.hide_constraint.SetValue(False)
186            self.show_constraint.SetValue(True)
187            self._display_constraint(None)
188
189        for index, item in enumerate(sim_state.constraints_list):
190            model_cbox = item.pop('model_cbox')
191            if model_cbox != "":
192                constraint_value = item.pop('constraint')
193                param = item.pop('param_cbox')
194                equality = item.pop('egal_txt')
195                for key, value in model_map.iteritems():
196                    model_cbox.replace(key, value)
197                    constraint_value.replace(key, value)
198
199                self.constraints_list[index][0].SetValue(model_cbox)
200                self._on_select_model(None)
201                self.constraints_list[index][1].SetValue(param)
202                self.constraints_list[index][2].SetLabel(equality)
203                self.constraints_list[index][3].SetValue(constraint_value)
204                self._on_add_constraint(None)
205                self._manager.sim_page = self
206
207    def _format_id(self, original_id):
208        original_id = original_id.rstrip('1234567890.')
209        new_id_list = original_id.split()
210        new_id = ' '.join(new_id_list)
211        return new_id
212
213
214
215    def draw_page(self):
216        """
217        Construct the Simultaneous/Constrained fit page. fills the first
218        region (sizer1) with the list of available fit page pairs of data
219        and models.  Then fills sizer2 with the checkbox for adding
220        constraints, and finally fills sizer3 with the fit button and
221        instructions.
222        """
223
224        # create blank list of constraints
225        self.model_list = []
226        self.model_to_fit = []
227        self.constraints_list = []
228        self.constraint_dict = {}
229        self.nb_constraint = 0
230        self.model_cbox_left = None
231        self.model_cbox_right = None
232
233        if len(self.model_list) > 0:
234            for item in self.model_list:
235                item[0].SetValue(False)
236                self.manager.schedule_for_fit(value=0, uid=item[2])
237
238        #-------------------------------------------------------
239        # setup sizer1 (which fitpages to include)
240        self.data_selection_sizer.Clear(True)
241        box_description = wx.StaticBox(self, wx.ID_ANY, "Fit Combinations")
242        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
243        sizer_title = wx.BoxSizer(wx.HORIZONTAL)
244        sizer_couples = wx.GridBagSizer(5, 5)
245
246        # The wx GUI has a flag to enable a menu item, but can still be
247        # reached via scripting. There is no guearantee future GUI
248        # implementations force this check, either.
249        # IMHO, this if statement should stay -- JRK 2016-OCT-05
250        if len(self.page_finder) == 0:
251            msg = " No fit combinations are found! \n\n"
252            msg += " Please load data and set up "
253            msg += "at least one fit panels first..."
254            sizer_title.Add(wx.StaticText(self, wx.ID_ANY, msg))
255        else:
256            # store model
257            self._store_model()
258
259            self.cb1 = wx.CheckBox(self, wx.ID_ANY, 'Select all')
260            self.cb1.SetValue(False)
261            wx.EVT_CHECKBOX(self, self.cb1.GetId(), self.check_all_model_name)
262
263            sizer_title.Add((10, 10), 0,
264                wx.TOP | wx.BOTTOM | wx.EXPAND | wx.ADJUST_MINSIZE, border=5)
265            sizer_title.Add(self.cb1, 0,
266                wx.TOP | wx.BOTTOM | wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE,
267                border=5)
268
269            # draw list of model and data names
270            self._fill_sizer_model_list(sizer_couples)
271
272        boxsizer1.Add(sizer_title, flag=wx.TOP | wx.BOTTOM, border=5)
273        boxsizer1.Add(sizer_couples, 1, flag=wx.TOP | wx.BOTTOM, border=5)
274        self.data_selection_sizer.Add(boxsizer1, 1, wx.EXPAND | wx.ALL, 10)
275        # self.sizer1.Layout()
276
277        #--------------------------------------------------------
278        # set up the other 2 sizers: the constraints list and the
279        # buttons (fit, help etc) sizer at the bottom of the page.
280        # Note: the if statement should be removed along with the above
281        # if statement as soon as it can be properly tested.
282        # Nov. 22 2015  --PDB
283        # As above, this page can be accessed through other means than the
284        # base SasView GUI.
285        # Oct. 5, 2016 --JRK
286        if len(self.page_finder) > 0:
287            # draw the sizer containing constraint info
288            if not self.batch_on:
289                self._fill_sizer_constraint()
290            # draw fit button sizer
291            self._fill_sizer_fit()
292
293    def _fill_sizer_model_list(self, sizer):
294        """
295        Receive a dictionary containing information to display model name
296        """
297        ix = 0
298        iy = 0
299        sizer.Clear(True)
300
301        new_name = wx.StaticText(self, wx.ID_ANY, '  Model Title ',
302                                 style=wx.ALIGN_CENTER)
303        new_name.SetBackgroundColour('orange')
304        new_name.SetForegroundColour(wx.WHITE)
305        sizer.Add(new_name, (iy, ix), (1, 1),
306                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
307        ix += 2
308        model_type = wx.StaticText(self, wx.ID_ANY, '  Model ')
309        model_type.SetBackgroundColour('grey')
310        model_type.SetForegroundColour(wx.WHITE)
311        sizer.Add(model_type, (iy, ix), (1, 1),
312                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
313        ix += 1
314        data_used = wx.StaticText(self, wx.ID_ANY, '  Data ')
315        data_used.SetBackgroundColour('grey')
316        data_used.SetForegroundColour(wx.WHITE)
317        sizer.Add(data_used, (iy, ix), (1, 1),
318                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
319        ix += 1
320        tab_used = wx.StaticText(self, wx.ID_ANY, '  FitPage ')
321        tab_used.SetBackgroundColour('grey')
322        tab_used.SetForegroundColour(wx.WHITE)
323        sizer.Add(tab_used, (iy, ix), (1, 1),
324                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
325        for id, value in self.page_finder.iteritems():
326            if id not in self.parent.opened_pages:
327                continue
328
329            if self.batch_on != self.parent.get_page_by_id(id).batch_on:
330                continue
331
332            data_list = []
333            model_list = []
334            # get data name and model objetta
335            for fitproblem in value.get_fit_problem():
336
337                data = fitproblem.get_fit_data()
338                if not data.is_data:
339                    continue
340                name = '-'
341                if data is not None and data.is_data:
342                    name = str(data.name)
343                data_list.append(name)
344
345                model = fitproblem.get_model()
346                if model is None:
347                    continue
348                model_list.append(model)
349
350            if len(model_list) == 0:
351                continue
352            # Draw sizer
353            ix = 0
354            iy += 1
355            model = model_list[0]
356            name = '_'
357            if model is not None:
358                name = str(model.name)
359            cb = wx.CheckBox(self, wx.ID_ANY, name)
360            cb.SetValue(False)
361            cb.Enable(model is not None and data.is_data)
362            sizer.Add(cb, (iy, ix), (1, 1),
363                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
364            wx.EVT_CHECKBOX(self, cb.GetId(), self.check_model_name)
365            ix += 2
366            model_type = wx.StaticText(self, wx.ID_ANY,
367                                       model.__class__.__name__)
368            sizer.Add(model_type, (iy, ix), (1, 1),
369                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
370            if self.batch_on:
371                data_used = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
372                data_used.AppendItems(data_list)
373                data_used.SetSelection(0)
374            else:
375                data_used = wx.StaticText(self, wx.ID_ANY, data_list[0])
376
377            ix += 1
378            sizer.Add(data_used, (iy, ix), (1, 1),
379                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
380            ix += 1
381            caption = value.get_fit_tab_caption()
382            tab_caption_used = wx.StaticText(self, wx.ID_ANY, str(caption))
383            sizer.Add(tab_caption_used, (iy, ix), (1, 1),
384                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
385
386            self.model_list.append([cb, value, id, model])
387
388        iy += 1
389        sizer.Add((20, 20), (iy, ix), (1, 1),
390                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
391
392    def _fill_sizer_constraint(self):
393        """
394        Fill sizer containing constraint info
395        """
396        msg = "Select at least 1 model to add constraint "
397        wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
398
399        self.constraints_sizer.Clear(True)
400        if self.batch_on:
401            if self.constraints_sizer.IsShown():
402                self.constraints_sizer.Show(False)
403            return
404        box_description = wx.StaticBox(self, wx.ID_ANY, "Fit Constraints")
405        box_sizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
406        sizer_title = wx.BoxSizer(wx.HORIZONTAL)
407        self.sizer_all_constraints = wx.BoxSizer(wx.HORIZONTAL)
408        self.sizer_constraints = wx.BoxSizer(wx.VERTICAL)
409        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
410
411        self.hide_constraint = wx.RadioButton(self, wx.ID_ANY, 'No', (10, 10),
412                                              style=wx.RB_GROUP)
413        self.show_constraint = wx.RadioButton(self, wx.ID_ANY, 'Yes', (10, 30))
414        self.Bind(wx.EVT_RADIOBUTTON, self._display_constraint,
415                  id=self.hide_constraint.GetId())
416        self.Bind(wx.EVT_RADIOBUTTON, self._display_constraint,
417                  id=self.show_constraint.GetId())
418        if self.batch_on:
419            self.hide_constraint.Enable(False)
420            self.show_constraint.Enable(False)
421        self.hide_constraint.SetValue(True)
422        self.show_constraint.SetValue(False)
423
424        sizer_title.Add(wx.StaticText(self, wx.ID_ANY, " Model"))
425        sizer_title.Add((10, 10))
426        sizer_title.Add(wx.StaticText(self, wx.ID_ANY, " Parameter"))
427        sizer_title.Add((10, 10))
428        sizer_title.Add(wx.StaticText(self, wx.ID_ANY, " Add Constraint?"))
429        sizer_title.Add((10, 10))
430        sizer_title.Add(self.show_constraint)
431        sizer_title.Add(self.hide_constraint)
432        sizer_title.Add((10, 10))
433
434        self.btAdd = wx.Button(self, self.ID_ADD, 'Add')
435        self.btAdd.Bind(wx.EVT_BUTTON, self._on_add_constraint,
436                        id=self.btAdd.GetId())
437        self.btAdd.SetToolTipString("Add another constraint?")
438        self.btAdd.Hide()
439
440        text_hint = wx.StaticText(self, wx.ID_ANY,
441                                  "Example: [M0][parameter] = M1.parameter")
442        sizer_button.Add(text_hint, 0,
443                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
444        sizer_button.Add(self.btAdd, 0,
445                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
446
447        box_sizer1.Add(sizer_title, flag=wx.TOP | wx.BOTTOM, border=10)
448        box_sizer1.Add(self.sizer_all_constraints, flag=wx.TOP | wx.BOTTOM,
449                       border=10)
450        box_sizer1.Add(self.sizer_constraints, flag=wx.TOP | wx.BOTTOM,
451                       border=10)
452        box_sizer1.Add(sizer_button, flag=wx.TOP | wx.BOTTOM, border=10)
453
454        self.constraints_sizer.Add(box_sizer1, 0, wx.EXPAND | wx.ALL, 10)
455
456    def _fill_sizer_fit(self):
457        """
458        Draw fit button
459        """
460        self.run_fit_sizer.Clear(True)
461        box_description = wx.StaticBox(self, wx.ID_ANY, "Fit ")
462        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
463        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
464
465        # Fit button
466        self.btFit = wx.Button(self, self.ID_FIT, 'Fit', size=wx.DefaultSize)
467        self.btFit.Bind(wx.EVT_BUTTON, self.on_fit, id=self.btFit.GetId())
468        self.btFit.SetToolTipString("Perform fit.")
469
470        # General Help button
471        self.btHelp = wx.Button(self, wx.ID_HELP, 'HELP')
472        if self.batch_on:
473            self.btHelp.SetToolTipString("Combined Batch Fitting help.")
474        else:
475            self.btHelp.SetToolTipString("Simultaneous/Constrained Fitting help.")
476        self.btHelp.Bind(wx.EVT_BUTTON, self._on_help)
477
478        # hint text on button line
479        if self.batch_on:
480            text = " Fit in Parallel all Data sets\n"
481            text += "and model selected."
482        else:
483            text = " At least one set of model and data\n"
484            text += " must be selected for fitting."
485        text_hint = wx.StaticText(self, wx.ID_ANY, text)
486
487        sizer_button.Add(text_hint)
488        sizer_button.Add(self.btFit, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
489        sizer_button.Add(self.btHelp, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
490
491        boxsizer1.Add(sizer_button, flag=wx.TOP | wx.BOTTOM, border=10)
492        self.run_fit_sizer.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
493
494    def on_remove(self, event):
495        """
496        Remove constraint fields
497        """
498        if len(self.constraints_list) == 1:
499            self.hide_constraint.SetValue(True)
500            self._hide_constraint()
501            return
502        if len(self.constraints_list) == 0:
503            return
504        wx.CallAfter(self._remove_after, event.GetId())
505        # self._onAdd_constraint(None)
506
507    def _remove_after(self, id):
508        for item in self.constraints_list:
509            if id == item.btRemove.GetId():
510                self.sizer_constraints.Hide(item.sizer)
511                item.sizer.Clear(True)
512                self.sizer_constraints.Remove(item.sizer)
513                self.constraints_list.remove(item)
514                self.nb_constraint -= 1
515                self.constraints_sizer.Layout()
516                self.FitInside()
517                break
518
519    def on_fit(self, event):
520        """
521        signal for fitting
522
523        """
524        if self.fit_started:
525            self._stop_fit()
526            self.fit_started = False
527            return
528
529        flag = False
530        # check if the current page a simultaneous fit page or a batch page
531        if self == self._manager.sim_page:
532            flag = (self._manager.sim_page.uid == self.uid)
533
534        # making sure all parameters content a constraint
535        if not self.batch_on and self.show_constraint.GetValue():
536            if not self._set_constraint():
537                return
538        # model was actually selected from this page to be fit
539        if len(self.model_to_fit) >= 1:
540            self.manager._reset_schedule_problem(value=0)
541            for item in self.model_list:
542                if item[0].GetValue():
543                    self.manager.schedule_for_fit(value=1, uid=item[2])
544            try:
545                self.fit_started = True
546                wx.CallAfter(self.set_fitbutton)
547                if not self.manager.onFit(uid=self.uid):
548                    return
549            except:
550                msg = "Select at least one parameter to fit in the FitPages."
551                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
552        else:
553            msg = "Select at least one model check box to fit "
554            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
555        self.set_state()
556
557    def _on_fit_complete(self):
558        """
559        Set the completion flag and display the updated fit button label.
560        """
561        self.fit_started = False
562        self.set_fitbutton()
563
564    def _stop_fit(self, event=None):
565        """
566        Attempt to stop the fitting thread
567
568        :param event: Event handler when stop fit is clicked
569        """
570        if event is not None:
571            event.Skip()
572        self.manager.stop_fit(self.uid)
573        self.manager._reset_schedule_problem(value=0)
574        self._on_fit_complete()
575
576    def set_fitbutton(self):
577        """
578        Set fit button label depending on the fit_started
579        """
580        label = "Stop" if self.fit_started else "Fit"
581        color = "red" if self.fit_started else "black"
582
583        self.btFit.SetLabel(label)
584        self.btFit.SetForegroundColour(color)
585        self.btFit.Enable(True)
586
587    def _on_help(self, event):
588        """
589        Bring up the simultaneous Fitting Documentation whenever the HELP
590        button is clicked.
591
592        Calls DocumentationWindow with the path of the location within the
593        documentation tree (after /doc/ ....".  Note that when using old
594        versions of Wx (before 2.9) and thus not the release version of
595        installers, the help comes up at the top level of the file as
596        web browser does not pass anything past the # to the browser when it is
597        running "file:///...."
598
599    :param event: Triggers on clicking the help button
600    """
601        _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html"
602        if not self.batch_on:
603            _PageAnchor = "#simultaneous-fit-mode"
604            _doc_viewer = DocumentationWindow(self, self.ID_DOC, _TreeLocation,
605                                          _PageAnchor,
606                                          "Simultaneous/Constrained Fitting Help")
607        else:
608            _PageAnchor = "#combined-batch-fit-mode"
609            _doc_viewer = DocumentationWindow(self, self.ID_DOC, _TreeLocation,
610                                          _PageAnchor,
611                                          "Combined Batch Fit Help")
612
613    def set_manager(self, manager):
614        """
615        set panel manager
616
617        :param manager: instance of plugin fitting
618        """
619        self.manager = manager
620
621    def check_all_model_name(self, event=None):
622        """
623        check all models names
624        """
625        self.model_to_fit = []
626        if self.cb1.GetValue():
627            for item in self.model_list:
628                if item[0].IsEnabled():
629                    item[0].SetValue(True)
630                    self.model_to_fit.append(item)
631
632            # constraint info
633            self._store_model()
634            if not self.batch_on:
635                # display constraint fields
636                if (self.show_constraint.GetValue() and
637                                 len(self.constraints_list) == 0):
638                    self._show_all_constraint()
639                    self._show_constraint()
640        else:
641            for item in self.model_list:
642                item[0].SetValue(False)
643
644            if not self.batch_on:
645                # constraint info
646                self._hide_constraint()
647
648        self._update_easy_setup_cb()
649        self.FitInside()
650
651    def check_model_name(self, event):
652        """
653        Save information related to checkbox and their states
654        """
655        self.model_to_fit = []
656        for item in self.model_list:
657            if item[0].GetValue():
658                self.model_to_fit.append(item)
659            else:
660                if item in self.model_to_fit:
661                    self.model_to_fit.remove(item)
662                    self.cb1.SetValue(False)
663
664        # display constraint fields
665        if len(self.model_to_fit) >= 1:
666            self._store_model()
667            if not self.batch_on and self.show_constraint.GetValue() and\
668                             len(self.constraints_list) == 0:
669                self._show_all_constraint()
670                self._show_constraint()
671
672        elif len(self.model_to_fit) < 1:
673            # constraint info
674            self._hide_constraint()
675
676        self._update_easy_setup_cb()
677        # set the value of the main check button
678        if len(self.model_list) == len(self.model_to_fit):
679            self.cb1.SetValue(True)
680            self.FitInside()
681            return
682        else:
683            self.cb1.SetValue(False)
684            self.FitInside()
685
686    def _update_easy_setup_cb(self):
687        """
688        Update easy setup combobox on selecting a model
689        """
690        if self.model_cbox_left is None or self.model_cbox_right is None:
691            return
692
693        models = [(item[3].name, item[3]) for item in self.model_to_fit]
694        setComboBoxItems(self.model_cbox_left, models)
695        setComboBoxItems(self.model_cbox_right, models)
696        for item in self.constraints_list:
697            setComboBoxItems(item[0], models)
698        if self.model_cbox_left.GetSelection() == wx.NOT_FOUND:
699            self.model_cbox_left.SetSelection(0)
700        self.constraints_sizer.Layout()
701
702    def _store_model(self):
703        """
704         Store selected model
705        """
706        if len(self.model_to_fit) < 1:
707            return
708        for item in self.model_to_fit:
709            model = item[3]
710            page_id = item[2]
711            self.constraint_dict[page_id] = model
712
713    def _display_constraint(self, event):
714        """
715        Show fields to add constraint
716        """
717        if len(self.model_to_fit) < 1:
718            msg = "Select at least 1 model to add constraint "
719            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
720            # hide button
721            self._hide_constraint()
722            return
723        if self.show_constraint.GetValue():
724            self._show_all_constraint()
725            self._show_constraint()
726            self.FitInside()
727            return
728        else:
729            self._hide_constraint()
730            return
731
732    def _show_all_constraint(self):
733        """
734        Show constraint fields
735        """
736        box_description = wx.StaticBox(self, wx.ID_ANY, "Easy Setup ")
737        box_sizer = wx.StaticBoxSizer(box_description, wx.HORIZONTAL)
738        sizer_constraint = wx.BoxSizer(wx.HORIZONTAL)
739        self.model_cbox_left = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
740        self.model_cbox_left.Clear()
741        self.model_cbox_right = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
742        self.model_cbox_right.Clear()
743        wx.EVT_COMBOBOX(self.model_cbox_left, wx.ID_ANY, self._on_select_modelcb)
744        wx.EVT_COMBOBOX(self.model_cbox_right, wx.ID_ANY, self._on_select_modelcb)
745        egal_txt = wx.StaticText(self, wx.ID_ANY, " = ")
746        self.set_button = wx.Button(self, self.ID_SET_ALL, 'Set All')
747        self.set_button.Bind(wx.EVT_BUTTON, self._on_set_all_equal,
748                             id=self.set_button.GetId())
749        set_tip = "Add constraints for all the adjustable parameters "
750        set_tip += "(checked in FitPages) if exist."
751        self.set_button.SetToolTipString(set_tip)
752        self.set_button.Disable()
753
754        for id, model in self.constraint_dict.iteritems():
755            # check if all parameters have been selected for constraint
756            # then do not allow add constraint on parameters
757            self.model_cbox_left.Append(str(model.name), model)
758        self.model_cbox_left.Select(0)
759        for id, model in self.constraint_dict.iteritems():
760            # check if all parameters have been selected for constraint
761            # then do not allow add constraint on parameters
762            self.model_cbox_right.Append(str(model.name), model)
763        box_sizer.Add(self.model_cbox_left,
764                             flag=wx.RIGHT | wx.EXPAND, border=10)
765        # box_sizer.Add(wx.StaticText(self, wx.ID_ANY, ".parameters"),
766        #                     flag=wx.RIGHT | wx.EXPAND, border=5)
767        box_sizer.Add(egal_txt, flag=wx.RIGHT | wx.EXPAND, border=5)
768        box_sizer.Add(self.model_cbox_right,
769                             flag=wx.RIGHT | wx.EXPAND, border=10)
770        # box_sizer.Add(wx.StaticText(self, wx.ID_ANY, ".parameters"),
771        #                     flag=wx.RIGHT | wx.EXPAND, border=5)
772        box_sizer.Add((20, -1))
773        box_sizer.Add(self.set_button, flag=wx.RIGHT | wx.EXPAND, border=5)
774        sizer_constraint.Add(box_sizer, flag=wx.RIGHT | wx.EXPAND, border=5)
775        self.sizer_all_constraints.Insert(before=0,
776                             item=sizer_constraint,
777                             flag=wx.TOP | wx.BOTTOM | wx.EXPAND, border=5)
778        self.FitInside()
779
780    def _on_select_modelcb(self, event):
781        """
782        On select model left or right combobox
783        """
784        event.Skip()
785        flag = True
786        if self.model_cbox_left.GetValue().strip() == '':
787            flag = False
788        if self.model_cbox_right.GetValue().strip() == '':
789            flag = False
790        if (self.model_cbox_left.GetValue() ==
791                self.model_cbox_right.GetValue()):
792            flag = False
793        self.set_button.Enable(flag)
794
795    def _on_set_all_equal(self, event):
796        """
797        On set button
798        """
799        event.Skip()
800        length = len(self.constraints_list)
801        if length < 1:
802            return
803        param_list = []
804        param_list_b = []
805        selection = self.model_cbox_left.GetCurrentSelection()
806        model_left = self.model_cbox_left.GetValue()
807        model = self.model_cbox_left.GetClientData(selection)
808        selection_b = self.model_cbox_right.GetCurrentSelection()
809        model_right = self.model_cbox_right.GetValue()
810        model_b = self.model_cbox_right.GetClientData(selection_b)
811        for id, dic_model in self.constraint_dict.iteritems():
812            if model == dic_model:
813                param_list = self.page_finder[id].get_param2fit()
814            if model_b == dic_model:
815                param_list_b = self.page_finder[id].get_param2fit()
816            if len(param_list) > 0 and len(param_list_b) > 0:
817                break
818        num_cbox = 0
819        has_param = False
820        for param in param_list:
821            num_cbox += 1
822            if param in param_list_b:
823                item = self.constraints_list[-1]
824                item.model_cbox.SetStringSelection(model_left)
825                self._on_select_model(None)
826                item.param_cbox.Clear()
827                item.param_cbox.Append(str(param), model)
828                item.param_cbox.SetStringSelection(str(param))
829                item.constraint.SetValue(str(model_right + "." + str(param)))
830                has_param = True
831                if num_cbox == (len(param_list) + 1):
832                    break
833                self._show_constraint()
834
835        self.FitInside()
836        if not has_param:
837            msg = " There is no adjustable parameter (checked to fit)"
838            msg += " either one of the models."
839            wx.PostEvent(self.parent.parent, StatusEvent(info="warning",
840                                                         status=msg))
841        else:
842            msg = " The constraints are added."
843            wx.PostEvent(self.parent.parent, StatusEvent(info="info",
844                                                         status=msg))
845
846    def _show_constraint(self):
847        """
848        Show constraint fields
849        :param dict: dictionary mapping constraint values
850        """
851        self.btAdd.Show(True)
852        if len(self.constraints_list) != 0:
853            nb_fit_param = 0
854            for id, model in self.constraint_dict.iteritems():
855                nb_fit_param += len(self.page_finder[id].get_param2fit())
856            # Don't add anymore
857            if len(self.constraints_list) == nb_fit_param:
858                msg = "Cannot add another constraint. Maximum of number "
859                msg += "Parameters name reached %s" % str(nb_fit_param)
860                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
861                self.sizer_constraints.Layout()
862                self.constraints_sizer.Layout()
863                return
864        if len(self.model_to_fit) < 1:
865            msg = "Select at least 1 model to add constraint "
866            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
867            self.sizer_constraints.Layout()
868            self.constraints_sizer.Layout()
869            return
870
871        sizer_constraint = wx.BoxSizer(wx.HORIZONTAL)
872
873        # Model list
874        model_cbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
875        model_cbox.Clear()
876        for id, model in self.constraint_dict.iteritems():
877            # check if all parameters have been selected for constraint
878            # then do not allow add constraint on parameters
879            model_cbox.Append(str(model.name), model)
880        wx.EVT_COMBOBOX(model_cbox, wx.ID_ANY, self._on_select_model)
881
882        # Parameters in model
883        param_cbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY,
884                                 size=(100, -1))
885        param_cbox.Hide()
886        wx.EVT_COMBOBOX(param_cbox, wx.ID_ANY, self._on_select_param)
887
888        egal_txt = wx.StaticText(self, wx.ID_ANY, " = ")
889
890        # Parameter constraint
891        constraint = wx.TextCtrl(self, wx.ID_ANY)
892
893        # Remove button
894        #btRemove = wx.Button(self, self.ID_REMOVE, 'Remove')
895        bt_remove = wx.Button(self, self._ids.next(), 'Remove')
896        bt_remove.Bind(wx.EVT_BUTTON, self.on_remove,
897                      id=bt_remove.GetId())
898        bt_remove.SetToolTipString("Remove constraint.")
899        bt_remove.Hide()
900
901        # Hid the add button, if it exists
902        if hasattr(self, "btAdd"):
903            self.btAdd.Hide()
904
905        sizer_constraint.Add((5, -1))
906        sizer_constraint.Add(model_cbox, flag=wx.RIGHT | wx.EXPAND, border=10)
907        sizer_constraint.Add(param_cbox, flag=wx.RIGHT | wx.EXPAND, border=5)
908        sizer_constraint.Add(egal_txt, flag=wx.RIGHT | wx.EXPAND, border=5)
909        sizer_constraint.Add(constraint, flag=wx.RIGHT | wx.EXPAND, border=10)
910        sizer_constraint.Add(bt_remove, flag=wx.RIGHT | wx.EXPAND, border=10)
911
912        self.sizer_constraints.Insert(before=self.nb_constraint,
913                item=sizer_constraint, flag=wx.TOP | wx.BOTTOM | wx.EXPAND,
914                border=5)
915        c = ConstraintLine(model_cbox, param_cbox, egal_txt,
916                           constraint, bt_remove, sizer_constraint)
917        self.constraints_list.append(c)
918
919        self.nb_constraint += 1
920        self.sizer_constraints.Layout()
921        self.constraints_sizer.Layout()
922        self.Layout()
923
924    def _hide_constraint(self):
925        """
926        hide buttons related constraint
927        """
928        for id in self.page_finder.iterkeys():
929            self.page_finder[id].clear_model_param()
930
931        self.nb_constraint = 0
932        self.constraint_dict = {}
933        if hasattr(self, "btAdd"):
934            self.btAdd.Hide()
935        self._store_model()
936        if self.model_cbox_left is not None:
937            self.model_cbox_left.Clear()
938            self.model_cbox_left = None
939        if self.model_cbox_right is not None:
940            self.model_cbox_right.Clear()
941            self.model_cbox_right = None
942        self.constraints_list = []
943        self.sizer_all_constraints.Clear(True)
944        self.sizer_all_constraints.Layout()
945        self.sizer_constraints.Clear(True)
946        self.sizer_constraints.Layout()
947        self.constraints_sizer.Layout()
948        self.Layout()
949        self.FitInside()
950
951    def _on_select_model(self, event):
952        """
953        fill combo box with list of parameters
954        """
955        if not self.constraints_list:
956            return
957
958        # This way PC/MAC both work, instead of using event.GetClientData().
959        model_cbox = self.constraints_list[-1].model_cbox
960        n = model_cbox.GetCurrentSelection()
961        if n == wx.NOT_FOUND:
962            return
963
964        model = model_cbox.GetClientData(n)
965        param_list = []
966        for id, dic_model in self.constraint_dict.iteritems():
967            if model == dic_model:
968                param_list = self.page_finder[id].get_param2fit()
969                break
970
971        param_cbox = self.constraints_list[-1].param_cbox
972        param_cbox.Clear()
973        # insert only fittable paramaters
974        for param in param_list:
975            param_cbox.Append(str(param), model)
976        param_cbox.Show(True)
977
978        bt_remove = self.constraints_list[-1].btRemove
979        bt_remove.Show(True)
980        self.btAdd.Show(True)
981#        self.Layout()
982        self.FitInside()
983
984    def _on_select_param(self, event):
985        """
986        Store the appropriate constraint in the page_finder
987        """
988        # This way PC/MAC both work, instead of using event.GetClientData().
989        # n = self.param_cbox.GetCurrentSelection()
990        # model = self.param_cbox.GetClientData(n)
991        # param = event.GetString()
992
993        if self.constraints_list:
994            self.constraints_list[-1].egal_txt.Show(True)
995            self.constraints_list[-1].constraint.Show(True)
996
997    def _on_add_constraint(self, event):
998        """
999        Add another line for constraint
1000        """
1001        if not self.show_constraint.GetValue():
1002            msg = " Select Yes to add Constraint "
1003            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
1004            return
1005        # check that a constraint is added
1006        # before allow to add another constraint
1007        for item in self.constraints_list:
1008            if item.model_cbox.GetString(0) == "":
1009                msg = " Select a model Name! "
1010                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
1011                return
1012            if item.param_cbox.GetString(0) == "":
1013                msg = " Select a parameter Name! "
1014                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
1015                return
1016            if item.constraint.GetValue().lstrip().rstrip() == "":
1017                model = item.param_cbox.GetClientData(
1018                                        item.param_cbox.GetCurrentSelection())
1019                if model is not None:
1020                    msg = " Enter a constraint for %s.%s! " % (model.name,
1021                                        item.param_cbox.GetString(0))
1022                else:
1023                    msg = " Enter a constraint"
1024                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
1025                return
1026        # some model or parameters can be constrained
1027        self._show_constraint()
1028        self.FitInside()
1029
1030    def _set_constraint(self):
1031        """
1032        get values from the constraint textcrtl ,parses them into model name
1033        parameter name and parameters values.
1034        store them in a list self.params .when when params is not empty
1035        set_model uses it to reset the appropriate model
1036        and its appropriates parameters
1037        """
1038        for item in self.constraints_list:
1039            select0 = item.model_cbox.GetSelection()
1040            if select0 == wx.NOT_FOUND:
1041                continue
1042            model = item.model_cbox.GetClientData(select0)
1043            select1 = item.param_cbox.GetSelection()
1044            if select1 == wx.NOT_FOUND:
1045                continue
1046            param = item.param_cbox.GetString(select1)
1047            constraint = item.constraint.GetValue().lstrip().rstrip()
1048            if param.lstrip().rstrip() == "":
1049                param = None
1050                msg = " Constraint will be ignored!. missing parameters"
1051                msg += " in combobox to set constraint! "
1052                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
1053            for id, value in self.constraint_dict.iteritems():
1054                if model == value:
1055                    if constraint == "":
1056                        msg = " Constraint will be ignored!. missing value"
1057                        msg += " in textcrtl to set constraint! "
1058                        wx.PostEvent(self.parent.parent,
1059                                     StatusEvent(status=msg))
1060                        constraint = None
1061                    if str(param) in self.page_finder[id].get_param2fit():
1062                        msg = " Checking constraint for parameter: %s ", param
1063                        wx.PostEvent(self.parent.parent,
1064                                     StatusEvent(info="info", status=msg))
1065                    else:
1066                        model_name = item[0].GetLabel()
1067                        fitpage = self.page_finder[id].get_fit_tab_caption()
1068                        msg = "All constrainted parameters must be set "
1069                        msg += " adjustable: '%s.%s' " % (model_name, param)
1070                        msg += "is NOT checked in '%s'. " % fitpage
1071                        msg += " Please check it to fit or"
1072                        msg += " remove the line of the constraint."
1073                        wx.PostEvent(self.parent.parent,
1074                                StatusEvent(info="error", status=msg))
1075                        return False
1076
1077                    for fid in self.page_finder[id].iterkeys():
1078                        # wrap in param/constraint in str() to remove unicode
1079                        self.page_finder[id].set_model_param(str(param),
1080                                str(constraint), fid=fid)
1081                    break
1082        return True
1083
1084    def on_set_focus(self, event=None):
1085        """
1086        The derivative class is on focus if implemented
1087        """
1088        if self.parent is not None:
1089            if self.parent.parent is not None:
1090                wx.PostEvent(self.parent.parent, PanelOnFocusEvent(panel=self))
1091            self.page_finder = self.parent._manager.get_page_finder()
1092
1093
1094def setComboBoxItems(cbox, items):
1095    assert isinstance(cbox, wx.ComboBox)
1096    selected = cbox.GetStringSelection()
1097    cbox.Clear()
1098    for k, (name, value) in enumerate(items):
1099        cbox.Append(name, value)
1100    cbox.SetStringSelection(selected)
Note: See TracBrowser for help on using the repository browser.