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
25using 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)", "Sqare 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) { m_uiForm.lstCategory->addItem(cat); }
112 // try to restore current item selection
113 auto items = m_uiForm.lstCategory->findItems(currentCategory, Qt::MatchExactly);
114 if (!items.isEmpty()) {
115 m_uiForm.lstCategory->setCurrentItem(items[0]);
116 }
117}
118
123void UserFunctionDialog::selectCategory(const QString &cat) {
124 QSet<QString> funs = functionNames(cat);
125 m_uiForm.lstFunction->clear();
126 foreach (QString fun, funs) {
127 QString value = getFunction(cat, fun);
128 if (!value.isEmpty()) {
129 m_uiForm.lstFunction->addItem(fun);
130 }
131 }
132 if (m_uiForm.lstFunction->count() > 0) {
133 m_uiForm.lstFunction->sortItems();
134 m_uiForm.lstFunction->setCurrentRow(0);
135 } else {
136 m_uiForm.teExpression->clear();
137 }
138 m_uiForm.btnRemove->setEnabled(!isBuiltin(cat));
139}
140
145void UserFunctionDialog::selectFunction(const QString &fun) {
146 if (fun.isEmpty()) {
147 return;
148 }
149 QString cat = m_uiForm.lstCategory->currentItem()->text();
150 m_uiForm.teExpression->clear();
151
152 QString value = getFunction(cat, fun);
153 QString comment = getComment(cat, fun);
154 if (!comment.isEmpty()) {
155 value += "\n\n" + comment;
156 }
157
158 m_uiForm.teExpression->setText(value);
159}
160
165 QString expr = m_uiForm.teExpression->toPlainText();
166 int iBr = expr.indexOf('\n');
167 if (iBr > 0) {
168 expr.remove(iBr, expr.size());
169 }
170
171 checkParameters(expr);
172
173 if (expr.isEmpty())
174 return;
175
176 QRect rect = m_uiForm.teUserFunction->cursorRect();
177 QTextCursor cursor = m_uiForm.teUserFunction->cursorForPosition(rect.topLeft());
178 if (cursor.position() > 0) {
179 expr.prepend('+');
180 }
181 cursor.insertText(expr);
182
183 // updateFunction();
184}
185
191 if (expr.isEmpty())
192 return;
193 QString fun = m_uiForm.teUserFunction->toPlainText();
194 if (fun.isEmpty())
195 return;
196
197 // collect parameter names in sets vars1 and vars2
200 try {
201 e1.parse(fun.toStdString());
202 e2.parse(expr.toStdString());
203 } catch (...) {
204 return;
205 }
206 auto vars1 = e1.getVariables();
207 auto vars2 = e2.getVariables();
208 vars1.erase("x");
209 vars2.erase("x");
210
211 // combine all names frm the two sets
212 std::vector<std::string> all(vars1.size() + vars2.size(), "");
213 std::set_union(vars1.begin(), vars1.end(), vars2.begin(), vars2.end(), all.begin());
214 std::vector<std::string>::iterator it = std::find(all.begin(), all.end(), "");
215 if (it != all.end()) {
216 all.erase(it, all.end());
217 }
218
219 // compare variable names and collect common names
220 std::vector<std::string> common(std::min<size_t>(vars1.size(), vars2.size()), "");
221 std::set_intersection(vars1.begin(), vars1.end(), vars2.begin(), vars2.end(), common.begin());
222 it = std::find(common.begin(), common.end(), "");
223 if (it != common.end()) {
224 common.erase(it, common.end());
225 }
226
227 // ask the user to rename the common names
228 if (!common.empty()) {
229 RenameParDialog dlg(all, common);
230 if (dlg.exec() == QDialog::Accepted) {
231 auto vars_new = dlg.setOutput();
232 std::vector<std::string>::const_iterator v_old = common.begin();
233 std::vector<std::string>::const_iterator v_new = vars_new.begin();
234 for (; v_old != common.end(); ++v_old, ++v_new) {
235 e2.renameAll(*v_old, *v_new);
236 expr = QString::fromStdString(e2.str());
237 }
238 } else {
239 expr = "";
240 }
241 }
242}
243
248 QString fun = m_uiForm.teUserFunction->toPlainText();
250 try {
251 e.parse(fun.toStdString());
252 } catch (...) { // the formula could be being edited manually
253 m_uiForm.leParams->setText("");
254 return;
255 }
256 auto vars = e.getVariables();
257 vars.erase("x");
258 QString params;
259 for (auto it = vars.begin(); it != vars.end(); ++it) {
260 if (it != vars.begin()) {
261 params += ",";
262 }
263 params += QString::fromStdString(*it);
264 }
265 m_uiForm.leParams->setText(params);
266}
267
272 QSet<QString> out;
273 QMap<QString, QString>::const_iterator it = m_funs.begin();
274 for (; it != m_funs.end(); ++it) {
275 QStringList cn = it.key().split('.');
276 out.insert(cn[0]);
277 }
278 return out;
279}
280
286QSet<QString> UserFunctionDialog::functionNames(const QString &cat) const {
287 QSet<QString> out;
288 QMap<QString, QString>::const_iterator it = m_funs.begin();
289 for (; it != m_funs.end(); ++it) {
290 QStringList cn = it.key().split('.');
291 if (cn[0] == cat) {
292 out.insert(cn[1]);
293 }
294 }
295 return out;
296}
297
304 QString cur_category;
305 QListWidgetItem *currentCategoryItem = m_uiForm.lstCategory->currentItem();
306 if (currentCategoryItem) {
307 cur_category = m_uiForm.lstCategory->currentItem()->text();
308 }
309 return cur_category;
310}
311
316 // select one of user-defined categories
317 QString cur_category = getCurrentCategory();
318
319 if (cur_category == "Base" || cur_category == "Built-in") {
320 cur_category = "";
321 }
322
323 auto *dlg = new InputFunctionNameDialog(this, cur_category);
324 if (dlg->exec() == QDialog::Accepted) {
325 QString cat;
326 QString fun;
327 QString comment;
328 dlg->getFunctionName(cat, fun, comment);
329 if (fun.isEmpty()) {
330 QMessageBox::critical(this, "Mantid - Error", "The function name is empty");
331 return;
332 }
333 // check if the category already exists
334 QList<QListWidgetItem *> items = m_uiForm.lstCategory->findItems(cat, Qt::MatchExactly);
335 if (!items.isEmpty()) { // check if a function with this name already exists
336 const QSet<QString> functions = functionNames(cat);
337 QSet<QString>::const_iterator found = functions.find(fun);
338 if (found != functions.end() &&
339 QMessageBox::question(this, "Mantid",
340 "A function with name " + fun + " already exists in category " + cat +
341 ".\n"
342 "Would you like to replace it?",
343 QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
344 return;
345 }
346 }
347 QString expr = m_uiForm.teUserFunction->toPlainText();
348 setFunction(cat, fun, expr, comment);
350 } // QDialog::Accepted
351 saveToFile();
352}
353
355 QFile funFile(QString::fromStdString(Mantid::Kernel::ConfigService::Instance().getUserPropertiesDir()) +
356 "Mantid.user.functions");
357 if (funFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
358 QMap<QString, QString>::const_iterator it = m_funs.begin();
359 for (; it != m_funs.end(); ++it) {
360 QTextStream out(&funFile);
361 QStringList cn = it.key().split('.');
362 if (cn[0] != "Base" && cn[0] != "Built-in") {
363 out << it.key() << "=" << it.value() << '\n';
364 }
365 }
366 }
367}
368
373 QString cat = m_uiForm.lstCategory->currentItem()->text();
374 if (isBuiltin(cat) || (m_uiForm.lstFunction->currentItem() == nullptr)) {
375 return;
376 }
377
378 QString fun = m_uiForm.lstFunction->currentItem()->text();
379 if (QMessageBox::question(this, "Mantid", "Are you sure you want to remove function " + fun + "?",
380 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
381 QString fun_key = cat + "." + fun;
382 QMap<QString, QString>::iterator it = m_funs.find(fun_key);
383 if (it != m_funs.end()) {
384 m_funs.erase(it);
385 it = m_funs.find(fun_key + ".comment");
386 if (it != m_funs.end()) {
387 m_funs.erase(it);
388 }
389 }
390 }
391 selectCategory(cat);
392 saveToFile();
393}
394
396 QStringList out;
397 for (int i = 0; i < m_uiForm.lstCategory->count(); ++i) {
398 out << m_uiForm.lstCategory->item(i)->text();
399 }
400 return out;
401}
402
403bool UserFunctionDialog::eventFilter(QObject *obj, QEvent *ev) {
404 if (ev->type() == QEvent::KeyPress) {
405 auto *keyEvent = static_cast<QKeyEvent *>(ev);
406 if (keyEvent->key() == Qt::Key_Return) {
407 return true;
408 }
409 }
410
411 // standard event processing
412 return QObject::eventFilter(obj, ev);
413}
414
423QString UserFunctionDialog::getFunction(const QString &cat, const QString &fun) const {
424 if (cat.isEmpty() || fun.isEmpty())
425 return "";
426 QMap<QString, QString>::const_iterator it = m_funs.find(cat + "." + fun);
427 if (it != m_funs.end())
428 return it.value();
429 return "";
430}
431
439QString UserFunctionDialog::getComment(const QString &cat, const QString &fun) const {
440 if (cat.isEmpty() || fun.isEmpty())
441 return "";
442 QMap<QString, QString>::const_iterator it = m_funs.find(cat + "." + fun + ".comment");
443 if (it != m_funs.end())
444 return it.value();
445 return "";
446}
447
457void UserFunctionDialog::setFunction(const QString &cat, const QString &fun, const QString &expr,
458 const QString &comment) {
459 if (cat.isEmpty() || fun.isEmpty() || expr.isEmpty())
460 return;
461 // if (cat == "Base" || cat == "Built-in") return;
462 QString fun_key = cat + "." + fun;
463 m_funs[fun_key] = expr;
464 QString cmnt_key = fun_key + ".comment";
465 if (!comment.isEmpty()) {
466 m_funs[cmnt_key] = comment;
467 } else {
468 QMap<QString, QString>::iterator it = m_funs.find(cmnt_key);
469 if (it != m_funs.end()) {
470 m_funs.erase(it);
471 }
472 }
473}
474
478bool UserFunctionDialog::isBuiltin(const QString &cat) const { return cat == "Base" || cat == "Built-in"; }
479
484 MantidDesktopServices::openUrl(QUrl("http://www.mantidproject.org/MantidPlot:_User_Function_Dialog"));
485}
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 = ((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}
double value
The value of the point.
Definition: FitMW.cpp:51
std::map< DeltaEMode::Type, std::string > index
Definition: DeltaEMode.cpp:19
double obj
the value of the quadratic function
This class provides a wrapper around QDesktopServices to fix a bug in opening URLs in firefox when tc...
static bool openUrl(const QUrl &url)
Opens a url in the appropriate web browser.
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.
Definition: Expression.cpp:524
void parse(const std::string &str)
Parse a string and create an expression.
Definition: Expression.cpp:159
void renameAll(const std::string &oldName, const std::string &newName)
Rename all variables with a given name.
Definition: Expression.cpp:549
std::string str() const
Returns this expression as a string.
Definition: Expression.cpp:469
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...