"""
FitPanel class contains fields allowing to fit models and data
:note: For Fit to be performed the user should check at least one parameter
on fit Panel window.
"""
import wx
from wx.aui import AuiNotebook as nb
from sas.sascalc.fit.models import ModelManager
from sas.sasgui.guiframe.panel_base import PanelBase
from sas.sasgui.guiframe.events import PanelOnFocusEvent, StatusEvent
from sas.sasgui.guiframe.dataFitting import check_data_validity
from . import basepage
from .fitpage import FitPage
from .simfitpage import SimultaneousFitPage
from .batchfitpage import BatchFitPage
from .fitting_widgets import BatchDataDialog
_BOX_WIDTH = 80
[docs]class FitPanel(nb, PanelBase):
"""
FitPanel class contains fields allowing to fit models and data
:note: For Fit to be performed the user should check at least one parameter
on fit Panel window.
"""
# Internal name for the AUI manager
window_name = "Fit panel"
# Title to appear on top of the window
window_caption = "Fit Panel "
CENTER_PANE = True
def __init__(self, parent, manager=None, *args, **kwargs):
"""
"""
nb.__init__(self, parent, wx.ID_ANY,
style=wx.aui.AUI_NB_WINDOWLIST_BUTTON |
wx.aui.AUI_NB_DEFAULT_STYLE |
wx.CLIP_CHILDREN)
PanelBase.__init__(self, parent)
# self.SetWindowStyleFlag(style=nb.FNB_FANCY_TABS)
self._manager = manager
self.parent = parent
self.event_owner = None
# dictionary of miodel {model class name, model class}
self.menu_mng = ModelManager()
self.model_list_box = self.menu_mng.get_model_list()
# pageClosedEvent = nb.EVT_FLATNOTEBOOK_PAGE_CLOSING
self.model_dictionary = self.menu_mng.get_model_dictionary()
self.pageClosedEvent = wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE
self.Bind(self.pageClosedEvent, self.on_close_page)
# save the title of the last page tab added
self.fit_page_name = {}
# list of existing fit page
self.opened_pages = {}
# index of fit page
self.fit_page_index = 0
# index of batch page
self.batch_page_index = 0
# page of simultaneous fit
self.sim_page = None
self.batch_page = None
# get the state of a page
self.Bind(basepage.EVT_PAGE_INFO, self._onGetstate)
self.Bind(basepage.EVT_PREVIOUS_STATE, self._onUndo)
self.Bind(basepage.EVT_NEXT_STATE, self._onRedo)
self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.on_page_changing)
self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSED, self.on_closed)
[docs] def on_closed(self, event):
"""
"""
if self.GetPageCount() == 0:
self.add_empty_page()
self.enable_close_button()
[docs] def save_project(self, doc=None):
"""
return an xml node containing state of the panel
that guiframe can write to file
"""
# Iterate through all pages and check for batch fitting
batch_state = None
if self.sim_page is not None:
batch_state = self.sim_page.set_state()
for uid, page in self.opened_pages.iteritems():
data = page.get_data()
# state must be cloned
state = page.get_state().clone()
# data_list only populated with real data
# Fake object in data from page.get_data() if model is selected
if len(page.data_list) is not 0 and page.model is not None:
new_doc = self._manager.state_reader.write_toXML(data,
state,
batch_state)
# Fit #2 through #n are append to first fit
if doc is not None and hasattr(doc, "firstChild"):
# Only append if properly formed new_doc
if new_doc is not None and hasattr(new_doc, "firstChild"):
child = new_doc.firstChild.firstChild
doc.firstChild.appendChild(child)
# First fit defines the main document
else:
doc = new_doc
return doc
[docs] def update_model_list(self):
"""
"""
temp = self.menu_mng.update()
if temp:
self.model_list_box = temp
return temp
[docs] def reset_pmodel_list(self):
"""
"""
self.model_list_box = self.menu_mng.plugins_reset()
return self.model_list_box
[docs] def get_page_by_id(self, uid):
"""
"""
if uid not in self.opened_pages:
msg = "Fitpanel cannot find ID: %s in self.opened_pages" % str(uid)
raise ValueError, msg
else:
return self.opened_pages[uid]
[docs] def on_page_changing(self, event):
"""
calls the function when the current event handler has exited. avoiding
to call panel on focus on a panel that is currently deleted
"""
wx.CallAfter(self.helper_on_page_change)
[docs] def helper_on_page_change(self):
"""
"""
pos = self.GetSelection()
if pos != -1:
selected_page = self.GetPage(pos)
wx.PostEvent(self._manager.parent,
PanelOnFocusEvent(panel=selected_page))
self.enable_close_button()
[docs] def on_set_focus(self, event):
"""
"""
pos = self.GetSelection()
if pos != -1:
selected_page = self.GetPage(pos)
wx.PostEvent(self._manager.parent,
PanelOnFocusEvent(panel=selected_page))
[docs] def get_data(self):
"""
get the data in the current page
"""
pos = self.GetSelection()
if pos != -1:
selected_page = self.GetPage(pos)
return selected_page.get_data()
[docs] def set_model_state(self, state):
"""
receive a state to reset the model in the current page
"""
pos = self.GetSelection()
if pos != -1:
selected_page = self.GetPage(pos)
selected_page.set_model_state(state)
[docs] def get_state(self):
"""
return the state of the current selected page
"""
pos = self.GetSelection()
if pos != -1:
selected_page = self.GetPage(pos)
return selected_page.get_state()
[docs] def close_all(self):
"""
remove all pages, used when a svs file is opened
"""
# use while-loop, for-loop will not do the job well.
while (self.GetPageCount() > 0):
page = self.GetPage(self.GetPageCount() - 1)
if self._manager.parent.panel_on_focus == page:
self._manager.parent.panel_on_focus = None
self._close_helper(selected_page=page)
self.DeletePage(self.GetPageCount() - 1)
# Clear list of names
self.fit_page_name = {}
# Clear list of opened pages
self.opened_pages = {}
self.fit_page_index = 0
self.batch_page_index = 0
[docs] def set_state(self, state):
"""
Restore state of the panel
"""
page_is_opened = False
if state is not None:
for uid, panel in self.opened_pages.iteritems():
# Don't return any panel is the exact same page is created
if uid == panel.uid and panel.data == state.data:
# the page is still opened
panel.reset_page(state=state)
panel.save_current_state()
page_is_opened = True
if not page_is_opened:
if state.data.__class__.__name__ != 'list':
# To support older state file format
list_data = [state.data]
else:
# Todo: need new file format for the list
list_data = state.data
panel = self._manager.add_fit_page(data=list_data)
# add data associated to the page created
if panel is not None:
self._manager.store_data(uid=panel.uid,
data_list=list_data,
caption=panel.window_caption)
panel.reset_page(state=state)
panel.save_current_state()
[docs] def clear_panel(self):
"""
Clear and close all panels, used by guimanager
"""
# close all panels only when svs file opened
self.close_all()
self.sim_page = None
self.batch_page = None
[docs] def on_close_page(self, event=None):
"""
close page and remove all references to the closed page
"""
selected_page = self.GetPage(self.GetSelection())
if self.GetPageCount() == 1:
if selected_page.get_data() is not None:
if event is not None:
event.Veto()
return
self._close_helper(selected_page=selected_page)
[docs] def close_page_with_data(self, deleted_data):
"""
close a fit page when its data is completely remove from the graph
"""
if deleted_data is None:
return
for index in range(self.GetPageCount()):
selected_page = self.GetPage(index)
if hasattr(selected_page, "get_data"):
data = selected_page.get_data()
if data is None:
# the fitpanel exists and only the initial fit page is open
# with no selected data
return
if data.id == deleted_data.id:
self._close_helper(selected_page)
self.DeletePage(index)
break
[docs] def set_manager(self, manager):
"""
set panel manager
:param manager: instance of plugin fitting
"""
self._manager = manager
for pos in range(self.GetPageCount()):
page = self.GetPage(pos)
if page is not None:
page.set_manager(self._manager)
[docs] def set_model_list(self, dict):
"""
copy a dictionary of model into its own dictionary
:param dict: dictionnary made of model name as key and model class
as value
"""
self.model_list_box = dict
[docs] def set_model_dictionary(self, model_dictionary):
"""
copy a dictionary of model name -> model object
:param model_dictionary: dictionary linking model name -> model object
"""
[docs] def get_current_page(self):
"""
:return: the current page selected
"""
return self.GetPage(self.GetSelection())
[docs] def add_sim_page(self, caption="Const & Simul Fit"):
"""
Add the simultaneous fit page
"""
page_finder = self._manager.get_page_finder()
if caption == "Const & Simul Fit":
self.sim_page = SimultaneousFitPage(self, page_finder=page_finder,
id=wx.ID_ANY, batch_on=False)
self.sim_page.window_caption = caption
self.sim_page.window_name = caption
self.sim_page.uid = wx.NewId()
self.AddPage(self.sim_page, caption, True)
self.sim_page.set_manager(self._manager)
self.enable_close_button()
return self.sim_page
else:
self.batch_page = SimultaneousFitPage(self, batch_on=True,
page_finder=page_finder)
self.batch_page.window_caption = caption
self.batch_page.window_name = caption
self.batch_page.uid = wx.NewId()
self.AddPage(self.batch_page, caption, True)
self.batch_page.set_manager(self._manager)
self.enable_close_button()
return self.batch_page
[docs] def add_empty_page(self):
"""
add an empty page
"""
if self.batch_on:
panel = BatchFitPage(parent=self)
self.batch_page_index += 1
caption = "BatchPage" + str(self.batch_page_index)
panel.set_index_model(self.batch_page_index)
else:
# Increment index of fit page
panel = FitPage(parent=self)
self.fit_page_index += 1
caption = "FitPage" + str(self.fit_page_index)
panel.set_index_model(self.fit_page_index)
panel.batch_on = self.batch_on
panel._set_save_flag(not panel.batch_on)
panel.set_model_dictionary(self.model_dictionary)
panel.populate_box(model_list_box=self.model_list_box)
panel.formfactor_combo_init()
panel.set_manager(self._manager)
panel.window_caption = caption
panel.window_name = caption
self.AddPage(panel, caption, select=True)
self.opened_pages[panel.uid] = panel
self._manager.create_fit_problem(panel.uid)
self._manager.page_finder[panel.uid].add_data(panel.get_data())
self.enable_close_button()
panel.on_set_focus(None)
return panel
[docs] def delete_data(self, data):
"""
Delete the given data
"""
if data.__class__.__name__ != "list":
raise ValueError, "Fitpanel delete_data expect list of id"
else:
for page in self.opened_pages.values():
pos = self.GetPageIndex(page)
temp_data = page.get_data()
if temp_data is not None and temp_data.id in data:
self.close_page_with_data(temp_data)
if self.sim_page is not None:
if len(self.sim_page.model_list) == 0:
pos = self.GetPageIndex(self.sim_page)
self.SetSelection(pos)
self.on_close_page(event=None)
self.DeletePage(pos)
self.sim_page = None
self.batch_on = False
if self.GetPageCount() == 0:
self._manager.on_add_new_page(event=None)
[docs] def set_data_on_batch_mode(self, data_list):
"""
Add all data to a single tab when the application is on Batch mode.
However all data in the set of data must be either 1D or 2D type.
This method presents option to select the data type before creating a
tab.
"""
data_1d_list = []
data_2d_list = []
group_id_1d = wx.NewId()
# separate data into data1d and data2d list
for data in data_list:
if data.__class__.__name__ == "Data1D":
data.group_id = group_id_1d
data_1d_list.append(data)
if data.__class__.__name__ == "Data2D":
data.group_id = wx.NewId()
data_2d_list.append(data)
page = None
for p in self.opened_pages.values():
# check if there is an empty page to fill up
if not check_data_validity(p.get_data()) and p.batch_on:
# make sure data get placed in 1D empty tab if data is 1D
# else data get place on 2D tab empty tab
enable2D = p.get_view_mode()
if (data.__class__.__name__ == "Data2D" and enable2D)\
or (data.__class__.__name__ == "Data1D" and not enable2D):
page = p
break
if data_1d_list and data_2d_list:
# need to warning the user that this batch is a special case
dlg = BatchDataDialog(self)
if dlg.ShowModal() == wx.ID_OK:
data_type = dlg.get_data()
dlg.Destroy()
if page is None:
page = self.add_empty_page()
if data_type == 1:
# user has selected only data1D
page.fill_data_combobox(data_1d_list)
elif data_type == 2:
page.fill_data_combobox(data_2d_list)
else:
# the batch analysis is canceled
dlg.Destroy()
return None
else:
if page is None:
page = self.add_empty_page()
if data_1d_list and not data_2d_list:
# only on type of data
page.fill_data_combobox(data_1d_list)
elif not data_1d_list and data_2d_list:
page.fill_data_combobox(data_2d_list)
pos = self.GetPageIndex(page)
page.batch_on = self.batch_on
page._set_save_flag(not page.batch_on)
self.SetSelection(pos)
self.opened_pages[page.uid] = page
return page
[docs] def set_data(self, data_list):
"""
Add a fitting page on the notebook contained by fitpanel
:param data_list: data to fit
:return panel : page just added for further used.
is used by fitting module
"""
if not data_list:
return None
if self.batch_on:
return self.set_data_on_batch_mode(data_list)
else:
data = None
try:
data = data_list[0]
except Exception:
# for 'fitv' files
data_list = [data]
data = data_list[0]
if data is None:
return None
focused_page = self.GetPage(self.GetSelection())
for page in self.opened_pages.values():
# check if the selected data existing in the fitpanel
pos = self.GetPageIndex(page)
if not check_data_validity(page.get_data()) and not page.batch_on:
if page.model is not None and page != focused_page:
# Page has an active theory and is in background - don't
# send data here.
continue
# make sure data get placed in 1D empty tab if data is 1D
# else data get place on 2D tab empty tab
enable2D = page.get_view_mode()
if (data.__class__.__name__ == "Data2D" and enable2D)\
or (data.__class__.__name__ == "Data1D" and not enable2D):
page.batch_on = self.batch_on
page._set_save_flag(not page.batch_on)
page.fill_data_combobox(data_list)
# caption = "FitPage" + str(self.fit_page_index)
self.SetPageText(pos, page.window_caption)
self.SetSelection(pos)
return page
# create new page and add data
page = self.add_empty_page()
pos = self.GetPageIndex(page)
page.fill_data_combobox(data_list)
self.opened_pages[page.uid] = page
self.SetSelection(pos)
return page
def _onGetstate(self, event):
"""
copy the state of a page
"""
page = event.page
if page.uid in self.fit_page_name:
self.fit_page_name[page.uid].appendItem(page.createMemento())
def _onUndo(self, event):
"""
return the previous state of a given page is available
"""
page = event.page
if page.uid in self.fit_page_name:
if self.fit_page_name[page.uid].getCurrentPosition() == 0:
state = None
else:
state = self.fit_page_name[page.uid].getPreviousItem()
page._redo.Enable(True)
page.reset_page(state)
def _onRedo(self, event):
"""
return the next state available
"""
page = event.page
if page.uid in self.fit_page_name:
length = len(self.fit_page_name[page.uid])
if self.fit_page_name[page.uid].getCurrentPosition() == length - 1:
state = None
page._redo.Enable(False)
page._redo.Enable(True)
else:
state = self.fit_page_name[page.uid].getNextItem()
page.reset_page(state)
def _close_helper(self, selected_page):
"""
Delete the given page from the notebook
"""
# remove hint page
# if selected_page == self.hint_page:
# return
# removing sim_page
if selected_page == self.sim_page:
self._manager.sim_page = None
return
if selected_page == self.batch_page:
self._manager.batch_page = None
return
# closing other pages
state = selected_page.createMemento()
page_finder = self._manager.get_page_finder()
# removing fit page
data = selected_page.get_data()
# Don' t remove plot for 2D
flag = True
if data.__class__.__name__ == 'Data2D':
flag = False
if selected_page in page_finder:
# Delete the name of the page into the list of open page
for uid, list in self.opened_pages.iteritems():
# Don't return any panel is the exact same page is created
if flag and selected_page.uid == uid:
self._manager.remove_plot(uid, theory=False)
break
del page_finder[selected_page]
# Delete the name of the page into the list of open page
for uid, list in self.opened_pages.iteritems():
# Don't return any panel is the exact same page is created
if selected_page.uid == uid:
del self.opened_pages[selected_page.uid]
break
# remove the check box link to the model name of the selected_page
try:
self.sim_page.draw_page()
except:
# that page is already deleted no need to remove check box on
# non existing page
pass
try:
self.batch_page.draw_page()
except:
# that page is already deleted no need to remove check box on
# non existing page
pass