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

Last change on this file since 2e63860 was c8e1996, checked in by krzywon, 8 years ago

Fixes #738: No errors are thrown on loading projects with fits, plus linting.

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