Mantid
Loading...
Searching...
No Matches
UserFunctionDialog.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
12
13#include <QComboBox>
14#include <QDialogButtonBox>
15#include <QFile>
16#include <QKeyEvent>
17#include <QMessageBox>
18#include <QStringListModel>
19#include <QTextStream>
20#include <QUrl>
21
22#include <algorithm>
23
24using namespace MantidQt::MantidWidgets;
26
27UserFunctionDialog::UserFunctionDialog(QWidget *parent, const QString &formula) : QDialog(parent) {
28 m_uiForm.setupUi(this);
29 connect(m_uiForm.lstCategory, SIGNAL(currentTextChanged(const QString &)), this,
30 SLOT(selectCategory(const QString &)));
31 connect(m_uiForm.lstFunction, SIGNAL(currentTextChanged(const QString &)), this,
32 SLOT(selectFunction(const QString &)));
33 connect(m_uiForm.btnSave, SIGNAL(clicked()), this, SLOT(saveFunction()));
34 connect(m_uiForm.btnRemove, SIGNAL(clicked()), this, SLOT(removeCurrentFunction()));
35 connect(m_uiForm.btnAdd, SIGNAL(clicked()), this, SLOT(addExpression()));
36 connect(m_uiForm.btnUse, SIGNAL(clicked()), this, SLOT(accept()));
37 connect(m_uiForm.btnCancel, SIGNAL(clicked()), this, SLOT(reject()));
38 connect(m_uiForm.btnHelp, SIGNAL(clicked()), this, SLOT(helpClicked()));
39 connect(m_uiForm.teUserFunction, SIGNAL(textChanged()), this, SLOT(updateFunction()));
40 m_uiForm.teUserFunction->installEventFilter(this);
41
44 if (!formula.isEmpty()) {
45 QRect rect = m_uiForm.teUserFunction->cursorRect();
46 QTextCursor cursor = m_uiForm.teUserFunction->cursorForPosition(rect.topLeft());
47 cursor.insertText(formula);
48 // updateFunction();
49 }
50}
51
56
63 // define the built-in functions
64 setFunction("Base", "abs", "abs(x)", "Absolute value of x");
65 setFunction("Base", "sin", "sin(x)", "Sine of x");
66 setFunction("Base", "cos", "cos(x)", "Cosine of x");
67 setFunction("Base", "tan", "tan(x)", "Tangent of x");
68 setFunction("Base", "asin", "asin(x)", "Arc-sine of x");
69 setFunction("Base", "acos", "acos(x)", "Arc-cosine of x");
70 setFunction("Base", "atan", "atan(x)", "Arc-tangent of x");
71 setFunction("Base", "sinh", "sinh(x)", "Sine hyperbolic of x");
72 setFunction("Base", "cosh", "cosh(x)", "Cosine hyperbolic of x");
73 setFunction("Base", "tanh", "tanh(x)", "Tangent hyperbolic of x");
74 setFunction("Base", "asinh", "asinh(x)", "Arc-sine hyperbolic of x");
75 setFunction("Base", "acosh", "acosh(x)", "Arc-cosine hyperbolic of x");
76 setFunction("Base", "atanh", "atanh(x)", "Arc-tangent hyperbolic of x");
77 setFunction("Base", "log2", "log2(x)", "Logarithm to the base 2");
78 setFunction("Base", "log10", "log10(x)", "Logarithm to the base 10");
79 setFunction("Base", "log", "log(x)", "Logarithm to the base 10");
80 setFunction("Base", "ln", "ln(x)", "Logarithm to the base e = 2.71828...");
81 setFunction("Base", "exp", "exp(x)", "e to the power of x");
82 setFunction("Base", "sqrt", "sqrt(x)", "Square root of x");
83 setFunction("Base", "sign", "sign(x)", "Sign of x");
84 setFunction("Base", "rint", "rint(x)", "Round to nearest integer");
85 setFunction("Base", "erf", "erf(x)", "error function of x");
86 setFunction("Base", "erfc", "erfc(x)", "Complementary error function erfc(x) = 1 - erf(x)");
87 setFunction("Built-in", "Gauss", "h*exp(-s*(x-c)^2)");
88 setFunction("Built-in", "ExpDecay", "h*exp(-x/t)");
89 QFile funFile(QString::fromStdString(Mantid::Kernel::ConfigService::Instance().getUserPropertiesDir()) +
90 "Mantid.user.functions");
91 if (funFile.exists() && funFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
92 QTextStream in(&funFile);
93 while (!in.atEnd()) {
94 QString line = in.readLine();
95 QStringList key_value = line.split('=');
96 if (key_value.size() != 2)
97 continue;
98 m_funs.insert(key_value[0].trimmed(), key_value[1].trimmed());
99 }
100 }
101}
102
107 // store the name of the current item
108 QString currentCategory = getCurrentCategory();
109 m_uiForm.lstCategory->clear();
110 QSet<QString> cats = categoryNames();
111 foreach (QString cat, cats) {
112 m_uiForm.lstCategory->addItem(cat);
113 }
114 // try to restore current item selection
115 auto items = m_uiForm.lstCategory->findItems(currentCategory, Qt::MatchExactly);
116 if (!items.isEmpty()) {
117 m_uiForm.lstCategory->setCurrentItem(items[0]);
118 }
119}
120
125void UserFunctionDialog::selectCategory(const QString &cat) {
126 QSet<QString> funs = functionNames(cat);
127 m_uiForm.lstFunction->clear();
128 foreach (QString fun, funs) {
129 QString value = getFunction(cat, fun);
130 if (!value.isEmpty()) {
131 m_uiForm.lstFunction->addItem(fun);
132 }
133 }
134 if (m_uiForm.lstFunction->count() > 0) {
135 m_uiForm.lstFunction->sortItems();
136 m_uiForm.lstFunction->setCurrentRow(0);
137 } else {
138 m_uiForm.teExpression->clear();
139 }
140 m_uiForm.btnRemove->setEnabled(!isBuiltin(cat));
141}
142
147void UserFunctionDialog::selectFunction(const QString &fun) {
148 if (fun.isEmpty()) {
149 return;
150 }
151 QString cat = m_uiForm.lstCategory->currentItem()->text();
152 m_uiForm.teExpression->clear();
153
154 QString value = getFunction(cat, fun);
155 QString comment = getComment(cat, fun);
156 if (!comment.isEmpty()) {
157 value += "\n\n" + comment;
158 }
159
160 m_uiForm.teExpression->setText(value);
161}
162
167 QString expr = m_uiForm.teExpression->toPlainText();
168 int iBr = expr.indexOf('\n');
169 if (iBr > 0) {
170 expr.remove(iBr, expr.size());
171 }
172
173 checkParameters(expr);
174
175 if (expr.isEmpty())
176 return;
177
178 QRect rect = m_uiForm.teUserFunction->cursorRect();
179 QTextCursor cursor = m_uiForm.teUserFunction->cursorForPosition(rect.topLeft());
180 if (cursor.position() > 0) {
181 expr.prepend('+');
182 }
183 cursor.insertText(expr);
184
185 // updateFunction();
186}
187
193 if (expr.isEmpty())
194 return;
195 QString fun = m_uiForm.teUserFunction->toPlainText();
196 if (fun.isEmpty())
197 return;
198
199 // collect parameter names in sets vars1 and vars2
202 try {
203 e1.parse(fun.toStdString());
204 e2.parse(expr.toStdString());
205 } catch (...) {
206 return;
207 }
208 auto vars1 = e1.getVariables();
209 auto vars2 = e2.getVariables();
210 vars1.erase("x");
211 vars2.erase("x");
212
213 // combine all names frm the two sets
214 std::vector<std::string> all(vars1.size() + vars2.size(), "");
215 std::set_union(vars1.begin(), vars1.end(), vars2.begin(), vars2.end(), all.begin());
216 std::vector<std::string>::iterator it = std::find(all.begin(), all.end(), "");
217 if (it != all.end()) {
218 all.erase(it, all.end());
219 }
220
221 // compare variable names and collect common names
222 std::vector<std::string> common(std::min<size_t>(vars1.size(), vars2.size()), "");
223 std::set_intersection(vars1.begin(), vars1.end(), vars2.begin(), vars2.end(), common.begin());
224 it = std::find(common.begin(), common.end(), "");
225 if (it != common.end()) {
226 common.erase(it, common.end());
227 }
228
229 // ask the user to rename the common names
230 if (!common.empty()) {
231 RenameParDialog dlg(all, common);
232 if (dlg.exec() == QDialog::Accepted) {
233 auto vars_new = dlg.setOutput();
234 std::vector<std::string>::const_iterator v_old = common.begin();
235 std::vector<std::string>::const_iterator v_new = vars_new.begin();
236 for (; v_old != common.end(); ++v_old, ++v_new) {
237 e2.renameAll(*v_old, *v_new);
238 expr = QString::fromStdString(e2.str());
239 }
240 } else {
241 expr = "";
242 }
243 }
244}
245
250 QString fun = m_uiForm.teUserFunction->toPlainText();
252 try {
253 e.parse(fun.toStdString());
254 } catch (...) { // the formula could be being edited manually
255 m_uiForm.leParams->setText("");
256 return;
257 }
258 auto vars = e.getVariables();
259 vars.erase("x");
260 QString params;
261 for (auto it = vars.begin(); it != vars.end(); ++it) {
262 if (it != vars.begin()) {
263 params += ",";
264 }
265 params += QString::fromStdString(*it);
266 }
267 m_uiForm.leParams->setText(params);
268}
269
274 QSet<QString> out;
275 QMap<QString, QString>::const_iterator it = m_funs.begin();
276 for (; it != m_funs.end(); ++it) {
277 QStringList cn = it.key().split('.');
278 out.insert(cn[0]);
279 }
280 return out;
281}
282
288QSet<QString> UserFunctionDialog::functionNames(const QString &cat) const {
289 QSet<QString> out;
290 QMap<QString, QString>::const_iterator it = m_funs.begin();
291 for (; it != m_funs.end(); ++it) {
292 QStringList cn = it.key().split('.');
293 if (cn[0] == cat) {
294 out.insert(cn[1]);
295 }
296 }
297 return out;
298}
299
306 QString cur_category;
307 QListWidgetItem const *currentCategoryItem = m_uiForm.lstCategory->currentItem();
308 if (currentCategoryItem) {
309 cur_category = m_uiForm.lstCategory->currentItem()->text();
310 }
311 return cur_category;
312}
313
318 // select one of user-defined categories
319 QString cur_category = getCurrentCategory();
320
321 if (cur_category == "Base" || cur_category == "Built-in") {
322 cur_category = "";
323 }
324
325 auto *dlg = new InputFunctionNameDialog(this, cur_category);
326 if (dlg->exec() == QDialog::Accepted) {
327 QString cat;
328 QString fun;
329 QString comment;
330 dlg->getFunctionName(cat, fun, comment);
331 if (fun.isEmpty()) {
332 QMessageBox::critical(this, "Mantid - Error", "The function name is empty");
333 return;
334 }
335 // check if the category already exists
336 QList<QListWidgetItem *> items = m_uiForm.lstCategory->findItems(cat, Qt::MatchExactly);
337 if (!items.isEmpty()) { // check if a function with this name already exists
338 const QSet<QString> functions = functionNames(cat);
339 QSet<QString>::const_iterator found = functions.find(fun);
340 if (found != functions.end() &&
341 QMessageBox::question(this, "Mantid",
342 "A function with name " + fun + " already exists in category " + cat +
343 ".\n"
344 "Would you like to replace it?",
345 QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
346 return;
347 }
348 }
349 QString expr = m_uiForm.teUserFunction->toPlainText();
350 setFunction(cat, fun, expr, comment);
352 } // QDialog::Accepted
353 saveToFile();
354}
355
357 QFile funFile(QString::fromStdString(Mantid::Kernel::ConfigService::Instance().getUserPropertiesDir()) +
358 "Mantid.user.functions");
359 if (funFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
360 QMap<QString, QString>::const_iterator it = m_funs.begin();
361 for (; it != m_funs.end(); ++it) {
362 QTextStream out(&funFile);
363 QStringList cn = it.key().split('.');
364 if (cn[0] != "Base" && cn[0] != "Built-in") {
365 out << it.key() << "=" << it.value() << '\n';
366 }
367 }
368 }
369}
370
375 QString cat = m_uiForm.lstCategory->currentItem()->text();
376 if (isBuiltin(cat) || (m_uiForm.lstFunction->currentItem() == nullptr)) {
377 return;
378 }
379
380 QString fun = m_uiForm.lstFunction->currentItem()->text();
381 if (QMessageBox::question(this, "Mantid", "Are you sure you want to remove function " + fun + "?",
382 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
383 QString fun_key = cat + "." + fun;
384 QMap<QString, QString>::iterator it = m_funs.find(fun_key);
385 if (it != m_funs.end()) {
386 m_funs.erase(it);
387 it = m_funs.find(fun_key + ".comment");
388 if (it != m_funs.end()) {
389 m_funs.erase(it);
390 }
391 }
392 }
393 selectCategory(cat);
394 saveToFile();
395}
396
398 QStringList out;
399 for (int i = 0; i < m_uiForm.lstCategory->count(); ++i) {
400 out << m_uiForm.lstCategory->item(i)->text();
401 }
402 return out;
403}
404
405bool UserFunctionDialog::eventFilter(QObject *obj, QEvent *ev) {
406 if (ev->type() == QEvent::KeyPress) {
407 auto *keyEvent = static_cast<QKeyEvent *>(ev);
408 if (keyEvent->key() == Qt::Key_Return) {
409 return true;
410 }
411 }
412
413 // standard event processing
414 return QObject::eventFilter(obj, ev);
415}
416
425QString UserFunctionDialog::getFunction(const QString &cat, const QString &fun) const {
426 if (cat.isEmpty() || fun.isEmpty())
427 return "";
428 QMap<QString, QString>::const_iterator it = m_funs.find(cat + "." + fun);
429 if (it != m_funs.end())
430 return it.value();
431 return "";
432}
433
441QString UserFunctionDialog::getComment(const QString &cat, const QString &fun) const {
442 if (cat.isEmpty() || fun.isEmpty())
443 return "";
444 QMap<QString, QString>::const_iterator it = m_funs.find(cat + "." + fun + ".comment");
445 if (it != m_funs.end())
446 return it.value();
447 return "";
448}
449
459void UserFunctionDialog::setFunction(const QString &cat, const QString &fun, const QString &expr,
460 const QString &comment) {
461 if (cat.isEmpty() || fun.isEmpty() || expr.isEmpty())
462 return;
463 // if (cat == "Base" || cat == "Built-in") return;
464 QString fun_key = cat + "." + fun;
465 m_funs[fun_key] = expr;
466 QString cmnt_key = fun_key + ".comment";
467 if (!comment.isEmpty()) {
468 m_funs[cmnt_key] = comment;
469 } else {
470 QMap<QString, QString>::iterator it = m_funs.find(cmnt_key);
471 if (it != m_funs.end()) {
472 m_funs.erase(it);
473 }
474 }
475}
476
480bool UserFunctionDialog::isBuiltin(const QString &cat) const { return cat == "Base" || cat == "Built-in"; }
481
485void UserFunctionDialog::helpClicked() { HelpWindow::showPage(QUrl("workbench/userfunctiondialog.html")); }
486
492InputFunctionNameDialog::InputFunctionNameDialog(QWidget *parent, const QString &category) : QDialog(parent) {
493 auto *layout = new QVBoxLayout();
494 layout->addWidget(new QLabel("Enter new or select a category"));
495 QStringList cats = (static_cast<UserFunctionDialog *>(parent))->categories();
496 cats.removeOne("Base");
497 cats.removeOne("Built-in");
498 m_category = new QComboBox();
499 m_category->addItems(cats);
500 m_category->setEditable(true);
501 int index = m_category->findText(category);
502 if (index >= 0) {
503 m_category->setCurrentIndex(index);
504 }
505 layout->addWidget(m_category);
506 connect(m_category, SIGNAL(currentIndexChanged(const QString &)), parent, SLOT(selectCategory(const QString &)));
507 layout->addWidget(new QLabel("Enter a name for the new function"));
508 m_name = new QLineEdit();
509 layout->addWidget(m_name);
510 layout->addWidget(new QLabel("Enter a comment"));
511 m_comment = new QTextEdit();
512 layout->addWidget(m_comment);
513
514 auto *buttons = new QDialogButtonBox();
515 buttons->addButton("OK", QDialogButtonBox::AcceptRole);
516 buttons->addButton("Cancel", QDialogButtonBox::RejectRole);
517 buttons->setCenterButtons(true);
518 connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
519 connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
520 layout->addWidget(buttons);
521 setLayout(layout);
522}
523
530void InputFunctionNameDialog::getFunctionName(QString &category, QString &name, QString &comment) {
531 category = m_category->currentText();
532 name = m_name->text();
533 comment = m_comment->toPlainText();
534}
std::string name
Definition Run.cpp:60
double value
The value of the point.
Definition FitMW.cpp:51
std::map< DeltaEMode::Type, std::string > index
double obj
the value of the quadratic function
static void showPage(const std::string &url=std::string())
A dialog to enter a category and function name for a new function for saving.
InputFunctionNameDialog(QWidget *parent, const QString &category)
Constructor.
void getFunctionName(QString &category, QString &name, QString &comment)
Return the entered category and function name and comment.
A dialog for renaming parameters for a user function.
std::vector< std::string > setOutput() const
Output the new names to a vector.
A dialog for construction a user fitting function from existing components.
bool isBuiltin(const QString &cat) const
Checks if a category is a buil-in one and cannot be changed.
void checkParameters(QString &expr)
Check an expression for name clashes with user function.
void saveFunction()
Save the constructed function for future use.
QSet< QString > functionNames(const QString &cat) const
Returns function names in category cat.
void addExpression()
Add selected expression to the user function.
QString getComment(const QString &cat, const QString &fun) const
Get the comment for saved function in category cat with name fun.
QMap< QString, QString > m_funs
Container for prerecorded functions: key = category.name, value = formula Records with key = category...
void helpClicked()
Open the help wiki page in the web browser.
QString getCurrentCategory() const
Get the name of currently selected category.
bool eventFilter(QObject *obj, QEvent *ev) override
void loadFunctions()
Load saved functions form Mantid(.user).properties file.
Ui::UserFunctionDialog m_uiForm
User interface elements.
void setFunction(const QString &cat, const QString &fun, const QString &expr, const QString &comment="")
Set an expression to a new function in category cat and with name fun.
void updateFunction()
Updates the parameter list.
void selectCategory(const QString &cat)
Make a category current.
void removeCurrentFunction()
Remove the current function.
~UserFunctionDialog() override
Write saved functions in the destructor.
QString getFunction(const QString &cat, const QString &fun) const
Get the expression for saved function in category cat with name fun.
UserFunctionDialog(QWidget *parent=nullptr, const QString &formula="")
void updateCategories()
Update the GUI element displaying categories.
void selectFunction(const QString &fun)
Make a function current.
QSet< QString > categoryNames() const
Returns a list of category names.
This class represents an expression made up of names, binary operators and brackets.
Definition Expression.h:36
std::unordered_set< std::string > getVariables() const
Return a list of all variable names in this expression.
void parse(const std::string &str)
Parse a string and create an expression.
void renameAll(const std::string &oldName, const std::string &newName)
Rename all variables with a given name.
std::string str() const
Returns this expression as a string.