source: sasview/src/sas/sasgui/perspectives/fitting/fitpanel.py @ ae2f623

Last change on this file since ae2f623 was 13374be, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

Merge branch 'master' into 4_1_issues

  • Property mode set to 100644
File size: 22.5 KB
Line 
1"""
2FitPanel class contains fields allowing to fit  models and  data
3
4:note: For Fit to be performed the user should check at least one parameter
5    on fit Panel window.
6
7"""
8import wx
9from wx.aui import AuiNotebook as nb
10
11from sas.sasgui.guiframe.panel_base import PanelBase
12from sas.sasgui.guiframe.events import PanelOnFocusEvent, StatusEvent
13from sas.sasgui.guiframe.dataFitting import check_data_validity
14from sas.sasgui.perspectives.fitting.simfitpage import SimultaneousFitPage
15
16import basepage
17import models
18_BOX_WIDTH = 80
19
20
21class FitPanel(nb, PanelBase):
22    """
23    FitPanel class contains fields allowing to fit  models and  data
24
25    :note: For Fit to be performed the user should check at least one parameter
26        on fit Panel window.
27
28    """
29    # Internal name for the AUI manager
30    window_name = "Fit panel"
31    # Title to appear on top of the window
32    window_caption = "Fit Panel "
33    CENTER_PANE = True
34
35    def __init__(self, parent, manager=None, *args, **kwargs):
36        """
37        """
38        nb.__init__(self, parent, wx.ID_ANY,
39                    style=wx.aui.AUI_NB_WINDOWLIST_BUTTON |
40                    wx.aui.AUI_NB_DEFAULT_STYLE |
41                    wx.CLIP_CHILDREN)
42        PanelBase.__init__(self, parent)
43        # self.SetWindowStyleFlag(style=nb.FNB_FANCY_TABS)
44        self._manager = manager
45        self.parent = parent
46        self.event_owner = None
47        # dictionary of miodel {model class name, model class}
48        self.menu_mng = models.ModelManager()
49        self.model_list_box = self.menu_mng.get_model_list()
50        # pageClosedEvent = nb.EVT_FLATNOTEBOOK_PAGE_CLOSING
51        self.model_dictionary = self.menu_mng.get_model_dictionary()
52        self.pageClosedEvent = wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE
53
54        self.Bind(self.pageClosedEvent, self.on_close_page)
55        # save the title of the last page tab added
56        self.fit_page_name = {}
57        # list of existing fit page
58        self.opened_pages = {}
59        # index of fit page
60        self.fit_page_index = 0
61        # index of batch page
62        self.batch_page_index = 0
63        # page of simultaneous fit
64        self.sim_page = None
65        self.batch_page = None
66        # get the state of a page
67        self.Bind(basepage.EVT_PAGE_INFO, self._onGetstate)
68        self.Bind(basepage.EVT_PREVIOUS_STATE, self._onUndo)
69        self.Bind(basepage.EVT_NEXT_STATE, self._onRedo)
70        self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_page_changing)
71        self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSED, self.on_closed)
72
73    def on_closed(self, event):
74        """
75        """
76        if self.GetPageCount() == 0:
77            self.add_empty_page()
78            self.enable_close_button()
79
80    def save_project(self, doc=None):
81        """
82        return an xml node containing state of the panel
83        that guiframe can write to file
84        """
85        # Iterate through all pages and check for batch fitting
86        batch_state = None
87        if self.sim_page is not None:
88            batch_state = self.sim_page.set_state()
89
90        for uid, page in self.opened_pages.iteritems():
91            data = page.get_data()
92            # state must be cloned
93            state = page.get_state().clone()
94            # data_list only populated with real data
95            # Fake object in data from page.get_data() if model is selected
96            if len(page.data_list) is not 0 and page.model is not None:
97                new_doc = self._manager.state_reader.write_toXML(data,
98                                                                 state,
99                                                                 batch_state)
100                # Fit #2 through #n are append to first fit
101                if doc is not None and hasattr(doc, "firstChild"):
102                    # Only append if properly formed new_doc
103                    if new_doc is not None and hasattr(new_doc, "firstChild"):
104                        child = new_doc.firstChild.firstChild
105                        doc.firstChild.appendChild(child)
106                # First fit defines the main document
107                else:
108                    doc = new_doc
109
110        return doc
111
112    def update_model_list(self):
113        """
114        """
115        temp = self.menu_mng.update()
116        if len(temp):
117            self.model_list_box = temp
118        return temp
119
120    def reset_pmodel_list(self):
121        """
122        """
123        temp = self.menu_mng.plugins_reset()
124        if len(temp):
125            self.model_list_box = temp
126        return temp
127
128    def get_page_by_id(self, uid):
129        """
130        """
131        if uid not in self.opened_pages:
132            msg = "Fitpanel cannot find ID: %s in self.opened_pages" % str(uid)
133            raise ValueError, msg
134        else:
135            return self.opened_pages[uid]
136
137    def on_page_changing(self, event):
138        """
139        calls the function when the current event handler has exited. avoiding
140        to call panel on focus on a panel that is currently deleted
141        """
142        wx.CallAfter(self.helper_on_page_change)
143
144    def helper_on_page_change(self):
145        """
146        """
147        pos = self.GetSelection()
148        if pos != -1:
149            selected_page = self.GetPage(pos)
150            wx.PostEvent(self._manager.parent,
151                         PanelOnFocusEvent(panel=selected_page))
152        self.enable_close_button()
153
154    def on_set_focus(self, event):
155        """
156        """
157        pos = self.GetSelection()
158        if pos != -1:
159            selected_page = self.GetPage(pos)
160            wx.PostEvent(self._manager.parent,
161                         PanelOnFocusEvent(panel=selected_page))
162
163    def get_data(self):
164        """
165        get the data in the current page
166        """
167        pos = self.GetSelection()
168        if pos != -1:
169            selected_page = self.GetPage(pos)
170            return selected_page.get_data()
171
172    def set_model_state(self, state):
173        """
174        receive a state to reset the model in the current page
175        """
176        pos = self.GetSelection()
177        if pos != -1:
178            selected_page = self.GetPage(pos)
179            selected_page.set_model_state(state)
180
181    def get_state(self):
182        """
183        return the state of the current selected page
184        """
185        pos = self.GetSelection()
186        if pos != -1:
187            selected_page = self.GetPage(pos)
188            return selected_page.get_state()
189
190    def close_all(self):
191        """
192        remove all pages, used when a svs file is opened
193        """
194
195        # use while-loop, for-loop will not do the job well.
196        while (self.GetPageCount() > 0):
197            page = self.GetPage(self.GetPageCount() - 1)
198            if self._manager.parent.panel_on_focus == page:
199                self._manager.parent.panel_on_focus = None
200            self._close_helper(selected_page=page)
201            self.DeletePage(self.GetPageCount() - 1)
202        # Clear list of names
203        self.fit_page_name = {}
204        # Clear list of opened pages
205        self.opened_pages = {}
206        self.fit_page_index = 0
207        self.batch_page_index = 0
208
209    def set_state(self, state):
210        """
211        Restore state of the panel
212        """
213        page_is_opened = False
214        if state is not None:
215            for uid, panel in self.opened_pages.iteritems():
216                # Don't return any panel is the exact same page is created
217                if uid == panel.uid and panel.data == state.data:
218                    # the page is still opened
219                    panel.reset_page(state=state)
220                    panel.save_current_state()
221                    page_is_opened = True
222            if not page_is_opened:
223                if state.data.__class__.__name__ != 'list':
224                    # To support older state file format
225                    list_data = [state.data]
226                else:
227                    # Todo: need new file format for the list
228                    list_data = state.data
229                panel = self._manager.add_fit_page(data=list_data)
230                # add data associated to the page created
231                if panel is not None:
232                    self._manager.store_data(uid=panel.uid,
233                                             data_list=list_data,
234                                             caption=panel.window_caption)
235                    panel.reset_page(state=state)
236                    panel.save_current_state()
237
238    def clear_panel(self):
239        """
240        Clear and close all panels, used by guimanager
241        """
242        # close all panels only when svs file opened
243        self.close_all()
244        self.sim_page = None
245        self.batch_page = None
246
247    def on_close_page(self, event=None):
248        """
249        close page and remove all references to the closed page
250        """
251        selected_page = self.GetPage(self.GetSelection())
252        if self.GetPageCount() == 1:
253            if selected_page.get_data() is not None:
254                if event is not None:
255                    event.Veto()
256                return
257        self._close_helper(selected_page=selected_page)
258
259    def close_page_with_data(self, deleted_data):
260        """
261        close a fit page when its data is completely remove from the graph
262        """
263        if deleted_data is None:
264            return
265        for index in range(self.GetPageCount()):
266            selected_page = self.GetPage(index)
267            if hasattr(selected_page, "get_data"):
268                data = selected_page.get_data()
269
270                if data is None:
271                    # the fitpanel exists and only the initial fit page is open
272                    # with no selected data
273                    return
274                if data.id == deleted_data.id:
275                    self._close_helper(selected_page)
276                    self.DeletePage(index)
277                    break
278
279    def set_manager(self, manager):
280        """
281        set panel manager
282
283        :param manager: instance of plugin fitting
284        """
285        self._manager = manager
286        for pos in range(self.GetPageCount()):
287            page = self.GetPage(pos)
288            if page is not None:
289                page.set_manager(self._manager)
290
291    def set_model_list(self, dict):
292        """
293        copy a dictionary of model into its own dictionary
294
295        :param dict: dictionnary made of model name as key and model class
296            as value
297        """
298        self.model_list_box = dict
299
300    def set_model_dict(self, m_dict):
301        """
302        copy a dictionary of model name -> model object
303
304        :param m_dict: dictionary linking model name -> model object
305        """
306
307    def get_current_page(self):
308        """
309        :return: the current page selected
310
311        """
312        return self.GetPage(self.GetSelection())
313
314    def add_sim_page(self, caption="Const & Simul Fit"):
315        """
316        Add the simultaneous fit page
317        """
318        from simfitpage import SimultaneousFitPage
319        page_finder = self._manager.get_page_finder()
320        if caption == "Const & Simul Fit":
321            self.sim_page = SimultaneousFitPage(self, page_finder=page_finder,
322                                                 id=wx.ID_ANY, batch_on=False)
323            self.sim_page.window_caption = caption
324            self.sim_page.window_name = caption
325            self.sim_page.uid = wx.NewId()
326            self.AddPage(self.sim_page, caption, True)
327            self.sim_page.set_manager(self._manager)
328            self.enable_close_button()
329            return self.sim_page
330        else:
331            self.batch_page = SimultaneousFitPage(self, batch_on=True,
332                                                  page_finder=page_finder)
333            self.batch_page.window_caption = caption
334            self.batch_page.window_name = caption
335            self.batch_page.uid = wx.NewId()
336            self.AddPage(self.batch_page, caption, True)
337            self.batch_page.set_manager(self._manager)
338            self.enable_close_button()
339            return self.batch_page
340
341    def add_empty_page(self):
342        """
343        add an empty page
344        """
345        if self.batch_on:
346            from batchfitpage import BatchFitPage
347            panel = BatchFitPage(parent=self)
348            self.batch_page_index += 1
349            caption = "BatchPage" + str(self.batch_page_index)
350            panel.set_index_model(self.batch_page_index)
351        else:
352            # Increment index of fit page
353            from fitpage import FitPage
354            panel = FitPage(parent=self)
355            self.fit_page_index += 1
356            caption = "FitPage" + str(self.fit_page_index)
357            panel.set_index_model(self.fit_page_index)
358        panel.batch_on = self.batch_on
359        panel._set_save_flag(not panel.batch_on)
360        panel.set_model_dictionary(self.model_dictionary)
361        panel.populate_box(model_dict=self.model_list_box)
362        panel.formfactor_combo_init()
363        panel.set_manager(self._manager)
364        panel.window_caption = caption
365        panel.window_name = caption
366        self.AddPage(panel, caption, select=True)
367        self.opened_pages[panel.uid] = panel
368        self._manager.create_fit_problem(panel.uid)
369        self._manager.page_finder[panel.uid].add_data(panel.get_data())
370        self.enable_close_button()
371        panel.on_set_focus(None)
372        return panel
373
374    def enable_close_button(self):
375        """
376        display the close button on tab for more than 1 tabs else remove the
377        close button
378        """
379        if self.GetPageCount() <= 1:
380            style = self.GetWindowStyleFlag()
381            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
382            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB == flag:
383                style = style & ~wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
384                self.SetWindowStyle(style)
385        else:
386            style = self.GetWindowStyleFlag()
387            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
388            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB != flag:
389                style |= wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
390                self.SetWindowStyle(style)
391
392    def delete_data(self, data):
393        """
394        Delete the given data
395        """
396        if data.__class__.__name__ != "list":
397            raise ValueError, "Fitpanel delete_data expect list of id"
398        else:
399            for page in self.opened_pages.values():
400                pos = self.GetPageIndex(page)
401                temp_data = page.get_data()
402                if temp_data is not None and temp_data.id in data:
403                    self.close_page_with_data(temp_data)
404            if self.sim_page is not None:
405                if len(self.sim_page.model_list) == 0:
406                    pos = self.GetPageIndex(self.sim_page)
407                    self.SetSelection(pos)
408                    self.on_close_page(event=None)
409                    self.DeletePage(pos)
410                    self.sim_page = None
411                    self.batch_on = False
412            if self.GetPageCount() == 0:
413                self._manager.on_add_new_page(event=None)
414
415    def set_data_on_batch_mode(self, data_list):
416        """
417        Add all data to a single tab when the application is on Batch mode.
418        However all data in the set of data must be either 1D or 2D type.
419        This method presents option to select the data type before creating a
420        tab.
421        """
422        data_1d_list = []
423        data_2d_list = []
424        group_id_1d = wx.NewId()
425        # separate data into data1d and data2d list
426        for data in data_list:
427            if data.__class__.__name__ == "Data1D":
428                data.group_id = group_id_1d
429                data_1d_list.append(data)
430            if data.__class__.__name__ == "Data2D":
431                data.group_id = wx.NewId()
432                data_2d_list.append(data)
433        page = None
434        for p in self.opened_pages.values():
435            # check if there is an empty page to fill up
436            if not check_data_validity(p.get_data()) and p.batch_on:
437
438                # make sure data get placed in 1D empty tab if data is 1D
439                # else data get place on 2D tab empty tab
440                enable2D = p.get_view_mode()
441                if (data.__class__.__name__ == "Data2D" and enable2D)\
442                or (data.__class__.__name__ == "Data1D" and not enable2D):
443                    page = p
444                    break
445        if data_1d_list and data_2d_list:
446            # need to warning the user that this batch is a special case
447            from sas.sasgui.perspectives.fitting.fitting_widgets import \
448                BatchDataDialog
449            dlg = BatchDataDialog(self)
450            if dlg.ShowModal() == wx.ID_OK:
451                data_type = dlg.get_data()
452                dlg.Destroy()
453                if page is None:
454                    page = self.add_empty_page()
455                if data_type == 1:
456                    # user has selected only data1D
457                    page.fill_data_combobox(data_1d_list)
458                elif data_type == 2:
459                    page.fill_data_combobox(data_2d_list)
460            else:
461                # the batch analysis is canceled
462                dlg.Destroy()
463                return None
464        else:
465            if page is None:
466                page = self.add_empty_page()
467            if data_1d_list and not data_2d_list:
468                # only on type of data
469                page.fill_data_combobox(data_1d_list)
470            elif not data_1d_list and data_2d_list:
471                page.fill_data_combobox(data_2d_list)
472
473        pos = self.GetPageIndex(page)
474        page.batch_on = self.batch_on
475        page._set_save_flag(not page.batch_on)
476        self.SetSelection(pos)
477        self.opened_pages[page.uid] = page
478        return page
479
480    def set_data(self, data_list):
481        """
482        Add a fitting page on the notebook contained by fitpanel
483
484        :param data_list: data to fit
485
486        :return panel : page just added for further used.
487        is used by fitting module
488
489        """
490        if not data_list:
491            return None
492        if self.batch_on:
493            return self.set_data_on_batch_mode(data_list)
494        else:
495            data = None
496            try:
497                data = data_list[0]
498            except Exception:
499                # for 'fitv' files
500                data_list = [data]
501                data = data_list[0]
502
503            if data is None:
504                return None
505        focused_page = self.GetPage(self.GetSelection())
506        for page in self.opened_pages.values():
507            # check if the selected data existing in the fitpanel
508            pos = self.GetPageIndex(page)
509            if not check_data_validity(page.get_data()) and not page.batch_on:
510                if page.model is not None and page != focused_page:
511                    # Page has an active theory and is in background - don't
512                    # send data here.
513                    continue
514                # make sure data get placed in 1D empty tab if data is 1D
515                # else data get place on 2D tab empty tab
516                enable2D = page.get_view_mode()
517                if (data.__class__.__name__ == "Data2D" and enable2D)\
518                   or (data.__class__.__name__ == "Data1D" and not enable2D):
519                    page.batch_on = self.batch_on
520                    page._set_save_flag(not page.batch_on)
521                    page.fill_data_combobox(data_list)
522                    # caption = "FitPage" + str(self.fit_page_index)
523                    self.SetPageText(pos, page.window_caption)
524                    self.SetSelection(pos)
525                    return page
526        # create new page and add data
527        page = self.add_empty_page()
528        pos = self.GetPageIndex(page)
529        page.fill_data_combobox(data_list)
530        self.opened_pages[page.uid] = page
531        self.SetSelection(pos)
532        return page
533
534    def _onGetstate(self, event):
535        """
536        copy the state of a page
537        """
538        page = event.page
539        if page.uid in self.fit_page_name:
540            self.fit_page_name[page.uid].appendItem(page.createMemento())
541
542    def _onUndo(self, event):
543        """
544        return the previous state of a given page is available
545        """
546        page = event.page
547        if page.uid in self.fit_page_name:
548            if self.fit_page_name[page.uid].getCurrentPosition() == 0:
549                state = None
550            else:
551                state = self.fit_page_name[page.uid].getPreviousItem()
552                page._redo.Enable(True)
553            page.reset_page(state)
554
555    def _onRedo(self, event):
556        """
557        return the next state available
558        """
559        page = event.page
560        if page.uid in self.fit_page_name:
561            length = len(self.fit_page_name[page.uid])
562            if self.fit_page_name[page.uid].getCurrentPosition() == length - 1:
563                state = None
564                page._redo.Enable(False)
565                page._redo.Enable(True)
566            else:
567                state = self.fit_page_name[page.uid].getNextItem()
568            page.reset_page(state)
569
570    def _close_helper(self, selected_page):
571        """
572        Delete the given page from the notebook
573        """
574        # remove hint page
575        # if selected_page == self.hint_page:
576        #    return
577        # removing sim_page
578        if selected_page == self.sim_page:
579            self._manager.sim_page = None
580            return
581        if selected_page == self.batch_page:
582            self._manager.batch_page = None
583            return
584        # closing other pages
585        state = selected_page.createMemento()
586        page_finder = self._manager.get_page_finder()
587        # removing fit page
588        data = selected_page.get_data()
589        # Don' t remove plot for 2D
590        flag = True
591        if data.__class__.__name__ == 'Data2D':
592            flag = False
593        if selected_page in page_finder:
594            # Delete the name of the page into the list of open page
595            for uid, list in self.opened_pages.iteritems():
596                # Don't return any panel is the exact same page is created
597                if flag and selected_page.uid == uid:
598                    self._manager.remove_plot(uid, theory=False)
599                    break
600            del page_finder[selected_page]
601
602        # Delete the name of the page into the list of open page
603        for uid, list in self.opened_pages.iteritems():
604            # Don't return any panel is the exact same page is created
605            if selected_page.uid == uid:
606                del self.opened_pages[selected_page.uid]
607                break
608        # remove the check box link to the model name of the selected_page
609        try:
610            self.sim_page.draw_page()
611        except:
612            # that page is already deleted no need to remove check box on
613            # non existing page
614            pass
615        try:
616            self.batch_page.draw_page()
617        except:
618            # that page is already deleted no need to remove check box on
619            # non existing page
620            pass
Note: See TracBrowser for help on using the repository browser.