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

Last change on this file since 199b515e was e6de6b8, checked in by krzywon, 8 years ago

Delinting and small structural changes.

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