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

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

Don't send data to fit panel if it has an active theory and is in bg

Fixes #986

  • Property mode set to 100644
File size: 22.3 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.SetSelection(pos)
398                    self.on_close_page(event=None)
399                    temp = self.GetSelection()
400                    self.DeletePage(temp)
401            if self.sim_page is not None:
402                if len(self.sim_page.model_list) == 0:
403                    pos = self.GetPageIndex(self.sim_page)
404                    self.SetSelection(pos)
405                    self.on_close_page(event=None)
406                    temp = self.GetSelection()
407                    self.DeletePage(temp)
408                    self.sim_page = None
409                    self.batch_on = False
410            if self.GetPageCount() == 0:
411                self._manager.on_add_new_page(event=None)
412
413    def set_data_on_batch_mode(self, data_list):
414        """
415        Add all data to a single tab when the application is on Batch mode.
416        However all data in the set of data must be either 1D or 2D type.
417        This method presents option to select the data type before creating a
418        tab.
419        """
420        data_1d_list = []
421        data_2d_list = []
422        group_id_1d = wx.NewId()
423        # separate data into data1d and data2d list
424        for data in data_list:
425            if data.__class__.__name__ == "Data1D":
426                data.group_id = group_id_1d
427                data_1d_list.append(data)
428            if data.__class__.__name__ == "Data2D":
429                data.group_id = wx.NewId()
430                data_2d_list.append(data)
431        page = None
432        for p in self.opened_pages.values():
433            # check if there is an empty page to fill up
434            if not check_data_validity(p.get_data()) and p.batch_on:
435
436                # make sure data get placed in 1D empty tab if data is 1D
437                # else data get place on 2D tab empty tab
438                enable2D = p.get_view_mode()
439                if (data.__class__.__name__ == "Data2D" and enable2D)\
440                or (data.__class__.__name__ == "Data1D" and not enable2D):
441                    page = p
442                    break
443        if data_1d_list and data_2d_list:
444            # need to warning the user that this batch is a special case
445            from sas.sasgui.perspectives.fitting.fitting_widgets import \
446                BatchDataDialog
447            dlg = BatchDataDialog(self)
448            if dlg.ShowModal() == wx.ID_OK:
449                data_type = dlg.get_data()
450                dlg.Destroy()
451                if page is None:
452                    page = self.add_empty_page()
453                if data_type == 1:
454                    # user has selected only data1D
455                    page.fill_data_combobox(data_1d_list)
456                elif data_type == 2:
457                    page.fill_data_combobox(data_2d_list)
458            else:
459                # the batch analysis is canceled
460                dlg.Destroy()
461                return None
462        else:
463            if page is None:
464                page = self.add_empty_page()
465            if data_1d_list and not data_2d_list:
466                # only on type of data
467                page.fill_data_combobox(data_1d_list)
468            elif not data_1d_list and data_2d_list:
469                page.fill_data_combobox(data_2d_list)
470
471        pos = self.GetPageIndex(page)
472        page.batch_on = self.batch_on
473        page._set_save_flag(not page.batch_on)
474        self.SetSelection(pos)
475        self.opened_pages[page.uid] = page
476        return page
477
478    def set_data(self, data_list):
479        """
480        Add a fitting page on the notebook contained by fitpanel
481
482        :param data_list: data to fit
483
484        :return panel : page just added for further used.
485        is used by fitting module
486
487        """
488        if not data_list:
489            return None
490        if self.batch_on:
491            return self.set_data_on_batch_mode(data_list)
492        else:
493            data = None
494            try:
495                data = data_list[0]
496            except Exception:
497                # for 'fitv' files
498                data_list = [data]
499                data = data_list[0]
500
501            if data is None:
502                return None
503        focused_page = self.GetPage(self.GetSelection())
504        for page in self.opened_pages.values():
505            # check if the selected data existing in the fitpanel
506            pos = self.GetPageIndex(page)
507            if not check_data_validity(page.get_data()) and not page.batch_on:
508                if page.model is not None and page != focused_page:
509                    # Page has an active theory and is in background - don't
510                    # send data here.
511                    continue
512                # make sure data get placed in 1D empty tab if data is 1D
513                # else data get place on 2D tab empty tab
514                enable2D = page.get_view_mode()
515                if (data.__class__.__name__ == "Data2D" and enable2D)\
516                   or (data.__class__.__name__ == "Data1D" and not enable2D):
517                    page.batch_on = self.batch_on
518                    page._set_save_flag(not page.batch_on)
519                    page.fill_data_combobox(data_list)
520                    # caption = "FitPage" + str(self.fit_page_index)
521                    self.SetPageText(pos, page.window_caption)
522                    self.SetSelection(pos)
523                    return page
524        # create new page and add data
525        page = self.add_empty_page()
526        pos = self.GetPageIndex(page)
527        page.fill_data_combobox(data_list)
528        self.opened_pages[page.uid] = page
529        self.SetSelection(pos)
530        return page
531
532    def _onGetstate(self, event):
533        """
534        copy the state of a page
535        """
536        page = event.page
537        if page.uid in self.fit_page_name:
538            self.fit_page_name[page.uid].appendItem(page.createMemento())
539
540    def _onUndo(self, event):
541        """
542        return the previous state of a given page is available
543        """
544        page = event.page
545        if page.uid in self.fit_page_name:
546            if self.fit_page_name[page.uid].getCurrentPosition() == 0:
547                state = None
548            else:
549                state = self.fit_page_name[page.uid].getPreviousItem()
550                page._redo.Enable(True)
551            page.reset_page(state)
552
553    def _onRedo(self, event):
554        """
555        return the next state available
556        """
557        page = event.page
558        if page.uid in self.fit_page_name:
559            length = len(self.fit_page_name[page.uid])
560            if self.fit_page_name[page.uid].getCurrentPosition() == length - 1:
561                state = None
562                page._redo.Enable(False)
563                page._redo.Enable(True)
564            else:
565                state = self.fit_page_name[page.uid].getNextItem()
566            page.reset_page(state)
567
568    def _close_helper(self, selected_page):
569        """
570        Delete the given page from the notebook
571        """
572        # remove hint page
573        # if selected_page == self.hint_page:
574        #    return
575        # removing sim_page
576        if selected_page == self.sim_page:
577            self._manager.sim_page = None
578            return
579        if selected_page == self.batch_page:
580            self._manager.batch_page = None
581            return
582        # closing other pages
583        state = selected_page.createMemento()
584        page_finder = self._manager.get_page_finder()
585        # removing fit page
586        data = selected_page.get_data()
587        # Don' t remove plot for 2D
588        flag = True
589        if data.__class__.__name__ == 'Data2D':
590            flag = False
591        if selected_page in page_finder:
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 flag and selected_page.uid == uid:
596                    self._manager.remove_plot(uid, theory=False)
597                    break
598            del page_finder[selected_page]
599
600        # Delete the name of the page into the list of open page
601        for uid, list in self.opened_pages.iteritems():
602            # Don't return any panel is the exact same page is created
603            if selected_page.uid == uid:
604                del self.opened_pages[selected_page.uid]
605                break
606        # remove the check box link to the model name of the selected_page
607        try:
608            self.sim_page.draw_page()
609        except:
610            # that page is already deleted no need to remove check box on
611            # non existing page
612            pass
613        try:
614            self.batch_page.draw_page()
615        except:
616            # that page is already deleted no need to remove check box on
617            # non existing page
618            pass
Note: See TracBrowser for help on using the repository browser.