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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 3968752 was 05228b0, checked in by Paul Kienzle <pkienzle@…>, 9 years ago

use the new bumps fitter changed event to update the active fitter indicator

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