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

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 998ca90 was 998ca90, checked in by krzywon, 8 years ago

#189 #12 Saving and loading project now fully saves and loads a simultaneous fits. Loading a project will reset Sasview state to its base state (with a warning). One error on load to fix before calling this finished.

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