source: sasview/fittingview/src/sans/perspectives/fitting/fitpanel.py @ df7a7e3

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since df7a7e3 was df7a7e3, checked in by Mathieu Doucet <doucetm@…>, 12 years ago

merging category branch

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