diff --git a/Code/Mantid/Framework/API/src/ParameterTie.cpp b/Code/Mantid/Framework/API/src/ParameterTie.cpp index 3b71b74f17d2facd819c1bb9943148d1ef99c017..a719385ce3cfec4471a7a492afd56fe82ca9a334 100644 --- a/Code/Mantid/Framework/API/src/ParameterTie.cpp +++ b/Code/Mantid/Framework/API/src/ParameterTie.cpp @@ -103,6 +103,7 @@ namespace API i++; } + m_expression = ""; while(boost::regex_search(start,end,res,rx)) { m_expression.append(start,res[0].first); diff --git a/Code/Mantid/MantidPlot/CMakeLists.txt b/Code/Mantid/MantidPlot/CMakeLists.txt index e3a0adb7606078ce3d4374afe23152a119609bea..be3a9959d4221b3fcea5646dde21279ec4386469 100644 --- a/Code/Mantid/MantidPlot/CMakeLists.txt +++ b/Code/Mantid/MantidPlot/CMakeLists.txt @@ -763,6 +763,7 @@ include_directories ( ${CMAKE_CURRENT_BINARY_DIR} ) qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/Images/images.qrc ) qt4_add_resources ( RES_FILES ${CMAKE_CURRENT_SOURCE_DIR}/icons/icons.qrc ) qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/MantidQt/SliceViewer/icons/SliceViewerIcons.qrc ) +qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/MantidQt/CustomInterfaces/icons/CustomInterfacesIcons.qrc ) qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/Vates/VatesSimpleGui/ViewWidgets/icons/ViewWidgetsIcons.qrc ) ########################################################################### diff --git a/Code/Mantid/MantidQt/CustomInterfaces/CMakeLists.txt b/Code/Mantid/MantidQt/CustomInterfaces/CMakeLists.txt index 2c14d45c6bd0a3df07311218e24c3c612debfce4..01fa2bc88e9304358293ef35abc988736cb37f8d 100644 --- a/Code/Mantid/MantidQt/CustomInterfaces/CMakeLists.txt +++ b/Code/Mantid/MantidQt/CustomInterfaces/CMakeLists.txt @@ -36,6 +36,7 @@ set ( SRC_FILES src/MSDFit.cpp src/MantidEV.cpp src/MantidEVWorker.cpp + src/MultiDatasetFit.cpp src/Muon/ALCBaselineModellingModel.cpp src/Muon/ALCBaselineModellingPresenter.cpp src/Muon/ALCBaselineModellingView.cpp @@ -120,6 +121,7 @@ set ( INC_FILES inc/MantidQtCustomInterfaces/MSDFit.h inc/MantidQtCustomInterfaces/MantidEV.h inc/MantidQtCustomInterfaces/MantidEVWorker.h + inc/MantidQtCustomInterfaces/MultiDatasetFit.h inc/MantidQtCustomInterfaces/Muon/ALCBaselineModellingModel.h inc/MantidQtCustomInterfaces/Muon/ALCBaselineModellingPresenter.h inc/MantidQtCustomInterfaces/Muon/ALCBaselineModellingView.h @@ -205,6 +207,7 @@ set ( MOC_FILES inc/MantidQtCustomInterfaces/Background.h inc/MantidQtCustomInterfaces/IndirectTab.h inc/MantidQtCustomInterfaces/JumpFit.h inc/MantidQtCustomInterfaces/MSDFit.h + inc/MantidQtCustomInterfaces/MultiDatasetFit.h inc/MantidQtCustomInterfaces/Muon/ALCBaselineModellingPresenter.h inc/MantidQtCustomInterfaces/Muon/ALCBaselineModellingView.h inc/MantidQtCustomInterfaces/Muon/ALCDataLoadingPresenter.h @@ -236,7 +239,8 @@ set ( MOC_FILES inc/MantidQtCustomInterfaces/Background.h inc/MantidQtCustomInterfaces/StepScan.h ) -set ( UI_FILES inc/MantidQtCustomInterfaces/CreateMDWorkspace.ui +set ( UI_FILES inc/MantidQtCustomInterfaces/AddWorkspace.ui + inc/MantidQtCustomInterfaces/CreateMDWorkspace.ui inc/MantidQtCustomInterfaces/CreateMDWorkspaceAlgDialog.ui inc/MantidQtCustomInterfaces/DirectConvertToEnergy.ui inc/MantidQtCustomInterfaces/IndirectBayes.ui @@ -249,6 +253,7 @@ set ( UI_FILES inc/MantidQtCustomInterfaces/CreateMDWorkspace.ui inc/MantidQtCustomInterfaces/IndirectSassena.ui inc/MantidQtCustomInterfaces/IndirectSimulation.ui inc/MantidQtCustomInterfaces/JumpFit.ui + inc/MantidQtCustomInterfaces/MultiDatasetFit.ui inc/MantidQtCustomInterfaces/Muon/ALCBaselineModellingView.ui inc/MantidQtCustomInterfaces/Muon/ALCDataLoadingView.ui inc/MantidQtCustomInterfaces/Muon/ALCInterface.ui @@ -265,6 +270,7 @@ set ( UI_FILES inc/MantidQtCustomInterfaces/CreateMDWorkspace.ui inc/MantidQtCustomInterfaces/Stretch.ui inc/MantidQtCustomInterfaces/MantidEV.ui inc/MantidQtCustomInterfaces/StepScan.ui + inc/MantidQtCustomInterfaces/EditLocalParameterDialog.ui ) set ( TEST_FILES @@ -296,8 +302,9 @@ else ( ${Boost_VERSION} GREATER 104799 AND ${qt_version} VERSION_GREATER 4.7.3 ) endif ( ${Boost_VERSION} GREATER 104799 AND ${qt_version} VERSION_GREATER 4.7.3 ) qt4_wrap_cpp ( MOCCED_FILES ${MOC_FILES} OPTIONS ${extra_options} ) +qt4_add_resources ( RES_FILES icons/CustomInterfacesIcons.qrc ) -set ( ALL_SRC ${SRC_FILES} ${MOCCED_FILES} ) +set ( ALL_SRC ${SRC_FILES} ${MOCCED_FILES} )#${RES_FILES}) qt4_wrap_ui ( UI_HDRS ${UI_FILES} ) include_directories ( ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/Code/Mantid/MantidQt/CustomInterfaces/icons/CustomInterfacesIcons.qrc b/Code/Mantid/MantidQt/CustomInterfaces/icons/CustomInterfacesIcons.qrc new file mode 100644 index 0000000000000000000000000000000000000000..a9dba762f557dc591637a434ab628092634b27c5 --- /dev/null +++ b/Code/Mantid/MantidQt/CustomInterfaces/icons/CustomInterfacesIcons.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/MultiDatasetFit/icons"> + <file>zoom.png</file> + <file>panning.png</file> + </qresource> +</RCC> diff --git a/Code/Mantid/MantidQt/CustomInterfaces/icons/panning.png b/Code/Mantid/MantidQt/CustomInterfaces/icons/panning.png new file mode 100644 index 0000000000000000000000000000000000000000..6b48aa1d092071beec4bc7214a01cc4cecbef555 Binary files /dev/null and b/Code/Mantid/MantidQt/CustomInterfaces/icons/panning.png differ diff --git a/Code/Mantid/MantidQt/CustomInterfaces/icons/zoom.png b/Code/Mantid/MantidQt/CustomInterfaces/icons/zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..b6f49f6d1ed122aad52117d51e6abeb26ddb2ad3 Binary files /dev/null and b/Code/Mantid/MantidQt/CustomInterfaces/icons/zoom.png differ diff --git a/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/AddWorkspace.ui b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/AddWorkspace.ui new file mode 100644 index 0000000000000000000000000000000000000000..091bbe5a6bd025941a8e8b9d65a08c08fa4dec23 --- /dev/null +++ b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/AddWorkspace.ui @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AddWorkspace</class> + <widget class="QDialog" name="AddWorkspace"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>349</width> + <height>171</height> + </rect> + </property> + <property name="windowTitle"> + <string>Add Workspace</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Select data</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout" columnstretch="0,0"> + <item row="0" column="1"> + <widget class="QComboBox" name="cbWorkspaceName"> + <property name="toolTip"> + <string>Select a workspace for fitting</string> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <property name="insertPolicy"> + <enum>QComboBox::NoInsert</enum> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Workspace Indices</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="leWSIndices"> + <property name="toolTip"> + <string>List of workspace indices. For example, 1,3,5,10-20</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Workspace</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="cbAllSpectra"> + <property name="toolTip"> + <string>Check to select all spectra in the workspace</string> + </property> + <property name="text"> + <string>All Spectra</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>11</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>AddWorkspace</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>AddWorkspace</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EditLocalParameterDialog.ui b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EditLocalParameterDialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..cf485339ee42a1d75200864d04b7abdd291839cd --- /dev/null +++ b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/EditLocalParameterDialog.ui @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>EditLocalParameterDialog</class> + <widget class="QDialog" name="EditLocalParameterDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit parameter values</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTableWidget" name="tableWidget"> + <column> + <property name="text"> + <string>Parameter</string> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + </column> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>EditLocalParameterDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>EditLocalParameterDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/MultiDatasetFit.h b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/MultiDatasetFit.h new file mode 100644 index 0000000000000000000000000000000000000000..185b48f983775e327f6022a9e3ba2e7b0aa16098 --- /dev/null +++ b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/MultiDatasetFit.h @@ -0,0 +1,224 @@ +#ifndef MULTIDATASETFIT_H_ +#define MULTIDATASETFIT_H_ + +#include "MantidQtAPI/UserSubWindow.h" +#include "MantidAPI/AlgorithmObserver.h" +#include "MantidQtAPI/WorkspaceObserver.h" +#include "ui_MultiDatasetFit.h" +#include "ui_AddWorkspace.h" +#include "ui_EditLocalParameterDialog.h" + +#include <boost/shared_ptr.hpp> +#include <QMap> +#include <vector> + +// Forward declarations +class QwtPlot; +class QwtPlotZoomer; +class QwtPlotPanner; +class QwtPlotMagnifier; +class QTableWidget; +class QComboBox; +class QPushButton; + +namespace Mantid +{ +namespace API +{ + class IFunction; + class IAlgorithm; +} +} + +namespace MantidQt +{ + +// Forward declarations +namespace MantidWidgets +{ + class FunctionBrowser; +} + +namespace API +{ + class AlgorithmRunner; +} + +namespace CustomInterfaces +{ + +// Forward declarations +class PlotController; +class DatasetPlotData; + +/** + * Class MultiDatasetFitDialog implements a dialog for setting up a multi-dataset fit + * and displaying the results. + */ + +class MultiDatasetFit: public API::UserSubWindow +{ + Q_OBJECT +public: + /// The name of the interface as registered into the factory + static std::string name() { return "Multi dataset fitting"; } + // This interface's categories. + static QString categoryInfo() { return "General"; } + /// Constructor + MultiDatasetFit(QWidget *parent = NULL); + ~MultiDatasetFit(); + /// Get the name of the output workspace + QString getOutputWorkspaceName() const {return QString::fromStdString(m_outputWorkspaceName);} + /// Workspace name for the i-th spectrum + std::string getWorkspaceName(int i) const; + /// Workspace index of the i-th spectrum + int getWorkspaceIndex(int i) const; + /// Total number of spectra (datasets). + int getNumberOfSpectra() const; + /// Get value of a local parameter + double getLocalParameterValue(const QString& parName, int i) const; + /// Display info about the plot. + void showPlotInfo(); + /// Check that the data sets in the table are valid + void checkDataSets(); + +signals: + void dataTableUpdated(); + +public slots: + void setLocalParameterValue(const QString& parName, int i, double value); + void reset(); + +private slots: + void addWorkspace(); + void workspaceSelectionChanged(); + void removeSelectedSpectra(); + void fit(); + void editLocalParameterValues(const QString& parName); + void finishFit(bool); + void updateLocalParameters(int index); + +protected: + /// To be overridden to set the appropriate layout + virtual void initLayout(); + +private: + void createPlotToolbar(); + void addWorkspaceSpectrum(const QString &wsName, int wsIndex); + boost::shared_ptr<Mantid::API::IFunction> createFunction() const; + void initLocalParameter(const QString& parName)const; + void updateParameters(const Mantid::API::IFunction& fun); + void showInfo(const QString& text); + bool eventFilter(QObject *widget, QEvent *evn); + void showFunctionBrowserInfo(); + void showTableInfo(); + void removeDataSets(std::vector<int>& rows); + + /// The form generated by Qt Designer + Ui::MultiDatasetFit m_uiForm; + /// Controls the plot and plotted data. + PlotController *m_plotController; + /// Function editor + MantidWidgets::FunctionBrowser *m_functionBrowser; + /// Name of the output workspace + std::string m_outputWorkspaceName; + /// Storage for local paramtere values + mutable QMap<QString,QVector<double>> m_localParameterValues; + /// Fit algorithm runner + boost::shared_ptr<API::AlgorithmRunner> m_fitRunner; +}; + +/*==========================================================================================*/ +/** + * A dialog for selecting a workspace from the ADS. + */ +class AddWorkspaceDialog: public QDialog +{ + Q_OBJECT +public: + AddWorkspaceDialog(QWidget *parent); + QString workspaceName() const {return m_workspaceName;} + std::vector<int> workspaceIndices() const {return m_wsIndices;} +private slots: + void accept(); + void reject(); + void workspaceNameChanged(const QString&); + void selectAllSpectra(int state); +private: + /// Name of the selected workspace + QString m_workspaceName; + /// Selected workspace index + std::vector<int> m_wsIndices; + /// Maximum index in the selected workspace + int m_maxIndex; + Ui::AddWorkspace m_uiForm; +}; + +/*==========================================================================================*/ +/** + * A class for controlling the plot widget and the displayed data. + */ +class PlotController: public QObject +{ + Q_OBJECT +public: + PlotController(MultiDatasetFit *parent, QwtPlot *plot, QTableWidget *table, QComboBox *plotSelector, QPushButton *prev, QPushButton *next); + ~PlotController(); + void clear(); + void update(); + int getCurrentIndex() const {return m_currentIndex;} + bool isZoomEnabled() const; + bool isPanEnabled() const; +signals: + void currentIndexChanged(int); +public slots: + void enableZoom(); + void enablePan(); +private slots: + void tableUpdated(); + void prevPlot(); + void nextPlot(); + void plotDataSet(int); +private: + MultiDatasetFit *owner() const {return static_cast<MultiDatasetFit*>(parent());} + /// The plot widget + QwtPlot *m_plot; + /// The zoomer + QwtPlotZoomer *m_zoomer; + /// The panner + QwtPlotPanner *m_panner; + /// The magnifier + QwtPlotMagnifier *m_magnifier; + + /// The workspace table + QTableWidget *m_table; + QComboBox *m_plotSelector; + QPushButton *m_prevPlot; + QPushButton *m_nextPlot; + QMap<int,boost::shared_ptr<DatasetPlotData>> m_plotData; + int m_currentIndex; +}; + +/*==========================================================================================*/ +/** + * A dialog for displaying and editing values of local parameters. + */ +class EditLocalParameterDialog: public QDialog +{ + Q_OBJECT +public: + EditLocalParameterDialog(MultiDatasetFit *parent, const QString &parName); +private slots: + //void accept(); + //void reject(); + void valueChanged(int,int); +private: + MultiDatasetFit *owner() const {return static_cast<MultiDatasetFit*>(parent());} + Ui::EditLocalParameterDialog m_uiForm; + QString m_parName; +}; + +} // CustomInterfaces +} // MantidQt + +#endif /*MULTIDATASETFITDIALOG_H_*/ diff --git a/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/MultiDatasetFit.ui b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/MultiDatasetFit.ui new file mode 100644 index 0000000000000000000000000000000000000000..c2b680fd31c25c646e454c2d30e1e7b77f37c6bc --- /dev/null +++ b/Code/Mantid/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/MultiDatasetFit.ui @@ -0,0 +1,236 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MultiDatasetFit</class> + <widget class="QMainWindow" name="MultiDatasetFit"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>895</width> + <height>604</height> + </rect> + </property> + <property name="windowTitle"> + <string>Multi-dataset fitting</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QVBoxLayout" name="verticalLayout_3" stretch="1,0"> + <item> + <widget class="QSplitter" name="hSplitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QWidget" name="verticalLayoutWidget_2"> + <layout class="QVBoxLayout" name="browserLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="btnFit"> + <property name="toolTip"> + <string>Start multi-dataset fitting</string> + </property> + <property name="text"> + <string>&Fit</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QSplitter" name="vSplitter"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <widget class="QWidget" name="layoutWidget"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QTableWidget" name="dataTable"> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <column> + <property name="text"> + <string>Workspace</string> + </property> + </column> + <column> + <property name="text"> + <string>WS Index</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="btnAddWorkspace"> + <property name="toolTip"> + <string>Add one or more spectra from a workspace</string> + </property> + <property name="text"> + <string>&Add Workspace</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnRemove"> + <property name="toolTip"> + <string>Remove selected spectra</string> + </property> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="btnPrev"> + <property name="toolTip"> + <string>Display previous data set</string> + </property> + <property name="text"> + <string><</string> + </property> + <property name="shortcut"> + <string>Alt+,</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cbPlotSelector"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>200</width> + <height>0</height> + </size> + </property> + <property name="toolTip"> + <string>Select a data set</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnNext"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="toolTip"> + <string>Display next data set</string> + </property> + <property name="text"> + <string>></string> + </property> + <property name="shortcut"> + <string>Alt+.</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QwtPlot" name="plot"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </widget> + </item> + <item> + <widget class="QLabel" name="infoBar"> + <property name="text"> + <string>infoBar</string> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <customwidgets> + <customwidget> + <class>QwtPlot</class> + <extends>QFrame</extends> + <header location="global">qwt_plot.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>btnAddWorkspace</tabstop> + <tabstop>dataTable</tabstop> + <tabstop>btnRemove</tabstop> + <tabstop>btnPrev</tabstop> + <tabstop>btnFit</tabstop> + <tabstop>btnNext</tabstop> + <tabstop>cbPlotSelector</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/Code/Mantid/MantidQt/CustomInterfaces/src/MultiDatasetFit.cpp b/Code/Mantid/MantidQt/CustomInterfaces/src/MultiDatasetFit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d9f2694392965fc4a44092c1a2f33868aa212aa7 --- /dev/null +++ b/Code/Mantid/MantidQt/CustomInterfaces/src/MultiDatasetFit.cpp @@ -0,0 +1,1124 @@ +#include "MantidQtCustomInterfaces/MultiDatasetFit.h" +#include "MantidQtMantidWidgets/FunctionBrowser.h" +#include "MantidQtAPI/AlgorithmRunner.h" + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/FunctionFactory.h" +#include "MantidAPI/MultiDomainFunction.h" +#include "MantidAPI/AlgorithmManager.h" +#include "MantidAPI/ParameterTie.h" +#include "MantidKernel/ArrayProperty.h" +#include "MantidKernel/ArrayBoundedValidator.h" + +#include "qtpropertybrowser.h" + +#include <QDialog> +#include <QHeaderView> +#include <QMessageBox> +#include <QToolBar> +#include <QActionGroup> + +#include <boost/make_shared.hpp> +#include <qwt_plot_curve.h> +#include <qwt_plot_zoomer.h> +#include <qwt_plot_panner.h> +#include <qwt_plot_magnifier.h> +#include <Poco/ActiveResult.h> + +#include <vector> +#include <algorithm> + +namespace{ + const int wsColumn = 0; + const int wsIndexColumn = 1; +} + +namespace MantidQt +{ +namespace CustomInterfaces +{ + +/*==========================================================================================*/ +/* AddWorkspaceDialog */ +/*==========================================================================================*/ +AddWorkspaceDialog::AddWorkspaceDialog(QWidget *parent):QDialog(parent) +{ + m_uiForm.setupUi(this); + // populate the combo box with names of eligible workspaces + QStringList workspaceNames; + auto wsNames = Mantid::API::AnalysisDataService::Instance().getObjectNames(); + for(auto name = wsNames.begin(); name != wsNames.end(); ++name) + { + auto ws = Mantid::API::AnalysisDataService::Instance().retrieveWS<Mantid::API::MatrixWorkspace>( *name ); + if ( ws ) + { + workspaceNames << QString::fromStdString( *name ); + } + } + connect(m_uiForm.cbWorkspaceName,SIGNAL(currentIndexChanged(const QString&)),this,SLOT(workspaceNameChanged(const QString&))); + m_uiForm.cbWorkspaceName->addItems( workspaceNames ); + + connect(m_uiForm.cbAllSpectra,SIGNAL(stateChanged(int)),this,SLOT(selectAllSpectra(int))); +} + +/** + * Slot. Reacts on change of workspace name in the selection combo box. + * @param wsName :: Name of newly selected workspace. + */ +void AddWorkspaceDialog::workspaceNameChanged(const QString& wsName) +{ + auto ws = Mantid::API::AnalysisDataService::Instance().retrieveWS<Mantid::API::MatrixWorkspace>( wsName.toStdString() ); + if ( ws ) + { + int maxValue = static_cast<int>(ws->getNumberHistograms()) - 1; + if ( maxValue < 0 ) maxValue = 0; + m_maxIndex = maxValue; + if ( m_uiForm.cbAllSpectra->isChecked() ) + { + m_uiForm.leWSIndices->setText(QString("0-%1").arg(m_maxIndex)); + } + else + { + m_uiForm.leWSIndices->clear(); + } + } + else + { + m_maxIndex = 0; + m_uiForm.leWSIndices->clear(); + m_uiForm.cbAllSpectra->setChecked(false); + } +} + +/** + * Slot. Called when "All Spectra" check box changes its state + */ +void AddWorkspaceDialog::selectAllSpectra(int state) +{ + if ( state == Qt::Checked ) + { + m_uiForm.leWSIndices->setText(QString("0-%1").arg(m_maxIndex)); + m_uiForm.leWSIndices->setEnabled(false); + } + else + { + m_uiForm.leWSIndices->setEnabled(true); + } + +} + +/** + * Called on close if selection accepted. + */ +void AddWorkspaceDialog::accept() +{ + m_workspaceName = m_uiForm.cbWorkspaceName->currentText(); + m_wsIndices.clear(); + QString indexInput = m_uiForm.leWSIndices->text(); + if ( !m_workspaceName.isEmpty() && !indexInput.isEmpty() ) + { + auto validator = boost::make_shared<Mantid::Kernel::ArrayBoundedValidator<int>>(0,m_maxIndex); + Mantid::Kernel::ArrayProperty<int> prop("Indices",validator); + std::string err = prop.setValue( indexInput.toStdString() ); + if ( err.empty() ) + { + m_wsIndices = prop; + } + else + { + QMessageBox::warning(this, "MantidPlot - Error", QString("Some of the indices are outside the allowed range [0,%1]").arg(m_maxIndex)); + } + } + QDialog::accept(); +} + +/** + * Called on close if selection rejected. + */ +void AddWorkspaceDialog::reject() +{ + m_workspaceName.clear(); + m_wsIndices.clear(); + QDialog::reject(); +} + +/*==========================================================================================*/ +/* DatasetPlotData */ +/*==========================================================================================*/ + +/** + * Contains graphics for a single data set: fitting data, claculated result, difference. + */ +class DatasetPlotData +{ +public: + DatasetPlotData(const QString& wsName, int wsIndex, const QString& outputWSName); + ~DatasetPlotData(); + void show(QwtPlot *plot); + void hide(); + QwtDoubleRect boundingRect() const; +private: + // no copying + DatasetPlotData(const DatasetPlotData&); + DatasetPlotData& operator=(const DatasetPlotData&); + void setData(const Mantid::API::MatrixWorkspace *ws, int wsIndex, const Mantid::API::MatrixWorkspace *outputWS); + QwtPlotCurve *m_dataCurve; + QwtPlotCurve *m_calcCurve; + QwtPlotCurve *m_diffCurve; +}; + +/** + * Constructor. + * @param wsName :: Name of a MatrixWorkspace with the data for fitting. + * @param wsIndex :: Workspace index of a spectrum in wsName to plot. + * @param outputWSName :: Name of the Fit's output workspace containing at least 3 spectra: + * #0 - original data (the same as in wsName[wsIndex]), #1 - calculated data, #3 - difference. + * If empty - ignore this workspace. + */ +DatasetPlotData::DatasetPlotData(const QString& wsName, int wsIndex, const QString& outputWSName): + m_dataCurve(new QwtPlotCurve(wsName + QString(" (%1)").arg(wsIndex))), + m_calcCurve(NULL), + m_diffCurve(NULL) +{ + // get the data workspace + auto ws = Mantid::API::AnalysisDataService::Instance().retrieveWS<Mantid::API::MatrixWorkspace>( wsName.toStdString() ); + if ( !ws ) + { + QString mess = QString("Workspace %1 either doesn't exist or isn't a MatrixWorkspace").arg(wsName); + throw std::runtime_error( mess.toStdString() ); + } + // check that the index is in range + if ( static_cast<size_t>(wsIndex) >= ws->getNumberHistograms() ) + { + QString mess = QString("Spectrum %1 doesn't exist in workspace %2").arg(wsIndex).arg(wsName); + throw std::runtime_error( mess.toStdString() ); + } + + // get the data workspace + Mantid::API::MatrixWorkspace_sptr outputWS; + if ( !outputWSName.isEmpty() ) + { + outputWS = Mantid::API::AnalysisDataService::Instance().retrieveWS<Mantid::API::MatrixWorkspace>( outputWSName.toStdString() ); + if ( !outputWS ) + { + QString mess = QString("Workspace %1 either doesn't exist or isn't a MatrixWorkspace").arg(outputWSName); + throw std::runtime_error( mess.toStdString() ); + } + } + + // create the curves + setData( ws.get(), wsIndex, outputWS.get() ); + +} + +/** + * Destructor. + */ +DatasetPlotData::~DatasetPlotData() +{ + m_dataCurve->detach(); + delete m_dataCurve; + if ( m_calcCurve ) + { + m_calcCurve->detach(); + delete m_calcCurve; + } + if ( m_diffCurve ) + { + m_diffCurve->detach(); + delete m_diffCurve; + } +} + +/** + * Set the data to the curves. + * @param ws :: A Fit's input workspace. + * @param wsIndex :: Workspace index of a spectrum to costruct the plot data for. + * @param outputWS :: The output workspace from Fit containing the calculated spectrum. + */ +void DatasetPlotData::setData(const Mantid::API::MatrixWorkspace *ws, int wsIndex, const Mantid::API::MatrixWorkspace *outputWS) +{ + std::vector<double> xValues = ws->readX(wsIndex); + if ( ws->isHistogramData() ) + { + auto xend = xValues.end() - 1; + for(auto x = xValues.begin(); x != xend; ++x) + { + *x = (*x + *(x+1))/2; + } + xValues.pop_back(); + } + m_dataCurve->setData( xValues.data(), ws->readY(wsIndex).data(), static_cast<int>(xValues.size()) ); + + if ( outputWS && outputWS->getNumberHistograms() >= 3 ) + { + m_calcCurve = new QwtPlotCurve("calc"); + m_calcCurve->setData( xValues.data(), outputWS->readY(1).data(), static_cast<int>(xValues.size()) ); + QPen penCalc("red"); + m_calcCurve->setPen(penCalc); + m_diffCurve = new QwtPlotCurve("diff"); + m_diffCurve->setData( xValues.data(), outputWS->readY(2).data(), static_cast<int>(xValues.size()) ); + QPen penDiff("green"); + m_diffCurve->setPen(penDiff); + } +} + +/** + * Show the curves on a plot. + */ +void DatasetPlotData::show(QwtPlot *plot) +{ + m_dataCurve->attach(plot); + if ( m_calcCurve ) + { + m_calcCurve->attach(plot); + } + if ( m_diffCurve ) + { + m_diffCurve->attach(plot); + } +} + +/** + * Hide the curves from any plot. + */ +void DatasetPlotData::hide() +{ + m_dataCurve->detach(); + if ( m_calcCurve ) + { + m_calcCurve->detach(); + } + if ( m_diffCurve ) + { + m_diffCurve->detach(); + } +} + +/** + * Get the bounding rect including all plotted data. + */ +QwtDoubleRect DatasetPlotData::boundingRect() const +{ + QwtDoubleRect rect = m_dataCurve->boundingRect(); + if ( m_calcCurve ) + { + rect = rect.united( m_calcCurve->boundingRect() ); + } + if ( m_diffCurve ) + { + rect = rect.united( m_diffCurve->boundingRect() ); + } + return rect; +} + +/*==========================================================================================*/ +/* PlotController */ +/*==========================================================================================*/ + +PlotController::PlotController(MultiDatasetFit *parent,QwtPlot *plot, QTableWidget *table, QComboBox *plotSelector, QPushButton *prev, QPushButton *next): + QObject(parent),m_plot(plot),m_table(table),m_plotSelector(plotSelector),m_prevPlot(prev),m_nextPlot(next),m_currentIndex(-1) +{ + connect(parent,SIGNAL(dataTableUpdated()),this,SLOT(tableUpdated())); + connect(prev,SIGNAL(clicked()),this,SLOT(prevPlot())); + connect(next,SIGNAL(clicked()),this,SLOT(nextPlot())); + connect(plotSelector,SIGNAL(currentIndexChanged(int)),this,SLOT(plotDataSet(int))); + + m_zoomer = new QwtPlotZoomer(QwtPlot::xBottom, QwtPlot::yLeft, + QwtPicker::DragSelection | QwtPicker::CornerToCorner, QwtPicker::AlwaysOff, plot->canvas()); + + m_panner = new QwtPlotPanner( plot->canvas() ); + m_panner->setEnabled(false); + + m_magnifier = new QwtPlotMagnifier( plot->canvas() ); + m_magnifier->setEnabled( false ); +} + +PlotController::~PlotController() +{ + m_plotData.clear(); +} + +/** + * Slot. Respond to changes in the data table. + */ +void PlotController::tableUpdated() +{ + m_plotSelector->blockSignals(true); + m_plotSelector->clear(); + int rowCount = m_table->rowCount(); + for(int row = 0; row < rowCount; ++row) + { + QString itemText = QString("%1 (%2)").arg(m_table->item(row,wsColumn)->text(),m_table->item(row,wsIndexColumn)->text()); + m_plotSelector->insertItem( itemText ); + } + m_plotData.clear(); + m_currentIndex = -1; + m_plotSelector->blockSignals(false); + plotDataSet( m_plotSelector->currentIndex() ); +} + +/** + * Display the previous plot if there is one. + */ +void PlotController::prevPlot() +{ + int index = m_plotSelector->currentIndex(); + if ( index > 0 ) + { + --index; + m_plotSelector->setCurrentIndex( index ); + } +} + +/** + * Display the next plot if there is one. + */ +void PlotController::nextPlot() +{ + int index = m_plotSelector->currentIndex(); + if ( index < m_plotSelector->count() - 1 ) + { + ++index; + m_plotSelector->setCurrentIndex( index ); + } +} + +/** + * Plot a data set. + * @param index :: Index (row) of the data set in the table. + */ +void PlotController::plotDataSet(int index) +{ + if ( index < 0 || index >= m_table->rowCount() ) + { + clear(); + owner()->checkDataSets(); + m_plot->replot(); + return; + } + + bool resetZoom = m_plotData.isEmpty(); + + // create data if index is displayed for the first time + if ( !m_plotData.contains(index) ) + { + QString wsName = m_table->item( index, wsColumn )->text(); + int wsIndex = m_table->item( index, wsIndexColumn )->text().toInt(); + QString outputWorkspaceName = owner()->getOutputWorkspaceName(); + if ( !outputWorkspaceName.isEmpty() ) + { + outputWorkspaceName += QString("_%1").arg(index); + } + try + { + auto value = boost::make_shared<DatasetPlotData>( wsName, wsIndex, outputWorkspaceName ); + m_plotData.insert(index, value ); + } + catch(...) + { + clear(); + owner()->checkDataSets(); + m_plot->replot(); + return; + } + } + + // hide the previously shown data + if ( m_currentIndex > -1 ) + { + m_plotData[m_currentIndex]->hide(); + } + + // try to keep the zooming from the previous view + // but if zoom rect doesn't show any data reset zoom base to show all + auto dataRect = m_plotData[index]->boundingRect(); + auto zoomRect = m_zoomer->zoomRect(); + if ( !zoomRect.intersects( dataRect ) ) + { + m_plot->setAxisAutoScale(QwtPlot::xBottom); + m_plot->setAxisAutoScale(QwtPlot::yLeft); + } + + // show the new data + m_plotData[index]->show( m_plot ); + m_plot->replot(); + // the idea is to set the zoom base (the largest view) to the data's bounding rect + // but it looks like the base is set to the union of dataRect and current zoomRect + m_zoomer->setZoomBase( dataRect ); + // if it's first data set ever set the zoomer's base + // if it's not done the base is set to some default rect that has nothing to do with the data + if ( resetZoom ) + { + m_zoomer->setZoomBase(true); + } + // change the current data set index + m_currentIndex = index; + emit currentIndexChanged( index ); +} + +void PlotController::clear() +{ + m_plotData.clear(); +} + +void PlotController::update() +{ + plotDataSet( m_currentIndex ); +} + +void PlotController::enableZoom() +{ + m_zoomer->setEnabled(true); + m_panner->setEnabled(false); + m_magnifier->setEnabled(false); + m_plot->canvas()->setCursor(QCursor(Qt::CrossCursor)); + owner()->showPlotInfo(); +} + +void PlotController::enablePan() +{ + m_zoomer->setEnabled(false); + m_panner->setEnabled(true); + m_magnifier->setEnabled(true); + m_plot->canvas()->setCursor(Qt::pointingHandCursor); + owner()->showPlotInfo(); +} + +bool PlotController::isZoomEnabled() const +{ + return m_zoomer->isEnabled(); +} + +bool PlotController::isPanEnabled() const +{ + return m_panner->isEnabled(); +} + +/*==========================================================================================*/ +/* EditLocalParameterDialog */ +/*==========================================================================================*/ + +EditLocalParameterDialog::EditLocalParameterDialog(MultiDatasetFit *parent, const QString &parName): + QDialog(parent),m_parName(parName) +{ + m_uiForm.setupUi(this); + QHeaderView *header = m_uiForm.tableWidget->horizontalHeader(); + header->setResizeMode(0,QHeaderView::Stretch); + header->setResizeMode(1,QHeaderView::Stretch); + connect(m_uiForm.tableWidget,SIGNAL(cellChanged(int,int)),this,SLOT(valueChanged(int,int))); + + auto multifit = owner(); + auto n = static_cast<int>( multifit->getNumberOfSpectra() ); + for(int i = 0; i < n; ++i) + { + m_uiForm.tableWidget->insertRow(i); + auto cell = new QTableWidgetItem( QString("f%1.").arg(i) + parName ); + m_uiForm.tableWidget->setItem( i, 0, cell ); + cell = new QTableWidgetItem( QString::number(multifit->getLocalParameterValue(parName,i)) ); + m_uiForm.tableWidget->setItem( i, 1, cell ); + } +} + +/** + * Slot. Called when a value changes. + * @param row :: Row index of the changed cell. + * @param col :: Column index of the changed cell. + */ +void EditLocalParameterDialog::valueChanged(int row, int col) +{ + if ( col == 1 ) + { + QString text = m_uiForm.tableWidget->item(row,col)->text(); + try + { + double value = text.toDouble(); + owner()->setLocalParameterValue(m_parName,row,value); + } + catch(std::exception&) + { + // restore old value + m_uiForm.tableWidget->item(row,col)->setText( QString::number(owner()->getLocalParameterValue(m_parName,row)) ); + } + } +} + +/*==========================================================================================*/ +/* MultiDatasetFit */ +/*==========================================================================================*/ + +//Register the class with the factory +DECLARE_SUBWINDOW(MultiDatasetFit); + +/** + * Constructor + * @param parent :: The parent widget + */ +MultiDatasetFit::MultiDatasetFit(QWidget *parent) +:UserSubWindow(parent) +{ +} + +MultiDatasetFit::~MultiDatasetFit() +{ + m_plotController->clear(); +} + +/** + * Initilize the layout. + */ +void MultiDatasetFit::initLayout() +{ + m_uiForm.setupUi(this); + m_uiForm.hSplitter->setStretchFactor(0,0); + m_uiForm.hSplitter->setStretchFactor(1,1); + m_uiForm.vSplitter->setStretchFactor(0,0); + m_uiForm.vSplitter->setStretchFactor(1,1); + + QHeaderView *header = m_uiForm.dataTable->horizontalHeader(); + header->setResizeMode(0,QHeaderView::Stretch); + header->setResizeMode(1,QHeaderView::Fixed); + + m_uiForm.btnRemove->setEnabled( false ); + + connect(m_uiForm.btnAddWorkspace,SIGNAL(clicked()),this,SLOT(addWorkspace())); + connect(m_uiForm.btnRemove,SIGNAL(clicked()),this,SLOT(removeSelectedSpectra())); + connect(m_uiForm.dataTable,SIGNAL(itemSelectionChanged()), this,SLOT(workspaceSelectionChanged())); + connect(m_uiForm.btnFit,SIGNAL(clicked()),this,SLOT(fit())); + connect(this,SIGNAL(dataTableUpdated()),this,SLOT(reset())); + + m_plotController = new PlotController(this, + m_uiForm.plot, + m_uiForm.dataTable, + m_uiForm.cbPlotSelector, + m_uiForm.btnPrev, + m_uiForm.btnNext); + connect(m_plotController,SIGNAL(currentIndexChanged(int)),this,SLOT(updateLocalParameters(int))); + + m_functionBrowser = new MantidQt::MantidWidgets::FunctionBrowser(NULL, true); + m_uiForm.browserLayout->addWidget( m_functionBrowser ); + connect(m_functionBrowser,SIGNAL(localParameterButtonClicked(const QString&)),this,SLOT(editLocalParameterValues(const QString&))); + connect(m_functionBrowser,SIGNAL(functionStructureChanged()),this,SLOT(reset())); + + createPlotToolbar(); + + // filters + m_functionBrowser->installEventFilter( this ); + m_uiForm.plot->installEventFilter( this ); + m_uiForm.dataTable->installEventFilter( this ); + + showInfo( "Add some data, define fitting function" ); +} + +void MultiDatasetFit::createPlotToolbar() +{ + auto toolBar = new QToolBar(this); + auto group = new QActionGroup(this); + + auto action = new QAction(this); + action->setIcon(QIcon(":/MultiDatasetFit/icons/zoom.png")); + action->setCheckable(true); + action->setChecked(true); + action->setToolTip("Zooming tool"); + connect(action,SIGNAL(triggered()),m_plotController,SLOT(enableZoom())); + group->addAction(action); + + action = new QAction(this); + action->setIcon(QIcon(":/MultiDatasetFit/icons/panning.png")); + action->setCheckable(true); + action->setToolTip("Panning tool"); + connect(action,SIGNAL(triggered()),m_plotController,SLOT(enablePan())); + group->addAction(action); + + toolBar->addActions(group->actions()); + + m_uiForm.horizontalLayout->insertWidget(3,toolBar); +} + +/** + * Show a dialog to select a workspace. + */ +void MultiDatasetFit::addWorkspace() +{ + AddWorkspaceDialog dialog(this); + if ( dialog.exec() == QDialog::Accepted ) + { + QString wsName = dialog.workspaceName().stripWhiteSpace(); + // if name is empty assume that there are no workspaces in the ADS + if ( wsName.isEmpty() ) return; + if ( Mantid::API::AnalysisDataService::Instance().doesExist( wsName.toStdString()) ) + { + auto indices = dialog.workspaceIndices(); + for(auto i = indices.begin(); i != indices.end(); ++i) + { + addWorkspaceSpectrum( wsName, *i ); + } + emit dataTableUpdated(); + } + else + { + QMessageBox::warning(this,"MantidPlot - Warning",QString("Workspace \"%1\" doesn't exist.").arg(wsName)); + } + } +} + +/** + * Add a spectrum from a workspace to the table. + * @param wsName :: Name of a workspace. + * @param wsIndex :: Index of a spectrum in the workspace (workspace index). + */ +void MultiDatasetFit::addWorkspaceSpectrum(const QString &wsName, int wsIndex) +{ + int row = m_uiForm.dataTable->rowCount(); + m_uiForm.dataTable->insertRow(row); + + auto cell = new QTableWidgetItem( wsName ); + m_uiForm.dataTable->setItem( row, wsColumn, cell ); + cell = new QTableWidgetItem( QString::number(wsIndex) ); + m_uiForm.dataTable->setItem( row, wsIndexColumn, cell ); +} + +/** + * Slot. Called when selection in the data table changes. + */ +void MultiDatasetFit::workspaceSelectionChanged() +{ + auto selection = m_uiForm.dataTable->selectionModel(); + bool enableRemoveButton = selection->hasSelection(); + if ( enableRemoveButton ) + { + enableRemoveButton = selection->selectedRows().size() > 0; + } + + m_uiForm.btnRemove->setEnabled( enableRemoveButton ); +} + +/** + * Slot. Called when "Remove" button is pressed. + */ +void MultiDatasetFit::removeSelectedSpectra() +{ + auto ranges = m_uiForm.dataTable->selectedRanges(); + if ( ranges.isEmpty() ) return; + std::vector<int> rows; + for(auto range = ranges.begin(); range != ranges.end(); ++range) + { + for(int row = range->topRow(); row <= range->bottomRow(); ++row) + { + rows.push_back( row ); + } + } + removeDataSets( rows ); +} + +/** + * Create a multi-domain function to fit all the spectra in the data table. + */ +boost::shared_ptr<Mantid::API::IFunction> MultiDatasetFit::createFunction() const +{ + // number of spectra to fit == size of the multi-domain function + size_t nOfDataSets = getNumberOfSpectra(); + if ( nOfDataSets == 0 ) + { + throw std::runtime_error("There are no data sets specified."); + } + + // description of a single function + QString funStr = m_functionBrowser->getFunctionString(); + + if ( nOfDataSets == 1 ) + { + return Mantid::API::FunctionFactory::Instance().createInitialized( funStr.toStdString() ); + } + + bool isComposite = (std::find(funStr.begin(),funStr.end(),';') != funStr.end()); + if ( isComposite ) + { + funStr = ";(" + funStr + ")"; + } + else + { + funStr = ";" + funStr; + } + + QString multiFunStr = "composite=MultiDomainFunction,NumDeriv=1"; + for(size_t i = 0; i < nOfDataSets; ++i) + { + multiFunStr += funStr; + } + + // add the global ties + QStringList globals = m_functionBrowser->getGlobalParameters(); + QString globalTies; + if ( !globals.isEmpty() ) + { + globalTies = "ties=("; + bool isFirst = true; + foreach(QString par, globals) + { + if ( !isFirst ) globalTies += ","; + else + isFirst = false; + + for(size_t i = 1; i < nOfDataSets; ++i) + { + globalTies += QString("f%1.").arg(i) + par + "="; + } + globalTies += QString("f0.%1").arg(par); + } + globalTies += ")"; + multiFunStr += ";" + globalTies; + } + + // create the multi-domain function + std::string tmpStr = multiFunStr.toStdString(); + auto fun = Mantid::API::FunctionFactory::Instance().createInitialized( tmpStr ); + boost::shared_ptr<Mantid::API::MultiDomainFunction> multiFun = boost::dynamic_pointer_cast<Mantid::API::MultiDomainFunction>( fun ); + if ( !multiFun ) + { + throw std::runtime_error("Failed to create the MultiDomainFunction"); + } + + auto globalParams = m_functionBrowser->getGlobalParameters(); + + // set the domain indices, initial local parameter values and ties + for(size_t i = 0; i < nOfDataSets; ++i) + { + multiFun->setDomainIndex(i,i); + auto fun1 = multiFun->getFunction(i); + for(size_t j = 0; j < fun1->nParams(); ++j) + { + auto tie = fun1->getTie(j); + if ( tie ) + { + // if a local parameter has a constant tie (is fixed) set tie's value to + // the value of the local parameter + if ( tie->isConstant() ) + { + QString parName = QString::fromStdString(fun1->parameterName(j)); + if ( !globalParams.contains(parName) ) + { + std::string expr = boost::lexical_cast<std::string>( getLocalParameterValue(parName,static_cast<int>(i)) ); + tie->set( expr ); + } + } + } + else + { + // if local parameter isn't tied set its local value + QString parName = QString::fromStdString(fun1->parameterName(j)); + if ( !globalParams.contains(parName) ) + { + fun1->setParameter(j, getLocalParameterValue(parName,static_cast<int>(i))); + } + } + } + } + assert( multiFun->nFunctions() == nOfDataSets ); + + return fun; +} + +/** + * Run the fitting algorithm. + */ +void MultiDatasetFit::fit() +{ + if ( !m_functionBrowser->hasFunction() ) + { + QMessageBox::warning( this, "MantidPlot - Warning","Function wasn't set." ); + return; + } + + try + { + auto fun = createFunction(); + auto fit = Mantid::API::AlgorithmManager::Instance().create("Fit"); + fit->initialize(); + fit->setProperty("Function", fun ); + fit->setPropertyValue("InputWorkspace", getWorkspaceName(0)); + fit->setProperty("WorkspaceIndex", getWorkspaceIndex(0)); + + m_outputWorkspaceName = "out"; + fit->setPropertyValue("Output",m_outputWorkspaceName); + m_outputWorkspaceName += "_Workspace"; + + int n = getNumberOfSpectra(); + for(int ispec = 1; ispec < n; ++ispec) + { + std::string suffix = boost::lexical_cast<std::string>(ispec); + fit->setPropertyValue( "InputWorkspace_" + suffix, getWorkspaceName(ispec) ); + fit->setProperty( "WorkspaceIndex_" + suffix, getWorkspaceIndex(ispec) ); + } + + m_fitRunner.reset( new API::AlgorithmRunner() ); + connect( m_fitRunner.get(),SIGNAL(algorithmComplete(bool)), this, SLOT(finishFit(bool)), Qt::QueuedConnection ); + + m_fitRunner->startAlgorithm(fit); + + } + catch(std::exception& e) + { + QString mess(e.what()); + const int maxSize = 500; + if ( mess.size() > maxSize ) + { + mess = mess.mid(0,maxSize); + mess += "..."; + } + QMessageBox::critical( this, "MantidPlot - Error", QString("Fit failed:\n\n %1").arg(mess) ); + } +} + +/** + * Get the workspace name of the i-th spectrum. + * @param i :: Index of a spectrum in the data table. + */ +std::string MultiDatasetFit::getWorkspaceName(int i) const +{ + return m_uiForm.dataTable->item(i, wsColumn)->text().toStdString(); +} + +/** + * Get the workspace index of the i-th spectrum. + * @param i :: Index of a spectrum in the data table. + */ +int MultiDatasetFit::getWorkspaceIndex(int i) const +{ + return m_uiForm.dataTable->item(i, wsIndexColumn)->text().toInt(); +} + +/** + * Get the number of spectra to fit to. + */ +int MultiDatasetFit::getNumberOfSpectra() const +{ + return m_uiForm.dataTable->rowCount(); +} + +/** + * Start an editor to display and edit individual local parameter values. + * @param parName :: Fully qualified name for a local parameter (Global unchecked). + */ +void MultiDatasetFit::editLocalParameterValues(const QString& parName) +{ + EditLocalParameterDialog dialog(this,parName); + dialog.exec(); +} + +/** + * Get value of a local parameter + * @param parName :: Name of a parameter. + * @param i :: Data set index. + */ +double MultiDatasetFit::getLocalParameterValue(const QString& parName, int i) const +{ + if ( !m_localParameterValues.contains(parName) || m_localParameterValues[parName].size() != getNumberOfSpectra() ) + { + initLocalParameter(parName); + } + return m_localParameterValues[parName][i]; +} + +void MultiDatasetFit::setLocalParameterValue(const QString& parName, int i, double value) +{ + if ( !m_localParameterValues.contains(parName) || m_localParameterValues[parName].size() != getNumberOfSpectra() ) + { + initLocalParameter(parName); + } + m_localParameterValues[parName][i] = value; +} + +/** + * Init a local parameter. Define initial values for all datasets. + * @param parName :: Name of parametere to init. + */ +void MultiDatasetFit::initLocalParameter(const QString& parName)const +{ + double value = m_functionBrowser->getParameter(parName); + QVector<double> values( static_cast<int>(getNumberOfSpectra()), value ); + m_localParameterValues[parName] = values; +} + +void MultiDatasetFit::reset() +{ + m_localParameterValues.clear(); +} + +void MultiDatasetFit::finishFit(bool) +{ + m_plotController->clear(); + m_plotController->update(); + Mantid::API::IFunction_sptr fun = m_fitRunner->getAlgorithm()->getProperty("Function"); + updateParameters( *fun ); +} + +/** + * Update the interface to have the sametparameter values as in a function. + */ +void MultiDatasetFit::updateParameters(const Mantid::API::IFunction& fun) +{ + m_localParameterValues.clear(); + auto cfun = dynamic_cast<const Mantid::API::CompositeFunction*>( &fun ); + if ( cfun && cfun->nFunctions() > 0 ) + { + auto qLocalParameters = m_functionBrowser->getLocalParameters(); + std::vector<std::string> localParameters; + foreach(QString par, qLocalParameters) + { + localParameters.push_back( par.toStdString() ); + } + size_t currentIndex = static_cast<size_t>( m_plotController->getCurrentIndex() ); + for(size_t i = 0; i < cfun->nFunctions(); ++i) + { + auto sfun = cfun->getFunction(i); + if ( i == currentIndex ) + { + m_functionBrowser->updateParameters( *sfun ); + } + for(int j = 0; j < qLocalParameters.size(); ++j) + { + setLocalParameterValue( qLocalParameters[j], static_cast<int>(i), sfun->getParameter(localParameters[j]) ); + } + } + } + else + { + m_functionBrowser->updateParameters( fun ); + } +} + +/** + * Update the local parameters in the function browser to show values corresponding + * to a particular dataset. + * @param index :: Index of a dataset. + */ +void MultiDatasetFit::updateLocalParameters(int index) +{ + auto localParameters = m_functionBrowser->getLocalParameters(); + foreach(QString par, localParameters) + { + m_functionBrowser->setParameter( par, getLocalParameterValue( par, index ) ); + } +} + +/** + * Show a message in the info bar at the bottom of the interface. + */ +void MultiDatasetFit::showInfo(const QString& text) +{ + m_uiForm.infoBar->setText(text); +} + +bool MultiDatasetFit::eventFilter(QObject *widget, QEvent *evn) +{ + if ( evn->type() == QEvent::Enter ) + { + if ( qobject_cast<QObject*>( m_functionBrowser ) == widget ) + { + showFunctionBrowserInfo(); + } + else if ( qobject_cast<QObject*>( m_uiForm.plot ) == widget ) + { + showPlotInfo(); + } + else if ( qobject_cast<QObject*>( m_uiForm.dataTable ) == widget ) + { + showTableInfo(); + } + else + { + showInfo(""); + } + } + return false; +} + +/** + * Show info about the function browser. + */ +void MultiDatasetFit::showFunctionBrowserInfo() +{ + if ( m_functionBrowser->hasFunction() ) + { + showInfo( "Use context menu to add more functions. Set parameters and attributes." ); + } + else + { + showInfo( "Use context menu to add a function." ); + } +} + +/** + * Show info about the plot. + */ +void MultiDatasetFit::showPlotInfo() +{ + QString text = "Use Alt+. and Alt+, to change the data set. "; + + if ( m_plotController->isZoomEnabled() ) + { + text += "Click and drag to zoom in. Use middle or right button to zoom out"; + } + else if ( m_plotController->isPanEnabled() ) + { + text += "Click and drag to move. Use mouse wheel to zoom in and out."; + } + + showInfo( text ); +} + +void MultiDatasetFit::showTableInfo() +{ + if ( getNumberOfSpectra() > 0 ) + { + showInfo("Select spectra by selecting rows. For multiple selection use Shift or Ctrl keys."); + } + else + { + showInfo("Add some data sets. Click \"Add Workspace\" button."); + } +} + +/** + * Check that the data sets in the table are valid and remove invalid ones. + */ +void MultiDatasetFit::checkDataSets() +{ + std::vector<int> rows; + int nrows = getNumberOfSpectra(); + auto& ADS = Mantid::API::AnalysisDataService::Instance(); + for( int row = 0; row < nrows; ++row) + { + auto wsName = getWorkspaceName( row ); + auto i = getWorkspaceIndex( row ); + if ( !ADS.doesExist( wsName ) ) + { + rows.push_back( row ); + continue; + } + auto ws = ADS.retrieveWS<Mantid::API::MatrixWorkspace>( wsName ); + if ( !ws || i >= static_cast<int>( ws->getNumberHistograms() ) ) + { + rows.push_back( row ); + continue; + } + } + + removeDataSets( rows ); +} + +void MultiDatasetFit::removeDataSets( std::vector<int>& rows ) +{ + if ( rows.empty() ) return; + std::sort( rows.begin(), rows.end() ); + for(auto row = rows.rbegin(); row != rows.rend(); ++row) + { + m_uiForm.dataTable->removeRow( *row ); + } + emit dataTableUpdated(); +} + +/*==========================================================================================*/ +} // CustomInterfaces +} // MantidQt diff --git a/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/FunctionBrowser.h b/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/FunctionBrowser.h index eb357a5945f2a93af8b3b754487c3e8bff314d73..1f2b9aba85fc93265981ab8a6cd13ccd17ae6d40 100644 --- a/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/FunctionBrowser.h +++ b/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/FunctionBrowser.h @@ -80,7 +80,7 @@ public: }; /// Constructor - FunctionBrowser(QWidget *parent = NULL); + FunctionBrowser(QWidget *parent = NULL, bool multi = false); /// Destructor ~FunctionBrowser(); /// Clear the contents @@ -93,6 +93,12 @@ public: QString getFunctionString(); /// Return the function Mantid::API::IFunction_sptr getFunction(QtProperty* prop = NULL, bool attributesOnly = false); + /// Check if a function is set + bool hasFunction() const; + /// Get a list of names of global parameters + QStringList getGlobalParameters() const; + /// Get a list of names of local parameters + QStringList getLocalParameters() const; /// Return a function with specified index Mantid::API::IFunction_sptr getFunctionByIndex(const QString& index); @@ -102,6 +108,14 @@ public: /// Update the function parameter value void setParameter(const QString& funcIndex, const QString& paramName, double value); + /// Get a value of a parameter + double getParameter(const QString& funcIndex, const QString& paramName) const; + /// Update the function parameter value + void setParameter(const QString& paramName, double value); + /// Get a value of a parameter + double getParameter(const QString& paramName) const; + /// Update parameter values in the browser to match those of a function. + void updateParameters(const Mantid::API::IFunction& fun); signals: /// User selects a different function (or one of it's sub-properties) @@ -112,6 +126,11 @@ signals: /// @param paramName :: Name of the changed parameter void parameterChanged(const QString& funcIndex, const QString& paramName); + /// In multi-dataset context a button value editor was clicked + void localParameterButtonClicked(const QString& parName); + + void functionStructureChanged(); + protected: /// Create the Qt property browser void createBrowser(); @@ -138,7 +157,7 @@ protected: /// Update function index properties void updateFunctionIndices(QtProperty* prop = NULL, QString index = ""); /// Get property of the overall function - AProperty getFunctionProperty(); + AProperty getFunctionProperty() const; /// Check if property is a function group bool isFunction(QtProperty* prop) const; /// Check if property is a function attribute @@ -162,7 +181,9 @@ protected: /// Get the function index for a property QString getIndex(QtProperty* prop) const; /// Get function property for the index - QtProperty* getFunctionProperty(const QString& index); + QtProperty* getFunctionProperty(const QString& index)const; + /// Split a qualified parameter name into function index and local parameter name. + QStringList splitParameterName(const QString& paramName) const; /// Add a tie property AProperty addTieProperty(QtProperty* prop, QString tie); @@ -222,6 +243,7 @@ protected slots: void attributeVectorDoubleChanged(QtProperty*); /// Called when a function parameter property is changed void parameterChanged(QtProperty*); + void parameterButtonClicked(QtProperty*); protected: /// Manager for function group properties @@ -295,11 +317,13 @@ protected: /// Index of currently selected function. Gets updated in updateCurrentFunctionIndex() boost::optional<QString> m_currentFunctionIndex; + /// Set true if the constructed function is intended to be used in a multi-dataset fit + bool m_multiDataset; + friend class CreateAttributePropertyForFunctionBrowser; friend class SetAttributeFromProperty; }; - } // MantidWidgets } // MantidQt diff --git a/Code/Mantid/MantidQt/MantidWidgets/src/FunctionBrowser.cpp b/Code/Mantid/MantidQt/MantidWidgets/src/FunctionBrowser.cpp index 68d07530a20272ceca8d812953f856e655bc870f..874e0423532f04af14b9dda86758be24108fb236 100644 --- a/Code/Mantid/MantidQt/MantidWidgets/src/FunctionBrowser.cpp +++ b/Code/Mantid/MantidQt/MantidWidgets/src/FunctionBrowser.cpp @@ -40,6 +40,8 @@ #endif #include "qteditorfactory.h" #include "DoubleEditorFactory.h" +#include "CompositeEditorFactory.h" +#include "ButtonEditorFactory.h" #if defined(__INTEL_COMPILER) #pragma warning enable 1125 #elif defined(__GNUC__) @@ -65,6 +67,9 @@ #include <algorithm> +namespace{ + const char * globalOptionName = "Global"; +} namespace MantidQt { @@ -74,9 +79,10 @@ namespace MantidWidgets /** * Constructor * @param parent :: The parent widget. + * @param multi :: Option to use the browser for multi-dataset fitting. */ -FunctionBrowser::FunctionBrowser(QWidget *parent) -:QWidget(parent) +FunctionBrowser::FunctionBrowser(QWidget *parent, bool multi) + :QWidget(parent),m_multiDataset(multi) { // create m_browser createBrowser(); @@ -101,6 +107,12 @@ FunctionBrowser::~FunctionBrowser() */ void FunctionBrowser::createBrowser() { + QStringList options; + if ( m_multiDataset ) + { + options << globalOptionName; + } + /* Create property managers: they create, own properties, get and set values */ m_functionManager = new QtGroupPropertyManager(this); m_parameterManager = new QtDoublePropertyManager(this); @@ -121,15 +133,30 @@ void FunctionBrowser::createBrowser() // create editor factories QtSpinBoxFactory *spinBoxFactory = new QtSpinBoxFactory(this); DoubleEditorFactory *doubleEditorFactory = new DoubleEditorFactory(this); + + QtAbstractEditorFactory<QtDoublePropertyManager> *parameterEditorFactory(NULL); + if ( m_multiDataset ) + { + auto buttonFactory = new DoubleButtonEditorFactory(this); + auto compositeFactory = new CompositeEditorFactory<QtDoublePropertyManager>(this,buttonFactory); + compositeFactory->setSecondaryFactory(globalOptionName, doubleEditorFactory); + parameterEditorFactory = compositeFactory; + connect(buttonFactory,SIGNAL(buttonClicked(QtProperty*)), this,SLOT(parameterButtonClicked(QtProperty*))); + } + else + { + parameterEditorFactory = doubleEditorFactory; + } + QtLineEditFactory *lineEditFactory = new QtLineEditFactory(this); QtCheckBoxFactory *checkBoxFactory = new QtCheckBoxFactory(this); FilenameDialogEditorFactory* filenameDialogEditorFactory = new FilenameDialogEditorFactory(this); FormulaDialogEditorFactory* formulaDialogEditFactory = new FormulaDialogEditorFactory(this); WorkspaceEditorFactory* workspaceEditorFactory = new WorkspaceEditorFactory(this); - m_browser = new QtTreePropertyBrowser(); + m_browser = new QtTreePropertyBrowser(NULL,options); // assign factories to property managers - m_browser->setFactoryForManager(m_parameterManager, doubleEditorFactory); + m_browser->setFactoryForManager(m_parameterManager, parameterEditorFactory); m_browser->setFactoryForManager(m_attributeStringManager, lineEditFactory); m_browser->setFactoryForManager(m_attributeDoubleManager, doubleEditorFactory); m_browser->setFactoryForManager(m_attributeIntManager, spinBoxFactory); @@ -238,6 +265,7 @@ void FunctionBrowser::setFunction(Mantid::API::IFunction_sptr fun) { clear(); addFunction(NULL,fun); + emit functionStructureChanged(); } /** @@ -378,7 +406,12 @@ FunctionBrowser::AProperty FunctionBrowser::addParameterProperty(QtProperty* par throw std::runtime_error("Unexpected error in FunctionBrowser [3]"); } QtProperty* prop = m_parameterManager->addProperty(paramName); + m_parameterManager->setDecimals(prop,6); m_parameterManager->setValue(prop,paramValue); + if ( m_multiDataset ) + { + prop->setOption(globalOptionName,false); + } return addProperty(parent,prop); } @@ -710,7 +743,7 @@ void FunctionBrowser::updateFunctionIndices(QtProperty* prop, QString index) /** * Get property of the overall function. */ -FunctionBrowser::AProperty FunctionBrowser::getFunctionProperty() +FunctionBrowser::AProperty FunctionBrowser::getFunctionProperty() const { auto props = m_browser->properties(); if ( props.isEmpty() ) @@ -725,6 +758,39 @@ FunctionBrowser::AProperty FunctionBrowser::getFunctionProperty() return m_properties[prop]; } +/** + * Get a list of names of global parameters + */ +QStringList FunctionBrowser::getGlobalParameters() const +{ + QStringList out; + for(auto propIt = m_properties.begin(); propIt != m_properties.end(); ++propIt) + { + QtProperty *prop = propIt->prop; + if ( prop->hasOption(globalOptionName) && prop->checkOption(globalOptionName) ) + { + out << getIndex(prop) + prop->propertyName(); + } + } + return out; +} + +/** + * Get a list of names of local parameters + */ +QStringList FunctionBrowser::getLocalParameters() const +{ + QStringList out; + for(auto propIt = m_properties.begin(); propIt != m_properties.end(); ++propIt) + { + QtProperty *prop = propIt->prop; + if ( prop->hasOption(globalOptionName) && !prop->checkOption(globalOptionName) ) + { + out << getIndex(prop) + prop->propertyName(); + } + } + return out; +} /** * Check if property is a function group @@ -736,7 +802,7 @@ bool FunctionBrowser::isFunction(QtProperty* prop) const } /** - * Check if property is a function attribute + * Check if property is any of the string attributes * @param prop :: Property to check */ bool FunctionBrowser::isStringAttribute(QtProperty* prop) const @@ -852,7 +918,7 @@ QString FunctionBrowser::getIndex(QtProperty* prop) const * @param index :: Function index to search, or empty string for top-level function * @return Function property, or NULL if not found */ -QtProperty* FunctionBrowser::getFunctionProperty(const QString& index) +QtProperty* FunctionBrowser::getFunctionProperty(const QString& index) const { // Might not be the most efficient way to do it. m_functionManager might be searched instead, // but it is not being kept up-to-date at the moment (is not cleared). @@ -1277,6 +1343,7 @@ void FunctionBrowser::addFunction() {// the browser is empty - add first function addFunction(NULL,f); } + emit functionStructureChanged(); } /** @@ -1444,6 +1511,84 @@ void FunctionBrowser::setParameter(const QString& funcIndex, const QString& para } } +/** + * Get a value of a parameter + * @param funcIndex :: Index of the function + * @param paramName :: Parameter name + */ +double FunctionBrowser::getParameter(const QString& funcIndex, const QString& paramName) const +{ + if (auto prop = getFunctionProperty(funcIndex)) + { + auto children = prop->subProperties(); + foreach(QtProperty* child, children) + { + if (isParameter(child) && child->propertyName() == paramName) + { + return m_parameterManager->value(child); + } + } + } + throw std::runtime_error("Unknown function parameter " + (funcIndex + paramName).toStdString()); +} + +/** + * Split a qualified parameter name into function index and local parameter name. + * @param paramName :: Fully qualified parameter name (includes function index) + * @return :: A string list with the first item is the function index and the second + * item is the param local name. + */ +QStringList FunctionBrowser::splitParameterName(const QString& paramName) const +{ + QString functionIndex; + QString parameterName = paramName; + int j = paramName.lastIndexOf('.'); + if ( j > 0 ) + { + ++j; + functionIndex = paramName.mid(0,j); + parameterName = paramName.mid(j); + } + QStringList res; + res << functionIndex << parameterName; + return res; +} + +/** + * Updates the function parameter value + * @param paramName :: Fully qualified parameter name (includes function index) + * @param value :: New value + */ +void FunctionBrowser::setParameter(const QString& paramName, double value) +{ + QStringList name = splitParameterName(paramName); + setParameter(name[0],name[1],value); +} + +/** + * Get a value of a parameter + * @param paramName :: Fully qualified parameter name (includes function index) + */ +double FunctionBrowser::getParameter(const QString& paramName) const +{ + QStringList name = splitParameterName(paramName); + return getParameter(name[0],name[1]); +} + +/** + * Update parameter values in the browser to match those of a function. + * @param fun :: A function to copy the values from. It must have the same + * type (composition) as the function in the browser. + */ +void FunctionBrowser::updateParameters(const Mantid::API::IFunction& fun) +{ + auto paramNames = fun.getParameterNames(); + for(auto par = paramNames.begin(); par != paramNames.end(); ++par) + { + setParameter( QString::fromStdString(*par), fun.getParameter(*par) ); + } +} + /** * Return FunctionFactory function string */ @@ -1465,6 +1610,7 @@ void FunctionBrowser::removeFunction() if (!isFunction(prop)) return; removeProperty(prop); updateFunctionIndices(); + emit functionStructureChanged(); } /** @@ -1673,6 +1819,15 @@ void FunctionBrowser::parameterChanged(QtProperty* prop) emit parameterChanged(getIndex(prop), prop->propertyName()); } +void FunctionBrowser::parameterButtonClicked(QtProperty *prop) +{ + emit localParameterButtonClicked(getIndex(prop) + prop->propertyName()); +} + +bool FunctionBrowser::hasFunction() const +{ + return ! m_functionManager->properties().isEmpty(); +} } // MantidWidgets } // MantidQt diff --git a/Code/Mantid/QtPropertyBrowser/CMakeLists.txt b/Code/Mantid/QtPropertyBrowser/CMakeLists.txt index 1602611929eff1e9c95c4154f96b2ee21d0d7c6a..7c29f5f3e662163dae3b50d1fda730ecd743bc92 100644 --- a/Code/Mantid/QtPropertyBrowser/CMakeLists.txt +++ b/Code/Mantid/QtPropertyBrowser/CMakeLists.txt @@ -40,7 +40,7 @@ qt4_generate_moc ( ${CMAKE_CURRENT_BINARY_DIR}/moc_qtpropertymanager.cpp ) qt4_generate_moc ( - src/qteditorfactory.h + src/qteditorfactory.h ${CMAKE_CURRENT_BINARY_DIR}/moc_qteditorfactory.cpp ) qt4_generate_moc ( @@ -103,6 +103,7 @@ set ( ) qt4_wrap_cpp ( EXTRA_MOCS src/DoubleEditorFactory.h + src/ButtonEditorFactory.h src/ParameterPropertyManager.h ) diff --git a/Code/Mantid/QtPropertyBrowser/src/ButtonEditorFactory.h b/Code/Mantid/QtPropertyBrowser/src/ButtonEditorFactory.h new file mode 100644 index 0000000000000000000000000000000000000000..5377cf692879688e245cffffb3c0e317619f4930 --- /dev/null +++ b/Code/Mantid/QtPropertyBrowser/src/ButtonEditorFactory.h @@ -0,0 +1,73 @@ +#ifndef BUTTONEDITORFACTORY_H +#define BUTTONEDITORFACTORY_H + +#include "qtpropertymanager.h" +#include <QPushButton> + +class QT_QTPROPERTYBROWSER_EXPORT ButtonEditor: public QPushButton +{ + Q_OBJECT +public: + ButtonEditor(QtProperty *property, QWidget *parent): + QPushButton("...",parent), m_property(property) + { + connect(this,SIGNAL(clicked()),this,SLOT(sendClickedSignal())); + } +Q_SIGNALS: + void buttonClicked(QtProperty *); +private Q_SLOTS: + void sendClickedSignal() + { + emit buttonClicked(m_property); + } + +private: + QtProperty* m_property; +}; + +template <class ManagerType> +class ButtonEditorFactory : public QtAbstractEditorFactory<ManagerType> +{ +// Q_OBJECT +public: + ButtonEditorFactory(QObject *parent) + : QtAbstractEditorFactory<ManagerType>(parent) + { + } + +protected: + void connectPropertyManager(ManagerType *manager) + { + (void) manager; // Unused + // Do nothing + } + + void disconnectPropertyManager(ManagerType *manager) + { + (void) manager; // Unused + // Do nothing + } + + QWidget *createEditor(ManagerType *manager, QtProperty *property, QWidget *parent) + { + (void) manager; // Unused + auto button = new ButtonEditor(property, parent); + this->connect(button,SIGNAL(buttonClicked(QtProperty *)),this,SIGNAL(buttonClicked(QtProperty *))); + return button; + } +}; + +class QT_QTPROPERTYBROWSER_EXPORT DoubleButtonEditorFactory: public ButtonEditorFactory<QtDoublePropertyManager> +{ + Q_OBJECT + +public: + DoubleButtonEditorFactory(QObject *parent):ButtonEditorFactory<QtDoublePropertyManager>(parent){} + +Q_SIGNALS: + void buttonClicked(QtProperty *); + +}; + + +#endif // BUTTONEDITORFACTORY_H diff --git a/Code/Mantid/QtPropertyBrowser/src/CompositeEditorFactory.h b/Code/Mantid/QtPropertyBrowser/src/CompositeEditorFactory.h new file mode 100644 index 0000000000000000000000000000000000000000..236cefcc8eb965c6805119e439ed05c846e64d22 --- /dev/null +++ b/Code/Mantid/QtPropertyBrowser/src/CompositeEditorFactory.h @@ -0,0 +1,66 @@ +#ifndef COMPOSITEEDITORFACTORY_H +#define COMPOSITEEDITORFACTORY_H + +#include "qtpropertybrowser.h" +#include <QPushButton> + +/** + * Composite factory for a particular property manger type. + * Client must specify a secondary factory for properties with a particular + * option set. If this option is set crates editor using the scondary factory. + * Creates the default editor (with the factory passed to the constructor) + * if the option isn't set or property doesn't have this option. + * @param ManagerType :: Manager class to use + */ +template <class ManagerType> +class CompositeEditorFactory : public QtAbstractEditorFactory<ManagerType> +{ + typedef QtAbstractEditorFactory<ManagerType> FactoryBaseType; +public: + CompositeEditorFactory(QObject *parent, FactoryBaseType *defaultFactory) + : QtAbstractEditorFactory<ManagerType>(parent), + m_defaultFactory(defaultFactory), + m_secondaryFactory(NULL) + { + } + + void setSecondaryFactory( const QString &optionName, FactoryBaseType *factory ) + { + m_optionName = optionName; + m_secondaryFactory = factory; + } + +protected: + void connectPropertyManager(ManagerType *manager) + { + (void) manager; // Unused + // Do nothing + } + + void disconnectPropertyManager(ManagerType *manager) + { + (void) manager; // Unused + // Do nothing + } + + QWidget *createEditor(ManagerType *manager, QtProperty *property, QWidget *parent) + { + if ( !m_secondaryFactory ) + { + throw std::logic_error("Secondary editor factory isn't set."); + } + + if ( property->hasOption(m_optionName) && property->checkOption(m_optionName) ) + { + return m_secondaryFactory->createEditor( manager, property, parent ); + } + + return m_defaultFactory->createEditor( manager, property, parent ); + } +private: + FactoryBaseType *m_defaultFactory; + FactoryBaseType *m_secondaryFactory; + QString m_optionName; +}; + +#endif // COMPOSITEEDITORFACTORY_H diff --git a/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.cpp b/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.cpp index f38d0230ae103673212edec018aff471e2346b40..32964e20387c3b6d4f914704eddca3fcbfdc1cc4 100644 --- a/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.cpp +++ b/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.cpp @@ -106,6 +106,7 @@ public: QSet<QtProperty *> m_parentItems; QList<QtProperty *> m_subItems; + QMap<QString,bool> m_options; QString m_toolTip; QString m_statusTip; @@ -527,6 +528,22 @@ void QtProperty::removeSubProperty(QtProperty *property) } } +bool QtProperty::hasOption(const QString &opt) const +{ + return d_ptr->m_options.contains(opt); +} + +bool QtProperty::checkOption(const QString &opt) const +{ + if ( !d_ptr->m_options.contains(opt) ) return false; + return d_ptr->m_options[opt]; +} + +void QtProperty::setOption(const QString &opt, bool on) +{ + d_ptr->m_options[opt] = on; +} + /** \internal */ diff --git a/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.h b/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.h index 544f7765f19b125e3b6b8c62067ceea48fe6de2e..756389241b184b653683172d9e082e0f4bf9b0de 100644 --- a/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.h +++ b/Code/Mantid/QtPropertyBrowser/src/qtpropertybrowser.h @@ -145,6 +145,10 @@ public: void addSubProperty(QtProperty *property); void insertSubProperty(QtProperty *property, QtProperty *afterProperty); void removeSubProperty(QtProperty *property); + + bool hasOption(const QString &opt) const; + bool checkOption(const QString &opt) const; + void setOption(const QString &opt, bool on); protected: explicit QtProperty(QtAbstractPropertyManager *manager); void propertyChanged(); @@ -204,6 +208,10 @@ protected Q_SLOTS: friend class QtAbstractPropertyBrowser; }; +template <class PropertyManager> +class CompositeEditorFactory; + + template <class PropertyManager> class QtAbstractEditorFactory : public QtAbstractEditorFactoryBase { @@ -255,6 +263,7 @@ public: return 0; } protected: + friend class CompositeEditorFactory<PropertyManager>; virtual void connectPropertyManager(PropertyManager *manager) = 0; virtual QWidget *createEditor(PropertyManager *manager, QtProperty *property, QWidget *parent) = 0; diff --git a/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.cpp b/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.cpp index c53fb787eb348d661a47ac392675d7f44da189f7..500b031701796236b458cbf9172f875a55cd78b5 100644 --- a/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.cpp +++ b/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.cpp @@ -97,6 +97,8 @@ #include <QtGui/QFocusEvent> #include <QtGui/QStyle> #include <QtGui/QPalette> +#include <QtGui/qcheckbox.h> +#include <QtGui/qlineedit.h> #include <iostream> @@ -104,6 +106,43 @@ QT_BEGIN_NAMESPACE #endif +class PropertyOptionCheckBox: public QWidget +{ + Q_OBJECT +public: + PropertyOptionCheckBox(QWidget *parent,QtProperty *property, const QString &optionName): + QWidget(parent), + m_property(property), + m_optionName(optionName), + m_checked(property->checkOption(optionName)) + { + setFocusPolicy(Qt::StrongFocus); + } + void paintEvent (QPaintEvent*) + { + QStyleOptionButton opt; + auto state = isChecked() ? QStyle::State_On : QStyle::State_Off; + opt.state |= state; + opt.rect = rect(); + opt.rect.setWidth(opt.rect.height()); + QPainter painter(this); + QApplication::style()->drawPrimitive(QStyle::PE_IndicatorCheckBox,&opt,&painter); + } + void mousePressEvent (QMouseEvent* event) + { + event->accept(); + setChecked( ! isChecked() ); + m_property->setOption( m_optionName, isChecked() ); + update(); + } + void setChecked(bool on){m_checked = on;} + bool isChecked() const {return m_checked;} +private: + QtProperty *m_property; + QString m_optionName; + bool m_checked; +}; + class QtPropertyEditorView; class QtTreePropertyBrowserPrivate @@ -113,7 +152,7 @@ class QtTreePropertyBrowserPrivate public: QtTreePropertyBrowserPrivate(); - void init(QWidget *parent); + void init(QWidget *parent, const QStringList &options); void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex); void propertyRemoved(QtBrowserItem *index); @@ -145,6 +184,8 @@ public: QTreeWidgetItem *editedItem() const; + const QStringList& options() const {return m_options;} + private: void updateItem(QTreeWidgetItem *item); @@ -161,6 +202,7 @@ private: bool m_markPropertiesWithoutValue; bool m_browserChangedBlocked; QIcon m_expandIcon; + QStringList m_options; // options that can be associated with QtProperties }; // ------------ QtPropertyEditorView @@ -255,6 +297,7 @@ void QtPropertyEditorView::mousePressEvent(QMouseEvent *event) { QTreeWidget::mousePressEvent(event); QTreeWidgetItem *item = itemAt(event->pos()); + auto index = currentIndex(); if (item) { if ((item != m_editorPrivate->editedItem()) && (event->button() == Qt::LeftButton) @@ -265,6 +308,10 @@ void QtPropertyEditorView::mousePressEvent(QMouseEvent *event) if (event->pos().x() + header()->offset() < 20) item->setExpanded(!item->isExpanded()); } + else if (index.column() == 2) + { + editItem(item,2); + } } } @@ -374,6 +421,20 @@ QWidget *QtPropertyEditorDelegate::createEditor(QWidget *parent, return editor; } } + if (index.column() > 1 && m_editorPrivate) { + QtProperty *property = m_editorPrivate->indexToProperty(index); + int optionIndex = index.column() - 2; + if ( optionIndex >= m_editorPrivate->options().size() ) + { + return NULL; + } + QString optionName = m_editorPrivate->options()[optionIndex]; + if ( property->hasOption(optionName) ) + { + QWidget *editor = new PropertyOptionCheckBox(parent,property,optionName); + return editor; + } + } return 0; } @@ -424,6 +485,25 @@ void QtPropertyEditorDelegate::paint(QPainter *painter, const QStyleOptionViewIt painter->drawLine(right, option.rect.y(), right, option.rect.bottom()); } painter->restore(); + if ( index.column() > 1 ) + { + QtProperty *property = m_editorPrivate->indexToProperty(index); + int optionIndex = index.column() - 2; + if ( optionIndex >= m_editorPrivate->options().size() ) + { + return; + } + QString optionName = m_editorPrivate->options()[optionIndex]; + if ( property->hasOption(optionName) ) + { + QStyleOptionButton opt; + auto state = property->checkOption(optionName) ? QStyle::State_On : QStyle::State_Off; + opt.state |= state; + opt.rect = option.rect; + opt.rect.setWidth(opt.rect.height()); + QApplication::style()->drawPrimitive(QStyle::PE_IndicatorCheckBox,&opt,painter); + } + } } QSize QtPropertyEditorDelegate::sizeHint(const QStyleOptionViewItem &option, @@ -483,7 +563,7 @@ static QIcon drawIndicatorIcon(const QPalette &palette, QStyle *style) return rc; } -void QtTreePropertyBrowserPrivate::init(QWidget *parent) +void QtTreePropertyBrowserPrivate::init(QWidget *parent, const QStringList &options) { QHBoxLayout *layout = new QHBoxLayout(parent); layout->setMargin(0); @@ -492,10 +572,17 @@ void QtTreePropertyBrowserPrivate::init(QWidget *parent) m_treeWidget->setIconSize(QSize(18, 18)); layout->addWidget(m_treeWidget); - m_treeWidget->setColumnCount(2); + m_options = options; + const int columnCount = 2 + m_options.size(); + m_treeWidget->setColumnCount( columnCount ); QStringList labels; labels.append(QApplication::translate("QtTreePropertyBrowser", "Property", 0, QApplication::UnicodeUTF8)); labels.append(QApplication::translate("QtTreePropertyBrowser", "Value", 0, QApplication::UnicodeUTF8)); + // add optional columns + for(auto opt = m_options.begin(); opt != m_options.end(); ++opt) + { + labels.append(QApplication::translate("QtTreePropertyBrowser", *opt, 0, QApplication::UnicodeUTF8)); + } m_treeWidget->setHeaderLabels(labels); m_treeWidget->setAlternatingRowColors(true); m_treeWidget->setEditTriggers(QAbstractItemView::EditKeyPressed); @@ -790,13 +877,13 @@ void QtTreePropertyBrowserPrivate::editItem(QtBrowserItem *browserItem) /** Creates a property browser with the given \a parent. */ -QtTreePropertyBrowser::QtTreePropertyBrowser(QWidget *parent) +QtTreePropertyBrowser::QtTreePropertyBrowser(QWidget *parent, const QStringList &options) : QtAbstractPropertyBrowser(parent) { d_ptr = new QtTreePropertyBrowserPrivate; d_ptr->q_ptr = this; - d_ptr->init(this); + d_ptr->init(this,options); connect(this, SIGNAL(currentItemChanged(QtBrowserItem*)), this, SLOT(slotCurrentBrowserItemChanged(QtBrowserItem*))); } diff --git a/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.h b/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.h index f664609d79e3ca4a25ae844b06708f6ef50da2d7..eedded423ddd08f40be4e32637bf8950e95b3ebc 100644 --- a/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.h +++ b/Code/Mantid/QtPropertyBrowser/src/qttreepropertybrowser.h @@ -118,7 +118,7 @@ public: ResizeToContents }; - QtTreePropertyBrowser(QWidget *parent = 0); + QtTreePropertyBrowser(QWidget *parent = 0, const QStringList &options = QStringList()); ~QtTreePropertyBrowser(); int indentation() const;