Newer
Older
#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>
using namespace MantidQt::MantidWidgets;
UserFunctionDialog::UserFunctionDialog(QWidget *parent,const QString& formula)
:QDialog(parent)
{
m_uiForm.setupUi(this);
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() )
{
QRect rect = m_uiForm.teUserFunction->cursorRect();
QTextCursor cursor = m_uiForm.teUserFunction->cursorForPosition(rect.topLeft());
cursor.insertText(formula);
//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';
}
}
}
}
/**
* Load saved functions form Mantid(.user).properties file.
* Property: userfunctions.CategoryName.FunctionName = Expression-in-Mu::Parser-format
*/
void UserFunctionDialog::loadFunctions()
{
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// 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());
}
}
}
/**
* Update the GUI element displaying categories.
*/
void UserFunctionDialog::updateCategories()
{
m_uiForm.lstCategory->clear();
QSet<QString> cats = names();
foreach(QString cat ,cats)
{
m_uiForm.lstCategory->addItem(cat);
}
}
/**
* Make a category current
* @param cat The category to select
*/
void UserFunctionDialog::selectCategory(const QString& cat)
{
QSet<QString> funs = names(cat);
m_uiForm.lstFunction->clear();
foreach(QString fun ,funs)
{
QString value = getFunction(cat,fun);
if ( !value.isEmpty() )
{
m_uiForm.lstFunction->addItem(fun);
}
}
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));
}
/**
* Make a function current
* @param fun The function to select
*/
void UserFunctionDialog::selectFunction(const QString& fun)
{
if (fun.isEmpty())
{
return;
}
QString cat = m_uiForm.lstCategory->currentItem()->text();
m_uiForm.teExpression->clear();
QString value = getFunction(cat,fun);
QString comment = getComment(cat,fun);
if ( !comment.isEmpty() )
{
value += "\n\n" + comment;
}
m_uiForm.teExpression->setText(value);
}
/**
* Add selected expression to the user function
*/
void UserFunctionDialog::addExpression()
{
QString expr = m_uiForm.teExpression->toPlainText();
int iBr = expr.indexOf('\n');
if (iBr > 0)
{
expr.remove(iBr,expr.size());
}
checkParameters(expr);
if (expr.isEmpty()) return;
QRect rect = m_uiForm.teUserFunction->cursorRect();
QTextCursor cursor = m_uiForm.teUserFunction->cursorForPosition(rect.topLeft());
if (cursor.position() > 0)
{
expr.prepend('+');
}
cursor.insertText(expr);
//updateFunction();
}
/**
* Check an expression for name clashes with user function
* @param expr An expression prepared to be added to the user function.
*/
void UserFunctionDialog::checkParameters(QString& expr)
{
if (expr.isEmpty()) return;
QString fun = m_uiForm.teUserFunction->toPlainText();
if (fun.isEmpty()) return;
// collect parameter names in sets vars1 and vars2
Mantid::API::Expression e1;
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");
vars2.erase("x");
// combine all names frm the two sets
std::vector<std::string> all(vars1.size()+vars2.size(),"");
std::set_union(vars1.begin(),vars1.end(),vars2.begin(),vars2.end(),all.begin());
std::vector<std::string>::iterator it = std::find(all.begin(),all.end(),"");
if (it != all.end())
{
all.erase(it,all.end());
}
// compare variable names and collect common names
Michael Whitty
committed
std::vector<std::string> common(std::min<int>(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() )
{
RenameParDialog dlg(all,common);
if (dlg.exec() == QDialog::Accepted)
{
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(*v_old,*v_new);
expr = QString::fromStdString(e2.str());
}
else
{
expr = "";
}
}
}
/**
* Updates the parameter list
*/
void UserFunctionDialog::updateFunction()
{
QString fun = m_uiForm.teUserFunction->toPlainText();
Mantid::API::Expression e;
try
{
e.parse(fun.toStdString());
}
catch(...)
{// the formula could be being edited manually
return;
}
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
std::set<std::string> vars = e.getVariables();
vars.erase("x");
QString params;
for(std::set<std::string>::iterator it=vars.begin();it!=vars.end();++it)
{
if (it != vars.begin())
{
params += ",";
}
params += QString::fromStdString(*it);
}
m_uiForm.leParams->setText(params);
}
/**
* Returns function names: If the input cat parameter is empty the returned set
* contains funtion categories, otherwise it returns function names in category cat.
* @param cat The category for which functions will be returned.
* @return A set of funtion names.
*/
QSet<QString> UserFunctionDialog::names(const QString& cat)const
{
QSet<QString> out;
if (cat.isEmpty())
{
QMap<QString,QString>::const_iterator it = m_funs.begin();
for(; it != m_funs.end(); ++it)
{
QStringList cn = it.key().split('.');
out.insert(cn[0]);
}
}
else
{
QMap<QString,QString>::const_iterator it = m_funs.begin();
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
for(; it != m_funs.end(); ++it)
{
QStringList cn = it.key().split('.');
if (cn[0] == cat)
{
out.insert(cn[1]);
}
}
}
return out;
}
/**
* Save the constructed function for future use
*/
void UserFunctionDialog::saveFunction()
{
QString cur_category = m_uiForm.lstCategory->currentItem()->text();
if (cur_category == "Base" || cur_category == "Built-in")
{
cur_category = "";
}
InputFunctionNameDialog* dlg = new InputFunctionNameDialog(this,cur_category);
if (dlg->exec() == QDialog::Accepted)
{
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(cat,Qt::MatchExactly);
if ( !items.isEmpty() )
{// check if a function with this name already exists
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 "+fun+" already exists in category "+cat+".\n"
"Would you like to replace it?",QMessageBox::Yes|QMessageBox::No) == QMessageBox::No)
{
return;
}
}
QString expr = m_uiForm.teUserFunction->toPlainText();
setFunction(cat,fun,expr,comment);
updateCategories();
}//QDialog::Accepted
}
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/**
* 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;
for(int i = 0; i < m_uiForm.lstCategory->count(); ++i)
{
out << m_uiForm.lstCategory->item(i)->text();
}
return out;
}
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
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
*/
InputFunctionNameDialog::InputFunctionNameDialog(QWidget *parent,const QString& category)
:QDialog(parent)
{
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(cats);
m_category->setEditable(true);
int index = m_category->findText(category);
if (index >= 0)
{
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);
buttons->setCenterButtons(true);
connect(buttons,SIGNAL(accepted()),this,SLOT(accept()));
connect(buttons,SIGNAL(rejected()),this,SLOT(reject()));
layout->addWidget(buttons);
setLayout(layout);
}
/**
* 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,QString& comment)
{
category = m_category->currentText();
name = m_name->text();
comment = m_comment->toPlainText();