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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_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 dab94f6 was dab94f6, checked in by krzywon, 7 years ago

Deleting data from first fit page is no longer causing the issue. see #929

  • Property mode set to 100644
File size: 21.9 KB
Line 
1"""
2FitPanel class contains fields allowing to fit  models and  data
3
4:note: For Fit to be performed the user should check at least one parameter
5    on fit Panel window.
6
7"""
8import wx
9from wx.aui import AuiNotebook as nb
10
11from sas.sasgui.guiframe.panel_base import PanelBase
12from sas.sasgui.guiframe.events import PanelOnFocusEvent, StatusEvent
13from sas.sasgui.guiframe.dataFitting import check_data_validity
14from sas.sasgui.perspectives.fitting.simfitpage import SimultaneousFitPage
15
16import basepage
17import models
18_BOX_WIDTH = 80
19
20
21class FitPanel(nb, PanelBase):
22    """
23    FitPanel class contains fields allowing to fit  models and  data
24
25    :note: For Fit to be performed the user should check at least one parameter
26        on fit Panel window.
27
28    """
29    # Internal name for the AUI manager
30    window_name = "Fit panel"
31    # Title to appear on top of the window
32    window_caption = "Fit Panel "
33    CENTER_PANE = True
34
35    def __init__(self, parent, manager=None, *args, **kwargs):
36        """
37        """
38        nb.__init__(self, parent, wx.ID_ANY,
39                    style=wx.aui.AUI_NB_WINDOWLIST_BUTTON |
40                    wx.aui.AUI_NB_DEFAULT_STYLE |
41                    wx.CLIP_CHILDREN)
42        PanelBase.__init__(self, parent)
43        # self.SetWindowStyleFlag(style=nb.FNB_FANCY_TABS)
44        self._manager = manager
45        self.parent = parent
46        self.event_owner = None
47        # dictionary of miodel {model class name, model class}
48        self.menu_mng = models.ModelManager()
49        self.model_list_box = self.menu_mng.get_model_list()
50        # pageClosedEvent = nb.EVT_FLATNOTEBOOK_PAGE_CLOSING
51        self.model_dictionary = self.menu_mng.get_model_dictionary()
52        self.pageClosedEvent = wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE
53
54        self.Bind(self.pageClosedEvent, self.on_close_page)
55        # save the title of the last page tab added
56        self.fit_page_name = {}
57        # list of existing fit page
58        self.opened_pages = {}
59        # index of fit page
60        self.fit_page_index = 0
61        # index of batch page
62        self.batch_page_index = 0
63        # page of simultaneous fit
64        self.sim_page = None
65        self.batch_page = None
66        # get the state of a page
67        self.Bind(basepage.EVT_PAGE_INFO, self._onGetstate)
68        self.Bind(basepage.EVT_PREVIOUS_STATE, self._onUndo)
69        self.Bind(basepage.EVT_NEXT_STATE, self._onRedo)
70        self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_page_changing)
71        self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSED, self.on_closed)
72
73    def on_closed(self, event):
74        """
75        """
76        if self.GetPageCount() == 0:
77            self.add_empty_page()
78            self.enable_close_button()
79
80    def save_project(self, doc=None):
81        """
82        return an xml node containing state of the panel
83        that guiframe can write to file
84        """
85        # Iterate through all pages and check for batch fitting
86        batch_state = None
87        if self.sim_page is not None:
88            batch_state = self.sim_page.set_state()
89
90        for uid, page in self.opened_pages.iteritems():
91            data = page.get_data()
92            # state must be cloned
93            state = page.get_state().clone()
94            if data is not None or page.model is not None:
95                new_doc = self._manager.state_reader.write_toXML(data,
96                                                                 state,
97                                                                 batch_state)
98                if doc is not None and hasattr(doc, "firstChild"):
99                    child = new_doc.firstChild.firstChild
100                    doc.firstChild.appendChild(child)
101                else:
102                    doc = new_doc
103
104        return doc
105
106    def update_model_list(self):
107        """
108        """
109        temp = self.menu_mng.update()
110        if len(temp):
111            self.model_list_box = temp
112        return temp
113
114    def reset_pmodel_list(self):
115        """
116        """
117        temp = self.menu_mng.plugins_reset()
118        if len(temp):
119            self.model_list_box = temp
120        return temp
121
122    def get_page_by_id(self, uid):
123        """
124        """
125        if uid not in self.opened_pages:
126            msg = "Fitpanel cannot find ID: %s in self.opened_pages" % str(uid)
127            raise ValueError, msg
128        else:
129            return self.opened_pages[uid]
130
131    def on_page_changing(self, event):
132        """
133        calls the function when the current event handler has exited. avoiding
134        to call panel on focus on a panel that is currently deleted
135        """
136        wx.CallAfter(self.helper_on_page_change)
137
138    def helper_on_page_change(self):
139        """
140        """
141        pos = self.GetSelection()
142        if pos != -1:
143            selected_page = self.GetPage(pos)
144            wx.PostEvent(self._manager.parent,
145                         PanelOnFocusEvent(panel=selected_page))
146        self.enable_close_button()
147
148    def on_set_focus(self, event):
149        """
150        """
151        pos = self.GetSelection()
152        if pos != -1:
153            selected_page = self.GetPage(pos)
154            wx.PostEvent(self._manager.parent,
155                         PanelOnFocusEvent(panel=selected_page))
156
157    def get_data(self):
158        """
159        get the data in the current page
160        """
161        pos = self.GetSelection()
162        if pos != -1:
163            selected_page = self.GetPage(pos)
164            return selected_page.get_data()
165
166    def set_model_state(self, state):
167        """
168        receive a state to reset the model in the current page
169        """
170        pos = self.GetSelection()
171        if pos != -1:
172            selected_page = self.GetPage(pos)
173            selected_page.set_model_state(state)
174
175    def get_state(self):
176        """
177        return the state of the current selected page
178        """
179        pos = self.GetSelection()
180        if pos != -1:
181            selected_page = self.GetPage(pos)
182            return selected_page.get_state()
183
184    def close_all(self):
185        """
186        remove all pages, used when a svs file is opened
187        """
188
189        # use while-loop, for-loop will not do the job well.
190        while (self.GetPageCount() > 0):
191            page = self.GetPage(self.GetPageCount() - 1)
192            if self._manager.parent.panel_on_focus == page:
193                self._manager.parent.panel_on_focus = None
194            self._close_helper(selected_page=page)
195            self.DeletePage(self.GetPageCount() - 1)
196        # Clear list of names
197        self.fit_page_name = {}
198        # Clear list of opened pages
199        self.opened_pages = {}
200        self.fit_page_index = 0
201        self.batch_page_index = 0
202
203    def set_state(self, state):
204        """
205        Restore state of the panel
206        """
207        page_is_opened = False
208        if state is not None:
209            for uid, panel in self.opened_pages.iteritems():
210                # Don't return any panel is the exact same page is created
211                if uid == panel.uid and panel.data == state.data:
212                    # the page is still opened
213                    panel.reset_page(state=state)
214                    panel.save_current_state()
215                    page_is_opened = True
216            if not page_is_opened:
217                if state.data.__class__.__name__ != 'list':
218                    # To support older state file format
219                    list_data = [state.data]
220                else:
221                    # Todo: need new file format for the list
222                    list_data = state.data
223                panel = self._manager.add_fit_page(data=list_data)
224                # add data associated to the page created
225                if panel is not None:
226                    self._manager.store_data(uid=panel.uid,
227                                             data_list=list_data,
228                                             caption=panel.window_caption)
229                    panel.reset_page(state=state)
230                    panel.save_current_state()
231
232    def clear_panel(self):
233        """
234        Clear and close all panels, used by guimanager
235        """
236        # close all panels only when svs file opened
237        self.close_all()
238        self.sim_page = None
239        self.batch_page = None
240
241    def on_close_page(self, event=None):
242        """
243        close page and remove all references to the closed page
244        """
245        selected_page = self.GetPage(self.GetSelection())
246        if self.GetPageCount() == 1:
247            if selected_page.get_data() is not None:
248                if event is not None:
249                    event.Veto()
250                return
251        self._close_helper(selected_page=selected_page)
252
253    def close_page_with_data(self, deleted_data):
254        """
255        close a fit page when its data is completely remove from the graph
256        """
257        if deleted_data is None:
258            return
259        for index in range(self.GetPageCount()):
260            selected_page = self.GetPage(index)
261            if hasattr(selected_page, "get_data"):
262                data = selected_page.get_data()
263
264                if data is None:
265                    # the fitpanel exists and only the initial fit page is open
266                    # with no selected data
267                    return
268                if data.id == deleted_data.id:
269                    self._close_helper(selected_page)
270                    self.DeletePage(index)
271                    break
272
273    def set_manager(self, manager):
274        """
275        set panel manager
276
277        :param manager: instance of plugin fitting
278        """
279        self._manager = manager
280        for pos in range(self.GetPageCount()):
281            page = self.GetPage(pos)
282            if page is not None:
283                page.set_manager(self._manager)
284
285    def set_model_list(self, dict):
286        """
287        copy a dictionary of model into its own dictionary
288
289        :param dict: dictionnary made of model name as key and model class
290            as value
291        """
292        self.model_list_box = dict
293
294    def set_model_dict(self, m_dict):
295        """
296        copy a dictionary of model name -> model object
297
298        :param m_dict: dictionary linking model name -> model object
299        """
300
301    def get_current_page(self):
302        """
303        :return: the current page selected
304
305        """
306        return self.GetPage(self.GetSelection())
307
308    def add_sim_page(self, caption="Const & Simul Fit"):
309        """
310        Add the simultaneous fit page
311        """
312        from simfitpage import SimultaneousFitPage
313        page_finder = self._manager.get_page_finder()
314        if caption == "Const & Simul Fit":
315            self.sim_page = SimultaneousFitPage(self, page_finder=page_finder,
316                                                 id=wx.ID_ANY, batch_on=False)
317            self.sim_page.window_caption = caption
318            self.sim_page.window_name = caption
319            self.sim_page.uid = wx.NewId()
320            self.AddPage(self.sim_page, caption, True)
321            self.sim_page.set_manager(self._manager)
322            self.enable_close_button()
323            return self.sim_page
324        else:
325            self.batch_page = SimultaneousFitPage(self, batch_on=True,
326                                                  page_finder=page_finder)
327            self.batch_page.window_caption = caption
328            self.batch_page.window_name = caption
329            self.batch_page.uid = wx.NewId()
330            self.AddPage(self.batch_page, caption, True)
331            self.batch_page.set_manager(self._manager)
332            self.enable_close_button()
333            return self.batch_page
334
335    def add_empty_page(self):
336        """
337        add an empty page
338        """
339        if self.batch_on:
340            from batchfitpage import BatchFitPage
341            panel = BatchFitPage(parent=self)
342            self.batch_page_index += 1
343            caption = "BatchPage" + str(self.batch_page_index)
344            panel.set_index_model(self.batch_page_index)
345        else:
346            # Increment index of fit page
347            from fitpage import FitPage
348            panel = FitPage(parent=self)
349            self.fit_page_index += 1
350            caption = "FitPage" + str(self.fit_page_index)
351            panel.set_index_model(self.fit_page_index)
352        panel.batch_on = self.batch_on
353        panel._set_save_flag(not panel.batch_on)
354        panel.set_model_dictionary(self.model_dictionary)
355        panel.populate_box(model_dict=self.model_list_box)
356        panel.formfactor_combo_init()
357        panel.set_manager(self._manager)
358        panel.window_caption = caption
359        panel.window_name = caption
360        self.AddPage(panel, caption, select=True)
361        self.opened_pages[panel.uid] = panel
362        self._manager.create_fit_problem(panel.uid)
363        self._manager.page_finder[panel.uid].add_data(panel.get_data())
364        self.enable_close_button()
365        panel.on_set_focus(None)
366        return panel
367
368    def enable_close_button(self):
369        """
370        display the close button on tab for more than 1 tabs else remove the
371        close button
372        """
373        if self.GetPageCount() <= 1:
374            style = self.GetWindowStyleFlag()
375            flag = wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
376            if style & wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB == flag:
377                style = style & ~wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
378                self.SetWindowStyle(style)
379        else:
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 |= wx.aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
384                self.SetWindowStyle(style)
385
386    def delete_data(self, data):
387        """
388        Delete the given data
389        """
390        if data.__class__.__name__ != "list":
391            raise ValueError, "Fitpanel delete_data expect list of id"
392        else:
393            for page in self.opened_pages.values():
394                pos = self.GetPageIndex(page)
395                temp_data = page.get_data()
396                if temp_data is not None and temp_data.id in data:
397                    self.close_page_with_data(temp_data)
398            if self.sim_page is not None:
399                if len(self.sim_page.model_list) == 0:
400                    pos = self.GetPageIndex(self.sim_page)
401                    self.SetSelection(pos)
402                    self.on_close_page(event=None)
403                    temp = self.GetSelection()
404                    self.DeletePage(pos)
405                    self.sim_page = None
406                    self.batch_on = False
407            if self.GetPageCount() == 0:
408                self._manager.on_add_new_page(event=None)
409
410    def set_data_on_batch_mode(self, data_list):
411        """
412        Add all data to a single tab when the application is on Batch mode.
413        However all data in the set of data must be either 1D or 2D type.
414        This method presents option to select the data type before creating a
415        tab.
416        """
417        data_1d_list = []
418        data_2d_list = []
419        group_id_1d = wx.NewId()
420        # separate data into data1d and data2d list
421        for data in data_list:
422            if data.__class__.__name__ == "Data1D":
423                data.group_id = group_id_1d
424                data_1d_list.append(data)
425            if data.__class__.__name__ == "Data2D":
426                data.group_id = wx.NewId()
427                data_2d_list.append(data)
428        page = None
429        for p in self.opened_pages.values():
430            # check if there is an empty page to fill up
431            if not check_data_validity(p.get_data()) and p.batch_on:
432
433                # make sure data get placed in 1D empty tab if data is 1D
434                # else data get place on 2D tab empty tab
435                enable2D = p.get_view_mode()
436                if (data.__class__.__name__ == "Data2D" and enable2D)\
437                or (data.__class__.__name__ == "Data1D" and not enable2D):
438                    page = p
439                    break
440        if data_1d_list and data_2d_list:
441            # need to warning the user that this batch is a special case
442            from sas.sasgui.perspectives.fitting.fitting_widgets import \
443                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_list: 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.