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

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 277257f was 277257f, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

clean up plugin-model handling code; preserve active parameter values when plugin is updated

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