Mantid
Loading...
Searching...
No Matches
PropertyWidget.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 +
11#include "MantidKernel/System.h"
12
13#include <cfloat>
14#include <climits>
15#include <cmath>
16
17#include <algorithm>
18#include <sstream>
19
20#include <QLineEdit>
21
22using namespace Mantid::Kernel;
23using namespace Mantid::API;
25
26namespace // anonymous
27{
39double stringToRoundedNumber(const std::string &s) {
40 // Using std::istringstream is a nice way to do string-to-double conversion
41 // in this situation as it rounds numbers for us at the same time.
42 // Unfortunately,
43 // commas seem to confuse it ("0,0,0" is converted to "0" without any
44 // warning).
45 const bool containsComma = s.find(",") != std::string::npos;
46 if (containsComma)
47 throw std::runtime_error("string contains a comma");
48
49 return std::stod(s);
50}
51
60bool isValidPropertyValue(Mantid::Kernel::Property *prop, const std::string &value) {
61 const auto guineaPig = std::shared_ptr<Property>(prop->clone());
62 return guineaPig->setValue(value).empty();
63}
64
75bool isEmptyNumMacro(const std::string &value, const double value_d) {
76 using namespace Mantid;
77
78 // Catch instances of Python's "sys.maxint" which otherwise seem to fall
79 // through our net.
80 if (value == "2.14748e+09")
81 return true;
82
83 static const std::vector<double> EMPTY_NUM_MACROS = {EMPTY_DBL(),
84 -DBL_MAX,
85 DBL_MAX,
86 static_cast<double>(EMPTY_INT()),
87 static_cast<double>(EMPTY_LONG()),
88 static_cast<double>(-INT_MAX),
89 static_cast<double>(-LONG_MAX)};
90
91 return std::find(EMPTY_NUM_MACROS.begin(), EMPTY_NUM_MACROS.end(), value_d) != EMPTY_NUM_MACROS.end();
92}
93
101bool isOptionalProperty(Mantid::Kernel::Property *prop) {
102 return isValidPropertyValue(prop, "") || isValidPropertyValue(prop, prop->getDefault());
103}
104
115std::string createFieldPlaceholderText(Mantid::Kernel::Property *prop) {
116 const std::string defaultValue = prop->getDefault();
117 if (defaultValue.empty())
118 return "";
119
120 if (!isValidPropertyValue(prop, defaultValue))
121 return "";
122
123 // It seems likely that any instance of "-0" or "-0.0" should be replaced with
124 // an appropriate
125 // EMPTY_* macro, but for now just display them as they appear in the Wiki.
126 if (defaultValue == "-0" || defaultValue == "-0.0")
127 return "0";
128
129 // If it can't be converted to a double, there is no reason to give
130 // it special handling
131 double roundedNumber;
132 try {
133 roundedNumber = stringToRoundedNumber(defaultValue);
134 } catch (std::exception &) {
135 return defaultValue;
136 }
137
138 if (isEmptyNumMacro(prop->getDefault(), roundedNumber))
139 return "";
140
141 // We'd like to round off any instances of "2.7999999999999998",
142 // "0.050000000000000003", or similar, but we want to keep the
143 // decimal point in values like "0.0" or "1.0" since they can be a
144 // visual clue that a double is expected.
145 static const std::size_t STRING_ROUNDING_LENGTH = 15;
146 if (defaultValue.length() >= STRING_ROUNDING_LENGTH) {
147 std::stringstream roundedValue;
148 roundedValue << roundedNumber;
149 return roundedValue.str();
150 }
151
152 return defaultValue;
153}
154} // anonymous namespace
155
156namespace MantidQt::API {
162ClickableLabel::ClickableLabel(QWidget *parent) : QLabel(parent) {}
167
173void ClickableLabel::mousePressEvent(QMouseEvent *event) {
174 UNUSED_ARG(event);
175 emit clicked();
176}
177
180PropertyWidget::PropertyWidget(Mantid::Kernel::Property *prop, QWidget *parent, QGridLayout *layout, int row)
181 : QWidget(parent), m_prop(prop), m_gridLayout(layout), m_parent(nullptr), m_row(row), // m_info(NULL),
182 m_doc(), m_replaceWSButton(nullptr), m_widgets(), m_error(), m_isOutputWsProp(false), m_previousValue(),
183 m_enteredValue(), m_icons(), m_useHistory(true) {
184 if (!prop)
185 throw std::runtime_error("NULL Property passed to the PropertyWidget constructor.");
186 setObjectName(QString::fromStdString(prop->name()));
187
188 if (!m_gridLayout) {
189 // Create a LOCAL grid layout
190 m_gridLayout = new QGridLayout(this);
191
192 m_gridLayout->setSpacing(5);
193 this->setLayout(m_gridLayout);
194 m_row = 0;
195 m_parent = this;
196 } else {
197 // Use the parent of the provided QGridLayout when adding widgets
198 m_parent = parent;
199 // HACK - In this mode a property widget is not a true self-contained
200 // widget: it has no children
201 // of its own. By default, when added to a parent widget, it will be
202 // drawn invisble
203 // at the top left of the parent widget and obscure mouse clicks etc.
204 // The hack fix is to lower it down the visible stack.
205 this->lower();
206 }
207
208 auto *infoWidget = new QWidget();
209 infoWidget->setLayout(new QHBoxLayout(this));
210 infoWidget->layout()->setSpacing(1);
211 infoWidget->layout()->setContentsMargins(0, 0, 0, 0);
212 m_gridLayout->addWidget(infoWidget, m_row, 4);
213
214 QMap<Info, QPair<QString, QString>> pathsAndToolTips;
215 pathsAndToolTips[RESTORE] =
216 QPair<QString, QString>(":/history.png", "This property had a previously-entered value. Click "
217 "to toggle it off and on.");
218 pathsAndToolTips[REPLACE] =
219 QPair<QString, QString>(":/replace.png", "A workspace with this name already exists and so will be overwritten.");
220 pathsAndToolTips[INVALID] = QPair<QString, QString>(":/invalid.png", "");
221
222 std::vector<Info> labelOrder = {RESTORE, REPLACE, INVALID};
223
224 for (const Info &info : labelOrder) {
225 const QString iconPath = pathsAndToolTips[info].first;
226 const QString toolTip = pathsAndToolTips[info].second;
227
228 auto icon = new ClickableLabel(this);
229 icon->setPixmap(QPixmap(iconPath).scaledToHeight(15));
230 icon->setVisible(false);
231 icon->setToolTip(toolTip);
232
233 infoWidget->layout()->addWidget(icon);
234 m_icons[info] = icon;
235 }
236
237 connect(m_icons[RESTORE], SIGNAL(clicked()), this, SLOT(toggleUseHistory()));
238
240 m_doc = QString::fromStdString(prop->documentation());
241
242 if (!isOptionalProperty(prop)) {
243 if (!m_doc.isEmpty())
244 m_doc += ".\n\n";
245 m_doc += "This property is required.";
246 }
247
248 if (prop->direction() == Direction::Output && dynamic_cast<IWorkspaceProperty *>(prop))
249 m_isOutputWsProp = true;
250}
251
255
261void PropertyWidget::setValue(const QString &value) {
265}
266
272void PropertyWidget::setPreviousValue(const QString &previousValue) {
273 m_previousValue = previousValue;
275
276 // Handle input workspace options that have been set to a workspace
277 // previously, but where the workspace no longer exists.
278 if (getValue() != previousValue) {
279 m_previousValue = "";
281 }
282
283 // Once we've made the history icon visible, it will stay that way for
284 // the lifetime of the property widget.
285 if (m_previousValue.toStdString() != m_prop->getDefault() && !m_previousValue.isEmpty())
286 m_icons[RESTORE]->setVisible(true);
287}
288
296 QString userError(error);
297 if (userError.isEmpty()) {
298 // Show "*" icon if the value is invalid for this property.
299 QString value = this->getValue().trimmed();
300 // Use the default if empty
301 if (value.isEmpty())
302 value = QString::fromStdString(m_prop->getDefault());
303
304 try {
305 userError = QString::fromStdString(m_prop->setValue(value.toStdString()));
306 } catch (std::exception &err_details) {
307 userError = QString::fromLatin1(err_details.what());
308 }
309 }
310 this->setError(userError.trimmed());
311
312 m_icons[INVALID]->setVisible(!m_error.isEmpty());
313 m_icons[INVALID]->setToolTip(m_error);
314 // Show "!" icon if a workspace would be overwritten.
315 if (m_isOutputWsProp) {
316 const bool wsExists = Mantid::API::AnalysisDataService::Instance().doesExist(getValue().toStdString());
317 m_icons[REPLACE]->setVisible(wsExists);
318 }
319}
320
322void PropertyWidget::replaceWSButtonClicked() { emit replaceWorkspaceName(QString::fromStdString(m_prop->name())); }
323
328 // This will be caught by the GenericDialog.
329 emit valueChanged(QString::fromStdString(m_prop->name()));
330}
331
338 if (getValue() != m_previousValue)
342}
343
349 if (m_useHistory)
351 else
353}
354
362 if (m_useHistory != useHistory) {
363 m_useHistory = useHistory;
364 const QString iconPath = useHistory ? ":/history.png" : ":/history_off.png";
365 m_icons[RESTORE]->setPixmap(QPixmap(iconPath).scaledToHeight(15));
366 }
367}
368
375 // Don't re-create it if it already exists
377 return;
378
379 auto *wsProp = dynamic_cast<IWorkspaceProperty *>(m_prop);
380 if (wsProp && (m_prop->direction() == Direction::Output)) {
381 m_replaceWSButton = new QPushButton(QIcon(":/data_replace.png"), "", m_parent);
382 // MG: There is no way with the QIcon class to actually ask what size it is
383 // so I had to hard
384 // code this number here to get it to a sensible size
385 m_replaceWSButton->setMaximumWidth(32);
386 // m_wsbtn_tracker[btn ] = 1;
387 m_replaceWSButton->setToolTip("Replace input workspace");
388 connect(m_replaceWSButton, SIGNAL(clicked()), this, SLOT(replaceWSButtonClicked()));
389 connect(m_replaceWSButton, SIGNAL(clicked()), this, SLOT(valueChangedSlot()));
390 m_widgets.push_back(m_replaceWSButton);
391 // Place in the grid on column 2.
392 m_gridLayout->addWidget(m_replaceWSButton, m_row, 2);
393 m_replaceWSButton->setVisible(true);
394 }
395}
396
401void PropertyWidget::setError(const QString &error) { m_error = error.trimmed(); }
402
406 for (auto &widget : m_widgets)
407 widget->setEnabled(val);
408 QWidget::setEnabled(val);
409}
410
414 for (auto &widget : m_widgets)
415 widget->setVisible(val);
416 QWidget::setVisible(val);
417}
418
428 if (!isOptionalProperty(prop)) {
429 auto font = label->font();
430 font.setBold(true);
431 label->setFont(font);
432 }
433}
434
444 field->setPlaceholderText(QString::fromStdString(createFieldPlaceholderText(prop)));
445}
446
447} // namespace MantidQt::API
double value
The value of the point.
Definition: FitMW.cpp:51
double error
Definition: IndexPeaks.cpp:133
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition: System.h:64
double lower
lower and upper bounds on the multiplier, if known
A small extension to QLabel, so that it emits a signal when clicked.
~ClickableLabel() override
Destructor.
void mousePressEvent(QMouseEvent *event) override
Catches the mouse press event and emits the signal.
ClickableLabel(QWidget *parent)
Constructor.
void clicked()
Signal emitted when a user clicks the label.
void setEnabled(bool val)
Sets all widgets contained within to Enabled.
void setValue(const QString &value)
Set the value of the property given into the GUI state.
virtual QString getValue() const =0
Return the value of the property given the GUI state.
void setUseHistoryIcon(bool useHistory)
Sets the history on/off icons.
PropertyWidget(Mantid::Kernel::Property *prop, QWidget *parent=nullptr, QGridLayout *layout=nullptr, int row=-1)
Constructor.
QString m_enteredValue
Stored the last non-previously-entered value entered entered by the user.
QGridLayout * m_gridLayout
Grid layout of the dialog to which we are adding widgets.
static void setFieldPlaceholderText(Mantid::Kernel::Property *prop, QLineEdit *field)
Set the placeholder text of the given field based on the default value of the given property.
void addReplaceWSButton()
Create and show the "Replace WS" button.
static void setLabelFont(Mantid::Kernel::Property *prop, QWidget *label)
Set the font of the given label based on the optional/required status of the given property.
QString m_doc
Documentation string (tooltip)
QVector< QWidget * > m_widgets
All contained widgets.
QPushButton * m_replaceWSButton
Button to "replace input workspace".
virtual void setValueImpl(const QString &value)=0
void replaceWorkspaceName(const QString &propName)
Signal is emitted whenever someone clicks the replace WS button.
void setError(const QString &error)
Externally set an error string to display in the validator.
void toggleUseHistory()
Toggle whether or not to use the previously-entered value.
void replaceWSButtonClicked()
Deal with the "replace workspace" button being clicked.
QMap< Info, ClickableLabel * > m_icons
Allow icon access by Info enum.
QString m_previousValue
Stores the previously entered value when this dialog was last open.
bool m_isOutputWsProp
Whether or not the property is an output workspace.
QWidget * m_parent
Parent widget to add sub-widgets to.
bool m_useHistory
History on/off flag.
void userEditedProperty()
To be called when a user edits a property, as opposed to one being set programmatically.
~PropertyWidget() override
Destructor.
int m_row
If using the GridLayout, this is the row where the widget was inserted.
void valueChangedSlot()
Emits a signal that the value of the property was changed.
void valueChanged(const QString &propName)
Signal is emitted whenever the value (as entered by the user) in the GUI changes.
QString m_error
Error message received when trying to set the value.
void setVisible(bool val) override
Sets all widgets contained within to Visible.
void setPreviousValue(const QString &previousValue)
Set this widget's previously-entered value.
void updateIconVisibility(const QString &error="")
Update which icons should be shown.
Mantid::Kernel::Property * m_prop
Property being looked at. This is NOT owned by the widget.
An interface that is implemented by WorkspaceProperty.
Base class for properties.
Definition: Property.h:94
unsigned int direction() const
returns the direction of the property
Definition: Property.h:172
const std::string & documentation() const
Get the property's documentation string.
Definition: Property.cpp:65
virtual std::string setValue(const std::string &)=0
Set the value of the property via a string.
virtual Property * clone() const =0
'Virtual copy constructor'
const std::string & name() const
Get the property's name.
Definition: Property.cpp:60
virtual std::string getDefault() const =0
Get the default value for the property which is the value the property was initialised with.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Helper class which provides the Collimation Length for SANS instruments.
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition: EmptyValues.h:25
constexpr long EMPTY_LONG() noexcept
Returns what we consider an "empty" long within a property.
Definition: EmptyValues.h:31
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition: EmptyValues.h:43
@ Output
An output workspace.
Definition: Property.h:54