Commit 183d41c8 authored by Roman Tolchenov's avatar Roman Tolchenov
Browse files

Implemented saving of user defined functions. re #971

parent 64a873c5
......@@ -4,6 +4,7 @@
#include <sstream>
#include <iostream>
#include <locale>
namespace Mantid
{
......@@ -541,7 +542,11 @@ std::set<std::string> Expression::getVariables()const
std::set<std::string> out;
if ( !isFunct() )
{
out.insert(name());
std::string s = name();
if ( !s.empty() && !isdigit(s[0]) )
{
out.insert(s);
}
}
else
{
......@@ -554,7 +559,11 @@ std::set<std::string> Expression::getVariables()const
}
else
{
out.insert(e->name());
std::string s = e->name();
if ( !s.empty() && !isdigit(s[0]) )
{
out.insert(s);
}
}
}
}
......
......@@ -5,7 +5,6 @@
#include "WidgetDllOption.h"
#include <vector>
#include <set>
#include <string>
namespace MantidQt
......@@ -24,7 +23,7 @@ namespace MantidQt
RenameParDialog(const std::vector<std::string>& old_params,
const std::vector<std::string>& new_params,
QWidget *parent=NULL);
void setOutput(std::set<std::string>& out)const;
void setOutput(std::vector<std::string>& out)const;
protected slots:
void uniqueIndexedNames(bool);
void doNotRename(bool);
......
......@@ -9,6 +9,7 @@
class QComboBox;
class QLineEdit;
class QTextEdit;
namespace MantidQt
{
......@@ -23,6 +24,7 @@ namespace MantidQt
public:
UserFunctionDialog(QWidget *parent=NULL,const QString& formula = "");
~UserFunctionDialog();
QStringList categories()const;
QString getFormula()const{return m_uiForm.teUserFunction->toPlainText();}
public slots:
......@@ -30,15 +32,21 @@ namespace MantidQt
void selectFunction(const QString& fun);
void addExpression();
void saveFunction();
void removeCurrentFunction();
void updateCategories();
void updateFunction();
protected:
/// User interface elements
Ui::UserFunctionDialog m_uiForm;
void loadFunctions();
void checkParameters(QString& expr);
void updateFunction();
QSet<QString> names(const QString& key = "")const;
bool eventFilter(QObject *obj, QEvent *ev);
QString getFunction(const QString& cat,const QString& fun)const;
QString getComment(const QString& cat,const QString& fun)const;
void setFunction(const QString& cat,const QString& fun,const QString& expr,const QString& comment = "");
bool isBuiltin(const QString& cat)const;
private:
/// Container for prerecorded functions: key = category.name, value = formula
......@@ -55,10 +63,11 @@ namespace MantidQt
Q_OBJECT
public:
InputFunctionNameDialog(QWidget *parent,const QString& category);
void getFunctionName(QString& category,QString& name);
void getFunctionName(QString& category,QString& name,QString& comment);
private:
QComboBox* m_category;
QLineEdit* m_name;
QTextEdit* m_comment;
};
}
......
......@@ -122,6 +122,16 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnRemove" >
<property name="text" >
<string>Remove</string>
</property>
<property name="autoDefault" >
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnUse" >
<property name="text" >
......
......@@ -95,13 +95,13 @@ QString RenameParDialog::makeUniqueIndexedName(const QString& name)
* Output the new names to a vector
* @param out Reference to a vector for output
*/
void RenameParDialog::setOutput(std::set<std::string>& out)const
void RenameParDialog::setOutput(std::vector<std::string>& out)const
{
out.clear();
QAbstractItemModel* model = m_uiForm.tableWidget->model();
for(int row=0;row< m_uiForm.tableWidget->rowCount(); ++row)
{
out.insert(model->data(model->index(row,1)).toString().toStdString());
out.push_back(model->data(model->index(row,1)).toString().toStdString());
}
}
......
#include "MantidQtMantidWidgets/UserFunctionDialog.h"
#include "MantidQtMantidWidgets/RenameParDialog.h"
#include "MantidAPI/Expression.h"
#include "MantidKernel/ConfigService.h"
#include <QComboBox>
#include <QStringListModel>
#include <QDialogButtonBox>
#include <QMessageBox>
#include <QKeyEvent>
#include <QFile>
#include <QTextStream>
#include <algorithm>
......@@ -18,9 +22,13 @@ UserFunctionDialog::UserFunctionDialog(QWidget *parent,const QString& formula)
connect(m_uiForm.lstCategory,SIGNAL(currentTextChanged(const QString&)),this,SLOT(selectCategory(const QString&)));
connect(m_uiForm.lstFunction,SIGNAL(currentTextChanged(const QString&)),this,SLOT(selectFunction(const QString&)));
connect(m_uiForm.btnSave,SIGNAL(clicked()),this,SLOT(saveFunction()));
connect(m_uiForm.btnRemove,SIGNAL(clicked()),this,SLOT(removeCurrentFunction()));
connect(m_uiForm.btnAdd,SIGNAL(clicked()),this,SLOT(addExpression()));
connect(m_uiForm.btnUse,SIGNAL(clicked()),this,SLOT(accept()));
connect(m_uiForm.btnCancel,SIGNAL(clicked()),this,SLOT(reject()));
connect(m_uiForm.teUserFunction,SIGNAL(textChanged()),this,SLOT(updateFunction()));
m_uiForm.teUserFunction->installEventFilter(this);
loadFunctions();
updateCategories();
if ( !formula.isEmpty() )
......@@ -28,7 +36,28 @@ UserFunctionDialog::UserFunctionDialog(QWidget *parent,const QString& formula)
QRect rect = m_uiForm.teUserFunction->cursorRect();
QTextCursor cursor = m_uiForm.teUserFunction->cursorForPosition(rect.topLeft());
cursor.insertText(formula);
updateFunction();
//updateFunction();
}
}
/**
* Write saved functions in the destructor
*/
UserFunctionDialog::~UserFunctionDialog()
{
QFile funFile(QString::fromStdString (Mantid::Kernel::ConfigService::Instance().getBaseDir()) + "Mantid.user.functions");
if (funFile.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMap<QString,QString>::const_iterator it = m_funs.begin();
for(; it != m_funs.end(); ++it)
{
QTextStream out(&funFile);
QStringList cn = it.key().split('.');
if (cn[0] != "Base" && cn[0] != "Built-in")
{
out << it.key() << "=" << it.value() <<'\n';
}
}
}
}
......@@ -38,13 +67,41 @@ UserFunctionDialog::UserFunctionDialog(QWidget *parent,const QString& formula)
*/
void UserFunctionDialog::loadFunctions()
{
// define the build-in functions
m_funs.insert("Base.abs","abs(x)");
m_funs.insert("Base.sin","sin(x)");
m_funs.insert("Base.cos","cos(x)");
//m_funs.insert("Base.","");
m_funs.insert("Built-in.Gauss","h*exp(-s*(x-c)^2)");
m_funs.insert("Built-in.ExpDecay","h*exp(-x/t)");
// define the built-in functions
setFunction ("Base","abs","abs(x)","Absolute value of x");
setFunction ("Base","sin","sin(x)","Sine of x");
setFunction ("Base","cos","cos(x)","Cosine of x");
setFunction ("Base","tan","tan(x)","Tangent of x");
setFunction ("Base","asin","asin(x)","Arc-sine of x");
setFunction ("Base","acos","acos(x)","Arc-cosine of x");
setFunction ("Base","atan","atan(x)","Arc-tangent of x");
setFunction ("Base","sinh","sinh(x)","Sine hyperbolic of x");
setFunction ("Base","cosh","cosh(x)","Cosine hyperbolic of x");
setFunction ("Base","tanh","tanh(x)","Tangent hyperbolic of x");
setFunction ("Base","asinh","asinh(x)","Arc-sine hyperbolic of x");
setFunction ("Base","acosh","acosh(x)","Arc-cosine hyperbolic of x");
setFunction ("Base","atanh","atanh(x)","Arc-tangent hyperbolic of x");
setFunction ("Base","log2","log2(x)","Logarithm to the base 2");
setFunction ("Base","log10","log10(x)","Logarithm to the base 10");
setFunction ("Base","log","log(x)","Logarithm to the base 10");
setFunction ("Base","ln","ln(x)","Logarithm to the base e = 2.71828...");
setFunction ("Base","exp","exp(x)","e to the power of x");
setFunction ("Base","sqrt","sqrt(x)","Sqare root of x");
setFunction ("Base","sign","sign(x)","Sign of x");
setFunction ("Base","rint","rint(x)","Round to nearest integer");
setFunction ("Built-in","Gauss","h*exp(-s*(x-c)^2)");
setFunction ("Built-in","ExpDecay","h*exp(-x/t)");
QFile funFile(QString::fromStdString (Mantid::Kernel::ConfigService::Instance().getBaseDir()) + "Mantid.user.functions");
if (funFile.exists() && funFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream in(&funFile);
while (!in.atEnd()) {
QString line = in.readLine();
QStringList key_value = line.split('=');
if (key_value.size() != 2) continue;
m_funs.insert (key_value[0].trimmed(),key_value[1].trimmed());
}
}
}
/**
......@@ -52,6 +109,7 @@ void UserFunctionDialog::loadFunctions()
*/
void UserFunctionDialog::updateCategories()
{
m_uiForm.lstCategory->clear();
QSet<QString> cats = names();
foreach(QString cat ,cats)
{
......@@ -65,21 +123,26 @@ void UserFunctionDialog::updateCategories()
*/
void UserFunctionDialog::selectCategory(const QString& cat)
{
//std::string category_key = "userfunctions."+cat.toStdString();
m_uiForm.lstFunction->clear();
QSet<QString> funs = names(cat);
m_uiForm.lstFunction->clear();
foreach(QString fun ,funs)
{
QString key = cat + "." + fun;
QString value = m_funs[key];
QString value = getFunction(cat,fun);
if ( !value.isEmpty() )
{
m_uiForm.lstFunction->addItem(fun);
}
}
m_uiForm.lstFunction->setCurrentRow(0);
if (m_uiForm.lstFunction->count() > 0)
{
m_uiForm.lstFunction->sortItems();
m_uiForm.lstFunction->setCurrentRow(0);
}
else
{
m_uiForm.teExpression->clear();
}
m_uiForm.btnRemove->setEnabled (!isBuiltin(cat));
}
/**
......@@ -88,13 +151,15 @@ void UserFunctionDialog::selectCategory(const QString& cat)
*/
void UserFunctionDialog::selectFunction(const QString& fun)
{
if (fun.isEmpty())
{
return;
}
QString cat = m_uiForm.lstCategory->currentItem()->text();
m_uiForm.teExpression->clear();
QString fun_key = cat + "." + fun;
QString value = m_funs[fun_key];
QString comment = m_funs[fun_key + ".comment"];
QString value = getFunction(cat,fun);
QString comment = getComment(cat,fun);
if ( !comment.isEmpty() )
{
value += "\n\n" + comment;
......@@ -117,6 +182,8 @@ void UserFunctionDialog::addExpression()
checkParameters(expr);
if (expr.isEmpty()) return;
QRect rect = m_uiForm.teUserFunction->cursorRect();
QTextCursor cursor = m_uiForm.teUserFunction->cursorForPosition(rect.topLeft());
if (cursor.position() > 0)
......@@ -125,7 +192,7 @@ void UserFunctionDialog::addExpression()
}
cursor.insertText(expr);
updateFunction();
//updateFunction();
}
/**
......@@ -140,9 +207,16 @@ void UserFunctionDialog::checkParameters(QString& expr)
// collect parameter names in sets vars1 and vars2
Mantid::API::Expression e1;
e1.parse(fun.toStdString());
Mantid::API::Expression e2;
try
{
e1.parse(fun.toStdString());
e2.parse(expr.toStdString());
}
catch(...)
{
return;
}
std::set<std::string> vars1 = e1.getVariables();
std::set<std::string> vars2 = e2.getVariables();
vars1.erase("x");
......@@ -160,6 +234,11 @@ void UserFunctionDialog::checkParameters(QString& expr)
// compare variable names and collect common names
std::vector<std::string> common(std::min(vars1.size(),vars2.size()),"");
std::set_intersection(vars1.begin(),vars1.end(),vars2.begin(),vars2.end(),common.begin());
it = std::find(common.begin(),common.end(),"");
if (it != common.end())
{
common.erase(it,common.end());
}
// ask the user to rename the common names
if ( !common.empty() )
......@@ -167,15 +246,19 @@ void UserFunctionDialog::checkParameters(QString& expr)
RenameParDialog dlg(all,common);
if (dlg.exec() == QDialog::Accepted)
{
std::set<std::string> vars2_new;
dlg.setOutput(vars2_new);
std::set<std::string>::const_iterator v2_old = vars2.begin();
std::set<std::string>::const_iterator v2_new = vars2_new.begin();
for(; v2_old != vars2.end(); ++v2_old,++v2_new)
std::vector<std::string> vars_new;
dlg.setOutput(vars_new);
std::vector<std::string>::const_iterator v_old = common.begin();
std::vector<std::string>::const_iterator v_new = vars_new.begin();
for(; v_old != common.end(); ++v_old,++v_new)
{
e2.renameAll(*v2_old,*v2_new);
e2.renameAll(*v_old,*v_new);
expr = QString::fromStdString(e2.str());
}
expr = QString::fromStdString(e2.str());
}
else
{
expr = "";
}
}
......@@ -188,7 +271,14 @@ void UserFunctionDialog::updateFunction()
{
QString fun = m_uiForm.teUserFunction->toPlainText();
Mantid::API::Expression e;
e.parse(fun.toStdString());
try
{
e.parse(fun.toStdString());
}
catch(...)
{// the formula could be being edited manually
return;
}
std::set<std::string> vars = e.getVariables();
vars.erase("x");
QString params;
......@@ -251,34 +341,63 @@ void UserFunctionDialog::saveFunction()
InputFunctionNameDialog* dlg = new InputFunctionNameDialog(this,cur_category);
if (dlg->exec() == QDialog::Accepted)
{
QString category;
QString name;
dlg->getFunctionName(category,name);
if (name.isEmpty())
QString cat;
QString fun;
QString comment;
dlg->getFunctionName(cat,fun,comment);
if (fun.isEmpty())
{
QMessageBox::critical(this,"Mantid - Error","The function name is empty");
return;
}
// check if the category already exists
QList<QListWidgetItem*> items = m_uiForm.lstCategory->findItems(category,Qt::MatchExactly);
QList<QListWidgetItem*> items = m_uiForm.lstCategory->findItems(cat,Qt::MatchExactly);
if ( !items.isEmpty() )
{// check if a function with this name already exists
const QSet<QString> functions = names(category);
QSet<QString>::const_iterator found = functions.find(name);
const QSet<QString> functions = names(cat);
QSet<QString>::const_iterator found = functions.find(fun);
if (found != functions.end() &&
QMessageBox::question(this,"Mantid","A function with name "+name+" already exists in category "+category+".\n"
QMessageBox::question(this,"Mantid","A function with name "+fun+" already exists in category "+cat+".\n"
"Would you like to replace it?",QMessageBox::Yes|QMessageBox::No) == QMessageBox::No)
{
return;
}
}
QString fun = m_uiForm.teUserFunction->toPlainText();
QString fun_key = category+"."+name;
m_funs[fun_key] = fun;
QString expr = m_uiForm.teUserFunction->toPlainText();
setFunction(cat,fun,expr,comment);
updateCategories();
}//QDialog::Accepted
}
/**
* Remove the current function
*/
void UserFunctionDialog::removeCurrentFunction()
{
QString cat = m_uiForm.lstCategory->currentItem()->text();
if (isBuiltin(cat))
{
return;
}
QString fun = m_uiForm.lstFunction->currentItem()->text();
if (QMessageBox::question(this,"Mantid","Are you sure you want to remove function " + fun + "?"
,QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes)
{
QString fun_key = cat + "." + fun;
QMap<QString,QString>::iterator it = m_funs.find(fun_key);
if (it != m_funs.end())
{
m_funs.erase(it);
it = m_funs.find(fun_key + ".comment");
if (it != m_funs.end())
{
m_funs.erase(it);
}
}
}
selectCategory(cat);
}
QStringList UserFunctionDialog::categories()const
{
QStringList out;
......@@ -289,6 +408,86 @@ QStringList UserFunctionDialog::categories()const
return out;
}
bool UserFunctionDialog::eventFilter(QObject *obj, QEvent *ev)
{
if (ev->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
if ( keyEvent->key() == Qt::Key_Return)
{
return true;
}
}
// standard event processing
return QObject::eventFilter(obj, ev);
}
/**
* Get the expression for saved function in category cat with name fun. If any of the
* arguments are empty string or function does not exist return empty string.
* @param cat The category
* @param fun The name of the function
* @return An expression that can be used as mu::Parser formula
*/
QString UserFunctionDialog::getFunction(const QString& cat,const QString& fun)const
{
if (cat.isEmpty() || fun.isEmpty()) return "";
QMap<QString,QString>::const_iterator it = m_funs.find(cat + "." + fun);
if (it != m_funs.end()) return it.value();
return "";
}
/**
* Get the comment for saved function in category cat with name fun. If any of the
* arguments are empty string or function does not exist return empty string.
* @param cat The category
* @param fun The name of the function
*/
QString UserFunctionDialog::getComment(const QString& cat,const QString& fun)const
{
if (cat.isEmpty() || fun.isEmpty()) return "";
QMap<QString,QString>::const_iterator it = m_funs.find(cat + "." + fun + ".comment");
if (it != m_funs.end()) return it.value();
return "";
}
/**
* Set an expression to a new function in category cat and with name fun. If any of the
* arguments are empty string does nothing.
* @param cat The category
* @param fun The name of the function
* @param expr The expression
* @param comment The comment
*/
void UserFunctionDialog::setFunction(const QString& cat,const QString& fun,const QString& expr,const QString& comment)
{
if (cat.isEmpty() || fun.isEmpty() || expr.isEmpty()) return;
//if (cat == "Base" || cat == "Built-in") return;
QString fun_key = cat + "." + fun;
m_funs[fun_key] = expr;
QString cmnt_key = fun_key + ".comment";
if (!comment.isEmpty())
{
m_funs[cmnt_key] = comment;
}
else
{
QMap<QString,QString>::iterator it = m_funs.find(cmnt_key);
if (it != m_funs.end())
{
m_funs.erase(it);
}
}
}
/**
* Checks if a category is a buil-in one and cannot be changed
*/
bool UserFunctionDialog::isBuiltin(const QString& cat)const
{
return cat == "Base" || cat == "Built-in";
}
/**
* Constructor
* @param category The initial suggestion for the category
......@@ -298,8 +497,11 @@ InputFunctionNameDialog::InputFunctionNameDialog(QWidget *parent,const QString&
{
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(new QLabel("Enter new or select a category"));
QStringList cats = ((UserFunctionDialog*)parent)->categories();
cats.removeOne("Base");
cats.removeOne("Built-in");
m_category = new QComboBox();
m_category->addItems(((UserFunctionDialog*)parent)->categories());
m_category->addItems(cats);
m_category->setEditable(true);
int index = m_category->findText(category);
if (index >= 0)
......@@ -307,9 +509,14 @@ InputFunctionNameDialog::InputFunctionNameDialog(QWidget *parent,const QString&
m_category->setCurrentIndex(index);
}
layout->addWidget(m_category);
connect(m_category,SIGNAL(currentIndexChanged(const QString&)),parent,SLOT(selectCategory(const QString&)));
layout->addWidget(new QLabel("Enter a name for the new function"));
m_name = new QLineEdit();
layout->addWidget(m_name);
layout->addWidget(new QLabel("Enter a comment"));
m_comment = new QTextEdit();
layout->addWidget(m_comment);
QDialogButtonBox* buttons = new QDialogButtonBox();
buttons->addButton("OK",QDialogButtonBox::AcceptRole);
buttons->addButton("Cancel",QDialogButtonBox::RejectRole);
......@@ -321,12 +528,14 @@ InputFunctionNameDialog::InputFunctionNameDialog(QWidget *parent,const QString&
}
/**
* Return the entered category and function name
* Return the entered category and function name and comment
* @param category A string to recieve the category
* @param name A string to recieve the function name
*/
void InputFunctionNameDialog::getFunctionName(QString& category,QString& name)
void InputFunctionNameDialog::getFunctionName(QStrin